@livetemplate/client 0.12.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/livetemplate-client.browser.js +4 -4
- package/dist/livetemplate-client.browser.js.map +3 -3
- package/dist/tests/upload-handler.test.js +44 -0
- package/dist/tests/upload-handler.test.js.map +1 -1
- package/dist/upload/types.d.ts +1 -0
- package/dist/upload/types.d.ts.map +1 -1
- package/dist/upload/upload-handler.d.ts +22 -1
- package/dist/upload/upload-handler.d.ts.map +1 -1
- package/dist/upload/upload-handler.js +62 -3
- package/dist/upload/upload-handler.js.map +1 -1
- package/package.json +1 -1
|
@@ -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", "../dom/redact.ts", "../utils/lvt-selector.ts", "../utils/legacy-attr.ts", "../dom/event-delegation.ts", "../dom/hash-link.ts", "../dom/link-interceptor.ts", "../dom/observer-manager.ts", "../dom/loading-indicator.ts", "../dom/form-disabler.ts", "../dom/invoker-polyfill.ts", "../dom/scroll-away.ts", "../dom/spy.ts", "../state/tree-renderer.ts", "../state/range-dom-applier.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 handleAreaSelectDirectives,\n handleAutoClickDirectives,\n handleHighlightDirectives,\n handleScrollDirectives,\n handleShadowRootHydration,\n handleToastDirectives,\n handleURLHashDirective,\n teardownAreaSelectForRoot,\n teardownURLHashForRoot,\n teardownAutoClickTimers,\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 { setupHashLink, teardownHashLink, openFromHash, safeMatchesPopoverOpen } from \"./dom/hash-link\";\nimport { setupScrollAway, teardownScrollAway } from \"./dom/scroll-away\";\nimport { setupSpy, teardownSpy } from \"./dom/spy\";\nimport { hydrateRedactedTokens } from \"./dom/redact\";\nimport { TreeRenderer } from \"./state/tree-renderer\";\nimport {\n RangeDomApplier,\n TARGETED_APPLIED_ATTR,\n TARGETED_SKIP_ATTR,\n} from \"./state/range-dom-applier\";\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 UploadStartMessage,\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 rangeDomApplier: RangeDomApplier;\n private nodesAddedThisRender: number = 0;\n private directiveTouchedThisRender: boolean = false;\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 // Visibility-based reconnection: when the browser tab returns from\n // background (iOS app switch, Android task switch), the WebSocket is\n // often silently dead. These fields drive a one-shot reconnect on\n // the visibilitychange event, independent of autoReconnect (which\n // guards against retry loops on persistent network failures).\n // Handlers are stored as instance properties so disconnect() can\n // remove them \u2014 without that, SPAs that build a new client per route\n // accumulate listeners that hold closures over destroyed instances.\n private visibilityHandlerAttached: boolean = false;\n private hiddenAt: number = 0;\n private reconnecting: boolean = false;\n private visibilityHandler: (() => void) | null = null;\n private pageshowHandler: ((e: PageTransitionEvent) => void) | null = null;\n private visibilityReconnectTimer: ReturnType<typeof setTimeout> | 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.rangeDomApplier = new RangeDomApplier({\n logger: this.logger.child(\"RangeDomApplier\"),\n renderItem: (item, idx, statics, sp) =>\n this.treeRenderer.renderRangeItem(item, idx, statics, sp),\n executeLifecycleHook: (el, hook) => this.executeLifecycleHook(el, hook),\n itemLookup: (rangePath, key) => {\n // O(N) linear scan over range.d. For one `u` op per render this is\n // ~50\u00B5s at N=10k \u2014 acceptable. For a render with many u ops on\n // the same range, this becomes O(N\u00D7K); building a Map<key, item>\n // once at apply() start would amortize, but the gain is small\n // (whole `u` op cost is dominated by morphdom on the row anyway).\n // Revisit if profiling shows this on the hot path.\n const range = this.treeRenderer.getTreeState()[rangePath];\n if (!range || !Array.isArray(range.d)) return null;\n const idKey = range.m?.idKey;\n for (const item of range.d) {\n if (!item || typeof item !== \"object\") continue;\n if (item._k === key) return item;\n if (\n idKey &&\n item[idKey] !== undefined &&\n String(item[idKey]) === key\n ) {\n return item;\n }\n }\n return null;\n },\n onNodeAdded: () => {\n this.nodesAddedThisRender++;\n },\n });\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 postMultipartUpload: (formData, signal) =>\n this.postUploadMultipart(formData, signal),\n isConnected: () =>\n !this.useHTTP && this.webSocketManager.getReadyState() === 1,\n postUploadStart: (message, signal) =>\n this.postUploadStartHTTP(message, signal),\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, destinationHref: string) =>\n this.handleNavigationResponse(html, destinationHref),\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 // Per-action loading indicator: opt-in via the\n // `data-lvt-loading-debounce-ms=\"<N>\"` attribute. Read from the\n // wrapper first (server-set), then fall back to `<body>` \u2014\n // because the wrapper is auto-injected by the framework, template\n // authors typically place the attribute on body themselves. The\n // attribute arms a timer on every `lvt:pending` and shows the\n // loading bar if it fires before `lvt:updated` arrives \u2014 used to\n // mask round-trip latency without flashing a bar on instant\n // responses.\n const debounceAttr =\n wrapper.getAttribute(\"data-lvt-loading-debounce-ms\") ??\n document.body?.getAttribute(\"data-lvt-loading-debounce-ms\") ??\n null;\n // Pre-validate as a pure integer string: parseInt is lenient\n // and would accept \"200abc\" as 200, silently masking a typo.\n if (debounceAttr !== null && /^\\d+$/.test(debounceAttr)) {\n const debounceMs = parseInt(debounceAttr, 10);\n if (Number.isFinite(debounceMs) && debounceMs >= 0) {\n // Defer wiring until after the first `lvt:updated`. If we\n // wired it now and `data-lvt-loading=\"true\"` is also set,\n // the initial-connect bar would be up when the first\n // user-initiated `lvt:pending` fires (no debounce armed,\n // since `bar !== null`), and the very next `lvt:updated` \u2014\n // which is the initial payload, not the action response \u2014\n // would decrement the count to zero and consume the\n // action's pending credit. Waiting one render makes the\n // per-action machinery start in steady state.\n wrapper.addEventListener(\n \"lvt:updated\",\n () => {\n client.loadingIndicator.enablePerActionIndicator(debounceMs);\n },\n { once: true, capture: true }\n );\n }\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 // Error envelope (proposal \u00A73 / V14): a distinct wire message\n // { type:\"error\", code, topic? } \u2014 NOT an UpdateResponse (it carries no\n // `tree`). Surface as an `lvt:error` CustomEvent on the wrapper and\n // short-circuit BEFORE the diff path, so analyzeStatics()/updateDOM()\n // never see a treeless payload. The server keeps the socket open after\n // emitting this (livetemplate Phase 4 / V14), so there is no disconnect\n // to handle here.\n //\n // Contract lock \u2014 `type === \"error\"` is the general envelope discriminator:\n // every `{type:\"error\",...}` payload from the server flows through this\n // single branch and surfaces as `lvt:error`. As of livetemplate Phase 4\n // the only `code` emitted is `topic_forbidden`, but the contract is\n // intentionally open-ended: new server-side error codes (e.g. rate-limit,\n // auth) will reuse this same shape and listener \u2014 apps consuming\n // `lvt:error` should switch on `event.detail.code`. Don't narrow this\n // check to a `code === \"topic_forbidden\"` literal: that would silently\n // drop future codes into `updateDOM` as treeless payloads.\n //\n // NOTE: state/form-lifecycle-manager.ts also dispatches an `lvt:error`\n // event \u2014 but on the <form> element with a ResponseMetadata detail. These\n // are two distinct, non-bubbling events disambiguated by target (wrapper\n // vs form) and detail shape; they do not collide. See proposal \u00A76 docs.\n const errorEnvelope = response as unknown as {\n type?: string;\n code?: string;\n topic?: string;\n };\n if (errorEnvelope.type === \"error\") {\n // Always short-circuit a `type:\"error\"` payload before the diff path \u2014\n // it has no `tree`, so analyzeStatics(undefined) would error. A\n // well-formed envelope carries a string `code` (V14 contract); a bare\n // `{type:\"error\"}` without one is malformed and is logged + dropped\n // (rather than dispatched as an `lvt:error` with no detail, which\n // would confuse listeners).\n if (typeof errorEnvelope.code !== \"string\") {\n this.logger.warn(\n \"Malformed error envelope (missing string `code`) \u2014 dropping\",\n errorEnvelope\n );\n return;\n }\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:error\", {\n detail: { code: errorEnvelope.code, topic: errorEnvelope.topic },\n })\n );\n } else {\n // Reachable only if handleWebSocketPayload runs before connect()\n // wires up wrapperElement \u2014 should never happen on the WS-onMessage\n // path, but warn rather than silently swallow so it's visible in\n // production logs.\n this.logger.warn(\n \"lvt:error envelope arrived before wrapperElement was set; dropping\",\n { code: errorEnvelope.code, topic: errorEnvelope.topic }\n );\n }\n return;\n }\n\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 // Re-run after first render \u2014 setupHashLink()'s internal call\n // fired before server content existed in the DOM.\n openFromHash();\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 setupHashLink();\n\n // url-hash directive needs an initial-load arm here (not just in\n // updateDOM's FIRE-ON-CHANGE block) because page load \u2192 no\n // updateDOM call \u2192 directive never arms \u2192 a `#path:L4` URL never\n // dispatches setURLHash. Subsequent state changes route through\n // the FIRE-ON-CHANGE block's call site, which keeps the data-attr\n // mirror in sync after server re-renders.\n //\n // Transport is reliable at this point: `connect()` above was\n // awaited, so either the WebSocket is OPEN (`useHTTP=false`,\n // readyState=1) or the HTTP fallback is wired (`useHTTP=true`).\n // `this.send` covers both paths plus a `readyState!==1 && WS\n // available` fallback that posts via HTTP, so the initial-arm\n // dispatch always reaches the server \u2014 no \"best-effort\" caveat.\n if (this.wrapperElement) {\n handleURLHashDirective(this.wrapperElement, (message) => this.send(message));\n }\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 this.setupVisibilityReconnect();\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.hiddenAt = 0;\n this.reconnecting = false;\n this.teardownVisibilityReconnect();\n this.uploadHandler.revokePreviews();\n this.eventDelegator.teardownDOMEventTriggerDelegation();\n teardownHashLink();\n teardownAutoClickTimers();\n this.loadingIndicator.disablePerActionIndicator();\n if (this.wrapperElement) {\n teardownFxDOMEventTriggers(this.wrapperElement);\n teardownFxLifecycleListeners(this.wrapperElement);\n teardownScrollAway(this.wrapperElement);\n teardownSpy(this.wrapperElement);\n teardownAreaSelectForRoot(this.wrapperElement);\n teardownURLHashForRoot(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.rangeDomApplier.invalidate();\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 private setupVisibilityReconnect(): void {\n if (this.visibilityHandlerAttached || typeof document === \"undefined\") return;\n this.visibilityHandlerAttached = true;\n\n this.visibilityHandler = () => {\n if (document.hidden) {\n // Only track hidden time if WebSocket is currently open.\n // Prevents stale handlers from triggering reconnection on\n // already-disconnected clients (e.g. after cross-handler\n // navigation or explicit disconnect).\n if (!this.useHTTP && this.webSocketManager.getReadyState() === 1) {\n this.hiddenAt = Date.now();\n }\n return;\n }\n if (this.hiddenAt === 0) return;\n const elapsed = Date.now() - this.hiddenAt;\n this.hiddenAt = 0;\n if (elapsed < 3000) return;\n this.scheduleVisibilityReconnect();\n };\n\n // pageshow's persisted=true means the page came back from bfcache \u2014\n // its in-memory connection state is unknown, so reconnect unconditionally\n // (no 3s threshold). visibilitychange handles ordinary tab-switch cases.\n this.pageshowHandler = (event: PageTransitionEvent) => {\n if (event.persisted) {\n this.scheduleVisibilityReconnect();\n }\n };\n\n document.addEventListener(\"visibilitychange\", this.visibilityHandler);\n window.addEventListener(\"pageshow\", this.pageshowHandler);\n }\n\n private teardownVisibilityReconnect(): void {\n if (this.visibilityReconnectTimer !== null) {\n clearTimeout(this.visibilityReconnectTimer);\n this.visibilityReconnectTimer = null;\n }\n if (!this.visibilityHandlerAttached || typeof document === \"undefined\") return;\n if (this.visibilityHandler) {\n document.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n this.visibilityHandler = null;\n }\n if (this.pageshowHandler) {\n window.removeEventListener(\"pageshow\", this.pageshowHandler);\n this.pageshowHandler = null;\n }\n this.visibilityHandlerAttached = false;\n }\n\n private scheduleVisibilityReconnect(): void {\n // 500ms delay lets onclose deliver before we ask for a new socket.\n // Tracked on the instance so teardownVisibilityReconnect() can\n // cancel a pending timer when disconnect() runs mid-window \u2014\n // otherwise a timer queued before disconnect can fire after a\n // subsequent connect on the same instance and trigger an\n // unwanted reconnect.\n //\n // Cancel any in-flight timer first so a rapid\n // visibilitychange+pageshow sequence doesn't leak the earlier\n // setTimeout (the tracked ref would only point at the last one,\n // leaving the first orphaned).\n if (this.visibilityReconnectTimer !== null) {\n clearTimeout(this.visibilityReconnectTimer);\n }\n this.visibilityReconnectTimer = setTimeout(() => {\n this.visibilityReconnectTimer = null;\n // Guard: only reconnect if a WebSocket transport exists. After\n // disconnect(), transport is null (readyState undefined) so we\n // correctly skip intentionally disconnected clients.\n // Note: we intentionally do NOT check readyState !== 1 (OPEN)\n // because iOS 15+ can leave zombie sockets that report OPEN while\n // the underlying TCP connection is dead. Always reconnecting after\n // a long background is cheap (morphdom diffs produce no DOM changes\n // on a healthy connection) and handles the zombie case.\n if (\n this.wrapperElement &&\n !this.useHTTP &&\n !this.reconnecting &&\n this.webSocketManager.getReadyState() !== undefined\n ) {\n this.performVisibilityReconnect();\n }\n }, 500);\n }\n\n private async performVisibilityReconnect(): Promise<void> {\n if (this.reconnecting) return;\n this.reconnecting = true;\n\n try {\n this.logger.info(\"Reconnecting after visibility change\");\n\n this.webSocketManager.disconnect();\n this.ws = null;\n this.resetSessionState();\n\n const result = await this.webSocketManager.connect();\n\n // disconnect() may have run during the await above. It clears the\n // reconnecting flag, so the absence of that flag here is our\n // \"torn down while suspended\" signal \u2014 abort without mutating\n // state. Without this, useHTTP / payload application would land\n // on a client the consumer expects to be inert (or, worse, on a\n // freshly-reconnected client created by a subsequent connect()).\n if (!this.reconnecting) return;\n\n this.useHTTP = !result.usingWebSocket;\n\n if (this.useHTTP) {\n this.ws = null;\n if (result.initialState && this.wrapperElement) {\n this.handleWebSocketPayload(result.initialState);\n }\n }\n\n this.wrapperElement?.dispatchEvent(new Event(\"lvt:reconnected\"));\n } catch (err) {\n this.logger.error(\"Visibility reconnect failed:\", err);\n } finally {\n this.reconnecting = false;\n }\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 * POST a Proxied upload's multipart body to the live URL and apply the tree\n * response. Unlike sendHTTPMultipart this REJECTS on a non-2xx response so the\n * upload handler can surface the failure to the app; it is transport-agnostic\n * (works whether or not the WebSocket is connected).\n */\n private async postUploadMultipart(\n formData: FormData,\n signal?: AbortSignal\n ): Promise<void> {\n const liveUrl = this.getLiveUrl();\n const response = await fetch(liveUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: {\n Accept: \"application/json\",\n // X-Lvt-Upload is a non-safelisted header, so it forces a CORS preflight\n // \u2014 a cross-site page can't silently POST the user's cookies here.\n \"X-Lvt-Upload\": \"proxied\",\n // Do NOT set Content-Type \u2014 the browser sets the multipart boundary.\n },\n body: formData,\n signal,\n });\n\n if (!response.ok) {\n const detail = (await response.text().catch(() => \"\")).trim();\n throw new Error(\n `Proxied upload failed: ${response.status}${detail ? ` \u2014 ${detail}` : \"\"}`\n );\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 }\n\n /**\n * Post an upload_start handshake over HTTP (WebSocket-disabled fallback) and\n * return the parsed UploadStartResponse. The X-Lvt-Upload header tells the\n * server to answer with the handshake response rather than treating the body\n * as a normal action.\n */\n private async postUploadStartHTTP(\n message: UploadStartMessage,\n signal?: AbortSignal\n ): Promise<UploadStartResponse> {\n const response = await fetch(this.getLiveUrl(), {\n method: \"POST\",\n credentials: \"include\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-Lvt-Upload\": \"start\",\n },\n body: JSON.stringify(message),\n signal,\n });\n if (!response.ok) {\n const detail = (await response.text().catch(() => \"\")).trim();\n throw new Error(\n `upload_start handshake failed: ${response.status}${detail ? ` \u2014 ${detail}` : \"\"}`\n );\n }\n return (await response.json()) as UploadStartResponse;\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 /**\n * Compare the set of <link rel=\"stylesheet\"> URLs in the current\n * document's <head> against those in a fetched (parsed) document.\n * Returns true when the two pages declare different stylesheets,\n * which is our signal that swapping the body alone would leave the\n * new content styled by the wrong app's CSS. URLs are compared as\n * absolute (the live doc's via the browser-resolved .href; the\n * fetched doc's via explicit base-URL resolution against the\n * destination, since the parsed Document has no inherent base) so\n * that \"/foo.css\" and \"https://host/foo.css\" don't appear to differ.\n */\n private stylesheetsDiffer(doc: Document, destinationHref: string): boolean {\n const currentLinks = document.querySelectorAll(\n 'link[rel~=\"stylesheet\"][href]'\n );\n const current = new Set<string>();\n currentLinks.forEach((l) => {\n const href = (l as HTMLLinkElement).href;\n if (href) current.add(href);\n });\n\n const fetchedLinks = doc.querySelectorAll('link[rel~=\"stylesheet\"][href]');\n const fetched = new Set<string>();\n fetchedLinks.forEach((l) => {\n const raw = l.getAttribute(\"href\");\n if (!raw) return;\n try {\n fetched.add(new URL(raw, destinationHref).href);\n } catch {\n fetched.add(raw);\n }\n });\n\n if (current.size !== fetched.size) return true;\n let differs = false;\n current.forEach((href) => {\n if (!fetched.has(href)) differs = true;\n });\n return differs;\n }\n\n /**\n * Force a full document navigation to `destinationHref`. Used when\n * an SPA-style body-swap is unsafe (e.g. cross-app navigation). The\n * indirection exists for testability \u2014 jsdom locks the real Location\n * object, so tests spy on this method instead.\n */\n private performFullNavigation(destinationHref: string): void {\n window.location.href = destinationHref;\n }\n\n private handleNavigationResponse(html: string, destinationHref: 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 // Cross-app boundary: the SPA optimization (swap body, keep head)\n // only works when both pages share the same <head> contract. When\n // a user navigates from one deployed app to another that shares\n // the origin (e.g., a docs site reverse-proxying a separately-\n // deployed pattern showcase), the two apps may declare different\n // <link rel=\"stylesheet\"> URLs. Patching only the body would leave\n // the new body styled by the previous app's CSS \u2014 broken layout\n // that \"fixes itself on refresh\" because a real navigation reloads\n // the head too. This guard runs BEFORE either branch (with-wrapper\n // and no-wrapper) below, because both branches end up swapping body\n // content while the head stays unchanged. The destination page may\n // not have a data-lvt-id at all (e.g., a static docs page), so the\n // check cannot live inside the wrapper-found branch.\n if (this.stylesheetsDiffer(doc, destinationHref)) {\n this.logger.info(\n \"Cross-app navigation detected (different <link rel=\\\"stylesheet\\\"> set in fetched HTML); falling back to full navigation\"\n );\n this.performFullNavigation(destinationHref);\n return;\n }\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\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 // Reset per-render counters before applying the update.\n // - nodesAddedThisRender: incremented by morphdom.onNodeAdded and the\n // applier's onNodeAdded callback for i/a/p ops.\n // - directiveTouchedThisRender: set by morphdom.onBeforeElUpdated when\n // it processes an element carrying a directive attribute (lvt-fx:*,\n // lvt-on:*, lvt-el:*) \u2014 covers attribute-only morphs that don't add\n // nodes but do change directive bindings, so the post-render scans\n // still need to wire them.\n // Either signal triggers the wrapper-wide directive scans below.\n this.nodesAddedThisRender = 0;\n this.directiveTouchedThisRender = false;\n\n // Apply update to internal state and get reconstructed HTML.\n // Pass canApplyTargeted so eligible top-level range diff ops mutate\n // treeState in place and are emitted as targetedOps for direct DOM\n // mutation (skipping the full HTML rebuild + morphdom diff for that\n // subtree).\n const result = this.treeRenderer.applyUpdate(update, {\n canApplyTargeted: (rangeStructure, rangePath) => {\n const r = this.rangeDomApplier.canApplyTargeted(\n element,\n rangeStructure,\n rangePath\n );\n return r.ok;\n },\n });\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 DOM changes to apply \u2014 skip morphdom. But the action still\n // completed, so its loading lifecycle must still resolve: fire the\n // form lifecycle (which dispatches lvt:done \u2192 reactive on:done\n // attributes, lvt-form:disable-with restore, form aria-busy). Without\n // this, a no-op action (re-submitting the same value, or any\n // idempotent action that produces an empty render diff) leaves\n // spinners spinning and disabled buttons stuck forever.\n if (meta) {\n this.formLifecycleManager.handleResponse(meta);\n }\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 // Defer the entire morphdom pass while a native datalist dropdown\n // may be showing. Datalist popups are browser-managed overlays with\n // no DOM representation \u2014 ANY mutation on the page (not just to the\n // datalist itself) triggers a reflow that dismisses the popup.\n // Since there is no API to query whether the popup is open, we use\n // document.activeElement being a datalist-connected <input> as the\n // signal. The deferred state is naturally applied on the next server\n // push after the user leaves the input (typically within one scan\n // interval).\n const activeEl = document.activeElement;\n if (\n activeEl instanceof HTMLInputElement &&\n activeEl.list instanceof HTMLDataListElement &&\n !tempWrapper.querySelector(\"[data-lvt-force-update]\")\n ) {\n this.logger.debug(\"[updateDOM] deferred: datalist input focused\");\n this.focusManager.restoreFocusedElement();\n return;\n }\n\n // Build morphdom options once so the applier's `u` op (which morphdoms\n // a single row) uses the same callback set \u2014 focus skip, lvt-ignore,\n // checkbox preservation, lifecycle hooks all stay consistent.\n const morphdomOptions = {\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: any, toEl: any) => {\n // Targeted-apply skip: the live container's children were already\n // mutated directly by RangeDomApplier, and the rebuilt tempWrapper\n // has the container empty + tagged with data-lvt-targeted-skip.\n // Returning false short-circuits the entire subtree update \u2014\n // morphdom skips both the diff walk AND the children-replacement.\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n (toEl as Element).hasAttribute(TARGETED_SKIP_ATTR)\n ) {\n return false;\n }\n\n // Track newly-introduced directive attributes so the post-render\n // scan can wire any new lvt-fx:/lvt-on:/lvt-el: bindings even on\n // renders that wouldn't otherwise trigger a wrapper-wide scan.\n // Only flag when the directive attribute is NEW on toEl (not\n // already present on fromEl) \u2014 otherwise high-frequency `u` ops\n // on rows that ALREADY carry a directive (e.g. Todos rows with\n // `lvt-fx:animate`) would trigger a wrapper-wide scan on every\n // render even though no new binding needs wiring.\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n fromEl.nodeType === Node.ELEMENT_NODE\n ) {\n const toAttrs = (toEl as Element).attributes;\n const fromElement = fromEl as Element;\n for (let i = 0; i < toAttrs.length; i++) {\n const n = toAttrs[i].name;\n if (\n n.length > 4 &&\n n.charCodeAt(0) === 0x6c /* l */ &&\n n.charCodeAt(1) === 0x76 /* v */ &&\n n.charCodeAt(2) === 0x74 /* t */ &&\n n.charCodeAt(3) === 0x2d /* - */ &&\n !fromElement.hasAttribute(n)\n ) {\n this.directiveTouchedThisRender = true;\n break;\n }\n }\n }\n\n // lvt-ignore: morphdom skips this element and its entire subtree.\n // Equivalent to Phoenix LiveView's phx-update=\"ignore\".\n // Checked on fromEl (live DOM) so both server templates and\n // client JS can add/remove it. Use data-lvt-force-update on\n // the server's version to bypass and resume diffing.\n if (\n fromEl.nodeType === Node.ELEMENT_NODE &&\n (fromEl as Element).hasAttribute(\"lvt-ignore\") &&\n !(toEl as Element).hasAttribute(\"data-lvt-force-update\")\n ) {\n return false;\n }\n\n // lvt-ignore-attrs: skip attribute diffing but still diff children.\n // Copies fromEl's missing attributes onto toEl so morphdom keeps\n // them. Server-set attributes still win (toEl value used when\n // both sides have the same attr). Checked on fromEl for\n // consistency with lvt-ignore; use data-lvt-force-update to\n // bypass.\n if (\n fromEl.nodeType === Node.ELEMENT_NODE &&\n (fromEl as Element).hasAttribute(\"lvt-ignore-attrs\") &&\n !(toEl as Element).hasAttribute(\"data-lvt-force-update\") &&\n toEl.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 if (!toElement.hasAttributeNS(attr.namespaceURI, attr.localName)) {\n toElement.setAttributeNS(attr.namespaceURI, attr.name, attr.value);\n }\n }\n }\n\n // Preserve <datalist> elements while their connected input is\n // focused. Native datalist dropdowns are dismissed if the element\n // is touched \u2014 unlike checkbox state, dropdown-open state has no\n // DOM representation and cannot be copied to the new element.\n if (\n fromEl instanceof HTMLDataListElement &&\n !(toEl as Element).hasAttribute('data-lvt-force-update')\n ) {\n const active = document.activeElement;\n if (\n active instanceof HTMLInputElement &&\n active.list === fromEl\n ) {\n return false;\n }\n }\n\n // Copy `open` onto toEl so morphdom's attr sync won't strip it (preserves top-layer state).\n if (\n fromEl instanceof HTMLDialogElement &&\n fromEl.hasAttribute('open') &&\n !(toEl as Element).hasAttribute('data-lvt-force-update')\n ) {\n (toEl as Element).setAttribute('open', '');\n }\n\n // Skip open popovers entirely (top-layer state has no DOM representation).\n if (\n !(toEl as Element).hasAttribute(\"data-lvt-force-update\") &&\n fromEl instanceof HTMLElement &&\n fromEl.hasAttribute(\"popover\") &&\n safeMatchesPopoverOpen(fromEl)\n ) {\n return false;\n }\n\n // Preserve checkbox/radio checked state across morphdom updates.\n // User selection wins by default \u2014 these controls lose focus on\n // click so the focusManager never protects them, and their checked\n // state is user input that must survive scan-loop refreshes. Use\n // data-lvt-force-update to let the server override the user state.\n //\n // Known limitation: force-update on one radio can uncheck a sibling\n // that was already processed earlier in the same morphdom pass, since\n // browser mutual exclusion fires synchronously mid-loop. To safely\n // reset a radio group, send data-lvt-force-update on ALL radios in\n // the group, not just the one being checked.\n if (\n fromEl instanceof HTMLInputElement &&\n toEl instanceof HTMLInputElement &&\n (fromEl.type === \"checkbox\" || fromEl.type === \"radio\")\n ) {\n if (toEl.hasAttribute(\"data-lvt-force-update\")) {\n fromEl.checked = toEl.checked;\n if (fromEl.type === \"checkbox\") {\n fromEl.indeterminate = toEl.indeterminate;\n }\n fromEl.removeAttribute(\"data-lvt-force-update\");\n } else {\n toEl.checked = fromEl.checked;\n // Align the checked attribute with the property so morphdom's\n // attribute diff doesn't add a spurious checked attr to fromEl\n // (which IS in the DOM and would trigger radio mutual exclusion).\n if (fromEl.checked) {\n toEl.setAttribute(\"checked\", \"\");\n } else {\n toEl.removeAttribute(\"checked\");\n }\n if (fromEl.type === \"checkbox\") {\n toEl.indeterminate = fromEl.indeterminate;\n }\n }\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 \u2014 but honour\n // data-lvt-force-update which means the server wants morphdom\n // to process this element or one of its descendants\n // unconditionally (e.g. resetting a checkbox whose checked\n // property differs from the attribute).\n // Note: querySelector is a defensive fallback \u2014 in steady state\n // the attr is stripped after each render, so isEqualNode returns\n // false and normal diffing reaches the descendant. The scan only\n // matters on the first render of a newly inserted subtree.\n if (fromEl.isEqualNode(toEl)) {\n if (\n !toEl.hasAttribute(\"data-lvt-force-update\") &&\n (toEl.children.length === 0 ||\n toEl.querySelector(\"[data-lvt-force-update]\") === null)\n ) {\n return false;\n }\n // Ancestor itself didn't change \u2014 only traversing for a\n // descendant's force-update. Skip the lvt-updated hook.\n return true;\n }\n // Execute lvt-updated lifecycle hook\n this.executeLifecycleHook(fromEl, \"lvt-updated\");\n return true;\n },\n onElUpdated: (el: any) => {\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 // Strip data-lvt-force-update from the live DOM after each\n // render. If the server stops sending it, preservation resumes;\n // if the server keeps including it, each render force-resets.\n if (el instanceof HTMLElement && el.hasAttribute(\"data-lvt-force-update\")) {\n el.removeAttribute(\"data-lvt-force-update\");\n }\n },\n onNodeAdded: (node: any) => {\n // Sync textarea value for newly inserted textarea elements\n if (node instanceof HTMLTextAreaElement) {\n node.value = node.textContent ?? \"\";\n }\n if (node instanceof HTMLElement && node.hasAttribute(\"data-lvt-force-update\")) {\n node.removeAttribute(\"data-lvt-force-update\");\n }\n // Execute lvt-mounted lifecycle hook\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.executeLifecycleHook(node as Element, \"lvt-mounted\");\n this.nodesAddedThisRender++;\n }\n },\n onBeforeNodeDiscarded: (node: any) => {\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 // Apply per-op targeted DOM mutations BEFORE morphdom. The applier\n // mutates the live DOM in place; tempWrapper has corresponding\n // <!--lvt-targeted-skip:path--> placeholders that we now convert to\n // data-lvt-targeted-skip markers on their parent elements so morphdom\n // short-circuits those subtrees.\n //\n // Robustness: if any targeted op fails (apply returns null \u2014 e.g.\n // container couldn't be located, or an op threw), the treeState was\n // updated but the live DOM wasn't, so leaving the placeholder in\n // place would either (a) tell morphdom to skip \u2192 live DOM stays\n // stale, or (b) leave an empty container in tempWrapper \u2192 morphdom\n // would empty the live container. Both are wrong. We re-render the\n // full HTML from treeState (which is authoritative) and let morphdom\n // sync from there.\n if (result.targetedOps && result.targetedOps.length > 0) {\n const successContainers: Element[] = [];\n let anyFailed = false;\n for (const op of result.targetedOps) {\n const container = this.rangeDomApplier.apply(\n element,\n op,\n morphdomOptions\n );\n if (container) {\n container.setAttribute(TARGETED_APPLIED_ATTR, \"\");\n successContainers.push(container);\n } else {\n anyFailed = true;\n }\n }\n\n if (anyFailed) {\n this.logger.warn(\n \"[updateDOM] one or more targeted DOM ops failed; rebuilding tempWrapper from treeState for a full morphdom sync\"\n );\n // Strip success markers \u2014 we're going to do a full diff now.\n for (const c of successContainers) {\n c.removeAttribute(TARGETED_APPLIED_ATTR);\n }\n // Re-render full HTML (no skip placeholders) and reset tempWrapper.\n const fullHtml = this.treeRenderer.renderState();\n tempWrapper.innerHTML = fullHtml;\n } else {\n this.replaceTargetedSkipPlaceholders(tempWrapper);\n }\n }\n\n try {\n // Use morphdom to efficiently update the element\n morphdom(element, tempWrapper, morphdomOptions);\n } finally {\n // Strip lifecycle markers regardless of whether morphdom threw,\n // preventing leaked attributes on the live DOM.\n this.rangeDomApplier.cleanupMarkers(element);\n }\n\n // Preview-mode hydration: fill [data-lvt-redact] elements from localStorage\n // (.value for inputs, textContent for spans). Runs on the LIVE element AFTER\n // morphdom (not the detached tempWrapper before it): a redacted element is\n // static, so it's never re-walked in an update patch \u2014 the committed DOM is\n // the only place to catch it. Runs before focus restoration so a restored\n // input's value is present when its focus/selection is reapplied. No-op when\n // nothing is tagged/stored.\n hydrateRedactedTokens(element);\n\n // Preview-mode uploads: re-attach local object URLs to any\n // [data-lvt-upload-preview] placeholder the server just re-rendered, so the\n // on-device preview survives server updates (same rationale as redact).\n this.uploadHandler.hydratePreviews(element);\n\n // Restore focus to previously focused element\n this.focusManager.restoreFocusedElement();\n\n // Two classes of post-render scans:\n //\n // FIRE-ON-CHANGE (always run): handleScrollDirectives,\n // handleHighlightDirectives, handleAnimateDirectives,\n // handleToastDirectives, setupScrollAway. These detect VALUE changes\n // on existing directive-bearing elements (e.g. lvt-fx:highlight\n // flashes on every render where the underlying value changed) \u2014 so\n // they must run on every render. Cost is bounded: each does a CSS\n // attribute selector qsa for its specific directive (`[lvt-fx\\:highlight]`\n // etc.); for a 10k-row LargeTable where rows DON'T have these\n // directives, the qsa returns empty in ~1-3ms total.\n //\n // WIRE-IDEMPOTENT (skip when nothing new): setupFxDOMEventTriggers,\n // setupDOMEventTriggerDelegation, uploadHandler.initializeFileInputs.\n // These walk EVERY descendant via qsa(\"*\") to attach event listeners\n // on lvt-fx:event:on:trigger / lvt-el: / file inputs. They have\n // per-element guards so re-running is safe but wasteful \u2014 at 80k\n // descendants the walk costs ~150-200ms each. Skip when neither\n // morphdom.onNodeAdded fired nor a new lvt-* directive attribute\n // appeared on any morphed element (tracked via onBeforeElUpdated).\n handleScrollDirectives(element);\n handleHighlightDirectives(element);\n handleAnimateDirectives(element);\n handleToastDirectives(element);\n handleAutoClickDirectives(element);\n // Area-select needs the send() callback to dispatch the final-\n // coords action on pointerup, unlike the other directives in this\n // block (which are visual-only or click-through). Reuse the same\n // send the event delegator uses so the WS/HTTP routing is\n // identical to a normal action.\n handleAreaSelectDirectives(element, (message) => this.send(message));\n // url-hash bridges server state \u2194 location.hash. Same send-callback\n // wiring as area-select: the directive needs to dispatch a server\n // action on user-driven hashchange events (anchor click, address-bar\n // edit, back-button) and read the latest server-rendered hash from\n // `data-lvt-url-hash` on every render.\n handleURLHashDirective(element, (message) => this.send(message));\n // Hydrate any server-emitted Declarative Shadow DOM templates that\n // morphdom inserted via DOM APIs (which don't auto-activate them).\n // Cheap when no templates are present (one querySelectorAll).\n //\n // Hard limitation: livetemplate directives placed INSIDE shadow\n // content (e.g. lvt-on:click on an element inside a `<template\n // shadowrootmode>`) NEVER register, on any render. The directive\n // sweeps above (setupFxDOMEventTriggers, eventDelegator) walk via\n // querySelectorAll which stops at shadow boundaries, and the\n // hydration that follows doesn't run them against the new shadow\n // root either. Treat shadow DOM as a style/structure isolation\n // primitive only \u2014 keep directives in light DOM. Today's consumer\n // (prereview's HTML preview) wraps sanitised user HTML in shadow\n // DOM purely for style isolation; no directives go in.\n handleShadowRootHydration(element);\n setupScrollAway(element);\n setupSpy(element);\n if (this.nodesAddedThisRender > 0 || this.directiveTouchedThisRender) {\n setupFxDOMEventTriggers(element, this.wrapperElement || undefined);\n this.eventDelegator.setupDOMEventTriggerDelegation(element);\n this.uploadHandler.initializeFileInputs(element);\n }\n\n // changeAutoWirer always runs: its eviction loop must process\n // wirings on removed elements too, regardless of additions.\n this.changeAutoWirer.wireElements();\n\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 * Walk tempWrapper for `<!--lvt-targeted-skip:path-->` comments left by\n * `reconstructFromTree` and convert each into a `data-lvt-targeted-skip`\n * attribute on its parent element. The marker tells morphdom (via its\n * onBeforeElUpdated callback) to short-circuit the subtree, leaving the\n * live container's existing children \u2014 already updated by the applier \u2014\n * untouched.\n */\n private replaceTargetedSkipPlaceholders(tempWrapper: Element): void {\n const walker = document.createTreeWalker(\n tempWrapper,\n NodeFilter.SHOW_COMMENT\n );\n const toReplace: Comment[] = [];\n let node: Node | null;\n while ((node = walker.nextNode())) {\n const c = node as Comment;\n if (c.nodeValue && /^lvt-targeted-skip:.+$/.test(c.nodeValue)) {\n toReplace.push(c);\n }\n }\n for (const c of toReplace) {\n const match = c.nodeValue!.match(/^lvt-targeted-skip:(.+)$/);\n const path = match ? match[1] : \"\";\n const parent = c.parentElement;\n if (parent) {\n parent.setAttribute(TARGETED_SKIP_ATTR, path);\n }\n c.remove();\n }\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, resolveTarget } 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// Tracks the prior value of the watched attribute for each element using\n// `lvt-fx:scroll=\"reset-on:<attr>\"`. We can't stash the prior value as a\n// data-* attribute \u2014 morphdom would diff against it and constantly fight\n// the reset. WeakMap auto-cleans when the element is GC'd.\nlet scrollResetPriors = new WeakMap<Element, string | null>();\n\n// Active timers for `lvt-fx:auto-click=\"<delay-ms>:<button-name>\"`. Stored\n// keyed by element so we can detect: (a) element re-renders unchanged \u2192\n// don't re-arm, (b) spec changed \u2192 cancel + re-arm, (c) element removed \u2192\n// cancel. Strong Map (not WeakMap) so we can iterate for the disconnected-\n// element sweep on each render pass.\n//\n// Leak management: cleanup of stale entries depends on render continuity \u2014\n// `handleAutoClickDirectives` runs the sweep on every render pass, so an\n// element removed between renders is collected on the next one. If the\n// page stops rendering entirely (e.g. websocket dies and is never\n// reconnected) while elements still have timers, those entries hold\n// strong refs until tear-down. In practice the timer's own fire-time\n// `isConnected` guard makes any leftover harmless \u2014 it would skip the\n// `.click()` for a disconnected node \u2014 but the Map itself remains.\nlet autoClickTimers = new Map<\n Element,\n { timer: ReturnType<typeof setTimeout>; spec: string }\n>();\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 scrollResetPriors = new WeakMap<Element, string | null>();\n for (const { timer } of autoClickTimers.values()) clearTimeout(timer);\n autoClickTimers = new Map();\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 const targetEl = resolveTarget(el) as HTMLElement;\n applyFxEffect(targetEl, 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 if (htmlElement.style.length === 0) {\n htmlElement.removeAttribute(\"style\");\n }\n (htmlElement as any).__lvtHighlighting = false;\n return;\n }\n htmlElement.style.backgroundColor = originalBackground;\n setTimeout(() => {\n if (htmlElement.isConnected) {\n htmlElement.style.transition = originalTransition;\n if (htmlElement.style.length === 0) {\n htmlElement.removeAttribute(\"style\");\n }\n }\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 initialized = htmlElement.dataset.lvtScrollSticky === \"1\";\n if (!initialized) {\n htmlElement.dataset.lvtScrollSticky = \"1\";\n htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior: \"instant\" });\n } else {\n const isNearBottom = htmlElement.scrollHeight - htmlElement.scrollTop - htmlElement.clientHeight <= threshold;\n if (isNearBottom) htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior });\n }\n break;\n }\n case \"top\":\n htmlElement.scrollTo({ top: 0, behavior });\n break;\n case \"into-view\": {\n // Scroll the element itself into view of its nearest scrollable\n // ancestor. Useful when server-side state needs to focus the\n // user on a specific element (e.g., a freshly-selected comment).\n // Honors --lvt-scroll-behavior; defaults to centered placement so\n // the user has surrounding context.\n //\n // One-shot semantics: handleScrollDirectives fires on every render,\n // but we don't want to re-scroll the user back every time after they\n // scrolled away. A `data-lvt-iv-done` guard records that this\n // element has already been scrolled into view; the directive only\n // fires again if the attribute is removed and re-added (new element\n // or new value, e.g. jumping to a different comment).\n if (htmlElement.dataset.lvtIvDone !== \"1\") {\n htmlElement.scrollIntoView({ block: \"center\", inline: \"nearest\", behavior });\n htmlElement.dataset.lvtIvDone = \"1\";\n }\n break;\n }\n case \"preserve\":\n break;\n default: {\n // `reset-on:<attr-name>` \u2014 reset scrollLeft/scrollTop to 0\n // whenever the value of `<attr-name>` differs from the last\n // render. Use case: an element whose content swaps without the\n // node itself being replaced (morphdom reuse), where the\n // previous scroll position is meaningless for the new content.\n if (mode.startsWith(\"reset-on:\")) {\n const attrName = mode.slice(\"reset-on:\".length);\n if (!attrName) {\n console.warn(`lvt-fx:scroll=\"reset-on:\" requires an attribute name`);\n break;\n }\n const currentValue = htmlElement.getAttribute(attrName);\n const seen = scrollResetPriors.has(htmlElement);\n if (!seen) {\n // First paint \u2014 establish the prior, don't reset. The\n // directive's semantic is \"reset on *change*\"; if a caller\n // has set scroll programmatically before our first sweep\n // (session restore, deep link, etc.), we must not clobber it.\n scrollResetPriors.set(htmlElement, currentValue);\n } else if (scrollResetPriors.get(htmlElement) !== currentValue) {\n scrollResetPriors.set(htmlElement, currentValue);\n htmlElement.scrollLeft = 0;\n htmlElement.scrollTop = 0;\n }\n break;\n }\n console.warn(`Unknown lvt-fx:scroll mode: ${mode}`);\n }\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 * Cancel and forget all active `lvt-fx:auto-click` timers. Called from\n * the LiveTemplate client's `disconnect()` so per-session timer state\n * doesn't survive across a session boundary (the next session re-arms\n * fresh on its first render pass). Safe to call when no timers exist.\n *\n * Multi-client scope warning: the timer Map is module-level (matching\n * the existing `animatedElements` / `scrollResetPriors` pattern), so\n * this teardown cancels timers across every `LiveTemplateClient`\n * instance on the page. If two clients coexist (e.g. a layout client\n * and a widget client), disconnect order matters \u2014 the surviving\n * client's pending auto-clicks are cancelled along with the\n * disconnecting client's. Surviving clients re-arm on their next\n * render pass, but any auto-click that was about to fire mid-window\n * is lost. Per-instance scoping would solve this but is a larger\n * refactor (the existing two singletons would need the same\n * treatment) and is deferred until a real multi-client use case\n * appears.\n */\nexport function teardownAutoClickTimers(): void {\n for (const { timer } of autoClickTimers.values()) clearTimeout(timer);\n autoClickTimers.clear();\n}\n\n/**\n * Apply auto-click directives. `lvt-fx:auto-click=\"<delay-ms>:<button-name>\"`\n * arms a timer when the element is first seen with this spec; on fire, the\n * directive locates a descendant `[name=<button-name>]` and synthesizes a\n * click on it \u2014 funneling through the existing event-delegation pipeline\n * so the server-side action runs identically to a user click. Use case:\n * auto-dismiss a toast/banner after N ms by clicking its existing dismiss\n * button (which already fires the dismissBanner server action).\n *\n * Idempotent across renders: an element that re-appears with the same\n * spec keeps its existing timer. A spec change cancels and re-arms. An\n * element that disappears has its timer canceled on the next render's\n * sweep (and even if the sweep doesn't run first, the fire-time\n * isConnected check skips the click).\n */\nexport function handleAutoClickDirectives(rootElement: Element): void {\n // Fast path: nothing armed and no matching elements \u2192 no work to do.\n // `querySelector` returns on the first hit, so this is cheaper than\n // the `querySelectorAll` below when there are no matches at all\n // (the common case for pages that don't use this directive).\n if (\n autoClickTimers.size === 0 &&\n rootElement.querySelector(\"[lvt-fx\\\\:auto-click]\") === null\n ) {\n return;\n }\n\n // Sweep: cancel timers for elements that have disconnected OR whose\n // attribute was cleared while they remain in the DOM (e.g. the server\n // resolved the toast's dismiss state without removing the element).\n // Without this, the Map grows unbounded across renders, and a stale\n // timer could fire `.click()` on a button whose owning element no\n // longer wants the auto-action.\n for (const [element, entry] of Array.from(autoClickTimers)) {\n if (\n !element.isConnected ||\n !element.hasAttribute(\"lvt-fx:auto-click\")\n ) {\n clearTimeout(entry.timer);\n autoClickTimers.delete(element);\n }\n }\n\n rootElement.querySelectorAll(\"[lvt-fx\\\\:auto-click]\").forEach((element) => {\n const spec = element.getAttribute(\"lvt-fx:auto-click\");\n if (!spec) return;\n\n const existing = autoClickTimers.get(element);\n if (existing && existing.spec === spec) return;\n if (existing) clearTimeout(existing.timer);\n\n const colonIdx = spec.indexOf(\":\");\n const delayStr = colonIdx > 0 ? spec.slice(0, colonIdx) : \"\";\n // Pre-validate as a pure integer string: parseInt is lenient and\n // would accept \"200abc\" as 200, silently masking a typo in the\n // attribute. Authors expect \"<delay>:<name>\" \u2014 anything else warns.\n const delayMs = /^\\d+$/.test(delayStr) ? parseInt(delayStr, 10) : NaN;\n const name = colonIdx > 0 ? spec.slice(colonIdx + 1) : \"\";\n // `delayMs === 0` is intentionally allowed: it means \"click on the\n // next tick after this element first appears\" \u2014 a useful primitive\n // for \"auto-execute action on render\" patterns. Authors who want\n // visible debounce should pass a non-zero value.\n //\n // Name is restricted to characters that cannot escape the CSS\n // attribute-selector interpolation below (no quotes, brackets,\n // whitespace, or backslashes). Word characters and hyphens cover\n // every valid HTML name attribute we expect to encounter \u2014 including\n // digit-prefixed names \u2014 while keeping the selector safe. JavaScript\n // `\\w` is ASCII-only (`[A-Za-z0-9_]`), so a Unicode button name\n // would warn here; revisit if i18n button naming becomes a real\n // requirement.\n if (\n !Number.isFinite(delayMs) ||\n delayMs < 0 ||\n !name ||\n !/^[\\w-]+$/.test(name)\n ) {\n console.warn(\n `lvt-fx:auto-click expects \"<delay-ms>:<button-name>\", got: ${spec}`\n );\n // Reached when an element's spec changes from valid to malformed\n // mid-life. The valid-spec branch above already cleared its\n // existing timer; this delete removes the now-stale map entry so\n // the next render doesn't see a phantom prior spec.\n autoClickTimers.delete(element);\n return;\n }\n\n const timer = setTimeout(() => {\n // Intentionally NOT deleting the map entry here. Doing so would\n // make the next render pass see \"no entry, attribute still set\"\n // and re-arm a fresh timer, firing `.click()` a second time \u2014\n // reachable whenever a render lands between fire and the server\n // removing the element. Leave the entry in place; the next\n // sweep cleans it up when the element disconnects or the\n // attribute is cleared. The fired timeout itself is now a no-op\n // (clearTimeout on a fired handle is harmless).\n if (!element.isConnected) return;\n // Scoped to <button>: clicking an arbitrary [name=\u2026] match (e.g.\n // a checkbox, a text input) would have surprising side effects\n // unrelated to the action-submission semantic this directive\n // promises. Buttons are the only correct target.\n const button = element.querySelector(\n `button[name=\"${name}\"]`\n ) as HTMLElement | null;\n if (button) button.click();\n }, delayMs);\n autoClickTimers.set(element, { timer, spec });\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// closedShadowRoots tracks shadow roots created in \"closed\" mode. The\n// platform makes them unreachable via `parent.shadowRoot` (it returns\n// null) \u2014 closed mode's whole point is that the host's normal DOM API\n// can't see them. On a re-render, without this side channel, the code\n// would call attachShadow a second time on the same host, throw\n// NotSupportedError, hit the catch, and silently drop the new content.\n// Open roots are reachable via parent.shadowRoot, so they don't need\n// the map.\n//\n// Module-scoped on purpose: WeakMap keys are garbage-collected with\n// their hosts, so detached elements don't leak. A function-scoped map\n// would forget closed roots across renders and the bug would return.\nconst closedShadowRoots = new WeakMap<Element, ShadowRoot>();\n\n/**\n * Activate Declarative Shadow DOM for `<template shadowrootmode>` elements\n * that the client inserted via DOM APIs (innerHTML setter, morphdom's\n * createElement+appendChild path). The HTML parser activates declarative\n * shadow roots only at parse time or via setHTMLUnsafe / parseHTMLUnsafe;\n * a `<template shadowrootmode>` set via `.innerHTML = ...` is parked as a\n * plain template with content but no attached shadow root. This sweep\n * closes that gap so server-emitted shadow roots survive a client\n * re-render.\n *\n * For each matching template found under rootElement:\n * - attach a shadow root on the parent (open by default; \"closed\" when\n * shadowrootmode=\"closed\");\n * - move the template's content into the shadow root (replaceChildren\n * accepts a DocumentFragment and re-parents its children atomically,\n * so re-renders cleanly reset prior shadow content);\n * - remove the template.\n *\n * Hosts that can't accept a shadow root (a small fixed set: <input>,\n * <textarea>, void elements, etc.) silently drop the template \u2014 better\n * than an unhandled exception that kills the render.\n *\n * Closed-mode roots are tracked in a module-level WeakMap so re-renders\n * can locate them (parent.shadowRoot returns null for closed roots).\n *\n * Idempotent: a re-run with no remaining templates is one qsa walk and\n * an early return (sub-millisecond on hundreds-of-rows pages).\n *\n * Known limitations:\n *\n * - Nested DSD is inert on EVERY render, not just re-renders. A\n * `<template>`'s content lives in a DocumentFragment (`tpl.content`),\n * not in the light DOM, and `querySelectorAll` does not descend into\n * that fragment. So a `<template shadowrootmode>` nested inside\n * another `<template>` is never in the qsa result. Once the outer\n * shadow has been attached, the inner template ends up behind a\n * shadow boundary \u2014 still unreachable. The fix would be a recursive\n * sweep per new shadow root from within this loop.\n *\n * - Shadow-root options (`delegatesFocus`, `clonable`, `serializable`,\n * even `mode`) are fixed at first attach. A re-render that toggles\n * `shadowrootdelegatesfocus` on a host that already has a shadow root\n * won't change the existing root's focus behaviour \u2014 re-attach isn't\n * possible. Matches the HTML parser, which would have made the same\n * one-shot decision; if the server needs to flip these flags, it\n * needs to swap the host element entirely. The mode-mismatch case\n * also logs a console.warn so the divergence is visible.\n */\nexport function handleShadowRootHydration(rootElement: Element): void {\n // Single qsa for both the empty-fast-path and the actual work \u2014 a\n // leading querySelector check would double-walk the tree when\n // templates are present. NodeList from querySelectorAll is static\n // (not live), so removing templates inside the loop doesn't disturb\n // iteration; no Array.from copy needed.\n // The selector guarantees <template> elements, so the typed qsa\n // overload removes the `as HTMLTemplateElement` cast inside the loop.\n const templates = rootElement.querySelectorAll<HTMLTemplateElement>(\n \"template[shadowrootmode]\"\n );\n if (templates.length === 0) return;\n for (const tpl of templates) {\n // qsa on an Element always returns descendants with a parentElement,\n // so !parent should be unreachable today. Kept as a defensive guard\n // in case a future caller passes a DocumentFragment-rooted tree\n // where the matched template could be a fragment's direct child.\n const parent = tpl.parentElement;\n if (!parent) {\n tpl.remove();\n continue;\n }\n const modeAttr = tpl.getAttribute(\"shadowrootmode\");\n // Align with the HTML parser: only \"open\" and \"closed\" trigger\n // activation. A typo like shadowrootmode=\"opne\" was previously\n // left in place \"so the author can inspect\" \u2014 but on every\n // subsequent render the qsa would re-find it, defeating the\n // fast-path advertised in the docblock. Remove it AND log a\n // console.warn so authors actually see the typo (the next morphdom\n // pass would overwrite it anyway).\n if (modeAttr !== \"open\" && modeAttr !== \"closed\") {\n console.warn(\n `livetemplate: invalid shadowrootmode=${JSON.stringify(modeAttr)}; ` +\n `expected \"open\" or \"closed\". Template removed.`,\n tpl\n );\n tpl.remove();\n continue;\n }\n const mode: ShadowRootMode = modeAttr;\n\n // For open roots, parent.shadowRoot is the reachable handle. For\n // closed roots, the platform returns null on purpose \u2014 consult the\n // WeakMap that we populated when we first attached the root.\n let shadow = parent.shadowRoot ?? closedShadowRoots.get(parent);\n // If the server flips shadowrootmode on a re-render (e.g. open \u2192\n // closed), attachShadow can't be called a second time \u2014 the existing\n // mode silently wins. Warn so the author notices the mistake instead\n // of debugging mysterious focus/encapsulation behaviour later.\n if (shadow && shadow.mode !== modeAttr) {\n console.warn(\n `livetemplate: shadowrootmode changed from \"${shadow.mode}\" to \"${modeAttr}\" ` +\n `on re-render \u2014 mode is fixed at first attach and cannot be changed.`,\n parent\n );\n }\n if (!shadow) {\n try {\n // Forward all Declarative Shadow DOM attributes so the hydrated\n // root matches the one the HTML parser would build natively:\n // - shadowrootdelegatesfocus \u2192 delegatesFocus\n // - shadowrootclonable \u2192 clonable (Chrome 124+)\n // - shadowrootserializable \u2192 serializable (Chrome 125+)\n // Unknown flags from older runtimes are silently ignored by\n // attachShadow, so we don't need a feature-detect.\n shadow = parent.attachShadow({\n mode,\n delegatesFocus: tpl.hasAttribute(\"shadowrootdelegatesfocus\"),\n clonable: tpl.hasAttribute(\"shadowrootclonable\"),\n serializable: tpl.hasAttribute(\"shadowrootserializable\"),\n });\n if (mode === \"closed\") {\n closedShadowRoots.set(parent, shadow);\n }\n } catch (e) {\n // attachShadow throws DOMException for hosts that can't accept\n // one (void elements, <input>, <textarea>, custom elements that\n // declared a different mode, etc.). Drop the template so it\n // doesn't keep tripping this hook on every render, AND warn so\n // a developer accidentally putting shadow content on an invalid\n // host gets a console signal rather than a mysteriously empty\n // preview.\n //\n // Anything OTHER than a DOMException is a real bug (typo in the\n // options object, runtime fault); re-raise so it surfaces in the\n // console instead of getting silently masked as \"unsupported\n // host\".\n if (!(e instanceof DOMException)) throw e;\n console.warn(\n `livetemplate: attachShadow rejected on <${parent.tagName.toLowerCase()}> ` +\n `(${e.name}: ${e.message}). Template removed.`,\n parent\n );\n tpl.remove();\n continue;\n }\n }\n\n // Pass the DocumentFragment directly \u2014 replaceChildren moves its\n // children into the shadow root in one atomic platform call. Avoids\n // both the spread (which could hit call-stack argument limits on\n // very large NodeLists) and the intermediate Array.from allocation.\n shadow.replaceChildren(tpl.content);\n tpl.remove();\n }\n}\n\n// areaSelectArmed tracks the cleanup callback for every element that\n// currently has a `lvt-fx:area-select` handler attached. Map (not\n// WeakMap) because the sweep needs to iterate to detect elements whose\n// attribute was removed by a server diff \u2014 without iteration those\n// elements would keep their listeners and silently dispatch the old\n// action on subsequent drags. Detached elements are cleaned up via\n// the same sweep (isConnected check).\nconst areaSelectArmed = new Map<Element, AreaSelectEntry>();\n\n// areaSelectWarnedParents dedupes the \"parent not positioned\" dev-warn\n// so a user who repeatedly drags on a mis-configured element gets a\n// single console message instead of one per pointerdown. WeakSet so\n// detached parents don't leak.\n//\n// Known limitation: once a parent is in the set, the warn never fires\n// again on that DOM node \u2014 even if the developer subsequently adds\n// `position: relative` to fix the issue. The WeakSet is per-object\n// (different DOM node = different entry), so re-mounting the parent\n// resets the dedupe; in-place CSS fixes do not. Fine in practice\n// (the user already saw the warn once, on the broken render).\nconst areaSelectWarnedParents = new WeakSet<Element>();\n\ninterface AreaSelectEntry {\n action: string;\n cleanup: () => void;\n // updateSend lets the idempotent re-arm path swap the captured\n // send callback without tearing down + rebuilding listeners. The\n // listeners close over a mutable `send` variable inside\n // attachAreaSelect; updateSend reassigns it.\n updateSend: (send: AreaSelectSendFn) => void;\n}\n\ntype AreaSelectSendFn = (\n message: { action: string; data: Record<string, unknown> }\n) => void;\n\n// MIN_AREA_FRACTION filters accidental click-style gestures where the\n// user meant to click, not drag. 2% of the element's rendered size is\n// big enough to be intentional on touch + mouse but small enough that\n// anyone seriously trying to annotate a tiny region can still do it.\nconst MIN_AREA_FRACTION = 0.02;\n\n/**\n * Apply area-select directives. `lvt-fx:area-select=\"<actionName>\"` on\n * an element (typically an `<img>` inside a positioned parent) lets\n * the user drag a rectangle locally \u2014 a `<div>` overlay tracks the\n * gesture in real time without a server round-trip \u2014 and on\n * `pointerup` dispatches a single livetemplate action with the final\n * `{x, y, w, h}` as 0..1 fractions of the element's rendered bounding\n * rect. The image's intrinsic dimensions don't matter for the\n * fractions: any uniform scale (zoom, responsive layout) preserves\n * the fraction. The consumer scales to pixels using the natural size\n * if it needs them.\n *\n * Contract:\n * - Host's `parentElement` must establish a positioning context\n * (`position: relative` / `absolute` / `fixed`). The overlay is\n * `position: absolute` inside that parent so it follows the host\n * on scroll / reflow.\n * - Consumers usually pair this with `touch-action: none` on the\n * host so iOS Safari doesn't interpret the drag as a pinch/scroll.\n * - `<img>` and other natively-draggable hosts work automatically:\n * the directive calls `preventDefault()` on `dragstart` so the\n * browser's native drag (which would otherwise steal the gesture)\n * is suppressed.\n * - On pointer-cancel (e.g. system gesture, app switch), the overlay\n * is removed and no action is dispatched \u2014 same effect as cancelling\n * a click on `mouseleave`.\n * - Drags smaller than `MIN_AREA_FRACTION` in BOTH dimensions are\n * dropped \u2014 a click on the host still fires normal `click`\n * handlers via the compatibility mouse events.\n * - For text-bearing hosts, set `user-select: none` (the directive\n * deliberately does NOT call `preventDefault()` on `pointerdown`\n * so click handlers still receive the gesture; that means the\n * browser's default text-selection-on-drag behaviour also fires\n * unless the host opts out via CSS).\n * - The overlay uses `z-index: var(--lvt-area-select-z-index, 9999)`.\n * 9999 is high enough for most use cases but can collide with\n * portals / modals / drawers that also sit at a high z-index.\n * Set `--lvt-area-select-z-index` on the host (or any ancestor)\n * to override. Color + fill follow the same pattern via\n * `--lvt-area-select-color` and `--lvt-area-select-fill`.\n * - **No keyboard equivalent.** Pointer-only by design (a keyboard-\n * selected rectangle requires a different UX \u2014 focus + arrow keys\n * to position + arrow keys to size). Consumers needing a11y for\n * area selection should provide a parallel form-based affordance.\n *\n * Idempotent across renders: an element re-armed with the same action\n * keeps its existing listeners. A different action causes a tear-down\n * and re-arm. Disconnected elements (and elements whose attribute was\n * cleared by a server diff) get their listeners cleaned up by the\n * sweep at the top of every call \u2014 we use a regular Map (not WeakMap)\n * specifically so the sweep can iterate.\n *\n * Module-level singleton: `areaSelectArmed` is shared across all\n * LiveTemplateClient instances in the same window. If two clients\n * ever arm the same element with different actions, the second wins\n * and the first client's send() is orphaned. Single-client use is\n * unaffected.\n */\nexport function handleAreaSelectDirectives(\n rootElement: Element,\n send: (message: { action: string; data: Record<string, unknown> }) => void\n): void {\n // Sweep stale entries before processing the current match set:\n // disconnected elements AND elements where the attribute was\n // removed by a server diff. Without this, a previously-armed\n // element whose lvt-fx:area-select was cleared would keep its\n // listeners and silently dispatch the old action on subsequent\n // drags. Iterate via Array.from so cleanup()'s delete() doesn't\n // disturb the iterator.\n for (const [element, entry] of Array.from(areaSelectArmed)) {\n if (!element.isConnected || !element.hasAttribute(\"lvt-fx:area-select\")) {\n entry.cleanup();\n }\n }\n\n const matches = rootElement.querySelectorAll<HTMLElement>(\n \"[lvt-fx\\\\:area-select]\"\n );\n if (matches.length === 0) return;\n\n for (const el of matches) {\n const action = el.getAttribute(\"lvt-fx:area-select\");\n // Empty attribute \u2192 consumer almost certainly typoed; warn and\n // skip rather than dispatching to a blank action name.\n if (!action) {\n console.warn(\n `lvt-fx:area-select requires an action name, got: ${JSON.stringify(action)}`\n );\n continue;\n }\n const existing = areaSelectArmed.get(el);\n if (existing && existing.action === action) {\n // Idempotent re-arm: keep the listeners + WeakMap entry, but\n // update the captured send so a subsequent drag dispatches\n // through the latest callback (e.g. after a WebSocket\n // reconnect rebuilt the transport).\n existing.updateSend(send);\n continue;\n }\n if (existing) existing.cleanup();\n areaSelectArmed.set(el, attachAreaSelect(el, action, send));\n }\n}\n\n/**\n * Cancel area-select listeners for every armed element under root.\n * Mirrors teardownAutoClickTimers: meant for the client's disconnect /\n * destroy lifecycle so the module-level singleton doesn't outlive a\n * client that was torn down without a subsequent\n * handleAreaSelectDirectives call (e.g. network error closed the\n * socket while an element was armed). Without this, a SPA that mounts\n * + tears down livetemplate trees would leak listeners across mounts.\n */\nexport function teardownAreaSelectForRoot(rootElement: Element): void {\n // `contains` returns true for the node itself, so this also handles\n // the (today-impossible) case of rootElement being armed directly.\n for (const [element, entry] of Array.from(areaSelectArmed)) {\n if (rootElement.contains(element)) {\n entry.cleanup();\n }\n }\n}\n\n// urlHashArmed tracks `lvt-fx:url-hash` listeners and their last-\n// mirrored hash. Same Map-not-WeakMap reasoning as area-select: the\n// sweep iterates to detect elements whose attribute was removed by a\n// server diff, and detached elements are cleaned up via the same\n// sweep (isConnected check).\n//\n// Module-level singleton: shared across all LiveTemplateClient\n// instances in the window. Two clients arming DIFFERENT elements\n// each get their own entry; the shared window hashchange listener\n// iterates the map and dispatches through every armed entry's own\n// send, so a multi-client page sees each client receive its hash\n// event. Teardown is scoped per root via teardownURLHashForRoot, so\n// clients don't tear down each other's listeners.\n//\n// Same-element multi-arm is \"last writer wins\": the Map key is the\n// element, so a second client arming the same element runs the\n// existing entry's cleanup() and replaces it. The first client's\n// send is orphaned. This matches area-select's behavior and is fine\n// for the documented single-arm-per-element contract.\nconst urlHashArmed = new Map<Element, URLHashEntry>();\n\n// urlHashWindowListener is the single window-level `hashchange`\n// listener shared across all armed elements. Registered on first arm,\n// removed when the armed map becomes empty. Per-element listeners\n// would multi-fire for the (rare) case of multiple armed elements;\n// one shared listener iterating the armed map keeps the dispatch\n// count deterministic.\nlet urlHashWindowListener: ((e: HashChangeEvent) => void) | null = null;\n\ninterface URLHashEntry {\n action: string;\n // send is stored on the entry so the shared window listener can\n // dispatch each armed entry's action through its own transport. The\n // idempotent re-arm path mutates this field directly (via\n // updateSend), so a reconnect that rebuilt the transport is picked\n // up on the next hashchange.\n send: URLHashSendFn;\n cleanup: () => void;\n updateSend: (send: URLHashSendFn) => void;\n // currentDataHash is the last `data-lvt-url-hash` value we mirrored\n // into `location.hash` (or the value we observed on a user-initiated\n // hashchange). Comparing against the next render's data-attr lets us\n // no-op when the server re-rendered with the same hash \u2014 avoids\n // extra history entries on every keystroke.\n currentDataHash: string;\n}\n\ntype URLHashSendFn = (\n message: { action: string; data: Record<string, unknown> }\n) => void;\n\n/**\n * Apply url-hash directives. `lvt-fx:url-hash=\"<actionName>\"` plus a\n * `data-lvt-url-hash=\"<hash>\"` attribute on an element (typically the\n * `<body>`) wires a two-way bridge between server state and\n * `location.hash`:\n *\n * - **State \u2192 URL** (every render): if `data-lvt-url-hash` differs\n * from `location.hash`, mirror the data-attr into the URL via\n * `history.pushState` when the path component changed (everything\n * before the first `:`) or `history.replaceState` when only the\n * target (line range / anchor) changed. Replace is the right\n * default for line scrolls so the back-button cycles between files,\n * not between every clicked line.\n * - **URL \u2192 State** (on `hashchange` AND initial arm): dispatch\n * `{action: <actionName>, data: {hash: <hash>}}` so the server can\n * parse the hash and update its state (which then renders back as\n * a matching data-attr \u2014 closing the loop).\n *\n * The directive uses `history.pushState`/`replaceState` (not\n * `location.hash = ...`) for the state\u2192URL direction precisely so\n * those writes do NOT fire `hashchange` \u2014 only true user-initiated\n * navigation (anchor click, address-bar edit, back-button) reaches\n * the URL\u2192state listener. This avoids the obvious infinite loop.\n *\n * Idempotent across renders: same action \u2192 keep listener + update\n * send. Different action \u2192 cleanup + re-arm. Detached / attribute-\n * removed elements are swept on every call (same pattern as\n * area-select). The window listener is registered on first arm and\n * removed when the armed map becomes empty.\n *\n * Coexistence with `setupHashLink`: prereview-style hashes\n * (`README.md:L4`, `foo/bar.html:h-anchor`) never match a\n * `document.getElementById(...)`, so the existing dialog/popover/\n * details hash machinery silently no-ops. If a deep-link hash\n * happens to collide with an element id, both handlers will fire \u2014\n * the server is expected to no-op on hashes that don't resolve to a\n * known file.\n *\n * **Pre-encoding contract**: `data-lvt-url-hash` must hold the hash\n * value already in URL-encoded form. The directive writes the\n * attribute verbatim into `history.pushState`/`replaceState`, so a\n * value containing spaces, `[`, `]`, `%`, or other reserved\n * characters needs to be percent-encoded by the server. The hash\n * sent to the action on `hashchange` is also passed through unmodified\n * (no decoding) \u2014 both directions are byte-exact mirrors of what's\n * in `location.hash`.\n *\n * **URL/state divergence after a non-deep-link initial load**: if\n * the user lands with a native-anchor hash (`#hero`) AND the server\n * has a selected file, the directive leaves the URL on `#hero` (case\n * b) \u2014 URL and server state diverge until the user navigates. This\n * is intentional: popovers/anchors aren't ours to overwrite. The\n * next user action that triggers a server render will re-sync only\n * once URL and state share a deep-link hash.\n *\n * **Path-only deep links require an extension**: the\n * `looksLikeDeepLinkHash` heuristic dispatches only hashes\n * containing `:`, `/`, or `.`. Extension-less root files\n * (`#Makefile`, `#Dockerfile`, `#LICENSE`) won't dispatch as\n * path-only deep links \u2014 use the line form (`#Makefile:L1`)\n * instead. The trade-off favours not clobbering native-anchor\n * machinery for single-token hashes.\n */\nexport function handleURLHashDirective(\n rootElement: Element,\n send: (message: { action: string; data: Record<string, unknown> }) => void\n): void {\n // Sweep stale entries first \u2014 disconnected hosts AND hosts whose\n // attribute was removed by a server diff. Iterate via Array.from so\n // cleanup()'s delete() doesn't disturb the iterator.\n for (const [element, entry] of Array.from(urlHashArmed)) {\n if (\n !element.isConnected ||\n !element.hasAttribute(\"lvt-fx:url-hash\")\n ) {\n entry.cleanup();\n }\n }\n\n // Match the root itself, descendants, AND the document body. The\n // url-hash directive is typically placed on `<body>`, but livetemplate\n // auto-injects its `<div data-lvt-id>` INSIDE body, so the rootElement\n // passed by the client is the wrapper div \u2014 a strict descendant of\n // body. Without the body check, a directive on `<body>` would never\n // arm. We accept body placement because URL hash is page-global\n // anyway; the directive's lifecycle is still tied to the wrapper via\n // teardownURLHashForRoot (called on disconnect of the wrapper).\n const matches: HTMLElement[] = [];\n if (\n rootElement instanceof HTMLElement &&\n rootElement.hasAttribute(\"lvt-fx:url-hash\")\n ) {\n matches.push(rootElement);\n }\n const body = rootElement.ownerDocument?.body;\n if (\n body &&\n body !== rootElement &&\n body.hasAttribute(\"lvt-fx:url-hash\") &&\n !matches.includes(body)\n ) {\n matches.push(body);\n }\n rootElement\n .querySelectorAll<HTMLElement>(\"[lvt-fx\\\\:url-hash]\")\n .forEach((el) => {\n if (!matches.includes(el)) matches.push(el);\n });\n if (matches.length === 0) return;\n\n for (const el of matches) {\n const action = el.getAttribute(\"lvt-fx:url-hash\");\n if (!action) {\n console.warn(\n `lvt-fx:url-hash requires an action name, got: ${JSON.stringify(action)}`\n );\n continue;\n }\n const dataHash = el.getAttribute(\"data-lvt-url-hash\") || \"\";\n const existing = urlHashArmed.get(el);\n if (existing && existing.action === action) {\n existing.updateSend(send);\n mirrorDataAttrToLocation(existing, dataHash);\n continue;\n }\n if (existing) existing.cleanup();\n const entry = attachURLHash(el, action, send);\n urlHashArmed.set(el, entry);\n // First-arm sync: three cases, in priority order.\n const initialLocation = window.location.hash.replace(/^#/, \"\");\n if (\n initialLocation &&\n initialLocation !== dataHash &&\n looksLikeDeepLinkHash(initialLocation)\n ) {\n // (a) URL has a deep-link hash that differs from server state.\n // URL \"wins\" on initial load \u2014 dispatch so the server can\n // reconcile, and seed currentDataHash so the converging render\n // doesn't try to mirror over the user's URL.\n entry.currentDataHash = initialLocation;\n send({ action, data: { hash: initialLocation } });\n } else if (initialLocation && !looksLikeDeepLinkHash(initialLocation)) {\n // (b) URL has a non-deep-link hash (e.g. `#hero` opening a\n // popover, or a native heading anchor). Leave it alone \u2014 it\n // belongs to other machinery (setupHashLink, native scroll).\n // Seed currentDataHash so a later mirror sees the data-attr\n // as the baseline to compare against, and only writes when\n // the user navigates away from the popover/anchor.\n entry.currentDataHash = dataHash;\n } else {\n // (c) URL is empty (or already matches the server). Mirror the\n // server's hash into the URL if any.\n mirrorDataAttrToLocation(entry, dataHash);\n }\n }\n}\n\n/**\n * Cancel url-hash listeners for every armed element under root. Same\n * lifecycle role as teardownAreaSelectForRoot.\n */\nexport function teardownURLHashForRoot(rootElement: Element): void {\n // Includes body when body is an ancestor of rootElement and body is\n // armed \u2014 the directive accepts body placement (see the matcher in\n // handleURLHashDirective), so teardown must symmetrically clean up\n // both directions.\n //\n // Multi-client caveat: a body-armed entry is shared across all\n // LiveTemplateClient instances (Map key is the element, so only one\n // entry per body). Tearing down client A's root will therefore also\n // tear down a body listener that client B armed last \u2014 there's no\n // \"owner\" tracked. Acceptable for the single-client case (the\n // common deployment) and matches the same-element-multi-arm\n // last-writer-wins behavior in attachURLHash. A \"fix\" that\n // restricted the body-cleanup branch to client A would leak\n // client A's own body listener \u2014 don't do that without also\n // tracking entry ownership.\n const body = rootElement.ownerDocument?.body;\n for (const [element, entry] of Array.from(urlHashArmed)) {\n if (rootElement.contains(element)) {\n entry.cleanup();\n continue;\n }\n if (body && element === body && body.contains(rootElement)) {\n entry.cleanup();\n }\n }\n}\n\n// mirrorDataAttrToLocation pushes `dataHash` into `location.hash` if\n// it differs from what's already in the URL. Chooses push vs replace\n// by comparing the path component (everything before the first `:`)\n// against the current location.hash's path: a path change is a \"file\n// switch\" (user-meaningful back-button entry) and gets pushState;\n// any other change is a target-only update (line scroll / anchor\n// scroll) and gets replaceState. Updates entry.currentDataHash so a\n// subsequent render with the same data-attr no-ops.\n//\n// Initial-mirror special case: if the URL was empty when we're\n// mirroring (no prior hash to compare against), use replaceState even\n// though the path-component comparison would say \"changed\". An empty\n// URL \u2192 first server hash isn't a \"navigation\" \u2014 we're establishing\n// the initial state. Using pushState here would let Back land the\n// user on `url-without-hash`, which re-triggers the same arm and\n// pushes the same hash again. Loop.\n//\n// Empty-dataHash special case: if the server transitions FROM a\n// selected file TO no-selection (state.URLHash() returns \"\"), we\n// would otherwise wipe location.hash entirely \u2014 including hashes the\n// directive doesn't own (a popover #hero the user opened during the\n// session). To stay safe, only clear when the URL currently holds a\n// deep-link-shaped hash; non-deep-link hashes are left alone.\nfunction mirrorDataAttrToLocation(entry: URLHashEntry, dataHash: string): void {\n if (entry.currentDataHash === dataHash) return;\n const currentLocation = window.location.hash.replace(/^#/, \"\");\n if (currentLocation === dataHash) {\n entry.currentDataHash = dataHash;\n return;\n }\n if (currentLocation !== \"\" && !looksLikeDeepLinkHash(currentLocation)) {\n // URL is on something not ours (popover id, native anchor) \u2014\n // don't clobber it, regardless of what the server's data-attr\n // says. This covers BOTH the server-clears case (dataHash=\"\")\n // and the rarer server-changes-selection-while-popover-open\n // case (dataHash transitions from one file to another while\n // the URL is parked on a non-deep-link hash).\n entry.currentDataHash = dataHash;\n return;\n }\n warnIfUnencodedHash(dataHash);\n const targetURL = dataHash ? `#${dataHash}` : window.location.pathname + window.location.search;\n const oldPath = currentLocation.split(\":\")[0];\n const newPath = dataHash.split(\":\")[0];\n // Preserve existing history.state \u2014 passing `null` would clobber\n // anything other SPA-like code on the page stores there (scroll\n // position, modal flag, etc.). The state object is independent of\n // the URL we're rewriting, so carrying it forward is the right\n // default.\n const currentState = window.history.state;\n // Empty currentLocation means we're establishing the URL from a\n // blank slate (initial render with no prior URL hash) \u2014 that's NOT\n // a back-button-meaningful navigation, so always replaceState.\n // Otherwise: a path change is a file switch (push), a target-only\n // change is a line/anchor scroll (replace).\n if (currentLocation !== \"\" && oldPath !== newPath) {\n window.history.pushState(currentState, \"\", targetURL);\n } else {\n window.history.replaceState(currentState, \"\", targetURL);\n }\n entry.currentDataHash = dataHash;\n}\n\n// warnIfUnencodedHash flags `data-lvt-url-hash` values containing\n// characters that should be percent-encoded (raw space, `<`, `>`,\n// `\"`, ``` ` ```, `#`, `[`, `]`, `%`). The directive writes the\n// hash verbatim into `pushState`/`replaceState`, so an unencoded\n// value will silently produce a malformed URL \u2014 `location.hash`\n// reads back differently from what was set. Cheap dev-time guard\n// against a server-side contract slip; dedupes by value to avoid\n// log spam.\n//\n// `%` is included because a raw `%` not followed by two hex digits\n// is itself a percent-encoding error. The check is a heuristic\n// (won't catch every malformed escape), but covers the common\n// \"forgot to encode\" cases.\nconst urlHashUnencodedWarned = new Set<string>();\n\n/**\n * Test-only: reset the per-page dedupe Set that suppresses repeated\n * `warnIfUnencodedHash` calls for the same hash value. Production\n * code shouldn't need this \u2014 the Set is bounded by the number of\n * unique malformed hashes \u2014 but tests that re-use the same hash\n * across cases need to clear it or the second test won't see the\n * warning. Mirrors `__resetAnimatedElementsForTesting`.\n */\nexport function __resetURLHashUnencodedWarnedForTesting(): void {\n urlHashUnencodedWarned.clear();\n}\n\nfunction warnIfUnencodedHash(hash: string): void {\n if (!hash || urlHashUnencodedWarned.has(hash)) return;\n if (/[ <>\"`#\\[\\]]/.test(hash) || /%(?![0-9A-Fa-f]{2})/.test(hash)) {\n urlHashUnencodedWarned.add(hash);\n console.warn(\n `lvt-fx:url-hash: data-lvt-url-hash=\"${hash}\" contains characters that should be percent-encoded. The directive writes it verbatim into history.pushState/replaceState; malformed URLs result. Server-side FormatHash (or equivalent) should percent-escape path segments and target ids before serialization.`\n );\n }\n}\n\n// looksLikeDeepLinkHash discriminates URL hashes the prereview deep-\n// link grammar can produce (file path with optional :L<n> or :h-id)\n// from hashes that belong to other native machinery (HTML element\n// anchors, dialog/popover/details ids, etc.). Deep-link hashes always\n// contain at least one of: `:` (target separator), `/` (nested path),\n// or `.` (file extension). Empty \u2192 false.\n//\n// False positives are possible but cheap. A heading id like\n// `#v1.0.0`, `#menu/item`, or `#key:value` matches this heuristic\n// and will dispatch the action \u2014 but the consuming server is\n// expected to no-op on hashes whose path doesn't resolve to a known\n// file (prereview's SetURLHash does, via the loadDiffCached failure\n// path). The cost is one wasted roundtrip per false positive, which\n// is acceptable for the alternative of missing real deep links.\n//\n// False negatives: extension-less filenames at the repo root \u2014\n// `#Makefile`, `#Dockerfile`, `#LICENSE` \u2014 don't match this\n// heuristic and won't be dispatched as path-only deep links. The\n// workaround is the line-form (`#Makefile:L1`), which always\n// dispatches. This trade-off is deliberate: a heuristic that\n// matched single-token hashes would also clobber every native\n// anchor / popover id, which is a much worse default. Consumers\n// that need extension-less file deep links can build a richer\n// directive on top.\nfunction looksLikeDeepLinkHash(hash: string): boolean {\n if (!hash) return false;\n return hash.includes(\":\") || hash.includes(\"/\") || hash.includes(\".\");\n}\n\nfunction attachURLHash(\n el: HTMLElement,\n action: string,\n initialSend: URLHashSendFn\n): URLHashEntry {\n const entry: URLHashEntry = {\n action,\n send: initialSend,\n cleanup: () => {\n urlHashArmed.delete(el);\n if (urlHashArmed.size === 0 && urlHashWindowListener) {\n window.removeEventListener(\"hashchange\", urlHashWindowListener);\n urlHashWindowListener = null;\n }\n },\n updateSend: (s) => {\n entry.send = s;\n },\n currentDataHash: \"\",\n };\n\n if (!urlHashWindowListener) {\n urlHashWindowListener = () => {\n const hash = window.location.hash.replace(/^#/, \"\");\n // Only dispatch hashes that look like deep-link targets \u2014 they\n // contain `:` (target separator), `/` (nested path), or `.`\n // (file extension). Plain element-id hashes like `#hero` or\n // `#confirm-delete-xyz` belong to the native anchor / dialog /\n // popover / details machinery (setupHashLink handles those) and\n // would otherwise be dispatched here, prompt a server no-op,\n // then get clobbered by the mirror step when the server's\n // data-attr (unchanged) doesn't match.\n //\n // Empty hash (user cleared the URL bar) is also intentionally\n // ignored. The directive treats the server as the source of\n // truth for \"what's selected\"; an empty URL is \"user navigated\n // away from a hash\" but not \"deselect everything\". If the user\n // wants to deselect, they use the in-app affordance\n // (clearSelection / Escape) which makes the server emit an\n // empty data-attr \u2014 at which point the mirror step propagates\n // the empty hash back to the URL.\n if (!looksLikeDeepLinkHash(hash)) return;\n // Iterate via Array.from in case a dispatched action triggers a\n // render that mutates the armed map (e.g. tears down this\n // element). Each armed entry dispatches through its OWN send +\n // action so multi-arm is deterministic \u2014 typically the body is\n // the only armed element so this is one iteration.\n for (const e of Array.from(urlHashArmed.values())) {\n // Record the user-driven hash as the new baseline so the\n // next render's mirror step doesn't immediately revert it.\n e.currentDataHash = hash;\n e.send({ action: e.action, data: { hash } });\n }\n };\n window.addEventListener(\"hashchange\", urlHashWindowListener);\n }\n\n return entry;\n}\n\n// attachAreaSelect captures `send` in a mutable local so the\n// idempotent re-arm path (same element, same action) can swap it via\n// the returned `updateSend` callback without tearing down + rebuilding\n// listeners. Listeners reference the closure-captured `send` variable\n// directly, so reassigning it propagates instantly. This guards\n// against the stale-closure trap a caller would hit if their `send`\n// reference changed across renders \u2014 e.g. a reconnect rebuilt the\n// transport.\nfunction attachAreaSelect(\n el: HTMLElement,\n action: string,\n initialSend: AreaSelectSendFn\n): AreaSelectEntry {\n let send = initialSend;\n let overlay: HTMLDivElement | null = null;\n let startClientX = 0;\n let startClientY = 0;\n let pointerId = -1;\n // Capture the parent at pointerdown time so a server diff that moves\n // the host to a NEW parent mid-drag doesn't split the drag across\n // two positioning contexts. updateOverlay positions against this\n // cached parent for the lifetime of the gesture; the overlay itself\n // stays a child of the parent we appended it to (overlay removal\n // uses overlay.parentElement, which is independent).\n let dragParent: HTMLElement | null = null;\n // Cache the host's rect at pointerdown \u2014 startClientX/Y are captured\n // in the SAME frame, so the start corner is meaningful only against\n // the rect that existed then. If a server diff repositions the host\n // mid-drag, finalize would otherwise clamp the (old-coord-system)\n // startClientX against the new rect and silently produce wrong\n // fractions. Anchoring to the start-rect keeps the dispatched\n // rectangle pinned to the visual region the user actually dragged.\n let startRect: DOMRect | null = null;\n\n const removeOverlay = () => {\n if (overlay) {\n // Element.remove() is a no-op if the node isn't in the DOM,\n // so we don't need the parent-null guard the older two-step\n // pattern needed.\n overlay.remove();\n }\n overlay = null;\n };\n\n const finalize = (e: PointerEvent | null, dispatch: boolean) => {\n if (pointerId === -1) return;\n // CRITICAL ORDER: reset pointerId + dragParent + startRect BEFORE\n // calling releasePointerCapture. Chromium fires lostpointercapture\n // SYNCHRONOUSLY during releasePointerCapture, which lands in\n // onLostCapture \u2192 finalize(null, false). Without the early reset,\n // the nested finalize sees pointerId still matching and runs to\n // completion (clearing startRect), then the outer finalize\n // resumes with startRect == null and silently drops the\n // dispatched action. Resetting first makes the nested call\n // return at the `pointerId === -1` guard, leaving outer state\n // intact.\n const capturedPointerId = pointerId;\n const rect = startRect;\n pointerId = -1;\n dragParent = null;\n startRect = null;\n try {\n el.releasePointerCapture(capturedPointerId);\n } catch {\n // Capture may already be gone (e.g. pointercancel) \u2014 ignore.\n }\n // Remove the per-gesture pointerleave fallback so a NEXT drag\n // doesn't inherit a stale listener from this one. {once: true}\n // only auto-removes if it fires; a stuck drag never fired it.\n el.removeEventListener(\"pointerleave\", onPointerLeaveCancel);\n if (!dispatch || !e || !rect) {\n removeOverlay();\n return;\n }\n if (rect.width <= 0 || rect.height <= 0) {\n removeOverlay();\n return;\n }\n // Clamp the two corners to the rect BEFORE computing fractions so\n // a drag that escapes the element still yields a rectangle inside\n // it (x \u2208 [0,1], w \u2208 [0,1-x]). Otherwise a far-off-rect endpoint\n // would push w past 1 even with x already > 0.\n const rectRight = rect.left + rect.width;\n const rectBottom = rect.top + rect.height;\n const x0 = clampRange(Math.min(startClientX, e.clientX), rect.left, rectRight);\n const y0 = clampRange(Math.min(startClientY, e.clientY), rect.top, rectBottom);\n const x1 = clampRange(Math.max(startClientX, e.clientX), rect.left, rectRight);\n const y1 = clampRange(Math.max(startClientY, e.clientY), rect.top, rectBottom);\n const x = (x0 - rect.left) / rect.width;\n const y = (y0 - rect.top) / rect.height;\n const w = (x1 - x0) / rect.width;\n const h = (y1 - y0) / rect.height;\n removeOverlay();\n // Reject zero-area rectangles outright. The MIN_AREA_FRACTION\n // check below uses `&&` (drop only when BOTH dims are small) so\n // a wide-but-thin selection is preserved \u2014 but a literal\n // 60%\u00D70 (or 0\u00D760%) collapses to no region, can't be rendered\n // sensibly, and would divide by zero in any pixel-space\n // conversion downstream. Drop independently of the threshold.\n if (w <= 0 || h <= 0) return;\n // Drop when BOTH dimensions are below the threshold (intentional\n // `&&` \u2014 NOT `||`). A wide-but-thin drag (e.g. an underline across\n // an annotated row) or a tall-but-thin drag (e.g. a vertical\n // highlight) is a real selection in this directive's contract,\n // not an accidental click. `||` would drop those legitimate\n // gestures. The click-vs-drag boundary lives in \"the rect has\n // basically no area\" \u2014 that's both dims below the threshold.\n if (w < MIN_AREA_FRACTION && h < MIN_AREA_FRACTION) {\n // Treat as a click, not a drag. Don't dispatch; let normal click\n // handlers (if any) run via the platform.\n return;\n }\n send({ action, data: { x, y, w, h } });\n };\n\n const onPointerLeaveCancel = (e: PointerEvent) => {\n // Fallback for the rare case where setPointerCapture failed: without\n // capture, pointermove + pointerup stop arriving once the pointer\n // leaves the host, freezing the overlay. Treating pointerleave as\n // a cancel keeps the overlay from getting stuck on screen.\n // Guard on pointerId \u2014 in multi-touch, a SECONDARY pointer's\n // leave shouldn't cancel the primary drag.\n if (e.pointerId !== pointerId) return;\n finalize(null, false);\n };\n\n // Chromium fires `dragstart` on an <img> after the first mousemove\n // following mousedown, yanking the gesture away from pointer events\n // before pointerup arrives \u2014 the overlay flashes and capture is\n // lost. preventDefault on dragstart suppresses the native image\n // drag without breaking pointer events. Cheap to attach on every\n // element type (non-img hosts simply never fire dragstart).\n const onDragStart = (e: DragEvent) => e.preventDefault();\n\n const onPointerDown = (e: PointerEvent) => {\n // Only primary button (left mouse / single touch / pen tip). Modifier\n // keys passed through so the server-side handler can decide what to\n // do with them via subsequent renders.\n if (!e.isPrimary || e.button !== 0) return;\n // Re-entrancy guard: if a prior drag never finished (e.g. capture\n // failed silently, then pointer left the element with no pointerup\n // ever delivered), the closed-over pointerId variable would still\n // hold the stale id. Cancel the prior drag \u2014 removing its overlay\n // and listeners \u2014 before starting a fresh one.\n if (pointerId !== -1) finalize(null, false);\n const parent = el.parentElement;\n if (!parent) return; // overlay needs a positioned container\n // Dev-time check: if the parent doesn't establish a positioning\n // context, the overlay's `position: absolute` will resolve against\n // the nearest positioned ANCESTOR \u2014 a distant element with no\n // visible relationship to the host. Result: overlay paints in\n // the wrong place with no error, just a confusing visual.\n // Check against the positive list of positioned values; the\n // default \"static\" and an unset/empty value both fail it (jsdom\n // returns \"\" for unset position). Dedupe via WeakSet so a user\n // dragging repeatedly on the same mis-configured parent gets ONE\n // console message, not one per pointerdown.\n if (!areaSelectWarnedParents.has(parent)) {\n const parentPos = window.getComputedStyle(parent).position;\n if (\n parentPos !== \"relative\" &&\n parentPos !== \"absolute\" &&\n parentPos !== \"fixed\" &&\n parentPos !== \"sticky\"\n ) {\n console.warn(\n \"lvt-fx:area-select: parentElement has no positioning context; the drag overlay will be mis-positioned. \" +\n \"Add position:relative (or absolute/fixed/sticky) to the parent.\",\n parent\n );\n areaSelectWarnedParents.add(parent);\n }\n }\n startClientX = e.clientX;\n startClientY = e.clientY;\n pointerId = e.pointerId;\n dragParent = parent;\n startRect = el.getBoundingClientRect();\n let captureOk = false;\n try {\n el.setPointerCapture(pointerId);\n captureOk = true;\n } catch {\n // Capture failure is non-fatal \u2014 without it, leaving the element\n // mid-drag will lose pointermove. Fall back to pointerleave as\n // the cancel signal so the overlay can't get stuck.\n }\n if (!captureOk) {\n el.addEventListener(\"pointerleave\", onPointerLeaveCancel, { once: true });\n }\n overlay = document.createElement(\"div\");\n overlay.className = \"lvt-area-select-overlay\";\n overlay.setAttribute(\"aria-hidden\", \"true\");\n // Inline styles so the directive doesn't depend on a CSS class\n // shipped by the consumer. Consumers can override via the class\n // selector if they want a different look.\n overlay.style.cssText =\n \"position:absolute;pointer-events:none;border:2px solid var(--lvt-area-select-color,#4cc2ff);\" +\n \"background:var(--lvt-area-select-fill,rgba(76,194,255,0.18));box-sizing:border-box;\" +\n \"z-index:var(--lvt-area-select-z-index,9999);\";\n parent.appendChild(overlay);\n updateOverlay(e);\n // NOT calling e.preventDefault() here: doing so on pointerdown\n // suppresses the compatibility mouse events (mousedown \u2192 mouseup\n // \u2192 click), so a small-rect drag (which finalize() treats as a\n // click) would never reach the host's click handlers. The\n // directive's contract promises clicks still bubble. Text-\n // selection during drag is the consumer's responsibility \u2014 set\n // `user-select: none` on the host (the contract docs this).\n };\n\n const updateOverlay = (e: PointerEvent) => {\n if (!overlay) return;\n // Use the parent captured at pointerdown \u2014 if a server diff\n // moved `el` to a new parent mid-drag, re-fetching el.parentElement\n // here would compute against the new container while the overlay\n // lives in the old, paint at the wrong place for the rest of the\n // gesture.\n const parent = dragParent;\n if (!parent) return;\n const elRect = el.getBoundingClientRect();\n const parentRect = parent.getBoundingClientRect();\n // Convert viewport coords (clientX/Y) to position:absolute CSS\n // offsets inside the parent. Three corrections, all subtracted\n // from / added to the same way for every value we compute:\n //\n // 1. parentRect.left/top \u2014 getBoundingClientRect is in viewport\n // coords; CSS offsets are relative to the parent's box.\n // 2. parent.clientLeft/Top \u2014 position:absolute is measured from\n // the padding box; getBoundingClientRect returns the border\n // box. A parent with a CSS border would otherwise shift the\n // overlay by the border width.\n // 3. parent.scrollLeft/Top \u2014 when the parent is scrolled, an\n // element at viewport_x = parentRect.left has CSS_left =\n // parent.scrollLeft (not 0). Without adding scroll back in,\n // the overlay paints offset by the scroll amount.\n const borderL = parent.clientLeft;\n const borderT = parent.clientTop;\n const scrollL = parent.scrollLeft;\n const scrollT = parent.scrollTop;\n const toCSSLeft = (vx: number) => vx - parentRect.left - borderL + scrollL;\n const toCSSTop = (vy: number) => vy - parentRect.top - borderT + scrollT;\n const left = toCSSLeft(Math.min(startClientX, e.clientX));\n const top = toCSSTop(Math.min(startClientY, e.clientY));\n const width = Math.abs(e.clientX - startClientX);\n const height = Math.abs(e.clientY - startClientY);\n // Clamp to the host's rendered rect (in the same CSS coord space)\n // so a drag that runs off the edge doesn't paint outside the host.\n const minLeft = toCSSLeft(elRect.left);\n const minTop = toCSSTop(elRect.top);\n const maxRight = minLeft + elRect.width;\n const maxBottom = minTop + elRect.height;\n const clampedLeft = Math.max(minLeft, Math.min(left, maxRight));\n const clampedTop = Math.max(minTop, Math.min(top, maxBottom));\n const clampedRight = Math.max(minLeft, Math.min(left + width, maxRight));\n const clampedBottom = Math.max(minTop, Math.min(top + height, maxBottom));\n overlay.style.left = `${clampedLeft}px`;\n overlay.style.top = `${clampedTop}px`;\n overlay.style.width = `${Math.max(0, clampedRight - clampedLeft)}px`;\n overlay.style.height = `${Math.max(0, clampedBottom - clampedTop)}px`;\n };\n\n const onPointerMove = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n // Host removed from the DOM mid-drag (e.g. server diff replaced it).\n // Without this, the overlay would be left orphaned under the parent\n // because the host's cleanup never runs.\n if (!el.isConnected) {\n finalize(null, false);\n return;\n }\n updateOverlay(e);\n };\n\n const onPointerUp = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n if (!el.isConnected) {\n finalize(null, false);\n return;\n }\n finalize(e, true);\n };\n\n const onPointerCancel = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n finalize(e, false);\n };\n // lostpointercapture handles the rare case where the platform yanks\n // capture (OS gesture, another setPointerCapture call). Guard on\n // pointerId \u2014 another code path could call setPointerCapture for a\n // DIFFERENT pointer on the same element, and we mustn't cancel\n // our in-progress drag because of an unrelated release.\n const onLostCapture = (e: PointerEvent) => {\n if (e.pointerId === pointerId) finalize(null, false);\n };\n\n el.addEventListener(\"pointerdown\", onPointerDown);\n el.addEventListener(\"pointermove\", onPointerMove);\n el.addEventListener(\"pointerup\", onPointerUp);\n el.addEventListener(\"pointercancel\", onPointerCancel);\n el.addEventListener(\"lostpointercapture\", onLostCapture);\n el.addEventListener(\"dragstart\", onDragStart);\n\n const cleanup = () => {\n el.removeEventListener(\"pointerdown\", onPointerDown);\n el.removeEventListener(\"pointermove\", onPointerMove);\n el.removeEventListener(\"pointerup\", onPointerUp);\n el.removeEventListener(\"pointercancel\", onPointerCancel);\n el.removeEventListener(\"lostpointercapture\", onLostCapture);\n el.removeEventListener(\"pointerleave\", onPointerLeaveCancel);\n el.removeEventListener(\"dragstart\", onDragStart);\n finalize(null, false);\n areaSelectArmed.delete(el);\n };\n\n return {\n action,\n cleanup,\n updateSend: (s) => {\n send = s;\n },\n };\n}\n\nfunction clampRange(n: number, lo: number, hi: number): number {\n if (!Number.isFinite(n) || n < lo) return lo;\n if (n > hi) return hi;\n return 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", "/**\n * Preview-mode field redaction.\n *\n * Some apps (e.g. a \"try it before you sign up\" preview) need sensitive field\n * values \u2014 passport numbers, tax IDs, draft answers \u2014 to stay in the visitor's\n * browser and never reach the server. An element opts in with the\n * `data-lvt-redact=\"<field>\"` attribute:\n *\n * <input name=\"passport\" data-lvt-redact=\"passport\">\n *\n * Two halves make the round-trip work:\n *\n * 1. Outgoing: before an action payload is sent, the raw value is written to\n * localStorage and replaced with a redact sentinel `{ redacted: true, field }`.\n * The server learns the field was provided (so it can keep structural state /\n * validate presence) but never sees the value. Two sinks cover the two send\n * transports: `redactActionData` for the JSON payload (WebSocket / HTTP-JSON)\n * and `redactFormData` for the multipart file-upload path.\n *\n * 2. Incoming (hydrateRedactedTokens): after each DOM patch, real values are\n * filled back into `[data-lvt-redact]` elements from localStorage \u2014 `.value`\n * for inputs, `textContent` for the <span> the Go `lvt.Redact` helper emits.\n * Substitution is scoped to the attribute, never a free text scan, so\n * user-posted content cannot trigger it.\n *\n * Values are namespaced by the page's `data-lvt-id` scope so two LiveTemplate\n * pages in the same origin don't collide.\n */\n\nconst STORAGE_PREFIX = \"lvt-redact\";\n\nexport interface RedactOptions {\n /** Storage backend; defaults to window.localStorage. Injectable for tests. */\n storage?: Storage;\n /** Namespace for stored values; defaults to the page's data-lvt-id. */\n scope?: string;\n}\n\n/** The sentinel that replaces a redacted value in an outgoing action payload. */\nexport interface RedactSentinel {\n redacted: true;\n field: string;\n}\n\nfunction storageKey(scope: string, field: string): string {\n return `${STORAGE_PREFIX}:${scope}:${field}`;\n}\n\n/**\n * Resolve the redaction namespace from the live DOM. Uses the wrapper's\n * `data-lvt-id` so values are scoped per page. Falls back to \"lvt-unknown\"\n * before the wrapper is wired (no value collides with a real scope).\n */\nfunction resolveScope(ownerDocument: Document | null | undefined): string {\n const doc = ownerDocument ?? (typeof document !== \"undefined\" ? document : null);\n const wrapper = doc?.querySelector?.(\"[data-lvt-id]\");\n return wrapper?.getAttribute(\"data-lvt-id\") || \"lvt-unknown\";\n}\n\nfunction getStorage(opts?: RedactOptions): Storage | null {\n if (opts?.storage) return opts.storage;\n try {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n } catch {\n // localStorage can throw (disabled cookies, sandboxed iframe). Treat as\n // absent \u2014 redaction degrades to a no-op rather than breaking the app.\n return null;\n }\n}\n\n/** Elements that carry a redactable value: <input>, <textarea>, <select>. */\nfunction hasValue(el: Element): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement {\n return (\n el instanceof HTMLInputElement ||\n el instanceof HTMLTextAreaElement ||\n el instanceof HTMLSelectElement\n );\n}\n\n/**\n * Collect every redact-tagged element at or under `root` \u2014 `root` itself when it\n * carries the attribute, plus all descendants. Used by both the outgoing path\n * (the action element may be the tagged input) and the incoming path (so a\n * tagged root is hydrated, not just its children).\n */\nfunction collectRedactElements(root: Element): Element[] {\n const found: Element[] = [];\n if (root.hasAttribute(\"data-lvt-redact\")) {\n found.push(root);\n }\n root.querySelectorAll?.(\"[data-lvt-redact]\").forEach((el) => found.push(el));\n return found;\n}\n\n/**\n * Shared outgoing-redaction core. For every redact-tagged element at/under\n * `actionElement`: persist its value to localStorage and hand the sink the\n * payload key + sentinel to write. The sink differs per transport (a JSON\n * payload object vs. a multipart FormData).\n *\n * Redaction is fail-closed: the sentinel is ALWAYS written, regardless of\n * whether persistence is possible. Persistence is the best-effort part \u2014 if\n * localStorage is entirely unavailable (disabled / sandboxed iframe) or setItem\n * throws (quota), the raw value is still dropped from the payload so it never\n * reaches the server. The security guarantee (never leak) takes priority over\n * the UX (being able to restore the value later).\n */\nfunction applyOutgoingRedaction(\n actionElement: Element,\n opts: RedactOptions | undefined,\n sink: (key: string, sentinel: RedactSentinel) => void,\n): void {\n const storage = getStorage(opts);\n const scope = storage\n ? (opts?.scope ?? resolveScope(actionElement.ownerDocument))\n : null;\n\n for (const el of collectRedactElements(actionElement)) {\n const field = el.getAttribute(\"data-lvt-redact\");\n if (!field || !hasValue(el)) continue;\n\n // Persist best-effort; both branches (no storage, setItem throws) fall\n // through to the sentinel write below \u2014 never to leaking the raw value.\n if (storage && scope) {\n try {\n storage.setItem(storageKey(scope, field), el.value);\n } catch {\n // Quota exceeded \u2014 value not persisted, but still redacted below.\n }\n }\n // The payload key the server sees is the element's `name` (falling back to\n // the redact field name, mirroring the event-delegation \"name || value\"\n // convention).\n const key = el.getAttribute(\"name\") || field;\n sink(key, { redacted: true, field });\n }\n}\n\n/**\n * Persist redacted values to localStorage and replace them in the outgoing\n * JSON action payload with a sentinel. Mutates `data` in place. Used by the\n * WebSocket / HTTP-JSON send path.\n */\nexport function redactActionData(\n actionElement: Element,\n data: Record<string, unknown>,\n opts?: RedactOptions,\n): void {\n applyOutgoingRedaction(actionElement, opts, (key, sentinel) => {\n data[key] = sentinel;\n });\n}\n\n/**\n * Persist redacted values to localStorage and replace them in an outgoing\n * multipart `FormData` with a JSON-encoded sentinel. Mutates `formData` in\n * place. Used by the Tier-1 file-upload send path (sendHTTPMultipart), which\n * bypasses the JSON payload \u2014 without this, a redacted field in a form that\n * also has a file input would POST its raw value as a multipart field.\n *\n * FormData values are strings, so the sentinel is JSON-encoded; the server\n * recognises a redacted multipart field by parsing the value and finding\n * `redacted:true` (mirroring the object sentinel on the JSON path).\n */\nexport function redactFormData(\n form: Element,\n formData: FormData,\n opts?: RedactOptions,\n): void {\n applyOutgoingRedaction(form, opts, (key, sentinel) => {\n formData.set(key, JSON.stringify(sentinel));\n });\n}\n\n/**\n * Fill every `[data-lvt-redact]` element from localStorage. Call on the LIVE\n * element after the patch commits. Substitution is scoped to elements carrying\n * the attribute \u2014 never a free text scan \u2014 so user-posted content can't trigger\n * it. Value elements (input/textarea/select) get `.value`; others (the <span>\n * the Go lvt.Redact helper emits) get `textContent`. Reads are cached per call.\n */\nexport function hydrateRedactedTokens(root: Element, opts?: RedactOptions): void {\n const storage = getStorage(opts);\n if (!storage) return;\n const scope = opts?.scope ?? resolveScope(root.ownerDocument);\n\n const cache = new Map<string, string | null>();\n const read = (field: string): string | null => {\n if (cache.has(field)) return cache.get(field)!;\n let v: string | null = null;\n try {\n v = storage.getItem(storageKey(scope, field));\n } catch {\n v = null;\n }\n cache.set(field, v);\n return v;\n };\n\n // Skip the element the user is currently editing \u2014 overwriting .value\n // mid-keystroke would clobber in-progress input (and the stored value may lag\n // what they've typed since the last dispatch).\n const active = root.ownerDocument?.activeElement ?? null;\n for (const el of collectRedactElements(root)) {\n if (el === active) continue;\n const field = el.getAttribute(\"data-lvt-redact\");\n if (!field) continue;\n const v = read(field);\n if (v === null) continue;\n if (hasValue(el)) {\n // For file inputs, el.value is the fake C:\\fakepath string, not file\n // contents \u2014 redacting file fields is out of scope for this helper.\n el.value = v;\n } else {\n el.textContent = v;\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 type { Logger } from \"./logger\";\n\n/**\n * Module-level warn-once latch shared by all `lvt-no-intercept` shim sites\n * (link interceptor + form auto-wiring + form submit handling). One warning\n * per process is enough to prompt migration without spamming the console.\n */\nlet legacyNoInterceptWarned = false;\n\n/**\n * Returns true when an element opts out of LiveTemplate interception.\n *\n * Recognizes both the current Tier 2 namespaced attribute (`newName`, e.g.\n * `lvt-nav:no-intercept` for links or `lvt-form:no-intercept` for forms) and\n * the pre-Phase 1A `lvt-no-intercept` name as a backward-compat shim. The\n * legacy name emits a one-time deprecation warning through the supplied\n * logger and is removed in v0.9.0.\n */\nexport function hasNoInterceptOptOut(\n el: Element,\n newName: string,\n logger: Logger\n): boolean {\n if (el.hasAttribute(newName)) return true;\n if (el.hasAttribute(\"lvt-no-intercept\")) {\n if (!legacyNoInterceptWarned) {\n legacyNoInterceptWarned = true;\n logger.warn(\n `lvt-no-intercept is deprecated; use ${newName}. The shim will be removed in v0.9.0.`\n );\n }\n return true;\n }\n return false;\n}\n\n/**\n * Reset the warn-once latch. Internal \u2014 test code only. Tests that exercise\n * the shim need to start from a clean state to assert that the warning fires\n * exactly once per process.\n *\n * @internal\n */\nexport function _resetLegacyNoInterceptWarned(): void {\n legacyNoInterceptWarned = false;\n}\n", "import { debounce, throttle } from \"../utils/rate-limit\";\nimport { redactActionData, redactFormData } from \"./redact\";\nimport { lvtSelector } from \"../utils/lvt-selector\";\nimport { executeAction, resolveTarget, processElementInteraction, isDOMEventTrigger, type ReactiveAction } from \"./reactive-attributes\";\nimport type { Logger } from \"../utils/logger\";\nimport { hasNoInterceptOptOut } from \"../utils/legacy-attr\";\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\n// Wire MIME for the dragged element's data-key. Set on dragstart, read on drop.\nconst LVT_DRAG_MIME = \"application/x-lvt-key\";\n\nconst DRAG_EVENTS = new Set([\n \"dragstart\",\n \"dragover\",\n \"drop\",\n \"dragend\",\n \"dragenter\",\n \"dragleave\",\n]);\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 \"dragstart\",\n \"dragover\",\n \"drop\",\n \"dragend\",\n \"dragenter\",\n \"dragleave\",\n] as const;\n\nexport class EventDelegator {\n // Track forms we've already warned about for lvt-form:emit-submitter on\n // GET. Per-form de-dup so dev consoles aren't flooded; WeakSet so forms\n // GC'd from the DOM don't pin memory.\n private warnedEmitSubmitterGETForms = new WeakSet<HTMLFormElement>();\n\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 (!hasNoInterceptOptOut(element, \"lvt-form:no-intercept\", this.logger)) {\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 } else if (element.hasAttribute(\"lvt-form:emit-submitter\")) {\n // Phase 2 of livetemplate#237: for non-intercepted (native HTML)\n // submissions, inject a hidden <input name=\"lvt-submitter\">\n // populated from SubmitEvent.submitter.name so the server can\n // route the action without falling back to the empty-value\n // heuristic. Creates on first submit if absent; updates on\n // subsequent submits. Pure no-JS forms cannot use this \u2014 they\n // keep relying on the heuristic.\n //\n // Only acts when SubmitEvent.submitter has a non-empty name,\n // matching the WS and HTTP-multipart paths above. Submits with\n // no named submitter (e.g., programmatic dispatch \u2014 out of\n // scope per the proposal) keep any previous value and fall\n // through to the heuristic for that submission.\n //\n // GET-form caveat: a `method=\"GET\"` form serializes form\n // fields into the URL query string, so `lvt-submitter` will\n // appear in the browser history bar and any shared/bookmarked\n // URLs. The directive does not guard against this \u2014 apps\n // routing GET forms with multiple submit buttons should\n // either not opt into this directive or accept the URL\n // pollution as the cost of explicit routing.\n const submitter = (e as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n if (submitter?.name) {\n // GET-form URL pollution: only warn when we'd actually\n // inject a field (named submitter). A GET form whose\n // current submission has no named submitter writes no\n // lvt-submitter, so there's nothing to pollute and no\n // warning to fire.\n if (element.method === \"get\" && !this.warnedEmitSubmitterGETForms.has(element)) {\n this.logger.warn(\n \"lvt-form:emit-submitter on a GET form serializes lvt-submitter into the URL query string, polluting browser history and any shared/bookmarked URLs. Use method=\\\"POST\\\" or remove the directive if URL pollution is unacceptable.\",\n element\n );\n this.warnedEmitSubmitterGETForms.add(element);\n }\n // Filter on type=\"hidden\" so we never mutate a developer-\n // authored visible <input name=\"lvt-submitter\"> that happens\n // to live in the form for some other purpose.\n let hiddenInput = element.querySelector<HTMLInputElement>(\n 'input[type=\"hidden\"][name=\"lvt-submitter\"]'\n );\n if (!hiddenInput) {\n hiddenInput = document.createElement(\"input\");\n hiddenInput.type = \"hidden\";\n hiddenInput.name = \"lvt-submitter\";\n element.appendChild(hiddenInput);\n }\n hiddenInput.value = submitter.name;\n } else {\n // No named submitter on this submission \u2014 clear any stale\n // hidden input left over from a previous named submit.\n // Without this, a [name=save] click followed by an unnamed\n // click would send the stale \"save\" value to the server\n // and misroute the action.\n element\n .querySelector<HTMLInputElement>(\n 'input[type=\"hidden\"][name=\"lvt-submitter\"]'\n )\n ?.remove();\n }\n }\n }\n\n if (action != null && actionElement) {\n if (eventType === \"submit\") {\n e.preventDefault();\n }\n\n // Drag side-effects run BEFORE the throttle gate below: the\n // browser only honors preventDefault and dataTransfer.setData\n // during the native event tick, so they cannot wait on a\n // throttled handleAction. dragover.preventDefault is what\n // licenses drop to fire; without it the browser silently\n // ignores drops on this target.\n if (DRAG_EVENTS.has(eventType)) {\n const dragEvent = e as DragEvent;\n if (eventType === \"dragstart\" && dragEvent.dataTransfer) {\n const keyEl = actionElement.closest(\"[data-key]\");\n const key = keyEl?.getAttribute(\"data-key\") ?? \"\";\n // Custom MIME only \u2014 never text/plain. Putting the key in\n // text/plain would leak it to any external drop target\n // (URL bar, text editor, another app).\n dragEvent.dataTransfer.setData(LVT_DRAG_MIME, key);\n dragEvent.dataTransfer.effectAllowed = \"move\";\n } else if (eventType === \"dragover\") {\n dragEvent.preventDefault();\n if (dragEvent.dataTransfer) {\n dragEvent.dataTransfer.dropEffect = \"move\";\n }\n } else if (eventType === \"drop\") {\n dragEvent.preventDefault();\n } else if (eventType === \"dragenter\" || eventType === \"dragleave\") {\n // Skip when the pointer is just crossing into / out of a\n // descendant of the same actionElement \u2014 those are noise\n // from the spec's bubble model. Only fire for boundary\n // crossings between distinct elements.\n const related = dragEvent.relatedTarget as Node | null;\n if (related && actionElement.contains(related)) {\n return;\n }\n }\n\n // Marker pattern: empty action (e.g. lvt-on:dragover=\"\" or\n // lvt-on:dragstart=\"\") opts in to the side-effects above\n // without a WS round-trip. Useful for any drag event when\n // only drop needs server-side handling.\n if (action === \"\") {\n return;\n }\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 if (submitter2.name) {\n message.submitter = submitter2.name;\n }\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 // Drop carries the source key (stashed in DataTransfer on\n // dragstart) and the target key (the drop target's nearest\n // [data-key]) \u2014 the pair a sortable controller needs.\n if (eventType === \"drop\") {\n // Source key only when our LVT MIME is set \u2014 guarantees\n // it came from a same-app dragstart. Cross-app drags\n // (text/plain only) deliberately produce no dragSourceKey\n // so controllers don't have to parse untrusted input.\n const src = (e as DragEvent).dataTransfer?.getData(LVT_DRAG_MIME);\n if (src) {\n message.data.dragSourceKey = src;\n }\n // Target key from the DOM works even when dataTransfer\n // is missing (some embedded environments).\n const tgtKey = actionElement.closest(\"[data-key]\")?.getAttribute(\"data-key\");\n if (tgtKey) {\n message.data.dragTargetKey = tgtKey;\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 if (submitter?.name) {\n tier1FormData.set(\"lvt-submitter\", submitter.name);\n }\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 // Preview-mode redaction on the multipart path: a redacted field\n // in a form that also has a file input would otherwise POST its\n // raw value as a multipart field, bypassing the JSON redaction\n // below. Redact the FormData before it leaves the browser.\n redactFormData(targetElement, tier1FormData);\n this.context.sendHTTPMultipart(\n targetElement as HTMLFormElement,\n action,\n tier1FormData\n );\n return;\n }\n\n // Preview-mode redaction: persist any data-lvt-redact values to\n // localStorage and replace them in the payload with a sentinel so\n // the raw value never reaches the server. No-op when no element is\n // tagged. Runs last so it sees the fully-built message.data.\n redactActionData(targetElement, message.data);\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", "/**\n * Hash-driven element activation (deep-linking).\n *\n * Synchronizes the URL hash fragment with the open/close state of\n * <dialog>, [popover], and <details> elements. When the hash matches\n * an element's ID, the element is activated (showModal, showPopover,\n * or open=true). When the element deactivates, the hash is cleared.\n *\n * Uses history.pushState (not location.hash) to avoid triggering\n * hashchange events that could cause double-activation errors.\n */\n\n/** @internal */\nexport function safeMatchesPopoverOpen(el: HTMLElement): boolean {\n try {\n return el.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\ninterface HashLinkHandler {\n matches(el: Element): boolean;\n isOpen(el: Element): boolean;\n open(el: Element): void;\n close(el: Element): void;\n}\n\nconst handlers: HashLinkHandler[] = [\n {\n matches: (el) => el instanceof HTMLDialogElement,\n isOpen: (el) => (el as HTMLDialogElement).open,\n open: (el) => (el as HTMLDialogElement).showModal(),\n close: (el) => (el as HTMLDialogElement).close(),\n },\n {\n matches: (el) =>\n el instanceof HTMLElement && el.hasAttribute(\"popover\"),\n isOpen: (el) => safeMatchesPopoverOpen(el as HTMLElement),\n open: (el) => {\n if (typeof (el as any).showPopover === \"function\")\n (el as HTMLElement).showPopover();\n },\n close: (el) => {\n if (typeof (el as any).hidePopover === \"function\")\n (el as HTMLElement).hidePopover();\n },\n },\n {\n matches: (el) => el instanceof HTMLDetailsElement,\n isOpen: (el) => (el as HTMLDetailsElement).open,\n open: (el) => {\n (el as HTMLDetailsElement).open = true;\n },\n close: (el) => {\n (el as HTMLDetailsElement).open = false;\n },\n },\n];\n\nfunction findHandler(el: Element): HashLinkHandler | undefined {\n return handlers.find((h) => h.matches(el));\n}\n\nconst SHOW_COMMANDS = new Set([\"show-modal\", \"show-popover\"]);\n\nfunction handleClick(e: Event): void {\n const el = e.target;\n if (!el || !(el instanceof Element)) return;\n\n const button = el.closest(\n \"button[command][commandfor]\"\n ) as HTMLButtonElement | null;\n if (!button || button.disabled) return;\n\n const command = button.getAttribute(\"command\");\n if (!command) return;\n\n const targetId = button.getAttribute(\"commandfor\");\n if (!targetId) return;\n\n const target = document.getElementById(targetId);\n if (!target) return;\n\n const handler = findHandler(target);\n if (!handler) return;\n\n if (SHOW_COMMANDS.has(command)) {\n if (location.hash === \"#\" + targetId) return;\n history.pushState(null, \"\", \"#\" + targetId);\n } else if (command === \"toggle-popover\" && !handler.isOpen(target)) {\n if (location.hash === \"#\" + targetId) return;\n history.pushState(null, \"\", \"#\" + targetId);\n }\n}\n\nfunction handleClose(e: Event): void {\n const el = e.target;\n if (!(el instanceof Element)) return;\n if (!el.id) return;\n if (!findHandler(el)) return;\n if (location.hash !== \"#\" + el.id) return;\n\n history.replaceState(null, \"\", location.pathname + location.search);\n}\n\nfunction handleToggle(e: Event): void {\n const el = e.target;\n if (!(el instanceof Element)) return;\n if (!el.id) return;\n\n const handler = findHandler(el);\n if (!handler) return;\n\n if (handler.isOpen(el)) {\n if (location.hash === \"#\" + el.id) return;\n history.pushState(null, \"\", \"#\" + el.id);\n } else {\n if (location.hash !== \"#\" + el.id) return;\n history.replaceState(null, \"\", location.pathname + location.search);\n }\n}\n\nfunction handlePopstate(): void {\n const id = location.hash.slice(1);\n\n // Only close dialogs/popovers \u2014 details elements are independent\n // (multiple can be open at once) and should not be swept closed.\n document.querySelectorAll(\"dialog, [popover]\").forEach((el) => {\n const handler = findHandler(el);\n if (handler && handler.isOpen(el) && el.id !== id) handler.close(el);\n });\n\n if (id) {\n const el = document.getElementById(id);\n if (el) {\n const handler = findHandler(el);\n if (handler && !handler.isOpen(el)) handler.open(el);\n }\n }\n}\n\nexport function openFromHash(): void {\n const id = location.hash.slice(1);\n if (!id) return;\n\n const el = document.getElementById(id);\n if (!el) return;\n\n const handler = findHandler(el);\n if (!handler) return;\n if (handler.isOpen(el)) return;\n\n handler.open(el);\n}\n\nexport function isHashLinkTarget(id: string): boolean {\n const el = document.getElementById(id);\n if (!el) return false;\n return !!findHandler(el);\n}\n\nexport function activateHashTarget(id: string): void {\n const el = document.getElementById(id);\n if (!el) return;\n\n const handler = findHandler(el);\n if (!handler || handler.isOpen(el)) return;\n\n history.pushState(null, \"\", \"#\" + id);\n handler.open(el);\n}\n\nlet installed = false;\n\nexport function setupHashLink(): void {\n if (installed) return;\n installed = true;\n\n document.addEventListener(\"click\", handleClick);\n document.addEventListener(\"close\", handleClose, true);\n document.addEventListener(\"toggle\", handleToggle, true);\n window.addEventListener(\"popstate\", handlePopstate);\n\n openFromHash();\n}\n\n/** @internal Remove all hash-link event listeners. */\nexport function teardownHashLink(): void {\n if (!installed) return;\n installed = false;\n\n document.removeEventListener(\"click\", handleClick);\n document.removeEventListener(\"close\", handleClose, true);\n document.removeEventListener(\"toggle\", handleToggle, true);\n window.removeEventListener(\"popstate\", handlePopstate);\n}\n", "import type { Logger } from \"../utils/logger\";\nimport { hasNoInterceptOptOut } from \"../utils/legacy-attr\";\nimport { isHashLinkTarget, activateHashTarget } from \"./hash-link\";\n\nexport interface LinkInterceptorContext {\n getWrapperElement(): Element | null;\n handleNavigationResponse(html: string, destinationHref: 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. The legacy lvt-no-intercept attribute is also accepted via\n * a backward-compat shim in shouldSkip(); the shim emits a one-time\n * deprecation warning and is removed in v0.9.0.\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 if (target.pathname === window.location.pathname && target.search === window.location.search && target.hash) {\n const hashId = target.hash.slice(1);\n if (hashId && isHashLinkTarget(hashId)) {\n e.preventDefault();\n activateHashTarget(hashId);\n }\n return;\n }\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. hasNoInterceptOptOut also\n // accepts the pre-Phase 1A `lvt-no-intercept` name and emits a one-time\n // deprecation warning. The shim is removed in v0.9.0.\n if (hasNoInterceptOptOut(link, \"lvt-nav:no-intercept\", this.logger)) 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, href);\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 = wrapperElement.querySelector(\"[lvt-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 *\n * Two activation paths share the same physical bar:\n *\n * 1. Initial connect \u2014 `data-lvt-loading=\"true\"` on the wrapper triggers\n * `show()` in autoInit; `hide()` fires from the first server payload.\n * 2. Per-action wait \u2014 `data-lvt-loading-debounce-ms=\"<ms>\"` on the wrapper\n * enables `enablePerActionIndicator(ms)`, which arms a timer on\n * `lvt:pending` (capture-phase) and hides on `lvt:updated`. Idempotent.\n */\nexport class LoadingIndicator {\n private bar: HTMLElement | null = null;\n private actionTimer: ReturnType<typeof setTimeout> | null = null;\n // Counts in-flight actions so concurrent server roundtrips don't hide\n // the bar prematurely: the bar stays visible as long as at least one\n // `lvt:pending` is outstanding. Without this counter, action B\n // completing first would clear the bar even though action A is still\n // in flight.\n private pendingCount = 0;\n private pendingHandler: ((ev: Event) => void) | null = null;\n private updatedHandler: ((ev: Event) => void) | null = null;\n // Current per-action debounce \u2014 tracked so a follow-up enable call\n // with a different value reconfigures cleanly instead of silently\n // dropping the new value.\n private currentDebounceMs: number | null = null;\n\n show(): void {\n if (this.bar) return;\n\n const bar = document.createElement(\"div\");\n bar.className = \"lvt-loading-bar\";\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 * Show the loading bar after `debounceMs` of an action being in flight;\n * hide on the next `lvt:updated`. Capture-phase listeners on document\n * catch both events regardless of dispatch target. Safe to call more\n * than once \u2014 repeat calls are no-ops.\n */\n enablePerActionIndicator(debounceMs: number): void {\n // Same value as already enabled \u2192 no-op (idempotent).\n // Different value \u2192 tear down and re-register cleanly. This\n // matters for callers that re-read the debounce attribute after\n // a config change; without it, the second call would silently\n // discard the new value.\n if (this.pendingHandler) {\n if (this.currentDebounceMs === debounceMs) return;\n this.disablePerActionIndicator();\n }\n this.pendingCount = 0;\n this.currentDebounceMs = debounceMs;\n\n this.pendingHandler = () => {\n this.pendingCount++;\n // Only arm a debounce timer on the 0\u21921 transition. Subsequent\n // concurrent actions don't reset the timer or re-show the bar \u2014\n // the bar that is or will become visible already represents \"at\n // least one action in flight\".\n if (\n this.pendingCount === 1 &&\n this.actionTimer === null &&\n this.bar === null\n ) {\n this.actionTimer = setTimeout(() => {\n this.actionTimer = null;\n // Re-check pendingCount at fire time. In single-threaded JS\n // `updatedHandler` would have called `clearTimeout` before the\n // count could reach zero, so this guard is defensive: it\n // documents the invariant and protects against any future\n // reordering of teardown vs. show.\n if (this.pendingCount > 0) this.show();\n }, debounceMs);\n }\n };\n this.updatedHandler = () => {\n // Math.max guards against an `lvt:updated` arriving without a\n // matching `lvt:pending` (e.g. a server push, or an action whose\n // pending event was dispatched before the listener was attached).\n this.pendingCount = Math.max(0, this.pendingCount - 1);\n if (this.pendingCount === 0) {\n if (this.actionTimer !== null) {\n clearTimeout(this.actionTimer);\n this.actionTimer = null;\n }\n this.hide();\n }\n };\n document.addEventListener(\"lvt:pending\", this.pendingHandler, true);\n document.addEventListener(\"lvt:updated\", this.updatedHandler, true);\n }\n\n /**\n * Teardown for `enablePerActionIndicator`. Stops the listeners, cancels\n * any pending debounce timer, resets the counter, and hides the bar\n * if it's currently visible. Hiding is part of teardown so a caller\n * that reconfigures (different debounce \u2192 disable + re-enable) doesn't\n * leave the prior cycle's bar orphaned waiting for an `lvt:updated`\n * that no listener will receive.\n *\n * Production callers reach this path via two routes: an explicit\n * reconfigure inside `enablePerActionIndicator`, and the LiveTemplate\n * client's `disconnect()` teardown.\n */\n disablePerActionIndicator(): void {\n if (this.pendingHandler) {\n document.removeEventListener(\"lvt:pending\", this.pendingHandler, true);\n this.pendingHandler = null;\n }\n if (this.updatedHandler) {\n document.removeEventListener(\"lvt:updated\", this.updatedHandler, true);\n this.updatedHandler = null;\n }\n if (this.actionTimer !== null) {\n clearTimeout(this.actionTimer);\n this.actionTimer = null;\n }\n this.pendingCount = 0;\n this.currentDebounceMs = null;\n this.hide();\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\"> and\n * popover commands to work cross-browser.\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) return;\n\n if (target instanceof HTMLDialogElement) {\n if (command === \"show-modal\" && !target.open) {\n target.showModal();\n } else if (command === \"close\" && target.open) {\n target.close();\n }\n } else if (target instanceof HTMLElement && target.hasAttribute(\"popover\")) {\n if (command === \"show-popover\" && typeof target.showPopover === \"function\") {\n target.showPopover();\n } else if (command === \"hide-popover\" && typeof target.hidePopover === \"function\") {\n target.hidePopover();\n } else if (command === \"toggle-popover\" && typeof target.togglePopover === \"function\") {\n target.togglePopover();\n }\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 { resolveTarget } from \"./reactive-attributes\";\n\ninterface ScrollAwayBinding {\n trigger: Element;\n target: Element;\n handler: () => void;\n}\n\nconst GUARD_KEY = \"__lvt_scroll_away\";\n\nconst activeBindings: ScrollAwayBinding[] = [];\n\nfunction pruneDisconnectedBindings(): void {\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const binding = activeBindings[i];\n if (binding.trigger.isConnected) continue;\n binding.target.removeEventListener(\"scroll\", binding.handler);\n delete (binding.trigger as any)[GUARD_KEY];\n activeBindings.splice(i, 1);\n }\n}\n\nexport function setupScrollAway(scanRoot: Element): void {\n pruneDisconnectedBindings();\n\n const processEl = (el: Element) => {\n const edge = el.getAttribute(\"lvt-scroll-away\");\n if (!edge) return;\n if (edge !== \"bottom\" && edge !== \"top\") {\n console.warn(`Unknown lvt-scroll-away edge: ${edge}`);\n return;\n }\n\n const target = resolveTarget(el) as HTMLElement;\n if (!target || target === el) {\n const existing = (el as any)[GUARD_KEY] as ScrollAwayBinding | undefined;\n if (existing) {\n existing.target.removeEventListener(\"scroll\", existing.handler);\n removeBinding(existing);\n delete (el as any)[GUARD_KEY];\n }\n console.warn(\"lvt-scroll-away requires data-lvt-target pointing to a scrollable container\");\n return;\n }\n\n const existing = (el as any)[GUARD_KEY] as ScrollAwayBinding | undefined;\n if (existing) {\n if (existing.target === target) return;\n existing.target.removeEventListener(\"scroll\", existing.handler);\n removeBinding(existing);\n }\n\n const raw = parseInt(\n getComputedStyle(el).getPropertyValue(\"--lvt-scroll-threshold\").trim(), 10\n );\n const threshold = isNaN(raw) ? 200 : raw;\n\n let ticking = false;\n const handler = () => {\n if (ticking) return;\n ticking = true;\n requestAnimationFrame(() => {\n ticking = false;\n const distance = edge === \"top\"\n ? target.scrollTop\n : target.scrollHeight - target.scrollTop - target.clientHeight;\n if (distance > threshold) {\n el.classList.add(\"visible\");\n } else {\n el.classList.remove(\"visible\");\n }\n });\n };\n\n target.addEventListener(\"scroll\", handler, { passive: true });\n handler();\n\n const binding: ScrollAwayBinding = { trigger: el, target, handler };\n (el as any)[GUARD_KEY] = binding;\n activeBindings.push(binding);\n };\n\n processEl(scanRoot);\n scanRoot.querySelectorAll(\"[lvt-scroll-away]\").forEach(processEl);\n}\n\nfunction removeBinding(binding: ScrollAwayBinding): void {\n const idx = activeBindings.indexOf(binding);\n if (idx !== -1) activeBindings.splice(idx, 1);\n}\n\nexport function teardownScrollAway(wrapper?: Element): void {\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const binding = activeBindings[i];\n if (wrapper && binding.trigger.isConnected && !wrapper.contains(binding.trigger)) continue;\n binding.target.removeEventListener(\"scroll\", binding.handler);\n binding.trigger.classList.remove(\"visible\");\n delete (binding.trigger as any)[GUARD_KEY];\n activeBindings.splice(i, 1);\n }\n}\n", "/**\n * Scroll-spy directive.\n *\n * Highlights navigation links as the user scrolls past corresponding section\n * targets. Pure client-side state \u2014 no server round-trip per scroll tick.\n *\n * <article lvt-spy=\"h1, h2, h3\"> // container mode: descendants\n * <h1 id=\"intro\">Intro</h1> matching the selector are\n * <h2 id=\"usage\">Usage</h2> the spy targets.\n * </article>\n *\n * <h2 id=\"other\" lvt-spy>Other</h2> // element mode: this element\n * IS the target. Empty value.\n *\n * <a href=\"#intro\" lvt-spy-link>Intro</a> // gets the `lvt-active` class\n * <a href=\"#usage\" lvt-spy-link>Usage</a> when its href=\"#<id>\" matches\n * the currently active target.\n *\n * Activation rule: walk targets in document order; the latest one whose top\n * edge has scrolled above a trigger line near the top of the viewport is\n * active. That way the first link stays active until the reader has actually\n * passed the first heading, and the active link advances in step with the\n * reader.\n *\n * Configuration via CSS custom property on the spy container:\n * --lvt-spy-margin: <length> (default: 25vh from the top)\n * The trigger line below the viewport top. A target counts as \"passed\"\n * once its top edge is at or above this line.\n *\n * Implementation: a rAF-throttled scroll listener on the nearest scrollable\n * ancestor (or window). Mirrors the lifecycle shape of `scroll-away.ts` so\n * the morphdom re-scan story is identical.\n */\n\nconst ACTIVE_CLASS = \"lvt-active\";\nconst BINDING_KEY = \"__lvt_spy\";\nconst LINK_HANDLER_KEY = \"__lvt_spy_link_handler\";\n\ninterface SpyBinding {\n container: Element;\n targets: Element[];\n // Pre-computed trigger-line distance from viewport top, in px.\n // Cached so the rAF-throttled scroll handler doesn't pay for a\n // synchronous style recalc (`getComputedStyle`) on every tick. The\n // window-resize listener re-computes this whenever the viewport\n // height changes (vh-based values shift).\n marginPx: number;\n // Pre-filtered list of [lvt-spy-link] elements whose href matches\n // one of `targets`. Cached so applyActive \u2014 called on every rAF\n // tick \u2014 doesn't do a fresh document.querySelectorAll each frame.\n // Refreshed on attach and on every processContainer pass\n // (setupSpy runs after each render), so morphdom-driven link\n // additions/removals are picked up at the next render cycle.\n links: Element[];\n scrollTarget: HTMLElement | Window;\n scrollHandler: () => void;\n resizeHandler: () => void;\n}\n\n// `activeBindings` is module-level \u2014 i.e. shared by every consumer\n// of this client bundle. In single-instance setups (the common case)\n// that's a non-issue. With multiple LiveTemplateClient mounts sharing\n// the same bundle module instance, the per-container `BINDING_KEY`\n// guard keeps each binding isolated and `teardownSpy(wrapper)`\n// touches only the wrapped subset, so the shared list is safe in\n// practice. Listed here so a future audit reading \"two clients\n// stomping each other\" finds the design intent.\nconst activeBindings: SpyBinding[] = [];\n\n// Tracks spy-target elements we've already warned about (missing id).\n// processContainer re-runs detach+attach whenever the target set\n// changes, so without per-element dedup the warning would spam the\n// console on every morphdom render that includes an id-less target.\n// WeakSet so cleared targets eventually get GC'd with the DOM nodes.\nconst warnedMissingId = new WeakSet<Element>();\n\nfunction pruneDisconnectedBindings(): void {\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const b = activeBindings[i];\n if (b.container.isConnected) continue;\n detach(b);\n activeBindings.splice(i, 1);\n }\n // Mirror teardownSpy's click-handler gate so a container removed\n // outside the framework lifecycle (direct DOM removal that never\n // calls teardownSpy) still ends with the document-level click\n // listener unhooked once the last binding is gone \u2014 instead of\n // leaving an orphaned no-op handler bound to the document.\n if (activeBindings.length === 0) {\n const handler = (document as any)[LINK_HANDLER_KEY];\n if (handler) {\n document.removeEventListener(\"click\", handler);\n delete (document as any)[LINK_HANDLER_KEY];\n }\n }\n}\n\n// detach fully removes a binding's effects from the page: event\n// listeners, the per-container guard, AND any lvt-active classes the\n// binding had applied to its links. Folding the class-clear into detach\n// means every caller (processContainer's re-attach, prune of\n// disconnected containers, teardownSpy) gets the cleanup for free \u2014\n// without it, a stale lvt-active leaked anywhere detach was called but\n// teardownSpy's surviving-id sweep wasn't (e.g. on every morphdom\n// update that removed a target).\nfunction detach(b: SpyBinding): void {\n applyActive(b, null);\n b.scrollTarget.removeEventListener(\n \"scroll\",\n b.scrollHandler as EventListener,\n );\n window.removeEventListener(\"resize\", b.resizeHandler as EventListener);\n delete (b.container as any)[BINDING_KEY];\n}\n\nfunction readMarginPx(container: Element): number {\n const raw = getComputedStyle(container).getPropertyValue(\"--lvt-spy-margin\").trim();\n const fallback = Math.round(window.innerHeight * 0.25);\n if (!raw) return fallback;\n const n = parseFloat(raw);\n if (isNaN(n)) return fallback;\n // Supported units: bare px (`200` or `200px`) and vh (`25vh`). Other\n // CSS units (rem, em, %, etc.) come through `getComputedStyle` as raw\n // strings, NOT resolved \u2014 `parseFloat(\"2rem\")` returns 2 and we'd\n // silently treat it as 2 px, which is wildly off. Reject explicitly\n // and warn so the author fixes the declaration.\n if (raw.endsWith(\"vh\")) return Math.round((n / 100) * window.innerHeight);\n // Accept \"200\" or \"200.0\" as unitless px (parseFloat strips zeros so\n // `raw === String(n)` would miss the latter). The regex pins the\n // entire string to a signed number, matching CSS's <number> grammar.\n if (raw.endsWith(\"px\") || /^-?\\d+(\\.\\d+)?$/.test(raw)) return n;\n console.warn(\n `lvt-spy: unsupported --lvt-spy-margin unit ${JSON.stringify(raw)}; supported units are vh and px (or unitless). Falling back to 25vh.`\n );\n return fallback;\n}\n\nfunction collectTargets(container: Element): Element[] {\n const selector = container.getAttribute(\"lvt-spy\");\n if (selector && selector.trim() !== \"\") {\n // Guard against typos in author-supplied selectors. An invalid\n // selector (e.g. `lvt-spy=\"h1, h2,\"` \u2014 trailing comma) makes\n // querySelectorAll throw SyntaxError, which would propagate out of\n // setupSpy and abort directive initialization for the whole scan\n // root. Warn and treat as empty so the rest of the page still works.\n try {\n return Array.from(container.querySelectorAll(selector));\n } catch (e) {\n console.warn(`lvt-spy: invalid selector ${JSON.stringify(selector)}:`, e);\n return [];\n }\n }\n return [container];\n}\n\n// applyActive updates lvt-active state for the links that belong to a\n// single binding. Ownership is decided by href matching one of the\n// binding's target ids \u2014 links pointing elsewhere (e.g. to a\n// neighbouring spy container's targets) are left untouched. This is\n// what lets multiple LiveTemplateClient mounts coexist on one page\n// without their scroll-spy state stomping each other.\n//\n// Hot path: called from the rAF-throttled scroll handler. Iterates the\n// pre-cached `binding.links` rather than re-querying the DOM each\n// frame; the cache is refreshed by refreshLinks() at attach time and\n// on every processContainer pass.\nfunction applyActive(binding: SpyBinding, activeId: string | null): void {\n for (const link of binding.links) {\n const href = link.getAttribute(\"href\") || \"\";\n const id = href.startsWith(\"#\") ? href.slice(1) : \"\";\n if (activeId !== null && id === activeId) {\n link.classList.add(ACTIVE_CLASS);\n } else {\n link.classList.remove(ACTIVE_CLASS);\n }\n }\n}\n\n// refreshLinks rebuilds binding.links to the current set of\n// [lvt-spy-link] elements in the document whose href points to one of\n// the binding's target ids. Called once per render cycle (cheap),\n// never per scroll tick (would defeat the cache).\nfunction refreshLinks(binding: SpyBinding): void {\n const ownIds = new Set<string>();\n for (const t of binding.targets) {\n if (t.id) ownIds.add(t.id);\n }\n const matched: Element[] = [];\n document.querySelectorAll(\"[lvt-spy-link]\").forEach((link) => {\n const href = link.getAttribute(\"href\") || \"\";\n const id = href.startsWith(\"#\") ? href.slice(1) : \"\";\n if (ownIds.has(id)) matched.push(link);\n });\n binding.links = matched;\n}\n\n// findBindingForId locates the binding that owns the given id, i.e.\n// has a target element whose id matches. Used by the optimistic click\n// handler so a click on a link only updates its own binding's scope \u2014\n// other bindings' active links stay put.\nfunction findBindingForId(id: string): SpyBinding | null {\n for (const b of activeBindings) {\n for (const t of b.targets) {\n if (t.id === id) return b;\n }\n }\n return null;\n}\n\nfunction pickActive(targets: Element[], marginPx: number): string | null {\n let activeId: string | null = null;\n // Walk every target. We deliberately do NOT bail early on the first\n // one below the trigger line \u2014 that optimisation assumes visual\n // top-to-bottom order (which holds for typical document flow) but\n // silently produces the wrong active link when targets are reordered\n // via CSS (sticky headers with negative top, transform: translateY,\n // flex/grid order). The full walk is O(n) with n = number of TOC\n // entries \u2014 small in practice, dwarfed by the rAF tick itself.\n for (const t of targets) {\n if (!t.id) continue;\n const top = t.getBoundingClientRect().top;\n if (top <= marginPx) {\n activeId = t.id;\n }\n }\n return activeId;\n}\n\nfunction findScrollTarget(el: Element): HTMLElement | Window {\n // Walk up to the nearest ancestor whose computed overflow-y is auto or\n // scroll. If none, the document scrolls \u2014 return window. Covers both\n // the common whole-document case and apps where a flex child scrolls\n // independently (prereview's main.viewer).\n let cur: Element | null = el.parentElement;\n while (cur && cur !== document.documentElement) {\n const oy = getComputedStyle(cur).overflowY;\n // 'overlay' is a non-standard Chromium value that behaves like\n // 'auto' but draws the scrollbar over the content. Apps that opt\n // into it (a common iOS-feel scrollbar pattern) would otherwise\n // be walked past and end up scrolling the document \u2014 wrong root,\n // wrong trigger line.\n if (oy === \"auto\" || oy === \"scroll\" || oy === \"overlay\") return cur as HTMLElement;\n cur = cur.parentElement;\n }\n return window;\n}\n\nfunction sameTargets(a: Element[], b: Element[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction attach(container: Element, preCollected?: Element[]): void {\n // Accept an optional pre-collected target list so processContainer's\n // re-attach path doesn't pay for collectTargets twice (once for the\n // diff check, once inside this function). Low-volume code path\n // (per-render, not per-tick) but easy to dedupe.\n const targets = preCollected ?? collectTargets(container);\n if (targets.length === 0) return;\n\n // Surface authoring mistakes once-per-element at attach time rather\n // than letting them silently produce a TOC entry that never lights\n // up. A target matched by the spy selector but missing `id` cannot\n // be the destination of `<a href=\"#...\">`, so no link can ever\n // activate for it. The WeakSet dedup is what keeps re-attach (which\n // fires on every morphdom render that adds/removes a heading) from\n // spamming the console \u2014 once we've warned about a given element,\n // we don't warn about it again, but a NEW id-less target on a later\n // render still gets surfaced.\n const missingId = targets.filter((t) => !t.id && !warnedMissingId.has(t));\n if (missingId.length > 0) {\n console.warn(\n `lvt-spy: ${missingId.length} target(s) without an id attribute; they cannot be linked from [lvt-spy-link]. Add id=\"...\" or drop them from the selector. First offender:`,\n missingId[0],\n );\n for (const t of missingId) warnedMissingId.add(t);\n }\n\n const binding: SpyBinding = {\n container,\n targets,\n marginPx: readMarginPx(container),\n links: [],\n scrollTarget: findScrollTarget(container),\n // scrollHandler/resizeHandler are populated below \u2014 declared here so\n // the closures can reference `binding` for the cached margin lookup.\n scrollHandler: () => {},\n resizeHandler: () => {},\n };\n refreshLinks(binding);\n\n let ticking = false;\n binding.scrollHandler = () => {\n if (ticking) return;\n ticking = true;\n requestAnimationFrame(() => {\n ticking = false;\n applyActive(binding, pickActive(binding.targets, binding.marginPx));\n });\n };\n binding.resizeHandler = () => {\n // The trigger line is vh-relative (or px), so on viewport resize\n // we need to recompute. Refresh active immediately because resize\n // doesn't fire `scroll`, so the rAF path won't otherwise reconcile.\n binding.marginPx = readMarginPx(binding.container);\n applyActive(binding, pickActive(binding.targets, binding.marginPx));\n };\n\n binding.scrollTarget.addEventListener(\"scroll\", binding.scrollHandler as EventListener, { passive: true });\n window.addEventListener(\"resize\", binding.resizeHandler as EventListener, { passive: true });\n\n (container as any)[BINDING_KEY] = binding;\n activeBindings.push(binding);\n\n // Initial synchronous pick so the right link is highlighted on first\n // paint, before any scroll event fires.\n applyActive(binding, pickActive(binding.targets, binding.marginPx));\n}\n\nfunction processContainer(container: Element): void {\n const existing = (container as any)[BINDING_KEY] as SpyBinding | undefined;\n if (existing) {\n const fresh = collectTargets(container);\n if (sameTargets(existing.targets, fresh)) {\n // No structural change \u2014 just re-pick in case scroll position\n // shifted via a non-scroll mechanism (history restore, etc.).\n // Also refresh the cached margin in case CSS variables changed\n // between renders.\n existing.marginPx = readMarginPx(container);\n // Targets stayed put but the link soup may have shifted (morphdom\n // could have added a TOC entry without touching headings). Refresh\n // the cached link set every render so the hot-path applyActive\n // never goes stale.\n refreshLinks(existing);\n applyActive(existing, pickActive(existing.targets, existing.marginPx));\n return;\n }\n detach(existing);\n const idx = activeBindings.indexOf(existing);\n if (idx !== -1) activeBindings.splice(idx, 1);\n // We already collected `fresh` above for the diff check \u2014 hand it\n // to attach() so it doesn't re-walk the DOM.\n attach(container, fresh);\n return;\n }\n attach(container);\n}\n\nfunction installLinkClickHandler(): void {\n if ((document as any)[LINK_HANDLER_KEY]) return;\n // Optimistic activation: clicking a link instantly applies lvt-active\n // to it. The next scroll-driven pick reconciles. Without this, clicking\n // the *last* heading (which may never become topmost-visible if the\n // doc ends shortly after it) would leave some earlier link highlighted\n // even though the user just asked to be at the last heading.\n const handler = (e: Event) => {\n const link = (e.target as Element | null)?.closest(\"[lvt-spy-link]\");\n if (!link) return;\n const href = link.getAttribute(\"href\") || \"\";\n const id = href.startsWith(\"#\") ? href.slice(1) : \"\";\n if (!id) return;\n // Route the optimistic flip to the binding that actually owns this\n // id. Otherwise clicking a link in TOC A would clear TOC B's\n // active highlight in a multi-instance layout.\n const owner = findBindingForId(id);\n if (owner) applyActive(owner, id);\n };\n // passive: true \u2014 we never preventDefault on the click (the native\n // <a href=\"#\u2026\"> scroll is what we want), so promising passive lets\n // the browser dispatch without parking the main thread waiting for\n // a possible cancel.\n document.addEventListener(\"click\", handler, { passive: true });\n (document as any)[LINK_HANDLER_KEY] = handler;\n}\n\nexport function setupSpy(scanRoot: Element): void {\n pruneDisconnectedBindings();\n installLinkClickHandler();\n\n if (scanRoot.hasAttribute(\"lvt-spy\")) {\n processContainer(scanRoot);\n }\n scanRoot.querySelectorAll(\"[lvt-spy]\").forEach(processContainer);\n}\n\nexport function teardownSpy(wrapper?: Element): void {\n // detach() now clears each binding's lvt-active classes as part of\n // its contract, so there's no separate global sweep here \u2014 surviving\n // bindings keep their highlights, and removed bindings drop theirs.\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const b = activeBindings[i];\n if (wrapper && b.container.isConnected && !wrapper.contains(b.container)) {\n continue;\n }\n detach(b);\n activeBindings.splice(i, 1);\n }\n // Detach the document-level optimistic-click handler when there are\n // no more live spy bindings. Gating on `activeBindings.length === 0`\n // (instead of `!wrapper`) covers the common case where the framework\n // always passes a wrapper to teardown: without this gate the click\n // handler outlives every binding, and any subsequent [lvt-spy-link]\n // click silently re-applies lvt-active with no scroll reconciliation\n // to undo it.\n if (activeBindings.length === 0) {\n const handler = (document as any)[LINK_HANDLER_KEY];\n if (handler) {\n document.removeEventListener(\"click\", handler);\n delete (document as any)[LINK_HANDLER_KEY];\n }\n }\n}\n", "import type { TargetedRangeOp, TreeNode, UpdateResult } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\n/**\n * Optional context for `applyUpdate` that opts into per-op targeted DOM\n * mutation. When `canApplyTargeted` returns true for a top-level diff-op\n * key, applyUpdate mutates treeState in place (no deepClone) and emits\n * a `TargetedRangeOp` in the result. The caller is then responsible for\n * applying the op directly to the live DOM (typically via RangeDomApplier).\n *\n * The corresponding range subtree is replaced in `result.html` with a\n * `<!--lvt-targeted-skip:${rangePath}-->` placeholder so that morphdom\n * can be told to short-circuit that subtree.\n */\nexport interface ApplyUpdateOptions {\n canApplyTargeted?: (rangeStructure: any, rangePath: string) => boolean;\n}\n\ninterface RangeStateEntry {\n items: any[];\n statics: any[];\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(\n update: TreeNode,\n opts?: ApplyUpdateOptions\n ): UpdateResult {\n let changed = false;\n const targetedOps: TargetedRangeOp[] = [];\n const skipPaths = new Set<string>();\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 const targetedEligible =\n opts?.canApplyTargeted?.(existing, key) === true;\n if (targetedEligible) {\n // Mutate in place \u2014 applyDifferentialOpsToRange uses splice/push/unshift\n // on existing.d, so no fresh array is created and treeState[key]\n // remains the same object reference (now with updated contents).\n this.applyDifferentialOpsToRange(existing, value, key);\n targetedOps.push({\n rangePath: key,\n ops: value,\n statics: existing.s,\n idKey: existing.m?.idKey,\n });\n skipPaths.add(key);\n } else {\n // Apply differential operations to a clone (defensive, fallback path)\n this.treeState[key] = deepClone(existing);\n this.applyDifferentialOpsToRange(this.treeState[key], value, key);\n }\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(\n this.treeState,\n \"\",\n skipPaths.size > 0 ? skipPaths : undefined\n );\n const result: UpdateResult = { html, changed };\n if (targetedOps.length > 0) {\n result.targetedOps = targetedOps;\n }\n return result;\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 /**\n * Re-render the current treeState as full HTML, with no targeted-apply\n * skip placeholders. Used by `LiveTemplateClient.updateDOM` as a fallback\n * when one or more targeted-apply DOM mutations failed: the treeState is\n * authoritative, so a full rebuild + morphdom pass restores consistency.\n */\n renderState(): string {\n return this.reconstructFromTree(this.treeState, \"\");\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 };\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 };\n }\n\n private reconstructFromTree(\n node: TreeNode,\n statePath: string,\n skipPaths?: Set<string>\n ): 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\n // Targeted-apply skip mechanism: emit a comment placeholder that\n // the client's updateDOM walks for, converting to the live\n // container's data-lvt-targeted-skip marker so morphdom can\n // short-circuit the subtree.\n if (skipPaths && skipPaths.has(newStatePath)) {\n html += `<!--lvt-targeted-skip:${newStatePath}-->`;\n continue;\n }\n\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 };\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 } = 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 if (statics && Array.isArray(statics)) {\n return dynamics\n .map((item: any, itemIdx: number) =>\n this.renderRangeItem(item, itemIdx, statics, statePath)\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 /**\n * Renders a single range item to HTML by interleaving its dynamic slots\n * with the range's statics.\n *\n * Used by `renderRangeStructure` and `renderItemsWithStatics` for full\n * range rendering, and by `range-dom-applier` to render a single new\n * or updated item for targeted DOM mutations.\n */\n renderRangeItem(\n item: any,\n itemIdx: number,\n statics: string[],\n statePath?: string\n ): string {\n const itemStatics = statics;\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\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 };\n\n this.treeState[statePath] = {\n d: currentItems,\n s: rangeData.statics,\n };\n\n const rangeStructure = this.getCurrentRangeStructure(statePath);\n if (rangeStructure && rangeStructure.s) {\n return this.renderItemsWithStatics(\n currentItems,\n rangeStructure.s,\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 };\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 statePath?: string\n ): string {\n const result = items\n .map((item: any, itemIdx: number) =>\n this.renderRangeItem(item, itemIdx, statics, statePath)\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 morphdom from \"morphdom\";\nimport type { Logger } from \"../utils/logger\";\nimport type { TargetedRangeOp } from \"../types\";\n\nconst KEY_ATTRIBUTES = [\"data-key\", \"data-lvt-key\"] as const;\n// Pre-compile attribute-presence regexes once at module load \u2014 these were\n// previously rebuilt on every staticsContainKeyAttribute call (per static\n// segment, per attribute) which showed up in profiling on initial render.\nconst KEY_ATTR_REGEXES: RegExp[] = KEY_ATTRIBUTES.map(\n (attr) => new RegExp(`(?:^|[\\\\s<])${attr}\\\\s*=`)\n);\nexport const TARGETED_APPLIED_ATTR = \"data-lvt-targeted-applied\";\nexport const TARGETED_SKIP_ATTR = \"data-lvt-targeted-skip\";\n\ntype RenderItemFn = (\n item: any,\n itemIdx: number,\n statics: string[],\n statePath?: string\n) => string;\n\ntype LifecycleHookFn = (el: Element, hookName: string) => void;\ntype NodeAddedFn = (el: Element) => void;\ntype ItemLookupFn = (rangePath: string, key: string) => any;\n\nexport interface RangeDomApplierContext {\n logger: Logger;\n renderItem: RenderItemFn;\n executeLifecycleHook: LifecycleHookFn;\n /**\n * Look up the current item state for the given range path + key. Used by\n * the `u` op to render the FULL post-merge item (treeState is mutated in\n * place by `applyDifferentialOpsToRange` before the applier runs).\n *\n * Required: an applier without an `itemLookup` would silently no-op every\n * `u` op, leaving the live DOM stale while morphdom's skip marker\n * prevents the fallback diff from running. The constructor enforces this.\n */\n itemLookup: ItemLookupFn;\n /**\n * Notification that the applier inserted a new element into the live DOM\n * (i/a/p ops). Lets the caller track per-render DOM additions so it can\n * decide whether the post-render directive scans need to walk the wrapper.\n */\n onNodeAdded?: NodeAddedFn;\n}\n\nexport interface CanApplyResult {\n ok: boolean;\n reason?: string;\n container?: Element;\n containerKey?: string;\n}\n\n/**\n * Applies range diff ops directly to the live DOM, bypassing full HTML\n * reconstruction + morphdom diff. Designed to handle the common case where\n * a 10k-row range receives a single-row mutation; the targeted path turns\n * what would be a 5+ second morphdom walk into a sub-millisecond DOM op.\n *\n * The applier is opt-in per range: `canApplyTargeted` checks that the\n * range has data-key emission, no nested-range items, and a resolvable\n * container element. When any check fails, the caller falls back to the\n * existing applyUpdate \u2192 reconstructFromTree \u2192 morphdom path.\n */\nexport class RangeDomApplier {\n private containerCache = new Map<string, Element>();\n\n constructor(private readonly ctx: RangeDomApplierContext) {}\n\n invalidate(): void {\n this.containerCache.clear();\n }\n\n invalidatePath(rangePath: string): void {\n this.containerCache.delete(rangePath);\n }\n\n /**\n * Locate the live container element for a range path. The container is\n * the parent element of items rendered with data-key. Cached per path;\n * cache invalidated automatically when a cached element becomes detached.\n *\n * Resolution order:\n * 1. Cached container (if still connected to the wrapper).\n * 2. `wrapper.querySelector('[data-key=\"anyKnownItemKey\"]').parentElement`.\n *\n * The original implementation also fell back to an unscoped\n * `wrapper.querySelector('[data-key]')` walk, but that could return a\n * container belonging to a *different* keyed range when the wrapper has\n * more than one \u2014 silently mutating the wrong DOM subtree on subsequent\n * ops. We now prefer to fail closed (return null \u2192 caller falls back to\n * full rebuild) over mutating an unrelated container.\n */\n findContainer(\n wrapper: Element,\n rangePath: string,\n anyKnownItemKey?: string\n ): Element | null {\n const cached = this.containerCache.get(rangePath);\n if (cached && cached.isConnected && wrapper.contains(cached)) {\n return cached;\n }\n if (cached) {\n this.containerCache.delete(rangePath);\n }\n\n if (anyKnownItemKey === undefined) {\n return null;\n }\n const sample = this.findItemByKey(wrapper, anyKnownItemKey);\n if (!sample || !sample.parentElement) {\n return null;\n }\n\n const container = sample.parentElement;\n this.containerCache.set(rangePath, container);\n return container;\n }\n\n /**\n * Decide whether a range update can take the targeted-apply path.\n * Returns the resolved container in the success case so the caller\n * can pass it to `apply` without re-resolving.\n */\n canApplyTargeted(\n wrapper: Element,\n rangeStructure: any,\n rangePath: string\n ): CanApplyResult {\n if (!rangeStructure || typeof rangeStructure !== \"object\") {\n return { ok: false, reason: \"no range structure\" };\n }\n if (!Array.isArray(rangeStructure.s) || rangeStructure.s.length === 0) {\n return { ok: false, reason: \"no statics\" };\n }\n\n const allStatics: string[][] = [rangeStructure.s];\n if (rangeStructure.sm && typeof rangeStructure.sm === \"object\") {\n for (const sm of Object.values(rangeStructure.sm)) {\n if (Array.isArray(sm)) {\n allStatics.push(sm as string[]);\n }\n }\n }\n\n const hasKeyInStatics = allStatics.some((arr) =>\n this.staticsContainKeyAttribute(arr)\n );\n if (!hasKeyInStatics) {\n return { ok: false, reason: \"no data-key attribute in statics\" };\n }\n\n const items = rangeStructure.d;\n if (Array.isArray(items)) {\n for (const item of items) {\n if (this.itemHasNestedRange(item)) {\n return { ok: false, reason: \"nested-range item\" };\n }\n }\n }\n\n const sampleKey = this.extractItemKey(items?.[0], rangeStructure);\n const container = this.findContainer(wrapper, rangePath, sampleKey);\n if (!container) {\n return { ok: false, reason: \"container not found in DOM\" };\n }\n\n // Walk up from container through wrapper (inclusive) \u2014 if any element\n // on the path is lvt-ignore'd, the targeted-apply path would mutate\n // DOM inside an ignored subtree while morphdom would have skipped it,\n // violating the lvt-ignore contract.\n let cur: Element | null = container;\n while (cur) {\n if (cur.hasAttribute(\"lvt-ignore\")) {\n return { ok: false, reason: \"lvt-ignore ancestor\" };\n }\n if (cur === wrapper) break;\n cur = cur.parentElement;\n }\n\n return { ok: true, container, containerKey: sampleKey };\n }\n\n /**\n * Apply a single targeted op to the live DOM. Returns the affected\n * container element so the caller can mark it for the morphdom skip\n * mechanism. Returns null if the op could not be applied (caller\n * should fall back to full-rebuild for the next render).\n */\n apply(\n wrapper: Element,\n targetedOp: TargetedRangeOp,\n morphdomOptions?: any\n ): Element | null {\n const { rangePath, ops, statics } = targetedOp;\n const sampleKey = this.firstKnownKey(ops);\n const container = this.findContainer(wrapper, rangePath, sampleKey);\n if (!container) {\n this.ctx.logger.debug(\n `[RangeDomApplier] container not found for range ${rangePath}; cannot apply`\n );\n return null;\n }\n\n let allOpsSucceeded = true;\n for (const op of ops) {\n if (!Array.isArray(op) || op.length < 1) continue;\n const opType = op[0];\n try {\n let opOK = true;\n switch (opType) {\n case \"r\":\n opOK = this.applyRemove(container, op[1] as string);\n break;\n case \"u\":\n opOK = this.applyUpdateRow(\n container,\n op[1] as string,\n statics,\n rangePath,\n morphdomOptions\n );\n break;\n case \"i\":\n opOK = this.applyInsertAfter(\n container,\n op[1] as string,\n op[2],\n statics,\n rangePath\n );\n break;\n case \"a\":\n opOK = this.applyAppend(\n container,\n op[1],\n statics,\n rangePath\n );\n break;\n case \"p\":\n opOK = this.applyPrepend(\n container,\n op[1],\n statics,\n rangePath\n );\n break;\n case \"o\":\n opOK = this.applyReorder(container, op[1] as string[]);\n break;\n default:\n // Forward-compat: an unrecognised op type means we can't\n // reason about the DOM mutation. Treat as failure so the\n // caller falls back to a full morphdom rebuild from\n // treeState (which the server-emitted unknown op type\n // presumably already mutated correctly).\n this.ctx.logger.warn(\n `[RangeDomApplier] unknown op type ${opType}; falling back`\n );\n opOK = false;\n }\n if (!opOK) {\n allOpsSucceeded = false;\n }\n } catch (err) {\n this.ctx.logger.error(\n `[RangeDomApplier] op ${opType} failed for range ${rangePath}`,\n err\n );\n return null;\n }\n }\n\n // If any per-op method silently no-op'd because of stale state\n // (e.g. `u` for a row that's no longer in the DOM, `i` with a\n // missing anchor), we MUST signal failure so the caller falls back\n // to a full rebuild \u2014 otherwise the live DOM stays out of sync with\n // treeState and morphdom would skip the subtree (TARGETED_APPLIED\n // marker tells it to).\n //\n // Note: earlier ops in this batch that succeeded are NOT rolled back.\n // No rollback is needed because `treeState` was already mutated to\n // its complete post-op state by `applyDifferentialOpsToRange` BEFORE\n // this method ran. The caller's fallback path re-renders from\n // `treeState.renderState()` and runs morphdom over the full HTML \u2014\n // morphdom reconciles whatever partial DOM mutations we made toward\n // the authoritative end state.\n if (!allOpsSucceeded) {\n return null;\n }\n\n // Observability hook: increment a global counter so E2E tests can\n // assert the targeted-apply path was actually taken (vs silently\n // hitting the fallback). Opt-in: tests must initialize the property\n // first (e.g. `window.__lvtTargetedHits = 0`); production never sets\n // it so the increment is skipped and we don't pollute the window\n // object outside of test environments.\n if (\n typeof window !== \"undefined\" &&\n \"__lvtTargetedHits\" in (window as any)\n ) {\n (window as any).__lvtTargetedHits++;\n }\n return container;\n }\n\n cleanupMarkers(wrapper: Element): void {\n const applied = wrapper.querySelectorAll(`[${TARGETED_APPLIED_ATTR}]`);\n applied.forEach((el) => el.removeAttribute(TARGETED_APPLIED_ATTR));\n if (wrapper.hasAttribute(TARGETED_APPLIED_ATTR)) {\n wrapper.removeAttribute(TARGETED_APPLIED_ATTR);\n }\n const skip = wrapper.querySelectorAll(`[${TARGETED_SKIP_ATTR}]`);\n skip.forEach((el) => el.removeAttribute(TARGETED_SKIP_ATTR));\n if (wrapper.hasAttribute(TARGETED_SKIP_ATTR)) {\n wrapper.removeAttribute(TARGETED_SKIP_ATTR);\n }\n }\n\n // --- per-op implementations -----------------------------------------------\n //\n // Each per-op method returns `boolean`:\n // true \u2192 the live DOM is now consistent with the new treeState\n // false \u2192 silent no-op (e.g. row not found, item state unavailable);\n // the caller should invalidate the targeted-apply marker and\n // fall back to a full rebuild\n\n private applyRemove(container: Element, key: string): boolean {\n const row = this.findItemByKey(container, key);\n if (!row) {\n // r is idempotent: if the row is already gone, treeState's post-op\n // view (also without the row) matches the DOM. No fallback needed.\n this.ctx.logger.debug(\n `[RangeDomApplier] r: row with key ${key} not found (idempotent no-op)`\n );\n return true;\n }\n this.fireHookOnSubtree(row, \"lvt-destroyed\");\n row.remove();\n return true;\n }\n\n private applyUpdateRow(\n container: Element,\n key: string,\n statics: string[],\n rangePath: string,\n morphdomOptions?: any\n ): boolean {\n const row = this.findItemByKey(container, key);\n if (!row) {\n this.ctx.logger.debug(\n `[RangeDomApplier] u: row with key ${key} not found in DOM; falling back`\n );\n return false;\n }\n const itemIdx = this.indexOfChild(container, row);\n const item = this.lookupCurrentItem(rangePath, key);\n if (!item) {\n this.ctx.logger.debug(\n `[RangeDomApplier] u: item state for key ${key} not available; falling back`\n );\n return false;\n }\n const newHtml = this.ctx.renderItem(\n item,\n itemIdx,\n statics,\n rangePath\n );\n const newRow = this.parseSingleRow(newHtml);\n if (!newRow) {\n this.ctx.logger.warn(\n `[RangeDomApplier] u: failed to parse rendered row HTML; falling back`\n );\n return false;\n }\n if (morphdomOptions) {\n // Override childrenOnly: the main morphdom call uses childrenOnly:true\n // because it's diffing the wrapper's children. For a single-row morph\n // we MUST diff the row element itself too (its attributes \u2014 class,\n // style, aria, etc. \u2014 are produced by statics+dynamics and may have\n // changed). Reuse the same callbacks for behavioral consistency.\n morphdom(row, newRow, { ...morphdomOptions, childrenOnly: false });\n } else {\n // No morphdom options provided \u2014 fall back to wholesale replacement.\n // morphdom's onNodeAdded / onBeforeNodeDiscarded callbacks would\n // normally fire lvt-mounted/lvt-destroyed hooks for us; here we have\n // to fire them manually on both sides AND notify the host so its\n // nodesAddedThisRender counter sees the new subtree (otherwise the\n // post-render directive scans would skip wiring listeners on it).\n this.fireHookOnSubtree(row, \"lvt-destroyed\");\n row.replaceWith(newRow);\n this.ctx.onNodeAdded?.(newRow);\n this.fireHookOnSubtree(newRow, \"lvt-mounted\");\n }\n return true;\n }\n\n private applyInsertAfter(\n container: Element,\n afterKey: string,\n items: any | any[],\n statics: string[],\n rangePath: string\n ): boolean {\n const anchor = this.findItemByKey(container, afterKey);\n if (!anchor) {\n this.ctx.logger.debug(\n `[RangeDomApplier] i: anchor key ${afterKey} not found; falling back`\n );\n return false;\n }\n return this.renderItemsAtomic(\n items,\n statics,\n rangePath,\n this.indexOfChild(container, anchor) + 1,\n (frag) => container.insertBefore(frag, anchor.nextSibling)\n );\n }\n\n private applyAppend(\n container: Element,\n items: any | any[],\n statics: string[],\n rangePath: string\n ): boolean {\n return this.renderItemsAtomic(\n items,\n statics,\n rangePath,\n container.children.length,\n (frag) => container.appendChild(frag)\n );\n }\n\n private applyPrepend(\n container: Element,\n items: any | any[],\n statics: string[],\n rangePath: string\n ): boolean {\n return this.renderItemsAtomic(\n items,\n statics,\n rangePath,\n 0,\n (frag) => container.insertBefore(frag, container.firstChild)\n );\n }\n\n /**\n * Render N items into a scratch DocumentFragment, splicing them into the\n * live DOM only if ALL renders succeeded. On partial failure no DOM\n * mutation happens and the caller falls back to a full rebuild \u2014 this\n * avoids `lvt-mounted` firing on items that morphdom is then about to\n * re-add (which would double-fire the hook).\n */\n private renderItemsAtomic(\n items: any | any[],\n statics: string[],\n rangePath: string,\n baseIdx: number,\n splice: (frag: DocumentFragment) => void\n ): boolean {\n const list = Array.isArray(items) ? items : [items];\n const scratch = document.createDocumentFragment();\n const newRows: Element[] = [];\n for (let i = 0; i < list.length; i++) {\n const newRow = this.renderAndParse(\n list[i],\n baseIdx + i,\n statics,\n rangePath\n );\n if (!newRow) {\n return false;\n }\n scratch.appendChild(newRow);\n newRows.push(newRow);\n }\n splice(scratch);\n for (const row of newRows) {\n this.ctx.onNodeAdded?.(row);\n this.fireHookOnSubtree(row, \"lvt-mounted\");\n }\n return true;\n }\n\n /**\n * Reorder existing children to match `newKeyOrder`. Protocol assumption:\n * the server emits the *full* new key order (mirrors the assumption in\n * `applyDifferentialOpsToRange`'s \"o\" case in tree-renderer). When the\n * new order is shorter than the current child set, we treat the missing\n * keys as removals and fire `lvt-destroyed` on each dropped subtree\n * (so user teardown \u2014 timer cancellation, observer disconnect, etc. \u2014\n * still runs) plus log a warning surfacing the protocol mismatch.\n */\n private applyReorder(container: Element, newKeyOrder: string[]): boolean {\n if (!Array.isArray(newKeyOrder)) return false;\n const byKey = new Map<string, Element>();\n Array.from(container.children).forEach((child) => {\n for (const attr of KEY_ATTRIBUTES) {\n const k = child.getAttribute(attr);\n if (k !== null) {\n byKey.set(k, child);\n break;\n }\n }\n });\n\n const fragment = document.createDocumentFragment();\n const newKeySet = new Set(newKeyOrder);\n for (const key of newKeyOrder) {\n const el = byKey.get(key);\n if (el) {\n fragment.appendChild(el);\n }\n }\n\n // Fire lvt-destroyed on children that aren't in the new order. The\n // protocol normally sends the FULL key order, but if a partial reorder\n // ever lands here, user-defined teardown (timer cancellation, observer\n // disconnect, etc.) must still run.\n if (newKeySet.size < byKey.size) {\n this.ctx.logger.warn(\n `[RangeDomApplier] o: newKeyOrder (${newKeySet.size}) shorter than existing children (${byKey.size}); ${byKey.size - newKeySet.size} children will be dropped`\n );\n for (const [k, el] of byKey) {\n if (!newKeySet.has(k)) {\n this.fireHookOnSubtree(el, \"lvt-destroyed\");\n }\n }\n }\n\n container.replaceChildren(fragment);\n return true;\n }\n\n // --- helpers --------------------------------------------------------------\n\n private renderAndParse(\n item: any,\n itemIdx: number,\n statics: string[],\n rangePath: string\n ): Element | null {\n const html = this.ctx.renderItem(item, itemIdx, statics, rangePath);\n return this.parseSingleRow(html);\n }\n\n /**\n * Parse a string of HTML containing a single root element and return it.\n * Uses <template> so orphan table-cell content (`<tr>`, `<td>`, etc.)\n * is tolerated by the parser.\n */\n private parseSingleRow(html: string): Element | null {\n const template = document.createElement(\"template\");\n template.innerHTML = html.trim();\n const first = template.content.firstElementChild;\n return first ?? null;\n }\n\n private findItemByKey(scope: Element, key: string): Element | null {\n let escaped: string;\n if (typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\") {\n escaped = CSS.escape(key);\n } else {\n // CSS.escape polyfill is incomplete: only handles `\"` and `\\`.\n // Keys containing other CSS special chars ([], (), :, ., #, >, ~,\n // whitespace, etc.) would produce a malformed selector and miss the\n // match. Warn so it's visible in test logs rather than silently\n // returning null and looking like the row simply doesn't exist.\n if (/[\\[\\]():.#>~+*=^$|! \\t\\n\\r]/.test(key)) {\n this.ctx.logger.warn(\n `[RangeDomApplier] CSS.escape unavailable; key \"${key}\" contains characters that need escaping. Lookup may miss the row.`\n );\n }\n escaped = key.replace(/([\"\\\\])/g, \"\\\\$1\");\n }\n for (const attr of KEY_ATTRIBUTES) {\n const el = scope.querySelector(`[${attr}=\"${escaped}\"]`);\n if (el) return el;\n }\n return null;\n }\n\n private indexOfChild(container: Element, child: Element): number {\n let i = 0;\n let cur = container.firstElementChild;\n while (cur) {\n if (cur === child) return i;\n i++;\n cur = cur.nextElementSibling;\n }\n return -1;\n }\n\n private firstKnownKey(ops: any[]): string | undefined {\n for (const op of ops) {\n if (!Array.isArray(op) || op.length < 2) continue;\n const t = op[0];\n if (t === \"r\" || t === \"u\" || t === \"i\") {\n return typeof op[1] === \"string\" ? op[1] : undefined;\n }\n if (t === \"o\" && Array.isArray(op[1]) && op[1].length > 0) {\n return typeof op[1][0] === \"string\" ? op[1][0] : undefined;\n }\n // For a/p ops there's no DOM-resident key in op[1], but the new\n // items themselves carry `_k`. Sample the first to give\n // findContainer something to walk to (rare cold-cache case;\n // canApplyTargeted normally warms the cache via existing.d[0]\n // before apply runs, so in practice the cache hit covers this).\n if (t === \"a\" || t === \"p\") {\n const items = Array.isArray(op[1]) ? op[1] : [op[1]];\n for (const it of items) {\n if (it && typeof it === \"object\" && it._k !== undefined) {\n return String(it._k);\n }\n }\n }\n }\n return undefined;\n }\n\n private staticsContainKeyAttribute(statics: string[]): boolean {\n // Reduces false positives vs. plain `s.includes('data-key=')`:\n // - requires word boundary before the attr name (excludes longer\n // attribute names like `data-keystone=`, `my-data-key=`)\n // - requires `=` to follow optional whitespace (excludes\n // `data-key-something`)\n //\n // Known limitation: cannot distinguish a real attribute from\n // `data-key=` appearing inside a quoted attribute value (e.g.\n // `title='see data-key=foo'`). Such cases would still match. False\n // positives are safe \u2014 `findContainer` just fails to locate by key,\n // canApplyTargeted falls back to full rebuild \u2014 but they cost a\n // render of wasted work. Real-world templates with `data-key` in\n // attribute values are vanishingly rare.\n for (const s of statics) {\n if (typeof s !== \"string\") continue;\n for (const re of KEY_ATTR_REGEXES) {\n if (re.test(s)) {\n return true;\n }\n }\n }\n return false;\n }\n\n private itemHasNestedRange(item: any): boolean {\n if (!item || typeof item !== \"object\") return false;\n for (const [key, val] of Object.entries(item)) {\n if (key.startsWith(\"_\")) continue;\n if (val && typeof val === \"object\" && !Array.isArray(val)) {\n const v = val as any;\n if (Array.isArray(v.d) && Array.isArray(v.s)) return true;\n if (this.itemHasNestedRange(v)) return true;\n }\n }\n return false;\n }\n\n private extractItemKey(item: any, rangeStructure: any): string | undefined {\n if (!item || typeof item !== \"object\") return undefined;\n if (item._k !== undefined) return String(item._k);\n const idKey = rangeStructure?.m?.idKey;\n if (idKey && item[idKey] !== undefined) return String(item[idKey]);\n return undefined;\n }\n\n private lookupCurrentItem(rangePath: string, key: string): any {\n // O(N) over range.d via the context callback (linear scan in\n // livetemplate-client.ts). Bounded cost per `u` op: one walk per\n // updated row per render. At N=10k that's ~50\u00B5s in JS \u2014 acceptable.\n return this.ctx.itemLookup(rangePath, key);\n }\n\n private fireHookOnSubtree(root: Element, hookName: string): void {\n if (root.hasAttribute(hookName)) {\n this.ctx.executeLifecycleHook(root, hookName);\n }\n const descendants = root.querySelectorAll(`[${hookName}]`);\n descendants.forEach((el) =>\n this.ctx.executeLifecycleHook(el, hookName)\n );\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\";\nimport { hasNoInterceptOptOut } from \"../utils/legacy-attr\";\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 (hasNoInterceptOptOut(parentForm, \"lvt-form:no-intercept\", this.logger)) 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 === WebSocket.OPEN) {\n this.socket.send(data);\n }\n }\n\n disconnect(): void {\n this.manuallyClosed = true;\n this.clearReconnectTimer();\n if (this.socket) {\n if (this.socket.readyState !== WebSocket.CLOSED) {\n this.options.onClose?.(\n new CloseEvent(\"close\", {\n code: 1000,\n reason: \"\",\n wasClean: this.socket.readyState === WebSocket.OPEN,\n }),\n );\n }\n this.socket.onopen = null;\n this.socket.onmessage = null;\n this.socket.onclose = null;\n this.socket.onerror = null;\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 postMultipartUpload?: (\n formData: FormData,\n signal?: AbortSignal\n ) => Promise<void>;\n private isConnected?: () => boolean;\n private postUploadStart?: (\n message: UploadStartMessage,\n signal?: AbortSignal\n ) => Promise<UploadStartResponse>;\n private pendingHandshakes: Set<AbortController> = new Set();\n private pendingUploads: Set<AbortController> = new Set(); // in-flight proxied fetches\n private previewUrls: Map<string, string> = new Map(); // uploadName -> object URL\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 this.postMultipartUpload = options.postMultipartUpload;\n this.isConnected = options.isConnected;\n this.postUploadStart = options.postUploadStart;\n\n // Register default uploaders\n this.uploaders.set(\"s3\", new S3Uploader());\n }\n\n /**\n * Dispatch a valid upload entry to the transport its mode requires. The server\n * declares the mode per-entry; existing entries that only carry `external`\n * (no mode) fall back to the Direct path for backward compatibility.\n */\n private dispatchUpload(entry: UploadEntry): void {\n switch (entry.mode) {\n case \"preview\":\n this.uploadPreview(entry);\n return;\n case \"proxied\":\n void this.uploadProxied(entry);\n return;\n case \"direct\":\n if (entry.external) {\n void this.uploadExternal(entry, entry.external);\n } else {\n // Direct requires presign metadata; surface the misconfig instead of\n // silently abandoning the entry.\n const msg = \"direct upload mode requires presigned upload metadata\";\n entry.error = msg;\n if (this.onError) this.onError(entry, msg);\n this.cleanupEntries(entry.uploadName);\n }\n return;\n case \"volume\":\n default:\n // entry.mode is normalized to a concrete mode at ingest (see\n // handleUploadStartResponse), so the default is just the Volume path.\n void this.uploadChunked(entry);\n }\n }\n\n /**\n * Mark an upload finished and run the shared post-completion steps: fire the\n * onComplete callback, clear the file input, and schedule entry cleanup.\n * Callers that talk to the server (chunked/external) send their own\n * upload_complete message first.\n */\n private finishUpload(entry: UploadEntry): void {\n entry.done = true;\n entry.progress = 100;\n // Emit a final 100% tick so every completed path (proxied included) sends it\n // \u2014 a progress bar waiting on 100% won't hang. Idempotent for chunked/external\n // which already reach 100% during transfer.\n if (this.onProgress) {\n this.onProgress(entry);\n }\n if (this.onComplete) {\n this.onComplete(entry.uploadName, [entry]);\n }\n this.clearFileInput(entry.uploadName);\n this.cleanupEntries(entry.uploadName);\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 const startMessage: UploadStartMessage = {\n action: \"upload_start\",\n upload_name: uploadName,\n files: fileMetadata,\n };\n\n // Over an open WebSocket the server replies with a separate\n // UploadStartResponse message (routed to handleUploadStartResponse). With\n // the socket down, post the handshake over HTTP and handle the response\n // inline so mode dispatch (and Direct presign) still work.\n const connected = this.isConnected ? this.isConnected() : true;\n if (!connected) {\n if (!this.postUploadStart) {\n this.failStart(\n uploadName,\n files,\n \"upload unavailable: WebSocket is closed and no HTTP fallback is configured\"\n );\n return;\n }\n const controller = new AbortController();\n this.pendingHandshakes.add(controller);\n try {\n const response = await this.postUploadStart(\n startMessage,\n controller.signal\n );\n // Skip if teardown aborted us mid-flight, so we don't create a preview\n // object URL after revokePreviews already ran.\n if (!controller.signal.aborted) {\n await this.handleUploadStartResponse(response);\n }\n } catch (error) {\n this.failStart(\n uploadName,\n files,\n error instanceof Error ? error.message : String(error)\n );\n } finally {\n this.pendingHandshakes.delete(controller);\n }\n return;\n }\n\n this.sendMessage(startMessage);\n }\n\n // failStart reports a handshake failure on every selected file and clears the\n // pending set, so a startUpload that can't reach the server isn't silent.\n private failStart(uploadName: string, files: File[], message: string): void {\n this.pendingFiles.delete(uploadName);\n const onError = this.onError;\n if (!onError) return;\n // Distinct synthetic ids per file (no server entry exists yet) so a consumer\n // keying per-file error state doesn't collapse a multi-file failure.\n files.forEach((file, i) => {\n onError(\n {\n id: `pending-${uploadName}-${i}`,\n file,\n uploadName,\n progress: 0,\n bytesUploaded: 0,\n valid: false,\n done: false,\n },\n message\n );\n });\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 // Normalize legacy responses (no mode) at the ingest boundary: a\n // presigned entry is Direct, otherwise server-side Volume. Downstream\n // dispatch then only ever sees a concrete mode.\n mode: info.mode ?? (info.external ? \"direct\" : \"volume\"),\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; otherwise the entry\n // waits for an explicit form-submit trigger.\n if (info.auto_upload) {\n this.dispatchUpload(entry);\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 this.finishUpload(entry);\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 a Proxied file as a single multipart POST to the live URL. The server\n * streams the bytes straight to the app's OnUpload handler (zero local-disk\n * staging) and dispatches the upload_<name>_complete action. Independent of\n * the WebSocket, so it works with the socket disabled.\n */\n private async uploadProxied(entry: UploadEntry): Promise<void> {\n if (!this.postMultipartUpload) {\n const msg =\n \"Proxied upload unavailable: no multipart transport configured\";\n entry.error = msg;\n if (this.onError) this.onError(entry, msg);\n this.cleanupEntries(entry.uploadName);\n return;\n }\n\n entry.abortController = new AbortController();\n this.pendingUploads.add(entry.abortController);\n // A single fetch has no native upload progress, so emit a start event (0%)\n // and let finishUpload emit 100% \u2014 a progress bar won't sit frozen with no\n // signal at all.\n if (this.onProgress) this.onProgress(entry);\n try {\n const formData = new FormData();\n // Write value fields BEFORE the file part so the server resolves the\n // action regardless of multipart part ordering.\n formData.set(\"lvt-action\", `upload_${entry.uploadName}_complete`);\n formData.set(entry.uploadName, entry.file, entry.file.name);\n\n await this.postMultipartUpload(formData, entry.abortController.signal);\n this.finishUpload(entry);\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 this.cleanupEntries(entry.uploadName);\n } finally {\n if (entry.abortController) this.pendingUploads.delete(entry.abortController);\n }\n }\n\n /**\n * Preview mode: keep the file on the device and show it locally via an object\n * URL. Nothing is uploaded \u2014 the server already has the file's metadata from\n * the upload_start handshake. The blob URL is re-applied after server\n * re-renders by hydratePreviews().\n */\n private uploadPreview(entry: UploadEntry): void {\n // Revoke any prior preview for this field to avoid leaking object URLs.\n const prev = this.previewUrls.get(entry.uploadName);\n if (prev) URL.revokeObjectURL(prev);\n\n const url = URL.createObjectURL(entry.file);\n this.previewUrls.set(entry.uploadName, url);\n this.applyPreview(entry.uploadName, url);\n\n // Share the completion path with every other mode (progress 100% tick,\n // onComplete, input clear, cleanup). The blob URL lives in previewUrls, not\n // the input, so it survives the clear.\n this.finishUpload(entry);\n }\n\n /** Point every preview placeholder for uploadName at the given object URL. */\n private applyPreview(uploadName: string, url: string): void {\n // Escape the field name so a name with a quote/bracket can't break or inject\n // into the selector. Prefer CSS.escape; without it, only proceed for a simple\n // identifier (the normal case) and otherwise skip rather than run a\n // possibly-broken selector (hydratePreviews still re-applies via attr reads).\n let safeName: string;\n if (typeof CSS !== \"undefined\" && CSS.escape) {\n safeName = CSS.escape(uploadName);\n } else if (/^[\\w-]+$/.test(uploadName)) {\n safeName = uploadName;\n } else {\n return;\n }\n const els = document.querySelectorAll<HTMLElement>(\n `[data-lvt-upload-preview=\"${safeName}\"]`\n );\n els.forEach((el) => {\n if (el instanceof HTMLImageElement) {\n if (el.src !== url) el.src = url;\n } else if (el.getAttribute(\"src\") !== url) {\n el.setAttribute(\"src\", url);\n }\n });\n }\n\n /**\n * Re-attach Preview-mode blob URLs after a DOM morph. The server re-renders\n * the {{.lvt.UploadPreview}} placeholder with an empty src, so this restores\n * the local object URL the visitor selected. Called by the client post-update.\n */\n hydratePreviews(root: Element): void {\n const apply = (el: Element) => {\n const name = el.getAttribute(\"data-lvt-upload-preview\");\n if (!name) return;\n const url = this.previewUrls.get(name);\n if (!url) return;\n if (el instanceof HTMLImageElement) {\n if (el.src !== url) el.src = url;\n } else if (el.getAttribute(\"src\") !== url) {\n el.setAttribute(\"src\", url);\n }\n };\n // querySelectorAll skips the root itself, so match it explicitly.\n if (root.matches(\"[data-lvt-upload-preview]\")) apply(root);\n root\n .querySelectorAll<HTMLElement>(\"[data-lvt-upload-preview]\")\n .forEach(apply);\n }\n\n /** Revoke all preview object URLs. Called on teardown to avoid leaks. */\n revokePreviews(): void {\n // Abort in-flight offline handshakes and proxied upload fetches first, so a\n // late resolution doesn't create a preview URL or apply a tree update after\n // the client has torn down.\n for (const controller of this.pendingHandshakes) {\n controller.abort();\n }\n this.pendingHandshakes.clear();\n for (const controller of this.pendingUploads) {\n controller.abort();\n }\n this.pendingUploads.clear();\n for (const url of this.previewUrls.values()) {\n URL.revokeObjectURL(url);\n }\n this.previewUrls.clear();\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 this.finishUpload(entry);\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 this.dispatchUpload(entry);\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", "scrollResetPriors", "autoClickTimers", "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", "targetEl", "resolveTarget", "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", "scrollResetPriors", "setupFxLifecycleListeners", "e", "teardownFxLifecycleListeners", "handleScrollDirectives", "element", "teardownAutoClickTimers", "timer", "autoClickTimers", "handleAutoClickDirectives", "spec", "existing", "colonIdx", "delayStr", "delayMs", "name", "button", "handleHighlightDirectives", "handleAnimateDirectives", "ensureAnimateKeyframes", "style", "PENDING_PROCESSED_KEY", "handleToastDirectives", "trigger", "pending", "messages", "position", "stack", "getOrCreateToastStack", "msg", "createToastElement", "setupToastClickOutside", "key", "applyPositionStyles", "s", "inner", "t", "b", "btn", "closedShadowRoots", "handleShadowRootHydration", "templates", "tpl", "parent", "modeAttr", "shadow", "areaSelectArmed", "areaSelectWarnedParents", "MIN_AREA_FRACTION", "handleAreaSelectDirectives", "send", "matches", "action", "attachAreaSelect", "teardownAreaSelectForRoot", "urlHashArmed", "urlHashWindowListener", "handleURLHashDirective", "body", "dataHash", "mirrorDataAttrToLocation", "attachURLHash", "initialLocation", "looksLikeDeepLinkHash", "teardownURLHashForRoot", "currentLocation", "warnIfUnencodedHash", "targetURL", "oldPath", "newPath", "currentState", "urlHashUnencodedWarned", "warnIfUnencodedHash", "hash", "urlHashUnencodedWarned", "looksLikeDeepLinkHash", "attachURLHash", "el", "action", "initialSend", "entry", "urlHashArmed", "urlHashWindowListener", "s", "e", "attachAreaSelect", "send", "overlay", "startClientX", "startClientY", "pointerId", "dragParent", "startRect", "removeOverlay", "finalize", "dispatch", "capturedPointerId", "rect", "onPointerLeaveCancel", "rectRight", "rectBottom", "x0", "clampRange", "y0", "x1", "y1", "x", "y", "w", "h", "MIN_AREA_FRACTION", "onDragStart", "onPointerDown", "parent", "areaSelectWarnedParents", "parentPos", "captureOk", "updateOverlay", "elRect", "parentRect", "borderL", "borderT", "scrollL", "scrollT", "toCSSLeft", "vx", "toCSSTop", "vy", "left", "top", "width", "height", "minLeft", "minTop", "maxRight", "maxBottom", "clampedLeft", "clampedTop", "clampedRight", "clampedBottom", "onPointerMove", "onPointerUp", "onPointerCancel", "onLostCapture", "areaSelectArmed", "n", "lo", "hi", "debounce", "func", "wait", "timeout", "args", "context", "throttle", "limit", "inThrottle", "STORAGE_PREFIX", "storageKey", "scope", "field", "resolveScope", "ownerDocument", "_a", "doc", "wrapper", "getStorage", "opts", "hasValue", "el", "collectRedactElements", "root", "found", "applyOutgoingRedaction", "actionElement", "sink", "storage", "e", "key", "redactActionData", "data", "sentinel", "redactFormData", "form", "formData", "hydrateRedactedTokens", "_b", "_c", "cache", "read", "v", "active", "lvtSelector", "attr", "value", "escaped", "safeValue", "legacyNoInterceptWarned", "hasNoInterceptOptOut", "el", "newName", "logger", "CLICK_AWAY_METHOD_MAP", "CLICK_AWAY_METHODS", "NON_BUBBLING", "LVT_DRAG_MIME", "DRAG_EVENTS", "DELEGATED_EVENT_TYPES", "EventDelegator", "context", "logger", "wrapperId", "eventType", "listenerKey", "existingListener", "button", "data", "attr", "key", "wrapperElement", "eventTypes", "rateLimitedHandlers", "listener", "e", "_a", "_b", "_c", "currentWrapper", "target", "element", "inWrapper", "attrName", "action", "actionElement", "isOrphanButton", "persistTable", "btn", "hasNoInterceptOptOut", "submitter", "hiddenInput", "explicitAction", "dialog", "dragEvent", "keyEl", "related", "keyFilter", "targetElement", "handleAction", "message", "formData", "checkboxes", "checkboxGroups", "cb", "group", "cbs", "name", "passwordFields", "el", "submitterForData", "actionFieldName", "value", "submitter2", "src", "tgtKey", "tier1FormData", "tier1FileInputs", "input", "submitButton", "originalButtonText", "uploadName", "redactFormData", "redactActionData", "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", "safeMatchesPopoverOpen", "el", "handlers", "findHandler", "h", "SHOW_COMMANDS", "handleClick", "e", "button", "command", "targetId", "target", "handler", "handleClose", "handleToggle", "handlePopstate", "id", "openFromHash", "isHashLinkTarget", "activateHashTarget", "installed", "setupHashLink", "teardownHashLink", "LinkInterceptor", "context", "logger", "wrapperId", "_a", "listenerKey", "existing", "wrapper", "listener", "e", "target", "currentWrapper", "hashId", "isHashLinkTarget", "activateHashTarget", "prevHref", "link", "hasNoInterceptOptOut", "protocol", "href", "pushState", "_b", "_c", "targetURL", "refURL", "response", "html", "_ObserverManager", "context", "logger", "wrapperElement", "sentinel", "entries", "wrapper", "e", "detail", "ObserverManager", "LoadingIndicator", "bar", "style", "debounceMs", "FormDisabler", "wrapper", "form", "input", "installed", "handleClick", "e", "el", "button", "targetId", "command", "target", "setupInvokerPolyfill", "GUARD_KEY", "activeBindings", "pruneDisconnectedBindings", "binding", "setupScrollAway", "scanRoot", "processEl", "el", "edge", "target", "resolveTarget", "existing", "removeBinding", "raw", "threshold", "ticking", "handler", "idx", "teardownScrollAway", "wrapper", "i", "ACTIVE_CLASS", "BINDING_KEY", "LINK_HANDLER_KEY", "activeBindings", "warnedMissingId", "pruneDisconnectedBindings", "b", "detach", "handler", "applyActive", "readMarginPx", "container", "raw", "fallback", "collectTargets", "selector", "e", "binding", "activeId", "link", "href", "id", "refreshLinks", "ownIds", "t", "matched", "findBindingForId", "pickActive", "targets", "marginPx", "findScrollTarget", "el", "cur", "oy", "sameTargets", "a", "i", "attach", "preCollected", "missingId", "ticking", "processContainer", "existing", "fresh", "idx", "installLinkClickHandler", "_a", "owner", "setupSpy", "scanRoot", "teardownSpy", "wrapper", "deepClone", "obj", "isRangeNode", "node", "hasRangeAnywhere", "depth", "key", "child", "shouldFullReplace", "existing", "update", "TreeRenderer", "logger", "opts", "_a", "_b", "changed", "targetedOps", "skipPaths", "value", "oldValue", "newValue", "result", "currentPath", "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", "a", "b", "itemStatePath", "idx", "rangeNode", "dynamics", "statics", "elseKey", "elseStatePath", "itemIdx", "itemStatics", "rangeData", "fieldValue", "items", "prepend", "itemsArray", "keyPosStr", "KEY_ATTRIBUTES", "KEY_ATTR_REGEXES", "attr", "TARGETED_APPLIED_ATTR", "TARGETED_SKIP_ATTR", "RangeDomApplier", "ctx", "rangePath", "wrapper", "anyKnownItemKey", "cached", "sample", "container", "rangeStructure", "allStatics", "sm", "arr", "items", "item", "sampleKey", "cur", "targetedOp", "morphdomOptions", "ops", "statics", "allOpsSucceeded", "op", "opType", "opOK", "err", "el", "key", "row", "_a", "_b", "itemIdx", "newHtml", "newRow", "morphdom_esm_default", "afterKey", "anchor", "frag", "baseIdx", "splice", "list", "scratch", "newRows", "i", "newKeyOrder", "byKey", "child", "k", "fragment", "newKeySet", "html", "template", "first", "scope", "escaped", "t", "it", "s", "re", "val", "v", "idKey", "root", "hookName", "FormLifecycleManager", "form", "button", "originalButtonText", "fieldset", "meta", "dialogParent", "ChangeAutoWirer", "context", "logger", "capabilities", "treeState", "prevSize", "wrapper", "el", "fieldName", "bindingType", "escapedName", "elements", "parentForm", "hasNoInterceptOptOut", "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", "entry", "msg", "container", "input", "uploadName", "existingHandler", "handler", "e", "files", "fileMetadata", "file", "startMessage", "controller", "response", "error", "message", "onError", "i", "_a", "upload_name", "entryInfos", "fileMap", "entries", "info", "meta", "uploader", "completeMessage", "errorMsg", "formData", "prev", "url", "safeName", "el", "root", "apply", "name", "id", "offset", "end", "chunk", "base64", "chunkMessage", "entryId", "pendingEntries", "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", "RangeDomApplier", "item", "idx", "statics", "sp", "el", "hook", "rangePath", "key", "_a", "range", "idKey", "FocusManager", "FormLifecycleManager", "LoadingIndicator", "FormDisabler", "UploadHandler", "message", "entry", "uploadName", "entries", "error", "formData", "signal", "EventDelegator", "value", "form", "action", "button", "originalButtonText", "LinkInterceptor", "html", "destinationHref", "href", "ObserverManager", "ChangeAutoWirer", "WebSocketManager", "_b", "_c", "response", "event", "autoInitLogger", "init", "wrapper", "client", "debounceAttr", "debounceMs", "errorEnvelope", "uploadMessage", "startResponse", "openFromHash", "wrapperSelector", "connectionResult", "setupToastClickOutside", "setupReactiveAttributeListeners", "setupInvokerPolyfill", "setupHashLink", "handleURLHashDirective", "setupFxLifecycleListeners", "teardownHashLink", "teardownAutoClickTimers", "teardownFxDOMEventTriggers", "teardownFxLifecycleListeners", "teardownScrollAway", "teardownSpy", "teardownAreaSelectForRoot", "teardownURLHashForRoot", "elapsed", "result", "err", "url", "flashParams", "hasFlashParams", "param", "readyState", "data", "seenKeys", "v", "k", "newLiveUrl", "liveUrl", "updateResponse", "detail", "doc", "currentLinks", "current", "l", "fetchedLinks", "fetched", "raw", "e", "differs", "oldId", "newTitleText", "newWrapper", "newId", "myEpoch", "n", "selector", "body", "trimmed", "num", "update", "existingHTML", "match", "innerContent", "newBodyContent", "element", "meta", "rangeStructure", "hasStaticsInTree", "node", "tempWrapper", "rawTag", "wrapTag", "root", "activeEl", "morphdomOptions", "fromEl", "toEl", "TARGETED_SKIP_ATTR", "toAttrs", "fromElement", "i", "fromAttrs", "toElement", "attr", "active", "safeMatchesPopoverOpen", "successContainers", "anyFailed", "op", "container", "TARGETED_APPLIED_ATTR", "c", "fullHtml", "morphdom_esm_default", "hydrateRedactedTokens", "handleScrollDirectives", "handleHighlightDirectives", "handleAnimateDirectives", "handleToastDirectives", "handleAutoClickDirectives", "handleAreaSelectDirectives", "handleShadowRootHydration", "setupScrollAway", "setupSpy", "setupFxDOMEventTriggers", "walker", "toReplace", "path", "parent", "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 handleAreaSelectDirectives,\n handleAutoClickDirectives,\n handleHighlightDirectives,\n handleScrollDirectives,\n handleShadowRootHydration,\n handleToastDirectives,\n handleURLHashDirective,\n teardownAreaSelectForRoot,\n teardownURLHashForRoot,\n teardownAutoClickTimers,\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 { setupHashLink, teardownHashLink, openFromHash, safeMatchesPopoverOpen } from \"./dom/hash-link\";\nimport { setupScrollAway, teardownScrollAway } from \"./dom/scroll-away\";\nimport { setupSpy, teardownSpy } from \"./dom/spy\";\nimport { hydrateRedactedTokens } from \"./dom/redact\";\nimport { TreeRenderer } from \"./state/tree-renderer\";\nimport {\n RangeDomApplier,\n TARGETED_APPLIED_ATTR,\n TARGETED_SKIP_ATTR,\n} from \"./state/range-dom-applier\";\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 UploadStartMessage,\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 rangeDomApplier: RangeDomApplier;\n private nodesAddedThisRender: number = 0;\n private directiveTouchedThisRender: boolean = false;\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 // Visibility-based reconnection: when the browser tab returns from\n // background (iOS app switch, Android task switch), the WebSocket is\n // often silently dead. These fields drive a one-shot reconnect on\n // the visibilitychange event, independent of autoReconnect (which\n // guards against retry loops on persistent network failures).\n // Handlers are stored as instance properties so disconnect() can\n // remove them \u2014 without that, SPAs that build a new client per route\n // accumulate listeners that hold closures over destroyed instances.\n private visibilityHandlerAttached: boolean = false;\n private hiddenAt: number = 0;\n private reconnecting: boolean = false;\n private visibilityHandler: (() => void) | null = null;\n private pageshowHandler: ((e: PageTransitionEvent) => void) | null = null;\n private visibilityReconnectTimer: ReturnType<typeof setTimeout> | 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.rangeDomApplier = new RangeDomApplier({\n logger: this.logger.child(\"RangeDomApplier\"),\n renderItem: (item, idx, statics, sp) =>\n this.treeRenderer.renderRangeItem(item, idx, statics, sp),\n executeLifecycleHook: (el, hook) => this.executeLifecycleHook(el, hook),\n itemLookup: (rangePath, key) => {\n // O(N) linear scan over range.d. For one `u` op per render this is\n // ~50\u00B5s at N=10k \u2014 acceptable. For a render with many u ops on\n // the same range, this becomes O(N\u00D7K); building a Map<key, item>\n // once at apply() start would amortize, but the gain is small\n // (whole `u` op cost is dominated by morphdom on the row anyway).\n // Revisit if profiling shows this on the hot path.\n const range = this.treeRenderer.getTreeState()[rangePath];\n if (!range || !Array.isArray(range.d)) return null;\n const idKey = range.m?.idKey;\n for (const item of range.d) {\n if (!item || typeof item !== \"object\") continue;\n if (item._k === key) return item;\n if (\n idKey &&\n item[idKey] !== undefined &&\n String(item[idKey]) === key\n ) {\n return item;\n }\n }\n return null;\n },\n onNodeAdded: () => {\n this.nodesAddedThisRender++;\n },\n });\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 postMultipartUpload: (formData, signal) =>\n this.postUploadMultipart(formData, signal),\n isConnected: () =>\n !this.useHTTP && this.webSocketManager.getReadyState() === 1,\n postUploadStart: (message, signal) =>\n this.postUploadStartHTTP(message, signal),\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, destinationHref: string) =>\n this.handleNavigationResponse(html, destinationHref),\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 // Per-action loading indicator: opt-in via the\n // `data-lvt-loading-debounce-ms=\"<N>\"` attribute. Read from the\n // wrapper first (server-set), then fall back to `<body>` \u2014\n // because the wrapper is auto-injected by the framework, template\n // authors typically place the attribute on body themselves. The\n // attribute arms a timer on every `lvt:pending` and shows the\n // loading bar if it fires before `lvt:updated` arrives \u2014 used to\n // mask round-trip latency without flashing a bar on instant\n // responses.\n const debounceAttr =\n wrapper.getAttribute(\"data-lvt-loading-debounce-ms\") ??\n document.body?.getAttribute(\"data-lvt-loading-debounce-ms\") ??\n null;\n // Pre-validate as a pure integer string: parseInt is lenient\n // and would accept \"200abc\" as 200, silently masking a typo.\n if (debounceAttr !== null && /^\\d+$/.test(debounceAttr)) {\n const debounceMs = parseInt(debounceAttr, 10);\n if (Number.isFinite(debounceMs) && debounceMs >= 0) {\n // Defer wiring until after the first `lvt:updated`. If we\n // wired it now and `data-lvt-loading=\"true\"` is also set,\n // the initial-connect bar would be up when the first\n // user-initiated `lvt:pending` fires (no debounce armed,\n // since `bar !== null`), and the very next `lvt:updated` \u2014\n // which is the initial payload, not the action response \u2014\n // would decrement the count to zero and consume the\n // action's pending credit. Waiting one render makes the\n // per-action machinery start in steady state.\n wrapper.addEventListener(\n \"lvt:updated\",\n () => {\n client.loadingIndicator.enablePerActionIndicator(debounceMs);\n },\n { once: true, capture: true }\n );\n }\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 // Error envelope (proposal \u00A73 / V14): a distinct wire message\n // { type:\"error\", code, topic? } \u2014 NOT an UpdateResponse (it carries no\n // `tree`). Surface as an `lvt:error` CustomEvent on the wrapper and\n // short-circuit BEFORE the diff path, so analyzeStatics()/updateDOM()\n // never see a treeless payload. The server keeps the socket open after\n // emitting this (livetemplate Phase 4 / V14), so there is no disconnect\n // to handle here.\n //\n // Contract lock \u2014 `type === \"error\"` is the general envelope discriminator:\n // every `{type:\"error\",...}` payload from the server flows through this\n // single branch and surfaces as `lvt:error`. As of livetemplate Phase 4\n // the only `code` emitted is `topic_forbidden`, but the contract is\n // intentionally open-ended: new server-side error codes (e.g. rate-limit,\n // auth) will reuse this same shape and listener \u2014 apps consuming\n // `lvt:error` should switch on `event.detail.code`. Don't narrow this\n // check to a `code === \"topic_forbidden\"` literal: that would silently\n // drop future codes into `updateDOM` as treeless payloads.\n //\n // NOTE: state/form-lifecycle-manager.ts also dispatches an `lvt:error`\n // event \u2014 but on the <form> element with a ResponseMetadata detail. These\n // are two distinct, non-bubbling events disambiguated by target (wrapper\n // vs form) and detail shape; they do not collide. See proposal \u00A76 docs.\n const errorEnvelope = response as unknown as {\n type?: string;\n code?: string;\n topic?: string;\n };\n if (errorEnvelope.type === \"error\") {\n // Always short-circuit a `type:\"error\"` payload before the diff path \u2014\n // it has no `tree`, so analyzeStatics(undefined) would error. A\n // well-formed envelope carries a string `code` (V14 contract); a bare\n // `{type:\"error\"}` without one is malformed and is logged + dropped\n // (rather than dispatched as an `lvt:error` with no detail, which\n // would confuse listeners).\n if (typeof errorEnvelope.code !== \"string\") {\n this.logger.warn(\n \"Malformed error envelope (missing string `code`) \u2014 dropping\",\n errorEnvelope\n );\n return;\n }\n if (this.wrapperElement) {\n this.wrapperElement.dispatchEvent(\n new CustomEvent(\"lvt:error\", {\n detail: { code: errorEnvelope.code, topic: errorEnvelope.topic },\n })\n );\n } else {\n // Reachable only if handleWebSocketPayload runs before connect()\n // wires up wrapperElement \u2014 should never happen on the WS-onMessage\n // path, but warn rather than silently swallow so it's visible in\n // production logs.\n this.logger.warn(\n \"lvt:error envelope arrived before wrapperElement was set; dropping\",\n { code: errorEnvelope.code, topic: errorEnvelope.topic }\n );\n }\n return;\n }\n\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 // Re-run after first render \u2014 setupHashLink()'s internal call\n // fired before server content existed in the DOM.\n openFromHash();\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 setupHashLink();\n\n // url-hash directive needs an initial-load arm here (not just in\n // updateDOM's FIRE-ON-CHANGE block) because page load \u2192 no\n // updateDOM call \u2192 directive never arms \u2192 a `#path:L4` URL never\n // dispatches setURLHash. Subsequent state changes route through\n // the FIRE-ON-CHANGE block's call site, which keeps the data-attr\n // mirror in sync after server re-renders.\n //\n // Transport is reliable at this point: `connect()` above was\n // awaited, so either the WebSocket is OPEN (`useHTTP=false`,\n // readyState=1) or the HTTP fallback is wired (`useHTTP=true`).\n // `this.send` covers both paths plus a `readyState!==1 && WS\n // available` fallback that posts via HTTP, so the initial-arm\n // dispatch always reaches the server \u2014 no \"best-effort\" caveat.\n if (this.wrapperElement) {\n handleURLHashDirective(this.wrapperElement, (message) => this.send(message));\n }\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 this.setupVisibilityReconnect();\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.hiddenAt = 0;\n this.reconnecting = false;\n this.teardownVisibilityReconnect();\n this.uploadHandler.revokePreviews();\n this.eventDelegator.teardownDOMEventTriggerDelegation();\n teardownHashLink();\n teardownAutoClickTimers();\n this.loadingIndicator.disablePerActionIndicator();\n if (this.wrapperElement) {\n teardownFxDOMEventTriggers(this.wrapperElement);\n teardownFxLifecycleListeners(this.wrapperElement);\n teardownScrollAway(this.wrapperElement);\n teardownSpy(this.wrapperElement);\n teardownAreaSelectForRoot(this.wrapperElement);\n teardownURLHashForRoot(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.rangeDomApplier.invalidate();\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 private setupVisibilityReconnect(): void {\n if (this.visibilityHandlerAttached || typeof document === \"undefined\") return;\n this.visibilityHandlerAttached = true;\n\n this.visibilityHandler = () => {\n if (document.hidden) {\n // Only track hidden time if WebSocket is currently open.\n // Prevents stale handlers from triggering reconnection on\n // already-disconnected clients (e.g. after cross-handler\n // navigation or explicit disconnect).\n if (!this.useHTTP && this.webSocketManager.getReadyState() === 1) {\n this.hiddenAt = Date.now();\n }\n return;\n }\n if (this.hiddenAt === 0) return;\n const elapsed = Date.now() - this.hiddenAt;\n this.hiddenAt = 0;\n if (elapsed < 3000) return;\n this.scheduleVisibilityReconnect();\n };\n\n // pageshow's persisted=true means the page came back from bfcache \u2014\n // its in-memory connection state is unknown, so reconnect unconditionally\n // (no 3s threshold). visibilitychange handles ordinary tab-switch cases.\n this.pageshowHandler = (event: PageTransitionEvent) => {\n if (event.persisted) {\n this.scheduleVisibilityReconnect();\n }\n };\n\n document.addEventListener(\"visibilitychange\", this.visibilityHandler);\n window.addEventListener(\"pageshow\", this.pageshowHandler);\n }\n\n private teardownVisibilityReconnect(): void {\n if (this.visibilityReconnectTimer !== null) {\n clearTimeout(this.visibilityReconnectTimer);\n this.visibilityReconnectTimer = null;\n }\n if (!this.visibilityHandlerAttached || typeof document === \"undefined\") return;\n if (this.visibilityHandler) {\n document.removeEventListener(\"visibilitychange\", this.visibilityHandler);\n this.visibilityHandler = null;\n }\n if (this.pageshowHandler) {\n window.removeEventListener(\"pageshow\", this.pageshowHandler);\n this.pageshowHandler = null;\n }\n this.visibilityHandlerAttached = false;\n }\n\n private scheduleVisibilityReconnect(): void {\n // 500ms delay lets onclose deliver before we ask for a new socket.\n // Tracked on the instance so teardownVisibilityReconnect() can\n // cancel a pending timer when disconnect() runs mid-window \u2014\n // otherwise a timer queued before disconnect can fire after a\n // subsequent connect on the same instance and trigger an\n // unwanted reconnect.\n //\n // Cancel any in-flight timer first so a rapid\n // visibilitychange+pageshow sequence doesn't leak the earlier\n // setTimeout (the tracked ref would only point at the last one,\n // leaving the first orphaned).\n if (this.visibilityReconnectTimer !== null) {\n clearTimeout(this.visibilityReconnectTimer);\n }\n this.visibilityReconnectTimer = setTimeout(() => {\n this.visibilityReconnectTimer = null;\n // Guard: only reconnect if a WebSocket transport exists. After\n // disconnect(), transport is null (readyState undefined) so we\n // correctly skip intentionally disconnected clients.\n // Note: we intentionally do NOT check readyState !== 1 (OPEN)\n // because iOS 15+ can leave zombie sockets that report OPEN while\n // the underlying TCP connection is dead. Always reconnecting after\n // a long background is cheap (morphdom diffs produce no DOM changes\n // on a healthy connection) and handles the zombie case.\n if (\n this.wrapperElement &&\n !this.useHTTP &&\n !this.reconnecting &&\n this.webSocketManager.getReadyState() !== undefined\n ) {\n this.performVisibilityReconnect();\n }\n }, 500);\n }\n\n private async performVisibilityReconnect(): Promise<void> {\n if (this.reconnecting) return;\n this.reconnecting = true;\n\n try {\n this.logger.info(\"Reconnecting after visibility change\");\n\n this.webSocketManager.disconnect();\n this.ws = null;\n this.resetSessionState();\n\n const result = await this.webSocketManager.connect();\n\n // disconnect() may have run during the await above. It clears the\n // reconnecting flag, so the absence of that flag here is our\n // \"torn down while suspended\" signal \u2014 abort without mutating\n // state. Without this, useHTTP / payload application would land\n // on a client the consumer expects to be inert (or, worse, on a\n // freshly-reconnected client created by a subsequent connect()).\n if (!this.reconnecting) return;\n\n this.useHTTP = !result.usingWebSocket;\n\n if (this.useHTTP) {\n this.ws = null;\n if (result.initialState && this.wrapperElement) {\n this.handleWebSocketPayload(result.initialState);\n }\n }\n\n this.wrapperElement?.dispatchEvent(new Event(\"lvt:reconnected\"));\n } catch (err) {\n this.logger.error(\"Visibility reconnect failed:\", err);\n } finally {\n this.reconnecting = false;\n }\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 * POST a Proxied upload's multipart body to the live URL and apply the tree\n * response. Unlike sendHTTPMultipart this REJECTS on a non-2xx response so the\n * upload handler can surface the failure to the app; it is transport-agnostic\n * (works whether or not the WebSocket is connected).\n */\n private async postUploadMultipart(\n formData: FormData,\n signal?: AbortSignal\n ): Promise<void> {\n const liveUrl = this.getLiveUrl();\n const response = await fetch(liveUrl, {\n method: \"POST\",\n credentials: \"include\",\n headers: {\n Accept: \"application/json\",\n // X-Lvt-Upload is a non-safelisted header, so it forces a CORS preflight\n // \u2014 a cross-site page can't silently POST the user's cookies here.\n \"X-Lvt-Upload\": \"proxied\",\n // Do NOT set Content-Type \u2014 the browser sets the multipart boundary.\n },\n body: formData,\n signal,\n });\n\n if (!response.ok) {\n const detail = (await response.text().catch(() => \"\")).trim();\n throw new Error(\n `Proxied upload failed: ${response.status}${detail ? ` \u2014 ${detail}` : \"\"}`\n );\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 }\n\n /**\n * Post an upload_start handshake over HTTP (WebSocket-disabled fallback) and\n * return the parsed UploadStartResponse. The X-Lvt-Upload header tells the\n * server to answer with the handshake response rather than treating the body\n * as a normal action.\n */\n private async postUploadStartHTTP(\n message: UploadStartMessage,\n signal?: AbortSignal\n ): Promise<UploadStartResponse> {\n const response = await fetch(this.getLiveUrl(), {\n method: \"POST\",\n credentials: \"include\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n \"X-Lvt-Upload\": \"start\",\n },\n body: JSON.stringify(message),\n signal,\n });\n if (!response.ok) {\n const detail = (await response.text().catch(() => \"\")).trim();\n throw new Error(\n `upload_start handshake failed: ${response.status}${detail ? ` \u2014 ${detail}` : \"\"}`\n );\n }\n return (await response.json()) as UploadStartResponse;\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 /**\n * Compare the set of <link rel=\"stylesheet\"> URLs in the current\n * document's <head> against those in a fetched (parsed) document.\n * Returns true when the two pages declare different stylesheets,\n * which is our signal that swapping the body alone would leave the\n * new content styled by the wrong app's CSS. URLs are compared as\n * absolute (the live doc's via the browser-resolved .href; the\n * fetched doc's via explicit base-URL resolution against the\n * destination, since the parsed Document has no inherent base) so\n * that \"/foo.css\" and \"https://host/foo.css\" don't appear to differ.\n */\n private stylesheetsDiffer(doc: Document, destinationHref: string): boolean {\n const currentLinks = document.querySelectorAll(\n 'link[rel~=\"stylesheet\"][href]'\n );\n const current = new Set<string>();\n currentLinks.forEach((l) => {\n const href = (l as HTMLLinkElement).href;\n if (href) current.add(href);\n });\n\n const fetchedLinks = doc.querySelectorAll('link[rel~=\"stylesheet\"][href]');\n const fetched = new Set<string>();\n fetchedLinks.forEach((l) => {\n const raw = l.getAttribute(\"href\");\n if (!raw) return;\n try {\n fetched.add(new URL(raw, destinationHref).href);\n } catch {\n fetched.add(raw);\n }\n });\n\n if (current.size !== fetched.size) return true;\n let differs = false;\n current.forEach((href) => {\n if (!fetched.has(href)) differs = true;\n });\n return differs;\n }\n\n /**\n * Force a full document navigation to `destinationHref`. Used when\n * an SPA-style body-swap is unsafe (e.g. cross-app navigation). The\n * indirection exists for testability \u2014 jsdom locks the real Location\n * object, so tests spy on this method instead.\n */\n private performFullNavigation(destinationHref: string): void {\n window.location.href = destinationHref;\n }\n\n private handleNavigationResponse(html: string, destinationHref: 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 // Cross-app boundary: the SPA optimization (swap body, keep head)\n // only works when both pages share the same <head> contract. When\n // a user navigates from one deployed app to another that shares\n // the origin (e.g., a docs site reverse-proxying a separately-\n // deployed pattern showcase), the two apps may declare different\n // <link rel=\"stylesheet\"> URLs. Patching only the body would leave\n // the new body styled by the previous app's CSS \u2014 broken layout\n // that \"fixes itself on refresh\" because a real navigation reloads\n // the head too. This guard runs BEFORE either branch (with-wrapper\n // and no-wrapper) below, because both branches end up swapping body\n // content while the head stays unchanged. The destination page may\n // not have a data-lvt-id at all (e.g., a static docs page), so the\n // check cannot live inside the wrapper-found branch.\n if (this.stylesheetsDiffer(doc, destinationHref)) {\n this.logger.info(\n \"Cross-app navigation detected (different <link rel=\\\"stylesheet\\\"> set in fetched HTML); falling back to full navigation\"\n );\n this.performFullNavigation(destinationHref);\n return;\n }\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\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 // Reset per-render counters before applying the update.\n // - nodesAddedThisRender: incremented by morphdom.onNodeAdded and the\n // applier's onNodeAdded callback for i/a/p ops.\n // - directiveTouchedThisRender: set by morphdom.onBeforeElUpdated when\n // it processes an element carrying a directive attribute (lvt-fx:*,\n // lvt-on:*, lvt-el:*) \u2014 covers attribute-only morphs that don't add\n // nodes but do change directive bindings, so the post-render scans\n // still need to wire them.\n // Either signal triggers the wrapper-wide directive scans below.\n this.nodesAddedThisRender = 0;\n this.directiveTouchedThisRender = false;\n\n // Apply update to internal state and get reconstructed HTML.\n // Pass canApplyTargeted so eligible top-level range diff ops mutate\n // treeState in place and are emitted as targetedOps for direct DOM\n // mutation (skipping the full HTML rebuild + morphdom diff for that\n // subtree).\n const result = this.treeRenderer.applyUpdate(update, {\n canApplyTargeted: (rangeStructure, rangePath) => {\n const r = this.rangeDomApplier.canApplyTargeted(\n element,\n rangeStructure,\n rangePath\n );\n return r.ok;\n },\n });\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 DOM changes to apply \u2014 skip morphdom. But the action still\n // completed, so its loading lifecycle must still resolve: fire the\n // form lifecycle (which dispatches lvt:done \u2192 reactive on:done\n // attributes, lvt-form:disable-with restore, form aria-busy). Without\n // this, a no-op action (re-submitting the same value, or any\n // idempotent action that produces an empty render diff) leaves\n // spinners spinning and disabled buttons stuck forever.\n if (meta) {\n this.formLifecycleManager.handleResponse(meta);\n }\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 // Defer the entire morphdom pass while a native datalist dropdown\n // may be showing. Datalist popups are browser-managed overlays with\n // no DOM representation \u2014 ANY mutation on the page (not just to the\n // datalist itself) triggers a reflow that dismisses the popup.\n // Since there is no API to query whether the popup is open, we use\n // document.activeElement being a datalist-connected <input> as the\n // signal. The deferred state is naturally applied on the next server\n // push after the user leaves the input (typically within one scan\n // interval).\n const activeEl = document.activeElement;\n if (\n activeEl instanceof HTMLInputElement &&\n activeEl.list instanceof HTMLDataListElement &&\n !tempWrapper.querySelector(\"[data-lvt-force-update]\")\n ) {\n this.logger.debug(\"[updateDOM] deferred: datalist input focused\");\n this.focusManager.restoreFocusedElement();\n return;\n }\n\n // Build morphdom options once so the applier's `u` op (which morphdoms\n // a single row) uses the same callback set \u2014 focus skip, lvt-ignore,\n // checkbox preservation, lifecycle hooks all stay consistent.\n const morphdomOptions = {\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: any, toEl: any) => {\n // Targeted-apply skip: the live container's children were already\n // mutated directly by RangeDomApplier, and the rebuilt tempWrapper\n // has the container empty + tagged with data-lvt-targeted-skip.\n // Returning false short-circuits the entire subtree update \u2014\n // morphdom skips both the diff walk AND the children-replacement.\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n (toEl as Element).hasAttribute(TARGETED_SKIP_ATTR)\n ) {\n return false;\n }\n\n // Track newly-introduced directive attributes so the post-render\n // scan can wire any new lvt-fx:/lvt-on:/lvt-el: bindings even on\n // renders that wouldn't otherwise trigger a wrapper-wide scan.\n // Only flag when the directive attribute is NEW on toEl (not\n // already present on fromEl) \u2014 otherwise high-frequency `u` ops\n // on rows that ALREADY carry a directive (e.g. Todos rows with\n // `lvt-fx:animate`) would trigger a wrapper-wide scan on every\n // render even though no new binding needs wiring.\n if (\n toEl.nodeType === Node.ELEMENT_NODE &&\n fromEl.nodeType === Node.ELEMENT_NODE\n ) {\n const toAttrs = (toEl as Element).attributes;\n const fromElement = fromEl as Element;\n for (let i = 0; i < toAttrs.length; i++) {\n const n = toAttrs[i].name;\n if (\n n.length > 4 &&\n n.charCodeAt(0) === 0x6c /* l */ &&\n n.charCodeAt(1) === 0x76 /* v */ &&\n n.charCodeAt(2) === 0x74 /* t */ &&\n n.charCodeAt(3) === 0x2d /* - */ &&\n !fromElement.hasAttribute(n)\n ) {\n this.directiveTouchedThisRender = true;\n break;\n }\n }\n }\n\n // lvt-ignore: morphdom skips this element and its entire subtree.\n // Equivalent to Phoenix LiveView's phx-update=\"ignore\".\n // Checked on fromEl (live DOM) so both server templates and\n // client JS can add/remove it. Use data-lvt-force-update on\n // the server's version to bypass and resume diffing.\n if (\n fromEl.nodeType === Node.ELEMENT_NODE &&\n (fromEl as Element).hasAttribute(\"lvt-ignore\") &&\n !(toEl as Element).hasAttribute(\"data-lvt-force-update\")\n ) {\n return false;\n }\n\n // lvt-ignore-attrs: skip attribute diffing but still diff children.\n // Copies fromEl's missing attributes onto toEl so morphdom keeps\n // them. Server-set attributes still win (toEl value used when\n // both sides have the same attr). Checked on fromEl for\n // consistency with lvt-ignore; use data-lvt-force-update to\n // bypass.\n if (\n fromEl.nodeType === Node.ELEMENT_NODE &&\n (fromEl as Element).hasAttribute(\"lvt-ignore-attrs\") &&\n !(toEl as Element).hasAttribute(\"data-lvt-force-update\") &&\n toEl.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 if (!toElement.hasAttributeNS(attr.namespaceURI, attr.localName)) {\n toElement.setAttributeNS(attr.namespaceURI, attr.name, attr.value);\n }\n }\n }\n\n // Preserve <datalist> elements while their connected input is\n // focused. Native datalist dropdowns are dismissed if the element\n // is touched \u2014 unlike checkbox state, dropdown-open state has no\n // DOM representation and cannot be copied to the new element.\n if (\n fromEl instanceof HTMLDataListElement &&\n !(toEl as Element).hasAttribute('data-lvt-force-update')\n ) {\n const active = document.activeElement;\n if (\n active instanceof HTMLInputElement &&\n active.list === fromEl\n ) {\n return false;\n }\n }\n\n // Copy `open` onto toEl so morphdom's attr sync won't strip it (preserves top-layer state).\n if (\n fromEl instanceof HTMLDialogElement &&\n fromEl.hasAttribute('open') &&\n !(toEl as Element).hasAttribute('data-lvt-force-update')\n ) {\n (toEl as Element).setAttribute('open', '');\n }\n\n // Skip open popovers entirely (top-layer state has no DOM representation).\n if (\n !(toEl as Element).hasAttribute(\"data-lvt-force-update\") &&\n fromEl instanceof HTMLElement &&\n fromEl.hasAttribute(\"popover\") &&\n safeMatchesPopoverOpen(fromEl)\n ) {\n return false;\n }\n\n // Preserve checkbox/radio checked state across morphdom updates.\n // User selection wins by default \u2014 these controls lose focus on\n // click so the focusManager never protects them, and their checked\n // state is user input that must survive scan-loop refreshes. Use\n // data-lvt-force-update to let the server override the user state.\n //\n // Known limitation: force-update on one radio can uncheck a sibling\n // that was already processed earlier in the same morphdom pass, since\n // browser mutual exclusion fires synchronously mid-loop. To safely\n // reset a radio group, send data-lvt-force-update on ALL radios in\n // the group, not just the one being checked.\n if (\n fromEl instanceof HTMLInputElement &&\n toEl instanceof HTMLInputElement &&\n (fromEl.type === \"checkbox\" || fromEl.type === \"radio\")\n ) {\n if (toEl.hasAttribute(\"data-lvt-force-update\")) {\n fromEl.checked = toEl.checked;\n if (fromEl.type === \"checkbox\") {\n fromEl.indeterminate = toEl.indeterminate;\n }\n fromEl.removeAttribute(\"data-lvt-force-update\");\n } else {\n toEl.checked = fromEl.checked;\n // Align the checked attribute with the property so morphdom's\n // attribute diff doesn't add a spurious checked attr to fromEl\n // (which IS in the DOM and would trigger radio mutual exclusion).\n if (fromEl.checked) {\n toEl.setAttribute(\"checked\", \"\");\n } else {\n toEl.removeAttribute(\"checked\");\n }\n if (fromEl.type === \"checkbox\") {\n toEl.indeterminate = fromEl.indeterminate;\n }\n }\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 \u2014 but honour\n // data-lvt-force-update which means the server wants morphdom\n // to process this element or one of its descendants\n // unconditionally (e.g. resetting a checkbox whose checked\n // property differs from the attribute).\n // Note: querySelector is a defensive fallback \u2014 in steady state\n // the attr is stripped after each render, so isEqualNode returns\n // false and normal diffing reaches the descendant. The scan only\n // matters on the first render of a newly inserted subtree.\n if (fromEl.isEqualNode(toEl)) {\n if (\n !toEl.hasAttribute(\"data-lvt-force-update\") &&\n (toEl.children.length === 0 ||\n toEl.querySelector(\"[data-lvt-force-update]\") === null)\n ) {\n return false;\n }\n // Ancestor itself didn't change \u2014 only traversing for a\n // descendant's force-update. Skip the lvt-updated hook.\n return true;\n }\n // Execute lvt-updated lifecycle hook\n this.executeLifecycleHook(fromEl, \"lvt-updated\");\n return true;\n },\n onElUpdated: (el: any) => {\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 // Strip data-lvt-force-update from the live DOM after each\n // render. If the server stops sending it, preservation resumes;\n // if the server keeps including it, each render force-resets.\n if (el instanceof HTMLElement && el.hasAttribute(\"data-lvt-force-update\")) {\n el.removeAttribute(\"data-lvt-force-update\");\n }\n },\n onNodeAdded: (node: any) => {\n // Sync textarea value for newly inserted textarea elements\n if (node instanceof HTMLTextAreaElement) {\n node.value = node.textContent ?? \"\";\n }\n if (node instanceof HTMLElement && node.hasAttribute(\"data-lvt-force-update\")) {\n node.removeAttribute(\"data-lvt-force-update\");\n }\n // Execute lvt-mounted lifecycle hook\n if (node.nodeType === Node.ELEMENT_NODE) {\n this.executeLifecycleHook(node as Element, \"lvt-mounted\");\n this.nodesAddedThisRender++;\n }\n },\n onBeforeNodeDiscarded: (node: any) => {\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 // Apply per-op targeted DOM mutations BEFORE morphdom. The applier\n // mutates the live DOM in place; tempWrapper has corresponding\n // <!--lvt-targeted-skip:path--> placeholders that we now convert to\n // data-lvt-targeted-skip markers on their parent elements so morphdom\n // short-circuits those subtrees.\n //\n // Robustness: if any targeted op fails (apply returns null \u2014 e.g.\n // container couldn't be located, or an op threw), the treeState was\n // updated but the live DOM wasn't, so leaving the placeholder in\n // place would either (a) tell morphdom to skip \u2192 live DOM stays\n // stale, or (b) leave an empty container in tempWrapper \u2192 morphdom\n // would empty the live container. Both are wrong. We re-render the\n // full HTML from treeState (which is authoritative) and let morphdom\n // sync from there.\n if (result.targetedOps && result.targetedOps.length > 0) {\n const successContainers: Element[] = [];\n let anyFailed = false;\n for (const op of result.targetedOps) {\n const container = this.rangeDomApplier.apply(\n element,\n op,\n morphdomOptions\n );\n if (container) {\n container.setAttribute(TARGETED_APPLIED_ATTR, \"\");\n successContainers.push(container);\n } else {\n anyFailed = true;\n }\n }\n\n if (anyFailed) {\n this.logger.warn(\n \"[updateDOM] one or more targeted DOM ops failed; rebuilding tempWrapper from treeState for a full morphdom sync\"\n );\n // Strip success markers \u2014 we're going to do a full diff now.\n for (const c of successContainers) {\n c.removeAttribute(TARGETED_APPLIED_ATTR);\n }\n // Re-render full HTML (no skip placeholders) and reset tempWrapper.\n const fullHtml = this.treeRenderer.renderState();\n tempWrapper.innerHTML = fullHtml;\n } else {\n this.replaceTargetedSkipPlaceholders(tempWrapper);\n }\n }\n\n try {\n // Use morphdom to efficiently update the element\n morphdom(element, tempWrapper, morphdomOptions);\n } finally {\n // Strip lifecycle markers regardless of whether morphdom threw,\n // preventing leaked attributes on the live DOM.\n this.rangeDomApplier.cleanupMarkers(element);\n }\n\n // Preview-mode hydration: fill [data-lvt-redact] elements from localStorage\n // (.value for inputs, textContent for spans). Runs on the LIVE element AFTER\n // morphdom (not the detached tempWrapper before it): a redacted element is\n // static, so it's never re-walked in an update patch \u2014 the committed DOM is\n // the only place to catch it. Runs before focus restoration so a restored\n // input's value is present when its focus/selection is reapplied. No-op when\n // nothing is tagged/stored.\n hydrateRedactedTokens(element);\n\n // Preview-mode uploads: re-attach local object URLs to any\n // [data-lvt-upload-preview] placeholder the server just re-rendered, so the\n // on-device preview survives server updates (same rationale as redact).\n this.uploadHandler.hydratePreviews(element);\n\n // Restore focus to previously focused element\n this.focusManager.restoreFocusedElement();\n\n // Two classes of post-render scans:\n //\n // FIRE-ON-CHANGE (always run): handleScrollDirectives,\n // handleHighlightDirectives, handleAnimateDirectives,\n // handleToastDirectives, setupScrollAway. These detect VALUE changes\n // on existing directive-bearing elements (e.g. lvt-fx:highlight\n // flashes on every render where the underlying value changed) \u2014 so\n // they must run on every render. Cost is bounded: each does a CSS\n // attribute selector qsa for its specific directive (`[lvt-fx\\:highlight]`\n // etc.); for a 10k-row LargeTable where rows DON'T have these\n // directives, the qsa returns empty in ~1-3ms total.\n //\n // WIRE-IDEMPOTENT (skip when nothing new): setupFxDOMEventTriggers,\n // setupDOMEventTriggerDelegation, uploadHandler.initializeFileInputs.\n // These walk EVERY descendant via qsa(\"*\") to attach event listeners\n // on lvt-fx:event:on:trigger / lvt-el: / file inputs. They have\n // per-element guards so re-running is safe but wasteful \u2014 at 80k\n // descendants the walk costs ~150-200ms each. Skip when neither\n // morphdom.onNodeAdded fired nor a new lvt-* directive attribute\n // appeared on any morphed element (tracked via onBeforeElUpdated).\n handleScrollDirectives(element);\n handleHighlightDirectives(element);\n handleAnimateDirectives(element);\n handleToastDirectives(element);\n handleAutoClickDirectives(element);\n // Area-select needs the send() callback to dispatch the final-\n // coords action on pointerup, unlike the other directives in this\n // block (which are visual-only or click-through). Reuse the same\n // send the event delegator uses so the WS/HTTP routing is\n // identical to a normal action.\n handleAreaSelectDirectives(element, (message) => this.send(message));\n // url-hash bridges server state \u2194 location.hash. Same send-callback\n // wiring as area-select: the directive needs to dispatch a server\n // action on user-driven hashchange events (anchor click, address-bar\n // edit, back-button) and read the latest server-rendered hash from\n // `data-lvt-url-hash` on every render.\n handleURLHashDirective(element, (message) => this.send(message));\n // Hydrate any server-emitted Declarative Shadow DOM templates that\n // morphdom inserted via DOM APIs (which don't auto-activate them).\n // Cheap when no templates are present (one querySelectorAll).\n //\n // Hard limitation: livetemplate directives placed INSIDE shadow\n // content (e.g. lvt-on:click on an element inside a `<template\n // shadowrootmode>`) NEVER register, on any render. The directive\n // sweeps above (setupFxDOMEventTriggers, eventDelegator) walk via\n // querySelectorAll which stops at shadow boundaries, and the\n // hydration that follows doesn't run them against the new shadow\n // root either. Treat shadow DOM as a style/structure isolation\n // primitive only \u2014 keep directives in light DOM. Today's consumer\n // (prereview's HTML preview) wraps sanitised user HTML in shadow\n // DOM purely for style isolation; no directives go in.\n handleShadowRootHydration(element);\n setupScrollAway(element);\n setupSpy(element);\n if (this.nodesAddedThisRender > 0 || this.directiveTouchedThisRender) {\n setupFxDOMEventTriggers(element, this.wrapperElement || undefined);\n this.eventDelegator.setupDOMEventTriggerDelegation(element);\n this.uploadHandler.initializeFileInputs(element);\n }\n\n // changeAutoWirer always runs: its eviction loop must process\n // wirings on removed elements too, regardless of additions.\n this.changeAutoWirer.wireElements();\n\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 * Walk tempWrapper for `<!--lvt-targeted-skip:path-->` comments left by\n * `reconstructFromTree` and convert each into a `data-lvt-targeted-skip`\n * attribute on its parent element. The marker tells morphdom (via its\n * onBeforeElUpdated callback) to short-circuit the subtree, leaving the\n * live container's existing children \u2014 already updated by the applier \u2014\n * untouched.\n */\n private replaceTargetedSkipPlaceholders(tempWrapper: Element): void {\n const walker = document.createTreeWalker(\n tempWrapper,\n NodeFilter.SHOW_COMMENT\n );\n const toReplace: Comment[] = [];\n let node: Node | null;\n while ((node = walker.nextNode())) {\n const c = node as Comment;\n if (c.nodeValue && /^lvt-targeted-skip:.+$/.test(c.nodeValue)) {\n toReplace.push(c);\n }\n }\n for (const c of toReplace) {\n const match = c.nodeValue!.match(/^lvt-targeted-skip:(.+)$/);\n const path = match ? match[1] : \"\";\n const parent = c.parentElement;\n if (parent) {\n parent.setAttribute(TARGETED_SKIP_ATTR, path);\n }\n c.remove();\n }\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, resolveTarget } 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// Tracks the prior value of the watched attribute for each element using\n// `lvt-fx:scroll=\"reset-on:<attr>\"`. We can't stash the prior value as a\n// data-* attribute \u2014 morphdom would diff against it and constantly fight\n// the reset. WeakMap auto-cleans when the element is GC'd.\nlet scrollResetPriors = new WeakMap<Element, string | null>();\n\n// Active timers for `lvt-fx:auto-click=\"<delay-ms>:<button-name>\"`. Stored\n// keyed by element so we can detect: (a) element re-renders unchanged \u2192\n// don't re-arm, (b) spec changed \u2192 cancel + re-arm, (c) element removed \u2192\n// cancel. Strong Map (not WeakMap) so we can iterate for the disconnected-\n// element sweep on each render pass.\n//\n// Leak management: cleanup of stale entries depends on render continuity \u2014\n// `handleAutoClickDirectives` runs the sweep on every render pass, so an\n// element removed between renders is collected on the next one. If the\n// page stops rendering entirely (e.g. websocket dies and is never\n// reconnected) while elements still have timers, those entries hold\n// strong refs until tear-down. In practice the timer's own fire-time\n// `isConnected` guard makes any leftover harmless \u2014 it would skip the\n// `.click()` for a disconnected node \u2014 but the Map itself remains.\nlet autoClickTimers = new Map<\n Element,\n { timer: ReturnType<typeof setTimeout>; spec: string }\n>();\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 scrollResetPriors = new WeakMap<Element, string | null>();\n for (const { timer } of autoClickTimers.values()) clearTimeout(timer);\n autoClickTimers = new Map();\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 const targetEl = resolveTarget(el) as HTMLElement;\n applyFxEffect(targetEl, 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 if (htmlElement.style.length === 0) {\n htmlElement.removeAttribute(\"style\");\n }\n (htmlElement as any).__lvtHighlighting = false;\n return;\n }\n htmlElement.style.backgroundColor = originalBackground;\n setTimeout(() => {\n if (htmlElement.isConnected) {\n htmlElement.style.transition = originalTransition;\n if (htmlElement.style.length === 0) {\n htmlElement.removeAttribute(\"style\");\n }\n }\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 initialized = htmlElement.dataset.lvtScrollSticky === \"1\";\n if (!initialized) {\n htmlElement.dataset.lvtScrollSticky = \"1\";\n htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior: \"instant\" });\n } else {\n const isNearBottom = htmlElement.scrollHeight - htmlElement.scrollTop - htmlElement.clientHeight <= threshold;\n if (isNearBottom) htmlElement.scrollTo({ top: htmlElement.scrollHeight, behavior });\n }\n break;\n }\n case \"top\":\n htmlElement.scrollTo({ top: 0, behavior });\n break;\n case \"into-view\": {\n // Scroll the element itself into view of its nearest scrollable\n // ancestor. Useful when server-side state needs to focus the\n // user on a specific element (e.g., a freshly-selected comment).\n // Honors --lvt-scroll-behavior; defaults to centered placement so\n // the user has surrounding context.\n //\n // One-shot semantics: handleScrollDirectives fires on every render,\n // but we don't want to re-scroll the user back every time after they\n // scrolled away. A `data-lvt-iv-done` guard records that this\n // element has already been scrolled into view; the directive only\n // fires again if the attribute is removed and re-added (new element\n // or new value, e.g. jumping to a different comment).\n if (htmlElement.dataset.lvtIvDone !== \"1\") {\n htmlElement.scrollIntoView({ block: \"center\", inline: \"nearest\", behavior });\n htmlElement.dataset.lvtIvDone = \"1\";\n }\n break;\n }\n case \"preserve\":\n break;\n default: {\n // `reset-on:<attr-name>` \u2014 reset scrollLeft/scrollTop to 0\n // whenever the value of `<attr-name>` differs from the last\n // render. Use case: an element whose content swaps without the\n // node itself being replaced (morphdom reuse), where the\n // previous scroll position is meaningless for the new content.\n if (mode.startsWith(\"reset-on:\")) {\n const attrName = mode.slice(\"reset-on:\".length);\n if (!attrName) {\n console.warn(`lvt-fx:scroll=\"reset-on:\" requires an attribute name`);\n break;\n }\n const currentValue = htmlElement.getAttribute(attrName);\n const seen = scrollResetPriors.has(htmlElement);\n if (!seen) {\n // First paint \u2014 establish the prior, don't reset. The\n // directive's semantic is \"reset on *change*\"; if a caller\n // has set scroll programmatically before our first sweep\n // (session restore, deep link, etc.), we must not clobber it.\n scrollResetPriors.set(htmlElement, currentValue);\n } else if (scrollResetPriors.get(htmlElement) !== currentValue) {\n scrollResetPriors.set(htmlElement, currentValue);\n htmlElement.scrollLeft = 0;\n htmlElement.scrollTop = 0;\n }\n break;\n }\n console.warn(`Unknown lvt-fx:scroll mode: ${mode}`);\n }\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 * Cancel and forget all active `lvt-fx:auto-click` timers. Called from\n * the LiveTemplate client's `disconnect()` so per-session timer state\n * doesn't survive across a session boundary (the next session re-arms\n * fresh on its first render pass). Safe to call when no timers exist.\n *\n * Multi-client scope warning: the timer Map is module-level (matching\n * the existing `animatedElements` / `scrollResetPriors` pattern), so\n * this teardown cancels timers across every `LiveTemplateClient`\n * instance on the page. If two clients coexist (e.g. a layout client\n * and a widget client), disconnect order matters \u2014 the surviving\n * client's pending auto-clicks are cancelled along with the\n * disconnecting client's. Surviving clients re-arm on their next\n * render pass, but any auto-click that was about to fire mid-window\n * is lost. Per-instance scoping would solve this but is a larger\n * refactor (the existing two singletons would need the same\n * treatment) and is deferred until a real multi-client use case\n * appears.\n */\nexport function teardownAutoClickTimers(): void {\n for (const { timer } of autoClickTimers.values()) clearTimeout(timer);\n autoClickTimers.clear();\n}\n\n/**\n * Apply auto-click directives. `lvt-fx:auto-click=\"<delay-ms>:<button-name>\"`\n * arms a timer when the element is first seen with this spec; on fire, the\n * directive locates a descendant `[name=<button-name>]` and synthesizes a\n * click on it \u2014 funneling through the existing event-delegation pipeline\n * so the server-side action runs identically to a user click. Use case:\n * auto-dismiss a toast/banner after N ms by clicking its existing dismiss\n * button (which already fires the dismissBanner server action).\n *\n * Idempotent across renders: an element that re-appears with the same\n * spec keeps its existing timer. A spec change cancels and re-arms. An\n * element that disappears has its timer canceled on the next render's\n * sweep (and even if the sweep doesn't run first, the fire-time\n * isConnected check skips the click).\n */\nexport function handleAutoClickDirectives(rootElement: Element): void {\n // Fast path: nothing armed and no matching elements \u2192 no work to do.\n // `querySelector` returns on the first hit, so this is cheaper than\n // the `querySelectorAll` below when there are no matches at all\n // (the common case for pages that don't use this directive).\n if (\n autoClickTimers.size === 0 &&\n rootElement.querySelector(\"[lvt-fx\\\\:auto-click]\") === null\n ) {\n return;\n }\n\n // Sweep: cancel timers for elements that have disconnected OR whose\n // attribute was cleared while they remain in the DOM (e.g. the server\n // resolved the toast's dismiss state without removing the element).\n // Without this, the Map grows unbounded across renders, and a stale\n // timer could fire `.click()` on a button whose owning element no\n // longer wants the auto-action.\n for (const [element, entry] of Array.from(autoClickTimers)) {\n if (\n !element.isConnected ||\n !element.hasAttribute(\"lvt-fx:auto-click\")\n ) {\n clearTimeout(entry.timer);\n autoClickTimers.delete(element);\n }\n }\n\n rootElement.querySelectorAll(\"[lvt-fx\\\\:auto-click]\").forEach((element) => {\n const spec = element.getAttribute(\"lvt-fx:auto-click\");\n if (!spec) return;\n\n const existing = autoClickTimers.get(element);\n if (existing && existing.spec === spec) return;\n if (existing) clearTimeout(existing.timer);\n\n const colonIdx = spec.indexOf(\":\");\n const delayStr = colonIdx > 0 ? spec.slice(0, colonIdx) : \"\";\n // Pre-validate as a pure integer string: parseInt is lenient and\n // would accept \"200abc\" as 200, silently masking a typo in the\n // attribute. Authors expect \"<delay>:<name>\" \u2014 anything else warns.\n const delayMs = /^\\d+$/.test(delayStr) ? parseInt(delayStr, 10) : NaN;\n const name = colonIdx > 0 ? spec.slice(colonIdx + 1) : \"\";\n // `delayMs === 0` is intentionally allowed: it means \"click on the\n // next tick after this element first appears\" \u2014 a useful primitive\n // for \"auto-execute action on render\" patterns. Authors who want\n // visible debounce should pass a non-zero value.\n //\n // Name is restricted to characters that cannot escape the CSS\n // attribute-selector interpolation below (no quotes, brackets,\n // whitespace, or backslashes). Word characters and hyphens cover\n // every valid HTML name attribute we expect to encounter \u2014 including\n // digit-prefixed names \u2014 while keeping the selector safe. JavaScript\n // `\\w` is ASCII-only (`[A-Za-z0-9_]`), so a Unicode button name\n // would warn here; revisit if i18n button naming becomes a real\n // requirement.\n if (\n !Number.isFinite(delayMs) ||\n delayMs < 0 ||\n !name ||\n !/^[\\w-]+$/.test(name)\n ) {\n console.warn(\n `lvt-fx:auto-click expects \"<delay-ms>:<button-name>\", got: ${spec}`\n );\n // Reached when an element's spec changes from valid to malformed\n // mid-life. The valid-spec branch above already cleared its\n // existing timer; this delete removes the now-stale map entry so\n // the next render doesn't see a phantom prior spec.\n autoClickTimers.delete(element);\n return;\n }\n\n const timer = setTimeout(() => {\n // Intentionally NOT deleting the map entry here. Doing so would\n // make the next render pass see \"no entry, attribute still set\"\n // and re-arm a fresh timer, firing `.click()` a second time \u2014\n // reachable whenever a render lands between fire and the server\n // removing the element. Leave the entry in place; the next\n // sweep cleans it up when the element disconnects or the\n // attribute is cleared. The fired timeout itself is now a no-op\n // (clearTimeout on a fired handle is harmless).\n if (!element.isConnected) return;\n // Scoped to <button>: clicking an arbitrary [name=\u2026] match (e.g.\n // a checkbox, a text input) would have surprising side effects\n // unrelated to the action-submission semantic this directive\n // promises. Buttons are the only correct target.\n const button = element.querySelector(\n `button[name=\"${name}\"]`\n ) as HTMLElement | null;\n if (button) button.click();\n }, delayMs);\n autoClickTimers.set(element, { timer, spec });\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// closedShadowRoots tracks shadow roots created in \"closed\" mode. The\n// platform makes them unreachable via `parent.shadowRoot` (it returns\n// null) \u2014 closed mode's whole point is that the host's normal DOM API\n// can't see them. On a re-render, without this side channel, the code\n// would call attachShadow a second time on the same host, throw\n// NotSupportedError, hit the catch, and silently drop the new content.\n// Open roots are reachable via parent.shadowRoot, so they don't need\n// the map.\n//\n// Module-scoped on purpose: WeakMap keys are garbage-collected with\n// their hosts, so detached elements don't leak. A function-scoped map\n// would forget closed roots across renders and the bug would return.\nconst closedShadowRoots = new WeakMap<Element, ShadowRoot>();\n\n/**\n * Activate Declarative Shadow DOM for `<template shadowrootmode>` elements\n * that the client inserted via DOM APIs (innerHTML setter, morphdom's\n * createElement+appendChild path). The HTML parser activates declarative\n * shadow roots only at parse time or via setHTMLUnsafe / parseHTMLUnsafe;\n * a `<template shadowrootmode>` set via `.innerHTML = ...` is parked as a\n * plain template with content but no attached shadow root. This sweep\n * closes that gap so server-emitted shadow roots survive a client\n * re-render.\n *\n * For each matching template found under rootElement:\n * - attach a shadow root on the parent (open by default; \"closed\" when\n * shadowrootmode=\"closed\");\n * - move the template's content into the shadow root (replaceChildren\n * accepts a DocumentFragment and re-parents its children atomically,\n * so re-renders cleanly reset prior shadow content);\n * - remove the template.\n *\n * Hosts that can't accept a shadow root (a small fixed set: <input>,\n * <textarea>, void elements, etc.) silently drop the template \u2014 better\n * than an unhandled exception that kills the render.\n *\n * Closed-mode roots are tracked in a module-level WeakMap so re-renders\n * can locate them (parent.shadowRoot returns null for closed roots).\n *\n * Idempotent: a re-run with no remaining templates is one qsa walk and\n * an early return (sub-millisecond on hundreds-of-rows pages).\n *\n * Known limitations:\n *\n * - Nested DSD is inert on EVERY render, not just re-renders. A\n * `<template>`'s content lives in a DocumentFragment (`tpl.content`),\n * not in the light DOM, and `querySelectorAll` does not descend into\n * that fragment. So a `<template shadowrootmode>` nested inside\n * another `<template>` is never in the qsa result. Once the outer\n * shadow has been attached, the inner template ends up behind a\n * shadow boundary \u2014 still unreachable. The fix would be a recursive\n * sweep per new shadow root from within this loop.\n *\n * - Shadow-root options (`delegatesFocus`, `clonable`, `serializable`,\n * even `mode`) are fixed at first attach. A re-render that toggles\n * `shadowrootdelegatesfocus` on a host that already has a shadow root\n * won't change the existing root's focus behaviour \u2014 re-attach isn't\n * possible. Matches the HTML parser, which would have made the same\n * one-shot decision; if the server needs to flip these flags, it\n * needs to swap the host element entirely. The mode-mismatch case\n * also logs a console.warn so the divergence is visible.\n */\nexport function handleShadowRootHydration(rootElement: Element): void {\n // Single qsa for both the empty-fast-path and the actual work \u2014 a\n // leading querySelector check would double-walk the tree when\n // templates are present. NodeList from querySelectorAll is static\n // (not live), so removing templates inside the loop doesn't disturb\n // iteration; no Array.from copy needed.\n // The selector guarantees <template> elements, so the typed qsa\n // overload removes the `as HTMLTemplateElement` cast inside the loop.\n const templates = rootElement.querySelectorAll<HTMLTemplateElement>(\n \"template[shadowrootmode]\"\n );\n if (templates.length === 0) return;\n for (const tpl of templates) {\n // qsa on an Element always returns descendants with a parentElement,\n // so !parent should be unreachable today. Kept as a defensive guard\n // in case a future caller passes a DocumentFragment-rooted tree\n // where the matched template could be a fragment's direct child.\n const parent = tpl.parentElement;\n if (!parent) {\n tpl.remove();\n continue;\n }\n const modeAttr = tpl.getAttribute(\"shadowrootmode\");\n // Align with the HTML parser: only \"open\" and \"closed\" trigger\n // activation. A typo like shadowrootmode=\"opne\" was previously\n // left in place \"so the author can inspect\" \u2014 but on every\n // subsequent render the qsa would re-find it, defeating the\n // fast-path advertised in the docblock. Remove it AND log a\n // console.warn so authors actually see the typo (the next morphdom\n // pass would overwrite it anyway).\n if (modeAttr !== \"open\" && modeAttr !== \"closed\") {\n console.warn(\n `livetemplate: invalid shadowrootmode=${JSON.stringify(modeAttr)}; ` +\n `expected \"open\" or \"closed\". Template removed.`,\n tpl\n );\n tpl.remove();\n continue;\n }\n const mode: ShadowRootMode = modeAttr;\n\n // For open roots, parent.shadowRoot is the reachable handle. For\n // closed roots, the platform returns null on purpose \u2014 consult the\n // WeakMap that we populated when we first attached the root.\n let shadow = parent.shadowRoot ?? closedShadowRoots.get(parent);\n // If the server flips shadowrootmode on a re-render (e.g. open \u2192\n // closed), attachShadow can't be called a second time \u2014 the existing\n // mode silently wins. Warn so the author notices the mistake instead\n // of debugging mysterious focus/encapsulation behaviour later.\n if (shadow && shadow.mode !== modeAttr) {\n console.warn(\n `livetemplate: shadowrootmode changed from \"${shadow.mode}\" to \"${modeAttr}\" ` +\n `on re-render \u2014 mode is fixed at first attach and cannot be changed.`,\n parent\n );\n }\n if (!shadow) {\n try {\n // Forward all Declarative Shadow DOM attributes so the hydrated\n // root matches the one the HTML parser would build natively:\n // - shadowrootdelegatesfocus \u2192 delegatesFocus\n // - shadowrootclonable \u2192 clonable (Chrome 124+)\n // - shadowrootserializable \u2192 serializable (Chrome 125+)\n // Unknown flags from older runtimes are silently ignored by\n // attachShadow, so we don't need a feature-detect.\n shadow = parent.attachShadow({\n mode,\n delegatesFocus: tpl.hasAttribute(\"shadowrootdelegatesfocus\"),\n clonable: tpl.hasAttribute(\"shadowrootclonable\"),\n serializable: tpl.hasAttribute(\"shadowrootserializable\"),\n });\n if (mode === \"closed\") {\n closedShadowRoots.set(parent, shadow);\n }\n } catch (e) {\n // attachShadow throws DOMException for hosts that can't accept\n // one (void elements, <input>, <textarea>, custom elements that\n // declared a different mode, etc.). Drop the template so it\n // doesn't keep tripping this hook on every render, AND warn so\n // a developer accidentally putting shadow content on an invalid\n // host gets a console signal rather than a mysteriously empty\n // preview.\n //\n // Anything OTHER than a DOMException is a real bug (typo in the\n // options object, runtime fault); re-raise so it surfaces in the\n // console instead of getting silently masked as \"unsupported\n // host\".\n if (!(e instanceof DOMException)) throw e;\n console.warn(\n `livetemplate: attachShadow rejected on <${parent.tagName.toLowerCase()}> ` +\n `(${e.name}: ${e.message}). Template removed.`,\n parent\n );\n tpl.remove();\n continue;\n }\n }\n\n // Pass the DocumentFragment directly \u2014 replaceChildren moves its\n // children into the shadow root in one atomic platform call. Avoids\n // both the spread (which could hit call-stack argument limits on\n // very large NodeLists) and the intermediate Array.from allocation.\n shadow.replaceChildren(tpl.content);\n tpl.remove();\n }\n}\n\n// areaSelectArmed tracks the cleanup callback for every element that\n// currently has a `lvt-fx:area-select` handler attached. Map (not\n// WeakMap) because the sweep needs to iterate to detect elements whose\n// attribute was removed by a server diff \u2014 without iteration those\n// elements would keep their listeners and silently dispatch the old\n// action on subsequent drags. Detached elements are cleaned up via\n// the same sweep (isConnected check).\nconst areaSelectArmed = new Map<Element, AreaSelectEntry>();\n\n// areaSelectWarnedParents dedupes the \"parent not positioned\" dev-warn\n// so a user who repeatedly drags on a mis-configured element gets a\n// single console message instead of one per pointerdown. WeakSet so\n// detached parents don't leak.\n//\n// Known limitation: once a parent is in the set, the warn never fires\n// again on that DOM node \u2014 even if the developer subsequently adds\n// `position: relative` to fix the issue. The WeakSet is per-object\n// (different DOM node = different entry), so re-mounting the parent\n// resets the dedupe; in-place CSS fixes do not. Fine in practice\n// (the user already saw the warn once, on the broken render).\nconst areaSelectWarnedParents = new WeakSet<Element>();\n\ninterface AreaSelectEntry {\n action: string;\n cleanup: () => void;\n // updateSend lets the idempotent re-arm path swap the captured\n // send callback without tearing down + rebuilding listeners. The\n // listeners close over a mutable `send` variable inside\n // attachAreaSelect; updateSend reassigns it.\n updateSend: (send: AreaSelectSendFn) => void;\n}\n\ntype AreaSelectSendFn = (\n message: { action: string; data: Record<string, unknown> }\n) => void;\n\n// MIN_AREA_FRACTION filters accidental click-style gestures where the\n// user meant to click, not drag. 2% of the element's rendered size is\n// big enough to be intentional on touch + mouse but small enough that\n// anyone seriously trying to annotate a tiny region can still do it.\nconst MIN_AREA_FRACTION = 0.02;\n\n/**\n * Apply area-select directives. `lvt-fx:area-select=\"<actionName>\"` on\n * an element (typically an `<img>` inside a positioned parent) lets\n * the user drag a rectangle locally \u2014 a `<div>` overlay tracks the\n * gesture in real time without a server round-trip \u2014 and on\n * `pointerup` dispatches a single livetemplate action with the final\n * `{x, y, w, h}` as 0..1 fractions of the element's rendered bounding\n * rect. The image's intrinsic dimensions don't matter for the\n * fractions: any uniform scale (zoom, responsive layout) preserves\n * the fraction. The consumer scales to pixels using the natural size\n * if it needs them.\n *\n * Contract:\n * - Host's `parentElement` must establish a positioning context\n * (`position: relative` / `absolute` / `fixed`). The overlay is\n * `position: absolute` inside that parent so it follows the host\n * on scroll / reflow.\n * - Consumers usually pair this with `touch-action: none` on the\n * host so iOS Safari doesn't interpret the drag as a pinch/scroll.\n * - `<img>` and other natively-draggable hosts work automatically:\n * the directive calls `preventDefault()` on `dragstart` so the\n * browser's native drag (which would otherwise steal the gesture)\n * is suppressed.\n * - On pointer-cancel (e.g. system gesture, app switch), the overlay\n * is removed and no action is dispatched \u2014 same effect as cancelling\n * a click on `mouseleave`.\n * - Drags smaller than `MIN_AREA_FRACTION` in BOTH dimensions are\n * dropped \u2014 a click on the host still fires normal `click`\n * handlers via the compatibility mouse events.\n * - For text-bearing hosts, set `user-select: none` (the directive\n * deliberately does NOT call `preventDefault()` on `pointerdown`\n * so click handlers still receive the gesture; that means the\n * browser's default text-selection-on-drag behaviour also fires\n * unless the host opts out via CSS).\n * - The overlay uses `z-index: var(--lvt-area-select-z-index, 9999)`.\n * 9999 is high enough for most use cases but can collide with\n * portals / modals / drawers that also sit at a high z-index.\n * Set `--lvt-area-select-z-index` on the host (or any ancestor)\n * to override. Color + fill follow the same pattern via\n * `--lvt-area-select-color` and `--lvt-area-select-fill`.\n * - **No keyboard equivalent.** Pointer-only by design (a keyboard-\n * selected rectangle requires a different UX \u2014 focus + arrow keys\n * to position + arrow keys to size). Consumers needing a11y for\n * area selection should provide a parallel form-based affordance.\n *\n * Idempotent across renders: an element re-armed with the same action\n * keeps its existing listeners. A different action causes a tear-down\n * and re-arm. Disconnected elements (and elements whose attribute was\n * cleared by a server diff) get their listeners cleaned up by the\n * sweep at the top of every call \u2014 we use a regular Map (not WeakMap)\n * specifically so the sweep can iterate.\n *\n * Module-level singleton: `areaSelectArmed` is shared across all\n * LiveTemplateClient instances in the same window. If two clients\n * ever arm the same element with different actions, the second wins\n * and the first client's send() is orphaned. Single-client use is\n * unaffected.\n */\nexport function handleAreaSelectDirectives(\n rootElement: Element,\n send: (message: { action: string; data: Record<string, unknown> }) => void\n): void {\n // Sweep stale entries before processing the current match set:\n // disconnected elements AND elements where the attribute was\n // removed by a server diff. Without this, a previously-armed\n // element whose lvt-fx:area-select was cleared would keep its\n // listeners and silently dispatch the old action on subsequent\n // drags. Iterate via Array.from so cleanup()'s delete() doesn't\n // disturb the iterator.\n for (const [element, entry] of Array.from(areaSelectArmed)) {\n if (!element.isConnected || !element.hasAttribute(\"lvt-fx:area-select\")) {\n entry.cleanup();\n }\n }\n\n const matches = rootElement.querySelectorAll<HTMLElement>(\n \"[lvt-fx\\\\:area-select]\"\n );\n if (matches.length === 0) return;\n\n for (const el of matches) {\n const action = el.getAttribute(\"lvt-fx:area-select\");\n // Empty attribute \u2192 consumer almost certainly typoed; warn and\n // skip rather than dispatching to a blank action name.\n if (!action) {\n console.warn(\n `lvt-fx:area-select requires an action name, got: ${JSON.stringify(action)}`\n );\n continue;\n }\n const existing = areaSelectArmed.get(el);\n if (existing && existing.action === action) {\n // Idempotent re-arm: keep the listeners + WeakMap entry, but\n // update the captured send so a subsequent drag dispatches\n // through the latest callback (e.g. after a WebSocket\n // reconnect rebuilt the transport).\n existing.updateSend(send);\n continue;\n }\n if (existing) existing.cleanup();\n areaSelectArmed.set(el, attachAreaSelect(el, action, send));\n }\n}\n\n/**\n * Cancel area-select listeners for every armed element under root.\n * Mirrors teardownAutoClickTimers: meant for the client's disconnect /\n * destroy lifecycle so the module-level singleton doesn't outlive a\n * client that was torn down without a subsequent\n * handleAreaSelectDirectives call (e.g. network error closed the\n * socket while an element was armed). Without this, a SPA that mounts\n * + tears down livetemplate trees would leak listeners across mounts.\n */\nexport function teardownAreaSelectForRoot(rootElement: Element): void {\n // `contains` returns true for the node itself, so this also handles\n // the (today-impossible) case of rootElement being armed directly.\n for (const [element, entry] of Array.from(areaSelectArmed)) {\n if (rootElement.contains(element)) {\n entry.cleanup();\n }\n }\n}\n\n// urlHashArmed tracks `lvt-fx:url-hash` listeners and their last-\n// mirrored hash. Same Map-not-WeakMap reasoning as area-select: the\n// sweep iterates to detect elements whose attribute was removed by a\n// server diff, and detached elements are cleaned up via the same\n// sweep (isConnected check).\n//\n// Module-level singleton: shared across all LiveTemplateClient\n// instances in the window. Two clients arming DIFFERENT elements\n// each get their own entry; the shared window hashchange listener\n// iterates the map and dispatches through every armed entry's own\n// send, so a multi-client page sees each client receive its hash\n// event. Teardown is scoped per root via teardownURLHashForRoot, so\n// clients don't tear down each other's listeners.\n//\n// Same-element multi-arm is \"last writer wins\": the Map key is the\n// element, so a second client arming the same element runs the\n// existing entry's cleanup() and replaces it. The first client's\n// send is orphaned. This matches area-select's behavior and is fine\n// for the documented single-arm-per-element contract.\nconst urlHashArmed = new Map<Element, URLHashEntry>();\n\n// urlHashWindowListener is the single window-level `hashchange`\n// listener shared across all armed elements. Registered on first arm,\n// removed when the armed map becomes empty. Per-element listeners\n// would multi-fire for the (rare) case of multiple armed elements;\n// one shared listener iterating the armed map keeps the dispatch\n// count deterministic.\nlet urlHashWindowListener: ((e: HashChangeEvent) => void) | null = null;\n\ninterface URLHashEntry {\n action: string;\n // send is stored on the entry so the shared window listener can\n // dispatch each armed entry's action through its own transport. The\n // idempotent re-arm path mutates this field directly (via\n // updateSend), so a reconnect that rebuilt the transport is picked\n // up on the next hashchange.\n send: URLHashSendFn;\n cleanup: () => void;\n updateSend: (send: URLHashSendFn) => void;\n // currentDataHash is the last `data-lvt-url-hash` value we mirrored\n // into `location.hash` (or the value we observed on a user-initiated\n // hashchange). Comparing against the next render's data-attr lets us\n // no-op when the server re-rendered with the same hash \u2014 avoids\n // extra history entries on every keystroke.\n currentDataHash: string;\n}\n\ntype URLHashSendFn = (\n message: { action: string; data: Record<string, unknown> }\n) => void;\n\n/**\n * Apply url-hash directives. `lvt-fx:url-hash=\"<actionName>\"` plus a\n * `data-lvt-url-hash=\"<hash>\"` attribute on an element (typically the\n * `<body>`) wires a two-way bridge between server state and\n * `location.hash`:\n *\n * - **State \u2192 URL** (every render): if `data-lvt-url-hash` differs\n * from `location.hash`, mirror the data-attr into the URL via\n * `history.pushState` when the path component changed (everything\n * before the first `:`) or `history.replaceState` when only the\n * target (line range / anchor) changed. Replace is the right\n * default for line scrolls so the back-button cycles between files,\n * not between every clicked line.\n * - **URL \u2192 State** (on `hashchange` AND initial arm): dispatch\n * `{action: <actionName>, data: {hash: <hash>}}` so the server can\n * parse the hash and update its state (which then renders back as\n * a matching data-attr \u2014 closing the loop).\n *\n * The directive uses `history.pushState`/`replaceState` (not\n * `location.hash = ...`) for the state\u2192URL direction precisely so\n * those writes do NOT fire `hashchange` \u2014 only true user-initiated\n * navigation (anchor click, address-bar edit, back-button) reaches\n * the URL\u2192state listener. This avoids the obvious infinite loop.\n *\n * Idempotent across renders: same action \u2192 keep listener + update\n * send. Different action \u2192 cleanup + re-arm. Detached / attribute-\n * removed elements are swept on every call (same pattern as\n * area-select). The window listener is registered on first arm and\n * removed when the armed map becomes empty.\n *\n * Coexistence with `setupHashLink`: prereview-style hashes\n * (`README.md:L4`, `foo/bar.html:h-anchor`) never match a\n * `document.getElementById(...)`, so the existing dialog/popover/\n * details hash machinery silently no-ops. If a deep-link hash\n * happens to collide with an element id, both handlers will fire \u2014\n * the server is expected to no-op on hashes that don't resolve to a\n * known file.\n *\n * **Pre-encoding contract**: `data-lvt-url-hash` must hold the hash\n * value already in URL-encoded form. The directive writes the\n * attribute verbatim into `history.pushState`/`replaceState`, so a\n * value containing spaces, `[`, `]`, `%`, or other reserved\n * characters needs to be percent-encoded by the server. The hash\n * sent to the action on `hashchange` is also passed through unmodified\n * (no decoding) \u2014 both directions are byte-exact mirrors of what's\n * in `location.hash`.\n *\n * **URL/state divergence after a non-deep-link initial load**: if\n * the user lands with a native-anchor hash (`#hero`) AND the server\n * has a selected file, the directive leaves the URL on `#hero` (case\n * b) \u2014 URL and server state diverge until the user navigates. This\n * is intentional: popovers/anchors aren't ours to overwrite. The\n * next user action that triggers a server render will re-sync only\n * once URL and state share a deep-link hash.\n *\n * **Path-only deep links require an extension**: the\n * `looksLikeDeepLinkHash` heuristic dispatches only hashes\n * containing `:`, `/`, or `.`. Extension-less root files\n * (`#Makefile`, `#Dockerfile`, `#LICENSE`) won't dispatch as\n * path-only deep links \u2014 use the line form (`#Makefile:L1`)\n * instead. The trade-off favours not clobbering native-anchor\n * machinery for single-token hashes.\n */\nexport function handleURLHashDirective(\n rootElement: Element,\n send: (message: { action: string; data: Record<string, unknown> }) => void\n): void {\n // Sweep stale entries first \u2014 disconnected hosts AND hosts whose\n // attribute was removed by a server diff. Iterate via Array.from so\n // cleanup()'s delete() doesn't disturb the iterator.\n for (const [element, entry] of Array.from(urlHashArmed)) {\n if (\n !element.isConnected ||\n !element.hasAttribute(\"lvt-fx:url-hash\")\n ) {\n entry.cleanup();\n }\n }\n\n // Match the root itself, descendants, AND the document body. The\n // url-hash directive is typically placed on `<body>`, but livetemplate\n // auto-injects its `<div data-lvt-id>` INSIDE body, so the rootElement\n // passed by the client is the wrapper div \u2014 a strict descendant of\n // body. Without the body check, a directive on `<body>` would never\n // arm. We accept body placement because URL hash is page-global\n // anyway; the directive's lifecycle is still tied to the wrapper via\n // teardownURLHashForRoot (called on disconnect of the wrapper).\n const matches: HTMLElement[] = [];\n if (\n rootElement instanceof HTMLElement &&\n rootElement.hasAttribute(\"lvt-fx:url-hash\")\n ) {\n matches.push(rootElement);\n }\n const body = rootElement.ownerDocument?.body;\n if (\n body &&\n body !== rootElement &&\n body.hasAttribute(\"lvt-fx:url-hash\") &&\n !matches.includes(body)\n ) {\n matches.push(body);\n }\n rootElement\n .querySelectorAll<HTMLElement>(\"[lvt-fx\\\\:url-hash]\")\n .forEach((el) => {\n if (!matches.includes(el)) matches.push(el);\n });\n if (matches.length === 0) return;\n\n for (const el of matches) {\n const action = el.getAttribute(\"lvt-fx:url-hash\");\n if (!action) {\n console.warn(\n `lvt-fx:url-hash requires an action name, got: ${JSON.stringify(action)}`\n );\n continue;\n }\n const dataHash = el.getAttribute(\"data-lvt-url-hash\") || \"\";\n const existing = urlHashArmed.get(el);\n if (existing && existing.action === action) {\n existing.updateSend(send);\n mirrorDataAttrToLocation(existing, dataHash);\n continue;\n }\n if (existing) existing.cleanup();\n const entry = attachURLHash(el, action, send);\n urlHashArmed.set(el, entry);\n // First-arm sync: three cases, in priority order.\n const initialLocation = window.location.hash.replace(/^#/, \"\");\n if (\n initialLocation &&\n initialLocation !== dataHash &&\n looksLikeDeepLinkHash(initialLocation)\n ) {\n // (a) URL has a deep-link hash that differs from server state.\n // URL \"wins\" on initial load \u2014 dispatch so the server can\n // reconcile, and seed currentDataHash so the converging render\n // doesn't try to mirror over the user's URL.\n entry.currentDataHash = initialLocation;\n send({ action, data: { hash: initialLocation } });\n } else if (initialLocation && !looksLikeDeepLinkHash(initialLocation)) {\n // (b) URL has a non-deep-link hash (e.g. `#hero` opening a\n // popover, or a native heading anchor). Leave it alone \u2014 it\n // belongs to other machinery (setupHashLink, native scroll).\n // Seed currentDataHash so a later mirror sees the data-attr\n // as the baseline to compare against, and only writes when\n // the user navigates away from the popover/anchor.\n entry.currentDataHash = dataHash;\n } else {\n // (c) URL is empty (or already matches the server). Mirror the\n // server's hash into the URL if any.\n mirrorDataAttrToLocation(entry, dataHash);\n }\n }\n}\n\n/**\n * Cancel url-hash listeners for every armed element under root. Same\n * lifecycle role as teardownAreaSelectForRoot.\n */\nexport function teardownURLHashForRoot(rootElement: Element): void {\n // Includes body when body is an ancestor of rootElement and body is\n // armed \u2014 the directive accepts body placement (see the matcher in\n // handleURLHashDirective), so teardown must symmetrically clean up\n // both directions.\n //\n // Multi-client caveat: a body-armed entry is shared across all\n // LiveTemplateClient instances (Map key is the element, so only one\n // entry per body). Tearing down client A's root will therefore also\n // tear down a body listener that client B armed last \u2014 there's no\n // \"owner\" tracked. Acceptable for the single-client case (the\n // common deployment) and matches the same-element-multi-arm\n // last-writer-wins behavior in attachURLHash. A \"fix\" that\n // restricted the body-cleanup branch to client A would leak\n // client A's own body listener \u2014 don't do that without also\n // tracking entry ownership.\n const body = rootElement.ownerDocument?.body;\n for (const [element, entry] of Array.from(urlHashArmed)) {\n if (rootElement.contains(element)) {\n entry.cleanup();\n continue;\n }\n if (body && element === body && body.contains(rootElement)) {\n entry.cleanup();\n }\n }\n}\n\n// mirrorDataAttrToLocation pushes `dataHash` into `location.hash` if\n// it differs from what's already in the URL. Chooses push vs replace\n// by comparing the path component (everything before the first `:`)\n// against the current location.hash's path: a path change is a \"file\n// switch\" (user-meaningful back-button entry) and gets pushState;\n// any other change is a target-only update (line scroll / anchor\n// scroll) and gets replaceState. Updates entry.currentDataHash so a\n// subsequent render with the same data-attr no-ops.\n//\n// Initial-mirror special case: if the URL was empty when we're\n// mirroring (no prior hash to compare against), use replaceState even\n// though the path-component comparison would say \"changed\". An empty\n// URL \u2192 first server hash isn't a \"navigation\" \u2014 we're establishing\n// the initial state. Using pushState here would let Back land the\n// user on `url-without-hash`, which re-triggers the same arm and\n// pushes the same hash again. Loop.\n//\n// Empty-dataHash special case: if the server transitions FROM a\n// selected file TO no-selection (state.URLHash() returns \"\"), we\n// would otherwise wipe location.hash entirely \u2014 including hashes the\n// directive doesn't own (a popover #hero the user opened during the\n// session). To stay safe, only clear when the URL currently holds a\n// deep-link-shaped hash; non-deep-link hashes are left alone.\nfunction mirrorDataAttrToLocation(entry: URLHashEntry, dataHash: string): void {\n if (entry.currentDataHash === dataHash) return;\n const currentLocation = window.location.hash.replace(/^#/, \"\");\n if (currentLocation === dataHash) {\n entry.currentDataHash = dataHash;\n return;\n }\n if (currentLocation !== \"\" && !looksLikeDeepLinkHash(currentLocation)) {\n // URL is on something not ours (popover id, native anchor) \u2014\n // don't clobber it, regardless of what the server's data-attr\n // says. This covers BOTH the server-clears case (dataHash=\"\")\n // and the rarer server-changes-selection-while-popover-open\n // case (dataHash transitions from one file to another while\n // the URL is parked on a non-deep-link hash).\n entry.currentDataHash = dataHash;\n return;\n }\n warnIfUnencodedHash(dataHash);\n const targetURL = dataHash ? `#${dataHash}` : window.location.pathname + window.location.search;\n const oldPath = currentLocation.split(\":\")[0];\n const newPath = dataHash.split(\":\")[0];\n // Preserve existing history.state \u2014 passing `null` would clobber\n // anything other SPA-like code on the page stores there (scroll\n // position, modal flag, etc.). The state object is independent of\n // the URL we're rewriting, so carrying it forward is the right\n // default.\n const currentState = window.history.state;\n // Empty currentLocation means we're establishing the URL from a\n // blank slate (initial render with no prior URL hash) \u2014 that's NOT\n // a back-button-meaningful navigation, so always replaceState.\n // Otherwise: a path change is a file switch (push), a target-only\n // change is a line/anchor scroll (replace).\n if (currentLocation !== \"\" && oldPath !== newPath) {\n window.history.pushState(currentState, \"\", targetURL);\n } else {\n window.history.replaceState(currentState, \"\", targetURL);\n }\n entry.currentDataHash = dataHash;\n}\n\n// warnIfUnencodedHash flags `data-lvt-url-hash` values containing\n// characters that should be percent-encoded (raw space, `<`, `>`,\n// `\"`, ``` ` ```, `#`, `[`, `]`, `%`). The directive writes the\n// hash verbatim into `pushState`/`replaceState`, so an unencoded\n// value will silently produce a malformed URL \u2014 `location.hash`\n// reads back differently from what was set. Cheap dev-time guard\n// against a server-side contract slip; dedupes by value to avoid\n// log spam.\n//\n// `%` is included because a raw `%` not followed by two hex digits\n// is itself a percent-encoding error. The check is a heuristic\n// (won't catch every malformed escape), but covers the common\n// \"forgot to encode\" cases.\nconst urlHashUnencodedWarned = new Set<string>();\n\n/**\n * Test-only: reset the per-page dedupe Set that suppresses repeated\n * `warnIfUnencodedHash` calls for the same hash value. Production\n * code shouldn't need this \u2014 the Set is bounded by the number of\n * unique malformed hashes \u2014 but tests that re-use the same hash\n * across cases need to clear it or the second test won't see the\n * warning. Mirrors `__resetAnimatedElementsForTesting`.\n */\nexport function __resetURLHashUnencodedWarnedForTesting(): void {\n urlHashUnencodedWarned.clear();\n}\n\nfunction warnIfUnencodedHash(hash: string): void {\n if (!hash || urlHashUnencodedWarned.has(hash)) return;\n if (/[ <>\"`#\\[\\]]/.test(hash) || /%(?![0-9A-Fa-f]{2})/.test(hash)) {\n urlHashUnencodedWarned.add(hash);\n console.warn(\n `lvt-fx:url-hash: data-lvt-url-hash=\"${hash}\" contains characters that should be percent-encoded. The directive writes it verbatim into history.pushState/replaceState; malformed URLs result. Server-side FormatHash (or equivalent) should percent-escape path segments and target ids before serialization.`\n );\n }\n}\n\n// looksLikeDeepLinkHash discriminates URL hashes the prereview deep-\n// link grammar can produce (file path with optional :L<n> or :h-id)\n// from hashes that belong to other native machinery (HTML element\n// anchors, dialog/popover/details ids, etc.). Deep-link hashes always\n// contain at least one of: `:` (target separator), `/` (nested path),\n// or `.` (file extension). Empty \u2192 false.\n//\n// False positives are possible but cheap. A heading id like\n// `#v1.0.0`, `#menu/item`, or `#key:value` matches this heuristic\n// and will dispatch the action \u2014 but the consuming server is\n// expected to no-op on hashes whose path doesn't resolve to a known\n// file (prereview's SetURLHash does, via the loadDiffCached failure\n// path). The cost is one wasted roundtrip per false positive, which\n// is acceptable for the alternative of missing real deep links.\n//\n// False negatives: extension-less filenames at the repo root \u2014\n// `#Makefile`, `#Dockerfile`, `#LICENSE` \u2014 don't match this\n// heuristic and won't be dispatched as path-only deep links. The\n// workaround is the line-form (`#Makefile:L1`), which always\n// dispatches. This trade-off is deliberate: a heuristic that\n// matched single-token hashes would also clobber every native\n// anchor / popover id, which is a much worse default. Consumers\n// that need extension-less file deep links can build a richer\n// directive on top.\nfunction looksLikeDeepLinkHash(hash: string): boolean {\n if (!hash) return false;\n return hash.includes(\":\") || hash.includes(\"/\") || hash.includes(\".\");\n}\n\nfunction attachURLHash(\n el: HTMLElement,\n action: string,\n initialSend: URLHashSendFn\n): URLHashEntry {\n const entry: URLHashEntry = {\n action,\n send: initialSend,\n cleanup: () => {\n urlHashArmed.delete(el);\n if (urlHashArmed.size === 0 && urlHashWindowListener) {\n window.removeEventListener(\"hashchange\", urlHashWindowListener);\n urlHashWindowListener = null;\n }\n },\n updateSend: (s) => {\n entry.send = s;\n },\n currentDataHash: \"\",\n };\n\n if (!urlHashWindowListener) {\n urlHashWindowListener = () => {\n const hash = window.location.hash.replace(/^#/, \"\");\n // Only dispatch hashes that look like deep-link targets \u2014 they\n // contain `:` (target separator), `/` (nested path), or `.`\n // (file extension). Plain element-id hashes like `#hero` or\n // `#confirm-delete-xyz` belong to the native anchor / dialog /\n // popover / details machinery (setupHashLink handles those) and\n // would otherwise be dispatched here, prompt a server no-op,\n // then get clobbered by the mirror step when the server's\n // data-attr (unchanged) doesn't match.\n //\n // Empty hash (user cleared the URL bar) is also intentionally\n // ignored. The directive treats the server as the source of\n // truth for \"what's selected\"; an empty URL is \"user navigated\n // away from a hash\" but not \"deselect everything\". If the user\n // wants to deselect, they use the in-app affordance\n // (clearSelection / Escape) which makes the server emit an\n // empty data-attr \u2014 at which point the mirror step propagates\n // the empty hash back to the URL.\n if (!looksLikeDeepLinkHash(hash)) return;\n // Iterate via Array.from in case a dispatched action triggers a\n // render that mutates the armed map (e.g. tears down this\n // element). Each armed entry dispatches through its OWN send +\n // action so multi-arm is deterministic \u2014 typically the body is\n // the only armed element so this is one iteration.\n for (const e of Array.from(urlHashArmed.values())) {\n // Record the user-driven hash as the new baseline so the\n // next render's mirror step doesn't immediately revert it.\n e.currentDataHash = hash;\n e.send({ action: e.action, data: { hash } });\n }\n };\n window.addEventListener(\"hashchange\", urlHashWindowListener);\n }\n\n return entry;\n}\n\n// attachAreaSelect captures `send` in a mutable local so the\n// idempotent re-arm path (same element, same action) can swap it via\n// the returned `updateSend` callback without tearing down + rebuilding\n// listeners. Listeners reference the closure-captured `send` variable\n// directly, so reassigning it propagates instantly. This guards\n// against the stale-closure trap a caller would hit if their `send`\n// reference changed across renders \u2014 e.g. a reconnect rebuilt the\n// transport.\nfunction attachAreaSelect(\n el: HTMLElement,\n action: string,\n initialSend: AreaSelectSendFn\n): AreaSelectEntry {\n let send = initialSend;\n let overlay: HTMLDivElement | null = null;\n let startClientX = 0;\n let startClientY = 0;\n let pointerId = -1;\n // Capture the parent at pointerdown time so a server diff that moves\n // the host to a NEW parent mid-drag doesn't split the drag across\n // two positioning contexts. updateOverlay positions against this\n // cached parent for the lifetime of the gesture; the overlay itself\n // stays a child of the parent we appended it to (overlay removal\n // uses overlay.parentElement, which is independent).\n let dragParent: HTMLElement | null = null;\n // Cache the host's rect at pointerdown \u2014 startClientX/Y are captured\n // in the SAME frame, so the start corner is meaningful only against\n // the rect that existed then. If a server diff repositions the host\n // mid-drag, finalize would otherwise clamp the (old-coord-system)\n // startClientX against the new rect and silently produce wrong\n // fractions. Anchoring to the start-rect keeps the dispatched\n // rectangle pinned to the visual region the user actually dragged.\n let startRect: DOMRect | null = null;\n\n const removeOverlay = () => {\n if (overlay) {\n // Element.remove() is a no-op if the node isn't in the DOM,\n // so we don't need the parent-null guard the older two-step\n // pattern needed.\n overlay.remove();\n }\n overlay = null;\n };\n\n const finalize = (e: PointerEvent | null, dispatch: boolean) => {\n if (pointerId === -1) return;\n // CRITICAL ORDER: reset pointerId + dragParent + startRect BEFORE\n // calling releasePointerCapture. Chromium fires lostpointercapture\n // SYNCHRONOUSLY during releasePointerCapture, which lands in\n // onLostCapture \u2192 finalize(null, false). Without the early reset,\n // the nested finalize sees pointerId still matching and runs to\n // completion (clearing startRect), then the outer finalize\n // resumes with startRect == null and silently drops the\n // dispatched action. Resetting first makes the nested call\n // return at the `pointerId === -1` guard, leaving outer state\n // intact.\n const capturedPointerId = pointerId;\n const rect = startRect;\n pointerId = -1;\n dragParent = null;\n startRect = null;\n try {\n el.releasePointerCapture(capturedPointerId);\n } catch {\n // Capture may already be gone (e.g. pointercancel) \u2014 ignore.\n }\n // Remove the per-gesture pointerleave fallback so a NEXT drag\n // doesn't inherit a stale listener from this one. {once: true}\n // only auto-removes if it fires; a stuck drag never fired it.\n el.removeEventListener(\"pointerleave\", onPointerLeaveCancel);\n if (!dispatch || !e || !rect) {\n removeOverlay();\n return;\n }\n if (rect.width <= 0 || rect.height <= 0) {\n removeOverlay();\n return;\n }\n // Clamp the two corners to the rect BEFORE computing fractions so\n // a drag that escapes the element still yields a rectangle inside\n // it (x \u2208 [0,1], w \u2208 [0,1-x]). Otherwise a far-off-rect endpoint\n // would push w past 1 even with x already > 0.\n const rectRight = rect.left + rect.width;\n const rectBottom = rect.top + rect.height;\n const x0 = clampRange(Math.min(startClientX, e.clientX), rect.left, rectRight);\n const y0 = clampRange(Math.min(startClientY, e.clientY), rect.top, rectBottom);\n const x1 = clampRange(Math.max(startClientX, e.clientX), rect.left, rectRight);\n const y1 = clampRange(Math.max(startClientY, e.clientY), rect.top, rectBottom);\n const x = (x0 - rect.left) / rect.width;\n const y = (y0 - rect.top) / rect.height;\n const w = (x1 - x0) / rect.width;\n const h = (y1 - y0) / rect.height;\n removeOverlay();\n // Reject zero-area rectangles outright. The MIN_AREA_FRACTION\n // check below uses `&&` (drop only when BOTH dims are small) so\n // a wide-but-thin selection is preserved \u2014 but a literal\n // 60%\u00D70 (or 0\u00D760%) collapses to no region, can't be rendered\n // sensibly, and would divide by zero in any pixel-space\n // conversion downstream. Drop independently of the threshold.\n if (w <= 0 || h <= 0) return;\n // Drop when BOTH dimensions are below the threshold (intentional\n // `&&` \u2014 NOT `||`). A wide-but-thin drag (e.g. an underline across\n // an annotated row) or a tall-but-thin drag (e.g. a vertical\n // highlight) is a real selection in this directive's contract,\n // not an accidental click. `||` would drop those legitimate\n // gestures. The click-vs-drag boundary lives in \"the rect has\n // basically no area\" \u2014 that's both dims below the threshold.\n if (w < MIN_AREA_FRACTION && h < MIN_AREA_FRACTION) {\n // Treat as a click, not a drag. Don't dispatch; let normal click\n // handlers (if any) run via the platform.\n return;\n }\n send({ action, data: { x, y, w, h } });\n };\n\n const onPointerLeaveCancel = (e: PointerEvent) => {\n // Fallback for the rare case where setPointerCapture failed: without\n // capture, pointermove + pointerup stop arriving once the pointer\n // leaves the host, freezing the overlay. Treating pointerleave as\n // a cancel keeps the overlay from getting stuck on screen.\n // Guard on pointerId \u2014 in multi-touch, a SECONDARY pointer's\n // leave shouldn't cancel the primary drag.\n if (e.pointerId !== pointerId) return;\n finalize(null, false);\n };\n\n // Chromium fires `dragstart` on an <img> after the first mousemove\n // following mousedown, yanking the gesture away from pointer events\n // before pointerup arrives \u2014 the overlay flashes and capture is\n // lost. preventDefault on dragstart suppresses the native image\n // drag without breaking pointer events. Cheap to attach on every\n // element type (non-img hosts simply never fire dragstart).\n const onDragStart = (e: DragEvent) => e.preventDefault();\n\n const onPointerDown = (e: PointerEvent) => {\n // Only primary button (left mouse / single touch / pen tip). Modifier\n // keys passed through so the server-side handler can decide what to\n // do with them via subsequent renders.\n if (!e.isPrimary || e.button !== 0) return;\n // Re-entrancy guard: if a prior drag never finished (e.g. capture\n // failed silently, then pointer left the element with no pointerup\n // ever delivered), the closed-over pointerId variable would still\n // hold the stale id. Cancel the prior drag \u2014 removing its overlay\n // and listeners \u2014 before starting a fresh one.\n if (pointerId !== -1) finalize(null, false);\n const parent = el.parentElement;\n if (!parent) return; // overlay needs a positioned container\n // Dev-time check: if the parent doesn't establish a positioning\n // context, the overlay's `position: absolute` will resolve against\n // the nearest positioned ANCESTOR \u2014 a distant element with no\n // visible relationship to the host. Result: overlay paints in\n // the wrong place with no error, just a confusing visual.\n // Check against the positive list of positioned values; the\n // default \"static\" and an unset/empty value both fail it (jsdom\n // returns \"\" for unset position). Dedupe via WeakSet so a user\n // dragging repeatedly on the same mis-configured parent gets ONE\n // console message, not one per pointerdown.\n if (!areaSelectWarnedParents.has(parent)) {\n const parentPos = window.getComputedStyle(parent).position;\n if (\n parentPos !== \"relative\" &&\n parentPos !== \"absolute\" &&\n parentPos !== \"fixed\" &&\n parentPos !== \"sticky\"\n ) {\n console.warn(\n \"lvt-fx:area-select: parentElement has no positioning context; the drag overlay will be mis-positioned. \" +\n \"Add position:relative (or absolute/fixed/sticky) to the parent.\",\n parent\n );\n areaSelectWarnedParents.add(parent);\n }\n }\n startClientX = e.clientX;\n startClientY = e.clientY;\n pointerId = e.pointerId;\n dragParent = parent;\n startRect = el.getBoundingClientRect();\n let captureOk = false;\n try {\n el.setPointerCapture(pointerId);\n captureOk = true;\n } catch {\n // Capture failure is non-fatal \u2014 without it, leaving the element\n // mid-drag will lose pointermove. Fall back to pointerleave as\n // the cancel signal so the overlay can't get stuck.\n }\n if (!captureOk) {\n el.addEventListener(\"pointerleave\", onPointerLeaveCancel, { once: true });\n }\n overlay = document.createElement(\"div\");\n overlay.className = \"lvt-area-select-overlay\";\n overlay.setAttribute(\"aria-hidden\", \"true\");\n // Inline styles so the directive doesn't depend on a CSS class\n // shipped by the consumer. Consumers can override via the class\n // selector if they want a different look.\n overlay.style.cssText =\n \"position:absolute;pointer-events:none;border:2px solid var(--lvt-area-select-color,#4cc2ff);\" +\n \"background:var(--lvt-area-select-fill,rgba(76,194,255,0.18));box-sizing:border-box;\" +\n \"z-index:var(--lvt-area-select-z-index,9999);\";\n parent.appendChild(overlay);\n updateOverlay(e);\n // NOT calling e.preventDefault() here: doing so on pointerdown\n // suppresses the compatibility mouse events (mousedown \u2192 mouseup\n // \u2192 click), so a small-rect drag (which finalize() treats as a\n // click) would never reach the host's click handlers. The\n // directive's contract promises clicks still bubble. Text-\n // selection during drag is the consumer's responsibility \u2014 set\n // `user-select: none` on the host (the contract docs this).\n };\n\n const updateOverlay = (e: PointerEvent) => {\n if (!overlay) return;\n // Use the parent captured at pointerdown \u2014 if a server diff\n // moved `el` to a new parent mid-drag, re-fetching el.parentElement\n // here would compute against the new container while the overlay\n // lives in the old, paint at the wrong place for the rest of the\n // gesture.\n const parent = dragParent;\n if (!parent) return;\n const elRect = el.getBoundingClientRect();\n const parentRect = parent.getBoundingClientRect();\n // Convert viewport coords (clientX/Y) to position:absolute CSS\n // offsets inside the parent. Three corrections, all subtracted\n // from / added to the same way for every value we compute:\n //\n // 1. parentRect.left/top \u2014 getBoundingClientRect is in viewport\n // coords; CSS offsets are relative to the parent's box.\n // 2. parent.clientLeft/Top \u2014 position:absolute is measured from\n // the padding box; getBoundingClientRect returns the border\n // box. A parent with a CSS border would otherwise shift the\n // overlay by the border width.\n // 3. parent.scrollLeft/Top \u2014 when the parent is scrolled, an\n // element at viewport_x = parentRect.left has CSS_left =\n // parent.scrollLeft (not 0). Without adding scroll back in,\n // the overlay paints offset by the scroll amount.\n const borderL = parent.clientLeft;\n const borderT = parent.clientTop;\n const scrollL = parent.scrollLeft;\n const scrollT = parent.scrollTop;\n const toCSSLeft = (vx: number) => vx - parentRect.left - borderL + scrollL;\n const toCSSTop = (vy: number) => vy - parentRect.top - borderT + scrollT;\n const left = toCSSLeft(Math.min(startClientX, e.clientX));\n const top = toCSSTop(Math.min(startClientY, e.clientY));\n const width = Math.abs(e.clientX - startClientX);\n const height = Math.abs(e.clientY - startClientY);\n // Clamp to the host's rendered rect (in the same CSS coord space)\n // so a drag that runs off the edge doesn't paint outside the host.\n const minLeft = toCSSLeft(elRect.left);\n const minTop = toCSSTop(elRect.top);\n const maxRight = minLeft + elRect.width;\n const maxBottom = minTop + elRect.height;\n const clampedLeft = Math.max(minLeft, Math.min(left, maxRight));\n const clampedTop = Math.max(minTop, Math.min(top, maxBottom));\n const clampedRight = Math.max(minLeft, Math.min(left + width, maxRight));\n const clampedBottom = Math.max(minTop, Math.min(top + height, maxBottom));\n overlay.style.left = `${clampedLeft}px`;\n overlay.style.top = `${clampedTop}px`;\n overlay.style.width = `${Math.max(0, clampedRight - clampedLeft)}px`;\n overlay.style.height = `${Math.max(0, clampedBottom - clampedTop)}px`;\n };\n\n const onPointerMove = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n // Host removed from the DOM mid-drag (e.g. server diff replaced it).\n // Without this, the overlay would be left orphaned under the parent\n // because the host's cleanup never runs.\n if (!el.isConnected) {\n finalize(null, false);\n return;\n }\n updateOverlay(e);\n };\n\n const onPointerUp = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n if (!el.isConnected) {\n finalize(null, false);\n return;\n }\n finalize(e, true);\n };\n\n const onPointerCancel = (e: PointerEvent) => {\n if (e.pointerId !== pointerId) return;\n finalize(e, false);\n };\n // lostpointercapture handles the rare case where the platform yanks\n // capture (OS gesture, another setPointerCapture call). Guard on\n // pointerId \u2014 another code path could call setPointerCapture for a\n // DIFFERENT pointer on the same element, and we mustn't cancel\n // our in-progress drag because of an unrelated release.\n const onLostCapture = (e: PointerEvent) => {\n if (e.pointerId === pointerId) finalize(null, false);\n };\n\n el.addEventListener(\"pointerdown\", onPointerDown);\n el.addEventListener(\"pointermove\", onPointerMove);\n el.addEventListener(\"pointerup\", onPointerUp);\n el.addEventListener(\"pointercancel\", onPointerCancel);\n el.addEventListener(\"lostpointercapture\", onLostCapture);\n el.addEventListener(\"dragstart\", onDragStart);\n\n const cleanup = () => {\n el.removeEventListener(\"pointerdown\", onPointerDown);\n el.removeEventListener(\"pointermove\", onPointerMove);\n el.removeEventListener(\"pointerup\", onPointerUp);\n el.removeEventListener(\"pointercancel\", onPointerCancel);\n el.removeEventListener(\"lostpointercapture\", onLostCapture);\n el.removeEventListener(\"pointerleave\", onPointerLeaveCancel);\n el.removeEventListener(\"dragstart\", onDragStart);\n finalize(null, false);\n areaSelectArmed.delete(el);\n };\n\n return {\n action,\n cleanup,\n updateSend: (s) => {\n send = s;\n },\n };\n}\n\nfunction clampRange(n: number, lo: number, hi: number): number {\n if (!Number.isFinite(n) || n < lo) return lo;\n if (n > hi) return hi;\n return 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", "/**\n * Preview-mode field redaction.\n *\n * Some apps (e.g. a \"try it before you sign up\" preview) need sensitive field\n * values \u2014 passport numbers, tax IDs, draft answers \u2014 to stay in the visitor's\n * browser and never reach the server. An element opts in with the\n * `data-lvt-redact=\"<field>\"` attribute:\n *\n * <input name=\"passport\" data-lvt-redact=\"passport\">\n *\n * Two halves make the round-trip work:\n *\n * 1. Outgoing: before an action payload is sent, the raw value is written to\n * localStorage and replaced with a redact sentinel `{ redacted: true, field }`.\n * The server learns the field was provided (so it can keep structural state /\n * validate presence) but never sees the value. Two sinks cover the two send\n * transports: `redactActionData` for the JSON payload (WebSocket / HTTP-JSON)\n * and `redactFormData` for the multipart file-upload path.\n *\n * 2. Incoming (hydrateRedactedTokens): after each DOM patch, real values are\n * filled back into `[data-lvt-redact]` elements from localStorage \u2014 `.value`\n * for inputs, `textContent` for the <span> the Go `lvt.Redact` helper emits.\n * Substitution is scoped to the attribute, never a free text scan, so\n * user-posted content cannot trigger it.\n *\n * Values are namespaced by the page's `data-lvt-id` scope so two LiveTemplate\n * pages in the same origin don't collide.\n */\n\nconst STORAGE_PREFIX = \"lvt-redact\";\n\nexport interface RedactOptions {\n /** Storage backend; defaults to window.localStorage. Injectable for tests. */\n storage?: Storage;\n /** Namespace for stored values; defaults to the page's data-lvt-id. */\n scope?: string;\n}\n\n/** The sentinel that replaces a redacted value in an outgoing action payload. */\nexport interface RedactSentinel {\n redacted: true;\n field: string;\n}\n\nfunction storageKey(scope: string, field: string): string {\n return `${STORAGE_PREFIX}:${scope}:${field}`;\n}\n\n/**\n * Resolve the redaction namespace from the live DOM. Uses the wrapper's\n * `data-lvt-id` so values are scoped per page. Falls back to \"lvt-unknown\"\n * before the wrapper is wired (no value collides with a real scope).\n */\nfunction resolveScope(ownerDocument: Document | null | undefined): string {\n const doc = ownerDocument ?? (typeof document !== \"undefined\" ? document : null);\n const wrapper = doc?.querySelector?.(\"[data-lvt-id]\");\n return wrapper?.getAttribute(\"data-lvt-id\") || \"lvt-unknown\";\n}\n\nfunction getStorage(opts?: RedactOptions): Storage | null {\n if (opts?.storage) return opts.storage;\n try {\n return typeof localStorage !== \"undefined\" ? localStorage : null;\n } catch {\n // localStorage can throw (disabled cookies, sandboxed iframe). Treat as\n // absent \u2014 redaction degrades to a no-op rather than breaking the app.\n return null;\n }\n}\n\n/** Elements that carry a redactable value: <input>, <textarea>, <select>. */\nfunction hasValue(el: Element): el is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement {\n return (\n el instanceof HTMLInputElement ||\n el instanceof HTMLTextAreaElement ||\n el instanceof HTMLSelectElement\n );\n}\n\n/**\n * Collect every redact-tagged element at or under `root` \u2014 `root` itself when it\n * carries the attribute, plus all descendants. Used by both the outgoing path\n * (the action element may be the tagged input) and the incoming path (so a\n * tagged root is hydrated, not just its children).\n */\nfunction collectRedactElements(root: Element): Element[] {\n const found: Element[] = [];\n if (root.hasAttribute(\"data-lvt-redact\")) {\n found.push(root);\n }\n root.querySelectorAll?.(\"[data-lvt-redact]\").forEach((el) => found.push(el));\n return found;\n}\n\n/**\n * Shared outgoing-redaction core. For every redact-tagged element at/under\n * `actionElement`: persist its value to localStorage and hand the sink the\n * payload key + sentinel to write. The sink differs per transport (a JSON\n * payload object vs. a multipart FormData).\n *\n * Redaction is fail-closed: the sentinel is ALWAYS written, regardless of\n * whether persistence is possible. Persistence is the best-effort part \u2014 if\n * localStorage is entirely unavailable (disabled / sandboxed iframe) or setItem\n * throws (quota), the raw value is still dropped from the payload so it never\n * reaches the server. The security guarantee (never leak) takes priority over\n * the UX (being able to restore the value later).\n */\nfunction applyOutgoingRedaction(\n actionElement: Element,\n opts: RedactOptions | undefined,\n sink: (key: string, sentinel: RedactSentinel) => void,\n): void {\n const storage = getStorage(opts);\n const scope = storage\n ? (opts?.scope ?? resolveScope(actionElement.ownerDocument))\n : null;\n\n for (const el of collectRedactElements(actionElement)) {\n const field = el.getAttribute(\"data-lvt-redact\");\n if (!field || !hasValue(el)) continue;\n\n // Persist best-effort; both branches (no storage, setItem throws) fall\n // through to the sentinel write below \u2014 never to leaking the raw value.\n if (storage && scope) {\n try {\n storage.setItem(storageKey(scope, field), el.value);\n } catch {\n // Quota exceeded \u2014 value not persisted, but still redacted below.\n }\n }\n // The payload key the server sees is the element's `name` (falling back to\n // the redact field name, mirroring the event-delegation \"name || value\"\n // convention).\n const key = el.getAttribute(\"name\") || field;\n sink(key, { redacted: true, field });\n }\n}\n\n/**\n * Persist redacted values to localStorage and replace them in the outgoing\n * JSON action payload with a sentinel. Mutates `data` in place. Used by the\n * WebSocket / HTTP-JSON send path.\n */\nexport function redactActionData(\n actionElement: Element,\n data: Record<string, unknown>,\n opts?: RedactOptions,\n): void {\n applyOutgoingRedaction(actionElement, opts, (key, sentinel) => {\n data[key] = sentinel;\n });\n}\n\n/**\n * Persist redacted values to localStorage and replace them in an outgoing\n * multipart `FormData` with a JSON-encoded sentinel. Mutates `formData` in\n * place. Used by the Tier-1 file-upload send path (sendHTTPMultipart), which\n * bypasses the JSON payload \u2014 without this, a redacted field in a form that\n * also has a file input would POST its raw value as a multipart field.\n *\n * FormData values are strings, so the sentinel is JSON-encoded; the server\n * recognises a redacted multipart field by parsing the value and finding\n * `redacted:true` (mirroring the object sentinel on the JSON path).\n */\nexport function redactFormData(\n form: Element,\n formData: FormData,\n opts?: RedactOptions,\n): void {\n applyOutgoingRedaction(form, opts, (key, sentinel) => {\n formData.set(key, JSON.stringify(sentinel));\n });\n}\n\n/**\n * Fill every `[data-lvt-redact]` element from localStorage. Call on the LIVE\n * element after the patch commits. Substitution is scoped to elements carrying\n * the attribute \u2014 never a free text scan \u2014 so user-posted content can't trigger\n * it. Value elements (input/textarea/select) get `.value`; others (the <span>\n * the Go lvt.Redact helper emits) get `textContent`. Reads are cached per call.\n */\nexport function hydrateRedactedTokens(root: Element, opts?: RedactOptions): void {\n const storage = getStorage(opts);\n if (!storage) return;\n const scope = opts?.scope ?? resolveScope(root.ownerDocument);\n\n const cache = new Map<string, string | null>();\n const read = (field: string): string | null => {\n if (cache.has(field)) return cache.get(field)!;\n let v: string | null = null;\n try {\n v = storage.getItem(storageKey(scope, field));\n } catch {\n v = null;\n }\n cache.set(field, v);\n return v;\n };\n\n // Skip the element the user is currently editing \u2014 overwriting .value\n // mid-keystroke would clobber in-progress input (and the stored value may lag\n // what they've typed since the last dispatch).\n const active = root.ownerDocument?.activeElement ?? null;\n for (const el of collectRedactElements(root)) {\n if (el === active) continue;\n const field = el.getAttribute(\"data-lvt-redact\");\n if (!field) continue;\n const v = read(field);\n if (v === null) continue;\n if (hasValue(el)) {\n // For file inputs, el.value is the fake C:\\fakepath string, not file\n // contents \u2014 redacting file fields is out of scope for this helper.\n el.value = v;\n } else {\n el.textContent = v;\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 type { Logger } from \"./logger\";\n\n/**\n * Module-level warn-once latch shared by all `lvt-no-intercept` shim sites\n * (link interceptor + form auto-wiring + form submit handling). One warning\n * per process is enough to prompt migration without spamming the console.\n */\nlet legacyNoInterceptWarned = false;\n\n/**\n * Returns true when an element opts out of LiveTemplate interception.\n *\n * Recognizes both the current Tier 2 namespaced attribute (`newName`, e.g.\n * `lvt-nav:no-intercept` for links or `lvt-form:no-intercept` for forms) and\n * the pre-Phase 1A `lvt-no-intercept` name as a backward-compat shim. The\n * legacy name emits a one-time deprecation warning through the supplied\n * logger and is removed in v0.9.0.\n */\nexport function hasNoInterceptOptOut(\n el: Element,\n newName: string,\n logger: Logger\n): boolean {\n if (el.hasAttribute(newName)) return true;\n if (el.hasAttribute(\"lvt-no-intercept\")) {\n if (!legacyNoInterceptWarned) {\n legacyNoInterceptWarned = true;\n logger.warn(\n `lvt-no-intercept is deprecated; use ${newName}. The shim will be removed in v0.9.0.`\n );\n }\n return true;\n }\n return false;\n}\n\n/**\n * Reset the warn-once latch. Internal \u2014 test code only. Tests that exercise\n * the shim need to start from a clean state to assert that the warning fires\n * exactly once per process.\n *\n * @internal\n */\nexport function _resetLegacyNoInterceptWarned(): void {\n legacyNoInterceptWarned = false;\n}\n", "import { debounce, throttle } from \"../utils/rate-limit\";\nimport { redactActionData, redactFormData } from \"./redact\";\nimport { lvtSelector } from \"../utils/lvt-selector\";\nimport { executeAction, resolveTarget, processElementInteraction, isDOMEventTrigger, type ReactiveAction } from \"./reactive-attributes\";\nimport type { Logger } from \"../utils/logger\";\nimport { hasNoInterceptOptOut } from \"../utils/legacy-attr\";\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\n// Wire MIME for the dragged element's data-key. Set on dragstart, read on drop.\nconst LVT_DRAG_MIME = \"application/x-lvt-key\";\n\nconst DRAG_EVENTS = new Set([\n \"dragstart\",\n \"dragover\",\n \"drop\",\n \"dragend\",\n \"dragenter\",\n \"dragleave\",\n]);\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 \"dragstart\",\n \"dragover\",\n \"drop\",\n \"dragend\",\n \"dragenter\",\n \"dragleave\",\n] as const;\n\nexport class EventDelegator {\n // Track forms we've already warned about for lvt-form:emit-submitter on\n // GET. Per-form de-dup so dev consoles aren't flooded; WeakSet so forms\n // GC'd from the DOM don't pin memory.\n private warnedEmitSubmitterGETForms = new WeakSet<HTMLFormElement>();\n\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 (!hasNoInterceptOptOut(element, \"lvt-form:no-intercept\", this.logger)) {\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 } else if (element.hasAttribute(\"lvt-form:emit-submitter\")) {\n // Phase 2 of livetemplate#237: for non-intercepted (native HTML)\n // submissions, inject a hidden <input name=\"lvt-submitter\">\n // populated from SubmitEvent.submitter.name so the server can\n // route the action without falling back to the empty-value\n // heuristic. Creates on first submit if absent; updates on\n // subsequent submits. Pure no-JS forms cannot use this \u2014 they\n // keep relying on the heuristic.\n //\n // Only acts when SubmitEvent.submitter has a non-empty name,\n // matching the WS and HTTP-multipart paths above. Submits with\n // no named submitter (e.g., programmatic dispatch \u2014 out of\n // scope per the proposal) keep any previous value and fall\n // through to the heuristic for that submission.\n //\n // GET-form caveat: a `method=\"GET\"` form serializes form\n // fields into the URL query string, so `lvt-submitter` will\n // appear in the browser history bar and any shared/bookmarked\n // URLs. The directive does not guard against this \u2014 apps\n // routing GET forms with multiple submit buttons should\n // either not opt into this directive or accept the URL\n // pollution as the cost of explicit routing.\n const submitter = (e as SubmitEvent).submitter as\n | HTMLButtonElement\n | HTMLInputElement\n | null;\n if (submitter?.name) {\n // GET-form URL pollution: only warn when we'd actually\n // inject a field (named submitter). A GET form whose\n // current submission has no named submitter writes no\n // lvt-submitter, so there's nothing to pollute and no\n // warning to fire.\n if (element.method === \"get\" && !this.warnedEmitSubmitterGETForms.has(element)) {\n this.logger.warn(\n \"lvt-form:emit-submitter on a GET form serializes lvt-submitter into the URL query string, polluting browser history and any shared/bookmarked URLs. Use method=\\\"POST\\\" or remove the directive if URL pollution is unacceptable.\",\n element\n );\n this.warnedEmitSubmitterGETForms.add(element);\n }\n // Filter on type=\"hidden\" so we never mutate a developer-\n // authored visible <input name=\"lvt-submitter\"> that happens\n // to live in the form for some other purpose.\n let hiddenInput = element.querySelector<HTMLInputElement>(\n 'input[type=\"hidden\"][name=\"lvt-submitter\"]'\n );\n if (!hiddenInput) {\n hiddenInput = document.createElement(\"input\");\n hiddenInput.type = \"hidden\";\n hiddenInput.name = \"lvt-submitter\";\n element.appendChild(hiddenInput);\n }\n hiddenInput.value = submitter.name;\n } else {\n // No named submitter on this submission \u2014 clear any stale\n // hidden input left over from a previous named submit.\n // Without this, a [name=save] click followed by an unnamed\n // click would send the stale \"save\" value to the server\n // and misroute the action.\n element\n .querySelector<HTMLInputElement>(\n 'input[type=\"hidden\"][name=\"lvt-submitter\"]'\n )\n ?.remove();\n }\n }\n }\n\n if (action != null && actionElement) {\n if (eventType === \"submit\") {\n e.preventDefault();\n }\n\n // Drag side-effects run BEFORE the throttle gate below: the\n // browser only honors preventDefault and dataTransfer.setData\n // during the native event tick, so they cannot wait on a\n // throttled handleAction. dragover.preventDefault is what\n // licenses drop to fire; without it the browser silently\n // ignores drops on this target.\n if (DRAG_EVENTS.has(eventType)) {\n const dragEvent = e as DragEvent;\n if (eventType === \"dragstart\" && dragEvent.dataTransfer) {\n const keyEl = actionElement.closest(\"[data-key]\");\n const key = keyEl?.getAttribute(\"data-key\") ?? \"\";\n // Custom MIME only \u2014 never text/plain. Putting the key in\n // text/plain would leak it to any external drop target\n // (URL bar, text editor, another app).\n dragEvent.dataTransfer.setData(LVT_DRAG_MIME, key);\n dragEvent.dataTransfer.effectAllowed = \"move\";\n } else if (eventType === \"dragover\") {\n dragEvent.preventDefault();\n if (dragEvent.dataTransfer) {\n dragEvent.dataTransfer.dropEffect = \"move\";\n }\n } else if (eventType === \"drop\") {\n dragEvent.preventDefault();\n } else if (eventType === \"dragenter\" || eventType === \"dragleave\") {\n // Skip when the pointer is just crossing into / out of a\n // descendant of the same actionElement \u2014 those are noise\n // from the spec's bubble model. Only fire for boundary\n // crossings between distinct elements.\n const related = dragEvent.relatedTarget as Node | null;\n if (related && actionElement.contains(related)) {\n return;\n }\n }\n\n // Marker pattern: empty action (e.g. lvt-on:dragover=\"\" or\n // lvt-on:dragstart=\"\") opts in to the side-effects above\n // without a WS round-trip. Useful for any drag event when\n // only drop needs server-side handling.\n if (action === \"\") {\n return;\n }\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 if (submitter2.name) {\n message.submitter = submitter2.name;\n }\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 // Drop carries the source key (stashed in DataTransfer on\n // dragstart) and the target key (the drop target's nearest\n // [data-key]) \u2014 the pair a sortable controller needs.\n if (eventType === \"drop\") {\n // Source key only when our LVT MIME is set \u2014 guarantees\n // it came from a same-app dragstart. Cross-app drags\n // (text/plain only) deliberately produce no dragSourceKey\n // so controllers don't have to parse untrusted input.\n const src = (e as DragEvent).dataTransfer?.getData(LVT_DRAG_MIME);\n if (src) {\n message.data.dragSourceKey = src;\n }\n // Target key from the DOM works even when dataTransfer\n // is missing (some embedded environments).\n const tgtKey = actionElement.closest(\"[data-key]\")?.getAttribute(\"data-key\");\n if (tgtKey) {\n message.data.dragTargetKey = tgtKey;\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 if (submitter?.name) {\n tier1FormData.set(\"lvt-submitter\", submitter.name);\n }\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 // Preview-mode redaction on the multipart path: a redacted field\n // in a form that also has a file input would otherwise POST its\n // raw value as a multipart field, bypassing the JSON redaction\n // below. Redact the FormData before it leaves the browser.\n redactFormData(targetElement, tier1FormData);\n this.context.sendHTTPMultipart(\n targetElement as HTMLFormElement,\n action,\n tier1FormData\n );\n return;\n }\n\n // Preview-mode redaction: persist any data-lvt-redact values to\n // localStorage and replace them in the payload with a sentinel so\n // the raw value never reaches the server. No-op when no element is\n // tagged. Runs last so it sees the fully-built message.data.\n redactActionData(targetElement, message.data);\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", "/**\n * Hash-driven element activation (deep-linking).\n *\n * Synchronizes the URL hash fragment with the open/close state of\n * <dialog>, [popover], and <details> elements. When the hash matches\n * an element's ID, the element is activated (showModal, showPopover,\n * or open=true). When the element deactivates, the hash is cleared.\n *\n * Uses history.pushState (not location.hash) to avoid triggering\n * hashchange events that could cause double-activation errors.\n */\n\n/** @internal */\nexport function safeMatchesPopoverOpen(el: HTMLElement): boolean {\n try {\n return el.matches(\":popover-open\");\n } catch {\n return false;\n }\n}\n\ninterface HashLinkHandler {\n matches(el: Element): boolean;\n isOpen(el: Element): boolean;\n open(el: Element): void;\n close(el: Element): void;\n}\n\nconst handlers: HashLinkHandler[] = [\n {\n matches: (el) => el instanceof HTMLDialogElement,\n isOpen: (el) => (el as HTMLDialogElement).open,\n open: (el) => (el as HTMLDialogElement).showModal(),\n close: (el) => (el as HTMLDialogElement).close(),\n },\n {\n matches: (el) =>\n el instanceof HTMLElement && el.hasAttribute(\"popover\"),\n isOpen: (el) => safeMatchesPopoverOpen(el as HTMLElement),\n open: (el) => {\n if (typeof (el as any).showPopover === \"function\")\n (el as HTMLElement).showPopover();\n },\n close: (el) => {\n if (typeof (el as any).hidePopover === \"function\")\n (el as HTMLElement).hidePopover();\n },\n },\n {\n matches: (el) => el instanceof HTMLDetailsElement,\n isOpen: (el) => (el as HTMLDetailsElement).open,\n open: (el) => {\n (el as HTMLDetailsElement).open = true;\n },\n close: (el) => {\n (el as HTMLDetailsElement).open = false;\n },\n },\n];\n\nfunction findHandler(el: Element): HashLinkHandler | undefined {\n return handlers.find((h) => h.matches(el));\n}\n\nconst SHOW_COMMANDS = new Set([\"show-modal\", \"show-popover\"]);\n\nfunction handleClick(e: Event): void {\n const el = e.target;\n if (!el || !(el instanceof Element)) return;\n\n const button = el.closest(\n \"button[command][commandfor]\"\n ) as HTMLButtonElement | null;\n if (!button || button.disabled) return;\n\n const command = button.getAttribute(\"command\");\n if (!command) return;\n\n const targetId = button.getAttribute(\"commandfor\");\n if (!targetId) return;\n\n const target = document.getElementById(targetId);\n if (!target) return;\n\n const handler = findHandler(target);\n if (!handler) return;\n\n if (SHOW_COMMANDS.has(command)) {\n if (location.hash === \"#\" + targetId) return;\n history.pushState(null, \"\", \"#\" + targetId);\n } else if (command === \"toggle-popover\" && !handler.isOpen(target)) {\n if (location.hash === \"#\" + targetId) return;\n history.pushState(null, \"\", \"#\" + targetId);\n }\n}\n\nfunction handleClose(e: Event): void {\n const el = e.target;\n if (!(el instanceof Element)) return;\n if (!el.id) return;\n if (!findHandler(el)) return;\n if (location.hash !== \"#\" + el.id) return;\n\n history.replaceState(null, \"\", location.pathname + location.search);\n}\n\nfunction handleToggle(e: Event): void {\n const el = e.target;\n if (!(el instanceof Element)) return;\n if (!el.id) return;\n\n const handler = findHandler(el);\n if (!handler) return;\n\n if (handler.isOpen(el)) {\n if (location.hash === \"#\" + el.id) return;\n history.pushState(null, \"\", \"#\" + el.id);\n } else {\n if (location.hash !== \"#\" + el.id) return;\n history.replaceState(null, \"\", location.pathname + location.search);\n }\n}\n\nfunction handlePopstate(): void {\n const id = location.hash.slice(1);\n\n // Only close dialogs/popovers \u2014 details elements are independent\n // (multiple can be open at once) and should not be swept closed.\n document.querySelectorAll(\"dialog, [popover]\").forEach((el) => {\n const handler = findHandler(el);\n if (handler && handler.isOpen(el) && el.id !== id) handler.close(el);\n });\n\n if (id) {\n const el = document.getElementById(id);\n if (el) {\n const handler = findHandler(el);\n if (handler && !handler.isOpen(el)) handler.open(el);\n }\n }\n}\n\nexport function openFromHash(): void {\n const id = location.hash.slice(1);\n if (!id) return;\n\n const el = document.getElementById(id);\n if (!el) return;\n\n const handler = findHandler(el);\n if (!handler) return;\n if (handler.isOpen(el)) return;\n\n handler.open(el);\n}\n\nexport function isHashLinkTarget(id: string): boolean {\n const el = document.getElementById(id);\n if (!el) return false;\n return !!findHandler(el);\n}\n\nexport function activateHashTarget(id: string): void {\n const el = document.getElementById(id);\n if (!el) return;\n\n const handler = findHandler(el);\n if (!handler || handler.isOpen(el)) return;\n\n history.pushState(null, \"\", \"#\" + id);\n handler.open(el);\n}\n\nlet installed = false;\n\nexport function setupHashLink(): void {\n if (installed) return;\n installed = true;\n\n document.addEventListener(\"click\", handleClick);\n document.addEventListener(\"close\", handleClose, true);\n document.addEventListener(\"toggle\", handleToggle, true);\n window.addEventListener(\"popstate\", handlePopstate);\n\n openFromHash();\n}\n\n/** @internal Remove all hash-link event listeners. */\nexport function teardownHashLink(): void {\n if (!installed) return;\n installed = false;\n\n document.removeEventListener(\"click\", handleClick);\n document.removeEventListener(\"close\", handleClose, true);\n document.removeEventListener(\"toggle\", handleToggle, true);\n window.removeEventListener(\"popstate\", handlePopstate);\n}\n", "import type { Logger } from \"../utils/logger\";\nimport { hasNoInterceptOptOut } from \"../utils/legacy-attr\";\nimport { isHashLinkTarget, activateHashTarget } from \"./hash-link\";\n\nexport interface LinkInterceptorContext {\n getWrapperElement(): Element | null;\n handleNavigationResponse(html: string, destinationHref: 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. The legacy lvt-no-intercept attribute is also accepted via\n * a backward-compat shim in shouldSkip(); the shim emits a one-time\n * deprecation warning and is removed in v0.9.0.\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 if (target.pathname === window.location.pathname && target.search === window.location.search && target.hash) {\n const hashId = target.hash.slice(1);\n if (hashId && isHashLinkTarget(hashId)) {\n e.preventDefault();\n activateHashTarget(hashId);\n }\n return;\n }\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. hasNoInterceptOptOut also\n // accepts the pre-Phase 1A `lvt-no-intercept` name and emits a one-time\n // deprecation warning. The shim is removed in v0.9.0.\n if (hasNoInterceptOptOut(link, \"lvt-nav:no-intercept\", this.logger)) 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, href);\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 = wrapperElement.querySelector(\"[lvt-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 *\n * Two activation paths share the same physical bar:\n *\n * 1. Initial connect \u2014 `data-lvt-loading=\"true\"` on the wrapper triggers\n * `show()` in autoInit; `hide()` fires from the first server payload.\n * 2. Per-action wait \u2014 `data-lvt-loading-debounce-ms=\"<ms>\"` on the wrapper\n * enables `enablePerActionIndicator(ms)`, which arms a timer on\n * `lvt:pending` (capture-phase) and hides on `lvt:updated`. Idempotent.\n */\nexport class LoadingIndicator {\n private bar: HTMLElement | null = null;\n private actionTimer: ReturnType<typeof setTimeout> | null = null;\n // Counts in-flight actions so concurrent server roundtrips don't hide\n // the bar prematurely: the bar stays visible as long as at least one\n // `lvt:pending` is outstanding. Without this counter, action B\n // completing first would clear the bar even though action A is still\n // in flight.\n private pendingCount = 0;\n private pendingHandler: ((ev: Event) => void) | null = null;\n private updatedHandler: ((ev: Event) => void) | null = null;\n // Current per-action debounce \u2014 tracked so a follow-up enable call\n // with a different value reconfigures cleanly instead of silently\n // dropping the new value.\n private currentDebounceMs: number | null = null;\n\n show(): void {\n if (this.bar) return;\n\n const bar = document.createElement(\"div\");\n bar.className = \"lvt-loading-bar\";\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 * Show the loading bar after `debounceMs` of an action being in flight;\n * hide on the next `lvt:updated`. Capture-phase listeners on document\n * catch both events regardless of dispatch target. Safe to call more\n * than once \u2014 repeat calls are no-ops.\n */\n enablePerActionIndicator(debounceMs: number): void {\n // Same value as already enabled \u2192 no-op (idempotent).\n // Different value \u2192 tear down and re-register cleanly. This\n // matters for callers that re-read the debounce attribute after\n // a config change; without it, the second call would silently\n // discard the new value.\n if (this.pendingHandler) {\n if (this.currentDebounceMs === debounceMs) return;\n this.disablePerActionIndicator();\n }\n this.pendingCount = 0;\n this.currentDebounceMs = debounceMs;\n\n this.pendingHandler = () => {\n this.pendingCount++;\n // Only arm a debounce timer on the 0\u21921 transition. Subsequent\n // concurrent actions don't reset the timer or re-show the bar \u2014\n // the bar that is or will become visible already represents \"at\n // least one action in flight\".\n if (\n this.pendingCount === 1 &&\n this.actionTimer === null &&\n this.bar === null\n ) {\n this.actionTimer = setTimeout(() => {\n this.actionTimer = null;\n // Re-check pendingCount at fire time. In single-threaded JS\n // `updatedHandler` would have called `clearTimeout` before the\n // count could reach zero, so this guard is defensive: it\n // documents the invariant and protects against any future\n // reordering of teardown vs. show.\n if (this.pendingCount > 0) this.show();\n }, debounceMs);\n }\n };\n this.updatedHandler = () => {\n // Math.max guards against an `lvt:updated` arriving without a\n // matching `lvt:pending` (e.g. a server push, or an action whose\n // pending event was dispatched before the listener was attached).\n this.pendingCount = Math.max(0, this.pendingCount - 1);\n if (this.pendingCount === 0) {\n if (this.actionTimer !== null) {\n clearTimeout(this.actionTimer);\n this.actionTimer = null;\n }\n this.hide();\n }\n };\n document.addEventListener(\"lvt:pending\", this.pendingHandler, true);\n document.addEventListener(\"lvt:updated\", this.updatedHandler, true);\n }\n\n /**\n * Teardown for `enablePerActionIndicator`. Stops the listeners, cancels\n * any pending debounce timer, resets the counter, and hides the bar\n * if it's currently visible. Hiding is part of teardown so a caller\n * that reconfigures (different debounce \u2192 disable + re-enable) doesn't\n * leave the prior cycle's bar orphaned waiting for an `lvt:updated`\n * that no listener will receive.\n *\n * Production callers reach this path via two routes: an explicit\n * reconfigure inside `enablePerActionIndicator`, and the LiveTemplate\n * client's `disconnect()` teardown.\n */\n disablePerActionIndicator(): void {\n if (this.pendingHandler) {\n document.removeEventListener(\"lvt:pending\", this.pendingHandler, true);\n this.pendingHandler = null;\n }\n if (this.updatedHandler) {\n document.removeEventListener(\"lvt:updated\", this.updatedHandler, true);\n this.updatedHandler = null;\n }\n if (this.actionTimer !== null) {\n clearTimeout(this.actionTimer);\n this.actionTimer = null;\n }\n this.pendingCount = 0;\n this.currentDebounceMs = null;\n this.hide();\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\"> and\n * popover commands to work cross-browser.\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) return;\n\n if (target instanceof HTMLDialogElement) {\n if (command === \"show-modal\" && !target.open) {\n target.showModal();\n } else if (command === \"close\" && target.open) {\n target.close();\n }\n } else if (target instanceof HTMLElement && target.hasAttribute(\"popover\")) {\n if (command === \"show-popover\" && typeof target.showPopover === \"function\") {\n target.showPopover();\n } else if (command === \"hide-popover\" && typeof target.hidePopover === \"function\") {\n target.hidePopover();\n } else if (command === \"toggle-popover\" && typeof target.togglePopover === \"function\") {\n target.togglePopover();\n }\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 { resolveTarget } from \"./reactive-attributes\";\n\ninterface ScrollAwayBinding {\n trigger: Element;\n target: Element;\n handler: () => void;\n}\n\nconst GUARD_KEY = \"__lvt_scroll_away\";\n\nconst activeBindings: ScrollAwayBinding[] = [];\n\nfunction pruneDisconnectedBindings(): void {\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const binding = activeBindings[i];\n if (binding.trigger.isConnected) continue;\n binding.target.removeEventListener(\"scroll\", binding.handler);\n delete (binding.trigger as any)[GUARD_KEY];\n activeBindings.splice(i, 1);\n }\n}\n\nexport function setupScrollAway(scanRoot: Element): void {\n pruneDisconnectedBindings();\n\n const processEl = (el: Element) => {\n const edge = el.getAttribute(\"lvt-scroll-away\");\n if (!edge) return;\n if (edge !== \"bottom\" && edge !== \"top\") {\n console.warn(`Unknown lvt-scroll-away edge: ${edge}`);\n return;\n }\n\n const target = resolveTarget(el) as HTMLElement;\n if (!target || target === el) {\n const existing = (el as any)[GUARD_KEY] as ScrollAwayBinding | undefined;\n if (existing) {\n existing.target.removeEventListener(\"scroll\", existing.handler);\n removeBinding(existing);\n delete (el as any)[GUARD_KEY];\n }\n console.warn(\"lvt-scroll-away requires data-lvt-target pointing to a scrollable container\");\n return;\n }\n\n const existing = (el as any)[GUARD_KEY] as ScrollAwayBinding | undefined;\n if (existing) {\n if (existing.target === target) return;\n existing.target.removeEventListener(\"scroll\", existing.handler);\n removeBinding(existing);\n }\n\n const raw = parseInt(\n getComputedStyle(el).getPropertyValue(\"--lvt-scroll-threshold\").trim(), 10\n );\n const threshold = isNaN(raw) ? 200 : raw;\n\n let ticking = false;\n const handler = () => {\n if (ticking) return;\n ticking = true;\n requestAnimationFrame(() => {\n ticking = false;\n const distance = edge === \"top\"\n ? target.scrollTop\n : target.scrollHeight - target.scrollTop - target.clientHeight;\n if (distance > threshold) {\n el.classList.add(\"visible\");\n } else {\n el.classList.remove(\"visible\");\n }\n });\n };\n\n target.addEventListener(\"scroll\", handler, { passive: true });\n handler();\n\n const binding: ScrollAwayBinding = { trigger: el, target, handler };\n (el as any)[GUARD_KEY] = binding;\n activeBindings.push(binding);\n };\n\n processEl(scanRoot);\n scanRoot.querySelectorAll(\"[lvt-scroll-away]\").forEach(processEl);\n}\n\nfunction removeBinding(binding: ScrollAwayBinding): void {\n const idx = activeBindings.indexOf(binding);\n if (idx !== -1) activeBindings.splice(idx, 1);\n}\n\nexport function teardownScrollAway(wrapper?: Element): void {\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const binding = activeBindings[i];\n if (wrapper && binding.trigger.isConnected && !wrapper.contains(binding.trigger)) continue;\n binding.target.removeEventListener(\"scroll\", binding.handler);\n binding.trigger.classList.remove(\"visible\");\n delete (binding.trigger as any)[GUARD_KEY];\n activeBindings.splice(i, 1);\n }\n}\n", "/**\n * Scroll-spy directive.\n *\n * Highlights navigation links as the user scrolls past corresponding section\n * targets. Pure client-side state \u2014 no server round-trip per scroll tick.\n *\n * <article lvt-spy=\"h1, h2, h3\"> // container mode: descendants\n * <h1 id=\"intro\">Intro</h1> matching the selector are\n * <h2 id=\"usage\">Usage</h2> the spy targets.\n * </article>\n *\n * <h2 id=\"other\" lvt-spy>Other</h2> // element mode: this element\n * IS the target. Empty value.\n *\n * <a href=\"#intro\" lvt-spy-link>Intro</a> // gets the `lvt-active` class\n * <a href=\"#usage\" lvt-spy-link>Usage</a> when its href=\"#<id>\" matches\n * the currently active target.\n *\n * Activation rule: walk targets in document order; the latest one whose top\n * edge has scrolled above a trigger line near the top of the viewport is\n * active. That way the first link stays active until the reader has actually\n * passed the first heading, and the active link advances in step with the\n * reader.\n *\n * Configuration via CSS custom property on the spy container:\n * --lvt-spy-margin: <length> (default: 25vh from the top)\n * The trigger line below the viewport top. A target counts as \"passed\"\n * once its top edge is at or above this line.\n *\n * Implementation: a rAF-throttled scroll listener on the nearest scrollable\n * ancestor (or window). Mirrors the lifecycle shape of `scroll-away.ts` so\n * the morphdom re-scan story is identical.\n */\n\nconst ACTIVE_CLASS = \"lvt-active\";\nconst BINDING_KEY = \"__lvt_spy\";\nconst LINK_HANDLER_KEY = \"__lvt_spy_link_handler\";\n\ninterface SpyBinding {\n container: Element;\n targets: Element[];\n // Pre-computed trigger-line distance from viewport top, in px.\n // Cached so the rAF-throttled scroll handler doesn't pay for a\n // synchronous style recalc (`getComputedStyle`) on every tick. The\n // window-resize listener re-computes this whenever the viewport\n // height changes (vh-based values shift).\n marginPx: number;\n // Pre-filtered list of [lvt-spy-link] elements whose href matches\n // one of `targets`. Cached so applyActive \u2014 called on every rAF\n // tick \u2014 doesn't do a fresh document.querySelectorAll each frame.\n // Refreshed on attach and on every processContainer pass\n // (setupSpy runs after each render), so morphdom-driven link\n // additions/removals are picked up at the next render cycle.\n links: Element[];\n scrollTarget: HTMLElement | Window;\n scrollHandler: () => void;\n resizeHandler: () => void;\n}\n\n// `activeBindings` is module-level \u2014 i.e. shared by every consumer\n// of this client bundle. In single-instance setups (the common case)\n// that's a non-issue. With multiple LiveTemplateClient mounts sharing\n// the same bundle module instance, the per-container `BINDING_KEY`\n// guard keeps each binding isolated and `teardownSpy(wrapper)`\n// touches only the wrapped subset, so the shared list is safe in\n// practice. Listed here so a future audit reading \"two clients\n// stomping each other\" finds the design intent.\nconst activeBindings: SpyBinding[] = [];\n\n// Tracks spy-target elements we've already warned about (missing id).\n// processContainer re-runs detach+attach whenever the target set\n// changes, so without per-element dedup the warning would spam the\n// console on every morphdom render that includes an id-less target.\n// WeakSet so cleared targets eventually get GC'd with the DOM nodes.\nconst warnedMissingId = new WeakSet<Element>();\n\nfunction pruneDisconnectedBindings(): void {\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const b = activeBindings[i];\n if (b.container.isConnected) continue;\n detach(b);\n activeBindings.splice(i, 1);\n }\n // Mirror teardownSpy's click-handler gate so a container removed\n // outside the framework lifecycle (direct DOM removal that never\n // calls teardownSpy) still ends with the document-level click\n // listener unhooked once the last binding is gone \u2014 instead of\n // leaving an orphaned no-op handler bound to the document.\n if (activeBindings.length === 0) {\n const handler = (document as any)[LINK_HANDLER_KEY];\n if (handler) {\n document.removeEventListener(\"click\", handler);\n delete (document as any)[LINK_HANDLER_KEY];\n }\n }\n}\n\n// detach fully removes a binding's effects from the page: event\n// listeners, the per-container guard, AND any lvt-active classes the\n// binding had applied to its links. Folding the class-clear into detach\n// means every caller (processContainer's re-attach, prune of\n// disconnected containers, teardownSpy) gets the cleanup for free \u2014\n// without it, a stale lvt-active leaked anywhere detach was called but\n// teardownSpy's surviving-id sweep wasn't (e.g. on every morphdom\n// update that removed a target).\nfunction detach(b: SpyBinding): void {\n applyActive(b, null);\n b.scrollTarget.removeEventListener(\n \"scroll\",\n b.scrollHandler as EventListener,\n );\n window.removeEventListener(\"resize\", b.resizeHandler as EventListener);\n delete (b.container as any)[BINDING_KEY];\n}\n\nfunction readMarginPx(container: Element): number {\n const raw = getComputedStyle(container).getPropertyValue(\"--lvt-spy-margin\").trim();\n const fallback = Math.round(window.innerHeight * 0.25);\n if (!raw) return fallback;\n const n = parseFloat(raw);\n if (isNaN(n)) return fallback;\n // Supported units: bare px (`200` or `200px`) and vh (`25vh`). Other\n // CSS units (rem, em, %, etc.) come through `getComputedStyle` as raw\n // strings, NOT resolved \u2014 `parseFloat(\"2rem\")` returns 2 and we'd\n // silently treat it as 2 px, which is wildly off. Reject explicitly\n // and warn so the author fixes the declaration.\n if (raw.endsWith(\"vh\")) return Math.round((n / 100) * window.innerHeight);\n // Accept \"200\" or \"200.0\" as unitless px (parseFloat strips zeros so\n // `raw === String(n)` would miss the latter). The regex pins the\n // entire string to a signed number, matching CSS's <number> grammar.\n if (raw.endsWith(\"px\") || /^-?\\d+(\\.\\d+)?$/.test(raw)) return n;\n console.warn(\n `lvt-spy: unsupported --lvt-spy-margin unit ${JSON.stringify(raw)}; supported units are vh and px (or unitless). Falling back to 25vh.`\n );\n return fallback;\n}\n\nfunction collectTargets(container: Element): Element[] {\n const selector = container.getAttribute(\"lvt-spy\");\n if (selector && selector.trim() !== \"\") {\n // Guard against typos in author-supplied selectors. An invalid\n // selector (e.g. `lvt-spy=\"h1, h2,\"` \u2014 trailing comma) makes\n // querySelectorAll throw SyntaxError, which would propagate out of\n // setupSpy and abort directive initialization for the whole scan\n // root. Warn and treat as empty so the rest of the page still works.\n try {\n return Array.from(container.querySelectorAll(selector));\n } catch (e) {\n console.warn(`lvt-spy: invalid selector ${JSON.stringify(selector)}:`, e);\n return [];\n }\n }\n return [container];\n}\n\n// applyActive updates lvt-active state for the links that belong to a\n// single binding. Ownership is decided by href matching one of the\n// binding's target ids \u2014 links pointing elsewhere (e.g. to a\n// neighbouring spy container's targets) are left untouched. This is\n// what lets multiple LiveTemplateClient mounts coexist on one page\n// without their scroll-spy state stomping each other.\n//\n// Hot path: called from the rAF-throttled scroll handler. Iterates the\n// pre-cached `binding.links` rather than re-querying the DOM each\n// frame; the cache is refreshed by refreshLinks() at attach time and\n// on every processContainer pass.\nfunction applyActive(binding: SpyBinding, activeId: string | null): void {\n for (const link of binding.links) {\n const href = link.getAttribute(\"href\") || \"\";\n const id = href.startsWith(\"#\") ? href.slice(1) : \"\";\n if (activeId !== null && id === activeId) {\n link.classList.add(ACTIVE_CLASS);\n } else {\n link.classList.remove(ACTIVE_CLASS);\n }\n }\n}\n\n// refreshLinks rebuilds binding.links to the current set of\n// [lvt-spy-link] elements in the document whose href points to one of\n// the binding's target ids. Called once per render cycle (cheap),\n// never per scroll tick (would defeat the cache).\nfunction refreshLinks(binding: SpyBinding): void {\n const ownIds = new Set<string>();\n for (const t of binding.targets) {\n if (t.id) ownIds.add(t.id);\n }\n const matched: Element[] = [];\n document.querySelectorAll(\"[lvt-spy-link]\").forEach((link) => {\n const href = link.getAttribute(\"href\") || \"\";\n const id = href.startsWith(\"#\") ? href.slice(1) : \"\";\n if (ownIds.has(id)) matched.push(link);\n });\n binding.links = matched;\n}\n\n// findBindingForId locates the binding that owns the given id, i.e.\n// has a target element whose id matches. Used by the optimistic click\n// handler so a click on a link only updates its own binding's scope \u2014\n// other bindings' active links stay put.\nfunction findBindingForId(id: string): SpyBinding | null {\n for (const b of activeBindings) {\n for (const t of b.targets) {\n if (t.id === id) return b;\n }\n }\n return null;\n}\n\nfunction pickActive(targets: Element[], marginPx: number): string | null {\n let activeId: string | null = null;\n // Walk every target. We deliberately do NOT bail early on the first\n // one below the trigger line \u2014 that optimisation assumes visual\n // top-to-bottom order (which holds for typical document flow) but\n // silently produces the wrong active link when targets are reordered\n // via CSS (sticky headers with negative top, transform: translateY,\n // flex/grid order). The full walk is O(n) with n = number of TOC\n // entries \u2014 small in practice, dwarfed by the rAF tick itself.\n for (const t of targets) {\n if (!t.id) continue;\n const top = t.getBoundingClientRect().top;\n if (top <= marginPx) {\n activeId = t.id;\n }\n }\n return activeId;\n}\n\nfunction findScrollTarget(el: Element): HTMLElement | Window {\n // Walk up to the nearest ancestor whose computed overflow-y is auto or\n // scroll. If none, the document scrolls \u2014 return window. Covers both\n // the common whole-document case and apps where a flex child scrolls\n // independently (prereview's main.viewer).\n let cur: Element | null = el.parentElement;\n while (cur && cur !== document.documentElement) {\n const oy = getComputedStyle(cur).overflowY;\n // 'overlay' is a non-standard Chromium value that behaves like\n // 'auto' but draws the scrollbar over the content. Apps that opt\n // into it (a common iOS-feel scrollbar pattern) would otherwise\n // be walked past and end up scrolling the document \u2014 wrong root,\n // wrong trigger line.\n if (oy === \"auto\" || oy === \"scroll\" || oy === \"overlay\") return cur as HTMLElement;\n cur = cur.parentElement;\n }\n return window;\n}\n\nfunction sameTargets(a: Element[], b: Element[]): boolean {\n if (a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) {\n if (a[i] !== b[i]) return false;\n }\n return true;\n}\n\nfunction attach(container: Element, preCollected?: Element[]): void {\n // Accept an optional pre-collected target list so processContainer's\n // re-attach path doesn't pay for collectTargets twice (once for the\n // diff check, once inside this function). Low-volume code path\n // (per-render, not per-tick) but easy to dedupe.\n const targets = preCollected ?? collectTargets(container);\n if (targets.length === 0) return;\n\n // Surface authoring mistakes once-per-element at attach time rather\n // than letting them silently produce a TOC entry that never lights\n // up. A target matched by the spy selector but missing `id` cannot\n // be the destination of `<a href=\"#...\">`, so no link can ever\n // activate for it. The WeakSet dedup is what keeps re-attach (which\n // fires on every morphdom render that adds/removes a heading) from\n // spamming the console \u2014 once we've warned about a given element,\n // we don't warn about it again, but a NEW id-less target on a later\n // render still gets surfaced.\n const missingId = targets.filter((t) => !t.id && !warnedMissingId.has(t));\n if (missingId.length > 0) {\n console.warn(\n `lvt-spy: ${missingId.length} target(s) without an id attribute; they cannot be linked from [lvt-spy-link]. Add id=\"...\" or drop them from the selector. First offender:`,\n missingId[0],\n );\n for (const t of missingId) warnedMissingId.add(t);\n }\n\n const binding: SpyBinding = {\n container,\n targets,\n marginPx: readMarginPx(container),\n links: [],\n scrollTarget: findScrollTarget(container),\n // scrollHandler/resizeHandler are populated below \u2014 declared here so\n // the closures can reference `binding` for the cached margin lookup.\n scrollHandler: () => {},\n resizeHandler: () => {},\n };\n refreshLinks(binding);\n\n let ticking = false;\n binding.scrollHandler = () => {\n if (ticking) return;\n ticking = true;\n requestAnimationFrame(() => {\n ticking = false;\n applyActive(binding, pickActive(binding.targets, binding.marginPx));\n });\n };\n binding.resizeHandler = () => {\n // The trigger line is vh-relative (or px), so on viewport resize\n // we need to recompute. Refresh active immediately because resize\n // doesn't fire `scroll`, so the rAF path won't otherwise reconcile.\n binding.marginPx = readMarginPx(binding.container);\n applyActive(binding, pickActive(binding.targets, binding.marginPx));\n };\n\n binding.scrollTarget.addEventListener(\"scroll\", binding.scrollHandler as EventListener, { passive: true });\n window.addEventListener(\"resize\", binding.resizeHandler as EventListener, { passive: true });\n\n (container as any)[BINDING_KEY] = binding;\n activeBindings.push(binding);\n\n // Initial synchronous pick so the right link is highlighted on first\n // paint, before any scroll event fires.\n applyActive(binding, pickActive(binding.targets, binding.marginPx));\n}\n\nfunction processContainer(container: Element): void {\n const existing = (container as any)[BINDING_KEY] as SpyBinding | undefined;\n if (existing) {\n const fresh = collectTargets(container);\n if (sameTargets(existing.targets, fresh)) {\n // No structural change \u2014 just re-pick in case scroll position\n // shifted via a non-scroll mechanism (history restore, etc.).\n // Also refresh the cached margin in case CSS variables changed\n // between renders.\n existing.marginPx = readMarginPx(container);\n // Targets stayed put but the link soup may have shifted (morphdom\n // could have added a TOC entry without touching headings). Refresh\n // the cached link set every render so the hot-path applyActive\n // never goes stale.\n refreshLinks(existing);\n applyActive(existing, pickActive(existing.targets, existing.marginPx));\n return;\n }\n detach(existing);\n const idx = activeBindings.indexOf(existing);\n if (idx !== -1) activeBindings.splice(idx, 1);\n // We already collected `fresh` above for the diff check \u2014 hand it\n // to attach() so it doesn't re-walk the DOM.\n attach(container, fresh);\n return;\n }\n attach(container);\n}\n\nfunction installLinkClickHandler(): void {\n if ((document as any)[LINK_HANDLER_KEY]) return;\n // Optimistic activation: clicking a link instantly applies lvt-active\n // to it. The next scroll-driven pick reconciles. Without this, clicking\n // the *last* heading (which may never become topmost-visible if the\n // doc ends shortly after it) would leave some earlier link highlighted\n // even though the user just asked to be at the last heading.\n const handler = (e: Event) => {\n const link = (e.target as Element | null)?.closest(\"[lvt-spy-link]\");\n if (!link) return;\n const href = link.getAttribute(\"href\") || \"\";\n const id = href.startsWith(\"#\") ? href.slice(1) : \"\";\n if (!id) return;\n // Route the optimistic flip to the binding that actually owns this\n // id. Otherwise clicking a link in TOC A would clear TOC B's\n // active highlight in a multi-instance layout.\n const owner = findBindingForId(id);\n if (owner) applyActive(owner, id);\n };\n // passive: true \u2014 we never preventDefault on the click (the native\n // <a href=\"#\u2026\"> scroll is what we want), so promising passive lets\n // the browser dispatch without parking the main thread waiting for\n // a possible cancel.\n document.addEventListener(\"click\", handler, { passive: true });\n (document as any)[LINK_HANDLER_KEY] = handler;\n}\n\nexport function setupSpy(scanRoot: Element): void {\n pruneDisconnectedBindings();\n installLinkClickHandler();\n\n if (scanRoot.hasAttribute(\"lvt-spy\")) {\n processContainer(scanRoot);\n }\n scanRoot.querySelectorAll(\"[lvt-spy]\").forEach(processContainer);\n}\n\nexport function teardownSpy(wrapper?: Element): void {\n // detach() now clears each binding's lvt-active classes as part of\n // its contract, so there's no separate global sweep here \u2014 surviving\n // bindings keep their highlights, and removed bindings drop theirs.\n for (let i = activeBindings.length - 1; i >= 0; i--) {\n const b = activeBindings[i];\n if (wrapper && b.container.isConnected && !wrapper.contains(b.container)) {\n continue;\n }\n detach(b);\n activeBindings.splice(i, 1);\n }\n // Detach the document-level optimistic-click handler when there are\n // no more live spy bindings. Gating on `activeBindings.length === 0`\n // (instead of `!wrapper`) covers the common case where the framework\n // always passes a wrapper to teardown: without this gate the click\n // handler outlives every binding, and any subsequent [lvt-spy-link]\n // click silently re-applies lvt-active with no scroll reconciliation\n // to undo it.\n if (activeBindings.length === 0) {\n const handler = (document as any)[LINK_HANDLER_KEY];\n if (handler) {\n document.removeEventListener(\"click\", handler);\n delete (document as any)[LINK_HANDLER_KEY];\n }\n }\n}\n", "import type { TargetedRangeOp, TreeNode, UpdateResult } from \"../types\";\nimport type { Logger } from \"../utils/logger\";\n\n/**\n * Optional context for `applyUpdate` that opts into per-op targeted DOM\n * mutation. When `canApplyTargeted` returns true for a top-level diff-op\n * key, applyUpdate mutates treeState in place (no deepClone) and emits\n * a `TargetedRangeOp` in the result. The caller is then responsible for\n * applying the op directly to the live DOM (typically via RangeDomApplier).\n *\n * The corresponding range subtree is replaced in `result.html` with a\n * `<!--lvt-targeted-skip:${rangePath}-->` placeholder so that morphdom\n * can be told to short-circuit that subtree.\n */\nexport interface ApplyUpdateOptions {\n canApplyTargeted?: (rangeStructure: any, rangePath: string) => boolean;\n}\n\ninterface RangeStateEntry {\n items: any[];\n statics: any[];\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(\n update: TreeNode,\n opts?: ApplyUpdateOptions\n ): UpdateResult {\n let changed = false;\n const targetedOps: TargetedRangeOp[] = [];\n const skipPaths = new Set<string>();\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 const targetedEligible =\n opts?.canApplyTargeted?.(existing, key) === true;\n if (targetedEligible) {\n // Mutate in place \u2014 applyDifferentialOpsToRange uses splice/push/unshift\n // on existing.d, so no fresh array is created and treeState[key]\n // remains the same object reference (now with updated contents).\n this.applyDifferentialOpsToRange(existing, value, key);\n targetedOps.push({\n rangePath: key,\n ops: value,\n statics: existing.s,\n idKey: existing.m?.idKey,\n });\n skipPaths.add(key);\n } else {\n // Apply differential operations to a clone (defensive, fallback path)\n this.treeState[key] = deepClone(existing);\n this.applyDifferentialOpsToRange(this.treeState[key], value, key);\n }\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(\n this.treeState,\n \"\",\n skipPaths.size > 0 ? skipPaths : undefined\n );\n const result: UpdateResult = { html, changed };\n if (targetedOps.length > 0) {\n result.targetedOps = targetedOps;\n }\n return result;\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 /**\n * Re-render the current treeState as full HTML, with no targeted-apply\n * skip placeholders. Used by `LiveTemplateClient.updateDOM` as a fallback\n * when one or more targeted-apply DOM mutations failed: the treeState is\n * authoritative, so a full rebuild + morphdom pass restores consistency.\n */\n renderState(): string {\n return this.reconstructFromTree(this.treeState, \"\");\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 };\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 };\n }\n\n private reconstructFromTree(\n node: TreeNode,\n statePath: string,\n skipPaths?: Set<string>\n ): 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\n // Targeted-apply skip mechanism: emit a comment placeholder that\n // the client's updateDOM walks for, converting to the live\n // container's data-lvt-targeted-skip marker so morphdom can\n // short-circuit the subtree.\n if (skipPaths && skipPaths.has(newStatePath)) {\n html += `<!--lvt-targeted-skip:${newStatePath}-->`;\n continue;\n }\n\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 };\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 } = 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 if (statics && Array.isArray(statics)) {\n return dynamics\n .map((item: any, itemIdx: number) =>\n this.renderRangeItem(item, itemIdx, statics, statePath)\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 /**\n * Renders a single range item to HTML by interleaving its dynamic slots\n * with the range's statics.\n *\n * Used by `renderRangeStructure` and `renderItemsWithStatics` for full\n * range rendering, and by `range-dom-applier` to render a single new\n * or updated item for targeted DOM mutations.\n */\n renderRangeItem(\n item: any,\n itemIdx: number,\n statics: string[],\n statePath?: string\n ): string {\n const itemStatics = statics;\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\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 };\n\n this.treeState[statePath] = {\n d: currentItems,\n s: rangeData.statics,\n };\n\n const rangeStructure = this.getCurrentRangeStructure(statePath);\n if (rangeStructure && rangeStructure.s) {\n return this.renderItemsWithStatics(\n currentItems,\n rangeStructure.s,\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 };\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 statePath?: string\n ): string {\n const result = items\n .map((item: any, itemIdx: number) =>\n this.renderRangeItem(item, itemIdx, statics, statePath)\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 morphdom from \"morphdom\";\nimport type { Logger } from \"../utils/logger\";\nimport type { TargetedRangeOp } from \"../types\";\n\nconst KEY_ATTRIBUTES = [\"data-key\", \"data-lvt-key\"] as const;\n// Pre-compile attribute-presence regexes once at module load \u2014 these were\n// previously rebuilt on every staticsContainKeyAttribute call (per static\n// segment, per attribute) which showed up in profiling on initial render.\nconst KEY_ATTR_REGEXES: RegExp[] = KEY_ATTRIBUTES.map(\n (attr) => new RegExp(`(?:^|[\\\\s<])${attr}\\\\s*=`)\n);\nexport const TARGETED_APPLIED_ATTR = \"data-lvt-targeted-applied\";\nexport const TARGETED_SKIP_ATTR = \"data-lvt-targeted-skip\";\n\ntype RenderItemFn = (\n item: any,\n itemIdx: number,\n statics: string[],\n statePath?: string\n) => string;\n\ntype LifecycleHookFn = (el: Element, hookName: string) => void;\ntype NodeAddedFn = (el: Element) => void;\ntype ItemLookupFn = (rangePath: string, key: string) => any;\n\nexport interface RangeDomApplierContext {\n logger: Logger;\n renderItem: RenderItemFn;\n executeLifecycleHook: LifecycleHookFn;\n /**\n * Look up the current item state for the given range path + key. Used by\n * the `u` op to render the FULL post-merge item (treeState is mutated in\n * place by `applyDifferentialOpsToRange` before the applier runs).\n *\n * Required: an applier without an `itemLookup` would silently no-op every\n * `u` op, leaving the live DOM stale while morphdom's skip marker\n * prevents the fallback diff from running. The constructor enforces this.\n */\n itemLookup: ItemLookupFn;\n /**\n * Notification that the applier inserted a new element into the live DOM\n * (i/a/p ops). Lets the caller track per-render DOM additions so it can\n * decide whether the post-render directive scans need to walk the wrapper.\n */\n onNodeAdded?: NodeAddedFn;\n}\n\nexport interface CanApplyResult {\n ok: boolean;\n reason?: string;\n container?: Element;\n containerKey?: string;\n}\n\n/**\n * Applies range diff ops directly to the live DOM, bypassing full HTML\n * reconstruction + morphdom diff. Designed to handle the common case where\n * a 10k-row range receives a single-row mutation; the targeted path turns\n * what would be a 5+ second morphdom walk into a sub-millisecond DOM op.\n *\n * The applier is opt-in per range: `canApplyTargeted` checks that the\n * range has data-key emission, no nested-range items, and a resolvable\n * container element. When any check fails, the caller falls back to the\n * existing applyUpdate \u2192 reconstructFromTree \u2192 morphdom path.\n */\nexport class RangeDomApplier {\n private containerCache = new Map<string, Element>();\n\n constructor(private readonly ctx: RangeDomApplierContext) {}\n\n invalidate(): void {\n this.containerCache.clear();\n }\n\n invalidatePath(rangePath: string): void {\n this.containerCache.delete(rangePath);\n }\n\n /**\n * Locate the live container element for a range path. The container is\n * the parent element of items rendered with data-key. Cached per path;\n * cache invalidated automatically when a cached element becomes detached.\n *\n * Resolution order:\n * 1. Cached container (if still connected to the wrapper).\n * 2. `wrapper.querySelector('[data-key=\"anyKnownItemKey\"]').parentElement`.\n *\n * The original implementation also fell back to an unscoped\n * `wrapper.querySelector('[data-key]')` walk, but that could return a\n * container belonging to a *different* keyed range when the wrapper has\n * more than one \u2014 silently mutating the wrong DOM subtree on subsequent\n * ops. We now prefer to fail closed (return null \u2192 caller falls back to\n * full rebuild) over mutating an unrelated container.\n */\n findContainer(\n wrapper: Element,\n rangePath: string,\n anyKnownItemKey?: string\n ): Element | null {\n const cached = this.containerCache.get(rangePath);\n if (cached && cached.isConnected && wrapper.contains(cached)) {\n return cached;\n }\n if (cached) {\n this.containerCache.delete(rangePath);\n }\n\n if (anyKnownItemKey === undefined) {\n return null;\n }\n const sample = this.findItemByKey(wrapper, anyKnownItemKey);\n if (!sample || !sample.parentElement) {\n return null;\n }\n\n const container = sample.parentElement;\n this.containerCache.set(rangePath, container);\n return container;\n }\n\n /**\n * Decide whether a range update can take the targeted-apply path.\n * Returns the resolved container in the success case so the caller\n * can pass it to `apply` without re-resolving.\n */\n canApplyTargeted(\n wrapper: Element,\n rangeStructure: any,\n rangePath: string\n ): CanApplyResult {\n if (!rangeStructure || typeof rangeStructure !== \"object\") {\n return { ok: false, reason: \"no range structure\" };\n }\n if (!Array.isArray(rangeStructure.s) || rangeStructure.s.length === 0) {\n return { ok: false, reason: \"no statics\" };\n }\n\n const allStatics: string[][] = [rangeStructure.s];\n if (rangeStructure.sm && typeof rangeStructure.sm === \"object\") {\n for (const sm of Object.values(rangeStructure.sm)) {\n if (Array.isArray(sm)) {\n allStatics.push(sm as string[]);\n }\n }\n }\n\n const hasKeyInStatics = allStatics.some((arr) =>\n this.staticsContainKeyAttribute(arr)\n );\n if (!hasKeyInStatics) {\n return { ok: false, reason: \"no data-key attribute in statics\" };\n }\n\n const items = rangeStructure.d;\n if (Array.isArray(items)) {\n for (const item of items) {\n if (this.itemHasNestedRange(item)) {\n return { ok: false, reason: \"nested-range item\" };\n }\n }\n }\n\n const sampleKey = this.extractItemKey(items?.[0], rangeStructure);\n const container = this.findContainer(wrapper, rangePath, sampleKey);\n if (!container) {\n return { ok: false, reason: \"container not found in DOM\" };\n }\n\n // Walk up from container through wrapper (inclusive) \u2014 if any element\n // on the path is lvt-ignore'd, the targeted-apply path would mutate\n // DOM inside an ignored subtree while morphdom would have skipped it,\n // violating the lvt-ignore contract.\n let cur: Element | null = container;\n while (cur) {\n if (cur.hasAttribute(\"lvt-ignore\")) {\n return { ok: false, reason: \"lvt-ignore ancestor\" };\n }\n if (cur === wrapper) break;\n cur = cur.parentElement;\n }\n\n return { ok: true, container, containerKey: sampleKey };\n }\n\n /**\n * Apply a single targeted op to the live DOM. Returns the affected\n * container element so the caller can mark it for the morphdom skip\n * mechanism. Returns null if the op could not be applied (caller\n * should fall back to full-rebuild for the next render).\n */\n apply(\n wrapper: Element,\n targetedOp: TargetedRangeOp,\n morphdomOptions?: any\n ): Element | null {\n const { rangePath, ops, statics } = targetedOp;\n const sampleKey = this.firstKnownKey(ops);\n const container = this.findContainer(wrapper, rangePath, sampleKey);\n if (!container) {\n this.ctx.logger.debug(\n `[RangeDomApplier] container not found for range ${rangePath}; cannot apply`\n );\n return null;\n }\n\n let allOpsSucceeded = true;\n for (const op of ops) {\n if (!Array.isArray(op) || op.length < 1) continue;\n const opType = op[0];\n try {\n let opOK = true;\n switch (opType) {\n case \"r\":\n opOK = this.applyRemove(container, op[1] as string);\n break;\n case \"u\":\n opOK = this.applyUpdateRow(\n container,\n op[1] as string,\n statics,\n rangePath,\n morphdomOptions\n );\n break;\n case \"i\":\n opOK = this.applyInsertAfter(\n container,\n op[1] as string,\n op[2],\n statics,\n rangePath\n );\n break;\n case \"a\":\n opOK = this.applyAppend(\n container,\n op[1],\n statics,\n rangePath\n );\n break;\n case \"p\":\n opOK = this.applyPrepend(\n container,\n op[1],\n statics,\n rangePath\n );\n break;\n case \"o\":\n opOK = this.applyReorder(container, op[1] as string[]);\n break;\n default:\n // Forward-compat: an unrecognised op type means we can't\n // reason about the DOM mutation. Treat as failure so the\n // caller falls back to a full morphdom rebuild from\n // treeState (which the server-emitted unknown op type\n // presumably already mutated correctly).\n this.ctx.logger.warn(\n `[RangeDomApplier] unknown op type ${opType}; falling back`\n );\n opOK = false;\n }\n if (!opOK) {\n allOpsSucceeded = false;\n }\n } catch (err) {\n this.ctx.logger.error(\n `[RangeDomApplier] op ${opType} failed for range ${rangePath}`,\n err\n );\n return null;\n }\n }\n\n // If any per-op method silently no-op'd because of stale state\n // (e.g. `u` for a row that's no longer in the DOM, `i` with a\n // missing anchor), we MUST signal failure so the caller falls back\n // to a full rebuild \u2014 otherwise the live DOM stays out of sync with\n // treeState and morphdom would skip the subtree (TARGETED_APPLIED\n // marker tells it to).\n //\n // Note: earlier ops in this batch that succeeded are NOT rolled back.\n // No rollback is needed because `treeState` was already mutated to\n // its complete post-op state by `applyDifferentialOpsToRange` BEFORE\n // this method ran. The caller's fallback path re-renders from\n // `treeState.renderState()` and runs morphdom over the full HTML \u2014\n // morphdom reconciles whatever partial DOM mutations we made toward\n // the authoritative end state.\n if (!allOpsSucceeded) {\n return null;\n }\n\n // Observability hook: increment a global counter so E2E tests can\n // assert the targeted-apply path was actually taken (vs silently\n // hitting the fallback). Opt-in: tests must initialize the property\n // first (e.g. `window.__lvtTargetedHits = 0`); production never sets\n // it so the increment is skipped and we don't pollute the window\n // object outside of test environments.\n if (\n typeof window !== \"undefined\" &&\n \"__lvtTargetedHits\" in (window as any)\n ) {\n (window as any).__lvtTargetedHits++;\n }\n return container;\n }\n\n cleanupMarkers(wrapper: Element): void {\n const applied = wrapper.querySelectorAll(`[${TARGETED_APPLIED_ATTR}]`);\n applied.forEach((el) => el.removeAttribute(TARGETED_APPLIED_ATTR));\n if (wrapper.hasAttribute(TARGETED_APPLIED_ATTR)) {\n wrapper.removeAttribute(TARGETED_APPLIED_ATTR);\n }\n const skip = wrapper.querySelectorAll(`[${TARGETED_SKIP_ATTR}]`);\n skip.forEach((el) => el.removeAttribute(TARGETED_SKIP_ATTR));\n if (wrapper.hasAttribute(TARGETED_SKIP_ATTR)) {\n wrapper.removeAttribute(TARGETED_SKIP_ATTR);\n }\n }\n\n // --- per-op implementations -----------------------------------------------\n //\n // Each per-op method returns `boolean`:\n // true \u2192 the live DOM is now consistent with the new treeState\n // false \u2192 silent no-op (e.g. row not found, item state unavailable);\n // the caller should invalidate the targeted-apply marker and\n // fall back to a full rebuild\n\n private applyRemove(container: Element, key: string): boolean {\n const row = this.findItemByKey(container, key);\n if (!row) {\n // r is idempotent: if the row is already gone, treeState's post-op\n // view (also without the row) matches the DOM. No fallback needed.\n this.ctx.logger.debug(\n `[RangeDomApplier] r: row with key ${key} not found (idempotent no-op)`\n );\n return true;\n }\n this.fireHookOnSubtree(row, \"lvt-destroyed\");\n row.remove();\n return true;\n }\n\n private applyUpdateRow(\n container: Element,\n key: string,\n statics: string[],\n rangePath: string,\n morphdomOptions?: any\n ): boolean {\n const row = this.findItemByKey(container, key);\n if (!row) {\n this.ctx.logger.debug(\n `[RangeDomApplier] u: row with key ${key} not found in DOM; falling back`\n );\n return false;\n }\n const itemIdx = this.indexOfChild(container, row);\n const item = this.lookupCurrentItem(rangePath, key);\n if (!item) {\n this.ctx.logger.debug(\n `[RangeDomApplier] u: item state for key ${key} not available; falling back`\n );\n return false;\n }\n const newHtml = this.ctx.renderItem(\n item,\n itemIdx,\n statics,\n rangePath\n );\n const newRow = this.parseSingleRow(newHtml);\n if (!newRow) {\n this.ctx.logger.warn(\n `[RangeDomApplier] u: failed to parse rendered row HTML; falling back`\n );\n return false;\n }\n if (morphdomOptions) {\n // Override childrenOnly: the main morphdom call uses childrenOnly:true\n // because it's diffing the wrapper's children. For a single-row morph\n // we MUST diff the row element itself too (its attributes \u2014 class,\n // style, aria, etc. \u2014 are produced by statics+dynamics and may have\n // changed). Reuse the same callbacks for behavioral consistency.\n morphdom(row, newRow, { ...morphdomOptions, childrenOnly: false });\n } else {\n // No morphdom options provided \u2014 fall back to wholesale replacement.\n // morphdom's onNodeAdded / onBeforeNodeDiscarded callbacks would\n // normally fire lvt-mounted/lvt-destroyed hooks for us; here we have\n // to fire them manually on both sides AND notify the host so its\n // nodesAddedThisRender counter sees the new subtree (otherwise the\n // post-render directive scans would skip wiring listeners on it).\n this.fireHookOnSubtree(row, \"lvt-destroyed\");\n row.replaceWith(newRow);\n this.ctx.onNodeAdded?.(newRow);\n this.fireHookOnSubtree(newRow, \"lvt-mounted\");\n }\n return true;\n }\n\n private applyInsertAfter(\n container: Element,\n afterKey: string,\n items: any | any[],\n statics: string[],\n rangePath: string\n ): boolean {\n const anchor = this.findItemByKey(container, afterKey);\n if (!anchor) {\n this.ctx.logger.debug(\n `[RangeDomApplier] i: anchor key ${afterKey} not found; falling back`\n );\n return false;\n }\n return this.renderItemsAtomic(\n items,\n statics,\n rangePath,\n this.indexOfChild(container, anchor) + 1,\n (frag) => container.insertBefore(frag, anchor.nextSibling)\n );\n }\n\n private applyAppend(\n container: Element,\n items: any | any[],\n statics: string[],\n rangePath: string\n ): boolean {\n return this.renderItemsAtomic(\n items,\n statics,\n rangePath,\n container.children.length,\n (frag) => container.appendChild(frag)\n );\n }\n\n private applyPrepend(\n container: Element,\n items: any | any[],\n statics: string[],\n rangePath: string\n ): boolean {\n return this.renderItemsAtomic(\n items,\n statics,\n rangePath,\n 0,\n (frag) => container.insertBefore(frag, container.firstChild)\n );\n }\n\n /**\n * Render N items into a scratch DocumentFragment, splicing them into the\n * live DOM only if ALL renders succeeded. On partial failure no DOM\n * mutation happens and the caller falls back to a full rebuild \u2014 this\n * avoids `lvt-mounted` firing on items that morphdom is then about to\n * re-add (which would double-fire the hook).\n */\n private renderItemsAtomic(\n items: any | any[],\n statics: string[],\n rangePath: string,\n baseIdx: number,\n splice: (frag: DocumentFragment) => void\n ): boolean {\n const list = Array.isArray(items) ? items : [items];\n const scratch = document.createDocumentFragment();\n const newRows: Element[] = [];\n for (let i = 0; i < list.length; i++) {\n const newRow = this.renderAndParse(\n list[i],\n baseIdx + i,\n statics,\n rangePath\n );\n if (!newRow) {\n return false;\n }\n scratch.appendChild(newRow);\n newRows.push(newRow);\n }\n splice(scratch);\n for (const row of newRows) {\n this.ctx.onNodeAdded?.(row);\n this.fireHookOnSubtree(row, \"lvt-mounted\");\n }\n return true;\n }\n\n /**\n * Reorder existing children to match `newKeyOrder`. Protocol assumption:\n * the server emits the *full* new key order (mirrors the assumption in\n * `applyDifferentialOpsToRange`'s \"o\" case in tree-renderer). When the\n * new order is shorter than the current child set, we treat the missing\n * keys as removals and fire `lvt-destroyed` on each dropped subtree\n * (so user teardown \u2014 timer cancellation, observer disconnect, etc. \u2014\n * still runs) plus log a warning surfacing the protocol mismatch.\n */\n private applyReorder(container: Element, newKeyOrder: string[]): boolean {\n if (!Array.isArray(newKeyOrder)) return false;\n const byKey = new Map<string, Element>();\n Array.from(container.children).forEach((child) => {\n for (const attr of KEY_ATTRIBUTES) {\n const k = child.getAttribute(attr);\n if (k !== null) {\n byKey.set(k, child);\n break;\n }\n }\n });\n\n const fragment = document.createDocumentFragment();\n const newKeySet = new Set(newKeyOrder);\n for (const key of newKeyOrder) {\n const el = byKey.get(key);\n if (el) {\n fragment.appendChild(el);\n }\n }\n\n // Fire lvt-destroyed on children that aren't in the new order. The\n // protocol normally sends the FULL key order, but if a partial reorder\n // ever lands here, user-defined teardown (timer cancellation, observer\n // disconnect, etc.) must still run.\n if (newKeySet.size < byKey.size) {\n this.ctx.logger.warn(\n `[RangeDomApplier] o: newKeyOrder (${newKeySet.size}) shorter than existing children (${byKey.size}); ${byKey.size - newKeySet.size} children will be dropped`\n );\n for (const [k, el] of byKey) {\n if (!newKeySet.has(k)) {\n this.fireHookOnSubtree(el, \"lvt-destroyed\");\n }\n }\n }\n\n container.replaceChildren(fragment);\n return true;\n }\n\n // --- helpers --------------------------------------------------------------\n\n private renderAndParse(\n item: any,\n itemIdx: number,\n statics: string[],\n rangePath: string\n ): Element | null {\n const html = this.ctx.renderItem(item, itemIdx, statics, rangePath);\n return this.parseSingleRow(html);\n }\n\n /**\n * Parse a string of HTML containing a single root element and return it.\n * Uses <template> so orphan table-cell content (`<tr>`, `<td>`, etc.)\n * is tolerated by the parser.\n */\n private parseSingleRow(html: string): Element | null {\n const template = document.createElement(\"template\");\n template.innerHTML = html.trim();\n const first = template.content.firstElementChild;\n return first ?? null;\n }\n\n private findItemByKey(scope: Element, key: string): Element | null {\n let escaped: string;\n if (typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\") {\n escaped = CSS.escape(key);\n } else {\n // CSS.escape polyfill is incomplete: only handles `\"` and `\\`.\n // Keys containing other CSS special chars ([], (), :, ., #, >, ~,\n // whitespace, etc.) would produce a malformed selector and miss the\n // match. Warn so it's visible in test logs rather than silently\n // returning null and looking like the row simply doesn't exist.\n if (/[\\[\\]():.#>~+*=^$|! \\t\\n\\r]/.test(key)) {\n this.ctx.logger.warn(\n `[RangeDomApplier] CSS.escape unavailable; key \"${key}\" contains characters that need escaping. Lookup may miss the row.`\n );\n }\n escaped = key.replace(/([\"\\\\])/g, \"\\\\$1\");\n }\n for (const attr of KEY_ATTRIBUTES) {\n const el = scope.querySelector(`[${attr}=\"${escaped}\"]`);\n if (el) return el;\n }\n return null;\n }\n\n private indexOfChild(container: Element, child: Element): number {\n let i = 0;\n let cur = container.firstElementChild;\n while (cur) {\n if (cur === child) return i;\n i++;\n cur = cur.nextElementSibling;\n }\n return -1;\n }\n\n private firstKnownKey(ops: any[]): string | undefined {\n for (const op of ops) {\n if (!Array.isArray(op) || op.length < 2) continue;\n const t = op[0];\n if (t === \"r\" || t === \"u\" || t === \"i\") {\n return typeof op[1] === \"string\" ? op[1] : undefined;\n }\n if (t === \"o\" && Array.isArray(op[1]) && op[1].length > 0) {\n return typeof op[1][0] === \"string\" ? op[1][0] : undefined;\n }\n // For a/p ops there's no DOM-resident key in op[1], but the new\n // items themselves carry `_k`. Sample the first to give\n // findContainer something to walk to (rare cold-cache case;\n // canApplyTargeted normally warms the cache via existing.d[0]\n // before apply runs, so in practice the cache hit covers this).\n if (t === \"a\" || t === \"p\") {\n const items = Array.isArray(op[1]) ? op[1] : [op[1]];\n for (const it of items) {\n if (it && typeof it === \"object\" && it._k !== undefined) {\n return String(it._k);\n }\n }\n }\n }\n return undefined;\n }\n\n private staticsContainKeyAttribute(statics: string[]): boolean {\n // Reduces false positives vs. plain `s.includes('data-key=')`:\n // - requires word boundary before the attr name (excludes longer\n // attribute names like `data-keystone=`, `my-data-key=`)\n // - requires `=` to follow optional whitespace (excludes\n // `data-key-something`)\n //\n // Known limitation: cannot distinguish a real attribute from\n // `data-key=` appearing inside a quoted attribute value (e.g.\n // `title='see data-key=foo'`). Such cases would still match. False\n // positives are safe \u2014 `findContainer` just fails to locate by key,\n // canApplyTargeted falls back to full rebuild \u2014 but they cost a\n // render of wasted work. Real-world templates with `data-key` in\n // attribute values are vanishingly rare.\n for (const s of statics) {\n if (typeof s !== \"string\") continue;\n for (const re of KEY_ATTR_REGEXES) {\n if (re.test(s)) {\n return true;\n }\n }\n }\n return false;\n }\n\n private itemHasNestedRange(item: any): boolean {\n if (!item || typeof item !== \"object\") return false;\n for (const [key, val] of Object.entries(item)) {\n if (key.startsWith(\"_\")) continue;\n if (val && typeof val === \"object\" && !Array.isArray(val)) {\n const v = val as any;\n if (Array.isArray(v.d) && Array.isArray(v.s)) return true;\n if (this.itemHasNestedRange(v)) return true;\n }\n }\n return false;\n }\n\n private extractItemKey(item: any, rangeStructure: any): string | undefined {\n if (!item || typeof item !== \"object\") return undefined;\n if (item._k !== undefined) return String(item._k);\n const idKey = rangeStructure?.m?.idKey;\n if (idKey && item[idKey] !== undefined) return String(item[idKey]);\n return undefined;\n }\n\n private lookupCurrentItem(rangePath: string, key: string): any {\n // O(N) over range.d via the context callback (linear scan in\n // livetemplate-client.ts). Bounded cost per `u` op: one walk per\n // updated row per render. At N=10k that's ~50\u00B5s in JS \u2014 acceptable.\n return this.ctx.itemLookup(rangePath, key);\n }\n\n private fireHookOnSubtree(root: Element, hookName: string): void {\n if (root.hasAttribute(hookName)) {\n this.ctx.executeLifecycleHook(root, hookName);\n }\n const descendants = root.querySelectorAll(`[${hookName}]`);\n descendants.forEach((el) =>\n this.ctx.executeLifecycleHook(el, hookName)\n );\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\";\nimport { hasNoInterceptOptOut } from \"../utils/legacy-attr\";\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 (hasNoInterceptOptOut(parentForm, \"lvt-form:no-intercept\", this.logger)) 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 === WebSocket.OPEN) {\n this.socket.send(data);\n }\n }\n\n disconnect(): void {\n this.manuallyClosed = true;\n this.clearReconnectTimer();\n if (this.socket) {\n if (this.socket.readyState !== WebSocket.CLOSED) {\n this.options.onClose?.(\n new CloseEvent(\"close\", {\n code: 1000,\n reason: \"\",\n wasClean: this.socket.readyState === WebSocket.OPEN,\n }),\n );\n }\n this.socket.onopen = null;\n this.socket.onmessage = null;\n this.socket.onclose = null;\n this.socket.onerror = null;\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 pendingInputs: Map<string, HTMLInputElement> = new Map(); // uploadName -> triggering input\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 postMultipartUpload?: (\n formData: FormData,\n signal?: AbortSignal\n ) => Promise<void>;\n private isConnected?: () => boolean;\n private postUploadStart?: (\n message: UploadStartMessage,\n signal?: AbortSignal\n ) => Promise<UploadStartResponse>;\n private pendingHandshakes: Set<AbortController> = new Set();\n private pendingUploads: Set<AbortController> = new Set(); // in-flight proxied fetches\n private previewUrls: Map<string, string> = new Map(); // uploadName -> object URL\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 this.postMultipartUpload = options.postMultipartUpload;\n this.isConnected = options.isConnected;\n this.postUploadStart = options.postUploadStart;\n\n // Register default uploaders\n this.uploaders.set(\"s3\", new S3Uploader());\n }\n\n /**\n * Dispatch a valid upload entry to the transport its mode requires. The server\n * declares the mode per-entry; existing entries that only carry `external`\n * (no mode) fall back to the Direct path for backward compatibility.\n */\n private dispatchUpload(entry: UploadEntry): void {\n switch (entry.mode) {\n case \"preview\":\n this.uploadPreview(entry);\n return;\n case \"proxied\":\n void this.uploadProxied(entry);\n return;\n case \"direct\":\n if (entry.external) {\n void this.uploadExternal(entry, entry.external);\n } else {\n // Direct requires presign metadata; surface the misconfig instead of\n // silently abandoning the entry.\n const msg = \"direct upload mode requires presigned upload metadata\";\n entry.error = msg;\n if (this.onError) this.onError(entry, msg);\n this.cleanupEntries(entry.uploadName);\n }\n return;\n case \"volume\":\n default:\n // entry.mode is normalized to a concrete mode at ingest (see\n // handleUploadStartResponse), so the default is just the Volume path.\n void this.uploadChunked(entry);\n }\n }\n\n /**\n * Mark an upload finished and run the shared post-completion steps: fire the\n * onComplete callback, clear the file input, and schedule entry cleanup.\n * Callers that talk to the server (chunked/external) send their own\n * upload_complete message first.\n */\n private finishUpload(entry: UploadEntry): void {\n entry.done = true;\n entry.progress = 100;\n // Emit a final 100% tick so every completed path (proxied included) sends it\n // \u2014 a progress bar waiting on 100% won't hang. Idempotent for chunked/external\n // which already reach 100% during transfer.\n if (this.onProgress) {\n this.onProgress(entry);\n }\n if (this.onComplete) {\n this.onComplete(entry.uploadName, [entry]);\n }\n this.clearFileInput(entry.uploadName);\n this.cleanupEntries(entry.uploadName);\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. Pass the triggering\n // input so a Proxied upload can serialize its enclosing form's fields.\n this.startUpload(uploadName, Array.from(files), input);\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(\n uploadName: string,\n files: File[],\n sourceInput?: HTMLInputElement\n ): Promise<void> {\n // Store files temporarily for when server response arrives\n this.pendingFiles.set(uploadName, files);\n if (sourceInput) {\n this.pendingInputs.set(uploadName, sourceInput);\n } else {\n this.pendingInputs.delete(uploadName);\n }\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 const startMessage: UploadStartMessage = {\n action: \"upload_start\",\n upload_name: uploadName,\n files: fileMetadata,\n };\n\n // Over an open WebSocket the server replies with a separate\n // UploadStartResponse message (routed to handleUploadStartResponse). With\n // the socket down, post the handshake over HTTP and handle the response\n // inline so mode dispatch (and Direct presign) still work.\n const connected = this.isConnected ? this.isConnected() : true;\n if (!connected) {\n if (!this.postUploadStart) {\n this.failStart(\n uploadName,\n files,\n \"upload unavailable: WebSocket is closed and no HTTP fallback is configured\"\n );\n return;\n }\n const controller = new AbortController();\n this.pendingHandshakes.add(controller);\n try {\n const response = await this.postUploadStart(\n startMessage,\n controller.signal\n );\n // Skip if teardown aborted us mid-flight, so we don't create a preview\n // object URL after revokePreviews already ran.\n if (!controller.signal.aborted) {\n await this.handleUploadStartResponse(response);\n }\n } catch (error) {\n this.failStart(\n uploadName,\n files,\n error instanceof Error ? error.message : String(error)\n );\n } finally {\n this.pendingHandshakes.delete(controller);\n }\n return;\n }\n\n this.sendMessage(startMessage);\n }\n\n // failStart reports a handshake failure on every selected file and clears the\n // pending set, so a startUpload that can't reach the server isn't silent.\n private failStart(uploadName: string, files: File[], message: string): void {\n this.pendingFiles.delete(uploadName);\n this.pendingInputs.delete(uploadName);\n const onError = this.onError;\n if (!onError) return;\n // Distinct synthetic ids per file (no server entry exists yet) so a consumer\n // keying per-file error state doesn't collapse a multi-file failure.\n files.forEach((file, i) => {\n onError(\n {\n id: `pending-${uploadName}-${i}`,\n file,\n uploadName,\n progress: 0,\n bytesUploaded: 0,\n valid: false,\n done: false,\n },\n message\n );\n });\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 // The input that triggered this upload (if any), so a Proxied entry can\n // serialize its enclosing form's fields. Consumed once, like pendingFiles.\n const sourceInput = this.pendingInputs.get(upload_name);\n this.pendingInputs.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 // Normalize legacy responses (no mode) at the ingest boundary: a\n // presigned entry is Direct, otherwise server-side Volume. Downstream\n // dispatch then only ever sees a concrete mode.\n mode: info.mode ?? (info.external ? \"direct\" : \"volume\"),\n external: info.external,\n sourceInput,\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; otherwise the entry\n // waits for an explicit form-submit trigger.\n if (info.auto_upload) {\n this.dispatchUpload(entry);\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 this.finishUpload(entry);\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 a Proxied file as a single multipart POST to the live URL. The server\n * streams the bytes straight to the app's OnUpload handler (zero local-disk\n * staging) and dispatches the upload_<name>_complete action. Independent of\n * the WebSocket, so it works with the socket disabled.\n */\n private async uploadProxied(entry: UploadEntry): Promise<void> {\n if (!this.postMultipartUpload) {\n const msg =\n \"Proxied upload unavailable: no multipart transport configured\";\n entry.error = msg;\n if (this.onError) this.onError(entry, msg);\n this.cleanupEntries(entry.uploadName);\n return;\n }\n\n entry.abortController = new AbortController();\n this.pendingUploads.add(entry.abortController);\n // A single fetch has no native upload progress, so emit a start event (0%)\n // and let finishUpload emit 100% \u2014 a progress bar won't sit frozen with no\n // signal at all.\n if (this.onProgress) this.onProgress(entry);\n try {\n const formData = new FormData();\n // Write value fields BEFORE the file part so the server resolves the\n // action regardless of multipart part ordering.\n formData.set(\"lvt-action\", `upload_${entry.uploadName}_complete`);\n // Then the triggering input's enclosing form fields (e.g. a record id), so\n // the server can associate the streamed bytes with a record in OnUpload.\n this.appendFormFields(formData, entry);\n formData.set(entry.uploadName, entry.file, entry.file.name);\n\n await this.postMultipartUpload(formData, entry.abortController.signal);\n this.finishUpload(entry);\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 this.cleanupEntries(entry.uploadName);\n } finally {\n if (entry.abortController) this.pendingUploads.delete(entry.abortController);\n // Release the DOM reference once the upload is done \u2014 the entry may linger\n // until the cleanup timer fires, and only uploadProxied ever reads it.\n entry.sourceInput = undefined;\n }\n }\n\n /**\n * appendFormFields serializes the value fields of the form enclosing the\n * upload's triggering input into formData, ahead of the file part \u2014 so a\n * Proxied OnUpload handler can read them (e.g. a record id) mid-stream. Uses\n * the browser's native form serialization (successful controls only), skipping\n * File values (file inputs are sent as the streaming part) and the reserved\n * lvt-action field. A no-op when the input is outside a form.\n *\n * A Proxied upload auto-fires on file selection (not an explicit submit), so it\n * POSTs the whole enclosing form. Password inputs are excluded by name so\n * credentials in a co-located form never ride along with an upload the user\n * didn't deliberately submit; keep other sensitive controls out of a form that\n * contains an auto-upload file input. (A broader opt-in model \u2014 only fields\n * marked to travel \u2014 is tracked upstream as a safer-by-default follow-up.)\n *\n * Values are read here, at upload time (after the upload_start round-trip), not\n * at file-selection time, so edits made between selecting the file and the\n * upload completing are reflected \u2014 the freshest form state wins.\n */\n private appendFormFields(formData: FormData, entry: UploadEntry): void {\n const form = entry.sourceInput?.form;\n if (!form) return;\n // Collect password field names to exclude. Casting to HTMLInputElement is safe\n // for the type check: only <input> has type=\"password\"; <select>/<textarea>\n // simply never match and are serialized normally by FormData below.\n const passwordFields = new Set<string>();\n for (const el of Array.from(form.elements)) {\n const input = el as HTMLInputElement;\n if (input.type === \"password\" && input.name) passwordFields.add(input.name);\n }\n for (const [name, value] of new FormData(form).entries()) {\n if (name === \"lvt-action\" || value instanceof File || passwordFields.has(name)) {\n continue;\n }\n formData.append(name, value);\n }\n }\n\n /**\n * Preview mode: keep the file on the device and show it locally via an object\n * URL. Nothing is uploaded \u2014 the server already has the file's metadata from\n * the upload_start handshake. The blob URL is re-applied after server\n * re-renders by hydratePreviews().\n */\n private uploadPreview(entry: UploadEntry): void {\n // Revoke any prior preview for this field to avoid leaking object URLs.\n const prev = this.previewUrls.get(entry.uploadName);\n if (prev) URL.revokeObjectURL(prev);\n\n const url = URL.createObjectURL(entry.file);\n this.previewUrls.set(entry.uploadName, url);\n this.applyPreview(entry.uploadName, url);\n\n // Share the completion path with every other mode (progress 100% tick,\n // onComplete, input clear, cleanup). The blob URL lives in previewUrls, not\n // the input, so it survives the clear.\n this.finishUpload(entry);\n }\n\n /** Point every preview placeholder for uploadName at the given object URL. */\n private applyPreview(uploadName: string, url: string): void {\n // Escape the field name so a name with a quote/bracket can't break or inject\n // into the selector. Prefer CSS.escape; without it, only proceed for a simple\n // identifier (the normal case) and otherwise skip rather than run a\n // possibly-broken selector (hydratePreviews still re-applies via attr reads).\n let safeName: string;\n if (typeof CSS !== \"undefined\" && CSS.escape) {\n safeName = CSS.escape(uploadName);\n } else if (/^[\\w-]+$/.test(uploadName)) {\n safeName = uploadName;\n } else {\n return;\n }\n const els = document.querySelectorAll<HTMLElement>(\n `[data-lvt-upload-preview=\"${safeName}\"]`\n );\n els.forEach((el) => {\n if (el instanceof HTMLImageElement) {\n if (el.src !== url) el.src = url;\n } else if (el.getAttribute(\"src\") !== url) {\n el.setAttribute(\"src\", url);\n }\n });\n }\n\n /**\n * Re-attach Preview-mode blob URLs after a DOM morph. The server re-renders\n * the {{.lvt.UploadPreview}} placeholder with an empty src, so this restores\n * the local object URL the visitor selected. Called by the client post-update.\n */\n hydratePreviews(root: Element): void {\n const apply = (el: Element) => {\n const name = el.getAttribute(\"data-lvt-upload-preview\");\n if (!name) return;\n const url = this.previewUrls.get(name);\n if (!url) return;\n if (el instanceof HTMLImageElement) {\n if (el.src !== url) el.src = url;\n } else if (el.getAttribute(\"src\") !== url) {\n el.setAttribute(\"src\", url);\n }\n };\n // querySelectorAll skips the root itself, so match it explicitly.\n if (root.matches(\"[data-lvt-upload-preview]\")) apply(root);\n root\n .querySelectorAll<HTMLElement>(\"[data-lvt-upload-preview]\")\n .forEach(apply);\n }\n\n /** Revoke all preview object URLs. Called on teardown to avoid leaks. */\n revokePreviews(): void {\n // Abort in-flight offline handshakes and proxied upload fetches first, so a\n // late resolution doesn't create a preview URL or apply a tree update after\n // the client has torn down.\n for (const controller of this.pendingHandshakes) {\n controller.abort();\n }\n this.pendingHandshakes.clear();\n for (const controller of this.pendingUploads) {\n controller.abort();\n }\n this.pendingUploads.clear();\n for (const url of this.previewUrls.values()) {\n URL.revokeObjectURL(url);\n }\n this.previewUrls.clear();\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 this.finishUpload(entry);\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 this.dispatchUpload(entry);\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": "odAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,wBAAAE,GAAA,gBAAAC,GAAA,uBAAAC,GAAA,oCAAAC,KCAA,IAAIC,GAAyB,GAE7B,SAASC,GAAWC,EAAUC,EAAQ,CAClC,IAAIC,EAAcD,EAAO,WACrBE,EACAC,EACAC,EACAC,EACAC,EAGJ,GAAI,EAAAN,EAAO,WAAaH,IAA0BE,EAAS,WAAaF,IAKxE,SAASU,EAAIN,EAAY,OAAS,EAAGM,GAAK,EAAGA,IACzCL,EAAOD,EAAYM,CAAC,EACpBJ,EAAWD,EAAK,KAChBE,EAAmBF,EAAK,aACxBG,EAAYH,EAAK,MAEbE,GACAD,EAAWD,EAAK,WAAaC,EAC7BG,EAAYP,EAAS,eAAeK,EAAkBD,CAAQ,EAE1DG,IAAcD,IACVH,EAAK,SAAW,UAChBC,EAAWD,EAAK,MAEpBH,EAAS,eAAeK,EAAkBD,EAAUE,CAAS,KAGjEC,EAAYP,EAAS,aAAaI,CAAQ,EAEtCG,IAAcD,GACdN,EAAS,aAAaI,EAAUE,CAAS,GASrD,QAFIG,EAAgBT,EAAS,WAEpB,EAAIS,EAAc,OAAS,EAAG,GAAK,EAAG,IAC3CN,EAAOM,EAAc,CAAC,EACtBL,EAAWD,EAAK,KAChBE,EAAmBF,EAAK,aAEpBE,GACAD,EAAWD,EAAK,WAAaC,EAExBH,EAAO,eAAeI,EAAkBD,CAAQ,GACjDJ,EAAS,kBAAkBK,EAAkBD,CAAQ,GAGpDH,EAAO,aAAaG,CAAQ,GAC7BJ,EAAS,gBAAgBI,CAAQ,EAIjD,CAEA,IAAIM,GACAC,GAAW,+BAEXC,EAAM,OAAO,UAAa,YAAc,OAAY,SACpDC,GAAuB,CAAC,CAACD,GAAO,YAAaA,EAAI,cAAc,UAAU,EACzEE,GAAoB,CAAC,CAACF,GAAOA,EAAI,aAAe,6BAA8BA,EAAI,YAAY,EAElG,SAASG,GAA2BC,EAAK,CACrC,IAAIC,EAAWL,EAAI,cAAc,UAAU,EAC3C,OAAAK,EAAS,UAAYD,EACdC,EAAS,QAAQ,WAAW,CAAC,CACxC,CAEA,SAASC,GAAwBF,EAAK,CAC7BN,KACDA,GAAQE,EAAI,YAAY,EACxBF,GAAM,WAAWE,EAAI,IAAI,GAG7B,IAAIO,EAAWT,GAAM,yBAAyBM,CAAG,EACjD,OAAOG,EAAS,WAAW,CAAC,CAChC,CAEA,SAASC,GAAuBJ,EAAK,CACjC,IAAIG,EAAWP,EAAI,cAAc,MAAM,EACvC,OAAAO,EAAS,UAAYH,EACdG,EAAS,WAAW,CAAC,CAChC,CAUA,SAASE,GAAUL,EAAK,CAEpB,OADAA,EAAMA,EAAI,KAAK,EACXH,GAIKE,GAA2BC,CAAG,EAC5BF,GACFI,GAAwBF,CAAG,EAG7BI,GAAuBJ,CAAG,CACrC,CAYA,SAASM,GAAiBC,EAAQC,EAAM,CACpC,IAAIC,EAAeF,EAAO,SACtBG,EAAaF,EAAK,SAClBG,EAAeC,EAEnB,OAAIH,IAAiBC,EACV,IAGXC,EAAgBF,EAAa,WAAW,CAAC,EACzCG,EAAcF,EAAW,WAAW,CAAC,EAMjCC,GAAiB,IAAMC,GAAe,GAC/BH,IAAiBC,EAAW,YAAY,EACxCE,GAAe,IAAMD,GAAiB,GACtCD,IAAeD,EAAa,YAAY,EAExC,GAEf,CAWA,SAASI,GAAgBC,EAAMC,EAAc,CACzC,MAAO,CAACA,GAAgBA,IAAiBpB,GACrCC,EAAI,cAAckB,CAAI,EACtBlB,EAAI,gBAAgBmB,EAAcD,CAAI,CAC9C,CAKA,SAASE,GAAaT,EAAQC,EAAM,CAEhC,QADIS,EAAWV,EAAO,WACfU,GAAU,CACb,IAAIC,EAAYD,EAAS,YACzBT,EAAK,YAAYS,CAAQ,EACzBA,EAAWC,CACf,CACA,OAAOV,CACX,CAEA,SAASW,GAAoBZ,EAAQC,EAAMM,EAAM,CACzCP,EAAOO,CAAI,IAAMN,EAAKM,CAAI,IAC1BP,EAAOO,CAAI,EAAIN,EAAKM,CAAI,EACpBP,EAAOO,CAAI,EACXP,EAAO,aAAaO,EAAM,EAAE,EAE5BP,EAAO,gBAAgBO,CAAI,EAGvC,CAEA,IAAIM,GAAoB,CACpB,OAAQ,SAASb,EAAQC,EAAM,CAC3B,IAAIa,EAAad,EAAO,WACxB,GAAIc,EAAY,CACZ,IAAIC,EAAaD,EAAW,SAAS,YAAY,EAC7CC,IAAe,aACfD,EAAaA,EAAW,WACxBC,EAAaD,GAAcA,EAAW,SAAS,YAAY,GAE3DC,IAAe,UAAY,CAACD,EAAW,aAAa,UAAU,IAC1Dd,EAAO,aAAa,UAAU,GAAK,CAACC,EAAK,WAIzCD,EAAO,aAAa,WAAY,UAAU,EAC1CA,EAAO,gBAAgB,UAAU,GAKrCc,EAAW,cAAgB,GAEnC,CACAF,GAAoBZ,EAAQC,EAAM,UAAU,CAChD,EAOA,MAAO,SAASD,EAAQC,EAAM,CAC1BW,GAAoBZ,EAAQC,EAAM,SAAS,EAC3CW,GAAoBZ,EAAQC,EAAM,UAAU,EAExCD,EAAO,QAAUC,EAAK,QACtBD,EAAO,MAAQC,EAAK,OAGnBA,EAAK,aAAa,OAAO,GAC1BD,EAAO,gBAAgB,OAAO,CAEtC,EAEA,SAAU,SAASA,EAAQC,EAAM,CAC7B,IAAIe,EAAWf,EAAK,MAChBD,EAAO,QAAUgB,IACjBhB,EAAO,MAAQgB,GAGnB,IAAIC,EAAajB,EAAO,WACxB,GAAIiB,EAAY,CAGZ,IAAIC,EAAWD,EAAW,UAE1B,GAAIC,GAAYF,GAAa,CAACA,GAAYE,GAAYlB,EAAO,YACzD,OAGJiB,EAAW,UAAYD,CAC3B,CACJ,EACA,OAAQ,SAAShB,EAAQC,EAAM,CAC3B,GAAI,CAACA,EAAK,aAAa,UAAU,EAAG,CAUhC,QATIkB,EAAgB,GAChBlC,EAAI,EAKJyB,EAAWV,EAAO,WAClBoB,EACAC,EACEX,GAEF,GADAW,EAAWX,EAAS,UAAYA,EAAS,SAAS,YAAY,EAC1DW,IAAa,WACbD,EAAWV,EACXA,EAAWU,EAAS,WAEfV,IACDA,EAAWU,EAAS,YACpBA,EAAW,UAEZ,CACH,GAAIC,IAAa,SAAU,CACvB,GAAIX,EAAS,aAAa,UAAU,EAAG,CACnCS,EAAgBlC,EAChB,KACJ,CACAA,GACJ,CACAyB,EAAWA,EAAS,YAChB,CAACA,GAAYU,IACbV,EAAWU,EAAS,YACpBA,EAAW,KAEnB,CAGJpB,EAAO,cAAgBmB,CAC3B,CACJ,CACJ,EAEIG,GAAe,EACfC,GAA2B,GAC3BC,GAAY,EACZC,GAAe,EAEnB,SAASC,GAAO,CAAC,CAEjB,SAASC,GAAkBC,EAAM,CAC/B,GAAIA,EACF,OAAQA,EAAK,cAAgBA,EAAK,aAAa,IAAI,GAAMA,EAAK,EAElE,CAEA,SAASC,GAAgBrD,EAAY,CAEnC,OAAO,SAAkBC,EAAUC,EAAQoD,EAAS,CAKlD,GAJKA,IACHA,EAAU,CAAC,GAGT,OAAOpD,GAAW,SACpB,GAAID,EAAS,WAAa,aAAeA,EAAS,WAAa,QAAUA,EAAS,WAAa,OAAQ,CACrG,IAAIsD,EAAarD,EACjBA,EAASW,EAAI,cAAc,MAAM,EACjCX,EAAO,UAAYqD,CACrB,MACErD,EAASoB,GAAUpB,CAAM,OAElBA,EAAO,WAAa6C,KAC7B7C,EAASA,EAAO,mBAGlB,IAAIsD,EAAaF,EAAQ,YAAcH,GACnCM,EAAoBH,EAAQ,mBAAqBJ,EACjDQ,EAAcJ,EAAQ,aAAeJ,EACrCS,EAAoBL,EAAQ,mBAAqBJ,EACjDU,EAAcN,EAAQ,aAAeJ,EACrCW,EAAwBP,EAAQ,uBAAyBJ,EACzDY,EAAkBR,EAAQ,iBAAmBJ,EAC7Ca,EAA4BT,EAAQ,2BAA6BJ,EACjEc,EAAmBV,EAAQ,kBAAoBJ,EAC/Ce,EAAWX,EAAQ,UAAY,SAASY,EAAQC,EAAM,CAAE,OAAOD,EAAO,YAAYC,CAAK,CAAG,EAC1FC,EAAed,EAAQ,eAAiB,GAGxCe,EAAkB,OAAO,OAAO,IAAI,EACpCC,EAAmB,CAAC,EAExB,SAASC,EAAgBC,EAAK,CAC5BF,EAAiB,KAAKE,CAAG,CAC3B,CAEA,SAASC,EAAwBrB,EAAMsB,EAAgB,CACrD,GAAItB,EAAK,WAAaN,GAEpB,QADIZ,EAAWkB,EAAK,WACblB,GAAU,CAEf,IAAIsC,EAAM,OAENE,IAAmBF,EAAMhB,EAAWtB,CAAQ,GAG9CqC,EAAgBC,CAAG,GAKnBV,EAAgB5B,CAAQ,EACpBA,EAAS,YACXuC,EAAwBvC,EAAUwC,CAAc,GAIpDxC,EAAWA,EAAS,WACtB,CAEJ,CAUA,SAASyC,EAAWvB,EAAMd,EAAYoC,EAAgB,CAChDb,EAAsBT,CAAI,IAAM,KAIhCd,GACFA,EAAW,YAAYc,CAAI,EAG7BU,EAAgBV,CAAI,EACpBqB,EAAwBrB,EAAMsB,CAAc,EAC9C,CA8BA,SAASE,EAAUxB,EAAM,CACvB,GAAIA,EAAK,WAAaN,IAAgBM,EAAK,WAAaL,GAEtD,QADIb,EAAWkB,EAAK,WACblB,GAAU,CACf,IAAIsC,EAAMhB,EAAWtB,CAAQ,EACzBsC,IACFH,EAAgBG,CAAG,EAAItC,GAIzB0C,EAAU1C,CAAQ,EAElBA,EAAWA,EAAS,WACtB,CAEJ,CAEA0C,EAAU3E,CAAQ,EAElB,SAAS4E,EAAgBC,EAAI,CAC3BpB,EAAYoB,CAAE,EAGd,QADI5C,EAAW4C,EAAG,WACX5C,GAAU,CACf,IAAI6C,EAAc7C,EAAS,YAEvBsC,EAAMhB,EAAWtB,CAAQ,EAC7B,GAAIsC,EAAK,CACP,IAAIQ,EAAkBX,EAAgBG,CAAG,EAGrCQ,GAAmBzD,GAAiBW,EAAU8C,CAAe,GAC/D9C,EAAS,WAAW,aAAa8C,EAAiB9C,CAAQ,EAC1D+C,EAAQD,EAAiB9C,CAAQ,GAEjC2C,EAAgB3C,CAAQ,CAE5B,MAGE2C,EAAgB3C,CAAQ,EAG1BA,EAAW6C,CACb,CACF,CAEA,SAASG,EAAc1D,EAAQ2D,EAAkBC,EAAgB,CAI/D,KAAOD,GAAkB,CACvB,IAAIE,EAAkBF,EAAiB,aAClCC,EAAiB5B,EAAW2B,CAAgB,GAG/CZ,EAAgBa,CAAc,EAI9BT,EAAWQ,EAAkB3D,EAAQ,EAA2B,EAElE2D,EAAmBE,CACrB,CACF,CAEA,SAASJ,EAAQzD,EAAQC,EAAM2C,EAAc,CAC3C,IAAIkB,EAAU9B,EAAW/B,CAAI,EAQ7B,GANI6D,GAGF,OAAOjB,EAAgBiB,CAAO,EAG5B,CAAClB,EAAc,CAEjB,IAAImB,EAAqB5B,EAAkBnC,EAAQC,CAAI,EAiBvD,GAhBI8D,IAAuB,KAEhBA,aAA8B,cACvC/D,EAAS+D,EAKTX,EAAUpD,CAAM,GAIlBxB,EAAWwB,EAAQC,CAAI,EAEvBmC,EAAYpC,CAAM,EAEduC,EAA0BvC,EAAQC,CAAI,IAAM,IAC9C,MAEJ,CAEID,EAAO,WAAa,WACtBgE,EAAchE,EAAQC,CAAI,EAE1BY,GAAkB,SAASb,EAAQC,CAAI,CAE3C,CAEA,SAAS+D,EAAchE,EAAQC,EAAM,CACnC,IAAIgE,EAAWzB,EAAiBxC,EAAQC,CAAI,EACxCiE,EAAiBjE,EAAK,WACtB0D,EAAmB3D,EAAO,WAC1BmE,EACAP,EAEAC,EACAO,GACAC,EAGJC,EAAO,KAAOJ,GAAgB,CAK5B,IAJAE,GAAgBF,EAAe,YAC/BC,EAAenC,EAAWkC,CAAc,EAGjC,CAACD,GAAYN,GAAkB,CAGpC,GAFAE,EAAkBF,EAAiB,YAE/BO,EAAe,YAAcA,EAAe,WAAWP,CAAgB,EAAG,CAC5EO,EAAiBE,GACjBT,EAAmBE,EACnB,SAASS,CACX,CAEAV,EAAiB5B,EAAW2B,CAAgB,EAE5C,IAAIY,EAAkBZ,EAAiB,SAGnCa,EAAe,OA8EnB,GA5EID,IAAoBL,EAAe,WACjCK,IAAoBjD,IAGlB6C,EAGEA,IAAiBP,KAIdS,EAAiBxB,EAAgBsB,CAAY,GAC5CN,IAAoBQ,EAMtBG,EAAe,IASfxE,EAAO,aAAaqE,EAAgBV,CAAgB,EAIhDC,EAGFb,EAAgBa,CAAc,EAI9BT,EAAWQ,EAAkB3D,EAAQ,EAA2B,EAGlE2D,EAAmBU,EACnBT,EAAiB5B,EAAW2B,CAAgB,GAK9Ca,EAAe,IAGVZ,IAETY,EAAe,IAGjBA,EAAeA,IAAiB,IAASzE,GAAiB4D,EAAkBO,CAAc,EACtFM,GAKFf,EAAQE,EAAkBO,CAAc,IAGjCK,IAAoB/C,IAAa+C,GAAmB9C,MAE7D+C,EAAe,GAGXb,EAAiB,YAAcO,EAAe,YAChDP,EAAiB,UAAYO,EAAe,aAM9CM,EAAc,CAGhBN,EAAiBE,GACjBT,EAAmBE,EACnB,SAASS,CACX,CAQIV,EAGFb,EAAgBa,CAAc,EAI9BT,EAAWQ,EAAkB3D,EAAQ,EAA2B,EAGlE2D,EAAmBE,CACrB,CAMA,GAAIM,IAAiBE,EAAiBxB,EAAgBsB,CAAY,IAAMpE,GAAiBsE,EAAgBH,CAAc,EAEjHD,GAAWxB,EAASzC,EAAQqE,CAAc,EAC9CZ,EAAQY,EAAgBH,CAAc,MACjC,CACL,IAAIO,GAA0BxC,EAAkBiC,CAAc,EAC1DO,KAA4B,KAC1BA,KACFP,EAAiBO,IAGfP,EAAe,YACjBA,EAAiBA,EAAe,UAAUlE,EAAO,eAAiBX,CAAG,GAEvEoD,EAASzC,EAAQkE,CAAc,EAC/Bb,EAAgBa,CAAc,EAElC,CAEAA,EAAiBE,GACjBT,EAAmBE,CACrB,CAEAH,EAAc1D,EAAQ2D,EAAkBC,CAAc,EAEtD,IAAIc,GAAmB7D,GAAkBb,EAAO,QAAQ,EACpD0E,IACFA,GAAiB1E,EAAQC,CAAI,CAEjC,CAEA,IAAI0E,EAAclG,EACdmG,EAAkBD,EAAY,SAC9BE,EAAanG,EAAO,SAExB,GAAI,CAACkE,GAGH,GAAIgC,IAAoBtD,GAClBuD,IAAevD,GACZvB,GAAiBtB,EAAUC,CAAM,IACpC4D,EAAgB7D,CAAQ,EACxBkG,EAAclE,GAAahC,EAAU6B,GAAgB5B,EAAO,SAAUA,EAAO,YAAY,CAAC,GAI5FiG,EAAcjG,UAEPkG,IAAoBpD,IAAaoD,IAAoBnD,GAAc,CAC5E,GAAIoD,IAAeD,EACjB,OAAID,EAAY,YAAcjG,EAAO,YACnCiG,EAAY,UAAYjG,EAAO,WAG1BiG,EAGPA,EAAcjG,CAElB,EAGF,GAAIiG,IAAgBjG,EAGlB4D,EAAgB7D,CAAQ,MACnB,CACL,GAAIC,EAAO,YAAcA,EAAO,WAAWiG,CAAW,EACpD,OAUF,GAPAlB,EAAQkB,EAAajG,EAAQkE,CAAY,EAOrCE,EACF,QAAS7D,EAAE,EAAG6F,EAAIhC,EAAiB,OAAQ7D,EAAE6F,EAAK7F,IAAK,CACrD,IAAI8F,EAAalC,EAAgBC,EAAiB7D,CAAC,CAAC,EAChD8F,GACF5B,EAAW4B,EAAYA,EAAW,WAAY,EAAK,CAEvD,CAEJ,CAEA,MAAI,CAACnC,GAAgB+B,IAAgBlG,GAAYA,EAAS,aACpDkG,EAAY,YACdA,EAAcA,EAAY,UAAUlG,EAAS,eAAiBY,CAAG,GAOnEZ,EAAS,WAAW,aAAakG,EAAalG,CAAQ,GAGjDkG,CACT,CACF,CAEA,IAAIK,GAAWnD,GAAgBrD,EAAU,EAElCyG,GAAQD,GCnwBR,IAAME,GAAmB,CAC9B,OACA,WACA,SACA,QACA,WACA,SACA,MACA,MACA,OACA,OACA,iBACA,QACA,OACF,ECbO,IAAMC,GAAN,KAAmB,CAOxB,YAA6BC,EAAgB,CAAhB,YAAAA,EAN7B,KAAQ,eAAiC,KACzC,KAAQ,kBAAmC,CAAC,EAC5C,KAAQ,mBAAyC,KACjD,KAAQ,0BAA2C,KACnD,KAAQ,wBAAyC,IAEH,CAE9C,OAAOC,EAA+B,CACpC,KAAK,eAAiBA,EAEjBA,IAIL,KAAK,wBAAwB,EAC7B,KAAK,mBAAmB,EAC1B,CAEA,OAAc,CACZ,KAAK,eAAiB,KACtB,KAAK,kBAAoB,CAAC,EAC1B,KAAK,mBAAqB,KAC1B,KAAK,0BAA4B,KACjC,KAAK,wBAA0B,IACjC,CAEA,yBAAgC,CAC9B,GAAI,CAAC,KAAK,eAAgB,OAU1B,IAAMC,EAAW,GARMC,GAAiB,IAAKC,GAC3CA,IAAS,WACL,2BACA,eAAeA,CAAI,oBACzB,EAAE,KAAK,IAAI,CAIuB,8GAElC,KAAK,kBAAoB,MAAM,KAC7B,KAAK,eAAe,iBAAiBF,CAAQ,CAC/C,CACF,CAEA,oBAA2B,CACzB,GAAI,CAAC,KAAK,eAAgB,OAE1B,IAAMG,EAAY,KAAK,eAAe,aAAa,aAAa,EAC1DC,EAAW,uBAAuBD,CAAS,GAC3CE,EAAU,sBAAsBF,CAAS,GAEzCG,EAAiBC,GAAiB,CAxD5C,IAAAC,EAyDM,IAAMC,EAASF,EAAM,OACjB,CAACE,GAAU,GAACD,EAAA,KAAK,iBAAL,MAAAA,EAAqB,SAASC,MAE1C,KAAK,eAAeA,CAAM,GAAKA,aAAkB,qBACnD,KAAK,mBAAqBA,EAC1B,KAAK,OAAO,MACV,4BACAA,EAAO,QACPA,EAAO,IAAMA,EAAO,aAAa,MAAM,CACzC,EAEI,KAAK,eAAeA,CAAM,IAC5B,KAAK,0BAA4BA,EAAO,eACxC,KAAK,wBAA0BA,EAAO,cAG5C,EAEMC,EAAgBH,GAAiB,CA3E3C,IAAAC,EA4EM,IAAMC,EAASF,EAAM,OACjB,CAACE,GAAU,GAACD,EAAA,KAAK,iBAAL,MAAAA,EAAqB,SAASC,KAE1C,KAAK,eAAeA,CAAM,GAAKA,IAAW,KAAK,qBACjD,KAAK,0BAA4BA,EAAO,eACxC,KAAK,wBAA0BA,EAAO,aACtC,KAAK,OAAO,MACV,gCACA,KAAK,0BACL,IACA,KAAK,uBACP,EAEJ,EAEK,SAAiBL,CAAQ,GAC5B,SAAS,oBAAoB,QAAU,SAAiBA,CAAQ,EAAG,EAAI,EAEpE,SAAiBC,CAAO,GAC3B,SAAS,oBAAoB,OAAS,SAAiBA,CAAO,EAAG,EAAI,EAGtE,SAAiBD,CAAQ,EAAIE,EAC7B,SAAiBD,CAAO,EAAIK,EAE7B,SAAS,iBAAiB,QAASJ,EAAe,EAAI,EACtD,SAAS,iBAAiB,OAAQI,EAAc,EAAI,EAEpD,KAAK,OAAO,MAAM,+BAA+B,CACnD,CAEA,uBAA8B,CA3GhC,IAAAF,EAAAG,EAAAC,EAmHI,GAPA,KAAK,OAAO,MACV,uDACAJ,EAAA,KAAK,qBAAL,YAAAA,EAAyB,UACzBG,EAAA,KAAK,qBAAL,YAAAA,EAAyB,OACvBC,EAAA,KAAK,qBAAL,YAAAA,EAAyB,aAAa,QAC1C,EAEI,CAAC,KAAK,oBAAsB,CAAC,KAAK,eAAgB,CACpD,KAAK,OAAO,MAAM,+BAA+B,EACjD,MACF,CAEA,IAAMZ,EAAW,KAAK,mBAAmB,KAAK,kBAAkB,EAGhE,GAFA,KAAK,OAAO,MAAM,qCAAsCA,CAAQ,EAE5D,CAACA,EAAU,CACb,KAAK,OAAO,MAAM,qCAAqC,EACvD,MACF,CAEA,IAAIa,EAA8B,KAElC,GAAIb,EAAS,WAAW,mBAAmB,EAAG,CAC5C,KAAK,wBAAwB,EAC7B,IAAMc,EAAQ,SAASd,EAAS,QAAQ,oBAAqB,EAAE,EAAG,EAAE,EACpEa,EAAU,KAAK,kBAAkBC,CAAK,GAAK,KAC3C,KAAK,OAAO,MAAM,0BAA2BA,EAAOD,GAAA,YAAAA,EAAS,OAAO,CACtE,MACEA,EAAU,KAAK,eAAe,cAAcb,CAAQ,EACpD,KAAK,OAAO,MACV,6BACAA,EACAa,GAAA,YAAAA,EAAS,OACX,EAGF,GAAI,CAACA,EAAS,CACZ,KAAK,OAAO,MAAM,0CAA0C,EAC5D,MACF,CAEA,IAAME,EAAaF,EAAQ,QAAQ,QAAQ,EAG3C,GAFA,KAAK,OAAO,MAAM,2BAA4BE,CAAU,EAEpDA,EAAY,CACd,KAAK,OAAO,MACV,4DACF,EACA,MACF,CAKA,IAAMC,EAAiB,KAAK,0BACtBC,EAAe,KAAK,wBAE1BJ,EAAQ,MAAM,EACd,KAAK,OAAO,MAAM,wBAAwB,EAGxC,KAAK,eAAeA,CAAO,GAC3BG,IAAmB,MACnBC,IAAiB,OAEjBJ,EAAQ,kBAAkBG,EAAgBC,CAAY,EACtD,KAAK,OAAO,MACV,2BACAD,EACA,IACAC,CACF,EAEJ,CAEA,eAAeC,EAA2D,CACxE,OAAIA,aAAc,oBAA4B,GAC1CA,aAAc,iBACTjB,GAAiB,QAAQiB,EAAG,IAAI,GAAK,EAEvC,EACT,CAEA,iBAAiBA,EAAsB,CAKrC,OAJIA,IAAO,SAAS,eAIhBA,EAAG,aAAa,uBAAuB,EAClC,GAGL,QAAK,eAAeA,CAAE,GAItBA,aAAc,kBAKpB,CAEA,uBAA4C,CAC1C,OAAO,KAAK,kBACd,CAEQ,mBAAmBA,EAAgC,CACzD,GAAIA,EAAG,GAAI,MAAO,IAAIA,EAAG,EAAE,GAC3B,GAAKA,EAAW,KAAM,MAAO,UAAWA,EAAW,IAAI,KACvD,GAAIA,EAAG,aAAa,UAAU,EAC5B,MAAO,cAAcA,EAAG,aAAa,UAAU,CAAC,KAElD,IAAMJ,EAAQ,KAAK,kBAAkB,QAAQI,CAAE,EAC/C,OAAOJ,GAAS,EAAI,oBAAoBA,CAAK,GAAK,IACpD,CACF,ECnLA,IAAMK,GAAqC,CAAC,UAAW,UAAW,QAAS,MAAM,EAC3EC,GAAgB,IAAI,IAAYD,EAAgB,EAOzCE,GAAqB,IAAI,IAAI,CAAC,YAAY,CAAC,EAGlDC,GAA6C,CACjD,MAAO,QACP,SAAU,WACV,YAAa,cACb,YAAa,cACb,QAAS,UACT,WAAY,YACd,EAYO,SAASC,GACdC,EACAC,EACwB,CAIxB,IAAMC,EAHQF,EAAS,YAAY,EAGZ,MAAM,wBAAwB,EACrD,GAAIE,EAAU,CACZ,IAAMC,EAAYD,EAAS,CAAC,EACtBE,EAASN,GAAWK,CAAS,EACnC,GAAI,CAACC,EAAQ,OAAO,KAEpB,IAAMC,EAAYH,EAAS,CAAC,EAI5B,GAFIL,GAAmB,IAAIQ,CAAS,GAEhC,CAACT,GAAc,IAAIS,CAAS,GAAK,CAACA,EAAU,SAAS,GAAG,EAAG,OAAO,KAEtE,IAAMC,EAAWD,EAAU,MAAM,GAAG,EAC9BE,EAAcD,EAASA,EAAS,OAAS,CAAC,EAChD,GAAI,CAACV,GAAc,IAAIW,CAAW,EAAG,OAAO,KAE5C,IAAMC,EAAYD,EACZE,EAAaH,EAAS,OAAS,EAAIA,EAAS,MAAM,EAAG,EAAE,EAAE,KAAK,GAAG,EAAI,OAE3E,MAAO,CACL,OAAAF,EACA,UAAAI,EACA,WAAYC,GAAc,OAC1B,MAAOR,GAAa,MACtB,CACF,CAEA,OAAO,IACT,CASO,SAASS,EAAcC,EAA2B,CACvD,IAAMC,EAAWD,EAAQ,aAAa,iBAAiB,EACvD,OAAKC,EACDA,EAAS,WAAW,GAAG,EAClB,SAAS,eAAeA,EAAS,MAAM,CAAC,CAAC,GAAKD,EAEnDC,EAAS,WAAW,UAAU,GACzBD,EAAQ,QAAQC,EAAS,MAAM,CAAC,CAAC,GAAKD,EALzBA,CAQxB,CAKO,SAASE,GACdF,EACAP,EACAU,EACM,CACN,OAAQV,EAAQ,CACd,IAAK,QACCO,aAAmB,iBACrBA,EAAQ,MAAM,EAEhB,MAEF,IAAK,WACH,GAAIG,EAAO,CACT,IAAMC,EAAUD,EAAM,MAAM,KAAK,EAAE,OAAO,OAAO,EACjDH,EAAQ,UAAU,IAAI,GAAGI,CAAO,CAClC,CACA,MAEF,IAAK,cACH,GAAID,EAAO,CACT,IAAMC,EAAUD,EAAM,MAAM,KAAK,EAAE,OAAO,OAAO,EACjDH,EAAQ,UAAU,OAAO,GAAGI,CAAO,CACrC,CACA,MAEF,IAAK,cACCD,GACcA,EAAM,MAAM,KAAK,EAAE,OAAO,OAAO,EACzC,QAASE,GAAML,EAAQ,UAAU,OAAOK,CAAC,CAAC,EAEpD,MAEF,IAAK,UACH,GAAIF,EAAO,CACT,IAAMG,EAAaH,EAAM,QAAQ,GAAG,EACpC,GAAIG,EAAa,EAAG,CAClB,IAAMC,EAAOJ,EAAM,UAAU,EAAGG,CAAU,EACpCE,EAAQL,EAAM,UAAUG,EAAa,CAAC,EAC5CN,EAAQ,aAAaO,EAAMC,CAAK,CAClC,CACF,CACA,MAEF,IAAK,aACCL,GACFH,EAAQ,gBAAgBG,CAAK,EAE/B,KACJ,CACF,CAKO,SAASM,GACdC,EACAb,EACAC,EACS,CACT,OAAIY,EAAQ,YAAcb,EAAkB,GACvCa,EAAQ,WACNA,EAAQ,aAAeZ,EADE,EAElC,CAKO,SAASa,GACdd,EACAC,EACM,CAKN,IAAMc,EAAa,OAAO,KAAKzB,EAAU,EACnC0B,EAA0B,CAAC,EAG3BC,EAAgBhB,EAClBA,EAAW,QAAQ,YAAa,MAAM,EACtC,OAEJ,QAAWiB,KAAKH,EACdC,EAAc,KAAK,aAAaE,CAAC,WAAWlB,CAAS,GAAG,EACpDiB,GACFD,EAAc,KAAK,aAAaE,CAAC,WAAWD,CAAa,MAAMjB,CAAS,GAAG,EAG/E,IAAMI,EAAWY,EAAc,KAAK,IAAI,EAEpCG,EACJ,GAAI,CACFA,EAAa,SAAS,iBAAiBf,CAAQ,CACjD,OAAQgB,EAAA,CAGN,IAAMC,EAAgBN,EAAW,IAAIG,GAAK,aAAaA,CAAC,WAAWlB,CAAS,GAAG,EAC/E,GAAI,CACFmB,EAAa,SAAS,iBAAiBE,EAAc,KAAK,IAAI,CAAC,CACjE,OAAQD,EAAA,CACN,MACF,CACF,CAEAD,EAAW,QAAShB,GAAY,CAC9B,MAAM,KAAKA,EAAQ,UAAU,EAAE,QAASmB,GAAS,CAC/C,GAAI,CAACA,EAAK,KAAK,WAAW,SAAS,GAAK,CAACA,EAAK,KAAK,SAAS,MAAM,EAChE,OAGF,IAAMT,EAAUtB,GAAuB+B,EAAK,KAAMA,EAAK,KAAK,EACxDT,GAAWD,GAAaC,EAASb,EAAWC,CAAU,GACxDI,GAAcH,EAAcC,CAAO,EAAGU,EAAQ,OAAQA,EAAQ,KAAK,CAEvE,CAAC,CACH,CAAC,CACH,CAKO,SAASU,GAA0BpB,EAAkBqB,EAAuB,CACjF,QAAWF,KAAQnB,EAAQ,WAAY,CACrC,IAAMsB,EAAQH,EAAK,KAAK,MAAM,8BAA8B,EAE5D,GADI,CAACG,GACDA,EAAM,CAAC,EAAE,YAAY,IAAMD,EAAS,SAExC,IAAM7B,EAAY8B,EAAM,CAAC,EAAE,YAAY,EACjC7B,EAASN,GAAWK,CAAS,EAC9BC,GAELS,GAAcH,EAAcC,CAAO,EAAGP,EAAQ0B,EAAK,KAAK,CAC1D,CACF,CAQO,SAASI,GAAkBF,EAA0B,CAC1D,MAAO,CAACpC,GAAc,IAAIoC,CAAO,GAAK,CAACnC,GAAmB,IAAImC,CAAO,CACvE,CAKO,SAASG,IAAwC,CACtDxC,GAAiB,QAASa,GAAc,CACtC,SAAS,iBACP,OAAOA,CAAS,GACf,GAAa,CAhSpB,IAAA4B,EAkSQ,IAAM3B,GAAa2B,EADC,EACW,SAAZ,YAAAA,EAAoB,OACvCd,GAA0Bd,EAAWC,CAAU,CACjD,EACA,EACF,CACF,CAAC,CACH,CCpSA,IAAM4B,GAAmB,IAAI,IAAI,CAAC,UAAW,UAAW,QAAS,MAAM,CAAC,EAapEC,GAAmB,IAAI,QAMvBC,GAAoB,IAAI,QAgBxBC,EAAkB,IAAI,IA+B1B,SAASC,GAAeC,EAAmE,CAEzF,IAAMC,EAAUD,EAAS,MAAM,uBAAuB,EACtD,GAAI,CAACC,EAAS,MAAO,CAAE,QAAS,IAAK,EAErC,IAAMC,EAAQD,EAAQ,CAAC,EAAE,MAAM,GAAG,EAClC,OAAIC,EAAM,SAAW,EACZ,CAAE,QAASA,EAAM,CAAC,EAAE,YAAY,CAAE,EAGpC,CACL,QAASA,EAAMA,EAAM,OAAS,CAAC,EAAE,YAAY,EAC7C,WAAYA,EAAM,MAAM,EAAG,EAAE,EAAE,KAAK,GAAG,CACzC,CACF,CAUO,SAASC,GAAwBC,EAAmBC,EAA8B,CACvF,IAAMC,EAAWD,GAAgBD,EAC3BG,EAAiB,yBAEjBC,GACFF,EAAiBC,CAAc,GAAK,CAAC,GAAG,OACvCE,GAA2BA,EAAM,GAAG,WACvC,EAEIC,EAAaC,GAAgB,CAvGrC,IAAAC,EAwGI,QAAWC,KAAQF,EAAG,WAAY,CAChC,GAAI,CAACE,EAAK,KAAK,WAAW,SAAS,EAAG,SACtC,IAAMC,EAASf,GAAec,EAAK,IAAI,EAGvC,GAFI,CAACC,EAAO,SACRC,GAAiB,IAAID,EAAO,OAAO,GACnCE,GAAmB,IAAIF,EAAO,OAAO,EAAG,SAG5C,IAAMG,EAAc,YAAYJ,EAAK,IAAI,GACzC,GAAKF,EAAWM,CAAW,EAAG,SAE9B,IAAMC,GAASN,EAAAC,EAAK,KAAK,MAAM,gBAAgB,IAAhC,YAAAD,EAAoC,GACnD,GAAI,CAACM,EAAQ,SAEb,IAAMC,EAAkBN,EAAK,KACvBO,EAAW,IAAM,CACrB,GAAI,CAACT,EAAG,aAAaQ,CAAe,EAAG,OACvC,IAAME,EAAeV,EAAG,aAAaQ,CAAe,GAAK,GACnDG,EAAWC,EAAcZ,CAAE,EACjCa,GAAcF,EAAUJ,EAAQG,CAAY,CAC9C,EACAV,EAAG,iBAAiBG,EAAO,QAASM,CAAQ,EAC3CT,EAAWM,CAAW,EAAIG,EAC3BZ,EAAY,KAAK,CAAE,GAAAG,EAAI,MAAOG,EAAO,QAAS,QAASM,EAAU,SAAUH,CAAY,CAAC,CAC1F,CACF,EAGAP,EAAUN,CAAQ,EAClBA,EAAS,iBAAiB,GAAG,EAAE,QAAQM,CAAS,EAE/CJ,EAAiBC,CAAc,EAAIC,CACtC,CAMO,SAASiB,GAA2BC,EAA4B,CACrE,IAAMnB,EAAiB,yBACjBoB,EACHD,EAAoBnB,CAAc,EACjCoB,IACFA,EAAU,QAAQ,CAAC,CAAE,GAAAhB,EAAI,MAAAiB,EAAO,QAAAC,EAAS,SAAAC,CAAS,IAAM,CACtDnB,EAAG,oBAAoBiB,EAAOC,CAAO,EACrC,OAAQlB,EAAWmB,CAAQ,CAC7B,CAAC,EACD,OAAQJ,EAAoBnB,CAAc,EAE9C,CAKO,SAASwB,GACdL,EACAM,EACAC,EACM,CACN,IAAMvB,EAAaC,GAAgB,CAnKrC,IAAAC,EAoKI,QAAWC,KAAQF,EAAG,WAAY,CAChC,GAAI,CAACE,EAAK,KAAK,WAAW,SAAS,EAAG,SACtC,IAAMC,EAASf,GAAec,EAAK,IAAI,EAGvC,GAFI,CAACC,EAAO,SAAW,CAACC,GAAiB,IAAID,EAAO,OAAO,GACvDA,EAAO,UAAYkB,GACnBlB,EAAO,YAAcA,EAAO,aAAemB,EAAY,SAE3D,IAAMf,GAASN,EAAAC,EAAK,KAAK,MAAM,gBAAgB,IAAhC,YAAAD,EAAoC,GAC9CM,GAELM,GAAcb,EAAmBO,EAAQL,EAAK,KAAK,CACrD,CACF,EACAH,EAAUgB,CAAW,EACrBA,EAAY,iBAAiB,GAAG,EAAE,QAAQhB,CAAS,CACrD,CAKA,SAASc,GAAcU,EAA0BhB,EAAgBiB,EAAsB,CACrF,IAAMC,EAAW,iBAAiBF,CAAW,EAE7C,OAAQhB,EAAQ,CACd,IAAK,YAAa,CAIhB,GAAKgB,EAAoB,kBAAmB,MAC3CA,EAAoB,kBAAoB,GAEzC,IAAMG,EAAW,SACfD,EAAS,iBAAiB,0BAA0B,EAAE,KAAK,GAAK,MAAO,EACzE,EACME,EAAQF,EAAS,iBAAiB,uBAAuB,EAAE,KAAK,GAAK,UACrEG,EAAqBL,EAAY,MAAM,gBACvCM,EAAqBN,EAAY,MAAM,WAE7CA,EAAY,MAAM,WAAa,oBAAoBG,CAAQ,cAC3DH,EAAY,MAAM,gBAAkBI,EAEpC,WAAW,IAAM,CACf,GAAI,CAACJ,EAAY,YAAa,CAC5BA,EAAY,MAAM,gBAAkBK,EACpCL,EAAY,MAAM,WAAaM,EAC3BN,EAAY,MAAM,SAAW,GAC/BA,EAAY,gBAAgB,OAAO,EAEpCA,EAAoB,kBAAoB,GACzC,MACF,CACAA,EAAY,MAAM,gBAAkBK,EACpC,WAAW,IAAM,CACXL,EAAY,cACdA,EAAY,MAAM,WAAaM,EAC3BN,EAAY,MAAM,SAAW,GAC/BA,EAAY,gBAAgB,OAAO,GAGtCA,EAAoB,kBAAoB,EAC3C,EAAGG,CAAQ,CACb,EAAG,EAAE,EACL,KACF,CACA,IAAK,UAAW,CAMd,GAAII,GAAiB,IAAIP,CAAW,EAAG,MACvCO,GAAiB,IAAIP,CAAW,EAEhC,IAAMG,EAAW,SACfD,EAAS,iBAAiB,wBAAwB,EAAE,KAAK,GAAK,MAAO,EACvE,EACMM,EAAYP,GAAU,OAExBQ,EAAiB,GACrB,OAAQD,EAAW,CACjB,IAAK,OACHC,EAAiB,eAAeN,CAAQ,cACxC,MACF,IAAK,QACHM,EAAiB,gBAAgBN,CAAQ,cACzC,MACF,IAAK,QACHM,EAAiB,gBAAgBN,CAAQ,cACzC,MACF,QACE,QAAQ,KAAK,gCAAgCK,CAAS,EAAE,CAC5D,CACA,GAAI,CAACC,EAAgB,MACrBT,EAAY,MAAM,UAAYS,EAC9BT,EAAY,iBAAiB,eAAgB,IAAM,CAMjDA,EAAY,MAAM,eAAe,WAAW,EACxCA,EAAY,MAAM,SAAW,GAC/BA,EAAY,gBAAgB,OAAO,CAEvC,EAAG,CAAE,KAAM,EAAK,CAAC,EACjB,KACF,CACA,IAAK,SAAU,CACb,IAAMU,EAAcR,EAAS,iBAAiB,uBAAuB,EAAE,KAAK,EACtES,EAA2BC,GAAuB,IAAIF,CAAW,EAClEA,EAAiC,OAChCG,EAAY,SAChBX,EAAS,iBAAiB,wBAAwB,EAAE,KAAK,GAAK,MAAO,EACvE,EACMY,EAAOb,GAAU,SAEvB,OAAQa,EAAM,CACZ,IAAK,SACHd,EAAY,SAAS,CAAE,IAAKA,EAAY,aAAc,SAAAW,CAAS,CAAC,EAChE,MACF,IAAK,gBAAiB,CACAX,EAAY,QAAQ,kBAAoB,IAKrCA,EAAY,aAAeA,EAAY,UAAYA,EAAY,cAAgBa,GAClFb,EAAY,SAAS,CAAE,IAAKA,EAAY,aAAc,SAAAW,CAAS,CAAC,GAJlFX,EAAY,QAAQ,gBAAkB,IACtCA,EAAY,SAAS,CAAE,IAAKA,EAAY,aAAc,SAAU,SAAU,CAAC,GAK7E,KACF,CACA,IAAK,MACHA,EAAY,SAAS,CAAE,IAAK,EAAG,SAAAW,CAAS,CAAC,EACzC,MACF,IAAK,YAAa,CAaZX,EAAY,QAAQ,YAAc,MACpCA,EAAY,eAAe,CAAE,MAAO,SAAU,OAAQ,UAAW,SAAAW,CAAS,CAAC,EAC3EX,EAAY,QAAQ,UAAY,KAElC,KACF,CACA,IAAK,WACH,MACF,QAAS,CAMP,GAAIc,EAAK,WAAW,WAAW,EAAG,CAChC,IAAMhD,EAAWgD,EAAK,MAAM,CAAkB,EAC9C,GAAI,CAAChD,EAAU,CACb,QAAQ,KAAK,sDAAsD,EACnE,KACF,CACA,IAAMqB,EAAea,EAAY,aAAalC,CAAQ,EACzCiD,GAAkB,IAAIf,CAAW,EAOnCe,GAAkB,IAAIf,CAAW,IAAMb,IAChD4B,GAAkB,IAAIf,EAAab,CAAY,EAC/Ca,EAAY,WAAa,EACzBA,EAAY,UAAY,GAJxBe,GAAkB,IAAIf,EAAab,CAAY,EAMjD,KACF,CACA,QAAQ,KAAK,+BAA+B2B,CAAI,EAAE,CACpD,CACF,CACA,KACF,CACA,QACE,QAAQ,KAAK,0BAA0B9B,CAAM,EAAE,CACnD,CACF,CAQO,SAASgC,GAA0BxB,EAA4B,CACpE,IAAMI,EAAW,wBACjB,GAAKJ,EAAoBI,CAAQ,EAAG,OACnCJ,EAAoBI,CAAQ,EAAI,GAEjC,IAAMH,EAA8D,CAAC,EAClD,CAAC,UAAW,UAAW,QAAS,MAAM,EAC9C,QAAQK,GAAa,CAC9B,IAAMH,EAAWsB,GAAa,CAlXlC,IAAAvC,EAoXM,IAAMqB,GAAarB,EADCuC,EACW,SAAZ,YAAAvC,EAAoB,OACvCmB,GAA6BL,EAAaM,EAAWC,CAAU,CACjE,EACA,SAAS,iBAAiB,OAAOD,CAAS,GAAIH,EAAS,EAAI,EAC3DF,EAAU,KAAK,CAAE,MAAO,OAAOK,CAAS,GAAI,QAAAH,CAAQ,CAAC,CACvD,CAAC,EACAH,EAAoB,0BAA4BC,CACnD,CAMO,SAASyB,GAA6B1B,EAA4B,CACvE,IAAMC,EACHD,EAAoB,0BACnBC,IACFA,EAAU,QAAQ,CAAC,CAAE,MAAAC,EAAO,QAAAC,CAAQ,IAAM,CACxC,SAAS,oBAAoBD,EAAOC,EAAS,EAAI,CACnD,CAAC,EACD,OAAQH,EAAoB,2BAE9B,OAAQA,EAAoB,qBAC9B,CAWA,IAAMoB,GAAyB,IAAI,IAAI,CAAC,OAAQ,SAAU,SAAS,CAAC,EAE7D,SAASO,GAAuB3B,EAA4B,CACjEA,EAAY,iBAAiB,mBAAmB,EAAE,QAAS4B,GAAY,CACrE,IAAMN,EAAOM,EAAQ,aAAa,eAAe,EAC5CN,GACLxB,GAAc8B,EAAwB,SAAUN,CAAI,CACtD,CAAC,CACH,CAqBO,SAASO,IAAgC,CAC9C,OAAW,CAAE,MAAAC,CAAM,IAAKC,EAAgB,OAAO,EAAG,aAAaD,CAAK,EACpEC,EAAgB,MAAM,CACxB,CAiBO,SAASC,GAA0BhC,EAA4B,CAKpE,GACE,EAAA+B,EAAgB,OAAS,GACzB/B,EAAY,cAAc,uBAAuB,IAAM,MAWzD,QAAW,CAAC4B,EAAS7C,CAAK,IAAK,MAAM,KAAKgD,CAAe,GAErD,CAACH,EAAQ,aACT,CAACA,EAAQ,aAAa,mBAAmB,KAEzC,aAAa7C,EAAM,KAAK,EACxBgD,EAAgB,OAAOH,CAAO,GAIlC5B,EAAY,iBAAiB,uBAAuB,EAAE,QAAS4B,GAAY,CACzE,IAAMK,EAAOL,EAAQ,aAAa,mBAAmB,EACrD,GAAI,CAACK,EAAM,OAEX,IAAMC,EAAWH,EAAgB,IAAIH,CAAO,EAC5C,GAAIM,GAAYA,EAAS,OAASD,EAAM,OACpCC,GAAU,aAAaA,EAAS,KAAK,EAEzC,IAAMC,EAAWF,EAAK,QAAQ,GAAG,EAC3BG,EAAWD,EAAW,EAAIF,EAAK,MAAM,EAAGE,CAAQ,EAAI,GAIpDE,EAAU,QAAQ,KAAKD,CAAQ,EAAI,SAASA,EAAU,EAAE,EAAI,IAC5DE,EAAOH,EAAW,EAAIF,EAAK,MAAME,EAAW,CAAC,EAAI,GAcvD,GACE,CAAC,OAAO,SAASE,CAAO,GACxBA,EAAU,GACV,CAACC,GACD,CAAC,WAAW,KAAKA,CAAI,EACrB,CACA,QAAQ,KACN,8DAA8DL,CAAI,EACpE,EAKAF,EAAgB,OAAOH,CAAO,EAC9B,MACF,CAEA,IAAME,EAAQ,WAAW,IAAM,CAS7B,GAAI,CAACF,EAAQ,YAAa,OAK1B,IAAMW,EAASX,EAAQ,cACrB,gBAAgBU,CAAI,IACtB,EACIC,GAAQA,EAAO,MAAM,CAC3B,EAAGF,CAAO,EACVN,EAAgB,IAAIH,EAAS,CAAE,MAAAE,EAAO,KAAAG,CAAK,CAAC,CAC9C,CAAC,EACH,CAQO,SAASO,GAA0BxC,EAA4B,CACpEA,EAAY,iBAAiB,sBAAsB,EAAE,QAAS4B,GAAY,CACxE,IAAMN,EAAOM,EAAQ,aAAa,kBAAkB,EAC/CN,GACLxB,GAAc8B,EAAwB,YAAaN,CAAI,CACzD,CAAC,CACH,CAOO,SAASmB,GAAwBzC,EAA4B,CAClEA,EAAY,iBAAiB,oBAAoB,EAAE,QAAS4B,GAAY,CACtE,IAAMZ,EAAYY,EAAQ,aAAa,gBAAgB,EAClDZ,GACLlB,GAAc8B,EAAwB,UAAWZ,CAAS,CAC5D,CAAC,EAED0B,GAAuB,CACzB,CAEA,SAASA,IAA+B,CACtC,GAAI,CAAC,SAAS,eAAe,oBAAoB,EAAG,CAClD,IAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,GAAK,qBACXA,EAAM,YAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAcpB,SAAS,KAAK,YAAYA,CAAK,CACjC,CACF,CAgBA,IAAMC,GAAwB,wBAMvB,SAASC,GAAsB7C,EAA4B,CAChEA,EACG,iBAA8B,sBAAsB,EACpD,QAAS8C,GAAY,CACpB,IAAMC,EAAUD,EAAQ,aAAa,cAAc,EAGnD,GAFI,CAACC,GAEAD,EAAgBF,EAAqB,IAAMG,EAAS,OACxDD,EAAgBF,EAAqB,EAAIG,EAE1C,IAAIC,EACJ,GAAI,CACFA,EAAW,KAAK,MAAMD,CAAO,CAC/B,OAAQtB,EAAA,CACN,MACF,CACA,GAAI,CAAC,MAAM,QAAQuB,CAAQ,GAAK,CAACA,EAAS,OAAQ,OAElD,IAAMC,EAAWH,EAAQ,aAAa,eAAe,GAAK,YACpDI,EAAQC,GAAsBF,CAAQ,EAC5CD,EAAS,QAASI,GAAQ,CACxB,IAAMnE,EAAKoE,GAAmBD,CAAG,EACjCF,EAAM,YAAYjE,CAAE,EAChB,OAAOmE,EAAI,WAAc,UAAYA,EAAI,UAAY,GACvD,WAAW,IAAMnE,EAAG,OAAO,EAAGmE,EAAI,SAAS,CAE/C,CAAC,CACH,CAAC,CACL,CAMO,SAASE,IAA+B,CAC7C,IAAMC,EAAM,4BACNrB,EAAY,SAAiBqB,CAAG,EAClCrB,GAAU,SAAS,oBAAoB,QAASA,CAAQ,EAC5D,IAAMxC,EAAY+B,GAAa,CAC7B,IAAMyB,EAAQ,SAAS,cAAc,wBAAwB,EACzD,CAACA,GAASA,EAAM,SAASzB,EAAE,MAAc,GAC7CyB,EAAM,iBAAiB,uBAAuB,EAAE,QAASjE,GAAOA,EAAG,OAAO,CAAC,CAC7E,EACC,SAAiBsE,CAAG,EAAI7D,EACzB,SAAS,iBAAiB,QAASA,CAAQ,CAC7C,CAEA,SAASyD,GAAsBF,EAA+B,CAC5D,IAAIC,EAAQ,SAAS,cACnB,wBACF,EACA,OAAKA,IACHA,EAAQ,SAAS,cAAc,KAAK,EACpCA,EAAM,aAAa,uBAAwB,EAAE,EAC7CA,EAAM,aAAa,YAAa,QAAQ,EACxCM,GAAoBN,EAAOD,CAAQ,EACnC,SAAS,KAAK,YAAYC,CAAK,GAE1BA,CACT,CAEA,SAASM,GAAoBN,EAAoBD,EAAwB,CACvE,IAAMQ,EAAIP,EAAM,MAChB,OAAQD,EAAU,CAChB,IAAK,WACHQ,EAAE,IAAM,OAAQA,EAAE,KAAO,OAAQ,MACnC,IAAK,aACHA,EAAE,IAAM,OAAQA,EAAE,KAAO,MAAOA,EAAE,UAAY,mBAAoB,MACpE,IAAK,eACHA,EAAE,OAAS,OAAQA,EAAE,MAAQ,OAAQ,MACvC,IAAK,cACHA,EAAE,OAAS,OAAQA,EAAE,KAAO,OAAQ,MACtC,IAAK,gBACHA,EAAE,OAAS,OAAQA,EAAE,KAAO,MAAOA,EAAE,UAAY,mBAAoB,MACvE,QACEA,EAAE,IAAM,OAAQA,EAAE,MAAQ,OAAQ,KACtC,CACF,CAEA,SAASJ,GAAmBD,EAAgC,CAC1D,IAAMnE,EAAK,SAAS,cAAc,KAAK,EACvCA,EAAG,aAAa,OAAQ,OAAO,EAC/BA,EAAG,aAAa,sBAAuBmE,EAAI,EAAE,EACzCA,EAAI,MAAMnE,EAAG,aAAa,YAAamE,EAAI,IAAI,EAEnD,IAAMM,EAAQ,SAAS,cAAc,KAAK,EAG1C,GAFAA,EAAM,aAAa,yBAA0B,EAAE,EAE3CN,EAAI,MAAO,CACb,IAAMO,EAAI,SAAS,cAAc,QAAQ,EACzCA,EAAE,YAAcP,EAAI,MACpBM,EAAM,YAAYC,CAAC,CACrB,CACA,GAAIP,EAAI,KAAM,CACZ,IAAMQ,EAAI,SAAS,cAAc,GAAG,EACpCA,EAAE,YAAcR,EAAI,KACpBM,EAAM,YAAYE,CAAC,CACrB,CAIA,GAFA3E,EAAG,YAAYyE,CAAK,EAEhBN,EAAI,YAAa,CACnB,IAAMS,EAAM,SAAS,cAAc,QAAQ,EAC3CA,EAAI,KAAO,SACXA,EAAI,aAAa,aAAc,SAAS,EACxCA,EAAI,YAAc,OAClBA,EAAI,iBAAiB,QAAS,IAAM5E,EAAG,OAAO,CAAC,EAC/CA,EAAG,YAAY4E,CAAG,CACpB,CAEA,OAAO5E,CACT,CAcA,IAAM6E,GAAoB,IAAI,QAkDvB,SAASC,GAA0B/D,EAA4B,CA7xBtE,IAAAd,EAqyBE,IAAM8E,EAAYhE,EAAY,iBAC5B,0BACF,EACA,GAAIgE,EAAU,SAAW,EACzB,QAAWC,KAAOD,EAAW,CAK3B,IAAME,EAASD,EAAI,cACnB,GAAI,CAACC,EAAQ,CACXD,EAAI,OAAO,EACX,QACF,CACA,IAAME,EAAWF,EAAI,aAAa,gBAAgB,EAQlD,GAAIE,IAAa,QAAUA,IAAa,SAAU,CAChD,QAAQ,KACN,wCAAwC,KAAK,UAAUA,CAAQ,CAAC,mDAEhEF,CACF,EACAA,EAAI,OAAO,EACX,QACF,CACA,IAAM3C,EAAuB6C,EAKzBC,GAASlF,EAAAgF,EAAO,aAAP,KAAAhF,EAAqB4E,GAAkB,IAAII,CAAM,EAY9D,GAPIE,GAAUA,EAAO,OAASD,GAC5B,QAAQ,KACN,8CAA8CC,EAAO,IAAI,SAASD,CAAQ,6EAE1ED,CACF,EAEE,CAACE,EACH,GAAI,CAQFA,EAASF,EAAO,aAAa,CAC3B,KAAA5C,EACA,eAAgB2C,EAAI,aAAa,0BAA0B,EAC3D,SAAUA,EAAI,aAAa,oBAAoB,EAC/C,aAAcA,EAAI,aAAa,wBAAwB,CACzD,CAAC,EACG3C,IAAS,UACXwC,GAAkB,IAAII,EAAQE,CAAM,CAExC,OAAS3C,EAAG,CAaV,GAAI,EAAEA,aAAa,cAAe,MAAMA,EACxC,QAAQ,KACN,2CAA2CyC,EAAO,QAAQ,YAAY,CAAC,MACjEzC,EAAE,IAAI,KAAKA,EAAE,OAAO,uBAC1ByC,CACF,EACAD,EAAI,OAAO,EACX,QACF,CAOFG,EAAO,gBAAgBH,EAAI,OAAO,EAClCA,EAAI,OAAO,CACb,CACF,CASA,IAAMI,GAAkB,IAAI,IAatBC,GAA0B,IAAI,QAoB9BC,GAAoB,IA4DnB,SAASC,GACdxE,EACAyE,EACM,CAQN,OAAW,CAAC7C,EAAS7C,CAAK,IAAK,MAAM,KAAKsF,EAAe,GACnD,CAACzC,EAAQ,aAAe,CAACA,EAAQ,aAAa,oBAAoB,IACpE7C,EAAM,QAAQ,EAIlB,IAAM2F,EAAU1E,EAAY,iBAC1B,wBACF,EACA,GAAI0E,EAAQ,SAAW,EAEvB,QAAWzF,KAAMyF,EAAS,CACxB,IAAMC,EAAS1F,EAAG,aAAa,oBAAoB,EAGnD,GAAI,CAAC0F,EAAQ,CACX,QAAQ,KACN,oDAAoD,KAAK,UAAUA,CAAM,CAAC,EAC5E,EACA,QACF,CACA,IAAMzC,EAAWmC,GAAgB,IAAIpF,CAAE,EACvC,GAAIiD,GAAYA,EAAS,SAAWyC,EAAQ,CAK1CzC,EAAS,WAAWuC,CAAI,EACxB,QACF,CACIvC,GAAUA,EAAS,QAAQ,EAC/BmC,GAAgB,IAAIpF,EAAI2F,GAAiB3F,EAAI0F,EAAQF,CAAI,CAAC,CAC5D,CACF,CAWO,SAASI,GAA0B7E,EAA4B,CAGpE,OAAW,CAAC4B,EAAS7C,CAAK,IAAK,MAAM,KAAKsF,EAAe,EACnDrE,EAAY,SAAS4B,CAAO,GAC9B7C,EAAM,QAAQ,CAGpB,CAqBA,IAAM+F,EAAe,IAAI,IAQrBC,GAA+D,KAuF5D,SAASC,GACdhF,EACAyE,EACM,CAlqCR,IAAAvF,EAsqCE,OAAW,CAAC0C,EAAS7C,CAAK,IAAK,MAAM,KAAK+F,CAAY,GAElD,CAAClD,EAAQ,aACT,CAACA,EAAQ,aAAa,iBAAiB,IAEvC7C,EAAM,QAAQ,EAYlB,IAAM2F,EAAyB,CAAC,EAE9B1E,aAAuB,aACvBA,EAAY,aAAa,iBAAiB,GAE1C0E,EAAQ,KAAK1E,CAAW,EAE1B,IAAMiF,GAAO/F,EAAAc,EAAY,gBAAZ,YAAAd,EAA2B,KAcxC,GAZE+F,GACAA,IAASjF,GACTiF,EAAK,aAAa,iBAAiB,GACnC,CAACP,EAAQ,SAASO,CAAI,GAEtBP,EAAQ,KAAKO,CAAI,EAEnBjF,EACG,iBAA8B,qBAAqB,EACnD,QAASf,GAAO,CACVyF,EAAQ,SAASzF,CAAE,GAAGyF,EAAQ,KAAKzF,CAAE,CAC5C,CAAC,EACCyF,EAAQ,SAAW,EAEvB,QAAWzF,KAAMyF,EAAS,CACxB,IAAMC,EAAS1F,EAAG,aAAa,iBAAiB,EAChD,GAAI,CAAC0F,EAAQ,CACX,QAAQ,KACN,iDAAiD,KAAK,UAAUA,CAAM,CAAC,EACzE,EACA,QACF,CACA,IAAMO,EAAWjG,EAAG,aAAa,mBAAmB,GAAK,GACnDiD,EAAW4C,EAAa,IAAI7F,CAAE,EACpC,GAAIiD,GAAYA,EAAS,SAAWyC,EAAQ,CAC1CzC,EAAS,WAAWuC,CAAI,EACxBU,GAAyBjD,EAAUgD,CAAQ,EAC3C,QACF,CACIhD,GAAUA,EAAS,QAAQ,EAC/B,IAAMnD,EAAQqG,GAAcnG,EAAI0F,EAAQF,CAAI,EAC5CK,EAAa,IAAI7F,EAAIF,CAAK,EAE1B,IAAMsG,EAAkB,OAAO,SAAS,KAAK,QAAQ,KAAM,EAAE,EAE3DA,GACAA,IAAoBH,GACpBI,GAAsBD,CAAe,GAMrCtG,EAAM,gBAAkBsG,EACxBZ,EAAK,CAAE,OAAAE,EAAQ,KAAM,CAAE,KAAMU,CAAgB,CAAE,CAAC,GACvCA,GAAmB,CAACC,GAAsBD,CAAe,EAOlEtG,EAAM,gBAAkBmG,EAIxBC,GAAyBpG,EAAOmG,CAAQ,CAE5C,CACF,CAMO,SAASK,GAAuBvF,EAA4B,CAjwCnE,IAAAd,EAixCE,IAAM+F,GAAO/F,EAAAc,EAAY,gBAAZ,YAAAd,EAA2B,KACxC,OAAW,CAAC0C,EAAS7C,CAAK,IAAK,MAAM,KAAK+F,CAAY,EAAG,CACvD,GAAI9E,EAAY,SAAS4B,CAAO,EAAG,CACjC7C,EAAM,QAAQ,EACd,QACF,CACIkG,GAAQrD,IAAYqD,GAAQA,EAAK,SAASjF,CAAW,GACvDjB,EAAM,QAAQ,CAElB,CACF,CAyBA,SAASoG,GAAyBpG,EAAqBmG,EAAwB,CAC7E,GAAInG,EAAM,kBAAoBmG,EAAU,OACxC,IAAMM,EAAkB,OAAO,SAAS,KAAK,QAAQ,KAAM,EAAE,EAC7D,GAAIA,IAAoBN,EAAU,CAChCnG,EAAM,gBAAkBmG,EACxB,MACF,CACA,GAAIM,IAAoB,IAAM,CAACF,GAAsBE,CAAe,EAAG,CAOrEzG,EAAM,gBAAkBmG,EACxB,MACF,CACAO,GAAoBP,CAAQ,EAC5B,IAAMQ,EAAYR,EAAW,IAAIA,CAAQ,GAAK,OAAO,SAAS,SAAW,OAAO,SAAS,OACnFS,EAAUH,EAAgB,MAAM,GAAG,EAAE,CAAC,EACtCI,EAAUV,EAAS,MAAM,GAAG,EAAE,CAAC,EAM/BW,EAAe,OAAO,QAAQ,MAMhCL,IAAoB,IAAMG,IAAYC,EACxC,OAAO,QAAQ,UAAUC,EAAc,GAAIH,CAAS,EAEpD,OAAO,QAAQ,aAAaG,EAAc,GAAIH,CAAS,EAEzD3G,EAAM,gBAAkBmG,CAC1B,CAeA,IAAMY,GAAyB,IAAI,IAcnC,SAASC,GAAoBC,EAAoB,CAC3C,CAACA,GAAQC,GAAuB,IAAID,CAAI,IACxC,eAAe,KAAKA,CAAI,GAAK,sBAAsB,KAAKA,CAAI,KAC9DC,GAAuB,IAAID,CAAI,EAC/B,QAAQ,KACN,uCAAuCA,CAAI,oQAC7C,EAEJ,CA0BA,SAASE,GAAsBF,EAAuB,CACpD,OAAKA,EACEA,EAAK,SAAS,GAAG,GAAKA,EAAK,SAAS,GAAG,GAAKA,EAAK,SAAS,GAAG,EADlD,EAEpB,CAEA,SAASG,GACPC,EACAC,EACAC,EACc,CACd,IAAMC,EAAsB,CAC1B,OAAAF,EACA,KAAMC,EACN,QAAS,IAAM,CACbE,EAAa,OAAOJ,CAAE,EAClBI,EAAa,OAAS,GAAKC,KAC7B,OAAO,oBAAoB,aAAcA,EAAqB,EAC9DA,GAAwB,KAE5B,EACA,WAAaC,GAAM,CACjBH,EAAM,KAAOG,CACf,EACA,gBAAiB,EACnB,EAEA,OAAKD,KACHA,GAAwB,IAAM,CAC5B,IAAMT,EAAO,OAAO,SAAS,KAAK,QAAQ,KAAM,EAAE,EAkBlD,GAAKE,GAAsBF,CAAI,EAM/B,QAAWW,KAAK,MAAM,KAAKH,EAAa,OAAO,CAAC,EAG9CG,EAAE,gBAAkBX,EACpBW,EAAE,KAAK,CAAE,OAAQA,EAAE,OAAQ,KAAM,CAAE,KAAAX,CAAK,CAAE,CAAC,CAE/C,EACA,OAAO,iBAAiB,aAAcS,EAAqB,GAGtDF,CACT,CAUA,SAASK,GACPR,EACAC,EACAC,EACiB,CACjB,IAAIO,EAAOP,EACPQ,EAAiC,KACjCC,EAAe,EACfC,EAAe,EACfC,EAAY,GAOZC,EAAiC,KAQjCC,EAA4B,KAE1BC,EAAgB,IAAM,CACtBN,GAIFA,EAAQ,OAAO,EAEjBA,EAAU,IACZ,EAEMO,EAAW,CAACV,EAAwBW,IAAsB,CAC9D,GAAIL,IAAc,GAAI,OAWtB,IAAMM,EAAoBN,EACpBO,EAAOL,EACbF,EAAY,GACZC,EAAa,KACbC,EAAY,KACZ,GAAI,CACFf,EAAG,sBAAsBmB,CAAiB,CAC5C,OAAQZ,EAAA,CAER,CAKA,GADAP,EAAG,oBAAoB,eAAgBqB,CAAoB,EACvD,CAACH,GAAY,CAACX,GAAK,CAACa,EAAM,CAC5BJ,EAAc,EACd,MACF,CACA,GAAII,EAAK,OAAS,GAAKA,EAAK,QAAU,EAAG,CACvCJ,EAAc,EACd,MACF,CAKA,IAAMM,EAAYF,EAAK,KAAOA,EAAK,MAC7BG,EAAaH,EAAK,IAAMA,EAAK,OAC7BI,EAAKC,GAAW,KAAK,IAAId,EAAcJ,EAAE,OAAO,EAAGa,EAAK,KAAME,CAAS,EACvEI,EAAKD,GAAW,KAAK,IAAIb,EAAcL,EAAE,OAAO,EAAGa,EAAK,IAAKG,CAAU,EACvEI,EAAKF,GAAW,KAAK,IAAId,EAAcJ,EAAE,OAAO,EAAGa,EAAK,KAAME,CAAS,EACvEM,EAAKH,GAAW,KAAK,IAAIb,EAAcL,EAAE,OAAO,EAAGa,EAAK,IAAKG,CAAU,EACvEM,GAAKL,EAAKJ,EAAK,MAAQA,EAAK,MAC5BU,GAAKJ,EAAKN,EAAK,KAAOA,EAAK,OAC3BW,GAAKJ,EAAKH,GAAMJ,EAAK,MACrBY,GAAKJ,EAAKF,GAAMN,EAAK,OAC3BJ,EAAc,EAOV,EAAAe,GAAK,GAAKC,GAAK,KAQfD,EAAIE,IAAqBD,EAAIC,IAKjCxB,EAAK,CAAE,OAAAR,EAAQ,KAAM,CAAE,EAAA4B,EAAG,EAAAC,EAAG,EAAAC,EAAG,EAAAC,CAAE,CAAE,CAAC,EACvC,EAEMX,EAAwBd,GAAoB,CAO5CA,EAAE,YAAcM,GACpBI,EAAS,KAAM,EAAK,CACtB,EAQMiB,EAAe3B,GAAiBA,EAAE,eAAe,EAEjD4B,EAAiB5B,GAAoB,CAIzC,GAAI,CAACA,EAAE,WAAaA,EAAE,SAAW,EAAG,OAMhCM,IAAc,IAAII,EAAS,KAAM,EAAK,EAC1C,IAAMmB,EAASpC,EAAG,cAClB,GAAI,CAACoC,EAAQ,OAWb,GAAI,CAACC,GAAwB,IAAID,CAAM,EAAG,CACxC,IAAME,EAAY,OAAO,iBAAiBF,CAAM,EAAE,SAEhDE,IAAc,YACdA,IAAc,YACdA,IAAc,SACdA,IAAc,WAEd,QAAQ,KACN,yKAEAF,CACF,EACAC,GAAwB,IAAID,CAAM,EAEtC,CACAzB,EAAeJ,EAAE,QACjBK,EAAeL,EAAE,QACjBM,EAAYN,EAAE,UACdO,EAAasB,EACbrB,EAAYf,EAAG,sBAAsB,EACrC,IAAIuC,EAAY,GAChB,GAAI,CACFvC,EAAG,kBAAkBa,CAAS,EAC9B0B,EAAY,EACd,OAAQhC,EAAA,CAIR,CACKgC,GACHvC,EAAG,iBAAiB,eAAgBqB,EAAsB,CAAE,KAAM,EAAK,CAAC,EAE1EX,EAAU,SAAS,cAAc,KAAK,EACtCA,EAAQ,UAAY,0BACpBA,EAAQ,aAAa,cAAe,MAAM,EAI1CA,EAAQ,MAAM,QACZ,8NAGF0B,EAAO,YAAY1B,CAAO,EAC1B8B,EAAcjC,CAAC,CAQjB,EAEMiC,EAAiBjC,GAAoB,CACzC,GAAI,CAACG,EAAS,OAMd,IAAM0B,EAAStB,EACf,GAAI,CAACsB,EAAQ,OACb,IAAMK,EAASzC,EAAG,sBAAsB,EAClC0C,EAAaN,EAAO,sBAAsB,EAe1CO,EAAUP,EAAO,WACjBQ,EAAUR,EAAO,UACjBS,EAAUT,EAAO,WACjBU,EAAUV,EAAO,UACjBW,EAAaC,GAAeA,EAAKN,EAAW,KAAOC,EAAUE,EAC7DI,EAAYC,GAAeA,EAAKR,EAAW,IAAME,EAAUE,EAC3DK,EAAOJ,EAAU,KAAK,IAAIpC,EAAcJ,EAAE,OAAO,CAAC,EAClD6C,EAAMH,EAAS,KAAK,IAAIrC,EAAcL,EAAE,OAAO,CAAC,EAChD8C,EAAQ,KAAK,IAAI9C,EAAE,QAAUI,CAAY,EACzC2C,EAAS,KAAK,IAAI/C,EAAE,QAAUK,CAAY,EAG1C2C,EAAUR,EAAUN,EAAO,IAAI,EAC/Be,EAASP,EAASR,EAAO,GAAG,EAC5BgB,EAAWF,EAAUd,EAAO,MAC5BiB,EAAYF,EAASf,EAAO,OAC5BkB,EAAc,KAAK,IAAIJ,EAAS,KAAK,IAAIJ,EAAMM,CAAQ,CAAC,EACxDG,EAAa,KAAK,IAAIJ,EAAQ,KAAK,IAAIJ,EAAKM,CAAS,CAAC,EACtDG,GAAe,KAAK,IAAIN,EAAS,KAAK,IAAIJ,EAAOE,EAAOI,CAAQ,CAAC,EACjEK,EAAgB,KAAK,IAAIN,EAAQ,KAAK,IAAIJ,EAAME,EAAQI,CAAS,CAAC,EACxEhD,EAAQ,MAAM,KAAO,GAAGiD,CAAW,KACnCjD,EAAQ,MAAM,IAAM,GAAGkD,CAAU,KACjClD,EAAQ,MAAM,MAAQ,GAAG,KAAK,IAAI,EAAGmD,GAAeF,CAAW,CAAC,KAChEjD,EAAQ,MAAM,OAAS,GAAG,KAAK,IAAI,EAAGoD,EAAgBF,CAAU,CAAC,IACnE,EAEMG,EAAiBxD,GAAoB,CACzC,GAAIA,EAAE,YAAcM,EAIpB,IAAI,CAACb,EAAG,YAAa,CACnBiB,EAAS,KAAM,EAAK,EACpB,MACF,CACAuB,EAAcjC,CAAC,EACjB,EAEMyD,EAAezD,GAAoB,CACvC,GAAIA,EAAE,YAAcM,EACpB,IAAI,CAACb,EAAG,YAAa,CACnBiB,EAAS,KAAM,EAAK,EACpB,MACF,CACAA,EAASV,EAAG,EAAI,EAClB,EAEM0D,EAAmB1D,GAAoB,CACvCA,EAAE,YAAcM,GACpBI,EAASV,EAAG,EAAK,CACnB,EAMM2D,EAAiB3D,GAAoB,CACrCA,EAAE,YAAcM,GAAWI,EAAS,KAAM,EAAK,CACrD,EAEA,OAAAjB,EAAG,iBAAiB,cAAemC,CAAa,EAChDnC,EAAG,iBAAiB,cAAe+D,CAAa,EAChD/D,EAAG,iBAAiB,YAAagE,CAAW,EAC5ChE,EAAG,iBAAiB,gBAAiBiE,CAAe,EACpDjE,EAAG,iBAAiB,qBAAsBkE,CAAa,EACvDlE,EAAG,iBAAiB,YAAakC,CAAW,EAcrC,CACL,OAAAjC,EACA,QAdc,IAAM,CACpBD,EAAG,oBAAoB,cAAemC,CAAa,EACnDnC,EAAG,oBAAoB,cAAe+D,CAAa,EACnD/D,EAAG,oBAAoB,YAAagE,CAAW,EAC/ChE,EAAG,oBAAoB,gBAAiBiE,CAAe,EACvDjE,EAAG,oBAAoB,qBAAsBkE,CAAa,EAC1DlE,EAAG,oBAAoB,eAAgBqB,CAAoB,EAC3DrB,EAAG,oBAAoB,YAAakC,CAAW,EAC/CjB,EAAS,KAAM,EAAK,EACpBkD,GAAgB,OAAOnE,CAAE,CAC3B,EAKE,WAAaM,GAAM,CACjBG,EAAOH,CACT,CACF,CACF,CAEA,SAASmB,GAAW2C,EAAWC,EAAYC,EAAoB,CAC7D,MAAI,CAAC,OAAO,SAASF,CAAC,GAAKA,EAAIC,EAAWA,EACtCD,EAAIE,EAAWA,EACZF,CACT,CC/xDO,SAASG,GACdC,EACAC,EACkC,CAClC,IAAIC,EAAyB,KAE7B,OAAO,YAA2CC,EAAqB,CACrE,IAAMC,EAAU,KAEZF,IAAY,MACd,aAAaA,CAAO,EAGtBA,EAAU,OAAO,WAAW,IAAM,CAChCF,EAAK,MAAMI,EAASD,CAAI,CAC1B,EAAGF,CAAI,CACT,CACF,CAMO,SAASI,GACdL,EACAM,EACkC,CAClC,IAAIC,EAAa,GAEjB,OAAO,YAA2CJ,EAAqB,CACrE,IAAMC,EAAU,KAEXG,IACHP,EAAK,MAAMI,EAASD,CAAI,EACxBI,EAAa,GAEb,WAAW,IAAM,CACfA,EAAa,EACf,EAAGD,CAAK,EAEZ,CACF,CCfA,IAAME,GAAiB,aAevB,SAASC,GAAWC,EAAeC,EAAuB,CACxD,MAAO,GAAGH,EAAc,IAAIE,CAAK,IAAIC,CAAK,EAC5C,CAOA,SAASC,GAAaC,EAAoD,CArD1E,IAAAC,EAsDE,IAAMC,EAAMF,GAAA,KAAAA,EAAkB,OAAO,UAAa,YAAc,SAAW,KACrEG,GAAUF,EAAAC,GAAA,YAAAA,EAAK,gBAAL,YAAAD,EAAA,KAAAC,EAAqB,iBACrC,OAAOC,GAAA,YAAAA,EAAS,aAAa,iBAAkB,aACjD,CAEA,SAASC,GAAWC,EAAsC,CACxD,GAAIA,GAAA,MAAAA,EAAM,QAAS,OAAOA,EAAK,QAC/B,GAAI,CACF,OAAO,OAAO,cAAiB,YAAc,aAAe,IAC9D,OAAQ,GAGN,OAAO,IACT,CACF,CAGA,SAASC,GAASC,EAA+E,CAC/F,OACEA,aAAc,kBACdA,aAAc,qBACdA,aAAc,iBAElB,CAQA,SAASC,GAAsBC,EAA0B,CArFzD,IAAAR,EAsFE,IAAMS,EAAmB,CAAC,EAC1B,OAAID,EAAK,aAAa,iBAAiB,GACrCC,EAAM,KAAKD,CAAI,GAEjBR,EAAAQ,EAAK,mBAAL,MAAAR,EAAA,KAAAQ,EAAwB,qBAAqB,QAASF,GAAOG,EAAM,KAAKH,CAAE,GACnEG,CACT,CAeA,SAASC,GACPC,EACAP,EACAQ,EACM,CA/GR,IAAAZ,EAgHE,IAAMa,EAAUV,GAAWC,CAAI,EACzBR,EAAQiB,GACTb,EAAAI,GAAA,YAAAA,EAAM,QAAN,KAAAJ,EAAeF,GAAaa,EAAc,aAAa,EACxD,KAEJ,QAAWL,KAAMC,GAAsBI,CAAa,EAAG,CACrD,IAAMd,EAAQS,EAAG,aAAa,iBAAiB,EAC/C,GAAI,CAACT,GAAS,CAACQ,GAASC,CAAE,EAAG,SAI7B,GAAIO,GAAWjB,EACb,GAAI,CACFiB,EAAQ,QAAQlB,GAAWC,EAAOC,CAAK,EAAGS,EAAG,KAAK,CACpD,OAAQQ,EAAA,CAER,CAKF,IAAMC,EAAMT,EAAG,aAAa,MAAM,GAAKT,EACvCe,EAAKG,EAAK,CAAE,SAAU,GAAM,MAAAlB,CAAM,CAAC,CACrC,CACF,CAOO,SAASmB,GACdL,EACAM,EACAb,EACM,CACNM,GAAuBC,EAAeP,EAAM,CAACW,EAAKG,IAAa,CAC7DD,EAAKF,CAAG,EAAIG,CACd,CAAC,CACH,CAaO,SAASC,GACdC,EACAC,EACAjB,EACM,CACNM,GAAuBU,EAAMhB,EAAM,CAACW,EAAKG,IAAa,CACpDG,EAAS,IAAIN,EAAK,KAAK,UAAUG,CAAQ,CAAC,CAC5C,CAAC,CACH,CASO,SAASI,GAAsBd,EAAeJ,EAA4B,CArLjF,IAAAJ,EAAAuB,EAAAC,EAsLE,IAAMX,EAAUV,GAAWC,CAAI,EAC/B,GAAI,CAACS,EAAS,OACd,IAAMjB,GAAQI,EAAAI,GAAA,YAAAA,EAAM,QAAN,KAAAJ,EAAeF,GAAaU,EAAK,aAAa,EAEtDiB,EAAQ,IAAI,IACZC,EAAQ7B,GAAiC,CAC7C,GAAI4B,EAAM,IAAI5B,CAAK,EAAG,OAAO4B,EAAM,IAAI5B,CAAK,EAC5C,IAAI8B,EAAmB,KACvB,GAAI,CACFA,EAAId,EAAQ,QAAQlB,GAAWC,EAAOC,CAAK,CAAC,CAC9C,OAAQiB,EAAA,CACNa,EAAI,IACN,CACA,OAAAF,EAAM,IAAI5B,EAAO8B,CAAC,EACXA,CACT,EAKMC,GAASJ,GAAAD,EAAAf,EAAK,gBAAL,YAAAe,EAAoB,gBAApB,KAAAC,EAAqC,KACpD,QAAWlB,KAAMC,GAAsBC,CAAI,EAAG,CAC5C,GAAIF,IAAOsB,EAAQ,SACnB,IAAM/B,EAAQS,EAAG,aAAa,iBAAiB,EAC/C,GAAI,CAACT,EAAO,SACZ,IAAM8B,EAAID,EAAK7B,CAAK,EAChB8B,IAAM,OACNtB,GAASC,CAAE,EAGbA,EAAG,MAAQqB,EAEXrB,EAAG,YAAcqB,EAErB,CACF,CCxNO,SAASE,GAAYC,EAAcC,EAAwB,CAChE,IAAMC,EAAUF,EAAK,QAAQ,KAAM,KAAK,EACxC,GAAIC,IAAU,OAAW,MAAO,IAAIC,CAAO,IAE3C,IAAMC,EAAYF,EAAM,QAAQ,MAAO,MAAM,EAAE,QAAQ,KAAM,KAAK,EAClE,MAAO,IAAIC,CAAO,KAAKC,CAAS,IAClC,CCAA,IAAIC,GAA0B,GAWvB,SAASC,GACdC,EACAC,EACAC,EACS,CACT,OAAIF,EAAG,aAAaC,CAAO,EAAU,GACjCD,EAAG,aAAa,kBAAkB,GAC/BF,KACHA,GAA0B,GAC1BI,EAAO,KACL,uCAAuCD,CAAO,uCAChD,GAEK,IAEF,EACT,CC1BA,IAAME,GAAwD,CAC5D,MAAO,QACP,SAAU,WACV,YAAa,cACb,YAAa,cACb,QAAS,UACT,WAAY,YACd,EACMC,GAAqB,OAAO,KAAKD,EAAqB,EAGtDE,GAAe,IAAI,IAAI,CAAC,aAAc,aAAc,QAAS,MAAM,CAAC,EAGpEC,GAAgB,wBAEhBC,GAAc,IAAI,IAAI,CAC1B,YACA,WACA,OACA,UACA,YACA,WACF,CAAC,EAsBYC,GAAwB,CACnC,QACA,SACA,SACA,QACA,SACA,UACA,QACA,QACA,OACA,aACA,aACA,YACA,WACA,OACA,UACA,YACA,WACF,EAEaC,GAAN,KAAqB,CAM1B,YACmBC,EACAC,EACjB,CAFiB,aAAAD,EACA,YAAAC,EAJnB,KAAQ,4BAA8B,IAAI,OAKvC,CAQH,mBAAmBC,EAAgC,CACjD,GAAKA,EACL,QAAWC,KAAaL,GAAuB,CAC7C,IAAMM,EAAc,mBAAmBD,CAAS,IAAID,CAAS,GACvDG,EAAoB,SAAiBD,CAAW,EAClDC,IACF,SAAS,oBAAoBF,EAAWE,EAAkB,EAAK,EAC/D,OAAQ,SAAiBD,CAAW,EAExC,CACF,CAEQ,kBAAkBE,EAA8CC,EAAiC,CACnGD,EAAO,QACTC,EAAK,MAAQ,KAAK,QAAQ,WAAWD,EAAO,KAAK,GAEnD,MAAM,KAAKA,EAAO,UAAU,EAAE,QAASE,GAAS,CAC9C,GAAIA,EAAK,KAAK,WAAW,OAAO,GAAKA,EAAK,OAAS,WAAY,CAC7D,IAAMC,EAAMD,EAAK,KAAK,MAAM,CAAC,EAC7BD,EAAKE,CAAG,EAAI,KAAK,QAAQ,WAAWD,EAAK,KAAK,CAChD,CACF,CAAC,CACH,CAEA,sBAA6B,CAC3B,IAAME,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAErB,IAAMC,EAAab,GACbI,EAAYQ,EAAe,aAAa,aAAa,EACrDE,EAAsB,KAAK,QAAQ,uBAAuB,EAEhED,EAAW,QAASR,GAAc,CAChC,IAAMC,EAAc,mBAAmBD,CAAS,IAAID,CAAS,GACvDG,EAAoB,SAAiBD,CAAW,EAClDC,GACF,SAAS,oBAAoBF,EAAWE,EAAkB,EAAK,EAGjE,IAAMQ,EAAYC,GAAa,CAjIrC,IAAAC,EAAAC,EAAAC,EAkIQ,IAAMC,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAErB,IAAMC,EAASL,EAAE,OAIjB,GAFA,KAAK,OAAO,MAAM,4BAA6BX,EAAWW,EAAE,MAAM,EAE9D,CAACK,EAAQ,OAEb,IAAIC,EAA0BD,EAC1BE,EAAY,GAEhB,KAAOD,GAAS,CACd,GAAIA,IAAYF,EAAgB,CAC9BG,EAAY,GACZ,KACF,CACAD,EAAUA,EAAQ,aACpB,CAEA,GAAI,CAACC,EAAW,OAEhB,IAAMC,EAAW,UAAUnB,CAAS,GAGpC,IAFAiB,EAAUD,EAEHC,GAAWA,IAAYF,EAAe,eAAe,CAC1D,IAAIK,EAASH,EAAQ,aAAaE,CAAQ,EACtCE,EAAgBJ,EAChBK,EAAiB,GAGrB,GAAI,CAACF,GAAUpB,IAAc,UAAYiB,aAAmB,gBAAiB,CAC3E,IAAMM,EAAeN,EAAQ,aAAa,aAAa,EACnDM,IACFH,EAAS,WAAWG,CAAY,GAChCF,EAAgBJ,EAEpB,CAOA,GAAI,CAACG,GAAUpB,IAAc,QAAS,CACpC,IAAMwB,EAAMP,aAAmB,kBAAoBA,EAAU,KAE3DO,GACAA,EAAI,MACJ,CAACA,EAAI,UACLA,EAAI,OAAS,SACbA,EAAI,OAAS,MACb,CAACA,EAAI,aAAa,YAAY,IAE9BJ,EAASI,EAAI,KACbH,EAAgBG,EAChBF,EAAiB,GAErB,CAYA,GAAI,CAACF,GAAUpB,IAAc,UAAYiB,aAAmB,gBAC1D,GAAKQ,GAAqBR,EAAS,wBAAyB,KAAK,MAAM,GA4BhE,GAAIA,EAAQ,aAAa,yBAAyB,EAAG,CAsB1D,IAAMS,EAAaf,EAAkB,UAIrC,GAAIe,GAAA,MAAAA,EAAW,KAAM,CAMfT,EAAQ,SAAW,OAAS,CAAC,KAAK,4BAA4B,IAAIA,CAAO,IAC3E,KAAK,OAAO,KACV,kOACAA,CACF,EACA,KAAK,4BAA4B,IAAIA,CAAO,GAK9C,IAAIU,EAAcV,EAAQ,cACxB,4CACF,EACKU,IACHA,EAAc,SAAS,cAAc,OAAO,EAC5CA,EAAY,KAAO,SACnBA,EAAY,KAAO,gBACnBV,EAAQ,YAAYU,CAAW,GAEjCA,EAAY,MAAQD,EAAU,IAChC,MAMEb,EAAAI,EACG,cACC,4CACF,IAHF,MAAAJ,EAII,QAER,MA5F0E,CAGxE,IAAMe,EAAiBX,EAAQ,aAAa,iBAAiB,EACvDS,EAAaf,EAAkB,UACjCiB,EACFR,EAASQ,EAELF,aAAqB,mBAAqBA,EAAU,KACtDN,EAASM,EAAU,KACVT,EAAQ,aAAa,MAAM,EACpCG,EAASH,EAAQ,aAAa,MAAM,EAEpCG,EAAS,SAGbC,EAAgBJ,EAEZS,IACDT,EAAgB,eAAiBS,GAKpC,IAAMG,EAASZ,EAAQ,QAAQ,QAAQ,EACnCY,KAAUjB,EAAAK,EAAQ,aAAa,QAAQ,IAA7B,YAAAL,EAAgC,iBAAkB,WAC7DK,EAAgB,iBAAmBY,EAExC,CAmEF,GAAIT,GAAU,MAAQC,EAAe,CAWnC,GAVIrB,IAAc,UAChBW,EAAE,eAAe,EASfjB,GAAY,IAAIM,CAAS,EAAG,CAC9B,IAAM8B,EAAYnB,EAClB,GAAIX,IAAc,aAAe8B,EAAU,aAAc,CACvD,IAAMC,EAAQV,EAAc,QAAQ,YAAY,EAC1Cf,GAAMQ,EAAAiB,GAAA,YAAAA,EAAO,aAAa,cAApB,KAAAjB,EAAmC,GAI/CgB,EAAU,aAAa,QAAQrC,GAAea,CAAG,EACjDwB,EAAU,aAAa,cAAgB,MACzC,SAAW9B,IAAc,WACvB8B,EAAU,eAAe,EACrBA,EAAU,eACZA,EAAU,aAAa,WAAa,gBAE7B9B,IAAc,OACvB8B,EAAU,eAAe,UAChB9B,IAAc,aAAeA,IAAc,YAAa,CAKjE,IAAMgC,EAAUF,EAAU,cAC1B,GAAIE,GAAWX,EAAc,SAASW,CAAO,EAC3C,MAEJ,CAMA,GAAIZ,IAAW,GACb,MAEJ,CAEA,IACGpB,IAAc,WAAaA,IAAc,UAC1CqB,EAAc,aAAa,SAAS,EACpC,CACA,IAAMY,EAAYZ,EAAc,aAAa,SAAS,EAEtD,GAAIY,GADkBtB,EACS,MAAQsB,EAAW,CAChDhB,EAAUA,EAAQ,cAClB,QACF,CACF,CAEA,IAAMiB,EAAgBb,EAEhBc,EAAe,IAAM,CAtWvC,IAAAvB,EAAAC,EAuWc,KAAK,OAAO,MAAM,sBAAuB,CACvC,OAAAO,EACA,UAAApB,EACA,cAAAkC,CACF,CAAC,EAED,IAAME,EAAe,CAAE,OAAAhB,EAAQ,KAAM,CAAC,CAAE,EAExC,GAAIc,aAAyB,gBAAiB,CAC5C,KAAK,OAAO,MAAM,yBAAyB,EAC3C,IAAMG,EAAW,IAAI,SAASH,CAAa,EAErCI,EAAa,MAAM,KACvBJ,EAAc,iBAAiB,8BAA8B,CAC/D,EAEMK,EAAiB,IAAI,IAC3BD,EAAW,QAASE,GAAO,CACzB,IAAMC,EAAQF,EAAe,IAAIC,EAAG,IAAI,GAAK,CAAC,EAC9CC,EAAM,KAAKD,CAAE,EACbD,EAAe,IAAIC,EAAG,KAAMC,CAAK,CACnC,CAAC,EAEDF,EAAe,QAAQ,CAACG,EAAKC,IAAS,CAChCD,EAAI,SAAW,EACjBN,EAAQ,KAAKO,CAAI,EAAID,EAAI,CAAC,EAAE,QAE5BN,EAAQ,KAAKO,CAAI,EAAID,EAAI,OAAOF,GAAMA,EAAG,OAAO,EAAE,IAAIA,GAAMA,EAAG,KAAK,CAExE,CAAC,EAGD,IAAMI,EAAiB,IAAI,IACzB,MAAM,KACJV,EAAc,iBAAiB,8BAA8B,CAC/D,EAAE,IAAKW,GAAQA,EAAwB,IAAI,CAC7C,EASMC,EAAoBZ,EAAsB,eAC1Ca,EAAkBD,GAAA,YAAAA,EAAkB,KAE1CT,EAAS,QAAQ,CAACW,EAAO1C,IAAQ,CAC3B0C,aAAiB,MACjBD,GAAmBzC,IAAQyC,GAC3BR,EAAe,IAAIjC,CAAG,IACtBsC,EAAe,IAAItC,CAAG,EAExB8B,EAAQ,KAAK9B,CAAG,EAAI0C,EAEpBZ,EAAQ,KAAK9B,CAAG,EAAI,KAAK,QAAQ,WAC/B0C,CACF,EAEJ,CAAC,EAKD,IAAMC,EAAcf,EAAsB,eACtCe,IACF,KAAK,kBAAkBA,EAAYb,EAAQ,IAAI,EAC3Ca,EAAW,OACbb,EAAQ,UAAYa,EAAW,MAEjC,OAAQf,EAAsB,gBAGhC,KAAK,OAAO,MAAM,uBAAwBE,EAAQ,IAAI,CACxD,SAAWpC,IAAc,UAAYA,IAAc,SAAWA,IAAc,UAC1E,GAAIkC,aAAyB,iBAAkB,CAC7C,IAAM5B,EAAM4B,EAAc,MAAQ,QAClCE,EAAQ,KAAK9B,CAAG,EAAI,KAAK,QAAQ,WAC/B4B,EAAc,KAChB,CACF,SAAWA,aAAyB,kBAAmB,CACrD,IAAM5B,EAAM4B,EAAc,MAAQ,QAClCE,EAAQ,KAAK9B,CAAG,EAAI,KAAK,QAAQ,WAC/B4B,EAAc,KAChB,CACF,SAAWA,aAAyB,oBAAqB,CACvD,IAAM5B,EAAM4B,EAAc,MAAQ,QAClCE,EAAQ,KAAK9B,CAAG,EAAI,KAAK,QAAQ,WAC/B4B,EAAc,KAChB,CACF,EAsBF,GAnBIZ,GACF,KAAK,kBAAkBD,EAAoCe,EAAQ,IAAI,EAMrE,EAAEF,aAAyB,kBAAoB,CAACZ,GAClD,MAAM,KAAKD,EAAc,UAAU,EAAE,QAAShB,GAAS,CACrD,GAAIA,EAAK,KAAK,WAAW,OAAO,GAAKA,EAAK,OAAS,YAAcA,EAAK,OAAS,cAAe,CAC5F,IAAMC,EAAMD,EAAK,KAAK,MAAM,CAAC,EAC7B+B,EAAQ,KAAK9B,CAAG,EAAI,KAAK,QAAQ,WAAWD,EAAK,KAAK,CACxD,CACF,CAAC,EAMCL,IAAc,OAAQ,CAKxB,IAAMkD,GAAOtC,EAAAD,EAAgB,eAAhB,YAAAC,EAA8B,QAAQnB,IAC/CyD,IACFd,EAAQ,KAAK,cAAgBc,GAI/B,IAAMC,GAAStC,EAAAQ,EAAc,QAAQ,YAAY,IAAlC,YAAAR,EAAqC,aAAa,YAC7DsC,IACFf,EAAQ,KAAK,cAAgBe,EAEjC,CAMA,IAAIC,EAAiC,KACrC,GACEpD,IAAc,UACdkC,aAAyB,gBACzB,CACA,IAAMmB,EAAkBnB,EAAc,iBACpC,sCACF,EAIA,GAHiB,MAAM,KAAKmB,CAAe,EAAE,KAC1CC,GAAUA,EAAM,OAASA,EAAM,MAAM,OAAS,CACjD,EACc,CAQZ,IAAM5B,EAAaf,EAAkB,UAIrCyC,EAAgB,IAAI,SAASlB,CAAa,EACtCR,GAAA,MAAAA,EAAW,MACb0B,EAAc,IAAI1B,EAAU,KAAMA,EAAU,KAAK,EAOnD0B,EAAc,IAAI,aAAchC,CAAM,EAClCM,GAAA,MAAAA,EAAW,MACb0B,EAAc,IAAI,gBAAiB1B,EAAU,IAAI,CAErD,CACF,CAEA,GACE1B,IAAc,UACdkC,aAAyB,gBACzB,CAEA,IAAMqB,EADc5C,EAEN,UACV6C,EAAoC,KAGtCD,GACAA,EAAa,aAAa,uBAAuB,IAEjDC,EAAqBD,EAAa,YAClCA,EAAa,SAAW,GACxBA,EAAa,YACXA,EAAa,aAAa,uBAAuB,EACnD,KAAK,OAAO,MAAM,wBAAwB,GAG5C,KAAK,QAAQ,oBACXrB,EACAqB,GAAgB,KAChBC,CACF,EAGmBtB,EAAc,iBAC/B,gCACF,EACW,QAASoB,GAAU,CAC5B,IAAMG,EAAaH,EAAM,aAAa,YAAY,EAC9CG,IACF,KAAK,OAAO,MAAM,kCAAmCA,CAAU,EAC/D,KAAK,QAAQ,sBAAsBA,CAAU,EAEjD,CAAC,EAEDvB,EAAc,cACZ,IAAI,YAAY,cAAe,CAAE,OAAQE,CAAQ,CAAC,CACpD,EACA,KAAK,OAAO,MAAM,2BAA2B,CAC/C,CAWA,GATA,KAAK,OAAO,MAAM,yBAA0BA,CAAO,EACnD,KAAK,OAAO,MACV,mBACA,KAAK,QAAQ,uBAAuB,CACtC,EAKIgB,IAAkB,KAAM,CAC1B,KAAK,OAAO,MAAM,+CAA+C,EAKjEM,GAAexB,EAAekB,CAAa,EAC3C,KAAK,QAAQ,kBACXlB,EACAd,EACAgC,CACF,EACA,MACF,CAYA,GANAO,GAAiBzB,EAAeE,EAAQ,IAAI,EAE5C,KAAK,QAAQ,KAAKA,CAAO,EACzB,KAAK,OAAO,MAAM,eAAe,EAG7BF,aAAyB,gBAAiB,CAC5C,IAAM0B,EAAiB1B,EAAsB,iBACzC0B,IACFA,EAAc,MAAM,EACpB,OAAQ1B,EAAsB,iBAElC,CACF,EAEM2B,EAAgBxC,EAAc,aAAa,kBAAkB,EAC7DyC,EAAgBzC,EAAc,aAAa,kBAAkB,EAKnE,IAFyBwC,GAAiBC,IAAkB9D,IAAc,SAErD,CACdS,EAAoB,IAAIY,CAAa,GACxCZ,EAAoB,IAAIY,EAAe,IAAI,GAAK,EAElD,IAAM0C,EAAetD,EAAoB,IAAIY,CAAa,EACpD2C,EAAW,GAAGhE,CAAS,IAAIoB,CAAM,GAIjC6C,EAAiB,kBAAkBD,CAAQ,GAC3CE,EAAsB7C,EACvB6C,EAAoBD,CAAc,IACrCC,EAAoBD,CAAc,EAAI,CAAE,QAAS9B,CAAa,GAGhE+B,EAAoBD,CAAc,EAAE,QAAU9B,EAE9C,IAAIgC,EAAqBJ,EAAa,IAAIC,CAAQ,EAClD,GAAI,CAACG,EAAoB,CAGvB,IAAMC,EAAa,IAAMF,EAAoBD,CAAc,EAAE,QAAQ,EACrE,GAAIJ,EAAe,CACjB,IAAMQ,EAAQ,SAASR,EAAe,EAAE,EACxCM,EAAqBG,GAASF,EAAYC,CAAK,CACjD,SAAWP,EAAe,CACxB,IAAMS,EAAO,SAAST,EAAe,EAAE,EACvCK,EAAqBK,GAASJ,EAAYG,CAAI,CAChD,CACIJ,GACFJ,EAAa,IAAIC,EAAUG,CAAkB,CAEjD,CAEIA,GACFA,EAAmB,CAEvB,MACEhC,EAAa,EAGf,MACF,CACAlB,EAAUA,EAAQ,aACpB,CACF,EAEC,SAAiBhB,CAAW,EAAIS,EACjC,SAAS,iBAAiBV,EAAWU,EAAU,EAAK,EACpD,KAAK,OAAO,MACV,6BACAV,EACA,YACAC,CACF,CACF,CAAC,CACH,CAEA,4BAAmC,CACjC,IAAMM,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAErB,IAAMkE,EAAe,CACnB,UACA,QACA,SACA,SACA,QACA,MACF,EACM1E,EAAYQ,EAAe,aAAa,aAAa,EACrDE,EAAsB,KAAK,QAAQ,uBAAuB,EAEhEgE,EAAa,QAASzE,GAAc,CAClC,IAAMC,EAAc,gBAAgBD,CAAS,IAAID,CAAS,GACpDG,EAAoB,OAAeD,CAAW,EAChDC,GACF,OAAO,oBAAoBF,EAAWE,CAAgB,EAGxD,IAAMQ,EAAYC,GAAa,CAC7B,IAAMI,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAErB,IAAMI,EAAW,iBAAiBnB,CAAS,GAC1Be,EAAe,iBAAiB2D,GAAYvD,CAAQ,CAAC,EAE7D,QAASF,GAAY,CAC5B,IAAMG,EAASH,EAAQ,aAAaE,CAAQ,EAC5C,GAAI,CAACC,EAAQ,OAEb,IACGpB,IAAc,WAAaA,IAAc,UAC1CiB,EAAQ,aAAa,SAAS,EAC9B,CACA,IAAMgB,EAAYhB,EAAQ,aAAa,SAAS,EAEhD,GAAIgB,GADkBtB,EACS,MAAQsB,EACrC,MAEJ,CAEA,IAAMG,EAAe,CAAE,OAAAhB,EAAQ,KAAM,CAAC,CAAE,EAGxC,MAAM,KAAKH,EAAQ,UAAU,EAAE,QAASZ,GAAS,CAC/C,GAAIA,EAAK,KAAK,WAAW,OAAO,GAAKA,EAAK,OAAS,YAAcA,EAAK,OAAS,cAAe,CAC5F,IAAMC,EAAMD,EAAK,KAAK,MAAM,CAAC,EAC7B+B,EAAQ,KAAK9B,CAAG,EAAI,KAAK,QAAQ,WAAWD,EAAK,KAAK,CACxD,CACF,CAAC,EAED,IAAMwD,EAAgB5C,EAAQ,aAAa,kBAAkB,EACvD6C,EAAgB7C,EAAQ,aAAa,kBAAkB,EAEvDkB,EAAe,IAAM,KAAK,QAAQ,KAAKC,CAAO,EAEpD,GAAIyB,GAAiBC,EAAe,CAC7BrD,EAAoB,IAAIQ,CAAO,GAClCR,EAAoB,IAAIQ,EAAS,IAAI,GAAK,EAE5C,IAAM8C,EAAetD,EAAoB,IAAIQ,CAAO,EAC9C+C,EAAW,UAAUhE,CAAS,IAAIoB,CAAM,GAE1C+C,EAAqBJ,EAAa,IAAIC,CAAQ,EAClD,GAAI,CAACG,EAAoB,CACvB,GAAIN,EAAe,CACjB,IAAMQ,EAAQ,SAASR,EAAe,EAAE,EACxCM,EAAqBG,GAASnC,EAAckC,CAAK,CACnD,SAAWP,EAAe,CACxB,IAAMS,EAAO,SAAST,EAAe,EAAE,EACvCK,EAAqBK,GAASrC,EAAcoC,CAAI,CAClD,CACIJ,GACFJ,EAAa,IAAIC,EAAUG,CAAkB,CAEjD,CAEIA,GACFA,EAAmB,CAEvB,MACEhC,EAAa,CAEjB,CAAC,CACH,EAEC,OAAelC,CAAW,EAAIS,EAC/B,OAAO,iBAAiBV,EAAWU,CAAQ,CAC7C,CAAC,CACH,CAOA,0BAAiC,CAC/B,IAAMH,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAGrB,IAAMN,EAAc,oBADFM,EAAe,aAAa,aAAa,CACV,GAC3CL,EAAoB,SAAiBD,CAAW,EAClDC,GACF,SAAS,oBAAoB,QAASA,CAAgB,EAGxD,IAAMQ,EAAYC,GAAa,CAC7B,IAAMI,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAErB,IAAMC,EAASL,EAAE,OAEXgE,EAAoBpF,GACvB,IAAIqF,GAAKF,GAAY,UAAUE,CAAC,gBAAgB,CAAC,EACjD,KAAK,IAAI,EACc7D,EAAe,iBAAiB4D,CAAiB,EACzD,QAAS1D,GAAY,CACjCA,EAAQ,SAASD,CAAM,GAE3B,MAAM,KAAKC,EAAQ,UAAU,EAAE,QAASZ,GAAS,CAC/C,GAAI,CAACA,EAAK,KAAK,SAAS,gBAAgB,EAAG,OAC3C,IAAMwE,EAAQxE,EAAK,KAAK,MAAM,8BAA8B,EAC5D,GAAI,CAACwE,EAAO,OACZ,IAAMC,EAASxF,GAAsBuF,EAAM,CAAC,EAAE,YAAY,CAAC,EACtDC,GACLC,GAAcC,EAAc/D,CAAO,EAAG6D,EAAQzE,EAAK,KAAK,CAC1D,CAAC,CACH,CAAC,CACH,EAEC,SAAiBJ,CAAW,EAAIS,EACjC,SAAS,iBAAiB,QAASA,CAAQ,CAC7C,CAoBA,+BAA+BuE,EAA0B,CACvD,IAAM1E,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAErB,IAAMR,EAAYQ,EAAe,aAAa,aAAa,EAC3D,GAAI,CAACR,EAAW,OAEhB,IAAMmF,EAAe,sBAAsBnF,CAAS,GAC9CoF,EAA0B5E,EAAuB2E,CAAY,GAAK,IAAI,IAItEE,EAAe,sBAAsBrF,CAAS,GAC9CsF,GACF9E,EAAuB6E,CAAY,GAAK,CAAC,GAAG,OAC3CE,GAA2BA,EAAM,GAAG,WACvC,EAIIC,EAAON,GAAY1E,EACnBiF,EAAa3C,GAAgB,CACjC,IAAM4C,EAAW,IAAI,IACrB,QAAWpF,KAAQwC,EAAG,WAAY,CAChC,GAAI,CAACxC,EAAK,KAAK,WAAW,SAAS,EAAG,SACtC,IAAMwE,EAAQxE,EAAK,KAAK,MAAM,4BAA4B,EAC1D,GAAI,CAACwE,EAAO,SACZ,IAAMa,EAAUb,EAAM,CAAC,EAAE,YAAY,EAChCc,GAAkBD,CAAO,GAC9BD,EAAS,IAAIC,CAAO,CACtB,CAEA,QAAWA,KAAWD,EACpB,GAAIjG,GAAa,IAAIkG,CAAO,EAAG,CAE7B,IAAMpF,EAAM,YAAYoF,CAAO,GAC/B,GAAK7C,EAAWvC,CAAG,EAAG,SACtB,IAAMI,EAAW,IAAMkF,GAA0B/C,EAAI6C,CAAO,EAC5D7C,EAAG,iBAAiB6C,EAAShF,CAAQ,EACpCmC,EAAWvC,CAAG,EAAII,EACnB2E,EAAa,KAAK,CAAE,GAAAxC,EAAI,MAAO6C,EAAS,QAAShF,EAAU,SAAUJ,CAAI,CAAC,CAC5E,SAAW,CAAC6E,EAAU,IAAIO,CAAO,EAAG,CAGlC,IAAMG,EAAUH,EAAQ,QAAQ,sBAAuB,MAAM,EACvDI,EAAiB,IAAI,OAAO,mBAAmBD,CAAO,IAAK,GAAG,EAC9DE,EAAWpF,GAAa,CAC5B,IAAIK,EAASL,EAAE,OACf,KAAOK,GAAUA,IAAWT,GAAgB,CAC1C,IAAIyF,EAAW,GACf,QAAWC,KAAKjF,EAAO,WACrB,GAAI8E,EAAe,KAAKG,EAAE,IAAI,EAAG,CAAED,EAAW,GAAM,KAAO,CAE7D,GAAIA,EAAU,CACZJ,GAA0B5E,EAAQ0E,CAAO,EACzC,MACF,CACA1E,EAASA,EAAO,aAClB,CAEIA,IAAWT,GACbqF,GAA0BrF,EAAgBmF,CAAO,CAErD,EACAnF,EAAe,iBAAiBmF,EAASK,CAAO,EAChDZ,EAAU,IAAIO,CAAO,EACrBL,EAAa,KAAK,CAAE,GAAI9E,EAAgB,MAAOmF,EAAS,QAAAK,CAAQ,CAAC,CACnE,CAEJ,EACAP,EAAUD,CAAI,EACdA,EAAK,iBAAiB,GAAG,EAAE,QAAQC,CAAS,EAE3CjF,EAAuB6E,CAAY,EAAIC,EACvC9E,EAAuB2E,CAAY,EAAIC,CAC1C,CAMA,mCAA0C,CACxC,IAAM5E,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAErB,IAAMR,EAAYQ,EAAe,aAAa,aAAa,EAC3D,GAAI,CAACR,EAAW,OAEhB,IAAMqF,EAAe,sBAAsBrF,CAAS,GAC9CmG,EACH3F,EAAuB6E,CAAY,EAClCc,IACFA,EAAU,QAAQ,CAAC,CAAE,GAAArD,EAAI,MAAAsD,EAAO,QAAAJ,EAAS,SAAAK,CAAS,IAAM,CACtDvD,EAAG,oBAAoBsD,EAAOJ,CAAO,EACjCK,GAAU,OAAQvD,EAAWuD,CAAQ,CAC3C,CAAC,EACD,OAAQ7F,EAAuB6E,CAAY,GAG7C,IAAMF,EAAe,sBAAsBnF,CAAS,GACpD,OAAQQ,EAAuB2E,CAAY,CAC7C,CAOA,0BAAiC,CAC/B,IAAM3E,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAGrB,IAAMN,EAAc,oBADFM,EAAe,aAAa,aAAa,CACV,GAC3CL,EAAoB,SAAiBD,CAAW,EAClDC,GACF,SAAS,oBAAoB,UAAWA,CAAgB,EAG1D,IAAMmG,EAAwBC,GAAsC,CAClE,IAAMC,EAAW,CACf,0BACA,yBACA,2BACA,6CACA,yBACA,kDACA,0BACF,EAAE,KAAK,IAAI,EAEX,OAAO,MAAM,KAAKD,EAAU,iBAAiBC,CAAQ,CAAC,EAAE,OACrD1D,GAAO,CACN,IAAM2D,EAAS3D,EAET4D,EAAQ,OAAO,iBAAiBD,CAAM,EACtCE,EAAmBD,EAAM,UAAY,OACrCE,EAAwBF,EAAM,aAAe,SAE7CG,EAAmBJ,EAAO,eAAiB,MACxBC,EAAM,WAAa,SACnBA,EAAM,WAAa,YAElB,OAAO,SAAY,aAAgB,GAC7D,OAAOC,GAAoBC,GAAyBC,CACtD,CACF,CACF,EAEMlG,EAAYC,GAAqB,CACrC,GAAIA,EAAE,MAAQ,MAAO,OAErB,IAAMI,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAGrB,IAAM8F,EAAoB9F,EAAe,iBAAiB,kBAAkB,EACxE+F,EAA6B,KAuBjC,GArBAD,EAAkB,QAASE,GAAS,CAC9BA,EAAK,SAAS,SAAS,aAAa,IAElC,CAACD,GAAcC,EAAK,SAASD,CAAU,KACzCA,EAAaC,EAGnB,CAAC,EAIID,GACHD,EAAkB,QAASE,GAAS,CAClC,IAAMC,EAAWD,EACXN,EAAQ,OAAO,iBAAiBO,CAAQ,EAC1CP,EAAM,UAAY,QAAUA,EAAM,aAAe,WACnDK,EAAaC,EAEjB,CAAC,EAGC,CAACD,EAAY,OAEjB,IAAMG,EAAoBZ,EAAqBS,CAAU,EACzD,GAAIG,EAAkB,SAAW,EAAG,OAEpC,IAAMC,EAAeD,EAAkB,CAAC,EAClCE,EAAcF,EAAkBA,EAAkB,OAAS,CAAC,EAE9DtG,EAAE,SAEA,SAAS,gBAAkBuG,IAC7BvG,EAAE,eAAe,EACjBwG,EAAY,MAAM,GAIhB,SAAS,gBAAkBA,IAC7BxG,EAAE,eAAe,EACjBuG,EAAa,MAAM,EAGzB,EAEC,SAAiBjH,CAAW,EAAIS,EACjC,SAAS,iBAAiB,UAAWA,CAAQ,EAC7C,KAAK,OAAO,MAAM,8BAA8B,CAClD,CAOA,0BAAiC,CAC/B,IAAMH,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAGrB,IAAM6G,EAAc,4BADF7G,EAAe,aAAa,aAAa,CACF,GAGnD8G,EAAoB9G,EAAuB6G,CAAW,EACxDC,GACFA,EAAiB,WAAW,EAG9B,IAAMC,EAAmB,IAAM,CAC7B,IAAMvG,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAGKA,EAAe,iBAAiB,iBAAiB,EAEzD,QAASE,GAAY,CACrC,IAAMsG,EAActG,EACdwF,EAAQ,OAAO,iBAAiBc,CAAW,EAK3Cb,EAAmBD,EAAM,UAAY,OACrCE,EAAwBF,EAAM,aAAe,SAC7CG,EAAmBW,EAAY,eAAiB,MAC7Bd,EAAM,WAAa,SACnBA,EAAM,WAAa,YACnBc,EAAY,UAAY,QAEvB,OAAO,SAAY,aAAgB,GACvDC,EAAYd,GAAoBC,GAAyBC,EAEzDa,EAAaF,EAAY,aAAa,sBAAsB,IAAM,OAEpEC,GAAa,CAACC,GAEhBF,EAAY,aAAa,uBAAwB,MAAM,EAGvD,sBAAsB,IAAM,CAC1BA,EAAY,MAAM,EAClB,KAAK,OAAO,MAAM,uBAAwBA,EAAY,QAASA,EAAY,IAAMA,EAAY,aAAa,MAAM,CAAC,CACnH,CAAC,GACQ,CAACC,GAAaC,GAEvBF,EAAY,gBAAgB,sBAAsB,CAEtD,CAAC,CACH,EAGAD,EAAiB,EAGjB,IAAMI,EAAW,IAAI,iBAAkBC,GAAc,CACnD,IAAIC,EAAgB,GAEpBD,EAAU,QAASE,GAAa,CAE1BA,EAAS,OAAS,aAAeA,EAAS,WAAW,OAAS,GAChEA,EAAS,WAAW,QAASC,GAAS,CAChCA,aAAgB,UACdA,EAAK,aAAa,eAAe,GAAKA,EAAK,cAAc,iBAAiB,KAC5EF,EAAgB,GAGtB,CAAC,EAQCC,EAAS,OAAS,eACLA,EAAS,OACb,aAAa,eAAe,GACnCA,EAAS,gBAAkB,UAC3BA,EAAS,gBAAkB,SAC3BA,EAAS,gBAAkB,WAC7BD,EAAgB,GAGtB,CAAC,EAEGA,GACFN,EAAiB,CAErB,CAAC,EAEDI,EAAS,QAAQnH,EAAgB,CAC/B,UAAW,GACX,QAAS,GACT,WAAY,GACZ,gBAAiB,CAAC,SAAU,QAAS,QAAS,eAAe,CAC/D,CAAC,EAEAA,EAAuB6G,CAAW,EAAIM,EACvC,KAAK,OAAO,MAAM,6BAA6B,CACjD,CAEF,ECxnCO,SAASK,GAAuBC,EAA0B,CAC/D,GAAI,CACF,OAAOA,EAAG,QAAQ,eAAe,CACnC,OAAQ,GACN,MAAO,EACT,CACF,CASA,IAAMC,GAA8B,CAClC,CACE,QAAUD,GAAOA,aAAc,kBAC/B,OAASA,GAAQA,EAAyB,KAC1C,KAAOA,GAAQA,EAAyB,UAAU,EAClD,MAAQA,GAAQA,EAAyB,MAAM,CACjD,EACA,CACE,QAAUA,GACRA,aAAc,aAAeA,EAAG,aAAa,SAAS,EACxD,OAASA,GAAOD,GAAuBC,CAAiB,EACxD,KAAOA,GAAO,CACR,OAAQA,EAAW,aAAgB,YACpCA,EAAmB,YAAY,CACpC,EACA,MAAQA,GAAO,CACT,OAAQA,EAAW,aAAgB,YACpCA,EAAmB,YAAY,CACpC,CACF,EACA,CACE,QAAUA,GAAOA,aAAc,mBAC/B,OAASA,GAAQA,EAA0B,KAC3C,KAAOA,GAAO,CACXA,EAA0B,KAAO,EACpC,EACA,MAAQA,GAAO,CACZA,EAA0B,KAAO,EACpC,CACF,CACF,EAEA,SAASE,EAAYF,EAA0C,CAC7D,OAAOC,GAAS,KAAME,GAAMA,EAAE,QAAQH,CAAE,CAAC,CAC3C,CAEA,IAAMI,GAAgB,IAAI,IAAI,CAAC,aAAc,cAAc,CAAC,EAE5D,SAASC,GAAYC,EAAgB,CACnC,IAAMN,EAAKM,EAAE,OACb,GAAI,CAACN,GAAM,EAAEA,aAAc,SAAU,OAErC,IAAMO,EAASP,EAAG,QAChB,6BACF,EACA,GAAI,CAACO,GAAUA,EAAO,SAAU,OAEhC,IAAMC,EAAUD,EAAO,aAAa,SAAS,EAC7C,GAAI,CAACC,EAAS,OAEd,IAAMC,EAAWF,EAAO,aAAa,YAAY,EACjD,GAAI,CAACE,EAAU,OAEf,IAAMC,EAAS,SAAS,eAAeD,CAAQ,EAC/C,GAAI,CAACC,EAAQ,OAEb,IAAMC,EAAUT,EAAYQ,CAAM,EAClC,GAAKC,GAEL,GAAIP,GAAc,IAAII,CAAO,EAAG,CAC9B,GAAI,SAAS,OAAS,IAAMC,EAAU,OACtC,QAAQ,UAAU,KAAM,GAAI,IAAMA,CAAQ,CAC5C,SAAWD,IAAY,kBAAoB,CAACG,EAAQ,OAAOD,CAAM,EAAG,CAClE,GAAI,SAAS,OAAS,IAAMD,EAAU,OACtC,QAAQ,UAAU,KAAM,GAAI,IAAMA,CAAQ,CAC5C,EACF,CAEA,SAASG,GAAYN,EAAgB,CACnC,IAAMN,EAAKM,EAAE,OACPN,aAAc,SACfA,EAAG,IACHE,EAAYF,CAAE,GACf,SAAS,OAAS,IAAMA,EAAG,IAE/B,QAAQ,aAAa,KAAM,GAAI,SAAS,SAAW,SAAS,MAAM,CACpE,CAEA,SAASa,GAAaP,EAAgB,CACpC,IAAMN,EAAKM,EAAE,OAEb,GADI,EAAEN,aAAc,UAChB,CAACA,EAAG,GAAI,OAEZ,IAAMW,EAAUT,EAAYF,CAAE,EAC9B,GAAKW,EAEL,GAAIA,EAAQ,OAAOX,CAAE,EAAG,CACtB,GAAI,SAAS,OAAS,IAAMA,EAAG,GAAI,OACnC,QAAQ,UAAU,KAAM,GAAI,IAAMA,EAAG,EAAE,CACzC,KAAO,CACL,GAAI,SAAS,OAAS,IAAMA,EAAG,GAAI,OACnC,QAAQ,aAAa,KAAM,GAAI,SAAS,SAAW,SAAS,MAAM,CACpE,CACF,CAEA,SAASc,IAAuB,CAC9B,IAAMC,EAAK,SAAS,KAAK,MAAM,CAAC,EAShC,GALA,SAAS,iBAAiB,mBAAmB,EAAE,QAASf,GAAO,CAC7D,IAAMW,EAAUT,EAAYF,CAAE,EAC1BW,GAAWA,EAAQ,OAAOX,CAAE,GAAKA,EAAG,KAAOe,GAAIJ,EAAQ,MAAMX,CAAE,CACrE,CAAC,EAEGe,EAAI,CACN,IAAMf,EAAK,SAAS,eAAee,CAAE,EACrC,GAAIf,EAAI,CACN,IAAMW,EAAUT,EAAYF,CAAE,EAC1BW,GAAW,CAACA,EAAQ,OAAOX,CAAE,GAAGW,EAAQ,KAAKX,CAAE,CACrD,CACF,CACF,CAEO,SAASgB,IAAqB,CACnC,IAAMD,EAAK,SAAS,KAAK,MAAM,CAAC,EAChC,GAAI,CAACA,EAAI,OAET,IAAMf,EAAK,SAAS,eAAee,CAAE,EACrC,GAAI,CAACf,EAAI,OAET,IAAMW,EAAUT,EAAYF,CAAE,EACzBW,IACDA,EAAQ,OAAOX,CAAE,GAErBW,EAAQ,KAAKX,CAAE,EACjB,CAEO,SAASiB,GAAiBF,EAAqB,CACpD,IAAMf,EAAK,SAAS,eAAee,CAAE,EACrC,OAAKf,EACE,CAAC,CAACE,EAAYF,CAAE,EADP,EAElB,CAEO,SAASkB,GAAmBH,EAAkB,CACnD,IAAMf,EAAK,SAAS,eAAee,CAAE,EACrC,GAAI,CAACf,EAAI,OAET,IAAMW,EAAUT,EAAYF,CAAE,EAC1B,CAACW,GAAWA,EAAQ,OAAOX,CAAE,IAEjC,QAAQ,UAAU,KAAM,GAAI,IAAMe,CAAE,EACpCJ,EAAQ,KAAKX,CAAE,EACjB,CAEA,IAAImB,GAAY,GAET,SAASC,IAAsB,CAChCD,KACJA,GAAY,GAEZ,SAAS,iBAAiB,QAASd,EAAW,EAC9C,SAAS,iBAAiB,QAASO,GAAa,EAAI,EACpD,SAAS,iBAAiB,SAAUC,GAAc,EAAI,EACtD,OAAO,iBAAiB,WAAYC,EAAc,EAElDE,GAAa,EACf,CAGO,SAASK,IAAyB,CAClCF,KACLA,GAAY,GAEZ,SAAS,oBAAoB,QAASd,EAAW,EACjD,SAAS,oBAAoB,QAASO,GAAa,EAAI,EACvD,SAAS,oBAAoB,SAAUC,GAAc,EAAI,EACzD,OAAO,oBAAoB,WAAYC,EAAc,EACvD,CC/JO,IAAMQ,GAAN,KAAsB,CAY3B,YACmBC,EACAC,EACjB,CAFiB,aAAAD,EACA,YAAAC,EAbnB,KAAQ,iBAAwC,KAChD,KAAQ,gBAA0C,KAQlD,KAAQ,YAAsB,OAAO,SAAS,IAK3C,CAWH,mBAAmBC,EAAgC,CA/DrD,IAAAC,EAsEI,IAHAA,EAAA,KAAK,kBAAL,MAAAA,EAAsB,QACtB,KAAK,gBAAkB,KAEnB,CAACD,EAAW,OAChB,IAAME,EAAc,wBAAwBF,CAAS,GAC/CG,EAAY,SAAiBD,CAAW,EAC1CC,IAIF,SAAS,oBAAoB,QAASA,EAAU,EAAK,EACrD,OAAQ,SAAiBD,CAAW,EAExC,CAEA,MAAME,EAAwB,CAK5B,KAAK,YAAc,OAAO,SAAS,KAGnC,IAAMF,EAAc,wBADFE,EAAQ,aAAa,aAAa,CACC,GAC/CD,EAAY,SAAiBD,CAAW,EAC1CC,GACF,SAAS,oBAAoB,QAASA,CAAQ,EAGhD,IAAME,EAAYC,GAAa,CAhGnC,IAAAL,EAiGM,IAAMM,GAAUN,EAAAK,EAAE,SAAF,YAAAL,EAAsB,QAAQ,WAC9C,GAAI,CAACM,EAAQ,OAEb,IAAMC,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,GAACA,GAAkB,CAACA,EAAe,SAASD,CAAM,IAElD,MAAK,WAAWA,CAAM,EAE1B,IAAIA,EAAO,WAAa,OAAO,SAAS,UAAYA,EAAO,SAAW,OAAO,SAAS,QAAUA,EAAO,KAAM,CAC3G,IAAME,EAASF,EAAO,KAAK,MAAM,CAAC,EAC9BE,GAAUC,GAAiBD,CAAM,IACnCH,EAAE,eAAe,EACjBK,GAAmBF,CAAM,GAE3B,MACF,CAEAH,EAAE,eAAe,EACjB,KAAK,SAASC,EAAO,IAAI,EAC3B,EAEA,SAAS,iBAAiB,QAASF,CAAQ,EAC1C,SAAiBH,CAAW,EAAIG,EAG5B,KAAK,mBACR,KAAK,iBAAmB,IAAM,CAK5B,IAAMO,EAAW,KAAK,YACtB,KAAK,YAAc,OAAO,SAAS,KACnC,KAAK,SAAS,OAAO,SAAS,KAAM,GAAOA,CAAQ,CACrD,EACA,OAAO,iBAAiB,WAAY,KAAK,gBAAgB,EAE7D,CAEQ,WAAWC,EAAkC,CAUnD,GARIA,EAAK,SAAW,OAAO,SAAS,QAEhCA,EAAK,QAAUA,EAAK,SAAW,SAE/BA,EAAK,aAAa,UAAU,GAI5BC,GAAqBD,EAAM,uBAAwB,KAAK,MAAM,EAAG,MAAO,GAE5E,IAAME,EAAWF,EAAK,SACtB,OAAIE,IAAa,SAAWA,IAAa,QAG3C,CAQA,MAAc,SACZC,EACAC,EAAqB,GACrBL,EAAmB,OAAO,SAAS,KACpB,CApKnB,IAAAX,EAAAiB,EAAAC,EAqKI,IAAMC,EAAY,IAAI,IAAIJ,EAAM,OAAO,SAAS,MAAM,EAChDK,EAAS,IAAI,IAAIT,EAAU,OAAO,SAAS,MAAM,EAKvD,GAHEQ,EAAU,SAAWC,EAAO,QAC5BD,EAAU,WAAaC,EAAO,SAElB,CAEZ,GADmBD,EAAU,SAAWC,EAAO,OAC/B,EAUdpB,EAAA,KAAK,kBAAL,MAAAA,EAAsB,QACtB,KAAK,gBAAkB,KACvB,MACF,CAQA,GAAIgB,GAAa,KAAK,QAAQ,gBAAgB,KAM5CC,EAAA,KAAK,kBAAL,MAAAA,EAAsB,QACtB,KAAK,gBAAkB,KAOV,KAAK,QAAQ,aAAaF,CAAI,GACjC,CACR,OAAO,QAAQ,UAAU,KAAM,GAAIA,CAAI,EACvC,KAAK,YAAcA,EACnB,MACF,CAKJ,EAGAG,EAAA,KAAK,kBAAL,MAAAA,EAAsB,QACtB,KAAK,gBAAkB,IAAI,gBAE3B,GAAI,CACF,IAAMG,EAAW,MAAM,MAAMN,EAAM,CACjC,YAAa,UACb,QAAS,CAAE,OAAQ,WAAY,EAC/B,OAAQ,KAAK,gBAAgB,MAC/B,CAAC,EAED,GAAI,CAACM,EAAS,GAAI,CAChB,OAAO,SAAS,KAAON,EACvB,MACF,CAEA,IAAMO,EAAO,MAAMD,EAAS,KAAK,EAK7BL,GACF,OAAO,QAAQ,UAAU,KAAM,GAAID,CAAI,EAGzC,KAAK,YAAcA,EACnB,KAAK,QAAQ,yBAAyBO,EAAMP,CAAI,CAClD,OAASV,EAAY,CAEnB,GAAIA,aAAa,cAAgBA,EAAE,OAAS,aAAc,OAC1D,OAAO,SAAS,KAAOU,CACzB,CACF,CACF,ECnPO,IAAMQ,GAAN,MAAMA,EAAgB,CA0B3B,YACmBC,EACAC,EACjB,CAFiB,aAAAD,EACA,YAAAC,EA3BnB,KAAQ,uBAAsD,KAC9D,KAAQ,iBAA4C,KACpD,KAAQ,iBAAmC,KAC3C,KAAQ,gBAA+C,KACvD,KAAQ,uBAAyC,KAWjD,KAAQ,gBAAkB,GAO1B,KAAQ,kBAAmC,IAMxC,CAEH,6BAAoC,CAClC,IAAMC,EAAiB,KAAK,QAAQ,kBAAkB,EACtD,GAAI,CAACA,EAAgB,OAMrB,KAAK,sBAAsBA,CAAc,EAEzC,IAAMC,EAAWD,EAAe,cAAc,uBAAuB,EACrE,GAAI,CAACC,EAAU,CAQT,KAAK,yBACP,KAAK,uBAAuB,WAAW,EACvC,KAAK,uBAAyB,KAC9B,KAAK,iBAAmB,MAE1B,KAAK,gBAAgB,EACrB,MACF,CAII,KAAK,wBAA0B,KAAK,mBAAqBA,IAIzD,KAAK,wBACP,KAAK,uBAAuB,WAAW,EAGzC,KAAK,uBAAyB,IAAI,qBAC/BC,GAAY,CACX,GAAKA,EAAQ,CAAC,EAAE,eAChB,IAAI,KAAK,gBAAiB,CACxB,KAAK,OAAO,MAAM,0DAA0D,EAC5E,MACF,CACA,KAAK,gBAAkB,GACvB,KAAK,mBAAmB,EACxB,KAAK,OAAO,MAAM,4CAA4C,EAC9D,KAAK,QAAQ,KAAK,CAAE,OAAQ,WAAY,CAAC,EAC3C,EACA,CACE,WAAY,OACd,CACF,EAEA,KAAK,uBAAuB,QAAQD,CAAQ,EAC5C,KAAK,iBAAmBA,EACxB,KAAK,OAAO,MAAM,8BAA8B,EAClD,CAEQ,sBAAsBE,EAAwB,CAChD,KAAK,iBAAmB,KAAK,yBAA2BA,IAGxD,KAAK,iBAAmB,KAAK,wBAC/B,KAAK,uBAAuB,oBAAoB,cAAe,KAAK,eAAe,EAErF,KAAK,gBAAmBC,GAAa,CACnC,IAAMC,EAAUD,EAAkB,QAC9BC,GAAA,YAAAA,EAAQ,UAAW,cACvB,KAAK,gBAAgB,EAKrB,KAAK,iBAAmB,KACxB,KAAK,4BAA4B,EACnC,EACAF,EAAQ,iBAAiB,cAAe,KAAK,eAAe,EAC5D,KAAK,uBAAyBA,EAChC,CAEQ,oBAA2B,CACjC,KAAK,qBAAqB,EAC1B,KAAK,kBAAoB,OAAO,WAAW,IAAM,CAC/C,KAAK,OAAO,KACV,0CAA0CN,GAAgB,oBAAoB,wBAChF,EACA,KAAK,kBAAoB,KACzB,KAAK,gBAAkB,GAGvB,KAAK,iBAAmB,KACxB,KAAK,4BAA4B,CACnC,EAAGA,GAAgB,oBAAoB,CACzC,CAEQ,iBAAwB,CAC9B,KAAK,gBAAkB,GACvB,KAAK,qBAAqB,CAC5B,CAEQ,sBAA6B,CAC/B,KAAK,oBAAsB,OAC7B,aAAa,KAAK,iBAAiB,EACnC,KAAK,kBAAoB,KAE7B,CAEA,qCAA4C,CAC1C,IAAMG,EAAiB,KAAK,QAAQ,kBAAkB,EACjDA,IAED,KAAK,kBACP,KAAK,iBAAiB,WAAW,EAOnC,KAAK,iBAAmB,IAAI,iBAAiB,IAAM,CACjD,KAAK,4BAA4B,CACnC,CAAC,EAED,KAAK,iBAAiB,QAAQA,EAAgB,CAC5C,UAAW,GACX,QAAS,EACX,CAAC,EAED,KAAK,OAAO,MAAM,sCAAsC,EAC1D,CAEA,UAAiB,CACX,KAAK,yBACP,KAAK,uBAAuB,WAAW,EACvC,KAAK,uBAAyB,MAE5B,KAAK,mBACP,KAAK,iBAAiB,WAAW,EACjC,KAAK,iBAAmB,MAEtB,KAAK,iBAAmB,KAAK,wBAC/B,KAAK,uBAAuB,oBAAoB,cAAe,KAAK,eAAe,EAErF,KAAK,gBAAkB,KACvB,KAAK,uBAAyB,KAC9B,KAAK,iBAAmB,KACxB,KAAK,gBAAgB,CACvB,CACF,EArLaH,GAwBa,qBAAuB,IAxB1C,IAAMS,GAANT,GCCA,IAAMU,GAAN,KAAuB,CAAvB,cACL,KAAQ,IAA0B,KAClC,KAAQ,YAAoD,KAM5D,KAAQ,aAAe,EACvB,KAAQ,eAA+C,KACvD,KAAQ,eAA+C,KAIvD,KAAQ,kBAAmC,KAE3C,MAAa,CACX,GAAI,KAAK,IAAK,OAEd,IAAMC,EAAM,SAAS,cAAc,KAAK,EAcxC,GAbAA,EAAI,UAAY,kBAChBA,EAAI,MAAM,QAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYhB,CAAC,SAAS,eAAe,oBAAoB,EAAG,CAClD,IAAMC,EAAQ,SAAS,cAAc,OAAO,EAC5CA,EAAM,GAAK,qBACXA,EAAM,YAAc;AAAA;AAAA;AAAA;AAAA;AAAA,QAMpB,SAAS,KAAK,YAAYA,CAAK,CACjC,CAEA,SAAS,KAAK,aAAaD,EAAK,SAAS,KAAK,UAAU,EACxD,KAAK,IAAMA,CACb,CAEA,MAAa,CACN,KAAK,MAEN,KAAK,IAAI,YACX,KAAK,IAAI,WAAW,YAAY,KAAK,GAAG,EAE1C,KAAK,IAAM,KACb,CAQA,yBAAyBE,EAA0B,CAMjD,GAAI,KAAK,eAAgB,CACvB,GAAI,KAAK,oBAAsBA,EAAY,OAC3C,KAAK,0BAA0B,CACjC,CACA,KAAK,aAAe,EACpB,KAAK,kBAAoBA,EAEzB,KAAK,eAAiB,IAAM,CAC1B,KAAK,eAMH,KAAK,eAAiB,GACtB,KAAK,cAAgB,MACrB,KAAK,MAAQ,OAEb,KAAK,YAAc,WAAW,IAAM,CAClC,KAAK,YAAc,KAMf,KAAK,aAAe,GAAG,KAAK,KAAK,CACvC,EAAGA,CAAU,EAEjB,EACA,KAAK,eAAiB,IAAM,CAI1B,KAAK,aAAe,KAAK,IAAI,EAAG,KAAK,aAAe,CAAC,EACjD,KAAK,eAAiB,IACpB,KAAK,cAAgB,OACvB,aAAa,KAAK,WAAW,EAC7B,KAAK,YAAc,MAErB,KAAK,KAAK,EAEd,EACA,SAAS,iBAAiB,cAAe,KAAK,eAAgB,EAAI,EAClE,SAAS,iBAAiB,cAAe,KAAK,eAAgB,EAAI,CACpE,CAcA,2BAAkC,CAC5B,KAAK,iBACP,SAAS,oBAAoB,cAAe,KAAK,eAAgB,EAAI,EACrE,KAAK,eAAiB,MAEpB,KAAK,iBACP,SAAS,oBAAoB,cAAe,KAAK,eAAgB,EAAI,EACrE,KAAK,eAAiB,MAEpB,KAAK,cAAgB,OACvB,aAAa,KAAK,WAAW,EAC7B,KAAK,YAAc,MAErB,KAAK,aAAe,EACpB,KAAK,kBAAoB,KACzB,KAAK,KAAK,CACZ,CACF,ECzJO,IAAMC,GAAN,KAAmB,CACxB,QAAQC,EAA+B,CACrC,GAAI,CAACA,EAAS,OAEAA,EAAQ,iBAAiB,MAAM,EACvC,QAASC,GAAS,CACPA,EAAK,iBAAiB,iCAAiC,EAC/D,QAASC,GAAU,CACvBA,EAA2B,SAAW,EACzC,CAAC,CACH,CAAC,CACH,CAEA,OAAOF,EAA+B,CACpC,GAAI,CAACA,EAAS,OAEAA,EAAQ,iBAAiB,MAAM,EACvC,QAASC,GAAS,CACPA,EAAK,iBAAiB,iCAAiC,EAC/D,QAASC,GAAU,CACvBA,EAA2B,SAAW,EACzC,CAAC,CACH,CAAC,CACH,CACF,EClBA,IAAIC,GAAY,GAEhB,SAASC,GAAYC,EAAgB,CACnC,IAAMC,EAAKD,EAAE,OACb,GAAI,CAACC,GAAM,EAAEA,aAAc,SAAU,OAErC,IAAMC,EAASD,EAAG,QAAQ,oBAAoB,EAC9C,GAAI,CAACC,GAAUA,EAAO,SAAU,OAEhC,IAAMC,EAAWD,EAAO,aAAa,YAAY,EACjD,GAAI,CAACC,EAAU,OAEf,IAAMC,EAAUF,EAAO,aAAa,SAAS,EACvCG,EAAS,SAAS,eAAeF,CAAQ,EAC1CE,IAEDA,aAAkB,kBAChBD,IAAY,cAAgB,CAACC,EAAO,KACtCA,EAAO,UAAU,EACRD,IAAY,SAAWC,EAAO,MACvCA,EAAO,MAAM,EAENA,aAAkB,aAAeA,EAAO,aAAa,SAAS,IACnED,IAAY,gBAAkB,OAAOC,EAAO,aAAgB,WAC9DA,EAAO,YAAY,EACVD,IAAY,gBAAkB,OAAOC,EAAO,aAAgB,WACrEA,EAAO,YAAY,EACVD,IAAY,kBAAoB,OAAOC,EAAO,eAAkB,YACzEA,EAAO,cAAc,GAG3B,CAEO,SAASC,IAA6B,CACvC,sBAAuB,kBAAkB,WACzCR,KAEJA,GAAY,GACZ,SAAS,iBAAiB,QAASC,EAAW,EAChD,CCxCA,IAAMQ,GAAY,oBAEZC,EAAsC,CAAC,EAE7C,SAASC,IAAkC,CACzC,QAAS,EAAID,EAAe,OAAS,EAAG,GAAK,EAAG,IAAK,CACnD,IAAME,EAAUF,EAAe,CAAC,EAC5BE,EAAQ,QAAQ,cACpBA,EAAQ,OAAO,oBAAoB,SAAUA,EAAQ,OAAO,EAC5D,OAAQA,EAAQ,QAAgBH,EAAS,EACzCC,EAAe,OAAO,EAAG,CAAC,EAC5B,CACF,CAEO,SAASG,GAAgBC,EAAyB,CACvDH,GAA0B,EAE1B,IAAMI,EAAaC,GAAgB,CACjC,IAAMC,EAAOD,EAAG,aAAa,iBAAiB,EAC9C,GAAI,CAACC,EAAM,OACX,GAAIA,IAAS,UAAYA,IAAS,MAAO,CACvC,QAAQ,KAAK,iCAAiCA,CAAI,EAAE,EACpD,MACF,CAEA,IAAMC,EAASC,EAAcH,CAAE,EAC/B,GAAI,CAACE,GAAUA,IAAWF,EAAI,CAC5B,IAAMI,EAAYJ,EAAWP,EAAS,EAClCW,IACFA,EAAS,OAAO,oBAAoB,SAAUA,EAAS,OAAO,EAC9DC,GAAcD,CAAQ,EACtB,OAAQJ,EAAWP,EAAS,GAE9B,QAAQ,KAAK,6EAA6E,EAC1F,MACF,CAEA,IAAMW,EAAYJ,EAAWP,EAAS,EACtC,GAAIW,EAAU,CACZ,GAAIA,EAAS,SAAWF,EAAQ,OAChCE,EAAS,OAAO,oBAAoB,SAAUA,EAAS,OAAO,EAC9DC,GAAcD,CAAQ,CACxB,CAEA,IAAME,EAAM,SACV,iBAAiBN,CAAE,EAAE,iBAAiB,wBAAwB,EAAE,KAAK,EAAG,EAC1E,EACMO,EAAY,MAAMD,CAAG,EAAI,IAAMA,EAEjCE,EAAU,GACRC,EAAU,IAAM,CAChBD,IACJA,EAAU,GACV,sBAAsB,IAAM,CAC1BA,EAAU,IACOP,IAAS,MACtBC,EAAO,UACPA,EAAO,aAAeA,EAAO,UAAYA,EAAO,cACrCK,EACbP,EAAG,UAAU,IAAI,SAAS,EAE1BA,EAAG,UAAU,OAAO,SAAS,CAEjC,CAAC,EACH,EAEAE,EAAO,iBAAiB,SAAUO,EAAS,CAAE,QAAS,EAAK,CAAC,EAC5DA,EAAQ,EAER,IAAMb,EAA6B,CAAE,QAASI,EAAI,OAAAE,EAAQ,QAAAO,CAAQ,EACjET,EAAWP,EAAS,EAAIG,EACzBF,EAAe,KAAKE,CAAO,CAC7B,EAEAG,EAAUD,CAAQ,EAClBA,EAAS,iBAAiB,mBAAmB,EAAE,QAAQC,CAAS,CAClE,CAEA,SAASM,GAAcT,EAAkC,CACvD,IAAMc,EAAMhB,EAAe,QAAQE,CAAO,EACtCc,IAAQ,IAAIhB,EAAe,OAAOgB,EAAK,CAAC,CAC9C,CAEO,SAASC,GAAmBC,EAAyB,CAC1D,QAASC,EAAInB,EAAe,OAAS,EAAGmB,GAAK,EAAGA,IAAK,CACnD,IAAMjB,EAAUF,EAAemB,CAAC,EAC5BD,GAAWhB,EAAQ,QAAQ,aAAe,CAACgB,EAAQ,SAAShB,EAAQ,OAAO,IAC/EA,EAAQ,OAAO,oBAAoB,SAAUA,EAAQ,OAAO,EAC5DA,EAAQ,QAAQ,UAAU,OAAO,SAAS,EAC1C,OAAQA,EAAQ,QAAgBH,EAAS,EACzCC,EAAe,OAAOmB,EAAG,CAAC,EAC5B,CACF,CClEA,IAAMC,GAAe,aACfC,GAAc,YACdC,GAAmB,yBA+BnBC,EAA+B,CAAC,EAOhCC,GAAkB,IAAI,QAE5B,SAASC,IAAkC,CACzC,QAAS,EAAIF,EAAe,OAAS,EAAG,GAAK,EAAG,IAAK,CACnD,IAAMG,EAAIH,EAAe,CAAC,EACtBG,EAAE,UAAU,cAChBC,GAAOD,CAAC,EACRH,EAAe,OAAO,EAAG,CAAC,EAC5B,CAMA,GAAIA,EAAe,SAAW,EAAG,CAC/B,IAAMK,EAAW,SAAiBN,EAAgB,EAC9CM,IACF,SAAS,oBAAoB,QAASA,CAAO,EAC7C,OAAQ,SAAiBN,EAAgB,EAE7C,CACF,CAUA,SAASK,GAAOD,EAAqB,CACnCG,GAAYH,EAAG,IAAI,EACnBA,EAAE,aAAa,oBACb,SACAA,EAAE,aACJ,EACA,OAAO,oBAAoB,SAAUA,EAAE,aAA8B,EACrE,OAAQA,EAAE,UAAkBL,EAAW,CACzC,CAEA,SAASS,GAAaC,EAA4B,CAChD,IAAMC,EAAM,iBAAiBD,CAAS,EAAE,iBAAiB,kBAAkB,EAAE,KAAK,EAC5EE,EAAW,KAAK,MAAM,OAAO,YAAc,GAAI,EACrD,GAAI,CAACD,EAAK,OAAOC,EACjB,IAAM,EAAI,WAAWD,CAAG,EACxB,OAAI,MAAM,CAAC,EAAUC,EAMjBD,EAAI,SAAS,IAAI,EAAU,KAAK,MAAO,EAAI,IAAO,OAAO,WAAW,EAIpEA,EAAI,SAAS,IAAI,GAAK,kBAAkB,KAAKA,CAAG,EAAU,GAC9D,QAAQ,KACN,8CAA8C,KAAK,UAAUA,CAAG,CAAC,sEACnE,EACOC,EACT,CAEA,SAASC,GAAeH,EAA+B,CACrD,IAAMI,EAAWJ,EAAU,aAAa,SAAS,EACjD,GAAII,GAAYA,EAAS,KAAK,IAAM,GAMlC,GAAI,CACF,OAAO,MAAM,KAAKJ,EAAU,iBAAiBI,CAAQ,CAAC,CACxD,OAASC,EAAG,CACV,eAAQ,KAAK,6BAA6B,KAAK,UAAUD,CAAQ,CAAC,IAAKC,CAAC,EACjE,CAAC,CACV,CAEF,MAAO,CAACL,CAAS,CACnB,CAaA,SAASF,GAAYQ,EAAqBC,EAA+B,CACvE,QAAWC,KAAQF,EAAQ,MAAO,CAChC,IAAMG,EAAOD,EAAK,aAAa,MAAM,GAAK,GACpCE,EAAKD,EAAK,WAAW,GAAG,EAAIA,EAAK,MAAM,CAAC,EAAI,GAC9CF,IAAa,MAAQG,IAAOH,EAC9BC,EAAK,UAAU,IAAInB,EAAY,EAE/BmB,EAAK,UAAU,OAAOnB,EAAY,CAEtC,CACF,CAMA,SAASsB,GAAaL,EAA2B,CAC/C,IAAMM,EAAS,IAAI,IACnB,QAAWC,KAAKP,EAAQ,QAClBO,EAAE,IAAID,EAAO,IAAIC,EAAE,EAAE,EAE3B,IAAMC,EAAqB,CAAC,EAC5B,SAAS,iBAAiB,gBAAgB,EAAE,QAASN,GAAS,CAC5D,IAAMC,EAAOD,EAAK,aAAa,MAAM,GAAK,GACpCE,EAAKD,EAAK,WAAW,GAAG,EAAIA,EAAK,MAAM,CAAC,EAAI,GAC9CG,EAAO,IAAIF,CAAE,GAAGI,EAAQ,KAAKN,CAAI,CACvC,CAAC,EACDF,EAAQ,MAAQQ,CAClB,CAMA,SAASC,GAAiBL,EAA+B,CACvD,QAAWf,KAAKH,EACd,QAAW,KAAKG,EAAE,QAChB,GAAI,EAAE,KAAOe,EAAI,OAAOf,EAG5B,OAAO,IACT,CAEA,SAASqB,GAAWC,EAAoBC,EAAiC,CACvE,IAAIX,EAA0B,KAQ9B,QAAWM,KAAKI,EAAS,CACvB,GAAI,CAACJ,EAAE,GAAI,SACCA,EAAE,sBAAsB,EAAE,KAC3BK,IACTX,EAAWM,EAAE,GAEjB,CACA,OAAON,CACT,CAEA,SAASY,GAAiBC,EAAmC,CAK3D,IAAIC,EAAsBD,EAAG,cAC7B,KAAOC,GAAOA,IAAQ,SAAS,iBAAiB,CAC9C,IAAMC,EAAK,iBAAiBD,CAAG,EAAE,UAMjC,GAAIC,IAAO,QAAUA,IAAO,UAAYA,IAAO,UAAW,OAAOD,EACjEA,EAAMA,EAAI,aACZ,CACA,OAAO,MACT,CAEA,SAASE,GAAYC,EAAc7B,EAAuB,CACxD,GAAI6B,EAAE,SAAW7B,EAAE,OAAQ,MAAO,GAClC,QAAS8B,EAAI,EAAGA,EAAID,EAAE,OAAQC,IAC5B,GAAID,EAAEC,CAAC,IAAM9B,EAAE8B,CAAC,EAAG,MAAO,GAE5B,MAAO,EACT,CAEA,SAASC,GAAO1B,EAAoB2B,EAAgC,CAKlE,IAAMV,EAAUU,GAAA,KAAAA,EAAgBxB,GAAeH,CAAS,EACxD,GAAIiB,EAAQ,SAAW,EAAG,OAW1B,IAAMW,EAAYX,EAAQ,OAAQJ,GAAM,CAACA,EAAE,IAAM,CAACpB,GAAgB,IAAIoB,CAAC,CAAC,EACxE,GAAIe,EAAU,OAAS,EAAG,CACxB,QAAQ,KACN,YAAYA,EAAU,MAAM,8IAC5BA,EAAU,CAAC,CACb,EACA,QAAWf,KAAKe,EAAWnC,GAAgB,IAAIoB,CAAC,CAClD,CAEA,IAAMP,EAAsB,CAC1B,UAAAN,EACA,QAAAiB,EACA,SAAUlB,GAAaC,CAAS,EAChC,MAAO,CAAC,EACR,aAAcmB,GAAiBnB,CAAS,EAGxC,cAAe,IAAM,CAAC,EACtB,cAAe,IAAM,CAAC,CACxB,EACAW,GAAaL,CAAO,EAEpB,IAAIuB,EAAU,GACdvB,EAAQ,cAAgB,IAAM,CACxBuB,IACJA,EAAU,GACV,sBAAsB,IAAM,CAC1BA,EAAU,GACV/B,GAAYQ,EAASU,GAAWV,EAAQ,QAASA,EAAQ,QAAQ,CAAC,CACpE,CAAC,EACH,EACAA,EAAQ,cAAgB,IAAM,CAI5BA,EAAQ,SAAWP,GAAaO,EAAQ,SAAS,EACjDR,GAAYQ,EAASU,GAAWV,EAAQ,QAASA,EAAQ,QAAQ,CAAC,CACpE,EAEAA,EAAQ,aAAa,iBAAiB,SAAUA,EAAQ,cAAgC,CAAE,QAAS,EAAK,CAAC,EACzG,OAAO,iBAAiB,SAAUA,EAAQ,cAAgC,CAAE,QAAS,EAAK,CAAC,EAE1FN,EAAkBV,EAAW,EAAIgB,EAClCd,EAAe,KAAKc,CAAO,EAI3BR,GAAYQ,EAASU,GAAWV,EAAQ,QAASA,EAAQ,QAAQ,CAAC,CACpE,CAEA,SAASwB,GAAiB9B,EAA0B,CAClD,IAAM+B,EAAY/B,EAAkBV,EAAW,EAC/C,GAAIyC,EAAU,CACZ,IAAMC,EAAQ7B,GAAeH,CAAS,EACtC,GAAIuB,GAAYQ,EAAS,QAASC,CAAK,EAAG,CAKxCD,EAAS,SAAWhC,GAAaC,CAAS,EAK1CW,GAAaoB,CAAQ,EACrBjC,GAAYiC,EAAUf,GAAWe,EAAS,QAASA,EAAS,QAAQ,CAAC,EACrE,MACF,CACAnC,GAAOmC,CAAQ,EACf,IAAME,EAAMzC,EAAe,QAAQuC,CAAQ,EACvCE,IAAQ,IAAIzC,EAAe,OAAOyC,EAAK,CAAC,EAG5CP,GAAO1B,EAAWgC,CAAK,EACvB,MACF,CACAN,GAAO1B,CAAS,CAClB,CAEA,SAASkC,IAAgC,CACvC,GAAK,SAAiB3C,EAAgB,EAAG,OAMzC,IAAMM,EAAW,GAAa,CAtWhC,IAAAsC,EAuWI,IAAM3B,GAAQ2B,EAAA,EAAE,SAAF,YAAAA,EAA6B,QAAQ,kBACnD,GAAI,CAAC3B,EAAM,OACX,IAAMC,EAAOD,EAAK,aAAa,MAAM,GAAK,GACpCE,EAAKD,EAAK,WAAW,GAAG,EAAIA,EAAK,MAAM,CAAC,EAAI,GAClD,GAAI,CAACC,EAAI,OAIT,IAAM0B,EAAQrB,GAAiBL,CAAE,EAC7B0B,GAAOtC,GAAYsC,EAAO1B,CAAE,CAClC,EAKA,SAAS,iBAAiB,QAASb,EAAS,CAAE,QAAS,EAAK,CAAC,EAC5D,SAAiBN,EAAgB,EAAIM,CACxC,CAEO,SAASwC,GAASC,EAAyB,CAChD5C,GAA0B,EAC1BwC,GAAwB,EAEpBI,EAAS,aAAa,SAAS,GACjCR,GAAiBQ,CAAQ,EAE3BA,EAAS,iBAAiB,WAAW,EAAE,QAAQR,EAAgB,CACjE,CAEO,SAASS,GAAYC,EAAyB,CAInD,QAASf,EAAIjC,EAAe,OAAS,EAAGiC,GAAK,EAAGA,IAAK,CACnD,IAAM9B,EAAIH,EAAeiC,CAAC,EACtBe,GAAW7C,EAAE,UAAU,aAAe,CAAC6C,EAAQ,SAAS7C,EAAE,SAAS,IAGvEC,GAAOD,CAAC,EACRH,EAAe,OAAOiC,EAAG,CAAC,EAC5B,CAQA,GAAIjC,EAAe,SAAW,EAAG,CAC/B,IAAMK,EAAW,SAAiBN,EAAgB,EAC9CM,IACF,SAAS,oBAAoB,QAASA,CAAO,EAC7C,OAAQ,SAAiBN,EAAgB,EAE7C,CACF,CCnYA,SAASkD,GAAaC,EAAW,CAC/B,OAAI,OAAO,iBAAoB,WACtB,gBAAgBA,CAAG,EAErB,KAAK,MAAM,KAAK,UAAUA,CAAG,CAAC,CACvC,CAeA,SAASC,GAAYC,EAAoB,CACvC,OACEA,GAAQ,MACR,OAAOA,GAAS,UAChB,MAAM,QAAQA,EAAK,CAAC,GACpB,MAAM,QAAQA,EAAK,CAAC,CAExB,CAeA,SAASC,GAAiBD,EAAWE,EAAQ,EAAY,CAGvD,GAAIA,EAAQ,IAAaF,GAAQ,MAAQ,OAAOA,GAAS,UAAY,MAAM,QAAQA,CAAI,EACrF,MAAO,GAIT,GAAID,GAAYC,CAAI,EAClB,MAAO,GAIT,QAAWG,KAAO,OAAO,KAAKH,CAAI,EAChC,GAAI,QAAQ,KAAKG,CAAG,EAAG,CACrB,IAAMC,EAAQJ,EAAKG,CAAG,EACtB,GAAIC,GAAS,MAAQ,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,GAChEH,GAAiBG,EAAOF,EAAQ,CAAC,EACnC,MAAO,EAGb,CAGF,MAAO,EACT,CAcA,SAASG,GAAkBC,EAAeC,EAAsB,CAE9D,GAAI,CAACA,EAAO,GAAK,CAAC,MAAM,QAAQA,EAAO,CAAC,EACtC,MAAO,GAIT,GAAIN,GAAiBK,CAAQ,GAAK,CAACL,GAAiBM,CAAM,EACxD,MAAO,GAIT,QAAWJ,KAAO,OAAO,KAAKG,CAAQ,EACpC,GAAI,QAAQ,KAAKH,CAAG,GAAK,EAAEA,KAAOI,GAGhC,MAAO,GAIX,MAAO,EACT,CAKO,IAAMC,GAAN,KAAmB,CAKxB,YAA6BC,EAAgB,CAAhB,YAAAA,EAJ7B,KAAQ,UAAsB,CAAC,EAC/B,KAAQ,WAA8C,CAAC,EACvD,KAAQ,YAAsC,CAAC,CAED,CAE9C,YACEF,EACAG,EACc,CAhJlB,IAAAC,EAAAC,EAiJI,IAAIC,EAAU,GACRC,EAAiC,CAAC,EAClCC,EAAY,IAAI,IAEtB,OAAW,CAACZ,EAAKa,CAAK,IAAK,OAAO,QAAQT,CAAM,EAO9C,GALE,MAAM,QAAQS,CAAK,GACnBA,EAAM,OAAS,GACf,MAAM,QAAQA,EAAM,CAAC,CAAC,GACtB,OAAOA,EAAM,CAAC,EAAE,CAAC,GAAM,SAEF,CAErB,IAAMV,EAAW,KAAK,UAAUH,CAAG,EAEjCG,GACA,OAAOA,GAAa,UACpB,CAAC,MAAM,QAAQA,CAAQ,GACvB,MAAM,QAAQA,EAAS,CAAC,GACxB,MAAM,QAAQA,EAAS,CAAC,IAItBK,EAAAD,GAAA,YAAAA,EAAM,mBAAN,YAAAC,EAAA,KAAAD,EAAyBJ,EAAUH,MAAS,IAK5C,KAAK,4BAA4BG,EAAUU,EAAOb,CAAG,EACrDW,EAAY,KAAK,CACf,UAAWX,EACX,IAAKa,EACL,QAASV,EAAS,EAClB,OAAOM,EAAAN,EAAS,IAAT,YAAAM,EAAY,KACrB,CAAC,EACDG,EAAU,IAAIZ,CAAG,IAGjB,KAAK,UAAUA,CAAG,EAAIN,GAAUS,CAAQ,EACxC,KAAK,4BAA4B,KAAK,UAAUH,CAAG,EAAGa,EAAOb,CAAG,GAIlE,KAAK,UAAUA,CAAG,EAAIa,EAExBH,EAAU,EACZ,KAAO,CACL,IAAMI,EAAW,KAAK,UAAUd,CAAG,EAC7Be,EACJ,OAAOF,GAAU,UAAYA,IAAU,MAAQ,CAAC,MAAM,QAAQA,CAAK,EAC/D,KAAK,mBAAmBC,EAAUD,EAAOb,CAAG,EAC5Ca,EAEF,KAAK,UAAUC,CAAQ,IAAM,KAAK,UAAUC,CAAQ,IACtD,KAAK,UAAUf,CAAG,EAAIe,EACtBL,EAAU,GAEd,CAQF,IAAMM,EAAuB,CAAE,KALlB,KAAK,oBAChB,KAAK,UACL,GACAJ,EAAU,KAAO,EAAIA,EAAY,MACnC,EACqC,QAAAF,CAAQ,EAC7C,OAAIC,EAAY,OAAS,IACvBK,EAAO,YAAcL,GAEhBK,CACT,CAEA,OAAc,CACZ,KAAK,UAAY,CAAC,EAClB,KAAK,WAAa,CAAC,EACnB,KAAK,YAAc,CAAC,CACtB,CAEA,cAAyB,CACvB,MAAO,CAAE,GAAG,KAAK,SAAU,CAC7B,CAEA,oBAAsC,CACpC,OAAO,KAAK,UAAU,GAAK,IAC7B,CAQA,aAAsB,CACpB,OAAO,KAAK,oBAAoB,KAAK,UAAW,EAAE,CACpD,CAEQ,mBACNb,EACAC,EACAa,EAAsB,GACjB,CArPT,IAAAT,EA8PI,GAPE,OAAOJ,GAAW,UAClBA,IAAW,MACX,MAAM,QAAQA,CAAM,GAMpB,OAAOD,GAAa,UACpBA,IAAa,MACb,MAAM,QAAQA,CAAQ,EAEtB,OAAOC,EAWT,GAAIF,GAAkBC,EAAUC,CAAM,EACpC,YAAK,OAAO,MACV,4CAA4Ca,CAAW,gCACzD,EACOb,EAGT,IAAMc,EAAc,CAAE,GAAGf,CAAS,EAElC,OAAW,CAACH,EAAKa,CAAK,IAAK,OAAO,QAAQT,CAAM,EAAG,CACjD,IAAMe,EAAYF,EAAc,GAAGA,CAAW,IAAIjB,CAAG,GAAKA,EAGpDoB,EACJ,MAAM,QAAQP,CAAK,GACnBA,EAAM,OAAS,GACf,MAAM,QAAQA,EAAM,CAAC,CAAC,GACtB,OAAOA,EAAM,CAAC,EAAE,CAAC,GAAM,SAGnBQ,EACJH,EAAOlB,CAAG,GACV,OAAOkB,EAAOlB,CAAG,GAAM,UACvB,CAAC,MAAM,QAAQkB,EAAOlB,CAAG,CAAC,GAC1B,MAAM,QAAQkB,EAAOlB,CAAG,EAAE,CAAC,GAC3B,MAAM,QAAQkB,EAAOlB,CAAG,EAAE,CAAC,EAEzBoB,GAAqBC,GAGvBH,EAAOlB,CAAG,EAAIN,GAAUwB,EAAOlB,CAAG,CAAC,EAEnC,KAAK,OAAO,MACV,yCAAyCmB,CAAS,GAClD,CAAE,IAAKN,EAAO,YAAYL,EAAAU,EAAOlB,CAAG,EAAE,IAAZ,YAAAQ,EAAe,MAAO,CAClD,EACA,KAAK,4BAA4BU,EAAOlB,CAAG,EAAGa,EAAOM,CAAS,GAE9D,OAAON,GAAU,UACjBA,IAAU,MACV,CAAC,MAAM,QAAQA,CAAK,GACpB,OAAOK,EAAOlB,CAAG,GAAM,UACvBkB,EAAOlB,CAAG,IAAM,MAChB,CAAC,MAAM,QAAQkB,EAAOlB,CAAG,CAAC,EAE1BkB,EAAOlB,CAAG,EAAI,KAAK,mBAAmBkB,EAAOlB,CAAG,EAAGa,EAAOM,CAAS,EAEnED,EAAOlB,CAAG,EAAIa,CAElB,CAEA,OAAOK,CACT,CAOQ,4BACNI,EACAC,EACAC,EACM,CAEN,GACE,CAACF,GACD,OAAOA,GAAmB,UAC1B,CAAC,MAAM,QAAQA,EAAe,CAAC,GAC/B,CAAC,MAAM,QAAQA,EAAe,CAAC,EAC/B,CACA,KAAK,OAAO,MACV,wDAAwDE,CAAS,GACjE,CAAE,eAAAF,CAAe,CACnB,EACA,MACF,CAEA,IAAMG,EAAeH,EAAe,EAG/B,KAAK,WAAWE,CAAS,IAC5B,KAAK,WAAWA,CAAS,EAAI,CAC3B,MAAOC,EACP,QAASH,EAAe,CAC1B,GAIAA,EAAe,GACf,OAAOA,EAAe,GAAM,UAC5B,OAAOA,EAAe,EAAE,OAAU,WAElC,KAAK,YAAYE,CAAS,EAAIF,EAAe,EAAE,OAGjD,KAAK,OAAO,MACV,8BAA8BE,CAAS,WAAW,KAAK,YAAYA,CAAS,CAAC,WAAWC,EAAa,MAAM,SAASF,EAAW,MAAM,EACvI,EAEA,QAAWG,KAAaH,EAAY,CAClC,GAAI,CAAC,MAAM,QAAQG,CAAS,GAAKA,EAAU,OAAS,EAClD,SAKF,OAFeA,EAAU,CAAC,EAEV,CACd,IAAK,IAAK,CACR,IAAM1B,EAAM0B,EAAU,CAAC,EACjBC,EAAc,KAAK,mBACvBF,EACAzB,EACAsB,EAAe,EACfE,CACF,EACA,KAAK,OAAO,MACV,qCAAqCxB,CAAG,WAAW2B,CAAW,WAAWF,EAAa,MAAM,EAC9F,EACIE,GAAe,GACjBF,EAAa,OAAOE,EAAa,CAAC,EAClC,KAAK,OAAO,MAAM,wCAAwCF,EAAa,MAAM,QAAQ,GAErF,KAAK,OAAO,MAAM,4CAA4CzB,CAAG,YAAY,EAE/E,KACF,CACA,IAAK,IAAK,CACR,IAAM4B,EAAc,KAAK,mBACvBH,EACAC,EAAU,CAAC,EACXJ,EAAe,EACfE,CACF,EACMK,EAAUH,EAAU,CAAC,EACvBE,GAAe,GAAKC,IACtBJ,EAAaG,CAAW,EAAI,KAAK,eAC/BH,EAAaG,CAAW,EACxBC,EACAL,CACF,GAEF,KACF,CACA,IAAK,IAAK,CACR,IAAMM,EAAa,MAAM,QAAQJ,EAAU,CAAC,CAAC,EACzCA,EAAU,CAAC,EACX,CAACA,EAAU,CAAC,CAAC,EACbA,EAAU,CAAC,IACbJ,EAAe,EAAII,EAAU,CAAC,GAEhCD,EAAa,KAAK,GAAGK,CAAU,EAE7BJ,EAAU,CAAC,GACX,OAAOA,EAAU,CAAC,GAAM,UACxBA,EAAU,CAAC,EAAE,QAEb,KAAK,YAAYF,CAAS,EAAIE,EAAU,CAAC,EAAE,OAE7C,KACF,CACA,IAAK,IAAK,CACR,IAAMK,EAAiB,MAAM,QAAQL,EAAU,CAAC,CAAC,EAC7CA,EAAU,CAAC,EACX,CAACA,EAAU,CAAC,CAAC,EACbA,EAAU,CAAC,IACbJ,EAAe,EAAII,EAAU,CAAC,GAEhCD,EAAa,QAAQ,GAAGM,CAAc,EACtC,KACF,CACA,IAAK,IAAK,CACR,IAAMC,EAAc,KAAK,mBACvBP,EACAC,EAAU,CAAC,EACXJ,EAAe,EACfE,CACF,EACA,GAAIQ,GAAe,EAAG,CACpB,IAAMC,EAAgB,MAAM,QAAQP,EAAU,CAAC,CAAC,EAC5CA,EAAU,CAAC,EACX,CAACA,EAAU,CAAC,CAAC,EACjBD,EAAa,OAAOO,EAAc,EAAG,EAAG,GAAGC,CAAa,CAC1D,CACA,KACF,CACA,IAAK,IAAK,CACR,IAAMC,EAAWR,EAAU,CAAC,EACtBS,EAAwB,CAAC,EACzBC,EAAa,IAAI,IAEvB,QAAWC,KAAQZ,EAAc,CAC/B,IAAMa,EAAU,KAAK,WAAWD,EAAMf,EAAe,EAAGE,CAAS,EAC7Dc,GACFF,EAAW,IAAIE,EAASD,CAAI,CAEhC,CAEA,QAAWE,KAAcL,EAAU,CACjC,IAAMG,EAAOD,EAAW,IAAIG,CAAU,EAClCF,GACFF,EAAe,KAAKE,CAAI,CAE5B,CAEAZ,EAAa,OAAS,EACtBA,EAAa,KAAK,GAAGU,CAAc,EACnC,KACF,CACA,QACE,KACJ,CACF,CAGA,KAAK,WAAWX,CAAS,EAAI,CAC3B,MAAOC,EACP,QAASH,EAAe,CAC1B,CACF,CAEQ,oBACNzB,EACA2B,EACAZ,EACQ,CACR,GAAIf,EAAK,GAAK,MAAM,QAAQA,EAAK,CAAC,EAAG,CACnC,IAAI2C,EAAO,GAEX,QAASC,EAAI,EAAGA,EAAI5C,EAAK,EAAE,OAAQ4C,IAAK,CACtC,IAAMC,EAAgB7C,EAAK,EAAE4C,CAAC,EAG9B,GAFAD,GAAQE,EAEJD,EAAI5C,EAAK,EAAE,OAAS,EAAG,CACzB,IAAM8C,EAAaF,EAAE,SAAS,EAC9B,GAAI5C,EAAK8C,CAAU,IAAM,OAAW,CAClC,IAAMC,EAAepB,EACjB,GAAGA,CAAS,IAAImB,CAAU,GAC1BA,EAMJ,GAAI/B,GAAaA,EAAU,IAAIgC,CAAY,EAAG,CAC5CJ,GAAQ,yBAAyBI,CAAY,MAC7C,QACF,CAEAJ,GAAQ,KAAK,YACX3C,EAAK8C,CAAU,EACfA,EACAC,CACF,CACF,CACF,CACF,CAEA,OAAAJ,EAAOA,EAAK,QAAQ,UAAW,EAAE,EAAE,QAAQ,YAAa,EAAE,EACnDA,CACT,CAEA,OAAO,KAAK,YAAY3C,EAAM,GAAI2B,CAAS,CAC7C,CAEQ,YACNX,EACAgC,EACArB,EACQ,CAKR,GAJIX,GAAU,MAKZ,OAAOA,GAAU,UACjBA,EAAM,WAAW,IAAI,GACrBA,EAAM,SAAS,IAAI,EAEnB,MAAO,GAGT,GAAI,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,EAAG,CACtD,GACEA,EAAM,GACN,MAAM,QAAQA,EAAM,CAAC,GACrBA,EAAM,GACN,MAAM,QAAQA,EAAM,CAAC,EACrB,CACA,IAAMiC,EAAWtB,GAAaqB,GAAY,GAC1C,OAAIC,IACF,KAAK,WAAWA,CAAQ,EAAI,CAC1B,MAAOjC,EAAM,EACb,QAASA,EAAM,CACjB,EAEEA,EAAM,GACN,OAAOA,EAAM,GAAM,UACnB,OAAOA,EAAM,EAAE,OAAU,WAEzB,KAAK,YAAYiC,CAAQ,EAAIjC,EAAM,EAAE,QAGlC,KAAK,qBAAqBA,EAAOgC,EAAUrB,CAAS,CAC7D,CAEA,GAAI,MAAOX,GAAS,MAAM,QAASA,EAAmB,CAAC,EACrD,OAAO,KAAK,oBAAoBA,EAAmBW,GAAa,EAAE,EAKpE,IAAMuB,EAAO,OAAO,KAAKlC,CAAK,EACxBmC,EAAcD,EAAK,OAAQE,GAAM,QAAQ,KAAKA,CAAC,CAAC,EAAE,KAAK,CAACC,EAAGC,IAAM,SAASD,CAAC,EAAI,SAASC,CAAC,CAAC,EAChG,GAAIH,EAAY,OAAS,GAAKA,EAAY,SAAWD,EAAK,OAExD,OAAOC,EACJ,IAAKC,GAAM,CACV,IAAMG,EAAgB5B,EAAY,GAAGA,CAAS,IAAIyB,CAAC,GAAKA,EACxD,OAAO,KAAK,YAAapC,EAAkCoC,CAAC,EAAGA,EAAGG,CAAa,CACjF,CAAC,EACA,KAAK,EAAE,CAEd,CAEA,OAAI,MAAM,QAAQvC,CAAK,EAEnBA,EAAM,OAAS,GACf,MAAM,QAAQA,EAAM,CAAC,CAAC,GACtB,OAAOA,EAAM,CAAC,EAAE,CAAC,GAAM,SAEhB,KAAK,4BAA4BA,EAAOW,CAAS,EAGnDX,EACJ,IAAI,CAACwB,EAAMgB,IAAQ,CAClB,IAAMf,EAAUe,EAAI,SAAS,EACvBD,EAAgB5B,EAAY,GAAGA,CAAS,IAAIc,CAAO,GAAKA,EAC9D,OAAI,OAAOD,GAAS,UAAYA,GAASA,EAAkB,EAClD,KAAK,oBAAoBA,EAAkBe,CAAa,EAE1D,KAAK,YAAYf,EAAMC,EAASc,CAAa,CACtD,CAAC,EACA,KAAK,EAAE,EAGR,OAAOvC,GAAU,UAKnB,KAAK,OAAO,MACV,oFACF,EACO,IAGF,OAAOA,CAAK,CACrB,CAEQ,qBACNyC,EACAT,EACArB,EACQ,CACR,GAAM,CAAE,EAAG+B,EAAUC,CAAW,EAAIF,EAEpC,GAAI,CAACC,GAAY,CAAC,MAAM,QAAQA,CAAQ,EACtC,MAAO,GAGT,GAAIA,EAAS,SAAW,EAAG,CACzB,GAAID,EAAU,KAAS,CACrB,IAAMG,EAAU,OACVC,EAAgBlC,EAAY,GAAGA,CAAS,QAAU,OACxD,OAAO,KAAK,YAAY8B,EAAU,KAASG,EAASC,CAAa,CACnE,CACA,MAAO,EACT,CAEA,OAAIF,GAAW,MAAM,QAAQA,CAAO,EAC3BD,EACJ,IAAI,CAAClB,EAAWsB,IACf,KAAK,gBAAgBtB,EAAMsB,EAASH,EAAShC,CAAS,CACxD,EACC,KAAK,EAAE,EAGL+B,EACJ,IAAI,CAAClB,EAAWgB,IAAgB,CAC/B,IAAMf,EAAUe,EAAI,SAAS,EACvBD,EAAgB5B,EAAY,GAAGA,CAAS,IAAIc,CAAO,GAAKA,EAC9D,OAAO,KAAK,YAAYD,EAAMC,EAASc,CAAa,CACtD,CAAC,EACA,KAAK,EAAE,CACZ,CAUA,gBACEf,EACAsB,EACAH,EACAhC,EACQ,CACR,IAAMoC,EAAcJ,EAEhBhB,EAAO,GAEX,QAASC,EAAI,EAAGA,EAAImB,EAAY,OAAQnB,IAGtC,GAFAD,GAAQoB,EAAYnB,CAAC,EAEjBA,EAAImB,EAAY,OAAS,EAAG,CAC9B,IAAMf,EAAWJ,EAAE,SAAS,EAC5B,GAAIJ,EAAKQ,CAAQ,IAAM,OAAW,CAChC,IAAMO,EAAgB5B,EAClB,GAAGA,CAAS,IAAImC,CAAO,IAAId,CAAQ,GACnC,GAAGc,CAAO,IAAId,CAAQ,GAC1BL,GAAQ,KAAK,YAAYH,EAAKQ,CAAQ,EAAGA,EAAUO,CAAa,CAClE,CACF,CAGF,OAAOZ,CACT,CAEQ,4BACNjB,EACAC,EACQ,CACR,GAAI,CAACA,GAAa,CAAC,KAAK,WAAWA,CAAS,EAC1C,MAAO,GAGT,IAAMqC,EAAY,KAAK,WAAWrC,CAAS,EACrCC,EAAe,CAAC,GAAGoC,EAAU,KAAK,EAClCL,EAAUK,EAAU,QAE1B,QAAWnC,KAAaH,EAAY,CAClC,GAAI,CAAC,MAAM,QAAQG,CAAS,GAAKA,EAAU,OAAS,EAClD,SAKF,OAFeA,EAAU,CAAC,EAEV,CACd,IAAK,IAAK,CACR,IAAMC,EAAc,KAAK,mBACvBF,EACAC,EAAU,CAAC,EACX8B,EACAhC,CACF,EACIG,GAAe,GACjBF,EAAa,OAAOE,EAAa,CAAC,EAEpC,KACF,CACA,IAAK,IAAK,CACR,IAAMC,EAAc,KAAK,mBACvBH,EACAC,EAAU,CAAC,EACX8B,EACAhC,CACF,EACMK,EAAUH,EAAU,CAAC,EACvBE,GAAe,GAAKC,IACtBJ,EAAaG,CAAW,EAAI,KAAK,eAC/BH,EAAaG,CAAW,EACxBC,EACAL,GAAa,EACf,GAEF,KACF,CACA,IAAK,IAAK,CACR,KAAK,gBACHC,EACAC,EAAU,CAAC,EACXA,EAAU,CAAC,EACXmC,EACA,EACF,EAEEnC,EAAU,CAAC,GACX,OAAOA,EAAU,CAAC,GAAM,UACxBA,EAAU,CAAC,EAAE,QAEb,KAAK,YAAYF,GAAa,EAAE,EAAIE,EAAU,CAAC,EAAE,OAEnD,KACF,CACA,IAAK,IAAK,CACR,KAAK,gBACHD,EACAC,EAAU,CAAC,EACXA,EAAU,CAAC,EACXmC,EACA,EACF,EACA,KACF,CACA,IAAK,IAAK,CACR,IAAM7B,EAAc,KAAK,mBACvBP,EACAC,EAAU,CAAC,EACX8B,EACAhC,CACF,EACA,GAAIQ,GAAe,EAAG,CACpB,IAAMC,EAAgB,MAAM,QAAQP,EAAU,CAAC,CAAC,EAC5CA,EAAU,CAAC,EACX,CAACA,EAAU,CAAC,CAAC,EACjBD,EAAa,OAAOO,EAAc,EAAG,EAAG,GAAGC,CAAa,CAC1D,CACA,KACF,CACA,IAAK,IAAK,CACR,IAAMC,EAAWR,EAAU,CAAC,EACtBS,EAAwB,CAAC,EACzBC,EAAa,IAAI,IAEvB,QAAWC,KAAQZ,EAAc,CAC/B,IAAMa,EAAU,KAAK,WAAWD,EAAMmB,EAAShC,CAAS,EACpDc,GACFF,EAAW,IAAIE,EAASD,CAAI,CAEhC,CAEA,QAAWE,KAAcL,EAAU,CACjC,IAAMG,EAAOD,EAAW,IAAIG,CAAU,EAClCF,GACFF,EAAe,KAAKE,CAAI,CAE5B,CAEAZ,EAAa,OAAS,EACtBA,EAAa,KAAK,GAAGU,CAAc,EACnC,KACF,CACA,QACE,KACJ,CACF,CAEA,KAAK,WAAWX,CAAS,EAAI,CAC3B,MAAOC,EACP,QAASoC,EAAU,OACrB,EAEA,KAAK,UAAUrC,CAAS,EAAI,CAC1B,EAAGC,EACH,EAAGoC,EAAU,OACf,EAEA,IAAMvC,EAAiB,KAAK,yBAAyBE,CAAS,EAC9D,OAAIF,GAAkBA,EAAe,EAC5B,KAAK,uBACVG,EACAH,EAAe,EACfE,CACF,EAGKC,EAAa,IAAKY,GAAS,KAAK,YAAYA,CAAI,CAAC,EAAE,KAAK,EAAE,CACnE,CAEQ,yBAAyBS,EAAuB,CACtD,GAAI,KAAK,WAAWA,CAAQ,EAC1B,MAAO,CACL,EAAG,KAAK,WAAWA,CAAQ,EAAE,MAC7B,EAAG,KAAK,WAAWA,CAAQ,EAAE,OAC/B,EAGF,IAAMgB,EAAa,KAAK,UAAUhB,CAAQ,EAC1C,OACEgB,GACA,OAAOA,GAAe,UACrBA,EAAwB,EAElBA,EAGF,IACT,CAEQ,uBACNC,EACAP,EACAhC,EACQ,CACR,IAAMR,EAAS+C,EACZ,IAAI,CAAC1B,EAAWsB,IACf,KAAK,gBAAgBtB,EAAMsB,EAASH,EAAShC,CAAS,CACxD,EACC,KAAK,EAAE,EAEV,OAAI,KAAK,OAAO,eAAe,IAC7B,KAAK,OAAO,MAAM,oCAAqCgC,CAAO,EAC9D,KAAK,OAAO,MAAM,wCAAyCO,EAAM,MAAM,EACvE,KAAK,OAAO,MACV,2CACA/C,EAAO,UAAU,EAAG,GAAG,CACzB,GAGKA,CACT,CAEQ,gBACNS,EACAsC,EACAP,EACAK,EACAG,EACM,CAKN,GAJIR,IACFK,EAAU,QAAUL,GAGlB,CAACO,EAAO,OAEZ,IAAME,EAAa,MAAM,QAAQF,CAAK,EAAIA,EAAQ,CAACA,CAAK,EACpDC,EACFvC,EAAa,QAAQ,GAAGwC,CAAU,EAElCxC,EAAa,KAAK,GAAGwC,CAAU,CAEnC,CAEQ,WACN5B,EACAmB,EACAhC,EACe,CAEf,GAAIa,EAAK,IAAM,OAAOA,EAAK,IAAO,SAChC,OAAOA,EAAK,GAGd,GAAI,CAACb,GAAa,CAAC,KAAK,YAAYA,CAAS,EAC3C,OAAO,KAGT,IAAM0C,EAAY,KAAK,YAAY1C,CAAS,EAC5C,OAAOa,EAAK6B,CAAS,GAAK,IAC5B,CAEQ,mBACNH,EACA/D,EACAwD,EACAhC,EACQ,CACR,OAAOuC,EAAM,UACV1B,GAAc,KAAK,WAAWA,EAAMmB,EAAShC,CAAS,IAAMxB,CAC/D,CACF,CAQQ,eAAeqC,EAAWR,EAAcL,EAAwB,CACtE,OAAO,KAAK,mBAAmBa,EAAMR,EAAS,GAAGL,CAAS,OAAO,CACnE,CACF,EC76BA,IAAM2C,GAAiB,CAAC,WAAY,cAAc,EAI5CC,GAA6BD,GAAe,IAC/CE,GAAS,IAAI,OAAO,eAAeA,CAAI,OAAO,CACjD,EACaC,EAAwB,4BACxBC,EAAqB,yBAqDrBC,GAAN,KAAsB,CAG3B,YAA6BC,EAA6B,CAA7B,SAAAA,EAF7B,KAAQ,eAAiB,IAAI,GAE8B,CAE3D,YAAmB,CACjB,KAAK,eAAe,MAAM,CAC5B,CAEA,eAAeC,EAAyB,CACtC,KAAK,eAAe,OAAOA,CAAS,CACtC,CAkBA,cACEC,EACAD,EACAE,EACgB,CAChB,IAAMC,EAAS,KAAK,eAAe,IAAIH,CAAS,EAChD,GAAIG,GAAUA,EAAO,aAAeF,EAAQ,SAASE,CAAM,EACzD,OAAOA,EAMT,GAJIA,GACF,KAAK,eAAe,OAAOH,CAAS,EAGlCE,IAAoB,OACtB,OAAO,KAET,IAAME,EAAS,KAAK,cAAcH,EAASC,CAAe,EAC1D,GAAI,CAACE,GAAU,CAACA,EAAO,cACrB,OAAO,KAGT,IAAMC,EAAYD,EAAO,cACzB,YAAK,eAAe,IAAIJ,EAAWK,CAAS,EACrCA,CACT,CAOA,iBACEJ,EACAK,EACAN,EACgB,CAChB,GAAI,CAACM,GAAkB,OAAOA,GAAmB,SAC/C,MAAO,CAAE,GAAI,GAAO,OAAQ,oBAAqB,EAEnD,GAAI,CAAC,MAAM,QAAQA,EAAe,CAAC,GAAKA,EAAe,EAAE,SAAW,EAClE,MAAO,CAAE,GAAI,GAAO,OAAQ,YAAa,EAG3C,IAAMC,EAAyB,CAACD,EAAe,CAAC,EAChD,GAAIA,EAAe,IAAM,OAAOA,EAAe,IAAO,SACpD,QAAWE,KAAM,OAAO,OAAOF,EAAe,EAAE,EAC1C,MAAM,QAAQE,CAAE,GAClBD,EAAW,KAAKC,CAAc,EAQpC,GAAI,CAHoBD,EAAW,KAAME,GACvC,KAAK,2BAA2BA,CAAG,CACrC,EAEE,MAAO,CAAE,GAAI,GAAO,OAAQ,kCAAmC,EAGjE,IAAMC,EAAQJ,EAAe,EAC7B,GAAI,MAAM,QAAQI,CAAK,GACrB,QAAWC,KAAQD,EACjB,GAAI,KAAK,mBAAmBC,CAAI,EAC9B,MAAO,CAAE,GAAI,GAAO,OAAQ,mBAAoB,EAKtD,IAAMC,EAAY,KAAK,eAAeF,GAAA,YAAAA,EAAQ,GAAIJ,CAAc,EAC1DD,EAAY,KAAK,cAAcJ,EAASD,EAAWY,CAAS,EAClE,GAAI,CAACP,EACH,MAAO,CAAE,GAAI,GAAO,OAAQ,4BAA6B,EAO3D,IAAIQ,EAAsBR,EAC1B,KAAOQ,GAAK,CACV,GAAIA,EAAI,aAAa,YAAY,EAC/B,MAAO,CAAE,GAAI,GAAO,OAAQ,qBAAsB,EAEpD,GAAIA,IAAQZ,EAAS,MACrBY,EAAMA,EAAI,aACZ,CAEA,MAAO,CAAE,GAAI,GAAM,UAAAR,EAAW,aAAcO,CAAU,CACxD,CAQA,MACEX,EACAa,EACAC,EACgB,CAChB,GAAM,CAAE,UAAAf,EAAW,IAAAgB,EAAK,QAAAC,CAAQ,EAAIH,EAC9BF,EAAY,KAAK,cAAcI,CAAG,EAClCX,EAAY,KAAK,cAAcJ,EAASD,EAAWY,CAAS,EAClE,GAAI,CAACP,EACH,YAAK,IAAI,OAAO,MACd,mDAAmDL,CAAS,gBAC9D,EACO,KAGT,IAAIkB,EAAkB,GACtB,QAAWC,KAAMH,EAAK,CACpB,GAAI,CAAC,MAAM,QAAQG,CAAE,GAAKA,EAAG,OAAS,EAAG,SACzC,IAAMC,EAASD,EAAG,CAAC,EACnB,GAAI,CACF,IAAIE,EAAO,GACX,OAAQD,EAAQ,CACd,IAAK,IACHC,EAAO,KAAK,YAAYhB,EAAWc,EAAG,CAAC,CAAW,EAClD,MACF,IAAK,IACHE,EAAO,KAAK,eACVhB,EACAc,EAAG,CAAC,EACJF,EACAjB,EACAe,CACF,EACA,MACF,IAAK,IACHM,EAAO,KAAK,iBACVhB,EACAc,EAAG,CAAC,EACJA,EAAG,CAAC,EACJF,EACAjB,CACF,EACA,MACF,IAAK,IACHqB,EAAO,KAAK,YACVhB,EACAc,EAAG,CAAC,EACJF,EACAjB,CACF,EACA,MACF,IAAK,IACHqB,EAAO,KAAK,aACVhB,EACAc,EAAG,CAAC,EACJF,EACAjB,CACF,EACA,MACF,IAAK,IACHqB,EAAO,KAAK,aAAahB,EAAWc,EAAG,CAAC,CAAa,EACrD,MACF,QAME,KAAK,IAAI,OAAO,KACd,qCAAqCC,CAAM,gBAC7C,EACAC,EAAO,EACX,CACKA,IACHH,EAAkB,GAEtB,OAASI,EAAK,CACZ,YAAK,IAAI,OAAO,MACd,wBAAwBF,CAAM,qBAAqBpB,CAAS,GAC5DsB,CACF,EACO,IACT,CACF,CAgBA,OAAKJ,GAWH,OAAO,QAAW,aAClB,sBAAwB,QAEvB,OAAe,oBAEXb,GAfE,IAgBX,CAEA,eAAeJ,EAAwB,CACrBA,EAAQ,iBAAiB,IAAIL,CAAqB,GAAG,EAC7D,QAAS2B,GAAOA,EAAG,gBAAgB3B,CAAqB,CAAC,EAC7DK,EAAQ,aAAaL,CAAqB,GAC5CK,EAAQ,gBAAgBL,CAAqB,EAElCK,EAAQ,iBAAiB,IAAIJ,CAAkB,GAAG,EAC1D,QAAS0B,GAAOA,EAAG,gBAAgB1B,CAAkB,CAAC,EACvDI,EAAQ,aAAaJ,CAAkB,GACzCI,EAAQ,gBAAgBJ,CAAkB,CAE9C,CAUQ,YAAYQ,EAAoBmB,EAAsB,CAC5D,IAAMC,EAAM,KAAK,cAAcpB,EAAWmB,CAAG,EAC7C,OAAKC,GAQL,KAAK,kBAAkBA,EAAK,eAAe,EAC3CA,EAAI,OAAO,EACJ,KAPL,KAAK,IAAI,OAAO,MACd,qCAAqCD,CAAG,+BAC1C,EACO,GAKX,CAEQ,eACNnB,EACAmB,EACAP,EACAjB,EACAe,EACS,CA9Vb,IAAAW,EAAAC,EA+VI,IAAMF,EAAM,KAAK,cAAcpB,EAAWmB,CAAG,EAC7C,GAAI,CAACC,EACH,YAAK,IAAI,OAAO,MACd,qCAAqCD,CAAG,iCAC1C,EACO,GAET,IAAMI,EAAU,KAAK,aAAavB,EAAWoB,CAAG,EAC1Cd,EAAO,KAAK,kBAAkBX,EAAWwB,CAAG,EAClD,GAAI,CAACb,EACH,YAAK,IAAI,OAAO,MACd,2CAA2Ca,CAAG,8BAChD,EACO,GAET,IAAMK,EAAU,KAAK,IAAI,WACvBlB,EACAiB,EACAX,EACAjB,CACF,EACM8B,EAAS,KAAK,eAAeD,CAAO,EAC1C,OAAKC,GAMDf,EAMFgB,GAASN,EAAKK,EAAQ,CAAE,GAAGf,EAAiB,aAAc,EAAM,CAAC,GAQjE,KAAK,kBAAkBU,EAAK,eAAe,EAC3CA,EAAI,YAAYK,CAAM,GACtBH,GAAAD,EAAA,KAAK,KAAI,cAAT,MAAAC,EAAA,KAAAD,EAAuBI,GACvB,KAAK,kBAAkBA,EAAQ,aAAa,GAEvC,KAxBL,KAAK,IAAI,OAAO,KACd,sEACF,EACO,GAsBX,CAEQ,iBACNzB,EACA2B,EACAtB,EACAO,EACAjB,EACS,CACT,IAAMiC,EAAS,KAAK,cAAc5B,EAAW2B,CAAQ,EACrD,OAAKC,EAME,KAAK,kBACVvB,EACAO,EACAjB,EACA,KAAK,aAAaK,EAAW4B,CAAM,EAAI,EACtCC,GAAS7B,EAAU,aAAa6B,EAAMD,EAAO,WAAW,CAC3D,GAXE,KAAK,IAAI,OAAO,MACd,mCAAmCD,CAAQ,0BAC7C,EACO,GASX,CAEQ,YACN3B,EACAK,EACAO,EACAjB,EACS,CACT,OAAO,KAAK,kBACVU,EACAO,EACAjB,EACAK,EAAU,SAAS,OAClB6B,GAAS7B,EAAU,YAAY6B,CAAI,CACtC,CACF,CAEQ,aACN7B,EACAK,EACAO,EACAjB,EACS,CACT,OAAO,KAAK,kBACVU,EACAO,EACAjB,EACA,EACCkC,GAAS7B,EAAU,aAAa6B,EAAM7B,EAAU,UAAU,CAC7D,CACF,CASQ,kBACNK,EACAO,EACAjB,EACAmC,EACAC,EACS,CAndb,IAAAV,EAAAC,EAodI,IAAMU,EAAO,MAAM,QAAQ3B,CAAK,EAAIA,EAAQ,CAACA,CAAK,EAC5C4B,EAAU,SAAS,uBAAuB,EAC1CC,EAAqB,CAAC,EAC5B,QAASC,EAAI,EAAGA,EAAIH,EAAK,OAAQG,IAAK,CACpC,IAAMV,EAAS,KAAK,eAClBO,EAAKG,CAAC,EACNL,EAAUK,EACVvB,EACAjB,CACF,EACA,GAAI,CAAC8B,EACH,MAAO,GAETQ,EAAQ,YAAYR,CAAM,EAC1BS,EAAQ,KAAKT,CAAM,CACrB,CACAM,EAAOE,CAAO,EACd,QAAWb,KAAOc,GAChBZ,GAAAD,EAAA,KAAK,KAAI,cAAT,MAAAC,EAAA,KAAAD,EAAuBD,GACvB,KAAK,kBAAkBA,EAAK,aAAa,EAE3C,MAAO,EACT,CAWQ,aAAapB,EAAoBoC,EAAgC,CACvE,GAAI,CAAC,MAAM,QAAQA,CAAW,EAAG,MAAO,GACxC,IAAMC,EAAQ,IAAI,IAClB,MAAM,KAAKrC,EAAU,QAAQ,EAAE,QAASsC,GAAU,CAChD,QAAWhD,KAAQF,GAAgB,CACjC,IAAMmD,EAAID,EAAM,aAAahD,CAAI,EACjC,GAAIiD,IAAM,KAAM,CACdF,EAAM,IAAIE,EAAGD,CAAK,EAClB,KACF,CACF,CACF,CAAC,EAED,IAAME,EAAW,SAAS,uBAAuB,EAC3CC,EAAY,IAAI,IAAIL,CAAW,EACrC,QAAWjB,KAAOiB,EAAa,CAC7B,IAAMlB,EAAKmB,EAAM,IAAIlB,CAAG,EACpBD,GACFsB,EAAS,YAAYtB,CAAE,CAE3B,CAMA,GAAIuB,EAAU,KAAOJ,EAAM,KAAM,CAC/B,KAAK,IAAI,OAAO,KACd,qCAAqCI,EAAU,IAAI,qCAAqCJ,EAAM,IAAI,MAAMA,EAAM,KAAOI,EAAU,IAAI,2BACrI,EACA,OAAW,CAACF,EAAGrB,CAAE,IAAKmB,EACfI,EAAU,IAAIF,CAAC,GAClB,KAAK,kBAAkBrB,EAAI,eAAe,CAGhD,CAEA,OAAAlB,EAAU,gBAAgBwC,CAAQ,EAC3B,EACT,CAIQ,eACNlC,EACAiB,EACAX,EACAjB,EACgB,CAChB,IAAM+C,EAAO,KAAK,IAAI,WAAWpC,EAAMiB,EAASX,EAASjB,CAAS,EAClE,OAAO,KAAK,eAAe+C,CAAI,CACjC,CAOQ,eAAeA,EAA8B,CACnD,IAAMC,EAAW,SAAS,cAAc,UAAU,EAClDA,EAAS,UAAYD,EAAK,KAAK,EAC/B,IAAME,EAAQD,EAAS,QAAQ,kBAC/B,OAAOC,GAAA,KAAAA,EAAS,IAClB,CAEQ,cAAcC,EAAgB1B,EAA6B,CACjE,IAAI2B,EACA,OAAO,KAAQ,aAAe,OAAO,IAAI,QAAW,WACtDA,EAAU,IAAI,OAAO3B,CAAG,GAOpB,8BAA8B,KAAKA,CAAG,GACxC,KAAK,IAAI,OAAO,KACd,kDAAkDA,CAAG,oEACvD,EAEF2B,EAAU3B,EAAI,QAAQ,WAAY,MAAM,GAE1C,QAAW7B,KAAQF,GAAgB,CACjC,IAAM8B,EAAK2B,EAAM,cAAc,IAAIvD,CAAI,KAAKwD,CAAO,IAAI,EACvD,GAAI5B,EAAI,OAAOA,CACjB,CACA,OAAO,IACT,CAEQ,aAAalB,EAAoBsC,EAAwB,CAC/D,IAAIH,EAAI,EACJ3B,EAAMR,EAAU,kBACpB,KAAOQ,GAAK,CACV,GAAIA,IAAQ8B,EAAO,OAAOH,EAC1BA,IACA3B,EAAMA,EAAI,kBACZ,CACA,MAAO,EACT,CAEQ,cAAcG,EAAgC,CACpD,QAAWG,KAAMH,EAAK,CACpB,GAAI,CAAC,MAAM,QAAQG,CAAE,GAAKA,EAAG,OAAS,EAAG,SACzC,IAAMiC,EAAIjC,EAAG,CAAC,EACd,GAAIiC,IAAM,KAAOA,IAAM,KAAOA,IAAM,IAClC,OAAO,OAAOjC,EAAG,CAAC,GAAM,SAAWA,EAAG,CAAC,EAAI,OAE7C,GAAIiC,IAAM,KAAO,MAAM,QAAQjC,EAAG,CAAC,CAAC,GAAKA,EAAG,CAAC,EAAE,OAAS,EACtD,OAAO,OAAOA,EAAG,CAAC,EAAE,CAAC,GAAM,SAAWA,EAAG,CAAC,EAAE,CAAC,EAAI,OAOnD,GAAIiC,IAAM,KAAOA,IAAM,IAAK,CAC1B,IAAM1C,EAAQ,MAAM,QAAQS,EAAG,CAAC,CAAC,EAAIA,EAAG,CAAC,EAAI,CAACA,EAAG,CAAC,CAAC,EACnD,QAAWkC,KAAM3C,EACf,GAAI2C,GAAM,OAAOA,GAAO,UAAYA,EAAG,KAAO,OAC5C,OAAO,OAAOA,EAAG,EAAE,CAGzB,CACF,CAEF,CAEQ,2BAA2BpC,EAA4B,CAc7D,QAAWqC,KAAKrC,EACd,GAAI,OAAOqC,GAAM,UACjB,QAAWC,KAAM7D,GACf,GAAI6D,EAAG,KAAKD,CAAC,EACX,MAAO,GAIb,MAAO,EACT,CAEQ,mBAAmB3C,EAAoB,CAC7C,GAAI,CAACA,GAAQ,OAAOA,GAAS,SAAU,MAAO,GAC9C,OAAW,CAACa,EAAKgC,CAAG,IAAK,OAAO,QAAQ7C,CAAI,EAC1C,GAAI,CAAAa,EAAI,WAAW,GAAG,GAClBgC,GAAO,OAAOA,GAAQ,UAAY,CAAC,MAAM,QAAQA,CAAG,EAAG,CACzD,IAAMC,EAAID,EAEV,GADI,MAAM,QAAQC,EAAE,CAAC,GAAK,MAAM,QAAQA,EAAE,CAAC,GACvC,KAAK,mBAAmBA,CAAC,EAAG,MAAO,EACzC,CAEF,MAAO,EACT,CAEQ,eAAe9C,EAAWL,EAAyC,CA1pB7E,IAAAoB,EA2pBI,GAAI,CAACf,GAAQ,OAAOA,GAAS,SAAU,OACvC,GAAIA,EAAK,KAAO,OAAW,OAAO,OAAOA,EAAK,EAAE,EAChD,IAAM+C,GAAQhC,EAAApB,GAAA,YAAAA,EAAgB,IAAhB,YAAAoB,EAAmB,MACjC,GAAIgC,GAAS/C,EAAK+C,CAAK,IAAM,OAAW,OAAO,OAAO/C,EAAK+C,CAAK,CAAC,CAEnE,CAEQ,kBAAkB1D,EAAmBwB,EAAkB,CAI7D,OAAO,KAAK,IAAI,WAAWxB,EAAWwB,CAAG,CAC3C,CAEQ,kBAAkBmC,EAAeC,EAAwB,CAC3DD,EAAK,aAAaC,CAAQ,GAC5B,KAAK,IAAI,qBAAqBD,EAAMC,CAAQ,EAE1BD,EAAK,iBAAiB,IAAIC,CAAQ,GAAG,EAC7C,QAASrC,GACnB,KAAK,IAAI,qBAAqBA,EAAIqC,CAAQ,CAC5C,CACF,CACF,EC7qBO,IAAMC,GAAN,KAA2B,CAKhC,aAAc,CAJd,KAAQ,WAAqC,KAC7C,KAAQ,aAAyC,KACjD,KAAQ,mBAAoC,IAE7B,CAEf,oBACEC,EACAC,EACAC,EACM,CAMN,GALA,KAAK,WAAaF,EAClB,KAAK,aAAeC,EACpB,KAAK,mBAAqBC,EAGtBF,EAAM,CACRA,EAAK,aAAa,YAAa,MAAM,EACrC,IAAMG,EAAWH,EAAK,cAAc,UAAU,EAC1CG,IACFA,EAAS,SAAW,GAExB,CACF,CAEA,eAAeC,EAA8B,CACvC,KAAK,YACP,KAAK,WAAW,cACd,IAAI,YAAY,WAAY,CAAE,OAAQA,CAAK,CAAC,CAC9C,EAGEA,EAAK,QACP,KAAK,cAAcA,CAAI,EAEvB,KAAK,YAAYA,CAAI,EAGvB,KAAK,iBAAiB,CACxB,CAEA,OAAc,CACZ,KAAK,iBAAiB,CACxB,CAEQ,cAAcA,EAA8B,CAClD,GAAI,CAAC,KAAK,WACR,OAGF,KAAK,WAAW,cACd,IAAI,YAAY,cAAe,CAAE,OAAQA,CAAK,CAAC,CACjD,EAGA,IAAMC,EAAe,KAAK,WAAW,QAAQ,QAAQ,EACjDA,GAAgBA,EAAa,MAC/BA,EAAa,MAAM,EAGhB,KAAK,WAAW,aAAa,mBAAmB,GACnD,KAAK,WAAW,MAAM,CAE1B,CAEQ,YAAYD,EAA8B,CAC3C,KAAK,YAIV,KAAK,WAAW,cACd,IAAI,YAAY,YAAa,CAAE,OAAQA,CAAK,CAAC,CAC/C,CACF,CAEQ,kBAAyB,CAC/B,GAAI,KAAK,WAAY,CACnB,KAAK,WAAW,gBAAgB,WAAW,EAC3C,IAAMD,EAAW,KAAK,WAAW,cAAc,UAAU,EACrDA,IACFA,EAAS,SAAW,GAExB,CAEI,KAAK,cAAgB,KAAK,qBAAuB,OACnD,KAAK,aAAa,SAAW,GAC7B,KAAK,aAAa,YAAc,KAAK,oBAGvC,KAAK,WAAa,KAClB,KAAK,aAAe,KACpB,KAAK,mBAAqB,IAC5B,CACF,EC5EO,IAAMG,GAAN,KAAsB,CAM3B,YACmBC,EACAC,EACjB,CAFiB,aAAAD,EACA,YAAAC,EAPnB,KAAQ,YAAwC,IAAI,IACpD,KAAQ,QAAmB,GAC3B,KAAQ,cAA8B,IAAI,IAC1C,KAAQ,gBAA4C,IAAI,GAKrD,CAEH,gBAAgBC,EAA8B,CAC5C,KAAK,QAAUA,EAAa,SAAS,QAAQ,EAC7C,KAAK,OAAO,MACV,6CACA,KAAK,QAAU,UAAY,UAC7B,CACF,CAEA,eAAeC,EAA2B,CACxC,IAAMC,EAAW,KAAK,YAAY,KAClC,KAAK,SAASD,CAAS,EACnB,KAAK,YAAY,KAAOC,GAC1B,KAAK,OAAO,MACV,YAAY,KAAK,YAAY,IAAI,mBACjC,MAAM,KAAK,KAAK,YAAY,KAAK,CAAC,CACpC,CAEJ,CAEA,cAAqB,CACnB,GAAI,CAAC,KAAK,QAAS,OAEnB,IAAMC,EAAU,KAAK,QAAQ,kBAAkB,EAC/C,GAAI,CAACA,EAAS,OAGd,QAAWC,KAAM,KAAK,gBAAgB,KAAK,EACpCA,EAAG,cACN,KAAK,gBAAgB,IAAIA,CAAE,EAAG,EAC9B,KAAK,gBAAgB,OAAOA,CAAE,EAC9B,KAAK,cAAc,OAAOA,CAAE,GAIhC,OAAW,CAACC,EAAWC,CAAW,IAAK,KAAK,YAAa,CACvD,IAAMC,EAAc,KAAK,kBAAkBF,CAAS,EAC9CG,EAAWL,EAAQ,iBAAiB,UAAUI,CAAW,IAAI,EAEnE,QAAWH,KAAMI,EAAU,CAEzB,GADI,KAAK,cAAc,IAAIJ,CAAE,GACzBA,EAAG,aAAa,WAAW,GAAKA,EAAG,aAAa,YAAY,EAC9D,SAEF,IAAMK,EAAaL,EAAG,QAAQ,MAAM,EAChCK,IACEA,EAAW,aAAa,YAAY,GACpCC,GAAqBD,EAAY,wBAAyB,KAAK,MAAM,IAGvEL,aAAc,mBAEdA,EAAG,OAAS,UACZA,EAAG,OAAS,UACZA,EAAG,OAAS,WAIZA,aAAc,oBAElB,KAAK,eAAeA,EAAmBC,EAAWC,CAAW,EAC7D,KAAK,cAAc,IAAIF,CAAE,EAC3B,CACF,CAKA,IAAMO,EAAUR,EAAQ,iBAAiB,cAAc,EACvD,QAAWC,KAAMO,EAAS,CAExB,GADI,KAAK,cAAc,IAAIP,CAAE,GACzBA,EAAG,aAAa,WAAW,GAAKA,EAAG,aAAa,YAAY,EAC9D,SAEF,IAAMK,EAAaL,EAAG,QAAQ,MAAM,EACpC,GAAIK,IACEA,EAAW,aAAa,YAAY,GACpCA,EAAW,aAAa,uBAAuB,GAAG,SAGxD,IAAMG,EAAOR,EAAG,aAAa,MAAM,EAC9BQ,IACL,KAAK,eAAeR,EAAmBQ,EAAM,OAAO,EACpD,KAAK,cAAc,IAAIR,CAAE,EAC3B,CACF,CAEA,UAAiB,CACf,QAAWS,KAAW,KAAK,gBAAgB,OAAO,EAChDA,EAAQ,EAEV,KAAK,gBAAgB,MAAM,EAC3B,KAAK,cAAc,MAAM,EACzB,KAAK,YAAY,MAAM,EACvB,KAAK,QAAU,EACjB,CAEA,gBAAmD,CACjD,OAAO,KAAK,WACd,CAEA,WAAqB,CACnB,OAAO,KAAK,OACd,CAEQ,SAASC,EAAiB,CAChC,GAAI,GAACA,GAAQ,OAAOA,GAAS,UAAY,MAAM,QAAQA,CAAI,GAE3D,CAAIA,EAAK,GAAK,MAAM,QAAQA,EAAK,CAAC,GAChC,KAAK,oBAAoBA,EAAK,CAAC,EAGjC,QAAWC,KAAO,OAAO,KAAKD,CAAI,EAChC,GAAI,QAAQ,KAAKC,CAAG,EAAG,CACrB,IAAMC,EAAQF,EAAKC,CAAG,EAClBC,GAAS,OAAOA,GAAU,UAAY,CAAC,MAAM,QAAQA,CAAK,GAC5D,KAAK,SAASA,CAAK,CAEvB,CAGF,GAAIF,EAAK,GAAK,MAAM,QAAQA,EAAK,CAAC,EAChC,QAAWG,KAAQH,EAAK,EAClBG,GAAQ,OAAOA,GAAS,UAAY,CAAC,MAAM,QAAQA,CAAI,GACzD,KAAK,SAASA,CAAI,EAI1B,CAEQ,oBAAoBC,EAAyB,CACnD,QAASC,EAAI,EAAGA,EAAID,EAAQ,OAAS,EAAGC,IAAK,CAC3C,IAAMC,EAAOF,EAAQC,CAAC,EAChBE,EAAQH,EAAQC,EAAI,CAAC,EAErBG,EAAU,KAAK,cAAcF,EAAMC,CAAK,EAC1CC,GAAW,CAAC,KAAK,YAAY,IAAIA,EAAQ,SAAS,GACpD,KAAK,YAAY,IAAIA,EAAQ,UAAWA,EAAQ,WAAW,CAE/D,CACF,CAEQ,cACNF,EACAC,EACwD,CACxD,IAAIhB,EAAY,KAAK,mBAAmBe,CAAI,EAC5C,OAAIf,EACK,CAAE,UAAAA,EAAW,YAAa,OAAQ,GAG3CA,EAAY,KAAK,sBAAsBe,EAAMC,CAAK,EAC9ChB,EACK,CAAE,UAAAA,EAAW,YAAa,SAAU,GAG7CA,EAAY,KAAK,uBAAuBe,EAAMC,CAAK,EAC/ChB,EACK,CAAE,UAAAA,EAAW,YAAa,WAAY,EAGxC,MACT,CAEQ,kBAAkBkB,EAAuB,CAC/C,OAAI,OAAO,KAAQ,aAAe,OAAO,IAAI,QAAW,WAC/C,IAAI,OAAOA,CAAK,EAKlBA,EAAM,QAAQ,MAAO,MAAM,EAAE,QAAQ,KAAM,KAAK,CACzD,CAMQ,mBAAmBC,EAA6B,CACtD,IAAMC,EAAWD,EAAK,YAAY,GAAG,EAC/BE,EAAYF,EAAK,YAAY,GAAG,EACtC,OAAIC,IAAa,IAAMA,GAAYC,EAAkB,KAC9CF,EAAK,UAAUC,CAAQ,CAChC,CAEQ,mBAAmBE,EAAmC,CAC5D,IAAMC,EAAQD,EAAW,MAAM,kBAAkB,EACjD,OAAOC,EAAQA,EAAM,CAAC,EAAI,IAC5B,CAGQ,mBAAmBR,EAA6B,CACtD,GAAI,CAACA,EAAK,SAAS,SAAS,EAAG,OAAO,KACtC,IAAMO,EAAa,KAAK,mBAAmBP,CAAI,EAC/C,OAAKO,EACE,KAAK,mBAAmBA,CAAU,EADjB,IAE1B,CAGQ,sBAAsBP,EAAcC,EAA8B,CACxE,GAAI,CAACA,EAAM,WAAW,YAAY,EAAG,OAAO,KAC5C,IAAMQ,EAAWT,EAAK,MAAM,uCAAuC,EACnE,OAAOS,EAAWA,EAAS,CAAC,EAAI,IAClC,CAGQ,uBAAuBT,EAAcC,EAA8B,CAEzE,GADI,CAACD,EAAK,SAAS,GAAG,GAClB,CAAC,gBAAgB,KAAKC,CAAK,EAAG,OAAO,KAEzC,IAAMM,EAAa,KAAK,mBAAmBP,CAAI,EAK/C,GAJI,CAACO,GAID,CAAC,aAAa,KAAKA,CAAU,EAAG,OAAO,KAC3C,IAAMG,EAAYH,EAAW,MAAM,mBAAmB,EACtD,GAAI,CAACG,EAAW,OAAO,KACvB,IAAMC,EAAYD,EAAU,CAAC,EAAE,YAAY,EAC3C,OAAIC,IAAc,YAAcA,IAAc,QAAgB,KAEvD,KAAK,mBAAmBJ,CAAU,CAC3C,CAEQ,eACNK,EACA3B,EACAC,EACM,CACN,IAAM2B,EAAiBD,EAAQ,aAAa,cAAc,EACpDE,EAASD,EAAiB,SAASA,EAAgB,EAAE,EAAI,IACzDE,EACJ,OAAO,MAAMD,CAAM,GAAKA,EAAS,EAAI,IAA6BA,EAkB9DE,EAAgBC,GAhBH,IAAM,CACvB,GAAI,CAAC,KAAK,QAAS,OAEnB,IAAMd,EACJjB,IAAgB,aACf0B,EAA6B,OAAS,WAClCA,EAA6B,QAC7BA,EACE,MAET,KAAK,QAAQ,KAAK,CAChB,OAAQ,SACR,KAAM,CAAE,CAAC3B,CAAS,EAAGkB,CAAM,CAC7B,CAAC,CACH,EAE2CY,CAAI,EAIzCG,EACJhC,IAAgB,aAAe0B,aAAmB,kBAC9C,SACA,QAENA,EAAQ,iBAAiBM,EAAWF,CAAa,EAEjD,KAAK,gBAAgB,IAAIJ,EAAS,IAAM,CACtCA,EAAQ,oBAAoBM,EAAWF,CAAa,CACtD,CAAC,EAED,KAAK,OAAO,MACV,cAAcE,CAAS,uBAAuBjC,CAAS,iBAAiB8B,CAAI,KAC9E,CACF,CACF,ECzRO,IAAMI,GAAN,KAAyB,CAM9B,YAA6BC,EAAoC,CAApC,aAAAA,EAL7B,KAAQ,OAA2B,KACnC,KAAQ,eAAgC,KACxC,KAAQ,eAAiB,GACzB,KAAQ,kBAAoB,CAEsC,CAElE,SAAgB,CACd,KAAK,eAAiB,GACtB,KAAK,oBAAoB,EAEzB,KAAK,OAAS,IAAI,UAAU,KAAK,QAAQ,GAAG,EAC5C,IAAMC,EAAS,KAAK,OAEpBA,EAAO,OAAS,IAAM,CApC1B,IAAAC,EAAAC,EAsCM,KAAK,kBAAoB,GACzBA,GAAAD,EAAA,KAAK,SAAQ,SAAb,MAAAC,EAAA,KAAAD,EAAsBD,EACxB,EAEAA,EAAO,UAAaG,GAAgC,CA1CxD,IAAAF,EAAAC,GA2CMA,GAAAD,EAAA,KAAK,SAAQ,YAAb,MAAAC,EAAA,KAAAD,EAAyBE,EAC3B,EAEAH,EAAO,QAAWG,GAAsB,CA9C5C,IAAAF,EAAAC,GA+CMA,GAAAD,EAAA,KAAK,SAAQ,UAAb,MAAAC,EAAA,KAAAD,EAAuBE,GACnB,CAAC,KAAK,gBAAkB,KAAK,QAAQ,eACvC,KAAK,kBAAkB,CAE3B,EAEAH,EAAO,QAAWG,GAAiB,CArDvC,IAAAF,EAAAC,GAsDMA,GAAAD,EAAA,KAAK,SAAQ,UAAb,MAAAC,EAAA,KAAAD,EAAuBE,EACzB,CACF,CAEA,KAAKC,EAAoB,CACnB,KAAK,QAAU,KAAK,OAAO,aAAe,UAAU,MACtD,KAAK,OAAO,KAAKA,CAAI,CAEzB,CAEA,YAAmB,CAhErB,IAAAH,EAAAC,EAiEI,KAAK,eAAiB,GACtB,KAAK,oBAAoB,EACrB,KAAK,SACH,KAAK,OAAO,aAAe,UAAU,UACvCA,GAAAD,EAAA,KAAK,SAAQ,UAAb,MAAAC,EAAA,KAAAD,EACE,IAAI,WAAW,QAAS,CACtB,KAAM,IACN,OAAQ,GACR,SAAU,KAAK,OAAO,aAAe,UAAU,IACjD,CAAC,IAGL,KAAK,OAAO,OAAS,KACrB,KAAK,OAAO,UAAY,KACxB,KAAK,OAAO,QAAU,KACtB,KAAK,OAAO,QAAU,KACtB,KAAK,OAAO,MAAM,EAClB,KAAK,OAAS,KAElB,CAEA,WAA8B,CAC5B,OAAO,KAAK,MACd,CAEQ,mBAA0B,CA1FpC,IAAAA,EAAAC,EAAAG,EAAAC,EAAAC,EA2FI,KAAK,oBAAoB,EAGzB,IAAMC,GAAcP,EAAA,KAAK,QAAQ,uBAAb,KAAAA,EAAqC,GACzD,GAAIO,EAAc,GAAK,KAAK,mBAAqBA,EAAa,EAC5DH,GAAAH,EAAA,KAAK,SAAQ,oBAAb,MAAAG,EAAA,KAAAH,GACA,MACF,CAEA,KAAK,oBAGL,IAAMO,GAAYH,EAAA,KAAK,QAAQ,iBAAb,KAAAA,EAA+B,IAC3CI,GAAWH,EAAA,KAAK,QAAQ,oBAAb,KAAAA,EAAkC,KAC7CI,EAAmBF,EAAY,KAAK,IAAI,EAAG,KAAK,kBAAoB,CAAC,EAGrEG,EAAS,KAAK,OAAO,EAAI,IAGzBC,EAAQ,KAAK,IAAIF,EAAmBC,EAAQF,CAAQ,EAE1D,KAAK,eAAiB,OAAO,WAAW,IAAM,CAjHlD,IAAAT,EAAAC,GAkHMA,GAAAD,EAAA,KAAK,SAAQ,qBAAb,MAAAC,EAAA,KAAAD,EAAkC,KAAK,kBAAmBY,GAC1D,KAAK,QAAQ,CACf,EAAGA,CAAK,CACV,CAEQ,qBAA4B,CAC9B,KAAK,iBAAmB,OAC1B,aAAa,KAAK,cAAc,EAChC,KAAK,eAAiB,KAE1B,CACF,EAkBaC,GAAN,KAAuB,CAO5B,YAA6BC,EAAgC,CAAhC,YAAAA,EAN7B,KAAQ,UAAuC,KAI/C,KAAQ,gBAAiC,IAEqB,CAO9D,WAAWC,EAAuB,CAChC,KAAK,gBAAkBA,CACzB,CAEA,MAAM,SAA2C,CAjKnD,IAAAf,EAkKI,IAAMe,EAAU,KAAK,WAAW,EAMhC,GAAI,CAJgB,MAAMC,GACxBD,EACA,KAAK,OAAO,MACd,EAGE,MAAO,CAAE,eAAgB,GAAO,aADX,MAAME,GAAkBF,EAAS,KAAK,OAAO,MAAM,CAC3B,EAW/C,IAAIG,EACAC,EACEC,EAAc,IAAI,QAAgC,CAACC,EAASC,IAAW,CAC3EJ,EAAcG,EACdF,EAAaG,CACf,CAAC,EACGC,EAAU,GAMVC,EAAe,GAKfC,EAAsD,KACpDC,EAAcC,GAAsB,CACpCJ,IACJA,EAAU,GACNE,IAAkB,OACpB,aAAaA,CAAa,EAC1BA,EAAgB,MAEdE,EAAKR,EAAWQ,CAAG,EAClBT,EAAY,CAAE,eAAgB,EAAK,CAAC,EAC3C,EACAO,EAAgB,WAAW,IAAM,CAC/BC,EAAW,IAAI,MAAM,oCAAoC,CAAC,CAC5D,EAAG,GAAK,EAER,KAAK,UAAY,IAAI7B,GAAmB,CACtC,IAAK,KAAK,gBAAgB,EAC1B,cAAe,KAAK,OAAO,QAAQ,cACnC,eAAgB,KAAK,OAAO,QAAQ,eACpC,kBAAmB,KACnB,qBAAsB,GACtB,OAAQ,IAAM,CACZ2B,EAAe,GACf,KAAK,OAAO,YAAY,EACxBE,EAAW,CACb,EACA,UAAYxB,GAAU,CACpB,GAAI,CACF,IAAM0B,EAA0B,KAAK,MAAM1B,EAAM,IAAI,EACrD,KAAK,OAAO,UAAU0B,EAAS1B,CAAK,CACtC,OAAS2B,EAAO,CACd,KAAK,OAAO,OAAO,MAAM,qCAAsCA,CAAK,CACtE,CACF,EACA,QAAS,IAAM,CASTL,EACF,KAAK,OAAO,eAAe,EAE3BE,EAAW,IAAI,MAAM,mCAAmC,CAAC,CAE7D,EACA,mBAAoB,CAACI,EAASlB,IAAU,CAvP9C,IAAAZ,EAAAC,GAwPQA,GAAAD,EAAA,KAAK,QAAO,qBAAZ,MAAAC,EAAA,KAAAD,EAAiC8B,EAASlB,EAC5C,EACA,kBAAmB,IAAM,CA1P/B,IAAAZ,EAAAC,GA2PQA,GAAAD,EAAA,KAAK,QAAO,oBAAZ,MAAAC,EAAA,KAAAD,EACF,EACA,QAAUE,GAAU,CA7P1B,IAAAF,EAAAC,GA8PQA,GAAAD,EAAA,KAAK,QAAO,UAAZ,MAAAC,EAAA,KAAAD,EAAsBE,GAUtBwB,EAAW,IAAI,MAAM,oCAAoC,CAAC,CAC5D,CACF,CAAC,EAED,KAAK,UAAU,QAAQ,EAEvB,GAAI,CACF,OAAO,MAAMN,CACf,OAASO,EAAK,CACZ,YAAK,OAAO,OAAO,KAAK,8CAA+CA,CAAG,GAG1E3B,EAAA,KAAK,YAAL,MAAAA,EAAgB,aAChB,KAAK,UAAY,KAEV,CAAE,eAAgB,GAAO,aADX,MAAMiB,GAAkBF,EAAS,KAAK,OAAO,MAAM,CAC3B,CAC/C,CACF,CAEA,YAAmB,CA3RrB,IAAAf,GA4RIA,EAAA,KAAK,YAAL,MAAAA,EAAgB,aAChB,KAAK,UAAY,IACnB,CAEA,KAAKG,EAAoB,CAhS3B,IAAAH,GAiSIA,EAAA,KAAK,YAAL,MAAAA,EAAgB,KAAKG,EACvB,CAEA,eAAoC,CApStC,IAAAH,EAAAC,EAqSI,OAAOA,GAAAD,EAAA,KAAK,YAAL,YAAAA,EAAgB,cAAhB,YAAAC,EAA6B,UACtC,CAEA,WAA8B,CAxShC,IAAAD,EAAAC,EAySI,OAAOA,GAAAD,EAAA,KAAK,YAAL,YAAAA,EAAgB,cAAhB,KAAAC,EAA+B,IACxC,CAEQ,iBAA0B,CAChC,IAAMc,EAAU,KAAK,iBAAmB,KAAK,OAAO,QAAQ,SAAW,QACjEgB,EAAU,KAAK,OAAO,QAAQ,MACpC,OAAIA,GAIG,GADU,OAAO,SAAS,WAAa,SAAW,OAAS,KAChD,KAAK,OAAO,SAAS,IAAI,GAAGhB,CAAO,EACvD,CAEQ,YAAqB,CAC3B,OACE,KAAK,iBACL,KAAK,OAAO,QAAQ,SACpB,OAAO,SAAS,SAAW,OAAO,SAAS,MAE/C,CACF,EAEA,eAAsBC,GACpBD,EACAiB,EACkB,CAClB,GAAI,CAKF,IAAMC,GAJW,MAAM,MAAMlB,EAAS,CACpC,OAAQ,MACV,CAAC,GAEyB,QAAQ,IAAI,0BAA0B,EAChE,OAAIkB,EACKA,IAAa,UAGf,EACT,OAASJ,EAAO,CACd,OAAAG,GAAA,MAAAA,EAAQ,KAAK,0CAA2CH,GACjD,EACT,CACF,CAEA,eAAsBZ,GACpBF,EACAiB,EACgC,CAChC,GAAI,CACF,IAAME,EAAW,MAAM,MAAMnB,EAAS,CACpC,OAAQ,MACR,YAAa,UACb,QAAS,CACP,OAAQ,kBACV,CACF,CAAC,EAED,GAAI,CAACmB,EAAS,GACZ,MAAM,IAAI,MAAM,kCAAkCA,EAAS,MAAM,EAAE,EAGrE,OAAQ,MAAMA,EAAS,KAAK,CAC9B,OAASL,EAAO,CACd,OAAAG,GAAA,MAAAA,EAAQ,KAAK,iCAAkCH,GACxC,IACT,CACF,CC/VO,IAAMM,GAAN,KAAqC,CAI1C,MAAM,OACJC,EACAC,EACAC,EACe,CACf,GAAM,CAAE,KAAAC,CAAK,EAAIH,EAGjBA,EAAM,gBAAkB,IAAI,gBAE5B,GAAI,CAEF,IAAMI,EAAM,IAAI,eAGhBA,EAAI,OAAO,iBAAiB,WAAaC,GAAM,CACzCA,EAAE,mBACJL,EAAM,cAAgBK,EAAE,OACxBL,EAAM,SAAW,KAAK,MAAOK,EAAE,OAASA,EAAE,MAAS,GAAG,EAElDH,GACFA,EAAWF,CAAK,EAGtB,CAAC,EAGDA,EAAM,gBAAgB,OAAO,iBAAiB,QAAS,IAAM,CAC3DI,EAAI,MAAM,CACZ,CAAC,EAGD,IAAME,EAAgB,IAAI,QAAc,CAACC,EAASC,IAAW,CAC3DJ,EAAI,iBAAiB,OAAQ,IAAM,CAC7BA,EAAI,QAAU,KAAOA,EAAI,OAAS,KACpCJ,EAAM,KAAO,GACbA,EAAM,SAAW,IACjBO,EAAQ,GAERC,EAAO,IAAI,MAAM,gCAAgCJ,EAAI,MAAM,KAAKA,EAAI,UAAU,EAAE,CAAC,CAErF,CAAC,EAEDA,EAAI,iBAAiB,QAAS,IAAM,CAClCI,EAAO,IAAI,MAAM,iCAAiC,CAAC,CACrD,CAAC,EAEDJ,EAAI,iBAAiB,QAAS,IAAM,CAClCI,EAAO,IAAI,MAAM,qBAAqB,CAAC,CACzC,CAAC,CACH,CAAC,EAMD,GAHAJ,EAAI,KAAK,MAAOH,EAAK,GAAG,EAGpBA,EAAK,QACP,OAAW,CAACQ,EAAKC,CAAK,IAAK,OAAO,QAAQT,EAAK,OAAO,EACpDG,EAAI,iBAAiBK,EAAKC,CAAK,EAKnCN,EAAI,KAAKD,CAAI,EAEb,MAAMG,CACR,OAASK,EAAO,CACd,MAAAX,EAAM,MAAQW,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EAC7DA,CACR,CACF,CACF,EC7DO,IAAMC,GAAN,KAAoB,CAwBzB,YACUC,EACRC,EAAgC,CAAC,EACjC,CAFQ,iBAAAD,EAxBV,KAAQ,QAAoC,IAAI,IAChD,KAAQ,aAAoC,IAAI,IAChD,KAAQ,cAA+C,IAAI,IAC3D,KAAQ,iBAAyC,IAAI,IAErD,KAAQ,UAAmC,IAAI,IAa/C,KAAQ,kBAA0C,IAAI,IACtD,KAAQ,eAAuC,IAAI,IACnD,KAAQ,YAAmC,IAAI,IAC/C,KAAQ,cAA0D,IAAI,QAMpE,KAAK,UAAYC,EAAQ,WAAa,IAAM,KAC5C,KAAK,WAAaA,EAAQ,WAC1B,KAAK,WAAaA,EAAQ,WAC1B,KAAK,QAAUA,EAAQ,QACvB,KAAK,oBAAsBA,EAAQ,oBACnC,KAAK,YAAcA,EAAQ,YAC3B,KAAK,gBAAkBA,EAAQ,gBAG/B,KAAK,UAAU,IAAI,KAAM,IAAIC,EAAY,CAC3C,CAOQ,eAAeC,EAA0B,CAC/C,OAAQA,EAAM,KAAM,CAClB,IAAK,UACH,KAAK,cAAcA,CAAK,EACxB,OACF,IAAK,UACE,KAAK,cAAcA,CAAK,EAC7B,OACF,IAAK,SACH,GAAIA,EAAM,SACH,KAAK,eAAeA,EAAOA,EAAM,QAAQ,MACzC,CAGL,IAAMC,EAAM,wDACZD,EAAM,MAAQC,EACV,KAAK,SAAS,KAAK,QAAQD,EAAOC,CAAG,EACzC,KAAK,eAAeD,EAAM,UAAU,CACtC,CACA,OACF,IAAK,SACL,QAGO,KAAK,cAAcA,CAAK,CACjC,CACF,CAQQ,aAAaA,EAA0B,CAC7CA,EAAM,KAAO,GACbA,EAAM,SAAW,IAIb,KAAK,YACP,KAAK,WAAWA,CAAK,EAEnB,KAAK,YACP,KAAK,WAAWA,EAAM,WAAY,CAACA,CAAK,CAAC,EAE3C,KAAK,eAAeA,EAAM,UAAU,EACpC,KAAK,eAAeA,EAAM,UAAU,CACtC,CAKA,qBAAqBE,EAAoB,CACxBA,EAAU,iBACvB,gCACF,EAEO,QAASC,GAAU,CACxB,IAAMC,EAAaD,EAAM,aAAa,YAAY,EAClD,GAAI,CAACC,EAAY,OAGjB,IAAMC,EAAkB,KAAK,cAAc,IAAIF,CAAK,EAChDE,GACFF,EAAM,oBAAoB,SAAUE,CAAe,EAIrD,IAAMC,EAAWC,GAAa,CAC5B,IAAMC,EAASD,EAAE,OAA4B,MACzC,CAACC,GAASA,EAAM,SAAW,GAK/B,KAAK,YAAYJ,EAAY,MAAM,KAAKI,CAAK,EAAGL,CAAK,CACvD,EAEAA,EAAM,iBAAiB,SAAUG,CAAO,EAExC,KAAK,cAAc,IAAIH,EAAOG,CAAO,CACvC,CAAC,CACH,CAKA,MAAM,YACJF,EACAI,EACAC,EACe,CAEf,KAAK,aAAa,IAAIL,EAAYI,CAAK,EACnCC,EACF,KAAK,cAAc,IAAIL,EAAYK,CAAW,EAE9C,KAAK,cAAc,OAAOL,CAAU,EAItC,IAAMM,EAA+BF,EAAM,IAAKG,IAAU,CACxD,KAAMA,EAAK,KACX,KAAMA,EAAK,MAAQ,2BACnB,KAAMA,EAAK,IACb,EAAE,EAEIC,EAAmC,CACvC,OAAQ,eACR,YAAaR,EACb,MAAOM,CACT,EAOA,GAAI,EADc,KAAK,YAAc,KAAK,YAAY,EAAI,IAC1C,CACd,GAAI,CAAC,KAAK,gBAAiB,CACzB,KAAK,UACHN,EACAI,EACA,4EACF,EACA,MACF,CACA,IAAMK,EAAa,IAAI,gBACvB,KAAK,kBAAkB,IAAIA,CAAU,EACrC,GAAI,CACF,IAAMC,EAAW,MAAM,KAAK,gBAC1BF,EACAC,EAAW,MACb,EAGKA,EAAW,OAAO,SACrB,MAAM,KAAK,0BAA0BC,CAAQ,CAEjD,OAASC,EAAO,CACd,KAAK,UACHX,EACAI,EACAO,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,CACvD,CACF,QAAE,CACA,KAAK,kBAAkB,OAAOF,CAAU,CAC1C,CACA,MACF,CAEA,KAAK,YAAYD,CAAY,CAC/B,CAIQ,UAAUR,EAAoBI,EAAeQ,EAAuB,CAC1E,KAAK,aAAa,OAAOZ,CAAU,EACnC,KAAK,cAAc,OAAOA,CAAU,EACpC,IAAMa,EAAU,KAAK,QAChBA,GAGLT,EAAM,QAAQ,CAACG,EAAMO,IAAM,CACzBD,EACE,CACE,GAAI,WAAWb,CAAU,IAAIc,CAAC,GAC9B,KAAAP,EACA,WAAAP,EACA,SAAU,EACV,cAAe,EACf,MAAO,GACP,KAAM,EACR,EACAY,CACF,CACF,CAAC,CACH,CAKA,MAAM,0BACJF,EACe,CA/PnB,IAAAK,EAgQI,GAAM,CAAE,YAAAC,EAAa,QAASC,CAAW,EAAIP,EAGzCO,EAAW,OAAS,GACtB,KAAK,iBAAiB,IAAID,EAAaC,EAAW,CAAC,EAAE,WAAW,EAIlE,IAAMb,EAAQ,KAAK,aAAa,IAAIY,CAAW,EAC/C,GAAI,CAACZ,EAAO,CACV,QAAQ,MAAM,sCAAsCY,CAAW,EAAE,EACjE,MACF,CAGA,KAAK,aAAa,OAAOA,CAAW,EAIpC,IAAMX,EAAc,KAAK,cAAc,IAAIW,CAAW,EACtD,KAAK,cAAc,OAAOA,CAAW,EAGrC,IAAME,EAAU,IAAI,IACpB,QAAWX,KAAQH,EACjBc,EAAQ,IAAIX,EAAK,KAAMA,CAAI,EAI7B,IAAMY,EAAyB,CAAC,EAEhC,QAAWC,KAAQH,EAAY,CAC7B,IAAMV,EAAOW,EAAQ,IAAIE,EAAK,WAAW,EAEzC,GAAI,CAACb,EAAM,CACT,QAAQ,KACN,2BAA2Ba,EAAK,QAAQ,kBAAkBA,EAAK,WAAW,GAC5E,EACA,QACF,CAEA,IAAMxB,EAAqB,CACzB,GAAIwB,EAAK,SACT,KAAAb,EACA,WAAYS,EACZ,SAAU,EACV,cAAe,EACf,MAAOI,EAAK,MACZ,KAAM,GACN,MAAOA,EAAK,MAIZ,MAAML,EAAAK,EAAK,OAAL,KAAAL,EAAcK,EAAK,SAAW,SAAW,SAC/C,SAAUA,EAAK,SACf,YAAAf,CACF,EAMA,GAJA,KAAK,QAAQ,IAAIT,EAAM,GAAIA,CAAK,EAChCuB,EAAQ,KAAKvB,CAAK,EAGd,CAACwB,EAAK,MAAO,CACX,KAAK,SAAWA,EAAK,OACvB,KAAK,QAAQxB,EAAOwB,EAAK,KAAK,EAEhC,QACF,CAIIA,EAAK,aACP,KAAK,eAAexB,CAAK,CAE7B,CACF,CAKA,MAAc,eACZA,EACAyB,EACe,CACf,GAAI,CACF,IAAMC,EAAW,KAAK,UAAU,IAAID,EAAK,QAAQ,EACjD,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,qBAAqBD,EAAK,QAAQ,EAAE,EAItD,MAAMC,EAAS,OAAO1B,EAAOyB,EAAM,KAAK,UAAU,EAGlD,IAAME,EAAyC,CAC7C,OAAQ,kBACR,YAAa3B,EAAM,WACnB,UAAW,CAACA,EAAM,EAAE,CACtB,EAEA,KAAK,YAAY2B,CAAe,EAChC,KAAK,aAAa3B,CAAK,CACzB,OAASe,EAAO,CACd,IAAMa,EAAWb,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACtEf,EAAM,MAAQ4B,EACV,KAAK,SACP,KAAK,QAAQ5B,EAAO4B,CAAQ,EAG9B,KAAK,eAAe5B,EAAM,UAAU,CACtC,CACF,CAQA,MAAc,cAAcA,EAAmC,CAC7D,GAAI,CAAC,KAAK,oBAAqB,CAC7B,IAAMC,EACJ,gEACFD,EAAM,MAAQC,EACV,KAAK,SAAS,KAAK,QAAQD,EAAOC,CAAG,EACzC,KAAK,eAAeD,EAAM,UAAU,EACpC,MACF,CAEAA,EAAM,gBAAkB,IAAI,gBAC5B,KAAK,eAAe,IAAIA,EAAM,eAAe,EAIzC,KAAK,YAAY,KAAK,WAAWA,CAAK,EAC1C,GAAI,CACF,IAAM6B,EAAW,IAAI,SAGrBA,EAAS,IAAI,aAAc,UAAU7B,EAAM,UAAU,WAAW,EAGhE,KAAK,iBAAiB6B,EAAU7B,CAAK,EACrC6B,EAAS,IAAI7B,EAAM,WAAYA,EAAM,KAAMA,EAAM,KAAK,IAAI,EAE1D,MAAM,KAAK,oBAAoB6B,EAAU7B,EAAM,gBAAgB,MAAM,EACrE,KAAK,aAAaA,CAAK,CACzB,OAASe,EAAO,CACd,IAAMa,EAAWb,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACtEf,EAAM,MAAQ4B,EACV,KAAK,SACP,KAAK,QAAQ5B,EAAO4B,CAAQ,EAE9B,KAAK,eAAe5B,EAAM,UAAU,CACtC,QAAE,CACIA,EAAM,iBAAiB,KAAK,eAAe,OAAOA,EAAM,eAAe,EAG3EA,EAAM,YAAc,MACtB,CACF,CAqBQ,iBAAiB6B,EAAoB7B,EAA0B,CArbzE,IAAAmB,EAsbI,IAAMW,GAAOX,EAAAnB,EAAM,cAAN,YAAAmB,EAAmB,KAChC,GAAI,CAACW,EAAM,OAIX,IAAMC,EAAiB,IAAI,IAC3B,QAAWC,KAAM,MAAM,KAAKF,EAAK,QAAQ,EAAG,CAC1C,IAAM3B,EAAQ6B,EACV7B,EAAM,OAAS,YAAcA,EAAM,MAAM4B,EAAe,IAAI5B,EAAM,IAAI,CAC5E,CACA,OAAW,CAAC8B,EAAMC,CAAK,IAAK,IAAI,SAASJ,CAAI,EAAE,QAAQ,EACjDG,IAAS,cAAgBC,aAAiB,MAAQH,EAAe,IAAIE,CAAI,GAG7EJ,EAAS,OAAOI,EAAMC,CAAK,CAE/B,CAQQ,cAAclC,EAA0B,CAE9C,IAAMmC,EAAO,KAAK,YAAY,IAAInC,EAAM,UAAU,EAC9CmC,GAAM,IAAI,gBAAgBA,CAAI,EAElC,IAAMC,EAAM,IAAI,gBAAgBpC,EAAM,IAAI,EAC1C,KAAK,YAAY,IAAIA,EAAM,WAAYoC,CAAG,EAC1C,KAAK,aAAapC,EAAM,WAAYoC,CAAG,EAKvC,KAAK,aAAapC,CAAK,CACzB,CAGQ,aAAaI,EAAoBgC,EAAmB,CAK1D,IAAIC,EACJ,GAAI,OAAO,KAAQ,aAAe,IAAI,OACpCA,EAAW,IAAI,OAAOjC,CAAU,UACvB,WAAW,KAAKA,CAAU,EACnCiC,EAAWjC,MAEX,QAEU,SAAS,iBACnB,6BAA6BiC,CAAQ,IACvC,EACI,QAASL,GAAO,CACdA,aAAc,iBACZA,EAAG,MAAQI,IAAKJ,EAAG,IAAMI,GACpBJ,EAAG,aAAa,KAAK,IAAMI,GACpCJ,EAAG,aAAa,MAAOI,CAAG,CAE9B,CAAC,CACH,CAOA,gBAAgBE,EAAqB,CACnC,IAAMC,EAASP,GAAgB,CAC7B,IAAMC,EAAOD,EAAG,aAAa,yBAAyB,EACtD,GAAI,CAACC,EAAM,OACX,IAAMG,EAAM,KAAK,YAAY,IAAIH,CAAI,EAChCG,IACDJ,aAAc,iBACZA,EAAG,MAAQI,IAAKJ,EAAG,IAAMI,GACpBJ,EAAG,aAAa,KAAK,IAAMI,GACpCJ,EAAG,aAAa,MAAOI,CAAG,EAE9B,EAEIE,EAAK,QAAQ,2BAA2B,GAAGC,EAAMD,CAAI,EACzDA,EACG,iBAA8B,2BAA2B,EACzD,QAAQC,CAAK,CAClB,CAGA,gBAAuB,CAIrB,QAAW1B,KAAc,KAAK,kBAC5BA,EAAW,MAAM,EAEnB,KAAK,kBAAkB,MAAM,EAC7B,QAAWA,KAAc,KAAK,eAC5BA,EAAW,MAAM,EAEnB,KAAK,eAAe,MAAM,EAC1B,QAAWuB,KAAO,KAAK,YAAY,OAAO,EACxC,IAAI,gBAAgBA,CAAG,EAEzB,KAAK,YAAY,MAAM,CACzB,CAKA,MAAc,cAAcpC,EAAmC,CAC7D,GAAM,CAAE,KAAAW,EAAM,GAAA6B,CAAG,EAAIxC,EACjByC,EAAS,EAGbzC,EAAM,gBAAkB,IAAI,gBAE5B,GAAI,CACF,KAAOyC,EAAS9B,EAAK,MAAM,CAEzB,GAAIX,EAAM,gBAAgB,OAAO,QAC/B,MAAM,IAAI,MAAM,kBAAkB,EAGpC,IAAM0C,EAAM,KAAK,IAAID,EAAS,KAAK,UAAW9B,EAAK,IAAI,EACjDgC,EAAQhC,EAAK,MAAM8B,EAAQC,CAAG,EAG9BE,EAAS,MAAM,KAAK,aAAaD,CAAK,EAGtCE,EAAmC,CACvC,OAAQ,eACR,SAAUL,EACV,aAAcI,EACd,OAAAH,EACA,MAAO9B,EAAK,IACd,EAEA,KAAK,YAAYkC,CAAY,EAG7BJ,EAASC,EACT1C,EAAM,cAAgByC,EACtBzC,EAAM,SAAW,KAAK,MAAOyC,EAAS9B,EAAK,KAAQ,GAAG,EAElD,KAAK,YACP,KAAK,WAAWX,CAAK,CAEzB,CAGAA,EAAM,KAAO,GACb,IAAM2B,EAAyC,CAC7C,OAAQ,kBACR,YAAa3B,EAAM,WACnB,UAAW,CAACwC,CAAE,CAChB,EAEA,KAAK,YAAYb,CAAe,EAChC,KAAK,aAAa3B,CAAK,CACzB,OAASe,EAAO,CACd,IAAMa,EAAWb,aAAiB,MAAQA,EAAM,QAAU,OAAOA,CAAK,EACtEf,EAAM,MAAQ4B,EACV,KAAK,SACP,KAAK,QAAQ5B,EAAO4B,CAAQ,EAG9B,KAAK,eAAe5B,EAAM,UAAU,CACtC,CACF,CAKA,sBAAsBgB,EAAsC,CAC1D,IAAMhB,EAAQ,KAAK,QAAQ,IAAIgB,EAAQ,QAAQ,EAC1ChB,IAELA,EAAM,SAAWgB,EAAQ,SACzBhB,EAAM,cAAgBgB,EAAQ,WAE1B,KAAK,YACP,KAAK,WAAWhB,CAAK,EAEzB,CAKA,aAAa8C,EAAuB,CAClC,IAAM9C,EAAQ,KAAK,QAAQ,IAAI8C,CAAO,EACjC9C,IAGDA,EAAM,iBACRA,EAAM,gBAAgB,MAAM,EAI9B,KAAK,YAAY,CACf,OAAQ,gBACR,SAAU8C,CACZ,CAAC,EAGD,KAAK,QAAQ,OAAOA,CAAO,EAC7B,CAKA,WAAW1C,EAAmC,CAC5C,IAAMmB,EAAyB,CAAC,EAChC,QAAWvB,KAAS,KAAK,QAAQ,OAAO,EAClCA,EAAM,aAAeI,GACvBmB,EAAQ,KAAKvB,CAAK,EAGtB,OAAOuB,CACT,CAMA,sBAAsBnB,EAA0B,CAE9C,IAAM2C,EAAgC,CAAC,EACvC,QAAW/C,KAAS,KAAK,QAAQ,OAAO,EAEpCA,EAAM,aAAeI,GACrBJ,EAAM,WAAa,GACnB,CAACA,EAAM,MACP,CAACA,EAAM,OAEP+C,EAAe,KAAK/C,CAAK,EAK7B,QAAWA,KAAS+C,EAClB,KAAK,eAAe/C,CAAK,CAE7B,CAKA,iBAAiBiC,EAAcP,EAA0B,CACvD,KAAK,UAAU,IAAIO,EAAMP,CAAQ,CACnC,CAMQ,eAAetB,EAA0B,CAEhC,SAAS,iBACtB,kCAAkCA,CAAU,IAC9C,EAEO,QAASD,GAAU,CAExBA,EAAM,MAAQ,EAChB,CAAC,CACH,CAQA,eAAeC,EAAqB4C,EAAgB,IAAY,CAC9D,WAAW,IAAM,CACf,IAAMC,EAA4B,CAAC,EAEnC,OAAW,CAACT,EAAIxC,CAAK,IAAK,KAAK,QAEzBI,GAAcJ,EAAM,aAAeI,IAGnCJ,EAAM,MAAQA,EAAM,QACtBiD,EAAgB,KAAKT,CAAE,EAI3B,QAAWA,KAAMS,EACf,KAAK,QAAQ,OAAOT,CAAE,CAE1B,EAAGQ,CAAK,CACV,CAKQ,aAAaE,EAA6B,CAChD,OAAO,IAAI,QAAQ,CAACC,EAASC,IAAW,CACtC,IAAMC,EAAS,IAAI,WACnBA,EAAO,OAAS,IAAM,CAGpB,IAAMT,EAFSS,EAAO,OAEA,MAAM,GAAG,EAAE,CAAC,EAClCF,EAAQP,CAAM,CAChB,EACAS,EAAO,QAAUD,EACjBC,EAAO,cAAcH,CAAI,CAC3B,CAAC,CACH,CACF,EC5uBA,IAAMI,GAA0C,CAC9C,OAAQ,EACR,MAAO,EACP,KAAM,EACN,KAAM,EACN,MAAO,CACT,EAQMC,GAAgB,eAKTC,GAAN,MAAMC,CAAO,CAClB,YACmBC,EACAC,EAAkB,CAAC,EACnBC,EAAgB,QACjC,CAHiB,WAAAF,EACA,WAAAC,EACA,UAAAC,CAChB,CAEH,SAASC,EAAuB,CAC9B,KAAK,MAAM,MAAQA,CACrB,CAEA,UAAqB,CACnB,OAAO,KAAK,MAAM,KACpB,CAEA,MAAMF,EAAuB,CAC3B,OAAO,IAAIF,EAAO,KAAK,MAAO,CAAC,GAAG,KAAK,MAAOE,CAAK,EAAG,KAAK,IAAI,CACjE,CAEA,gBAA0B,CACxB,OAAO,KAAK,UAAU,OAAO,CAC/B,CAEA,SAASG,EAAuB,CAC9B,KAAK,IAAI,QAAS,QAASA,CAAI,CACjC,CAEA,QAAQA,EAAuB,CAC7B,KAAK,IAAI,OAAQ,OAAQA,CAAI,CAC/B,CAEA,QAAQA,EAAuB,CAC7B,KAAK,IAAI,OAAQ,OAAQA,CAAI,CAC/B,CAEA,SAASA,EAAuB,CAC9B,KAAK,IAAI,QAAS,QAASA,CAAI,CACjC,CAEQ,IAAID,EAAiBE,EAAuBD,EAAuB,CACzE,GAAI,CAAC,KAAK,UAAUD,CAAK,EACvB,QAIC,KAAK,KAAKE,CAAM,GAChB,QAAQA,CAAM,GACf,QAAQ,KACH,MAAM,KAAK,KAAM,CAAC,KAAK,aAAa,EAAG,GAAGD,CAAI,CAAC,CACxD,CAEQ,UAAUD,EAA0B,CAC1C,OAAOP,GAAcO,CAAK,GAAKP,GAAc,KAAK,MAAM,KAAK,CAC/D,CAEQ,cAAuB,CAC7B,OAAI,KAAK,MAAM,SAAW,EACjB,IAAIC,EAAa,IAGnB,IAAIA,EAAa,IAAI,KAAK,MAAM,KAAK,GAAG,CAAC,GAClD,CACF,EAQO,SAASS,GAAaC,EAAyB,CAAC,EAAW,CA3FlE,IAAAC,EAAAC,EA4FE,IAAMT,EAAkB,CACtB,OAAOQ,EAAAD,EAAQ,QAAR,KAAAC,EAAiB,MAC1B,EAEMP,EAAQ,MAAM,QAAQM,EAAQ,KAAK,EACrCA,EAAQ,MACRA,EAAQ,MACR,CAACA,EAAQ,KAAK,EACd,CAAC,EAEL,OAAO,IAAIT,GAAOE,EAAOC,GAAOQ,EAAAF,EAAQ,OAAR,KAAAE,EAAgB,OAAO,CACzD,CCjGA,eAAsBC,GACpBC,EACAC,EACuB,CACvB,GAAI,CACF,IAAMC,EAAe,mCAAoB,QACzC,GAAI,OAAOA,GAAgB,WAAY,CACrC,IAAMC,EAAKD,EAAY,IAAI,EACrBE,EAAa,KAAK,MAAMD,EAAG,aAAaF,EAAY,MAAM,CAAC,EACjE,OAAOD,EAAO,YAAYI,CAAU,CACtC,CAGA,IAAMA,EAAa,MADF,MAAM,MAAMH,CAAU,GACL,KAAK,EACvC,OAAOD,EAAO,YAAYI,CAAU,CACtC,OAASC,EAAO,CACd,MAAM,IAAI,MAAM,8BAA8BJ,CAAU,KAAKI,CAAK,EAAE,CACtE,CACF,CAKO,SAASC,GACdC,EACAC,EAIA,CACA,IAAMC,EAAwB,CAAC,EAEzBC,EAAiBC,GACdA,EAAK,QAAQ,OAAQ,GAAG,EAAE,QAAQ,SAAU,IAAI,EAAE,KAAK,EAG1DC,EAAqBF,EAAcH,CAAQ,EAC3CM,EAAmBH,EAAcF,CAAM,EAE7C,GAAII,IAAuBC,EACzB,MAAO,CAAE,MAAO,GAAM,YAAa,CAAC,CAAE,EAGxC,IAAMC,EAAgBF,EAAmB,MAAM;AAAA,CAAI,EAC7CG,EAAcF,EAAiB,MAAM;AAAA,CAAI,EACzCG,EAAW,KAAK,IAAIF,EAAc,OAAQC,EAAY,MAAM,EAElE,QAASE,EAAI,EAAGA,EAAID,EAAUC,IAAK,CACjC,IAAMC,EAAeJ,EAAcG,CAAC,GAAK,GACnCE,EAAaJ,EAAYE,CAAC,GAAK,GAEjCC,IAAiBC,IACnBV,EAAY,KAAK,QAAQQ,EAAI,CAAC,GAAG,EACjCR,EAAY,KAAK,eAAeS,CAAY,EAAE,EAC9CT,EAAY,KAAK,eAAeU,CAAU,EAAE,EAEhD,CAEA,MAAO,CAAE,MAAO,GAAO,YAAAV,CAAY,CACrC,C3BDO,IAAMW,GAAN,MAAMC,CAAmB,CAgE9B,YAAYC,EAAqC,CAAC,EAAG,CA7DrD,KAAQ,qBAA+B,EACvC,KAAQ,2BAAsC,GAG9C,KAAQ,MAAuB,KAI/B,KAAO,GAAuB,KAC9B,KAAQ,eAAiC,KAEzC,KAAQ,QAAmB,GAC3B,KAAQ,cAA+B,KAGvC,KAAQ,oBACN,IAAI,QAYN,KAAQ,cAAyB,GAGjC,KAAQ,aAAuB,EAM/B,KAAQ,gBAA0B,EAOlC,KAAQ,gBAAiC,KAUzC,KAAQ,0BAAqC,GAC7C,KAAQ,SAAmB,EAC3B,KAAQ,aAAwB,GAChC,KAAQ,kBAAyC,KACjD,KAAQ,gBAA6D,KACrE,KAAQ,yBAAiE,KAGvE,GAAM,CAAE,OAAQC,EAAgB,SAAAC,EAAU,MAAAC,EAAO,GAAGC,CAAY,EAAIJ,EAC9DK,EAAgBH,GAAA,KAAAA,EAAaC,EAAQ,QAAU,OAC/CG,EAAaL,GAAA,KAAAA,EAAkBM,GAAa,CAAE,MAAOF,CAAc,CAAC,EAEtEJ,EACEC,EACFD,EAAe,SAASC,CAAQ,EACvBC,GACTF,EAAe,SAAS,OAAO,EAGjCK,EAAW,SAASD,CAAa,EAGnC,KAAK,OAASC,EAAW,MAAM,QAAQ,EAEvC,KAAK,QAAU,CACb,cAAe,GACf,eAAgB,IAchB,QAAS,OAAO,SAAS,SAAW,OAAO,SAAS,OACpD,GAAGF,CACL,EAEA,KAAK,aAAe,IAAII,GAAa,KAAK,OAAO,MAAM,cAAc,CAAC,EACtE,KAAK,gBAAkB,IAAIC,GAAgB,CACzC,OAAQ,KAAK,OAAO,MAAM,iBAAiB,EAC3C,WAAY,CAACC,EAAMC,EAAKC,EAASC,IAC/B,KAAK,aAAa,gBAAgBH,EAAMC,EAAKC,EAASC,CAAE,EAC1D,qBAAsB,CAACC,EAAIC,IAAS,KAAK,qBAAqBD,EAAIC,CAAI,EACtE,WAAY,CAACC,EAAWC,IAAQ,CA3KtC,IAAAC,EAkLQ,IAAMC,EAAQ,KAAK,aAAa,aAAa,EAAEH,CAAS,EACxD,GAAI,CAACG,GAAS,CAAC,MAAM,QAAQA,EAAM,CAAC,EAAG,OAAO,KAC9C,IAAMC,GAAQF,EAAAC,EAAM,IAAN,YAAAD,EAAS,MACvB,QAAWR,KAAQS,EAAM,EACvB,GAAI,GAACT,GAAQ,OAAOA,GAAS,YACzBA,EAAK,KAAOO,GAEdG,GACAV,EAAKU,CAAK,IAAM,QAChB,OAAOV,EAAKU,CAAK,CAAC,IAAMH,GAExB,OAAOP,EAGX,OAAO,IACT,EACA,YAAa,IAAM,CACjB,KAAK,sBACP,CACF,CAAC,EACD,KAAK,aAAe,IAAIW,GAAa,KAAK,OAAO,MAAM,cAAc,CAAC,EAEtE,KAAK,qBAAuB,IAAIC,GAChC,KAAK,iBAAmB,IAAIC,GAC5B,KAAK,aAAe,IAAIC,GAGxB,KAAK,cAAgB,IAAIC,GACtBC,GAAY,KAAK,KAAKA,CAAO,EAC9B,CACE,UAAW,IAAM,KACjB,WAAaC,GAAU,CAEjB,KAAK,gBACP,KAAK,eAAe,cAClB,IAAI,YAAY,sBAAuB,CACrC,OAAQ,CAAE,MAAAA,CAAM,CAClB,CAAC,CACH,CAEJ,EACA,WAAY,CAACC,EAAYC,IAAY,CACnC,KAAK,OAAO,KAAK,oBAAoBD,CAAU,GAAIC,CAAO,EACtD,KAAK,gBACP,KAAK,eAAe,cAClB,IAAI,YAAY,sBAAuB,CACrC,OAAQ,CAAE,WAAAD,EAAY,QAAAC,CAAQ,CAChC,CAAC,CACH,CAEJ,EACA,QAAS,CAACF,EAAOG,IAAU,CACzB,KAAK,OAAO,MAAM,oBAAoBH,EAAM,EAAE,IAAKG,CAAK,EACpD,KAAK,gBACP,KAAK,eAAe,cAClB,IAAI,YAAY,mBAAoB,CAClC,OAAQ,CAAE,MAAAH,EAAO,MAAAG,CAAM,CACzB,CAAC,CACH,CAEJ,EACA,oBAAqB,CAACC,EAAUC,IAC9B,KAAK,oBAAoBD,EAAUC,CAAM,EAC3C,YAAa,IACX,CAAC,KAAK,SAAW,KAAK,iBAAiB,cAAc,IAAM,EAC7D,gBAAiB,CAACN,EAASM,IACzB,KAAK,oBAAoBN,EAASM,CAAM,CAC5C,CACF,EAEA,KAAK,eAAiB,IAAIC,GACxB,CACE,kBAAmB,IAAM,KAAK,eAC9B,uBAAwB,IAAM,KAAK,oBACnC,WAAaC,GAAkB,KAAK,WAAWA,CAAK,EACpD,KAAOR,GAAiB,KAAK,KAAKA,CAAO,EACzC,kBAAmB,CAACS,EAAuBC,EAAgBL,IACzD,KAAK,kBAAkBI,EAAMC,EAAQL,CAAQ,EAC/C,oBAAqB,CACnBI,EACAE,EACAC,IAEA,KAAK,qBAAqB,oBACxBH,EACAE,EACAC,CACF,EACF,uBAAwB,IAAM,KAAK,iBAAiB,cAAc,EAClE,sBAAwBV,GACtB,KAAK,cAAc,sBAAsBA,CAAU,CACvD,EACA,KAAK,OAAO,MAAM,gBAAgB,CACpC,EAEA,KAAK,gBAAkB,IAAIW,GACzB,CACE,kBAAmB,IAAM,KAAK,eAC9B,yBAA0B,CAACC,EAAcC,IACvC,KAAK,yBAAyBD,EAAMC,CAAe,EACrD,aAAeC,GAAiB,KAAK,aAAaA,CAAI,EAKtD,gBAAiB,IACf,CAAC,KAAK,SACN,KAAK,iBAAiB,cAAc,IAAM,CAC9C,EACA,KAAK,OAAO,MAAM,iBAAiB,CACrC,EAEA,KAAK,gBAAkB,IAAIC,GACzB,CACE,kBAAmB,IAAM,KAAK,eAC9B,KAAOjB,GAAiB,KAAK,KAAKA,CAAO,CAC3C,EACA,KAAK,OAAO,MAAM,iBAAiB,CACrC,EAEA,KAAK,gBAAkB,IAAIkB,GACzB,CACE,kBAAmB,IAAM,KAAK,eAC9B,KAAOlB,GAAY,KAAK,KAAKA,CAAO,CACtC,EACA,KAAK,OAAO,MAAM,iBAAiB,CACrC,EAEA,KAAK,iBAAmB,IAAImB,GAAiB,CAC3C,QAAS,KAAK,QACd,OAAQ,KAAK,OAAO,MAAM,WAAW,EACrC,YAAa,IAAM,CArTzB,IAAA3B,EAAA4B,EAAAC,EAsTQ,KAAK,GAAK,KAAK,iBAAiB,UAAU,EAC1C,KAAK,OAAO,KAAK,qBAAqB,EAItC,KAAK,sBAAsB,GAE3BD,GAAA5B,EAAA,KAAK,SAAQ,YAAb,MAAA4B,EAAA,KAAA5B,IACA6B,EAAA,KAAK,iBAAL,MAAAA,EAAqB,cAAc,IAAI,MAAM,eAAe,EAC9D,EACA,eAAgB,IAAM,CAhU5B,IAAA7B,EAAA4B,EAAAC,EAiUQ,KAAK,GAAK,KACV,KAAK,OAAO,KAAK,wBAAwB,GACzCD,GAAA5B,EAAA,KAAK,SAAQ,eAAb,MAAA4B,EAAA,KAAA5B,IACA6B,EAAA,KAAK,iBAAL,MAAAA,EAAqB,cAAc,IAAI,MAAM,kBAAkB,EACjE,EACA,UAAW,CAACC,EAAUC,IAAU,CAC9B,KAAK,uBAAuBD,EAAUC,CAAK,CAC7C,EACA,mBAAoB,IAAM,CACxB,KAAK,OAAO,KAAK,4BAA4B,CAC/C,EACA,QAAUnB,GAAU,CA5U1B,IAAAZ,EAAA4B,EA6UQ,KAAK,OAAO,MAAM,mBAAoBhB,CAAK,GAC3CgB,GAAA5B,EAAA,KAAK,SAAQ,UAAb,MAAA4B,EAAA,KAAA5B,EAAuBY,EACzB,CACF,CAAC,CACH,CAMA,OAAO,UAAiB,CACtB,IAAMoB,EAAiB3C,GAAa,CAAE,MAAO,iBAAkB,CAAC,EAC1D4C,EAAO,IAAM,CAzVvB,IAAAjC,EAAA4B,EAAAC,EA0VM,IAAMK,EAAU,SAAS,cAAc,eAAe,EACtD,GAAIA,EAAS,CACX,IAAMC,EAAS,IAAItD,EACnBsD,EAAO,eAAiBD,EAItBA,EAAQ,aAAa,kBAAkB,IAAM,SAE7CC,EAAO,iBAAiB,KAAK,EAC7BA,EAAO,aAAa,QAAQA,EAAO,cAAc,GAYnD,IAAMC,GACJP,GAAAD,EAAAM,EAAQ,aAAa,8BAA8B,IAAnD,KAAAN,GACA5B,EAAA,SAAS,OAAT,YAAAA,EAAe,aAAa,kCAD5B,KAAA6B,EAEA,KAGF,GAAIO,IAAiB,MAAQ,QAAQ,KAAKA,CAAY,EAAG,CACvD,IAAMC,EAAa,SAASD,EAAc,EAAE,EACxC,OAAO,SAASC,CAAU,GAAKA,GAAc,GAU/CH,EAAQ,iBACN,cACA,IAAM,CACJC,EAAO,iBAAiB,yBAAyBE,CAAU,CAC7D,EACA,CAAE,KAAM,GAAM,QAAS,EAAK,CAC9B,CAEJ,CAEAF,EAAO,QAAQ,EAAE,MAAOvB,GAAU,CAChCoB,EAAe,MAAM,sCAAuCpB,CAAK,CACnE,CAAC,EAGA,OAAe,mBAAqBuB,CACvC,CACF,EAGI,SAAS,aAAe,UAC1B,SAAS,iBAAiB,mBAAoBF,CAAI,EAElDA,EAAK,CAET,CAKQ,uBACNH,EACAC,EACM,CAnaV,IAAA/B,EAAA4B,EAAAC,EA0bI,IAAMS,EAAgBR,EAKtB,GAAIQ,EAAc,OAAS,QAAS,CAOlC,GAAI,OAAOA,EAAc,MAAS,SAAU,CAC1C,KAAK,OAAO,KACV,mEACAA,CACF,EACA,MACF,CACI,KAAK,eACP,KAAK,eAAe,cAClB,IAAI,YAAY,YAAa,CAC3B,OAAQ,CAAE,KAAMA,EAAc,KAAM,MAAOA,EAAc,KAAM,CACjE,CAAC,CACH,EAMA,KAAK,OAAO,KACV,qEACA,CAAE,KAAMA,EAAc,KAAM,MAAOA,EAAc,KAAM,CACzD,EAEF,MACF,CAGA,IAAMC,EAAgBT,EACtB,GAAIS,EAAc,OAAS,kBAAmB,CAC5C,KAAK,cAAc,sBACjBA,CACF,EACA,MACF,CAGA,GAAIA,EAAc,aAAeA,EAAc,QAAS,CACtD,IAAMC,EAAgBD,EAEtB,GAAI,CACF,KAAK,0BAA0BC,CAAa,CAC9C,OAAS5B,EAAO,CACd,KAAK,OAAO,MAAM,wCAAyCA,CAAK,CAClE,CACA,MACF,CAGA,GAAI2B,EAAc,aAAeA,EAAc,eAAe,SAAS,EAAG,CAEpEA,EAAc,QAChB,KAAK,OAAO,KAAK,oBAAoBA,EAAc,WAAW,EAAE,EAEhE,KAAK,OAAO,MAAM,kBAAkBA,EAAc,WAAW,GAAIA,EAAc,KAAK,EAEtF,MACF,CAEK,KAAK,gBACR,KAAK,iBAAiB,KAAK,EAC3B,KAAK,aAAa,OAAO,KAAK,cAAc,EAE1C,KAAK,gBACL,KAAK,eAAe,aAAa,kBAAkB,GAEnD,KAAK,eAAe,gBAAgB,kBAAkB,EAExD,KAAK,cAAgB,GAGrBE,GAAa,GAGX,KAAK,kBACHzC,EAAA8B,EAAS,OAAT,MAAA9B,EAAe,cACjB,KAAK,gBAAgB,gBAAgB8B,EAAS,KAAK,YAAY,EAKjE,KAAK,gBAAgB,eAAeA,EAAS,IAAI,EAEjD,KAAK,UAAU,KAAK,eAAgBA,EAAS,KAAMA,EAAS,IAAI,EAChE,KAAK,eAEL,KAAK,eAAe,cAClB,IAAI,YAAY,cAAe,CAC7B,OAAQ,CACN,aAAc,KAAK,aACnB,QAAQF,EAAAE,EAAS,OAAT,YAAAF,EAAe,OACvB,SAASC,EAAAC,EAAS,OAAT,YAAAD,EAAe,OAC1B,CACF,CAAC,CACH,EAEJ,CAMA,MAAM,QAAQa,EAA0B,gBAAgC,CA3iB1E,IAAA1C,EAAA4B,EA8iBI,GADA,KAAK,eAAiB,SAAS,cAAcc,CAAe,EACxD,CAAC,KAAK,eACR,MAAM,IAAI,MACR,iDAAiDA,CAAe,EAClE,EAGF,KAAK,iBAAiB,WAAW,EAEjC,IAAMC,EAAmB,MAAM,KAAK,iBAAiB,QAAQ,EAC7D,KAAK,QAAU,CAACA,EAAiB,eAE7B,KAAK,UACP,KAAK,GAAK,KACV,KAAK,OAAO,KAAK,0CAA0C,GAC3Df,GAAA5B,EAAA,KAAK,SAAQ,YAAb,MAAA4B,EAAA,KAAA5B,GACI2C,EAAiB,cAAgB,KAAK,gBACxC,KAAK,uBAAuBA,EAAiB,YAAY,GAI7D,KAAK,eAAe,qBAAqB,EAGzC,KAAK,eAAe,2BAA2B,EAG/C,KAAK,eAAe,yBAAyB,EAG7C,KAAK,eAAe,+BAA+B,EAGnDC,GAAuB,EAGvB,KAAK,eAAe,yBAAyB,EAG7C,KAAK,eAAe,yBAAyB,EAG7C,KAAK,gBAAgB,MAAM,KAAK,cAAc,EAG9CC,GAAgC,EAEhCC,GAAqB,EACrBC,GAAc,EAeV,KAAK,gBACPC,GAAuB,KAAK,eAAiBxC,GAAY,KAAK,KAAKA,CAAO,CAAC,EAI7EyC,GAA0B,KAAK,cAAc,EAG7C,KAAK,aAAa,OAAO,KAAK,cAAc,EAG5C,KAAK,gBAAgB,4BAA4B,EACjD,KAAK,gBAAgB,oCAAoC,EAEzD,KAAK,yBAAyB,CAChC,CAKA,YAAmB,CACjB,KAAK,iBAAiB,WAAW,EACjC,KAAK,GAAK,KACV,KAAK,QAAU,GACf,KAAK,SAAW,EAChB,KAAK,aAAe,GACpB,KAAK,4BAA4B,EACjC,KAAK,cAAc,eAAe,EAClC,KAAK,eAAe,kCAAkC,EACtDC,GAAiB,EACjBC,GAAwB,EACxB,KAAK,iBAAiB,0BAA0B,EAC5C,KAAK,iBACPC,GAA2B,KAAK,cAAc,EAC9CC,GAA6B,KAAK,cAAc,EAChDC,GAAmB,KAAK,cAAc,EACtCC,GAAY,KAAK,cAAc,EAC/BC,GAA0B,KAAK,cAAc,EAC7CC,GAAuB,KAAK,cAAc,GAE5C,KAAK,kBAAkB,CACzB,CAeQ,mBAA0B,CAChC,KAAK,aAAa,MAAM,EACxB,KAAK,gBAAgB,WAAW,EAChC,KAAK,aAAa,MAAM,EACxB,KAAK,gBAAgB,SAAS,EAC9B,KAAK,gBAAgB,SAAS,EAC9B,KAAK,qBAAqB,MAAM,EAChC,KAAK,iBAAiB,KAAK,EAC3B,KAAK,aAAa,OAAO,KAAK,cAAc,EAC5C,KAAK,MAAQ,KACb,KAAK,cAAgB,EACvB,CAEQ,0BAAiC,CACnC,KAAK,2BAA6B,OAAO,UAAa,cAC1D,KAAK,0BAA4B,GAEjC,KAAK,kBAAoB,IAAM,CAC7B,GAAI,SAAS,OAAQ,CAKf,CAAC,KAAK,SAAW,KAAK,iBAAiB,cAAc,IAAM,IAC7D,KAAK,SAAW,KAAK,IAAI,GAE3B,MACF,CACA,GAAI,KAAK,WAAa,EAAG,OACzB,IAAMC,EAAU,KAAK,IAAI,EAAI,KAAK,SAClC,KAAK,SAAW,EACZ,EAAAA,EAAU,MACd,KAAK,4BAA4B,CACnC,EAKA,KAAK,gBAAmB3B,GAA+B,CACjDA,EAAM,WACR,KAAK,4BAA4B,CAErC,EAEA,SAAS,iBAAiB,mBAAoB,KAAK,iBAAiB,EACpE,OAAO,iBAAiB,WAAY,KAAK,eAAe,EAC1D,CAEQ,6BAAoC,CACtC,KAAK,2BAA6B,OACpC,aAAa,KAAK,wBAAwB,EAC1C,KAAK,yBAA2B,MAE9B,GAAC,KAAK,2BAA6B,OAAO,UAAa,eACvD,KAAK,oBACP,SAAS,oBAAoB,mBAAoB,KAAK,iBAAiB,EACvE,KAAK,kBAAoB,MAEvB,KAAK,kBACP,OAAO,oBAAoB,WAAY,KAAK,eAAe,EAC3D,KAAK,gBAAkB,MAEzB,KAAK,0BAA4B,GACnC,CAEQ,6BAAoC,CAYtC,KAAK,2BAA6B,MACpC,aAAa,KAAK,wBAAwB,EAE5C,KAAK,yBAA2B,WAAW,IAAM,CAC/C,KAAK,yBAA2B,KAU9B,KAAK,gBACL,CAAC,KAAK,SACN,CAAC,KAAK,cACN,KAAK,iBAAiB,cAAc,IAAM,QAE1C,KAAK,2BAA2B,CAEpC,EAAG,GAAG,CACR,CAEA,MAAc,4BAA4C,CAzwB5D,IAAA/B,EA0wBI,GAAI,MAAK,aACT,MAAK,aAAe,GAEpB,GAAI,CACF,KAAK,OAAO,KAAK,sCAAsC,EAEvD,KAAK,iBAAiB,WAAW,EACjC,KAAK,GAAK,KACV,KAAK,kBAAkB,EAEvB,IAAM2D,EAAS,MAAM,KAAK,iBAAiB,QAAQ,EAQnD,GAAI,CAAC,KAAK,aAAc,OAExB,KAAK,QAAU,CAACA,EAAO,eAEnB,KAAK,UACP,KAAK,GAAK,KACNA,EAAO,cAAgB,KAAK,gBAC9B,KAAK,uBAAuBA,EAAO,YAAY,IAInD3D,EAAA,KAAK,iBAAL,MAAAA,EAAqB,cAAc,IAAI,MAAM,iBAAiB,EAChE,OAAS4D,EAAK,CACZ,KAAK,OAAO,MAAM,+BAAgCA,CAAG,CACvD,QAAE,CACA,KAAK,aAAe,EACtB,EACF,CAOQ,uBAA8B,CACpC,IAAMC,EAAM,IAAI,IAAI,OAAO,SAAS,IAAI,EAClCC,EAAc,CAAC,QAAS,SAAS,EACnCC,EAAiB,GAErB,QAAWC,KAASF,EACdD,EAAI,aAAa,IAAIG,CAAK,IAC5BH,EAAI,aAAa,OAAOG,CAAK,EAC7BD,EAAiB,IAIjBA,IACF,KAAK,OAAO,MAAM,sCAAsC,EACxD,OAAO,QAAQ,aAAa,KAAM,GAAIF,EAAI,SAAS,CAAC,EAExD,CAKA,SAAmB,CACjB,IAAM3B,EAAU,KAAK,eAErB,MAAI,CAACA,GAAWA,EAAQ,aAAa,kBAAkB,EAC9C,GAGL,KAAK,QACA,GAGU,KAAK,iBAAiB,cAAc,IACjC,CACxB,CAMA,KAAK1B,EAAoB,CACvB,IAAMyD,EAAa,KAAK,iBAAiB,cAAc,EAEnD,KAAK,OAAO,eAAe,GAC7B,KAAK,OAAO,MAAM,iBAAkB,CAClC,QAAAzD,EACA,QAAS,KAAK,QACd,aAAcyD,IAAe,OAC7B,WAAAA,CACF,CAAC,EAGC,KAAK,SACP,KAAK,OAAO,MAAM,0BAA0B,EAC5C,KAAK,SAASzD,CAAO,GACZyD,IAAe,GACxB,KAAK,OAAO,MAAM,uBAAuB,EACzC,KAAK,iBAAiB,KAAK,KAAK,UAAUzD,CAAO,CAAC,GACzCyD,IAAe,QACxB,KAAK,OAAO,KACV,+BAA+BA,CAAU,wBAC3C,EACA,KAAK,SAASzD,CAAO,GAErB,KAAK,OAAO,MAAM,wBAAwB,CAE9C,CAOQ,YAAqB,CAC3B,OAAO,KAAK,iBAAmB,KAAK,QAAQ,SAAW,OACzD,CAsBQ,aAAagB,EAAuB,CAC1C,IAAMqC,EAAM,IAAI,IAAIrC,EAAM,OAAO,SAAS,MAAM,EAC1C0C,EAA+B,CAAC,EAIhCC,EAAW,IAAI,IACrBN,EAAI,aAAa,QAAQ,CAACO,EAAGC,IAAM,CAC7BF,EAAS,IAAIE,CAAC,GAChB,KAAK,OAAO,KAAK,qGAAiG,CAAE,IAAKA,EAAG,KAAA7C,CAAK,CAAC,EAEpI2C,EAAS,IAAIE,CAAC,EACdH,EAAKG,CAAC,EAAID,CACZ,CAAC,EAED,IAAME,EAAaT,EAAI,SAAWA,EAAI,OAWtC,GAAI,KAAK,iBAAiB,cAAc,IAAM,EAAwB,CACpE,IAAMI,EAAa,KAAK,iBAAiB,cAAc,EACvD,OAAIA,IAAe,EACjB,KAAK,OAAO,MACV,oIAEA,CAAE,KAAAzC,CAAK,CACT,EAGK,KAAK,QAAQ,cAMhB,KAAK,OAAO,KACV,kFACA,CAAE,KAAAA,EAAM,WAAAyC,CAAW,CACrB,EARA,KAAK,OAAO,MACV,4FACA,CAAE,KAAAzC,EAAM,WAAAyC,CAAW,CACrB,EAQG,EACT,CAMA,YAAK,gBAAkBK,EACvB,KAAK,iBAAiB,WAAWA,CAAU,EAC3C,KAAK,OAAO,MAAM,eAAgB,CAAE,KAAA9C,EAAM,KAAA0C,CAAK,CAAC,EAChD,KAAK,KAAK,CAAE,OAAQ,eAAgB,KAAAA,CAAK,CAAC,EACnC,EACT,CAKA,MAAc,SAAS1D,EAA6B,CAClD,GAAI,CACF,IAAM+D,EAAU,KAAK,WAAW,EAC1BzC,EAAW,MAAM,MAAMyC,EAAS,CACpC,OAAQ,OACR,YAAa,UACb,QAAS,CACP,eAAgB,mBAChB,OAAQ,kBACV,EACA,KAAM,KAAK,UAAU/D,CAAO,CAC9B,CAAC,EAED,GAAI,CAACsB,EAAS,GACZ,MAAM,IAAI,MAAM,wBAAwBA,EAAS,MAAM,EAAE,EAI3D,IAAM0C,EAAiC,MAAM1C,EAAS,KAAK,EACvD,KAAK,gBACP,KAAK,UACH,KAAK,eACL0C,EAAe,KACfA,EAAe,IACjB,CAEJ,OAAS5D,EAAO,CACd,KAAK,OAAO,MAAM,+BAAgCA,CAAK,CACzD,CACF,CAcA,kBAAkBK,EAAuBC,EAAgBL,EAA0B,CACjF,KAAK,oBAAoBI,EAAMC,EAAQL,CAAQ,CACjD,CAEA,MAAc,oBACZI,EACAC,EACAL,EACe,CACf,GAAI,CACF,IAAM0D,EAAU,KAAK,WAAW,EAE1BzC,EAAW,MAAM,MAAMyC,EAAS,CACpC,OAAQ,OACR,YAAa,UACb,QAAS,CACP,OAAQ,kBAEV,EACA,KAAM1D,CACR,CAAC,EAED,GAAI,CAACiB,EAAS,GACZ,MAAM,IAAI,MAAM,kCAAkCA,EAAS,MAAM,EAAE,EAGrE,IAAM0C,EAAiC,MAAM1C,EAAS,KAAK,EACvD,KAAK,gBACP,KAAK,UACH,KAAK,eACL0C,EAAe,KACfA,EAAe,IACjB,CAEJ,OAAS5D,EAAO,CACd,KAAK,OAAO,MAAM,yCAA0CA,CAAK,CACnE,CACF,CAQA,MAAc,oBACZC,EACAC,EACe,CACf,IAAMyD,EAAU,KAAK,WAAW,EAC1BzC,EAAW,MAAM,MAAMyC,EAAS,CACpC,OAAQ,OACR,YAAa,UACb,QAAS,CACP,OAAQ,mBAGR,eAAgB,SAElB,EACA,KAAM1D,EACN,OAAAC,CACF,CAAC,EAED,GAAI,CAACgB,EAAS,GAAI,CAChB,IAAM2C,GAAU,MAAM3C,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,GAAG,KAAK,EAC5D,MAAM,IAAI,MACR,0BAA0BA,EAAS,MAAM,GAAG2C,EAAS,WAAMA,CAAM,GAAK,EAAE,EAC1E,CACF,CAEA,IAAMD,EAAiC,MAAM1C,EAAS,KAAK,EACvD,KAAK,gBACP,KAAK,UACH,KAAK,eACL0C,EAAe,KACfA,EAAe,IACjB,CAEJ,CAQA,MAAc,oBACZhE,EACAM,EAC8B,CAC9B,IAAMgB,EAAW,MAAM,MAAM,KAAK,WAAW,EAAG,CAC9C,OAAQ,OACR,YAAa,UACb,QAAS,CACP,eAAgB,mBAChB,OAAQ,mBACR,eAAgB,OAClB,EACA,KAAM,KAAK,UAAUtB,CAAO,EAC5B,OAAAM,CACF,CAAC,EACD,GAAI,CAACgB,EAAS,GAAI,CAChB,IAAM2C,GAAU,MAAM3C,EAAS,KAAK,EAAE,MAAM,IAAM,EAAE,GAAG,KAAK,EAC5D,MAAM,IAAI,MACR,kCAAkCA,EAAS,MAAM,GAAG2C,EAAS,WAAMA,CAAM,GAAK,EAAE,EAClF,CACF,CACA,OAAQ,MAAM3C,EAAS,KAAK,CAC9B,CA2BQ,kBAAkB4C,EAAenD,EAAkC,CACzE,IAAMoD,EAAe,SAAS,iBAC5B,+BACF,EACMC,EAAU,IAAI,IACpBD,EAAa,QAASE,GAAM,CAC1B,IAAMrD,EAAQqD,EAAsB,KAChCrD,GAAMoD,EAAQ,IAAIpD,CAAI,CAC5B,CAAC,EAED,IAAMsD,EAAeJ,EAAI,iBAAiB,+BAA+B,EACnEK,EAAU,IAAI,IAWpB,GAVAD,EAAa,QAASD,GAAM,CAC1B,IAAMG,EAAMH,EAAE,aAAa,MAAM,EACjC,GAAKG,EACL,GAAI,CACFD,EAAQ,IAAI,IAAI,IAAIC,EAAKzD,CAAe,EAAE,IAAI,CAChD,OAAQ0D,EAAA,CACNF,EAAQ,IAAIC,CAAG,CACjB,CACF,CAAC,EAEGJ,EAAQ,OAASG,EAAQ,KAAM,MAAO,GAC1C,IAAIG,EAAU,GACd,OAAAN,EAAQ,QAASpD,GAAS,CACnBuD,EAAQ,IAAIvD,CAAI,IAAG0D,EAAU,GACpC,CAAC,EACMA,CACT,CAQQ,sBAAsB3D,EAA+B,CAC3D,OAAO,SAAS,KAAOA,CACzB,CAEQ,yBAAyBD,EAAcC,EAA+B,CAjrChF,IAAAvB,EAkrCI,GAAI,CAAC,KAAK,eAAgB,OAG1B,IAAM0E,EADS,IAAI,UAAU,EACV,gBAAgBpD,EAAM,WAAW,EAC9C6D,EAAQ,KAAK,eAAe,aAAa,aAAa,EAKtDC,GAAepF,EAAA0E,EAAI,cAAc,OAAO,IAAzB,YAAA1E,EAA4B,YA+BjD,GA9BIoF,IACF,SAAS,MAAQA,GA6Bf,KAAK,kBAAkBV,EAAKnD,CAAe,EAAG,CAChD,KAAK,OAAO,KACV,wHACF,EACA,KAAK,sBAAsBA,CAAe,EAC1C,MACF,CAGA,IAAM8D,EAAaX,EAAI,cAAc,eAAe,EACpD,GAAIW,EAAY,CACd,IAAMC,EAAQD,EAAW,aAAa,aAAa,EAEnD,GAAI,CAACC,EAAO,CACV,KAAK,OAAO,KAAK,6DAA6D,EAC9E,OAAO,SAAS,OAAO,EACvB,MACF,CAKA,KAAK,gBAAgB,mBAAmBH,CAAK,EAC7C,KAAK,eAAe,mBAAmBA,CAAK,EAG5C,IAAMI,EAAU,EAAE,KAAK,gBAEvB,KAAK,WAAW,EAChB,KAAK,eAAe,aAAa,cAAeD,CAAK,EACrD,KAAK,eAAe,gBAClB,GAAG,MAAM,KAAKD,EAAW,UAAU,EAAE,IAAKG,GAAMA,EAAE,UAAU,EAAI,CAAC,CACnE,EASA,KAAK,eAAe,qBAAqB,EACzC,KAAK,gBAAgB,MAAM,KAAK,cAAc,EAG9C,OAAO,SAAS,EAAG,CAAC,EAoBpB,IAAMlB,EACJ,OAAO,SAAS,SAAW,OAAO,SAAS,OAQ7C,GAPA,KAAK,gBAAkBA,EACvB,KAAK,iBAAiB,WAAWA,CAAU,EAMvC,CAAC,KAAK,QAAS,CA8BjB,IAAMmB,EAAW,iBADCH,EAAM,QAAQ,SAAU,MAAM,CACL,KAC3C,KAAK,QAAQG,CAAQ,EAAE,MAAO7B,GAAQ,CAChC2B,IAAY,KAAK,kBACrB,KAAK,OAAO,MAAM,kCAAmC3B,CAAG,EACxD,OAAO,SAAS,OAAO,EACzB,CAAC,CACH,CACA,MACF,CAMA,KAAK,gBAAgB,mBAAmBuB,CAAK,EAC7C,KAAK,eAAe,mBAAmBA,CAAK,EAC5C,KAAK,WAAW,EAChB,IAAMO,EAAOhB,EAAI,cAAc,MAAM,EACjCgB,GACF,KAAK,eAAe,gBAClB,GAAG,MAAM,KAAKA,EAAK,UAAU,EAAE,IAAKF,GAAMA,EAAE,UAAU,EAAI,CAAC,CAC7D,EAEF,KAAK,eAAe,qBAAqB,EACzC,KAAK,gBAAgB,MAAM,KAAK,cAAc,CAChD,CAOQ,WAAWxE,EAAoB,CAErC,IAAM2E,EAAU3E,EAAM,KAAK,EAGrB4E,EAAM,WAAWD,CAAO,EAC9B,GAAI,CAAC,MAAMC,CAAG,EAAG,CAIf,GAAI,OAAO,UAAUA,CAAG,GAAK,KAAK,IAAIA,CAAG,EAAI,OAAO,iBAClD,OAAOD,EAGT,GAAIA,IAAYC,EAAI,SAAS,EAC3B,OAAOA,CAEX,CAGA,OAAI5E,IAAU,OAAe,GACzBA,IAAU,QAAgB,GAGvBA,CACT,CAOA,YAAY6E,EAAgC,CAC1C,OAAO,KAAK,aAAa,YAAYA,CAAM,CAC7C,CAQA,kBAAkBC,EAAsBD,EAA0B,CAEhE,IAAMlC,EAAS,KAAK,YAAYkC,CAAM,EAGtC,GAAI,CAAC,KAAK,MAAO,CACf,IAAME,EAAQD,EAAa,MAAM,uBAAuB,EACpDC,IACF,KAAK,MAAQA,EAAM,CAAC,EAExB,CAGA,IAAMC,EAAerC,EAAO,KAI5B,GAAI,CADcmC,EAAa,MAAM,0BAA0B,EAE7D,OAAOA,EAOT,IAAMG,EAFe,qBAAqB,KAAK,OAAS,aAAa,KAE/BD,EADnB,SAGnB,OAAOF,EAAa,QAClB,yBACA,SAASG,CAAc,SACzB,CACF,CAQA,UAAUC,EAAkBL,EAAkBM,EAA+B,CAU3E,KAAK,qBAAuB,EAC5B,KAAK,2BAA6B,GAOlC,IAAMxC,EAAS,KAAK,aAAa,YAAYkC,EAAQ,CACnD,iBAAkB,CAACO,EAAgBtG,IACvB,KAAK,gBAAgB,iBAC7BoG,EACAE,EACAtG,CACF,EACS,EAEb,CAAC,EAGKuG,EAAoBC,GACpB,CAACA,GAAQ,OAAOA,GAAS,SAAiB,GAC1CA,EAAK,GAAK,MAAM,QAAQA,EAAK,CAAC,EAAU,GACrC,OAAO,OAAOA,CAAI,EAAE,KAAMlC,GAAMiC,EAAiBjC,CAAC,CAAC,EAG5D,GAAI,CAACT,EAAO,SAAW,CAAC0C,EAAiBR,CAAM,EAAG,CAQ5CM,GACF,KAAK,qBAAqB,eAAeA,CAAI,EAE/C,MACF,CAKA,IAAMI,EAAc,SAAS,cAAcL,EAAQ,OAAO,EAuC1D,GArCI,KAAK,OAAO,eAAe,IAC7B,KAAK,OAAO,MAAM,+BAAgCA,EAAQ,OAAO,EACjE,KAAK,OAAO,MACV,6CACAvC,EAAO,KAAK,UAAU,EAAG,GAAG,CAC9B,EACA,KAAK,OAAO,MACV,+BACAA,EAAO,KAAK,SAAS,SAAS,CAChC,EACA,KAAK,OAAO,MACV,+BACAA,EAAO,KAAK,SAAS,SAAS,CAChC,EACA,KAAK,OAAO,MACV,4BACAA,EAAO,KAAK,SAAS,KAAK,CAC5B,GAoBE,gBAAgB,KAAKA,EAAO,IAAI,EAAG,CAMrC,IAAM6C,EAASN,EAAQ,QAAQ,YAAY,EACrCO,EAAWD,IAAW,QAAUA,IAAW,OAAU,MAAQA,EAE7D9B,EADS,IAAI,UAAU,EACV,gBACjB,IAAI+B,CAAO,IAAI9C,EAAO,IAAI,KAAK8C,CAAO,IACtC,WACF,EACMC,EAAOhC,EAAI,KAAK,kBAClBgC,EAQFH,EAAY,gBAAgB,GAAG,MAAM,KAAKG,EAAK,UAAU,CAAC,GAO1D,KAAK,OAAO,KAAK,yEAAyE,EAC1FH,EAAY,gBAAgB,GAAG,MAAM,KAAK7B,EAAI,KAAK,UAAU,CAAC,EAElE,MACE6B,EAAY,UAAY5C,EAAO,KAG7B,KAAK,OAAO,eAAe,IAC7B,KAAK,OAAO,MACV,uDACA4C,EAAY,UAAU,UAAU,EAAG,GAAG,CACxC,EACA,KAAK,OAAO,MACV,uCACAA,EAAY,UAAU,SAAS,SAAS,CAC1C,EACA,KAAK,OAAO,MACV,uCACAA,EAAY,UAAU,SAAS,SAAS,CAC1C,EACA,KAAK,OAAO,MACV,oCACAA,EAAY,UAAU,SAAS,KAAK,CACtC,GAYF,IAAMI,EAAW,SAAS,cAC1B,GACEA,aAAoB,kBACpBA,EAAS,gBAAgB,qBACzB,CAACJ,EAAY,cAAc,yBAAyB,EACpD,CACA,KAAK,OAAO,MAAM,8CAA8C,EAChE,KAAK,aAAa,sBAAsB,EACxC,MACF,CAKA,IAAMK,EAAkB,CACtB,aAAc,GACd,WAAaN,GAAc,CAEzB,GAAIA,EAAK,WAAa,EACpB,OACEA,EAAK,aAAa,UAAU,GAC5BA,EAAK,aAAa,cAAc,GAChC,MAGN,EACA,kBAAmB,CAACO,EAAaC,IAAc,CAM7C,GACEA,EAAK,WAAa,KAAK,cACtBA,EAAiB,aAAaC,CAAkB,EAEjD,MAAO,GAWT,GACED,EAAK,WAAa,KAAK,cACvBD,EAAO,WAAa,KAAK,aACzB,CACA,IAAMG,EAAWF,EAAiB,WAC5BG,EAAcJ,EACpB,QAASK,EAAI,EAAGA,EAAIF,EAAQ,OAAQE,IAAK,CACvC,IAAM1B,EAAIwB,EAAQE,CAAC,EAAE,KACrB,GACE1B,EAAE,OAAS,GACXA,EAAE,WAAW,CAAC,IAAM,KACpBA,EAAE,WAAW,CAAC,IAAM,KACpBA,EAAE,WAAW,CAAC,IAAM,KACpBA,EAAE,WAAW,CAAC,IAAM,IACpB,CAACyB,EAAY,aAAazB,CAAC,EAC3B,CACA,KAAK,2BAA6B,GAClC,KACF,CACF,CACF,CAOA,GACEqB,EAAO,WAAa,KAAK,cACxBA,EAAmB,aAAa,YAAY,GAC7C,CAAEC,EAAiB,aAAa,uBAAuB,EAEvD,MAAO,GAST,GACED,EAAO,WAAa,KAAK,cACxBA,EAAmB,aAAa,kBAAkB,GACnD,CAAEC,EAAiB,aAAa,uBAAuB,GACvDA,EAAK,WAAa,KAAK,aACvB,CACA,IAAMK,EAAaN,EAAmB,WAChCO,EAAYN,EAClB,QAASI,EAAI,EAAGA,EAAIC,EAAU,OAAQD,IAAK,CACzC,IAAMG,EAAOF,EAAUD,CAAC,EACnBE,EAAU,eAAeC,EAAK,aAAcA,EAAK,SAAS,GAC7DD,EAAU,eAAeC,EAAK,aAAcA,EAAK,KAAMA,EAAK,KAAK,CAErE,CACF,CAMA,GACER,aAAkB,qBAClB,CAAEC,EAAiB,aAAa,uBAAuB,EACvD,CACA,IAAMQ,EAAS,SAAS,cACxB,GACEA,aAAkB,kBAClBA,EAAO,OAAST,EAEhB,MAAO,EAEX,CA8DA,OA1DEA,aAAkB,mBAClBA,EAAO,aAAa,MAAM,GAC1B,CAAEC,EAAiB,aAAa,uBAAuB,GAEtDA,EAAiB,aAAa,OAAQ,EAAE,EAKzC,CAAEA,EAAiB,aAAa,uBAAuB,GACvDD,aAAkB,aAClBA,EAAO,aAAa,SAAS,GAC7BU,GAAuBV,CAAM,IAiB7BA,aAAkB,kBAClBC,aAAgB,mBACfD,EAAO,OAAS,YAAcA,EAAO,OAAS,WAE3CC,EAAK,aAAa,uBAAuB,GAC3CD,EAAO,QAAUC,EAAK,QAClBD,EAAO,OAAS,aAClBA,EAAO,cAAgBC,EAAK,eAE9BD,EAAO,gBAAgB,uBAAuB,IAE9CC,EAAK,QAAUD,EAAO,QAIlBA,EAAO,QACTC,EAAK,aAAa,UAAW,EAAE,EAE/BA,EAAK,gBAAgB,SAAS,EAE5BD,EAAO,OAAS,aAClBC,EAAK,cAAgBD,EAAO,iBAQ9B,KAAK,aAAa,iBAAiBA,CAAM,GACpC,GAYLA,EAAO,YAAYC,CAAI,EAEvB,GAACA,EAAK,aAAa,uBAAuB,IACzCA,EAAK,SAAS,SAAW,GACxBA,EAAK,cAAc,yBAAyB,IAAM,QASxD,KAAK,qBAAqBD,EAAQ,aAAa,EACxC,GACT,EACA,YAAcjH,GAAY,CAjyDhC,IAAAI,EAsyDYJ,aAAc,sBAChBA,EAAG,OAAQI,EAAAJ,EAAG,cAAH,KAAAI,EAAkB,IAK3BJ,aAAc,aAAeA,EAAG,aAAa,uBAAuB,GACtEA,EAAG,gBAAgB,uBAAuB,CAE9C,EACA,YAAc0G,GAAc,CAhzDlC,IAAAtG,EAkzDYsG,aAAgB,sBAClBA,EAAK,OAAQtG,EAAAsG,EAAK,cAAL,KAAAtG,EAAoB,IAE/BsG,aAAgB,aAAeA,EAAK,aAAa,uBAAuB,GAC1EA,EAAK,gBAAgB,uBAAuB,EAG1CA,EAAK,WAAa,KAAK,eACzB,KAAK,qBAAqBA,EAAiB,aAAa,EACxD,KAAK,uBAET,EACA,sBAAwBA,IAElBA,EAAK,WAAa,KAAK,cACzB,KAAK,qBAAqBA,EAAiB,eAAe,EAErD,GAEX,EAgBA,GAAI3C,EAAO,aAAeA,EAAO,YAAY,OAAS,EAAG,CACvD,IAAM6D,EAA+B,CAAC,EAClCC,EAAY,GAChB,QAAWC,KAAM/D,EAAO,YAAa,CACnC,IAAMgE,EAAY,KAAK,gBAAgB,MACrCzB,EACAwB,EACAd,CACF,EACIe,GACFA,EAAU,aAAaC,EAAuB,EAAE,EAChDJ,EAAkB,KAAKG,CAAS,GAEhCF,EAAY,EAEhB,CAEA,GAAIA,EAAW,CACb,KAAK,OAAO,KACV,iHACF,EAEA,QAAWI,KAAKL,EACdK,EAAE,gBAAgBD,CAAqB,EAGzC,IAAME,EAAW,KAAK,aAAa,YAAY,EAC/CvB,EAAY,UAAYuB,CAC1B,MACE,KAAK,gCAAgCvB,CAAW,CAEpD,CAEA,GAAI,CAEFwB,GAAS7B,EAASK,EAAaK,CAAe,CAChD,QAAE,CAGA,KAAK,gBAAgB,eAAeV,CAAO,CAC7C,CASA8B,GAAsB9B,CAAO,EAK7B,KAAK,cAAc,gBAAgBA,CAAO,EAG1C,KAAK,aAAa,sBAAsB,EAsBxC+B,GAAuB/B,CAAO,EAC9BgC,GAA0BhC,CAAO,EACjCiC,GAAwBjC,CAAO,EAC/BkC,GAAsBlC,CAAO,EAC7BmC,GAA0BnC,CAAO,EAMjCoC,GAA2BpC,EAAU1F,GAAY,KAAK,KAAKA,CAAO,CAAC,EAMnEwC,GAAuBkD,EAAU1F,GAAY,KAAK,KAAKA,CAAO,CAAC,EAe/D+H,GAA0BrC,CAAO,EACjCsC,GAAgBtC,CAAO,EACvBuC,GAASvC,CAAO,GACZ,KAAK,qBAAuB,GAAK,KAAK,8BACxCwC,GAAwBxC,EAAS,KAAK,gBAAkB,MAAS,EACjE,KAAK,eAAe,+BAA+BA,CAAO,EAC1D,KAAK,cAAc,qBAAqBA,CAAO,GAKjD,KAAK,gBAAgB,aAAa,EAE9BC,GACF,KAAK,qBAAqB,eAAeA,CAAI,CAEjD,CAKQ,0BAA0BrE,EAAqC,CACrE,KAAK,cAAc,0BAA0BA,CAAQ,CACvD,CAUQ,gCAAgCyE,EAA4B,CAClE,IAAMoC,EAAS,SAAS,iBACtBpC,EACA,WAAW,YACb,EACMqC,EAAuB,CAAC,EAC1BtC,EACJ,KAAQA,EAAOqC,EAAO,SAAS,GAAI,CACjC,IAAMd,EAAIvB,EACNuB,EAAE,WAAa,yBAAyB,KAAKA,EAAE,SAAS,GAC1De,EAAU,KAAKf,CAAC,CAEpB,CACA,QAAWA,KAAKe,EAAW,CACzB,IAAM7C,EAAQ8B,EAAE,UAAW,MAAM,0BAA0B,EACrDgB,EAAO9C,EAAQA,EAAM,CAAC,EAAI,GAC1B+C,EAASjB,EAAE,cACbiB,GACFA,EAAO,aAAa/B,EAAoB8B,CAAI,EAE9ChB,EAAE,OAAO,CACX,CACF,CAOQ,qBAAqB3B,EAAkB6C,EAAwB,CACrE,IAAMC,EAAY9C,EAAQ,aAAa6C,CAAQ,EAC/C,GAAKC,EAIL,GAAI,CAGmB,IAAI,SAAS,UAAWA,CAAS,EACzC,KAAK9C,EAASA,CAAO,CACpC,OAAStF,EAAO,CACd,KAAK,OAAO,MAAM,mBAAmBmI,CAAQ,SAAUnI,CAAK,CAC9D,CACF,CAmBA,OAAc,CACZ,KAAK,kBAAkB,CACzB,CAKA,cAAyB,CACvB,OAAO,KAAK,aAAa,aAAa,CACxC,CAKA,oBAAsC,CACpC,OAAO,KAAK,aAAa,mBAAmB,CAC9C,CACF,EAGI,OAAO,QAAW,aACpBhC,GAAmB,SAAS",
|
|
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", "scrollResetPriors", "autoClickTimers", "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", "targetEl", "resolveTarget", "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", "scrollResetPriors", "setupFxLifecycleListeners", "e", "teardownFxLifecycleListeners", "handleScrollDirectives", "element", "teardownAutoClickTimers", "timer", "autoClickTimers", "handleAutoClickDirectives", "spec", "existing", "colonIdx", "delayStr", "delayMs", "name", "button", "handleHighlightDirectives", "handleAnimateDirectives", "ensureAnimateKeyframes", "style", "PENDING_PROCESSED_KEY", "handleToastDirectives", "trigger", "pending", "messages", "position", "stack", "getOrCreateToastStack", "msg", "createToastElement", "setupToastClickOutside", "key", "applyPositionStyles", "s", "inner", "t", "b", "btn", "closedShadowRoots", "handleShadowRootHydration", "templates", "tpl", "parent", "modeAttr", "shadow", "areaSelectArmed", "areaSelectWarnedParents", "MIN_AREA_FRACTION", "handleAreaSelectDirectives", "send", "matches", "action", "attachAreaSelect", "teardownAreaSelectForRoot", "urlHashArmed", "urlHashWindowListener", "handleURLHashDirective", "body", "dataHash", "mirrorDataAttrToLocation", "attachURLHash", "initialLocation", "looksLikeDeepLinkHash", "teardownURLHashForRoot", "currentLocation", "warnIfUnencodedHash", "targetURL", "oldPath", "newPath", "currentState", "urlHashUnencodedWarned", "warnIfUnencodedHash", "hash", "urlHashUnencodedWarned", "looksLikeDeepLinkHash", "attachURLHash", "el", "action", "initialSend", "entry", "urlHashArmed", "urlHashWindowListener", "s", "e", "attachAreaSelect", "send", "overlay", "startClientX", "startClientY", "pointerId", "dragParent", "startRect", "removeOverlay", "finalize", "dispatch", "capturedPointerId", "rect", "onPointerLeaveCancel", "rectRight", "rectBottom", "x0", "clampRange", "y0", "x1", "y1", "x", "y", "w", "h", "MIN_AREA_FRACTION", "onDragStart", "onPointerDown", "parent", "areaSelectWarnedParents", "parentPos", "captureOk", "updateOverlay", "elRect", "parentRect", "borderL", "borderT", "scrollL", "scrollT", "toCSSLeft", "vx", "toCSSTop", "vy", "left", "top", "width", "height", "minLeft", "minTop", "maxRight", "maxBottom", "clampedLeft", "clampedTop", "clampedRight", "clampedBottom", "onPointerMove", "onPointerUp", "onPointerCancel", "onLostCapture", "areaSelectArmed", "n", "lo", "hi", "debounce", "func", "wait", "timeout", "args", "context", "throttle", "limit", "inThrottle", "STORAGE_PREFIX", "storageKey", "scope", "field", "resolveScope", "ownerDocument", "_a", "doc", "wrapper", "getStorage", "opts", "hasValue", "el", "collectRedactElements", "root", "found", "applyOutgoingRedaction", "actionElement", "sink", "storage", "e", "key", "redactActionData", "data", "sentinel", "redactFormData", "form", "formData", "hydrateRedactedTokens", "_b", "_c", "cache", "read", "v", "active", "lvtSelector", "attr", "value", "escaped", "safeValue", "legacyNoInterceptWarned", "hasNoInterceptOptOut", "el", "newName", "logger", "CLICK_AWAY_METHOD_MAP", "CLICK_AWAY_METHODS", "NON_BUBBLING", "LVT_DRAG_MIME", "DRAG_EVENTS", "DELEGATED_EVENT_TYPES", "EventDelegator", "context", "logger", "wrapperId", "eventType", "listenerKey", "existingListener", "button", "data", "attr", "key", "wrapperElement", "eventTypes", "rateLimitedHandlers", "listener", "e", "_a", "_b", "_c", "currentWrapper", "target", "element", "inWrapper", "attrName", "action", "actionElement", "isOrphanButton", "persistTable", "btn", "hasNoInterceptOptOut", "submitter", "hiddenInput", "explicitAction", "dialog", "dragEvent", "keyEl", "related", "keyFilter", "targetElement", "handleAction", "message", "formData", "checkboxes", "checkboxGroups", "cb", "group", "cbs", "name", "passwordFields", "el", "submitterForData", "actionFieldName", "value", "submitter2", "src", "tgtKey", "tier1FormData", "tier1FileInputs", "input", "submitButton", "originalButtonText", "uploadName", "redactFormData", "redactActionData", "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", "safeMatchesPopoverOpen", "el", "handlers", "findHandler", "h", "SHOW_COMMANDS", "handleClick", "e", "button", "command", "targetId", "target", "handler", "handleClose", "handleToggle", "handlePopstate", "id", "openFromHash", "isHashLinkTarget", "activateHashTarget", "installed", "setupHashLink", "teardownHashLink", "LinkInterceptor", "context", "logger", "wrapperId", "_a", "listenerKey", "existing", "wrapper", "listener", "e", "target", "currentWrapper", "hashId", "isHashLinkTarget", "activateHashTarget", "prevHref", "link", "hasNoInterceptOptOut", "protocol", "href", "pushState", "_b", "_c", "targetURL", "refURL", "response", "html", "_ObserverManager", "context", "logger", "wrapperElement", "sentinel", "entries", "wrapper", "e", "detail", "ObserverManager", "LoadingIndicator", "bar", "style", "debounceMs", "FormDisabler", "wrapper", "form", "input", "installed", "handleClick", "e", "el", "button", "targetId", "command", "target", "setupInvokerPolyfill", "GUARD_KEY", "activeBindings", "pruneDisconnectedBindings", "binding", "setupScrollAway", "scanRoot", "processEl", "el", "edge", "target", "resolveTarget", "existing", "removeBinding", "raw", "threshold", "ticking", "handler", "idx", "teardownScrollAway", "wrapper", "i", "ACTIVE_CLASS", "BINDING_KEY", "LINK_HANDLER_KEY", "activeBindings", "warnedMissingId", "pruneDisconnectedBindings", "b", "detach", "handler", "applyActive", "readMarginPx", "container", "raw", "fallback", "collectTargets", "selector", "e", "binding", "activeId", "link", "href", "id", "refreshLinks", "ownIds", "t", "matched", "findBindingForId", "pickActive", "targets", "marginPx", "findScrollTarget", "el", "cur", "oy", "sameTargets", "a", "i", "attach", "preCollected", "missingId", "ticking", "processContainer", "existing", "fresh", "idx", "installLinkClickHandler", "_a", "owner", "setupSpy", "scanRoot", "teardownSpy", "wrapper", "deepClone", "obj", "isRangeNode", "node", "hasRangeAnywhere", "depth", "key", "child", "shouldFullReplace", "existing", "update", "TreeRenderer", "logger", "opts", "_a", "_b", "changed", "targetedOps", "skipPaths", "value", "oldValue", "newValue", "result", "currentPath", "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", "a", "b", "itemStatePath", "idx", "rangeNode", "dynamics", "statics", "elseKey", "elseStatePath", "itemIdx", "itemStatics", "rangeData", "fieldValue", "items", "prepend", "itemsArray", "keyPosStr", "KEY_ATTRIBUTES", "KEY_ATTR_REGEXES", "attr", "TARGETED_APPLIED_ATTR", "TARGETED_SKIP_ATTR", "RangeDomApplier", "ctx", "rangePath", "wrapper", "anyKnownItemKey", "cached", "sample", "container", "rangeStructure", "allStatics", "sm", "arr", "items", "item", "sampleKey", "cur", "targetedOp", "morphdomOptions", "ops", "statics", "allOpsSucceeded", "op", "opType", "opOK", "err", "el", "key", "row", "_a", "_b", "itemIdx", "newHtml", "newRow", "morphdom_esm_default", "afterKey", "anchor", "frag", "baseIdx", "splice", "list", "scratch", "newRows", "i", "newKeyOrder", "byKey", "child", "k", "fragment", "newKeySet", "html", "template", "first", "scope", "escaped", "t", "it", "s", "re", "val", "v", "idKey", "root", "hookName", "FormLifecycleManager", "form", "button", "originalButtonText", "fieldset", "meta", "dialogParent", "ChangeAutoWirer", "context", "logger", "capabilities", "treeState", "prevSize", "wrapper", "el", "fieldName", "bindingType", "escapedName", "elements", "parentForm", "hasNoInterceptOptOut", "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", "entry", "msg", "container", "input", "uploadName", "existingHandler", "handler", "e", "files", "sourceInput", "fileMetadata", "file", "startMessage", "controller", "response", "error", "message", "onError", "i", "_a", "upload_name", "entryInfos", "fileMap", "entries", "info", "meta", "uploader", "completeMessage", "errorMsg", "formData", "form", "passwordFields", "el", "name", "value", "prev", "url", "safeName", "root", "apply", "id", "offset", "end", "chunk", "base64", "chunkMessage", "entryId", "pendingEntries", "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", "RangeDomApplier", "item", "idx", "statics", "sp", "el", "hook", "rangePath", "key", "_a", "range", "idKey", "FocusManager", "FormLifecycleManager", "LoadingIndicator", "FormDisabler", "UploadHandler", "message", "entry", "uploadName", "entries", "error", "formData", "signal", "EventDelegator", "value", "form", "action", "button", "originalButtonText", "LinkInterceptor", "html", "destinationHref", "href", "ObserverManager", "ChangeAutoWirer", "WebSocketManager", "_b", "_c", "response", "event", "autoInitLogger", "init", "wrapper", "client", "debounceAttr", "debounceMs", "errorEnvelope", "uploadMessage", "startResponse", "openFromHash", "wrapperSelector", "connectionResult", "setupToastClickOutside", "setupReactiveAttributeListeners", "setupInvokerPolyfill", "setupHashLink", "handleURLHashDirective", "setupFxLifecycleListeners", "teardownHashLink", "teardownAutoClickTimers", "teardownFxDOMEventTriggers", "teardownFxLifecycleListeners", "teardownScrollAway", "teardownSpy", "teardownAreaSelectForRoot", "teardownURLHashForRoot", "elapsed", "result", "err", "url", "flashParams", "hasFlashParams", "param", "readyState", "data", "seenKeys", "v", "k", "newLiveUrl", "liveUrl", "updateResponse", "detail", "doc", "currentLinks", "current", "l", "fetchedLinks", "fetched", "raw", "e", "differs", "oldId", "newTitleText", "newWrapper", "newId", "myEpoch", "n", "selector", "body", "trimmed", "num", "update", "existingHTML", "match", "innerContent", "newBodyContent", "element", "meta", "rangeStructure", "hasStaticsInTree", "node", "tempWrapper", "rawTag", "wrapTag", "root", "activeEl", "morphdomOptions", "fromEl", "toEl", "TARGETED_SKIP_ATTR", "toAttrs", "fromElement", "i", "fromAttrs", "toElement", "attr", "active", "safeMatchesPopoverOpen", "successContainers", "anyFailed", "op", "container", "TARGETED_APPLIED_ATTR", "c", "fullHtml", "morphdom_esm_default", "hydrateRedactedTokens", "handleScrollDirectives", "handleHighlightDirectives", "handleAnimateDirectives", "handleToastDirectives", "handleAutoClickDirectives", "handleAreaSelectDirectives", "handleShadowRootHydration", "setupScrollAway", "setupSpy", "setupFxDOMEventTriggers", "walker", "toReplace", "path", "parent", "hookName", "hookValue"]
|
|
7
7
|
}
|