@magicx-eng/ai-autocomplete-vanilla 0.4.6 → 0.4.7
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/index.js +83 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +83 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/tokenManager.ts","../src/utils/auth.ts","../src/utils/api.ts","../src/utils/buildQuery.ts","../src/utils/filtering.ts","../src/utils/overrides.ts","../src/controllers/fetchController.ts","../src/dom/cursorUtils.ts","../src/controllers/keyboardController.ts","../src/controllers/pillsController.ts","../src/utils/segments.ts","../src/derive/dropdown.ts","../src/derive/state.ts","../src/promotion/promote.ts","../src/reEdit/ReEditManager.ts","../src/utils/footerHint.ts","../src/render/reconcileList.ts","../src/render/renderPills.ts","../src/render/renderSuggestionGrid.ts","../src/render/renderDropdown.ts","../src/render/renderDropdownOnly.ts","../src/render/renderEditable.ts","../src/render/renderSubmitButton.ts","../src/render/renderInput.ts","../src/selection/SelectionFlow.ts","../src/state.ts","../src/styleInjector.ts","../src/util/Emitter.ts","../src/util/TimerScheduler.ts","../src/utils/modeController.ts","../src/utils/telemetry.ts","../src/AIAutocomplete.ts"],"sourcesContent":["import type { AccessTokenConfig } from \"../shared-types\";\n\n/** Refresh 30 seconds before stated expiry to absorb clock drift and request latency. */\nconst REFRESH_SKEW_MS = 30_000;\n\nexport class TokenManager {\n private current: string | null = null;\n private expiresAt: number | null = null;\n private inFlightRefresh: Promise<string> | null = null;\n\n constructor(private config: AccessTokenConfig) {\n if (config.accessToken) {\n this.current = config.accessToken;\n }\n }\n\n /** Returns a valid token, refreshing if needed. Single-flight: concurrent callers share one refresh. */\n async getToken(forceRefresh = false): Promise<string> {\n if (!forceRefresh && this.current && !this.isExpired()) {\n return this.current;\n }\n if (!forceRefresh && this.inFlightRefresh) {\n return this.inFlightRefresh;\n }\n this.inFlightRefresh = this.refresh();\n try {\n return await this.inFlightRefresh;\n } finally {\n this.inFlightRefresh = null;\n }\n }\n\n private async refresh(): Promise<string> {\n const result = await this.config.getAccessToken();\n this.current = result.accessToken;\n this.expiresAt = result.expiresAt ?? null;\n return this.current;\n }\n\n private isExpired(): boolean {\n if (this.expiresAt == null) return false;\n return Date.now() >= this.expiresAt - REFRESH_SKEW_MS;\n }\n}\n","import type { AccessTokenConfig, APIConfig, APIKeyConfig } from \"../shared-types\";\nimport { TokenManager } from \"./tokenManager\";\n\n/** Default backend origin used when apiConfig.endpoint is not set. */\nexport const DEFAULT_API_ORIGIN = \"https://api.ai-autocomplete.com\";\nexport const DEFAULT_SUGGEST_ENDPOINT = `${DEFAULT_API_ORIGIN}/api/suggest`;\n\n// Keyed by getAccessToken function reference (stable across React re-renders)\nconst tokenManagers = new WeakMap<AccessTokenConfig[\"getAccessToken\"], TokenManager>();\n\nexport function isAccessTokenConfig(config?: APIConfig): config is AccessTokenConfig {\n return config?.type === \"accessToken\";\n}\n\nexport function getApiKeyConfig(config?: APIConfig): APIKeyConfig | undefined {\n if (!config || isAccessTokenConfig(config)) return undefined;\n return config;\n}\n\nexport function getTokenManager(config: AccessTokenConfig): TokenManager {\n let manager = tokenManagers.get(config.getAccessToken);\n if (!manager) {\n manager = new TokenManager(config);\n tokenManagers.set(config.getAccessToken, manager);\n }\n return manager;\n}\n\n/**\n * Builds shared (non-auth) request headers used by every backend call.\n * Mirrors what /suggest sends so /api/telemetry/events sees the same envelope.\n */\nexport function buildHeaders(apiConfig?: APIConfig): Record<string, string> {\n return {\n \"Content-Type\": \"application/json\",\n ...(apiConfig?.appIdentifier && { \"X-App-Identifier\": apiConfig.appIdentifier }),\n ...apiConfig?.headers,\n };\n}\n\n/**\n * Returns the Authorization header value for an API-key config, or `null` if no\n * apiKey is set (matches /suggest's behavior of sending the request without an\n * Authorization header in that case — the warn lives in api.ts so it fires only\n * for the user-visible suggest call).\n */\nexport function buildApiKeyAuthHeader(apiConfig?: APIConfig): string | null {\n const apiKeyConfig = getApiKeyConfig(apiConfig);\n const apiKey = apiKeyConfig?.apiKey;\n if (!apiKey) return null;\n const scheme = apiKeyConfig?.authScheme ?? \"Bearer\";\n return scheme === \"Basic\" ? `Basic ${btoa(apiKey)}` : `Bearer ${apiKey}`;\n}\n","import type {\n APIConfig,\n AutocompleteRequest,\n AutocompleteResponse,\n CompletedParam,\n CompletedParamState,\n} from \"../shared-types\";\nimport {\n buildApiKeyAuthHeader,\n buildHeaders,\n DEFAULT_SUGGEST_ENDPOINT,\n getTokenManager,\n isAccessTokenConfig,\n} from \"./auth\";\n\n// Replaced at build time by tsup/vitest `define` config with the package.json version.\ndeclare const __SDK_VERSION__: string;\nconst SDK_VERSION = __SDK_VERSION__;\n\nlet hasWarnedMissingKey = false;\n\nfunction generateRequestId(): string {\n return crypto.randomUUID();\n}\n\nfunction toWireParam(param: CompletedParamState, includeText: boolean): CompletedParam {\n return {\n placeholder: param.placeholder,\n type: param.type,\n ...(includeText && { text: param.text }),\n kind: param.kind,\n };\n}\n\nfunction buildRequestBody(\n rawQuery: string,\n completedParams: CompletedParamState[],\n includeText: boolean,\n sessionId: string,\n): AutocompleteRequest {\n const rawCount = completedParams.find(\n (p) => p.type === \"contact\" && p.metadata?.contact_account_count,\n )?.metadata?.contact_account_count;\n const contactAccountCount = typeof rawCount === \"number\" ? rawCount : undefined;\n\n return {\n data: {\n raw_query: rawQuery,\n completed_params: completedParams.map((p) => toWireParam(p, includeText)),\n ...(contactAccountCount != null && { contact_account_count: contactAccountCount }),\n },\n meta: {\n request_id: generateRequestId(),\n request_at: new Date().toISOString(),\n language: typeof navigator !== \"undefined\" ? navigator.language : \"en-US\",\n client_version: SDK_VERSION,\n session_id: sessionId,\n },\n };\n}\n\nasync function doFetch(\n endpoint: string,\n headers: Record<string, string>,\n token: string,\n body: string,\n signal?: AbortSignal,\n): Promise<Response> {\n return fetch(endpoint, {\n method: \"POST\",\n headers: { ...headers, Authorization: `Bearer ${token}` },\n body,\n signal,\n });\n}\n\nexport async function fetchSuggestions(\n rawQuery: string,\n completedParams: CompletedParamState[],\n options: {\n sessionId: string;\n maskCompletedText?: boolean;\n signal?: AbortSignal;\n apiConfig?: APIConfig;\n },\n): Promise<AutocompleteResponse> {\n const apiConfig = options.apiConfig;\n const includeText = !options.maskCompletedText;\n const body = buildRequestBody(rawQuery, completedParams, includeText, options.sessionId);\n const headers = buildHeaders(apiConfig);\n const endpoint = apiConfig?.endpoint ?? DEFAULT_SUGGEST_ENDPOINT;\n const jsonBody = JSON.stringify(body);\n\n // === Access token mode ===\n if (isAccessTokenConfig(apiConfig)) {\n const manager = getTokenManager(apiConfig);\n const token = await manager.getToken();\n\n let response = await doFetch(endpoint, headers, token, jsonBody, options.signal);\n\n // 401 retry: force-refresh token and retry exactly once\n if (response.status === 401) {\n const newToken = await manager.getToken(true);\n response = await doFetch(endpoint, headers, newToken, jsonBody, options.signal);\n }\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<AutocompleteResponse>;\n }\n\n // === API key mode (default) ===\n const authHeader = buildApiKeyAuthHeader(apiConfig);\n if (!authHeader && !hasWarnedMissingKey) {\n hasWarnedMissingKey = true;\n // biome-ignore lint/suspicious/noConsole: intentional dev warning\n console.warn(\n \"[AIAutocomplete] No apiKey in apiConfig. Requests will be sent without an Authorization header.\",\n );\n }\n if (authHeader) headers.Authorization = authHeader;\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers,\n body: jsonBody,\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<AutocompleteResponse>;\n}\n","import type { CompletedParamState } from \"../shared-types\";\n\ninterface BuildQueryResult {\n rawQuery: string;\n completedParams: CompletedParamState[];\n}\n\n/**\n * Takes the raw input text and completed params (without placeholders),\n * replaces each completed param's text in the string with a {{TYPE_N}} token,\n * and returns the transformed query + params with placeholders filled in.\n *\n * Replacements happen left-to-right, first occurrence only per param.\n * Counter is per-type (e.g. {{TASK_1}}, {{GOAL_1}}, {{GOAL_2}}).\n */\nexport function buildQuery(text: string, completedParams: CompletedParamState[]): BuildQueryResult {\n let result = text;\n const typeCounts: Record<string, number> = {};\n const updatedParams: CompletedParamState[] = [];\n\n for (const param of completedParams) {\n const count = (typeCounts[param.type] ?? 0) + 1;\n typeCounts[param.type] = count;\n\n const typeKey = param.type.toUpperCase().replace(/\\s+/g, \"_\");\n const placeholder = `{{${typeKey}_${count}}}`;\n\n // Replace first occurrence of the param text in the string\n const index = result.indexOf(param.text);\n if (index !== -1) {\n result = result.slice(0, index) + placeholder + result.slice(index + param.text.length);\n }\n\n updatedParams.push({ ...param, placeholder });\n }\n\n return { rawQuery: result, completedParams: updatedParams };\n}\n","import type { SuggestionOption } from \"../shared-types\";\n\n/**\n * Returns a filter base that respects the server-suggested placeholder. When\n * `filterBase` hasn't been set by a fetch yet (still 0) and `text` starts with\n * `placeholderText`, the placeholder occupies the same role as `filterBase` —\n * the user's filter query is whatever they typed past it. Returns `filterBase`\n * unchanged otherwise.\n */\nexport function effectiveFilterBase(\n text: string,\n filterBase: number,\n placeholderText: string,\n): number {\n if (filterBase > 0 || !placeholderText) return filterBase;\n if (text.toLowerCase().startsWith(placeholderText.toLowerCase())) {\n return placeholderText.length;\n }\n return filterBase;\n}\n\n/**\n * Extracts the effective filter query from the region after filterBase.\n * If the server marked the region as in_progress, the full region is used.\n * Otherwise, filtering only starts after the first space (to avoid filtering\n * mid-word when the user continues typing a word that was already in the input).\n */\nexport function extractFilterQuery(\n text: string,\n filterBase: number,\n isInProgress: boolean,\n): string {\n const rawRegion = text.slice(filterBase);\n if (isInProgress || filterBase === 0 || text[filterBase - 1] === \" \") {\n return rawRegion;\n }\n const spaceIdx = rawRegion.indexOf(\" \");\n return spaceIdx === -1 ? \"\" : rawRegion.slice(spaceIdx + 1);\n}\n\n/**\n * Finds the longest word-boundary-aligned suffix of `prefix` that matches a\n * prefix of `optionText` (case-insensitive). Returns the number of characters\n * to remove from the end of `prefix` to avoid duplication.\n */\nexport function findPrefixOverlap(prefix: string, optionText: string): number {\n // Normalize internal whitespace so words.join(\" \") roundtrips losslessly\n const trimmed = prefix.trimEnd().replace(/\\s+/g, \" \");\n if (trimmed.length === 0 || optionText.length === 0) return 0;\n\n const words = trimmed.split(\" \");\n const optionLower = optionText.toLowerCase();\n\n // Try from longest suffix (all words) to shortest (last word only)\n for (let i = 0; i < words.length; i++) {\n const candidate = words.slice(i).join(\" \");\n if (optionLower.startsWith(candidate.toLowerCase())) {\n const suffixStart = trimmed.length - candidate.length;\n return prefix.length - suffixStart;\n }\n }\n\n return 0;\n}\n\n/**\n * Filters options using partial substring match on the text after the last completed param.\n */\nexport function filterOptions(\n options: SuggestionOption[] | undefined,\n query: string,\n): SuggestionOption[] {\n if (!options) return [];\n const trimmed = query.trimStart();\n if (!trimmed) return options;\n const lower = trimmed.toLowerCase();\n return options.filter((o) => !o.is_tappable || o.text.toLowerCase().includes(lower));\n}\n\n/**\n * Finds an exact match for the trimmed filter query against options.\n */\nexport function findExactMatch(\n options: SuggestionOption[] | undefined,\n query: string,\n): SuggestionOption | null {\n if (!options) return null;\n const trimmed = query.trim();\n if (!trimmed) return null;\n const lower = trimmed.toLowerCase();\n return options.find((o) => o.is_tappable && o.text.toLowerCase() === lower) ?? null;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\n\n/**\n * Applies option overrides by calling the override function with an empty query,\n * then prepending results to suggestions that match by type, deduplicating by text.\n */\nexport function applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: Record<string, (query: string) => SuggestionOption[]>,\n): Suggestion[] {\n if (!overrides) return suggestions;\n return suggestions.map((s) => {\n const fn = overrides[s.type];\n if (!fn) return s;\n const extra = fn(\"\");\n if (extra.length === 0) return s;\n const existingTexts = new Set(extra.map((o) => o.text));\n const deduped = (s.options ?? []).filter((o) => !existingTexts.has(o.text));\n return { ...s, options: [...extra, ...deduped] };\n });\n}\n","import type {\n APIConfig,\n CompletedParamState,\n OptionOverrides,\n Suggestion,\n SuggestionOption,\n} from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { fetchSuggestions } from \"../utils/api\";\nimport { buildQuery } from \"../utils/buildQuery\";\nimport {\n effectiveFilterBase,\n extractFilterQuery,\n filterOptions,\n findExactMatch,\n} from \"../utils/filtering\";\nimport { applyOptionOverrides } from \"../utils/overrides\";\n\nconst DEBOUNCE_MS = 100;\nconst SLOW_DEBOUNCE_MS = 300;\nconst MIN_CHARS_DIFF = 2;\n\nexport interface FetchAutoMatchEvent {\n active: Suggestion;\n matched: SuggestionOption;\n rawQuery: string;\n}\n\nexport interface FetchControllerCallbacks {\n onAutoMatch?: (event: FetchAutoMatchEvent) => void;\n}\n\nexport class FetchController {\n private fetchVersion = 0;\n private abortController: AbortController | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private slowDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n private unsubscribe: (() => void) | null = null;\n\n constructor(\n private store: Store<CoreState>,\n private getApiConfig: () => APIConfig | undefined,\n private getOptionOverrides: () => OptionOverrides | undefined,\n private getMaskCompletedText: () => boolean | undefined,\n private getOnError: () => ((error: Error) => void) | undefined,\n private getSessionId: () => string,\n private callbacks: FetchControllerCallbacks = {},\n ) {}\n\n start() {\n // Initial fetch\n this.doFetch(\"\", []);\n\n // Subscribe to state changes for debounced fetching\n let prevText = this.store.get().text;\n let prevParams = this.store.get().completedParams;\n this.unsubscribe = this.store.subscribe((next) => {\n if (next.text !== prevText || next.completedParams !== prevParams) {\n prevText = next.text;\n prevParams = next.completedParams;\n this.scheduleFetch();\n }\n });\n }\n\n dispose() {\n this.abortController?.abort();\n this.clearTimers();\n this.unsubscribe?.();\n }\n\n async doFetch(rawQuery: string, completed: CompletedParamState[]) {\n this.abortController?.abort();\n const controller = new AbortController();\n this.abortController = controller;\n const version = ++this.fetchVersion;\n const textAtRequest = this.store.get().text.length;\n\n this.store.set({ isLoading: true, error: null });\n\n try {\n const res = await fetchSuggestions(rawQuery, completed, {\n sessionId: this.getSessionId(),\n maskCompletedText: this.getMaskCompletedText(),\n signal: controller.signal,\n apiConfig: this.getApiConfig(),\n });\n\n if (version !== this.fetchVersion) return;\n\n let newSuggestions = applyOptionOverrides(\n res.data.suggestions ?? [],\n this.getOptionOverrides(),\n );\n\n const input = res.data.input ?? [];\n const lastInput = input[input.length - 1];\n const currentText = this.store.get().text;\n let filterBase: number;\n let filterInProgress: boolean;\n\n if (lastInput?.state === \"in_progress\") {\n filterInProgress = true;\n const inProgressIdx = currentText.toLowerCase().lastIndexOf(lastInput.text.toLowerCase());\n filterBase = inProgressIdx !== -1 ? inProgressIdx : textAtRequest;\n } else {\n filterInProgress = false;\n filterBase = textAtRequest;\n }\n\n // Check if user already typed an exact match while waiting\n const actionable = newSuggestions.filter((s) => s.type !== \"placeholder\");\n const active = actionable[0];\n let extraParam: CompletedParamState | null = null;\n if (active) {\n const query = extractFilterQuery(currentText, filterBase, filterInProgress);\n const match = findExactMatch(active.options, query);\n if (match) {\n extraParam = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: active.type,\n text: match.text,\n kind: match.kind,\n suggestionType: active.type,\n suggestionPlaceholder: active.text,\n options: active.options ?? [],\n metadata: match.metadata,\n };\n newSuggestions = newSuggestions.filter((s) => s !== active);\n this.callbacks.onAutoMatch?.({ active, matched: match, rawQuery });\n }\n }\n\n this.store.set((s) => ({\n suggestions: newSuggestions,\n isLoading: false,\n isReady: res.data.is_ready ?? false,\n lastRawQuery: rawQuery,\n activeDropdownIndex: -1,\n filterBase,\n filterInProgress,\n ...(extraParam ? { completedParams: [...s.completedParams, extraParam] } : {}),\n }));\n } catch (err) {\n if (version === this.fetchVersion) {\n const caughtError = err instanceof Error ? err : new Error(String(err));\n this.store.set({ error: caughtError, isLoading: false });\n this.getOnError()?.(caughtError);\n }\n }\n }\n\n private scheduleFetch() {\n this.clearTimers();\n const state = this.store.get();\n\n if (state.skipNextFetch) {\n this.store.set({ skipNextFetch: false });\n return;\n }\n\n const attemptFetch = (minDiff: number): boolean => {\n const s = this.store.get();\n if (!s.text && s.completedParams.length === 0) {\n this.doFetch(\"\", []);\n return true;\n }\n\n const placeholderText = s.suggestions\n .filter((sg: Suggestion) => sg.type === \"placeholder\")\n .map((sg: Suggestion) => sg.text)\n .join(\" \");\n const effBase = effectiveFilterBase(s.text, s.filterBase, placeholderText);\n const currentQuery = extractFilterQuery(s.text, effBase, s.filterInProgress);\n const actionable = s.suggestions.filter((sg: Suggestion) => sg.type !== \"placeholder\");\n const active = actionable[0];\n const currentFiltered = active ? filterOptions(active.options, currentQuery) : [];\n const tappableFiltered = currentFiltered.filter((o: SuggestionOption) => o.is_tappable);\n const hasExactMatch = active ? findExactMatch(active.options, currentQuery) !== null : false;\n\n const isInFilterZone = currentQuery.trim().length > 0;\n if (tappableFiltered.length > 0 && !hasExactMatch && isInFilterZone) return false;\n\n // Mirror the filter-zone behavior for placeholder suggestions: if the user is\n // still typing a prefix of the server-provided placeholder, don't fetch yet.\n if (\n s.completedParams.length === 0 &&\n s.text.length > 0 &&\n placeholderText.length > 0 &&\n placeholderText.toLowerCase().startsWith(s.text.toLowerCase())\n ) {\n return false;\n }\n\n const { rawQuery, completedParams: updatedParams } = buildQuery(s.text, s.completedParams);\n const isDeleting = rawQuery.length < s.lastRawQuery.length;\n const charDiff = Math.abs(rawQuery.length - s.lastRawQuery.length);\n if (isDeleting || charDiff >= minDiff) {\n this.doFetch(rawQuery, updatedParams);\n return true;\n }\n return false;\n };\n\n this.debounceTimer = setTimeout(() => {\n if (attemptFetch(MIN_CHARS_DIFF)) {\n if (this.slowDebounceTimer) clearTimeout(this.slowDebounceTimer);\n }\n }, DEBOUNCE_MS);\n\n this.slowDebounceTimer = setTimeout(() => attemptFetch(1), SLOW_DEBOUNCE_MS);\n }\n\n private clearTimers() {\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n if (this.slowDebounceTimer) clearTimeout(this.slowDebounceTimer);\n this.debounceTimer = null;\n this.slowDebounceTimer = null;\n }\n}\n","/**\n * Plain-text caret utilities for contentEditable elements.\n *\n * Offsets are measured in plain-text characters that come from the editable\n * region only — subtrees inside `[contenteditable=\"false\"]` (e.g. pills)\n * contribute zero characters. Callers can think in string offsets without\n * touching DOM Ranges directly.\n */\n\nconst NON_EDITABLE_SELECTOR = '[contenteditable=\"false\"]';\n\ninterface GraphemeSegmenter {\n segment(input: string): Iterable<{ index: number; segment: string }>;\n}\n\nlet segmenter: GraphemeSegmenter | null | undefined;\nfunction getGraphemeSegmenter(): GraphemeSegmenter | null {\n if (segmenter !== undefined) return segmenter;\n // Intl.Segmenter is ES2022; lib target is ES2020. Access via globalThis to\n // avoid a hard compile dependency on the newer lib.\n const Segmenter = (globalThis as { Intl: typeof Intl & { Segmenter?: unknown } }).Intl\n .Segmenter as undefined | (new (locale?: string, options?: object) => GraphemeSegmenter);\n if (!Segmenter) {\n segmenter = null;\n return null;\n }\n try {\n segmenter = new Segmenter(undefined, { granularity: \"grapheme\" });\n } catch {\n segmenter = null;\n }\n return segmenter ?? null;\n}\n\nfunction isInsideNonEditable(node: Node, root: HTMLElement): boolean {\n let n: Node | null = node;\n while (n && n !== root) {\n if (n.nodeType === Node.ELEMENT_NODE) {\n const el = n as HTMLElement;\n if (el.matches(NON_EDITABLE_SELECTOR)) return true;\n }\n n = n.parentNode;\n }\n return false;\n}\n\nfunction createTextWalker(root: HTMLElement): TreeWalker {\n return (root.ownerDocument ?? document).createTreeWalker(root, NodeFilter.SHOW_TEXT, {\n acceptNode(node) {\n return isInsideNonEditable(node, root) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;\n },\n });\n}\n\nexport function extractPlainText(root: HTMLElement): string {\n const walker = createTextWalker(root);\n let out = \"\";\n let node = walker.nextNode() as Text | null;\n while (node) {\n out += node.data;\n node = walker.nextNode() as Text | null;\n }\n return out;\n}\n\nexport function plainTextLength(root: HTMLElement): number {\n const walker = createTextWalker(root);\n let total = 0;\n let node = walker.nextNode() as Text | null;\n while (node) {\n total += node.data.length;\n node = walker.nextNode() as Text | null;\n }\n return total;\n}\n\n/**\n * Read the current caret offset (in plain-text characters) within `root`.\n * Returns null when no selection is anchored inside `root`.\n */\nexport function getCursorOffset(root: HTMLElement): number | null {\n const sel = (root.ownerDocument ?? document).getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const anchorNode = sel.anchorNode;\n const anchorOffset = sel.anchorOffset;\n if (!anchorNode || !root.contains(anchorNode)) return null;\n\n // When the anchor is the editable itself (or an element child), interpret\n // anchorOffset as a child index and sum text lengths up to that child.\n if (anchorNode.nodeType === Node.ELEMENT_NODE) {\n const el = anchorNode as Element;\n if (isInsideNonEditable(el, root) && el !== root) return null;\n let offset = 0;\n for (let i = 0; i < anchorOffset && i < el.childNodes.length; i++) {\n offset += plainTextLengthOfSubtree(el.childNodes[i], root);\n }\n // Add lengths of previous siblings + ancestors up to root.\n return offset + offsetBeforeNode(el, root);\n }\n\n if (anchorNode.nodeType !== Node.TEXT_NODE) return null;\n if (isInsideNonEditable(anchorNode, root)) return null;\n\n return offsetBeforeNode(anchorNode, root) + anchorOffset;\n}\n\nfunction plainTextLengthOfSubtree(node: Node, root: HTMLElement): number {\n if (node.nodeType === Node.TEXT_NODE) {\n return isInsideNonEditable(node, root) ? 0 : (node as Text).data.length;\n }\n if (node.nodeType !== Node.ELEMENT_NODE) return 0;\n const el = node as HTMLElement;\n if (el.matches(NON_EDITABLE_SELECTOR)) return 0;\n let total = 0;\n for (const child of Array.from(el.childNodes)) {\n total += plainTextLengthOfSubtree(child, root);\n }\n return total;\n}\n\nfunction offsetBeforeNode(target: Node, root: HTMLElement): number {\n const walker = createTextWalker(root);\n let total = 0;\n let node = walker.nextNode() as Text | null;\n while (node) {\n if (node === target) return total;\n // If target is an ancestor element of this text node, we've already passed it.\n if (target.nodeType === Node.ELEMENT_NODE && (target as Element).contains(node)) {\n return total;\n }\n total += node.data.length;\n node = walker.nextNode() as Text | null;\n }\n return total;\n}\n\n/**\n * Set the caret at the given plain-text offset within `root`.\n *\n * Boundary policy: when the offset falls at the seam between text nodes, the\n * caret is placed at the START of the *following* text node so newly typed\n * characters do not inherit a preceding `<strong>`'s bold styling. When the\n * caret would land at the trailing edge of a text node inside a `<strong>`\n * with no following text node, we use `setStartAfter(strong)` so the caret\n * sits OUTSIDE the bold subtree — otherwise a caret at the end of a strong's\n * text is still \"inside\" the strong, which would falsely trigger re-edit mode.\n */\nexport function setCursorOffset(root: HTMLElement, offset: number): void {\n const doc = root.ownerDocument ?? document;\n const sel = doc.getSelection();\n if (!sel) return;\n\n const clamped = Math.max(0, Math.min(offset, plainTextLength(root)));\n const walker = createTextWalker(root);\n let cumulative = 0;\n let target: Text | null = null;\n let targetOffset = 0;\n let node = walker.nextNode() as Text | null;\n let lastNode: Text | null = null;\n\n while (node) {\n const len = node.data.length;\n if (clamped < cumulative + len) {\n target = node;\n targetOffset = clamped - cumulative;\n break;\n }\n if (clamped === cumulative + len) {\n const next = walker.nextNode() as Text | null;\n if (next) {\n // Prefer the start of the following text node so the caret sits past\n // any preceding `<strong>` boundary.\n target = next;\n targetOffset = 0;\n } else {\n target = node;\n targetOffset = len;\n }\n break;\n }\n cumulative += len;\n lastNode = node;\n node = walker.nextNode() as Text | null;\n }\n\n const range = doc.createRange();\n if (target) {\n // Boundary policy: the caret must never land at the leading or trailing\n // edge of a `<strong>`'s text node — visually it sits AT the boundary\n // but the DOM anchor is still INSIDE the strong, which would falsely\n // trigger re-edit mode. Hop OUT of the strong at those boundaries.\n const strongParent = target.parentElement?.closest<HTMLElement>('strong[data-seg=\"completed\"]');\n if (strongParent && strongParent !== root && root.contains(strongParent)) {\n if (targetOffset === 0) {\n range.setStartBefore(strongParent);\n } else if (targetOffset === target.data.length) {\n range.setStartAfter(strongParent);\n } else {\n range.setStart(target, targetOffset);\n }\n } else {\n range.setStart(target, targetOffset);\n }\n } else if (lastNode) {\n range.setStart(lastNode, lastNode.data.length);\n } else {\n range.setStart(root, 0);\n }\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n}\n\n/**\n * True when the caret offset equals the editable's plain-text length —\n * meaning the only content to the right is non-editable (e.g. trailing pills).\n */\nexport function cursorIsAtEnd(root: HTMLElement): boolean {\n const offset = getCursorOffset(root);\n if (offset == null) return false;\n return offset >= plainTextLength(root);\n}\n\n/**\n * Step back one grapheme from a plain-text offset, falling back to one UTF-16\n * code unit when Intl.Segmenter is unavailable. Used by Backspace handling so\n * emoji and combining marks are deleted as a single user-perceived character.\n */\nexport function previousGraphemeBoundary(text: string, offset: number): number {\n if (offset <= 0) return 0;\n const seg = getGraphemeSegmenter();\n if (!seg) return offset - 1;\n const slice = text.slice(0, offset);\n let last = 0;\n for (const { index } of seg.segment(slice)) {\n if (index < offset) last = index;\n }\n return last;\n}\n","import { cursorIsAtEnd, getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { AutocompleteResult, SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nexport interface KeyboardContext {\n columns: number;\n listboxId: string;\n getOnSubmit: () => ((result: AutocompleteResult) => void) | undefined;\n /**\n * Live read of the dropdown's vertical placement. When \"above\", the vertical\n * arrows are swapped so that pressing toward the dropdown enters/advances and\n * pressing toward the input exits/retreats.\n */\n getOptionsPosition: () => \"above\" | \"below\";\n /** Optional hook invoked after onSubmit fires (used by Tier 1 to auto-reset). */\n afterSubmit?: () => void;\n selectOption: (option: SuggestionOption) => void;\n /**\n * Tier 1 only: remove a completed param at the given caret offset, returning\n * true when a param was removed. Tier 2 consumers can omit this — Backspace\n * falls back to the browser default in their own input element.\n */\n removeParamAtCaret?: (offset: number) => boolean;\n /** Tier 1 only: exit re-edit mode (Escape, arrow-key escape). */\n exitEditMode?: () => void;\n}\n\n/**\n * Returns true when the caret in the event target sits at the end of its\n * editable content. Works for both `<textarea>`/`<input>` (Tier 2 consumer\n * inputs) and our contentEditable Tier 1 editor.\n */\nfunction isCursorAtEnd(target: EventTarget | null, state?: CoreState): boolean {\n if (target instanceof HTMLTextAreaElement || target instanceof HTMLInputElement) {\n return target.selectionStart != null && target.selectionStart === target.value.length;\n }\n if (target instanceof HTMLElement && target.hasAttribute(\"data-aia-input\")) {\n return cursorIsAtEnd(target);\n }\n // Headless consumers driving a custom editor (e.g. a Tiptap / ProseMirror\n // document) whose element the core can't introspect: fall back to the\n // plain-text caret offset they report via handleCaretMove /\n // handleCaretAfterInput. This makes arrow-key entry into the dropdown work\n // without requiring the host to tag its editable with `data-aia-input`.\n if (state?.caretOffset != null) {\n return state.caretOffset >= state.text.length;\n }\n return false;\n}\n\nfunction getEditableCaretOffset(target: EventTarget | null): number | null {\n if (target instanceof HTMLElement && target.hasAttribute(\"data-aia-input\")) {\n return getCursorOffset(target);\n }\n return null;\n}\n\nexport class KeyboardController {\n constructor(\n private store: Store<CoreState>,\n private ctx: KeyboardContext,\n ) {}\n\n handleKeyDown(e: KeyboardEvent) {\n const state = this.store.get();\n const { listboxId, getOnSubmit } = this.ctx;\n const columns = this.getEffectiveColumns();\n const onSubmit = getOnSubmit();\n const tappableIndices = this.getTappableIndices(columns);\n\n // Modifier + arrow means the user wants native text-navigation / selection\n // behavior: Shift extends selection (all platforms), Cmd jumps to line/doc\n // edge (Mac), Ctrl jumps word-by-word (Windows/Linux), Alt/Option jumps\n // word-by-word (Mac). Bail before the switch so the browser handles it.\n if (\n (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) &&\n (e.key === \"ArrowDown\" ||\n e.key === \"ArrowUp\" ||\n e.key === \"ArrowLeft\" ||\n e.key === \"ArrowRight\")\n ) {\n return;\n }\n\n // The \"toward dropdown\" arrow key opens the dropdown from the input. When\n // the dropdown sits below (default), that's ArrowDown; when it sits above,\n // that's ArrowUp. Within-grid navigation always follows visual direction —\n // ArrowDown moves the highlight down through the grid, ArrowUp moves it\n // up — regardless of where the dropdown is positioned.\n const above = this.ctx.getOptionsPosition() === \"above\";\n\n switch (e.key) {\n case \"ArrowDown\": {\n const cursorAtEnd = isCursorAtEnd(e.target, state);\n // While re-editing a bold param, the dropdown is showing cached\n // options for it — ArrowDown should descend into them regardless of\n // whether the caret is \"at end\" of the input.\n const inEditMode = !!state.editingParam;\n if (!cursorAtEnd && !inEditMode && state.activeDropdownIndex < 0) break;\n\n // Open from the input only when the dropdown sits below. When it's\n // above, the ArrowUp branch handles opening.\n if (state.activeDropdownIndex < 0) {\n if (above) break;\n e.preventDefault();\n if (!state.isDropdownOpen && state.actionableSuggestions.length > 0) {\n this.store.set({ pillTapped: true, activeDropdownIndex: tappableIndices[0] ?? 0 });\n break;\n }\n if (tappableIndices.length === 0) return;\n this.store.set({ activeDropdownIndex: tappableIndices[0] });\n break;\n }\n\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n // Exit past the bottom (last) row of the visual grid back to the\n // input, mirroring the ArrowUp top-row exit below.\n if (state.filteredOptions.length > 0) {\n const lastRow = Math.floor((state.filteredOptions.length - 1) / columns);\n const currentRow = Math.floor(state.activeDropdownIndex / columns);\n if (currentRow === lastRow) {\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\n }\n const currentPos = tappableIndices.indexOf(state.activeDropdownIndex);\n const nextPos = currentPos < tappableIndices.length - 1 ? currentPos + 1 : 0;\n this.store.set({ activeDropdownIndex: tappableIndices[nextPos] });\n break;\n }\n case \"ArrowUp\": {\n // Open from the input only when the dropdown sits above.\n if (state.activeDropdownIndex < 0) {\n if (!above) break;\n const cursorAtEnd = isCursorAtEnd(e.target, state);\n const inEditMode = !!state.editingParam;\n if (!cursorAtEnd && !inEditMode) break;\n e.preventDefault();\n // Land on the option closest to the input — the first tappable in\n // the bottom (last) row of the visual grid. Mirrors how ArrowDown +\n // dropdown-below lands on the top-left (also closest to the input).\n const initialIdx = this.firstTappableInBottomRow(columns) ?? tappableIndices[0] ?? 0;\n if (!state.isDropdownOpen && state.actionableSuggestions.length > 0) {\n this.store.set({ pillTapped: true, activeDropdownIndex: initialIdx });\n break;\n }\n if (tappableIndices.length === 0) return;\n this.store.set({ activeDropdownIndex: initialIdx });\n break;\n }\n if (tappableIndices.length === 0) break;\n e.preventDefault();\n if (state.activeDropdownIndex < columns) {\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\n const currentPos = tappableIndices.indexOf(state.activeDropdownIndex);\n const prevPos = currentPos > 0 ? currentPos - 1 : tappableIndices.length - 1;\n this.store.set({ activeDropdownIndex: tappableIndices[prevPos] });\n break;\n }\n case \"ArrowRight\": {\n // When a dropdown option is highlighted, arrows navigate the grid\n // only — the caret in the editor stays put. Always preventDefault so\n // a press at the rightmost column doesn't fall through to caret\n // movement.\n if (state.activeDropdownIndex >= 0) {\n e.preventDefault();\n const col = state.activeDropdownIndex % columns;\n if (col < columns - 1) {\n const rightNeighbor = state.activeDropdownIndex + 1;\n if (\n rightNeighbor < state.filteredOptions.length &&\n state.filteredOptions[rightNeighbor]?.is_tappable\n ) {\n this.store.set({ activeDropdownIndex: rightNeighbor });\n }\n }\n break;\n }\n // No highlight → arrow keys move the caret. In re-edit mode that\n // means collapse the highlight to the param's trailing edge and exit.\n if (state.editingParam && e.target instanceof HTMLElement && state.editingTail != null) {\n e.preventDefault();\n const editor = e.target.closest<HTMLElement>(\"[data-aia-input]\") ?? e.target;\n const tail = state.editingTail;\n this.ctx.exitEditMode?.();\n setCursorOffset(editor, tail);\n break;\n }\n const atEnd = isCursorAtEnd(e.target, state);\n if (atEnd && state.actionableSuggestions.length >= 1) {\n e.preventDefault();\n this.removeActivePill();\n }\n break;\n }\n case \"ArrowLeft\": {\n // When a dropdown option is highlighted, arrows navigate the grid\n // only — the caret stays put. Always preventDefault so a press at\n // the leftmost column doesn't fall through to caret movement.\n if (state.activeDropdownIndex >= 0) {\n e.preventDefault();\n if (state.activeDropdownIndex % columns > 0) {\n const leftNeighbor = state.activeDropdownIndex - 1;\n if (leftNeighbor >= 0 && state.filteredOptions[leftNeighbor]?.is_tappable) {\n this.store.set({ activeDropdownIndex: leftNeighbor });\n }\n }\n break;\n }\n // No highlight → arrow keys move the caret.\n if (state.editingParam && e.target instanceof HTMLElement && state.editingAnchor != null) {\n e.preventDefault();\n const editor = e.target.closest<HTMLElement>(\"[data-aia-input]\") ?? e.target;\n const anchor = state.editingAnchor;\n this.ctx.exitEditMode?.();\n setCursorOffset(editor, anchor);\n break;\n }\n break;\n }\n case \"Backspace\": {\n // In re-edit mode the entire bold param is selected; the browser's\n // default Backspace handles deletion of the selected range. Skip\n // removeParamAtCaret so we don't double-handle.\n if (state.editingParam) break;\n if (!this.ctx.removeParamAtCaret) break;\n const offset = getEditableCaretOffset(e.target);\n if (offset == null) break;\n if (this.ctx.removeParamAtCaret(offset)) {\n e.preventDefault();\n }\n break;\n }\n case \"Enter\": {\n e.preventDefault();\n if (\n state.activeDropdownIndex >= 0 &&\n state.filteredOptions[state.activeDropdownIndex]?.is_tappable\n ) {\n this.clickOrSelect(state.activeDropdownIndex, state.filteredOptions, listboxId);\n } else if (onSubmit) {\n const { rawQuery, completedParams: finalParams } = buildQuery(\n state.text,\n state.completedParams,\n );\n onSubmit({\n query: state.text.trim(),\n raw_query: rawQuery,\n completed_params: finalParams,\n });\n this.ctx.afterSubmit?.();\n }\n break;\n }\n case \"Tab\": {\n // Tab moves the highlight through the dropdown's tappable options\n // rather than committing one: with nothing highlighted it lands on the\n // first option, and each subsequent press advances to the next\n // (Shift+Tab retreats), wrapping at either end. Enter commits the\n // highlighted option. This holds even while a placeholder is visible —\n // Tab navigates the options rather than filling the placeholder text.\n const tappableOptionIndices = state.filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n if (tappableOptionIndices.length === 0) break;\n\n // Closed dropdown → the first Tab opens it (like ArrowDown) and lands\n // on the first option (or last, for Shift+Tab).\n if (!state.isDropdownOpen) {\n if (state.actionableSuggestions.length === 0) break;\n e.preventDefault();\n const firstIdx = e.shiftKey\n ? tappableOptionIndices[tappableOptionIndices.length - 1]\n : tappableOptionIndices[0];\n this.store.set({\n pillTapped: true,\n activeDropdownIndex: firstIdx,\n });\n break;\n }\n\n e.preventDefault();\n const currentPos = tappableOptionIndices.indexOf(state.activeDropdownIndex);\n let nextPos: number;\n if (currentPos < 0) {\n // Open but nothing highlighted — land on the first (or last, Shift).\n nextPos = e.shiftKey ? tappableOptionIndices.length - 1 : 0;\n } else {\n const delta = e.shiftKey ? -1 : 1;\n nextPos =\n (currentPos + delta + tappableOptionIndices.length) % tappableOptionIndices.length;\n }\n this.store.set({\n activeDropdownIndex: tappableOptionIndices[nextPos],\n });\n break;\n }\n case \"Escape\": {\n if (state.editingParam && e.target instanceof HTMLElement && state.editingTail != null) {\n const editor = e.target.closest<HTMLElement>(\"[data-aia-input]\") ?? e.target;\n const tail = state.editingTail;\n this.ctx.exitEditMode?.();\n setCursorOffset(editor, tail);\n }\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\n }\n }\n\n /**\n * Index of the first tappable option in the bottom (last) row of the visual\n * grid, or null if no tappable option lives in that row. Used when opening\n * the dropdown from the input while it sits above the input — the highlight\n * should land on the row closest to the input.\n */\n private firstTappableInBottomRow(columns: number): number | null {\n const state = this.store.get();\n if (state.filteredOptions.length === 0) return null;\n const lastRow = Math.floor((state.filteredOptions.length - 1) / columns);\n const bottomRowStart = lastRow * columns;\n for (let i = bottomRowStart; i < state.filteredOptions.length; i++) {\n if (state.filteredOptions[i]?.is_tappable) return i;\n }\n return null;\n }\n\n private getTappableIndices(columns: number): number[] {\n const state = this.store.get();\n const tappable = state.filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n const buckets: number[][] = Array.from({ length: columns }, () => []);\n for (const i of tappable) buckets[i % columns].push(i);\n return buckets.flat();\n }\n\n /**\n * The grid uses a container query to switch between 1 and 2 columns based on\n * width, so the prop value can disagree with what's actually rendered. Read\n * the live column count from the grid; fall back to the prop if unavailable.\n *\n * Walks up from the first option, skipping any ancestor whose\n * `gridTemplateColumns` is the default `\"none\"` (e.g. framework wrapper\n * components — Angular renders each option inside an `<aia-suggestion-item>`\n * tag between the `<div>` and the actual grid container). Without the walk,\n * the wrapper would silently collapse left/right navigation to a single\n * column even when the actual grid renders multiple.\n *\n * The walk is bounded by the listbox element itself: any grid further up the\n * tree is unrelated (e.g. a CSS Grid page layout). Without the bound, a page\n * wrapper with `grid-template-columns` set would be picked up first and break\n * left/right navigation in the dropdown.\n */\n private getEffectiveColumns(): number {\n const listbox = document.getElementById(this.ctx.listboxId);\n // No listbox in the DOM yet (e.g. the dropdown's *ngIf hasn't resolved on\n // the first tick) — there's no grid to measure, and without a valid\n // boundary the walk below would climb to the document root and could pick\n // up an unrelated page-level CSS grid. Fall back to the configured columns.\n if (!listbox) return this.ctx.columns;\n const firstOption = document.getElementById(`${this.ctx.listboxId}-option-0`);\n let el: HTMLElement | null = firstOption?.parentElement ?? null;\n while (el) {\n const gtc = getComputedStyle(el).gridTemplateColumns;\n if (gtc && gtc !== \"none\") {\n const tracks = gtc.split(\" \").filter(Boolean).length;\n if (tracks > 0) return tracks;\n }\n if (el === listbox) break;\n el = el.parentElement;\n }\n return this.ctx.columns;\n }\n\n private clickOrSelect(index: number, options: SuggestionOption[], listboxId: string) {\n const optionEl = document.getElementById(`${listboxId}-option-${index}`);\n if (optionEl) {\n optionEl.click();\n } else {\n this.ctx.selectOption(options[index]);\n }\n }\n\n /**\n * Drop the currently active pill (always index 0 of the actionable\n * suggestions) and promote the next pill to active. Used by ArrowRight at\n * the end of the input to skip past a pill. Inlined here rather than going\n * through PillsController to avoid a circular dependency.\n *\n * When the last pill is removed there are no options left to show, so the\n * dropdown closes on its own. We also clear `pillTapped` in that case: in\n * `manual` mode the dropdown then stays closed until the user taps again,\n * while in `auto` mode it reopens by itself once the next fetch returns\n * fresh suggestions.\n */\n private removeActivePill() {\n const state = this.store.get();\n const placeholders = state.suggestions.filter((s) => s.type === \"placeholder\");\n const actionable = state.suggestions.filter((s) => s.type !== \"placeholder\");\n if (actionable.length === 0) return;\n // The next actionable pill (was index 1) lands at index 0 → becomes active.\n const remaining = actionable.slice(1);\n this.store.set({\n suggestions: [...placeholders, ...remaining],\n pillTapped: remaining.length > 0,\n activeDropdownIndex: -1,\n });\n }\n}\n","import type { DerivedStore } from \"../state\";\nimport type { CoreDerivedState, CoreInputState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nexport interface PillSelectedEvent {\n rawQuery: string;\n selectedPill: string;\n otherPills: string[];\n}\n\nexport interface PillsControllerCallbacks {\n onPillSelected?: (event: PillSelectedEvent) => void;\n}\n\nexport class PillsController {\n constructor(\n private store: DerivedStore<CoreInputState, CoreDerivedState>,\n private callbacks: PillsControllerCallbacks = {},\n ) {}\n\n setActivePill(index: number) {\n const state = this.store.get();\n const actionable = state.suggestions.filter((s) => s.type !== \"placeholder\");\n if (index < 0 || index >= actionable.length) return;\n const moved = actionable[index];\n const rest = actionable.filter((_, i) => i !== index);\n const placeholders = state.suggestions.filter((s) => s.type === \"placeholder\");\n\n if (this.callbacks.onPillSelected) {\n const { rawQuery } = buildQuery(state.text, state.completedParams);\n this.callbacks.onPillSelected({\n rawQuery,\n selectedPill: moved.text,\n otherPills: rest.map((s) => s.text),\n });\n }\n\n const nextSuggestions = [...placeholders, moved, ...rest];\n\n // Highlight the moved pill's first tappable option so the pill reads as\n // \"selected\" (full opacity). `peek` derives the filtered options for the\n // not-yet-committed reorder so the highlight ships in the SAME write — no\n // intermediate notification carrying a stale activeDropdownIndex against the\n // new pill's options. -1 when the pill has no tappable option (it stays in\n // the `first` tier). In `hidden` mode the dropdown never opens, so the\n // highlight is inert there and the pill stays `first`.\n const firstTappable = this.store\n .peek({ suggestions: nextSuggestions })\n .filteredOptions.findIndex((o) => o.is_tappable);\n\n this.store.set({\n suggestions: nextSuggestions,\n pillTapped: true,\n activeDropdownIndex: firstTappable,\n });\n }\n\n removeLastParam() {\n const state = this.store.get();\n if (state.completedParams.length === 0) return;\n this.store.set((s) => ({\n completedParams: s.completedParams.slice(0, -1),\n activeDropdownIndex: -1,\n }));\n }\n}\n","import type { CompletedParamState, Segment } from \"../shared-types\";\n\n/**\n * Derives segments for overlay rendering by matching completed params in text.\n */\nexport function deriveSegments(text: string, completedParams: CompletedParamState[]): Segment[] {\n const result: Segment[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) continue;\n if (idx > pos) {\n result.push({ type: \"text\", value: text.slice(pos, idx) });\n }\n result.push({ type: \"completed\", value: param.text, param });\n pos = idx + param.text.length;\n }\n\n const remaining = text.slice(pos);\n if (remaining) {\n result.push({ type: \"text\", value: remaining });\n }\n\n return result;\n}\n\n/**\n * Checks which completed params still exist in the new text.\n */\nexport function reconcileParams(\n text: string,\n completedParams: CompletedParamState[],\n): { valid: CompletedParamState[]; invalid: CompletedParamState[] } {\n const valid: CompletedParamState[] = [];\n const invalid: CompletedParamState[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) {\n invalid.push(param);\n } else {\n valid.push(param);\n pos = idx + param.text.length;\n }\n }\n\n return { valid, invalid };\n}\n","export interface DropdownVisibilityInputs {\n /** True while re-editing a bold param. Bypasses caret-at-end and manual-trigger gating. */\n inEditMode: boolean;\n /** Number of options the dropdown would render right now (post-filter). */\n filteredOptionsLength: number;\n isFocused: boolean;\n text: string;\n caretOffset: number | null;\n isLoading: boolean;\n /** True after the user tapped a pill in `manual` trigger mode. */\n pillTapped: boolean;\n}\n\nexport interface DropdownVisibilityOpts {\n dropdownTrigger?: \"auto\" | \"manual\" | \"hidden\";\n closeDropdownOnBlur?: boolean;\n}\n\n// Pure: is the dropdown open right now? See call site in deriveAll for the gating rules.\nexport function computeDropdownVisibility(\n inputs: DropdownVisibilityInputs,\n opts: DropdownVisibilityOpts,\n): boolean {\n const trigger = opts.dropdownTrigger ?? \"auto\";\n const closeOnBlur = opts.closeDropdownOnBlur ?? true;\n const hasOptions = inputs.filteredOptionsLength > 0;\n\n if (inputs.inEditMode) {\n // While re-editing, dropdown visibility is governed solely by whether\n // cached options match the query — focus/manual gates don't apply (the\n // user is in a deliberate edit interaction).\n const focusGate = closeOnBlur ? inputs.isFocused : true;\n return hasOptions && focusGate;\n }\n\n if (trigger === \"auto\") {\n const focusGate = closeOnBlur ? inputs.isFocused : true;\n // Outside re-edit, dropdown only opens when the caret is at the end of\n // the input — middle-of-text caret should NOT show suggestions. Trailing\n // whitespace is ignored so the dropdown stays open right after an option\n // selection (which appends a trailing space) or when the user pauses on\n // a word boundary.\n const trimmedEnd = inputs.text.replace(/\\s+$/, \"\").length;\n const caretAtEnd = inputs.caretOffset == null || inputs.caretOffset >= trimmedEnd;\n return (hasOptions || inputs.isLoading) && focusGate && caretAtEnd;\n }\n\n if (trigger === \"manual\") {\n return (hasOptions || inputs.isLoading) && inputs.pillTapped;\n }\n\n return false;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreDerivedState, CoreInputState, CoreOptions } from \"../types\";\nimport { effectiveFilterBase, extractFilterQuery, filterOptions } from \"../utils/filtering\";\nimport { deriveSegments } from \"../utils/segments\";\nimport { computeDropdownVisibility } from \"./dropdown\";\n\n// Pure derive: raw inputs + opts → segments / actionable / filtered / placeholder / isDropdownOpen.\nexport function deriveAll(inputs: CoreInputState, opts: CoreOptions): CoreDerivedState {\n const segments = deriveSegments(inputs.text, inputs.completedParams);\n const actionableSuggestions = inputs.suggestions.filter((s) => s.type !== \"placeholder\");\n const activeSuggestion = actionableSuggestions[0] as Suggestion | undefined;\n const overrideFn = activeSuggestion ? opts.optionOverrides?.[activeSuggestion.type] : undefined;\n\n const placeholderText = inputs.suggestions\n .filter((s) => s.type === \"placeholder\")\n .map((s) => s.text)\n .join(\" \");\n\n // Clamp filterBase so it never exceeds text length. Promote the placeholder\n // to filterBase when the user has typed through it — the filter query should\n // be just the text past the server-suggested prefix.\n const clampedFilterBase = effectiveFilterBase(\n inputs.text,\n Math.min(inputs.filterBase, inputs.text.length),\n placeholderText,\n );\n // When filterBase is 0 and no fetch has set it yet, don't filter by typed\n // text — the user is still typing the initial query and no server context\n // exists.\n const hasServerContext = inputs.lastRawQuery !== \"\" || clampedFilterBase > 0;\n const filterQuery = hasServerContext\n ? extractFilterQuery(inputs.text, clampedFilterBase, inputs.filterInProgress)\n : \"\";\n const baseOptions = activeSuggestion\n ? overrideFn\n ? overrideFn(filterQuery.trim())\n : (activeSuggestion.options ?? [])\n : [];\n\n // Re-edit mode: dropdown filters the edited param's cached options instead\n // of the active suggestion's options. While the bold param's still in the\n // DOM (user just tapped it, hasn't typed yet), show the FULL cached list —\n // filtering by the param's own text would only match itself. Once the user\n // starts typing (param removed from completedParams), filter by what they\n // typed.\n const inEditMode = inputs.editingParam != null && inputs.editingAnchor != null;\n let filteredOptions: SuggestionOption[];\n if (inEditMode && inputs.editingParam && inputs.editingAnchor != null) {\n const editingId = inputs.editingParam.id;\n const paramStillPresent = inputs.completedParams.some((p) => p.id === editingId);\n const editCaret = inputs.caretOffset ?? inputs.editingAnchor;\n const editQuery = paramStillPresent ? \"\" : inputs.text.slice(inputs.editingAnchor, editCaret);\n filteredOptions = filterOptions(inputs.editingParam.options, editQuery);\n } else {\n filteredOptions = filterOptions(baseOptions, filterQuery);\n }\n // Consumer-controlled visibility of non-tappable options. Defaults to true\n // (current behavior). When false, strip them from what the dropdown sees.\n if (opts.showNonTappableOptions === false) {\n filteredOptions = filteredOptions.filter((o) => o.is_tappable);\n }\n\n const isDropdownOpen = computeDropdownVisibility(\n {\n inEditMode,\n filteredOptionsLength: filteredOptions.length,\n isFocused: inputs.isFocused,\n text: inputs.text,\n caretOffset: inputs.caretOffset,\n isLoading: inputs.isLoading,\n pillTapped: inputs.pillTapped,\n },\n {\n dropdownTrigger: opts.dropdownTrigger,\n closeDropdownOnBlur: opts.closeDropdownOnBlur,\n },\n );\n\n // The leading pill renders \"selected\" (full opacity) only while a tappable\n // dropdown option is highlighted; otherwise it shows the de-emphasized\n // `first` tier. This rule is uniform across triggers: in `auto` the highlight\n // comes from keyboard nav / hover / tapping the pill, and in `manual` tapping\n // the pill both opens the dropdown and highlights its first option (see\n // PillsController.setActivePill). In `hidden` the dropdown never opens, so the\n // guard below keeps the pill in `first`.\n const isActivePillSelected =\n isDropdownOpen &&\n inputs.activeDropdownIndex >= 0 &&\n Boolean(filteredOptions[inputs.activeDropdownIndex]?.is_tappable);\n\n return {\n segments,\n actionableSuggestions,\n filteredOptions,\n placeholderText,\n isDropdownOpen,\n isActivePillSelected,\n };\n}\n","import type { CompletedParamState, Suggestion } from \"../shared-types\";\nimport type { CoreState } from \"../types\";\nimport { effectiveFilterBase, extractFilterQuery, findExactMatch } from \"../utils/filtering\";\n\nexport type PromotionContext =\n | {\n mode: \"fresh\";\n text: string;\n completedParams: CompletedParamState[];\n suggestions: Suggestion[];\n filterBase: number;\n filterInProgress: boolean;\n }\n | {\n mode: \"edit\";\n text: string;\n completedParams: CompletedParamState[];\n editingParam: CompletedParamState;\n editingAnchor: number;\n editingTail: number;\n };\n\nexport interface PromotionResult {\n patch: Partial<CoreState>;\n caretPos: number;\n}\n\n// Promote typed text to a bold param when it exact-matches an option; null when no match.\nexport function tryPromoteExactMatch(ctx: PromotionContext): PromotionResult | null {\n if (ctx.mode === \"fresh\") {\n return promoteFresh(ctx);\n }\n return promoteEdit(ctx);\n}\n\nfunction promoteFresh(ctx: Extract<PromotionContext, { mode: \"fresh\" }>): PromotionResult | null {\n const { text, completedParams, suggestions, filterBase, filterInProgress } = ctx;\n const actionable = suggestions.filter((sg) => sg.type !== \"placeholder\");\n const active = actionable[0];\n if (!active?.options) return null;\n\n const placeholderText = suggestions\n .filter((sg) => sg.type === \"placeholder\")\n .map((sg) => sg.text)\n .join(\" \");\n const effBase = effectiveFilterBase(text, filterBase, placeholderText);\n const query = extractFilterQuery(text, effBase, filterInProgress);\n const match = findExactMatch(active.options, query);\n if (!match) return null;\n\n // Preserve the case the user actually typed so deriveSegments (case-sensitive\n // indexOf) can still match the completed param inside the text.\n const matchLower = match.text.toLowerCase();\n const optionStart = text.toLowerCase().lastIndexOf(matchLower);\n const paramStart = optionStart >= 0 ? optionStart : Math.max(0, text.length - match.text.length);\n const paramEnd = paramStart + match.text.length;\n const optionInText = text.slice(paramStart, paramEnd);\n\n const hasTrailingSpace = paramEnd < text.length && text[paramEnd] === \" \";\n const caretPos = hasTrailingSpace ? paramEnd + 1 : paramEnd;\n\n const completed: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: active.type,\n text: optionInText,\n kind: match.kind,\n suggestionType: active.type,\n suggestionPlaceholder: active.text,\n options: active.options ?? [],\n metadata: match.metadata,\n };\n\n return {\n patch: {\n text,\n completedParams: [...completedParams, completed],\n suggestions: suggestions.filter((sg) => sg !== active),\n filterBase: caretPos,\n newParamId: completed.id,\n caretOffset: caretPos,\n activeDropdownIndex: -1,\n },\n caretPos,\n };\n}\n\nfunction promoteEdit(ctx: Extract<PromotionContext, { mode: \"edit\" }>): PromotionResult | null {\n const { text, completedParams, editingParam, editingAnchor, editingTail } = ctx;\n // Skip while the original bold is still in completedParams (user hasn't\n // typed yet — the editQuery would just be the param's own text).\n if (completedParams.some((p) => p.id === editingParam.id)) return null;\n\n const editQuery = text.slice(editingAnchor, editingTail);\n const match = findExactMatch(editingParam.options, editQuery);\n if (!match) return null;\n\n // Locate the matched portion within the edit region so we know where the\n // new param's text sits in the full input. Preserve the user's typed\n // casing so deriveSegments (case-sensitive indexOf) can still find it.\n const matchLower = match.text.toLowerCase();\n const matchStart = editQuery.toLowerCase().lastIndexOf(matchLower);\n const paramStart = editingAnchor + Math.max(0, matchStart);\n const paramEnd = paramStart + match.text.length;\n const optionInText = text.slice(paramStart, paramEnd);\n\n const hasTrailingSpace = paramEnd < text.length && text[paramEnd] === \" \";\n const caretPos = hasTrailingSpace ? paramEnd + 1 : paramEnd;\n\n const newParam: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: editingParam.suggestionType,\n text: optionInText,\n kind: match.kind,\n suggestionType: editingParam.suggestionType,\n suggestionPlaceholder: editingParam.suggestionPlaceholder,\n options: editingParam.options,\n metadata: match.metadata,\n };\n\n // Insert at the correct text-order position. The editing param is already\n // gone from completedParams; walk the remaining params and insert before\n // the first one whose text sits past the edit region.\n let insertAt = completedParams.length;\n let scanPos = 0;\n for (let i = 0; i < completedParams.length; i++) {\n const idx = text.indexOf(completedParams[i].text, scanPos);\n if (idx === -1) continue;\n if (idx >= caretPos) {\n insertAt = i;\n break;\n }\n scanPos = idx + completedParams[i].text.length;\n }\n const newParams = [...completedParams];\n newParams.splice(insertAt, 0, newParam);\n\n return {\n patch: {\n text,\n completedParams: newParams,\n newParamId: newParam.id,\n filterBase: caretPos,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: caretPos,\n activeDropdownIndex: -1,\n },\n caretPos,\n };\n}\n","import { tryPromoteExactMatch } from \"../promotion/promote\";\nimport type { CompletedParamState, SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nexport interface ReEditDeps {\n store: Store<CoreState>;\n scheduleSetCursor: (offset: number) => void;\n fireTelemetry: (type: \"pill\" | \"option\", data: Record<string, unknown>) => void;\n startSelectionAnimationTimer: () => void;\n}\n\n// Re-edit lifecycle: enter / exit / atomic replace / caret tracking / select / promote-back-to-bold.\nexport class ReEditManager {\n constructor(private deps: ReEditDeps) {}\n\n // Caller highlights the param via Selection API; this just sets the state snapshot.\n start(paramId: string): void {\n const state = this.deps.store.get();\n if (state.editingParam?.id === paramId) return;\n const param = state.completedParams.find((p) => p.id === paramId);\n if (!param) return;\n // Find the param's position in text (same approach as deriveSegments).\n let pos = 0;\n let anchor = -1;\n for (const p of state.completedParams) {\n const idx = state.text.indexOf(p.text, pos);\n if (idx === -1) continue;\n if (p.id === paramId) {\n anchor = idx;\n break;\n }\n pos = idx + p.text.length;\n }\n if (anchor < 0) return;\n this.deps.store.set({\n editingParam: param,\n editingAnchor: anchor,\n editingTail: anchor + param.text.length,\n caretOffset: anchor + param.text.length,\n activeDropdownIndex: -1,\n });\n }\n\n /** Clear re-edit state. Selection collapsing is a DOM concern handled by the caller. */\n exit(): void {\n const state = this.deps.store.get();\n if (!state.editingParam) return;\n this.deps.store.set({\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n activeDropdownIndex: -1,\n });\n }\n\n // Atomic swap of `text[anchor..tail]` while the bold is still in completedParams; returns true if applied (caller preventDefaults).\n replaceRange(replacement: string): boolean {\n const state = this.deps.store.get();\n const editing = state.editingParam;\n const anchor = state.editingAnchor;\n const tail = state.editingTail;\n if (!editing || anchor == null || tail == null) return false;\n // Only intercept while the param's strong is still in the DOM (i.e. it\n // hasn't already been replaced by an earlier keystroke).\n if (!state.completedParams.some((p) => p.id === editing.id)) return false;\n const newText = state.text.slice(0, anchor) + replacement + state.text.slice(tail);\n const newTail = anchor + replacement.length;\n this.deps.store.set((s) => ({\n text: newText,\n completedParams: s.completedParams.filter((p) => p.id !== editing.id),\n editingTail: newTail,\n caretOffset: newTail,\n activeDropdownIndex: -1,\n }));\n this.deps.scheduleSetCursor(newTail);\n this.tryPromote();\n return true;\n }\n\n // Post-input: extends editingTail forward; exits if caret backspaced past anchor.\n caretAfterInput(offset: number | null): void {\n const state = this.deps.store.get();\n const patch: Partial<CoreState> = { caretOffset: offset };\n if (state.editingParam && state.editingAnchor != null && offset != null) {\n if (offset < state.editingAnchor) {\n patch.editingParam = null;\n patch.editingAnchor = null;\n patch.editingTail = null;\n patch.activeDropdownIndex = -1;\n } else if (state.editingTail != null) {\n patch.editingTail = Math.max(state.editingTail, offset);\n }\n }\n this.deps.store.set(patch);\n this.tryPromote();\n }\n\n // Caret moved by click/arrows (not typing): exits if outside [anchor, tail].\n caretMove(offset: number | null): void {\n const state = this.deps.store.get();\n if (\n state.editingParam &&\n state.editingAnchor != null &&\n state.editingTail != null &&\n offset != null &&\n (offset < state.editingAnchor || offset > state.editingTail)\n ) {\n this.deps.store.set({\n caretOffset: offset,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n activeDropdownIndex: -1,\n });\n return;\n }\n this.deps.store.set({ caretOffset: offset });\n }\n\n // Select-option flow for re-edit: new param inherits the same cached suggestion metadata.\n selectOption(option: SuggestionOption): void {\n const state = this.deps.store.get();\n const editing = state.editingParam;\n const anchor = state.editingAnchor;\n const tail = state.editingTail;\n if (!editing || anchor == null || tail == null) return;\n\n this.deps.fireTelemetry(\"option\", {\n raw_query: buildQuery(state.text, state.completedParams).rawQuery,\n selected_option: option.text,\n other_options: editing.options.filter((o) => o.text !== option.text).map((o) => o.text),\n });\n\n const before = state.text.slice(0, anchor);\n const after = state.text.slice(tail);\n // Capitalize when replacing at the very start of the input — matches the\n // case-handling in the normal selectOption path for \"first letter of the\n // input is uppercase\".\n const optionText =\n anchor === 0 && option.text.length > 0\n ? option.text[0].toUpperCase() + option.text.slice(1)\n : option.text;\n // Preserve the trailing-space convention: a single space follows the\n // replaced text unless the next char already provides one.\n const needsTrailingSpace = after.length === 0 || after[0] !== \" \";\n const replacement = needsTrailingSpace ? `${optionText} ` : optionText;\n const newText = before + replacement + after;\n // Caret lands AFTER the trailing space — whether we just added it or it\n // was already there from the original text.\n const caretPos = anchor + replacement.length + (needsTrailingSpace ? 0 : 1);\n\n const newParam: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: editing.suggestionType,\n text: optionText,\n kind: option.kind,\n suggestionType: editing.suggestionType,\n suggestionPlaceholder: editing.suggestionPlaceholder,\n options: editing.options,\n metadata: option.metadata,\n };\n const oldIdx = state.completedParams.findIndex((p) => p.id === editing.id);\n const params = state.completedParams.filter((p) => p.id !== editing.id);\n const insertAt = oldIdx >= 0 ? Math.min(oldIdx, params.length) : params.length;\n params.splice(insertAt, 0, newParam);\n\n this.deps.store.set({\n text: newText,\n completedParams: params,\n newParamId: newParam.id,\n filterBase: caretPos,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: caretPos,\n activeDropdownIndex: -1,\n pillTapped: false,\n skipNextFetch: true,\n inSelectionAnimation: true,\n });\n this.deps.startSelectionAnimationTimer();\n // Park caret right after the replacement (not end of text) once the render microtask commits.\n this.deps.scheduleSetCursor(caretPos);\n }\n\n private tryPromote(): void {\n const s = this.deps.store.get();\n if (!s.editingParam || s.editingAnchor == null || s.editingTail == null) return;\n const result = tryPromoteExactMatch({\n mode: \"edit\",\n text: s.text,\n completedParams: s.completedParams,\n editingParam: s.editingParam,\n editingAnchor: s.editingAnchor,\n editingTail: s.editingTail,\n });\n if (!result) return;\n this.deps.store.set(result.patch);\n this.deps.scheduleSetCursor(result.caretPos);\n }\n}\n","/**\n * The dropdown footer's keyboard hint has three states, in priority order:\n * 1. an option is highlighted → \"enter to proceed\" (Enter commits it);\n * 2. otherwise, the input is empty → \"tab to select\" (Tab highlights the first\n * option). This is the ONLY state that shows \"tab to select\";\n * 3. otherwise (the input has text) → \"→ to skip\" — the right arrow skips the\n * active pill.\n *\n * Pure computation — no DOM access. Shared by the vanilla renderer and the\n * React / Angular footer components so the wording can't drift between them.\n */\nexport function getFooterHint(\n optionHighlighted: boolean,\n isInputEmpty: boolean,\n): { key: string; hint: string } {\n if (optionHighlighted) return { key: \"enter\", hint: \"to proceed\" };\n if (isInputEmpty) return { key: \"tab\", hint: \"to select\" };\n return { key: \"→\", hint: \"to skip\" };\n}\n","const KEY_ATTR = \"data-aia-key\";\n\nexport interface ReconcileOptions<T> {\n keyOf: (item: T, index: number) => string;\n create: (item: T, index: number) => HTMLElement;\n update?: (el: HTMLElement, item: T, index: number) => void;\n}\n\n// Keyed DOM reconcile — reuses/repositions/removes `data-aia-key` children; unkeyed children untouched.\nexport function reconcileList<T>(\n parent: Element,\n items: readonly T[],\n opts: ReconcileOptions<T>,\n): HTMLElement[] {\n const existing = new Map<string, HTMLElement>();\n for (const child of Array.from(parent.children)) {\n const key = child.getAttribute(KEY_ATTR);\n if (key != null) existing.set(key, child as HTMLElement);\n }\n\n const used = new Set<string>();\n const result: HTMLElement[] = [];\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const key = opts.keyOf(item, i);\n used.add(key);\n let el = existing.get(key);\n if (!el) {\n el = opts.create(item, i);\n el.setAttribute(KEY_ATTR, key);\n }\n opts.update?.(el, item, i);\n if (parent.children[i] !== el) {\n parent.insertBefore(el, parent.children[i] ?? null);\n }\n result.push(el);\n }\n\n for (const [key, el] of existing) {\n if (!used.has(key)) el.remove();\n }\n\n return result;\n}\n","import type { Suggestion } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\nconst FALLBACK_SKELETON_WIDTHS = [125, 69];\n\n// Opacity per pill state. The selected pill (active pill while the dropdown is\n// open on it) is full opacity; otherwise pills take a positional tier:\n// first → next → last.\nfunction getPillOpacity(index: number, selected: boolean): number {\n if (selected) return 1; // Selected\n if (index === 0) return 0.7; // First\n if (index === 1) return 0.4; // Next\n return 0.2; // Last\n}\n\nexport function renderPills(\n container: HTMLElement,\n pills: Suggestion[],\n activePillIndex: number,\n onSelectPill: (index: number) => void,\n rounded = false,\n loading = false,\n /**\n * Whether the active (leading) pill is in its selected state — full opacity\n * instead of the positional `first` tier. Auto trigger ⇒ true while a\n * dropdown option is highlighted; manual trigger ⇒ true after the user taps\n * the pill. See `CoreDerivedState.isActivePillSelected`.\n */\n activeSelected = false,\n) {\n let list = container.querySelector<HTMLElement>(\".magicx-aia-pill-list\");\n if (!list) {\n list = document.createElement(\"span\");\n list.className = \"magicx-aia-pill-list\";\n container.appendChild(list);\n }\n\n // No cached pills + loading → fallback fixed-width placeholders.\n if (loading && pills.length === 0) {\n list.setAttribute(\"data-aia-pill-list-loading\", \"\");\n list.innerHTML = \"\";\n for (let i = 0; i < FALLBACK_SKELETON_WIDTHS.length; i++) {\n const width = FALLBACK_SKELETON_WIDTHS[i];\n const span = document.createElement(\"span\");\n span.setAttribute(\"data-aia-pill-skeleton\", \"\");\n span.className = `magicx-aia-pill magicx-aia-pill--skeleton${rounded ? \" magicx-aia-pill--rounded\" : \"\"}`;\n span.style.width = `${width}px`;\n span.style.opacity = String(getPillOpacity(i, false));\n list.appendChild(span);\n }\n return;\n }\n\n if (loading) {\n list.setAttribute(\"data-aia-pill-list-loading\", \"\");\n } else {\n list.removeAttribute(\"data-aia-pill-list-loading\");\n }\n\n // Drop any leftover fallback placeholders before diffing the real pills.\n for (const skel of list.querySelectorAll<HTMLElement>(\"[data-aia-pill-skeleton]\")) {\n skel.remove();\n }\n\n reconcileList(list, pills, {\n keyOf: (pill) => `${pill.type}-${pill.text}`,\n create: (pill) => {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.tabIndex = -1;\n btn.setAttribute(\"data-aia-pill\", \"\");\n btn.setAttribute(\"contenteditable\", \"false\");\n btn.textContent = pill.text;\n btn.addEventListener(\"mousedown\", (e) => e.preventDefault());\n return btn;\n },\n update: (el, _pill, i) => {\n const btn = el as HTMLButtonElement;\n // Selected = the active pill in its selected state (see activeSelected).\n const selected = activeSelected && i === activePillIndex && !loading;\n const classes = [\"magicx-aia-pill\"];\n if (rounded) classes.push(\"magicx-aia-pill--rounded\");\n if (selected) classes.push(\"magicx-aia-pill--active\");\n if (loading) classes.push(\"magicx-aia-pill--skeleton\");\n btn.className = classes.join(\" \");\n btn.style.width = \"\";\n btn.style.opacity = String(getPillOpacity(i, selected));\n if (loading) {\n btn.setAttribute(\"data-aia-loading\", \"\");\n btn.disabled = true;\n btn.onclick = null;\n } else {\n btn.removeAttribute(\"data-aia-loading\");\n btn.disabled = false;\n btn.onclick = () => onSelectPill(i);\n }\n },\n });\n}\n\nexport function clearPills(container: HTMLElement) {\n container.querySelector(\".magicx-aia-pill-list\")?.remove();\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\n// Per-wrapper scroll/resize wiring, kept so the bottom fade can be torn down\n// when the grid is removed instead of leaking observers across rebuilds.\nconst fadeObservers = new WeakMap<HTMLElement, { ro: ResizeObserver; onScroll: () => void }>();\n\n/**\n * Renders the dropdown's option grid into `parent`: a `.aia-grid-fade` wrapper\n * holding a `.aia-grid` element (the intrinsic Grid layout primitive — auto-fit\n * 250px columns, scrollable with a capped height) with one option element per\n * suggestion. Vanilla analogue of React's `SuggestionGrid` component. The\n * wrapper shows a bottom fade (`data-fade`) while the grid overflows below.\n * Creates the wrapper on first use, reuses it on later renders, and removes it\n * (tearing down its observers) when there are no options.\n */\nexport function renderSuggestionGrid(\n parent: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n onSelect: (option: SuggestionOption) => void,\n onHighlight: (index: number) => void,\n listboxId: string,\n loading: boolean,\n): void {\n let wrapper = parent.querySelector<HTMLElement>(\".aia-grid-fade\");\n if (options.length === 0) {\n if (wrapper) {\n teardownFade(wrapper);\n wrapper.remove();\n }\n return;\n }\n let grid = wrapper?.querySelector<HTMLElement>(\".aia-grid\") ?? null;\n if (!wrapper || !grid) {\n wrapper = document.createElement(\"div\");\n wrapper.className = \"aia-grid-fade\";\n grid = document.createElement(\"div\");\n grid.className = \"aia-grid magicx-aia-grid\";\n grid.setAttribute(\"data-scroll\", \"\");\n grid.style.setProperty(\"--aia-grid-min\", \"250px\");\n grid.style.setProperty(\"--aia-grid-max\", \"250px\");\n grid.style.setProperty(\"--aia-grid-gap\", \"0\");\n wrapper.appendChild(grid);\n setupFade(wrapper, grid);\n parent.appendChild(wrapper);\n }\n renderOptions(grid, options, activeIndex, onSelect, onHighlight, listboxId, loading);\n updateFade(wrapper, grid);\n}\n\n// Show the fade while there's hidden overflow below the grid; hide it once the\n// content fits or the user scrolls to the bottom. Mirrors React's Grid effect.\nfunction updateFade(wrapper: HTMLElement, grid: HTMLElement): void {\n const overflowBelow = grid.scrollHeight - grid.scrollTop - grid.clientHeight > 1;\n if (overflowBelow) wrapper.setAttribute(\"data-fade\", \"\");\n else wrapper.removeAttribute(\"data-fade\");\n}\n\nfunction setupFade(wrapper: HTMLElement, grid: HTMLElement): void {\n const onScroll = () => updateFade(wrapper, grid);\n const ro = new ResizeObserver(onScroll);\n ro.observe(grid);\n grid.addEventListener(\"scroll\", onScroll, { passive: true });\n fadeObservers.set(wrapper, { ro, onScroll });\n}\n\nfunction teardownFade(wrapper: HTMLElement): void {\n const entry = fadeObservers.get(wrapper);\n if (!entry) return;\n entry.ro.disconnect();\n wrapper.querySelector<HTMLElement>(\".aia-grid\")?.removeEventListener(\"scroll\", entry.onScroll);\n fadeObservers.delete(wrapper);\n}\n\nfunction renderOptions(\n grid: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n onSelect: (option: SuggestionOption) => void,\n onHighlight: (index: number) => void,\n listboxId: string,\n loading: boolean,\n) {\n // Loading flips re-key every option so any cached non-loading element\n // is replaced rather than reused with stale loading attrs.\n const loadingFlag = loading ? \"1\" : \"0\";\n\n reconcileList(grid, options, {\n keyOf: (opt) => `${opt.text}\\0${loadingFlag}`,\n create: (option) => buildOptionElement(option, loading),\n update: (el, option, i) => {\n const isHighlighted = i === activeIndex && !loading;\n el.id = `${listboxId}-option-${i}`;\n el.dataset.aiaIndex = String(i);\n el.setAttribute(\"aria-selected\", String(isHighlighted));\n el.classList.toggle(\"magicx-aia-option--highlighted\", isHighlighted);\n // Reassign every render so reused elements don't hold stale closures.\n if (!loading && option.is_tappable) {\n el.onclick = () => {\n el.classList.add(\"magicx-aia-option--pressed\");\n onSelect(option);\n setTimeout(() => el.classList.remove(\"magicx-aia-option--pressed\"), 500);\n };\n el.onmouseenter = () => {\n const idx = Number.parseInt(el.dataset.aiaIndex ?? \"-1\", 10);\n if (idx >= 0) onHighlight(idx);\n };\n } else {\n el.onclick = null;\n el.onmouseenter = null;\n }\n },\n });\n}\n\nfunction buildOptionElement(option: SuggestionOption, loading: boolean): HTMLElement {\n const item = document.createElement(\"div\");\n item.setAttribute(\"role\", \"option\");\n item.setAttribute(\"data-aia-option\", \"\");\n if (loading) item.setAttribute(\"data-aia-loading\", \"\");\n item.tabIndex = loading || !option.is_tappable ? -1 : 0;\n\n const classes = [\"magicx-aia-option\"];\n if (option.is_tappable) {\n classes.push(\"magicx-aia-option--tappable\");\n } else {\n classes.push(\"magicx-aia-option--non-tappable\");\n }\n item.className = classes.join(\" \");\n\n const streaks = document.createElement(\"div\");\n streaks.className = \"magicx-aia-streaks\";\n item.appendChild(streaks);\n\n const streaksVert = document.createElement(\"div\");\n streaksVert.className = \"magicx-aia-streaks-vert\";\n item.appendChild(streaksVert);\n\n const content = document.createElement(\"span\");\n content.className = \"magicx-aia-option-content\";\n\n // Inner inline span so multi-line options render the skeleton as one bar\n // per line. Painting the background on .content directly would collapse\n // all lines into one tall rectangle (flex blockifies it).\n const text = document.createElement(\"span\");\n text.className = \"magicx-aia-option-text\";\n text.textContent = option.icon ? `${option.icon} ${option.text}` : option.text;\n content.appendChild(text);\n\n if (option.tag) {\n const tag = document.createElement(\"span\");\n tag.className = \"magicx-aia-option-tag\";\n tag.textContent = option.tag;\n content.appendChild(tag);\n }\n\n item.appendChild(content);\n\n return item;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\nimport { getFooterHint } from \"../utils/footerHint\";\nimport { renderPills } from \"./renderPills\";\nimport { renderSuggestionGrid } from \"./renderSuggestionGrid\";\n\nconst FALLBACK_SKELETON_BAR_WIDTHS = [159, 119, 164];\n\ninterface DropdownState {\n suggestions: Suggestion[];\n filteredOptions: SuggestionOption[];\n activeIndex: number;\n isOpen: boolean;\n isLoading: boolean;\n listboxId: string;\n pills: Suggestion[];\n showPills: boolean;\n /** Whether the active pill renders selected — see CoreDerivedState.isActivePillSelected. */\n isActivePillSelected: boolean;\n /** Whether the input has no typed text — the sole state that surfaces \"tab to select\" (see getFooterHint). */\n isInputEmpty: boolean;\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n onPillClick: (index: number) => void;\n}\n\nexport function createDropdown(listboxId: string): HTMLElement {\n const dropdown = document.createElement(\"div\");\n dropdown.id = listboxId;\n dropdown.setAttribute(\"role\", \"listbox\");\n dropdown.setAttribute(\"data-aia-dropdown\", \"\");\n dropdown.className = \"magicx-aia-dropdown\";\n dropdown.addEventListener(\"mousedown\", (e) => e.preventDefault());\n return dropdown;\n}\n\nexport function renderDropdown(dropdown: HTMLElement, state: DropdownState) {\n const {\n filteredOptions,\n activeIndex,\n isOpen,\n isLoading,\n pills,\n showPills,\n isActivePillSelected,\n onSelect,\n onHighlight,\n onPillClick,\n } = state;\n\n const hasPills = showPills && pills.length > 0;\n const hasOptions = filteredOptions.length > 0;\n const isVisible = isOpen && (hasOptions || hasPills || isLoading);\n\n if (isVisible) {\n dropdown.classList.add(\"magicx-aia-dropdown--visible\");\n } else {\n dropdown.classList.remove(\"magicx-aia-dropdown--visible\");\n }\n\n if (isLoading) {\n dropdown.setAttribute(\"data-aia-loading\", \"\");\n } else {\n dropdown.removeAttribute(\"data-aia-loading\");\n }\n\n // When the dropdown is closing/closed, leave its last-rendered content in\n // place and let the opacity transition fade the whole populated dropdown out.\n // Tearing the content down here instead would strip the pills/options grid\n // immediately while the always-present footer keeps fading — producing a\n // flash of a footer-only \"no options\" box mid-transition (e.g. when the user\n // skips the last pill with →). Content is rebuilt on the next visible render.\n if (!isVisible) return;\n\n // --- Stack (vertical layout primitive) ---\n // Pill bar, options grid, and skeleton bars stack top-to-bottom inside it.\n let stack = dropdown.querySelector<HTMLElement>(\".aia-stack\");\n if (!stack) {\n stack = document.createElement(\"div\");\n stack.className = \"aia-stack\";\n stack.style.setProperty(\"--aia-stack-space\", \"8px\");\n dropdown.appendChild(stack);\n }\n\n // --- Pill bar (cluster of pills) ---\n // Render the pill bar when we have real pills OR when loading + showPills\n // (so the bar can host the fallback placeholder pills).\n const wantsPillBar = hasPills || (isLoading && showPills);\n let pillBar = stack.querySelector<HTMLElement>(\".magicx-aia-pill-bar\");\n if (wantsPillBar) {\n if (!pillBar) {\n pillBar = document.createElement(\"div\");\n pillBar.className = \"magicx-aia-pill-bar aia-cluster\";\n pillBar.setAttribute(\"data-nowrap\", \"\");\n pillBar.setAttribute(\"data-aia-pillbar\", \"\");\n stack.insertBefore(pillBar, stack.firstChild);\n }\n renderPills(pillBar, pills, 0, onPillClick, true, isLoading, isActivePillSelected);\n } else if (pillBar) {\n pillBar.remove();\n }\n\n // --- Options grid (SuggestionGrid) ---\n // The grid element + option items are managed by renderSuggestionGrid, the\n // vanilla analogue of React's <SuggestionGrid>.\n renderSuggestionGrid(\n stack,\n filteredOptions,\n activeIndex,\n onSelect,\n onHighlight,\n state.listboxId,\n isLoading,\n );\n\n // --- Fallback skeleton bars (when loading with no cached options) ---\n let skeleton = stack.querySelector<HTMLElement>(\".magicx-aia-skeleton-bars\");\n if (isLoading && !hasOptions) {\n if (!skeleton) {\n skeleton = document.createElement(\"div\");\n skeleton.className = \"magicx-aia-skeleton-bars\";\n skeleton.setAttribute(\"data-aia-skeleton-bars\", \"\");\n for (const width of FALLBACK_SKELETON_BAR_WIDTHS) {\n const bar = document.createElement(\"span\");\n bar.className = \"magicx-aia-skeleton-bar\";\n bar.style.width = `${width}px`;\n skeleton.appendChild(bar);\n }\n stack.appendChild(skeleton);\n }\n } else if (skeleton) {\n skeleton.remove();\n }\n\n // --- Footer (chrome — always last; hidden with the dropdown) ---\n // Keep it below a grid/skeleton created later, but only move it when it isn't\n // already the last child — appendChild on an attached node is a DOM mutation\n // that would otherwise fire on every render (spurious Mutation/ResizeObserver\n // callbacks in consumer apps on each keystroke/highlight).\n const footer = stack.querySelector<HTMLElement>(\".magicx-aia-footer\") ?? createFooter();\n const optionHighlighted = activeIndex >= 0 && Boolean(filteredOptions[activeIndex]?.is_tappable);\n updateFooterHint(footer, getFooterHint(optionHighlighted, state.isInputEmpty));\n if (stack.lastElementChild !== footer) stack.appendChild(footer);\n}\n\nfunction updateFooterHint(\n footer: HTMLElement,\n { key: nextKey, hint: nextHint }: ReturnType<typeof getFooterHint>,\n) {\n const key = footer.querySelector<HTMLElement>(\".magicx-aia-footer-key\");\n const hint = footer.querySelector<HTMLElement>(\".magicx-aia-footer-hint\");\n if (!key || !hint) return;\n if (key.textContent !== nextKey) key.textContent = nextKey;\n if (hint.textContent !== nextHint) hint.textContent = nextHint;\n}\n\nfunction createFooter(): HTMLElement {\n const footer = document.createElement(\"footer\");\n footer.className = \"magicx-aia-footer\";\n footer.setAttribute(\"data-aia-footer\", \"\");\n\n const divider = document.createElement(\"div\");\n divider.className = \"magicx-aia-footer-divider\";\n\n const row = document.createElement(\"div\");\n row.className = \"aia-cluster\";\n row.setAttribute(\"data-align\", \"center\");\n row.setAttribute(\"data-justify\", \"between\");\n row.setAttribute(\"data-nowrap\", \"\");\n\n const hintGroup = document.createElement(\"div\");\n hintGroup.className = \"aia-cluster\";\n hintGroup.setAttribute(\"data-align\", \"center\");\n hintGroup.style.setProperty(\"--aia-cluster-gap\", \"5px\");\n const key = document.createElement(\"kbd\");\n key.className = \"magicx-aia-footer-key\";\n key.textContent = \"tab\";\n const hint = document.createElement(\"span\");\n hint.className = \"magicx-aia-footer-hint\";\n hint.textContent = \"to select\";\n hintGroup.append(key, hint);\n\n const brandGroup = document.createElement(\"a\");\n brandGroup.className = \"aia-cluster magicx-aia-footer-brand-link\";\n brandGroup.setAttribute(\"data-align\", \"center\");\n brandGroup.href = \"https://ai-autocomplete.com\";\n brandGroup.target = \"_blank\";\n brandGroup.rel = \"noopener noreferrer\";\n brandGroup.style.setProperty(\"--aia-cluster-gap\", \"2px\");\n const brand = document.createElement(\"span\");\n brand.className = \"magicx-aia-footer-brand\";\n brand.textContent = \"AI\";\n const badge = document.createElement(\"span\");\n badge.className = \"magicx-aia-footer-badge\";\n badge.textContent = \"Autocomplete\";\n brandGroup.append(brand, badge);\n\n row.append(hintGroup, brandGroup);\n footer.append(divider, row);\n return footer;\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { createDropdown, renderDropdown } from \"./renderDropdown\";\n\ninterface DropdownOnlyRefs {\n dropdown: HTMLElement;\n}\n\ninterface DropdownOnlyOptions {\n store: Store<CoreState>;\n listboxId: string;\n selectOption: (option: SuggestionOption) => void;\n setActivePill: (index: number) => void;\n}\n\nexport function buildDropdownOnly(\n container: HTMLElement,\n opts: DropdownOnlyOptions,\n): DropdownOnlyRefs {\n const dropdown = createDropdown(opts.listboxId);\n container.appendChild(dropdown);\n return { dropdown };\n}\n\nexport function updateDropdownOnly(\n refs: DropdownOnlyRefs,\n state: CoreState,\n opts: DropdownOnlyOptions,\n) {\n renderDropdown(refs.dropdown, {\n suggestions:\n state.actionableSuggestions.length > 0\n ? [{ ...state.actionableSuggestions[0], options: state.filteredOptions }]\n : [],\n filteredOptions: state.filteredOptions,\n activeIndex: state.activeDropdownIndex,\n isOpen: state.isDropdownOpen,\n // Re-edit shows cached options, and the streak animation finishes before\n // we swap in the skeleton.\n isLoading: state.isLoading && !state.editingParam && !state.inSelectionAnimation,\n listboxId: opts.listboxId,\n pills: state.actionableSuggestions,\n showPills: true, // always show pills in dropdown-only mode\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n onSelect: opts.selectOption,\n onHighlight: (i) => opts.store.set({ activeDropdownIndex: i }),\n onPillClick: opts.setActivePill,\n });\n}\n","import { getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { Segment } from \"../shared-types\";\n\ninterface RenderEditableArgs {\n input: HTMLElement;\n segments: Segment[];\n newParamId: string | null;\n /** When set, the matching `<strong>` is decorated with the editing class. */\n editingParamId: string | null;\n placeholderText: string;\n isFocused: boolean;\n}\n\n/**\n * Renders text segments — including bold `<strong>` runs for completed params\n * — into the contentEditable input. Pills are NOT rendered here; they live as\n * a sibling element so the editable's subtree never contains non-editable\n * children. See renderInput.ts for the pill list placement.\n *\n * Skips rebuilds when the segment key is unchanged so an in-flight shimmer\n * animation isn't interrupted by unrelated state churn.\n */\nexport function renderEditableContent(args: RenderEditableArgs) {\n const { input, segments, newParamId, editingParamId, placeholderText, isFocused } = args;\n\n const empty = segments.length === 0;\n input.dataset.aiaEmpty = empty ? \"true\" : \"false\";\n if (empty && placeholderText) {\n input.dataset.placeholder = placeholderText;\n } else {\n delete input.dataset.placeholder;\n }\n\n const segKey = segments.map((s) => `${s.type}:${s.value}`).join(\"\\0\");\n const lastSegKey = input.dataset.segKey ?? \"\";\n const lastNewParamId = input.dataset.newParamId ?? \"\";\n const lastEditingParamId = input.dataset.editingParamId ?? \"\";\n if (\n segKey === lastSegKey &&\n (newParamId ?? \"\") === lastNewParamId &&\n (editingParamId ?? \"\") === lastEditingParamId\n ) {\n return;\n }\n\n const savedOffset = isFocused ? getCursorOffset(input) : null;\n input.dataset.segKey = segKey;\n input.dataset.newParamId = newParamId ?? \"\";\n input.dataset.editingParamId = editingParamId ?? \"\";\n\n const doc = input.ownerDocument ?? document;\n const frag = doc.createDocumentFragment();\n let newLength = 0;\n for (const seg of segments) {\n newLength += seg.value.length;\n if (seg.type === \"completed\") {\n const strong = doc.createElement(\"strong\");\n strong.dataset.seg = \"completed\";\n strong.dataset.paramId = seg.param.id;\n const isNew = seg.param.id === newParamId;\n const isEditing = seg.param.id === editingParamId;\n const classes = [\"magicx-aia-segment\", \"magicx-aia-segment--completed\"];\n if (isNew) classes.push(\"magicx-aia-shimmer-revealed\", \"magicx-aia-shimmer-sweep\");\n if (isEditing) classes.push(\"magicx-aia-segment--editing\");\n strong.className = classes.join(\" \");\n strong.textContent = seg.value;\n frag.appendChild(strong);\n } else {\n frag.appendChild(doc.createTextNode(seg.value));\n }\n }\n input.replaceChildren(frag);\n input.dataset.aiaTextLength = String(newLength);\n\n if (savedOffset != null) {\n // Restore the caret to where the browser left it (clamped to the new\n // text length). Callers that need the caret at a specific position\n // (e.g. Tab-on-placeholder, edit-mode replacement) schedule their own\n // `setCursorOffset` via `queueMicrotask` after the store mutation.\n setCursorOffset(input, Math.max(0, Math.min(savedOffset, newLength)));\n }\n}\n","const SUBMIT_SVG = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" role=\"img\" aria-label=\"Submit\"><path d=\"M9 14V4M9 4L4 9M9 4L14 9\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`;\n\n/**\n * Builds the default circular submit button (up-arrow). Vanilla analogue of\n * React's `<SubmitButton>`. The default/custom/null branching stays in the\n * caller (`renderInput`); this only constructs the built-in button.\n */\nexport function createSubmitButton(): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"magicx-aia-submit\";\n btn.setAttribute(\"aria-label\", \"Submit\");\n btn.setAttribute(\"data-aia-submit\", \"\");\n btn.innerHTML = SUBMIT_SVG;\n return btn;\n}\n","import { extractPlainText, getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { AutocompleteResult, Suggestion } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\nimport { createDropdown, renderDropdown } from \"./renderDropdown\";\nimport { renderEditableContent } from \"./renderEditable\";\nimport { clearPills, renderPills } from \"./renderPills\";\nimport { createSubmitButton } from \"./renderSubmitButton\";\n\ninterface RenderInputOptions {\n store: Store<CoreState>;\n listboxId: string;\n pillPlacement: \"inline\" | \"dropdown\" | \"hidden\";\n autoFocus?: boolean;\n onSubmit?: (result: AutocompleteResult) => void;\n /** Invoked after onSubmit fires — Tier 1 uses this to auto-reset. */\n afterSubmit?: () => void;\n submitButton?: HTMLElement | null;\n selectOption: (option: import(\"../shared-types\").SuggestionOption) => void;\n setActivePill: (index: number) => void;\n handleKeyDown: (e: KeyboardEvent) => void;\n handleChange: (value: string) => void;\n /** Re-edit entry: triggered when caret enters a bold completed param. */\n startEditingParam: (paramId: string) => void;\n /** Re-edit caret-tracking after an input event (extends edit tail). */\n handleCaretAfterInput: (offset: number | null) => void;\n /** Re-edit caret-tracking on selection-only moves (may exit edit mode). */\n handleCaretMove: (offset: number | null) => void;\n /** Re-edit beforeinput intercept: replace the editing range atomically. */\n replaceEditingRange: (replacement: string) => boolean;\n}\n\nexport interface DOMRefs {\n input: HTMLDivElement;\n /** Non-editable inline pill list rendered as a sibling of the editable. */\n inlinePillContainer: HTMLSpanElement;\n dropdown: HTMLElement;\n /** The default built-in button when no `submitButton` was provided. Null when consumer passed `null` or a custom element. */\n submitButton: HTMLButtonElement | null;\n /** Aborts all listeners attached during buildDOM. */\n abort: AbortController;\n}\n\nfunction supportsPlaintextOnly(): boolean {\n const probe = document.createElement(\"div\");\n probe.setAttribute(\"contenteditable\", \"plaintext-only\");\n return probe.contentEditable === \"plaintext-only\";\n}\n\n/**\n * Toggles `data-aia-pill-wrapped` on the pill list container when it has\n * wrapped to its own line (no preceding text on the same line). CSS uses the\n * attribute to drop the 8px left margin — that margin would otherwise appear\n * as a stray indent at the start of the wrapped line.\n *\n * Wrap detection compares the inner pill row's top with the editor's bottom.\n * If the pill row sits on (or above) the editor's last line, it's NOT\n * wrapped — keep the margin. This holds whether the editor has typed text or\n * just a placeholder; the placeholder still occupies the editor's line box,\n * so pills sitting alongside it should retain the visible gap.\n */\nfunction measurePillWrap(input: HTMLElement, container: HTMLElement): void {\n const inner = container.firstElementChild as HTMLElement | null;\n if (!inner) {\n container.removeAttribute(\"data-aia-pill-wrapped\");\n return;\n }\n const cRect = inner.getBoundingClientRect();\n const eRect = input.getBoundingClientRect();\n const wrapped = cRect.top >= eRect.bottom - 2;\n if (wrapped) container.setAttribute(\"data-aia-pill-wrapped\", \"\");\n else container.removeAttribute(\"data-aia-pill-wrapped\");\n}\n\nexport function buildDOM(container: HTMLElement, opts: RenderInputOptions): DOMRefs {\n const { listboxId } = opts;\n\n const dropdown = createDropdown(listboxId);\n container.appendChild(dropdown);\n\n const inputWrapper = document.createElement(\"div\");\n inputWrapper.className = \"magicx-aia-input-wrapper\";\n container.appendChild(inputWrapper);\n\n const editor = document.createElement(\"div\");\n editor.className = \"magicx-aia-editor\";\n editor.setAttribute(\"data-aia-editor\", \"\");\n inputWrapper.appendChild(editor);\n\n const input = document.createElement(\"div\");\n input.className = \"magicx-aia-input\";\n input.setAttribute(\"data-aia-input\", \"\");\n input.setAttribute(\"contenteditable\", supportsPlaintextOnly() ? \"plaintext-only\" : \"true\");\n input.setAttribute(\"role\", \"combobox\");\n input.setAttribute(\"aria-autocomplete\", \"list\");\n input.setAttribute(\"aria-haspopup\", \"listbox\");\n input.setAttribute(\"aria-controls\", listboxId);\n input.setAttribute(\"aria-expanded\", \"false\");\n input.setAttribute(\"spellcheck\", \"true\");\n input.setAttribute(\"enterkeyhint\", \"send\");\n editor.appendChild(input);\n\n // Pills sit as a sibling of the editable — they're never inside a\n // contentEditable subtree, so there's no risk of them becoming part of the\n // typing surface. Layout (CSS) keeps them visually adjacent to the text.\n const inlinePillContainer = document.createElement(\"span\");\n inlinePillContainer.className = \"magicx-aia-pill-list-container\";\n inlinePillContainer.setAttribute(\"data-aia-pill-list-container\", \"\");\n editor.appendChild(inlinePillContainer);\n\n let submitButton: HTMLButtonElement | null = null;\n let submitTarget: HTMLElement | null = null;\n if (opts.submitButton === undefined) {\n submitButton = createSubmitButton();\n inputWrapper.appendChild(submitButton);\n submitTarget = submitButton;\n } else if (opts.submitButton !== null) {\n submitTarget = opts.submitButton;\n if (!submitTarget.hasAttribute(\"data-aia-submit\")) {\n submitTarget.setAttribute(\"data-aia-submit\", \"\");\n }\n inputWrapper.appendChild(submitTarget);\n }\n\n const abort = new AbortController();\n const { signal } = abort;\n\n let composing = false;\n // Tracks when an input event has just fired so the immediately-following\n // selectionchange knows to extend the edit tail rather than treat the\n // caret move as a navigation event.\n let lastInputAt = 0;\n\n const fireInput = () => {\n const raw = extractPlainText(input);\n const shouldCapitalize = raw.length > 0 && raw[0] !== raw[0].toUpperCase();\n const newValue = shouldCapitalize ? raw[0].toUpperCase() + raw.slice(1) : raw;\n opts.handleChange(newValue);\n };\n\n const findEnclosingParamId = (): string | null => {\n const sel = (input.ownerDocument ?? document).getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const anchor = sel.anchorNode;\n if (!anchor || !input.contains(anchor)) return null;\n const startEl =\n anchor.nodeType === Node.ELEMENT_NODE ? (anchor as Element) : anchor.parentElement;\n const strong = startEl?.closest<HTMLElement>('strong[data-seg=\"completed\"][data-param-id]');\n return strong?.dataset.paramId ?? null;\n };\n\n inputWrapper.addEventListener(\n \"click\",\n (e) => {\n // Clicking a pill button should activate the pill, not steal focus.\n if ((e.target as HTMLElement | null)?.closest(\"[data-aia-pill]\")) return;\n input.focus();\n },\n { signal },\n );\n\n input.addEventListener(\n \"input\",\n () => {\n if (composing) return;\n lastInputAt = performance.now();\n fireInput();\n // After typing, extend the edit tail to the new caret position so the\n // user can keep typing past the param's original end without exiting.\n opts.handleCaretAfterInput(getCursorOffset(input));\n },\n { signal },\n );\n\n // selectionchange fires on the document (Selection API doesn't bubble to\n // elements). Filter to selections anchored inside our editor, then route\n // the caret state into the core. A short cooldown after `input` events\n // avoids treating the post-typing caret position as a \"move\" that would\n // exit edit mode.\n const doc = input.ownerDocument ?? document;\n doc.addEventListener(\n \"selectionchange\",\n () => {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n if (!input.contains(sel.anchorNode!)) return;\n const enclosing = findEnclosingParamId();\n const editingId = opts.store.get().editingParam?.id ?? null;\n if (enclosing && enclosing !== editingId) {\n opts.startEditingParam(enclosing);\n return;\n }\n if (performance.now() - lastInputAt < 50) {\n // Just-typed: caret move handled by handleCaretAfterInput above.\n return;\n }\n opts.handleCaretMove(getCursorOffset(input));\n },\n { signal },\n );\n\n input.addEventListener(\n \"compositionstart\",\n () => {\n composing = true;\n },\n { signal },\n );\n input.addEventListener(\n \"compositionend\",\n () => {\n composing = false;\n fireInput();\n },\n { signal },\n );\n\n input.addEventListener(\n \"beforeinput\",\n (e) => {\n const inputEvent = e as InputEvent;\n const t = inputEvent.inputType;\n if (t === \"insertParagraph\" || t === \"insertLineBreak\" || t === \"insertFromDrop\") {\n e.preventDefault();\n return;\n }\n // Re-edit mode atomic-replace: while the edited param's `<strong>` is\n // still in the DOM, swap the whole thing for whatever the user is\n // about to insert (or empty, for deletes). Without this, typing or\n // backspace would land inside the bold span instead of replacing it.\n if (t.startsWith(\"insert\") || t.startsWith(\"delete\")) {\n const replacement = t.startsWith(\"delete\") ? \"\" : (inputEvent.data ?? \"\");\n if (opts.replaceEditingRange(replacement)) {\n e.preventDefault();\n }\n }\n },\n { signal },\n );\n\n input.addEventListener(\n \"paste\",\n (e) => {\n e.preventDefault();\n const text = (e.clipboardData?.getData(\"text/plain\") ?? \"\").replace(/\\r?\\n/g, \" \");\n if (!text) return;\n const doc = input.ownerDocument ?? document;\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n const range = sel.getRangeAt(0);\n if (!input.contains(range.startContainer)) return;\n range.deleteContents();\n const node = doc.createTextNode(text);\n range.insertNode(node);\n range.setStartAfter(node);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n fireInput();\n },\n { signal },\n );\n\n input.addEventListener(\"keydown\", (e) => opts.handleKeyDown(e), { signal });\n\n input.addEventListener(\"focus\", () => opts.store.set({ isFocused: true }), { signal });\n input.addEventListener(\"blur\", () => opts.store.set({ isFocused: false }), { signal });\n\n if (submitTarget) {\n submitTarget.addEventListener(\n \"click\",\n (e) => {\n const state = opts.store.get();\n const canSubmit = !!state.text || state.completedParams.length > 0;\n if (!canSubmit || !opts.onSubmit) return;\n e.stopPropagation();\n const { rawQuery, completedParams: finalParams } = buildQuery(\n state.text,\n state.completedParams,\n );\n opts.onSubmit({\n query: state.text.trim(),\n raw_query: rawQuery,\n completed_params: finalParams,\n });\n opts.afterSubmit?.();\n },\n { signal },\n );\n }\n\n if (opts.autoFocus !== false) {\n input.focus();\n }\n\n // Width changes on the editor (window resize, container resize) can flip\n // whether the inline pill list still fits on the editor's last line. Re-run\n // the measurement so the margin attribute stays in sync without typing.\n if (typeof ResizeObserver !== \"undefined\") {\n const ro = new ResizeObserver(() => measurePillWrap(input, inlinePillContainer));\n ro.observe(input);\n abort.signal.addEventListener(\"abort\", () => ro.disconnect(), { once: true });\n }\n\n return { input, inlinePillContainer, dropdown, submitButton, abort };\n}\n\nexport function updateDOM(refs: DOMRefs, state: CoreState, opts: RenderInputOptions) {\n const { input, inlinePillContainer, dropdown, submitButton } = refs;\n const { pillPlacement, setActivePill, selectOption, store } = opts;\n\n input.setAttribute(\"aria-expanded\", String(state.isDropdownOpen));\n const activeDescendant =\n state.activeDropdownIndex >= 0 ? `${opts.listboxId}-option-${state.activeDropdownIndex}` : \"\";\n if (activeDescendant) {\n input.setAttribute(\"aria-activedescendant\", activeDescendant);\n } else {\n input.removeAttribute(\"aria-activedescendant\");\n }\n\n if (submitButton) {\n const canSubmit = !!state.text || state.completedParams.length > 0;\n submitButton.disabled = !canSubmit;\n }\n\n // Detect a fresh option selection BEFORE renderEditableContent mutates the\n // dataset. `newParamId` is set by selectOption() each time a suggestion\n // becomes a completed param. We compare against the last id the DOM saw so\n // the focus/caret jump only fires once per selection (not on every render\n // during the 650ms shimmer window).\n const previousParamId = input.dataset.newParamId ?? \"\";\n const justSelected = state.newParamId !== null && state.newParamId !== previousParamId;\n\n renderEditableContent({\n input,\n segments: state.segments,\n newParamId: state.newParamId,\n editingParamId: state.editingParam?.id ?? null,\n placeholderText: state.placeholderText,\n isFocused: state.isFocused,\n });\n\n if (pillPlacement === \"inline\") {\n const inlineLoading = state.isLoading && !state.editingParam && !state.inSelectionAnimation;\n if (inlineLoading || state.actionableSuggestions.length > 0) {\n renderPills(\n inlinePillContainer,\n state.actionableSuggestions,\n 0,\n setActivePill,\n false,\n inlineLoading,\n state.isActivePillSelected,\n );\n } else {\n clearPills(inlinePillContainer);\n }\n } else {\n clearPills(inlinePillContainer);\n }\n // After pill content changes (and text changes via renderEditableContent\n // above), re-evaluate whether the pill list has wrapped to a new line.\n measurePillWrap(input, inlinePillContainer);\n\n if (justSelected) {\n // After option selection, focus jumped to the clicked dropdown option\n // (or stayed on the editor via the dropdown's mousedown preventDefault).\n // Either way, bring focus to the editable and park the caret at the\n // position the promoter chose — typically right after the new param's\n // trailing space. Falls back to end-of-input for safety.\n input.focus();\n setCursorOffset(input, state.caretOffset ?? state.text.length);\n } else if (state.isFocused) {\n // Controlled-mode setValue / programmatic reset: keep the caret at the\n // end when DOM text diverges from state. Skip when not focused so we\n // don't steal focus on unrelated state churn.\n const domText = extractPlainText(input);\n if (domText !== state.text) {\n setCursorOffset(input, state.text.length);\n }\n }\n\n // In re-edit mode, the dropdown's pill bar shows a synthetic pill built\n // from the edited param's cached suggestion metadata (regardless of the\n // latest server suggestions). Inline pills are unaffected — they still\n // reflect the live actionable suggestions.\n const dropdownPill: Suggestion | null = state.editingParam\n ? {\n type: state.editingParam.suggestionType,\n text: state.editingParam.suggestionPlaceholder,\n required: true,\n options: state.editingParam.options,\n }\n : null;\n const dropdownActivePill = dropdownPill ?? state.actionableSuggestions[0];\n\n renderDropdown(dropdown, {\n suggestions: dropdownActivePill\n ? [{ ...dropdownActivePill, options: state.filteredOptions }]\n : [],\n filteredOptions: state.filteredOptions,\n activeIndex: state.activeDropdownIndex,\n isOpen: state.isDropdownOpen,\n // Re-edit shows cached options, and the streak animation finishes before\n // we swap in the skeleton.\n isLoading: state.isLoading && !state.editingParam && !state.inSelectionAnimation,\n listboxId: opts.listboxId,\n pills: dropdownPill ? [dropdownPill] : state.actionableSuggestions,\n showPills: pillPlacement === \"dropdown\",\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n onSelect: selectOption,\n onHighlight: (i) => store.set({ activeDropdownIndex: i }),\n onPillClick: setActivePill,\n });\n}\n","import type { CompletedParamState, Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreInputState } from \"../types\";\nimport { findPrefixOverlap } from \"../utils/filtering\";\n\nexport interface SelectionInputs {\n text: string;\n completedParams: CompletedParamState[];\n filterBase: number;\n filteredOptions: SuggestionOption[];\n actionableSuggestions: Suggestion[];\n placeholderText: string;\n}\n\nexport interface SelectionTelemetry {\n selectedOption: string;\n otherOptions: string[];\n}\n\nexport interface SelectionResult {\n patch: Partial<CoreInputState>;\n telemetry: SelectionTelemetry;\n /** The suggestion being consumed by this selection (filtered out post-animation when more remain). */\n consumedSuggestion: Suggestion;\n /** Number of actionable suggestions left after this one is consumed. */\n remainingActionable: number;\n}\n\n// Pure: non-edit-mode option selection → patch + telemetry + follow-up; null when no active suggestion.\nexport function computeSelectionPatch(\n inputs: SelectionInputs,\n option: SuggestionOption,\n): SelectionResult | null {\n const activeSuggestion = inputs.actionableSuggestions[0];\n if (!activeSuggestion) return null;\n\n const base = inputs.filterBase;\n let prefix = inputs.text.slice(0, base);\n\n const inputWasEmpty = prefix.length === 0 && inputs.text.length === 0;\n // The user is still typing within the server-provided placeholder (e.g.\n // typed \"Cre\" for placeholder \"Create a\"). Selecting an option here should\n // keep the placeholder rather than discard it — otherwise tapping \"email\"\n // replaces \"Cre\" with just \"email\" instead of \"Create a email\".\n const inputIsPlaceholderPrefix =\n prefix.length === 0 &&\n inputs.text.length > 0 &&\n inputs.placeholderText.length > 0 &&\n inputs.placeholderText.toLowerCase().startsWith(inputs.text.toLowerCase());\n if ((inputWasEmpty || inputIsPlaceholderPrefix) && inputs.placeholderText) {\n prefix = `${inputs.placeholderText} `;\n }\n\n const overlapChars = findPrefixOverlap(prefix, option.text);\n if (overlapChars > 0) {\n prefix = prefix.slice(0, prefix.length - overlapChars);\n }\n\n const needsSpace = prefix.length > 0 && prefix[prefix.length - 1] !== \" \";\n const newText = `${prefix}${needsSpace ? \" \" : \"\"}${option.text} `;\n const finalText =\n (inputWasEmpty || inputIsPlaceholderPrefix) && newText.length > 0\n ? newText[0].toUpperCase() + newText.slice(1)\n : newText;\n\n // Preserve the case of the option as it ends up in the final text (matters\n // when capitalize-first-letter rewrote it) so deriveSegments (case-sensitive\n // indexOf) can locate it.\n const optionStart = finalText.toLowerCase().lastIndexOf(option.text.toLowerCase());\n const optionInFinal =\n optionStart >= 0 ? finalText.slice(optionStart, optionStart + option.text.length) : option.text;\n\n const completed: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: activeSuggestion.type,\n text: optionInFinal,\n kind: option.kind,\n suggestionType: activeSuggestion.type,\n suggestionPlaceholder: activeSuggestion.text,\n options: activeSuggestion.options ?? [],\n metadata: option.metadata,\n };\n\n const remainingActionable = inputs.actionableSuggestions.length - 1;\n\n return {\n patch: {\n text: finalText,\n filterBase: finalText.length,\n completedParams: [...inputs.completedParams, completed],\n newParamId: completed.id,\n caretOffset: finalText.length,\n pillTapped: false,\n activeDropdownIndex: -1,\n skipNextFetch: remainingActionable > 0,\n inSelectionAnimation: true,\n },\n telemetry: {\n selectedOption: option.text,\n otherOptions: inputs.filteredOptions.filter((o) => o.text !== option.text).map((o) => o.text),\n },\n consumedSuggestion: activeSuggestion,\n remainingActionable,\n };\n}\n","export type Listener<S> = (next: S, prev: S) => void;\n\nexport interface Store<S> {\n get: () => S;\n set: (patch: Partial<S> | ((s: S) => Partial<S>)) => void;\n subscribe: (listener: Listener<S>) => () => void;\n}\n\nexport function createStore<S>(initial: S): Store<S> {\n let state = initial;\n const listeners = new Set<Listener<S>>();\n return {\n get: () => state,\n set: (patch) => {\n const resolved = typeof patch === \"function\" ? patch(state) : patch;\n const next = { ...state, ...resolved };\n const prev = state;\n state = next;\n for (const l of listeners) l(next, prev);\n },\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n// Narrows set() to Partial<I> only — derived fields are read-only on the\n// store. Callbacks still receive I & D so they can branch on derived state.\nexport interface DerivedStore<I, D> {\n get: () => I & D;\n set: (patch: Partial<I> | ((s: I & D) => Partial<I>)) => void;\n subscribe: (listener: Listener<I & D>) => () => void;\n /**\n * Compute the full (inputs + derived) state as if `patch` were applied to the\n * current inputs, WITHOUT mutating the store or notifying subscribers. Lets a\n * caller read derived fields that depend on a pending write (e.g. the filtered\n * options for a not-yet-committed pill reorder) so it can fold the result back\n * into a single `set()` — avoiding an intermediate notification that would\n * render a stale value.\n */\n peek: (patch: Partial<I>) => I & D;\n}\n\n// Wrap an input-only store with lazily-computed derived fields; cache invalidates on each set.\nexport function createDerivedStore<I extends object, D extends object>(\n base: Store<I>,\n derive: (inputs: I) => D,\n): DerivedStore<I, D> {\n let cachedInputs: I | undefined;\n let cachedDerived: D | undefined;\n\n const deriveCached = (inputs: I): D => {\n if (inputs !== cachedInputs) {\n cachedInputs = inputs;\n cachedDerived = derive(inputs);\n }\n return cachedDerived as D;\n };\n\n return {\n get: () => {\n const inputs = base.get();\n return { ...inputs, ...deriveCached(inputs) } as I & D;\n },\n set: (patch) => {\n // For function-form patches, materialize the full (inputs + derived) state\n // so the callback sees the same shape that `get()` returns — otherwise\n // `s.filteredOptions` etc. would be undefined at runtime despite type-checking.\n if (typeof patch === \"function\") {\n base.set((inputs) => {\n const full = { ...inputs, ...deriveCached(inputs) } as I & D;\n return patch(full);\n });\n } else {\n base.set(patch);\n }\n },\n // Derive on raw `derive` (not the cached path) so a hypothetical peek never\n // pollutes the single-slot memo with inputs that were never committed.\n peek: (patch) => {\n const inputs = { ...base.get(), ...patch } as I;\n return { ...inputs, ...derive(inputs) } as I & D;\n },\n // `prev` order intentional: prev for this notification was last notification's\n // `next` and is still in the single-slot cache → free hit. Computing `next`\n // after warms the cache for the next notification's prev.\n subscribe: (listener) =>\n base.subscribe((next, prev) => {\n const prevFull = { ...prev, ...deriveCached(prev) } as I & D;\n const nextFull = { ...next, ...deriveCached(next) } as I & D;\n listener(nextFull, prevFull);\n }),\n };\n}\n","let injected = false;\n\n/** Inject core + appearance CSS into document.head once. Idempotent. */\nexport function injectStyles() {\n if (injected || typeof document === \"undefined\") return;\n if (document.querySelector(\"style[data-magicx-aia]\")) {\n injected = true;\n return;\n }\n injected = true;\n\n const style = document.createElement(\"style\");\n style.setAttribute(\"data-magicx-aia\", \"\");\n style.textContent = STYLES;\n document.head.appendChild(style);\n}\n\n// Replaced at build time by tsup.config.core.ts / vitest.config.ts\ndeclare const __MAGICX_AC_STYLES__: string;\nconst STYLES = __MAGICX_AC_STYLES__;\n","type EventMap = Record<string, readonly unknown[]>;\ntype Listener<Args extends readonly unknown[]> = (...args: Args) => void;\n\n// Typed multi-listener emitter — on() adds, returned fn removes that listener, emit() fans out.\nexport class Emitter<E extends EventMap> {\n private listeners: { [K in keyof E]?: Set<Listener<E[K]>> } = {};\n\n on<K extends keyof E>(event: K, listener: Listener<E[K]>): () => void {\n // Wrap each registration in its own closure so the same `listener` reference\n // registered twice gets two independent Set slots (and unsubscribing one\n // doesn't take down the other).\n const entry: Listener<E[K]> = (...args) => listener(...args);\n let set = this.listeners[event];\n if (!set) {\n set = new Set();\n this.listeners[event] = set;\n }\n set.add(entry);\n return () => {\n this.listeners[event]?.delete(entry);\n };\n }\n\n emit<K extends keyof E>(event: K, ...args: E[K]): void {\n const set = this.listeners[event];\n if (!set) return;\n for (const listener of set) listener(...args);\n }\n\n hasListeners<K extends keyof E>(event: K): boolean {\n return (this.listeners[event]?.size ?? 0) > 0;\n }\n\n clear(): void {\n this.listeners = {};\n }\n}\n","// Keyed timer registry — schedule() auto-cancels the prior timer under the same key; clearAll() on destroy.\nexport class TimerScheduler {\n private timers = new Map<string, ReturnType<typeof setTimeout>>();\n\n schedule(key: string, fn: () => void, ms: number): void {\n this.clear(key);\n const id = setTimeout(() => {\n this.timers.delete(key);\n fn();\n }, ms);\n this.timers.set(key, id);\n }\n\n clear(key: string): void {\n const id = this.timers.get(key);\n if (id !== undefined) {\n clearTimeout(id);\n this.timers.delete(key);\n }\n }\n\n clearAll(): void {\n for (const id of this.timers.values()) clearTimeout(id);\n this.timers.clear();\n }\n}\n","import type { AppearanceMode } from \"../shared-types\";\n\nexport class ModeController {\n private mediaQuery: MediaQueryList | null = null;\n\n constructor(\n private container: HTMLElement,\n private mode: AppearanceMode = \"auto\",\n // Optional: invoked with the concrete resolved mode whenever it changes\n // (initial apply, setMode, or a prefers-color-scheme switch in \"auto\").\n // Lets a framework wrapper mirror the value into its own state — e.g. the\n // Angular component keeps an `[attr.data-mode]` binding in sync so the\n // attribute is also present during SSR, where this controller never runs.\n private onResolve?: (resolved: \"light\" | \"dark\") => void,\n ) {\n this.apply();\n }\n\n setMode(mode: AppearanceMode) {\n this.detachListener();\n this.mode = mode;\n this.apply();\n }\n\n destroy() {\n this.detachListener();\n }\n\n private apply() {\n if (this.mode === \"auto\") {\n this.mediaQuery ??= window.matchMedia(\"(prefers-color-scheme: dark)\");\n this.mediaQuery.addEventListener(\"change\", this.onSystemChange);\n this.setResolved(this.mediaQuery.matches ? \"dark\" : \"light\");\n } else {\n this.setResolved(this.mode);\n }\n }\n\n private onSystemChange = (e: MediaQueryListEvent) => {\n this.setResolved(e.matches ? \"dark\" : \"light\");\n };\n\n private setResolved(resolved: \"light\" | \"dark\") {\n this.container.dataset.mode = resolved;\n this.onResolve?.(resolved);\n }\n\n private detachListener() {\n this.mediaQuery?.removeEventListener(\"change\", this.onSystemChange);\n }\n}\n","import type { APIConfig } from \"../shared-types\";\nimport {\n buildApiKeyAuthHeader,\n buildHeaders,\n DEFAULT_SUGGEST_ENDPOINT,\n getTokenManager,\n isAccessTokenConfig,\n} from \"./auth\";\n\nexport type TelemetrySource = \"full-sdk\" | \"headless-sdk\" | \"endpoint-direct\";\nexport type TelemetryType = \"pill\" | \"option\";\n\nexport interface TelemetryEvent {\n source: TelemetrySource;\n sessionId: string;\n type: TelemetryType;\n queryData: Record<string, unknown>;\n apiConfig?: APIConfig;\n}\n\n/**\n * Telemetry sits next to /suggest on the server, so we derive the URL by\n * rewriting the trailing `/suggest` segment. This preserves whatever origin\n * and path-prefix the consumer configured — including relative dev-proxy\n * paths like \"/api/suggest\" → \"/api/telemetry/events\" or \"/ac/api/suggest\" →\n * \"/ac/api/telemetry/events\".\n */\nfunction deriveTelemetryEndpoint(suggestEndpoint?: string): string {\n const base = suggestEndpoint ?? DEFAULT_SUGGEST_ENDPOINT;\n return base.replace(/\\/suggest(\\?|#|$)/, \"/telemetry/events$1\");\n}\n\n/**\n * Resolves Authorization header using the same logic as /suggest:\n * - accessToken mode → Bearer from TokenManager\n * - apiKey mode → Bearer/Basic from configured key\n * - neither → null (request is sent without an Authorization header, matching\n * /suggest's behavior)\n */\nasync function resolveAuthHeader(apiConfig?: APIConfig): Promise<string | null> {\n if (isAccessTokenConfig(apiConfig)) {\n const token = await getTokenManager(apiConfig).getToken();\n return `Bearer ${token}`;\n }\n return buildApiKeyAuthHeader(apiConfig);\n}\n\n/**\n * Fire-and-forget telemetry POST. Never throws; failures are swallowed so the\n * UI is never disrupted by analytics. Auth headers are constructed via the\n * same helpers used by /suggest, so credential resolution stays consistent\n * across endpoints.\n */\nexport async function sendTelemetry(event: TelemetryEvent): Promise<void> {\n try {\n const endpoint = deriveTelemetryEndpoint(event.apiConfig?.endpoint);\n const headers = buildHeaders(event.apiConfig);\n const authHeader = await resolveAuthHeader(event.apiConfig);\n if (authHeader) headers.Authorization = authHeader;\n\n const body = JSON.stringify({\n source: event.source,\n session_id: event.sessionId,\n type: event.type,\n at: new Date().toISOString(),\n query_data: event.queryData,\n });\n\n await fetch(endpoint, { method: \"POST\", headers, body });\n } catch {\n // best-effort\n }\n}\n","import { FetchController } from \"./controllers/fetchController\";\nimport { KeyboardController } from \"./controllers/keyboardController\";\nimport { PillsController } from \"./controllers/pillsController\";\nimport { deriveAll } from \"./derive/state\";\nimport { previousGraphemeBoundary, setCursorOffset } from \"./dom/cursorUtils\";\nimport { tryPromoteExactMatch } from \"./promotion/promote\";\nimport { ReEditManager } from \"./reEdit/ReEditManager\";\nimport { buildDropdownOnly, updateDropdownOnly } from \"./render/renderDropdownOnly\";\nimport { buildDOM, type DOMRefs, updateDOM } from \"./render/renderInput\";\nimport { computeSelectionPatch } from \"./selection/SelectionFlow\";\nimport type {\n AppearanceMode,\n AutocompleteResult,\n CompletedParamState,\n SuggestionOption,\n} from \"./shared-types\";\nimport { createDerivedStore, createStore } from \"./state\";\nimport { injectStyles } from \"./styleInjector\";\nimport type { CoreInputState, CoreOptions, CoreState, RenderMode } from \"./types\";\nimport { Emitter } from \"./util/Emitter\";\nimport { TimerScheduler } from \"./util/TimerScheduler\";\nimport { buildQuery } from \"./utils/buildQuery\";\nimport { filterOptions } from \"./utils/filtering\";\nimport { ModeController } from \"./utils/modeController\";\nimport { reconcileParams } from \"./utils/segments\";\nimport { sendTelemetry, type TelemetrySource } from \"./utils/telemetry\";\n\nexport type AIAutocompleteEvents = {\n submit: [result: AutocompleteResult];\n error: [error: Error];\n change: [text: string];\n paramsChange: [params: CompletedParamState[]];\n stateChange: [state: CoreState];\n focus: [];\n blur: [];\n};\n\n// `update()` excludes event-listener props — they're registered once at construction.\n// Callers who need to swap a handler must use `on()` (and call the returned unsubscribe).\nexport type CoreUpdateOptions = Partial<\n Omit<\n CoreOptions,\n \"onSubmit\" | \"onError\" | \"onChange\" | \"onParamsChange\" | \"onStateChange\" | \"onFocus\" | \"onBlur\"\n >\n>;\n\nconst TIMER_NEW_PARAM = \"newParam\";\nconst TIMER_SUGGESTION_REMOVAL = \"suggestionRemoval\";\nconst TIMER_SELECTION_ANIMATION = \"selectionAnimation\";\nconst NEW_PARAM_SHIMMER_MS = 650;\n\nlet idCounter = 0;\nfunction stableId(): string {\n return `:ac-${++idCounter}:`;\n}\n\nconst SELECTION_ANIMATION_MS = 500;\n\nfunction initialInputs(): CoreInputState {\n return {\n text: \"\",\n completedParams: [],\n suggestions: [],\n activeDropdownIndex: -1,\n newParamId: null,\n isLoading: false,\n isReady: false,\n error: null,\n filterBase: 0,\n filterInProgress: false,\n pillTapped: false,\n skipNextFetch: false,\n lastRawQuery: \"\",\n isFocused: false,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: null,\n inSelectionAnimation: false,\n };\n}\n\nexport class AIAutocomplete {\n private inputStore = createStore<CoreInputState>(initialInputs());\n private store: ReturnType<\n typeof createDerivedStore<CoreInputState, ReturnType<typeof deriveAll>>\n >;\n private _listboxId = stableId();\n private opts: CoreOptions;\n private fetchController: FetchController;\n private keyboardController: KeyboardController;\n private pillsController: PillsController;\n private reEdit: ReEditManager;\n private modeController: ModeController | null = null;\n private container: HTMLElement;\n private unsubscribers: (() => void)[] = [];\n private renderMode: RenderMode;\n private domRefs: DOMRefs | null = null;\n private dropdownRefs: { dropdown: HTMLElement } | null = null;\n private timers = new TimerScheduler();\n private emitter = new Emitter<AIAutocompleteEvents>();\n private sessionId: string = crypto.randomUUID();\n\n // Stable dispatchers — bound once, handed to controllers / renderers when listeners exist.\n // Avoids allocating a fresh arrow per `getOnSubmit()` / error-getter call.\n private readonly emitSubmit = (result: AutocompleteResult) => this.emitter.emit(\"submit\", result);\n private readonly emitError = (err: Error) => this.emitter.emit(\"error\", err);\n\n constructor(container: HTMLElement, opts: CoreOptions = {}) {\n this.container = container;\n this.opts = opts;\n this.renderMode = opts.renderMode ?? \"full\";\n\n // Wrap the raw input store with a lazily-derived layer. `derive` closes\n // over `this.opts` so changes via `update()` are picked up on the next\n // get() (a fresh `store.set({})` invalidates the memoization).\n this.store = createDerivedStore(this.inputStore, (inputs) => deriveAll(inputs, this.opts));\n\n // Register any opts.on* callbacks as the initial listener set. Use `on()`\n // after construction for additional or replacement listeners — `update()`\n // does NOT swap these (intentional, since stable proxies from React refs\n // depend on it).\n if (opts.onSubmit) this.emitter.on(\"submit\", opts.onSubmit);\n if (opts.onError) this.emitter.on(\"error\", opts.onError);\n if (opts.onChange) this.emitter.on(\"change\", opts.onChange);\n if (opts.onParamsChange) this.emitter.on(\"paramsChange\", opts.onParamsChange);\n if (opts.onStateChange) this.emitter.on(\"stateChange\", opts.onStateChange);\n if (opts.onFocus) this.emitter.on(\"focus\", opts.onFocus);\n if (opts.onBlur) this.emitter.on(\"blur\", opts.onBlur);\n\n // Apply controlled initial values\n if (opts.value !== undefined) {\n this.store.set({ text: opts.value });\n }\n if (opts.completedParams !== undefined) {\n this.store.set({ completedParams: opts.completedParams });\n }\n\n // Controllers\n this.pillsController = new PillsController(this.store, {\n onPillSelected: ({ rawQuery, selectedPill, otherPills }) => {\n this.fireTelemetry(\"pill\", {\n raw_query: rawQuery,\n selected_pill: selectedPill,\n other_pills: otherPills,\n });\n },\n });\n\n this.reEdit = new ReEditManager({\n store: this.store,\n scheduleSetCursor: (offset) => this.scheduleSetCursor(offset),\n fireTelemetry: (type, data) => this.fireTelemetry(type, data),\n startSelectionAnimationTimer: () => this.startSelectionAnimationTimer(),\n });\n\n this.fetchController = new FetchController(\n this.store,\n () => this.opts.apiConfig,\n () => this.opts.optionOverrides,\n () => this.opts.maskCompletedText,\n () => (this.emitter.hasListeners(\"error\") ? this.emitError : undefined),\n () => this.sessionId,\n {\n onAutoMatch: ({ active, matched, rawQuery }) => {\n this.fireTelemetry(\"option\", {\n raw_query: rawQuery,\n selected_option: matched.text,\n other_options: (active.options ?? [])\n .filter((o) => o.text !== matched.text)\n .map((o) => o.text),\n });\n },\n },\n );\n\n this.keyboardController = new KeyboardController(this.store, {\n columns: opts.columns ?? 2,\n listboxId: this.listboxId,\n getOnSubmit: () => (this.emitter.hasListeners(\"submit\") ? this.emitSubmit : undefined),\n getOptionsPosition: () => this.opts.optionsPosition ?? \"below\",\n // Tier 1 (full) auto-resets after submit; Tier 2/3 leave it to the consumer.\n afterSubmit: this.renderMode === \"full\" ? () => this.reset() : undefined,\n selectOption: (option) => this.selectOption(option),\n removeParamAtCaret: (offset) => this.removeParamAtCaret(offset),\n exitEditMode: () => this.exitEditMode(),\n });\n\n // Event callbacks (derived state materializes lazily through the wrapper —\n // no recompute subscriber needed).\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.text !== prev.text) this.emitter.emit(\"change\", next.text);\n if (next.completedParams !== prev.completedParams)\n this.emitter.emit(\"paramsChange\", next.completedParams);\n if (next.isFocused !== prev.isFocused) {\n if (next.isFocused) this.emitter.emit(\"focus\");\n else this.emitter.emit(\"blur\");\n }\n this.emitter.emit(\"stateChange\", next);\n }),\n );\n\n // Auto-exit re-edit + fire an immediate fetch when the user has typed past\n // their re-edited param so that the filtered options collapse to zero —\n // staying in re-edit with a dead filter just strands them in stale UI.\n this.unsubscribers.push(this.store.subscribe(() => this.maybeExitReEditOnNoMatch()));\n\n // Setup DOM based on render mode\n if (this.renderMode !== \"headless\") {\n injectStyles();\n this.setupContainer();\n }\n if (this.renderMode === \"full\") {\n this.buildAndRenderFull();\n } else if (this.renderMode === \"dropdown\") {\n this.buildAndRenderDropdown();\n }\n this.fetchController.start();\n }\n\n // === Public API ===\n\n focus() {\n this.domRefs?.input.focus();\n }\n\n blur() {\n this.domRefs?.input.blur();\n }\n\n reset() {\n // skipNextFetch suppresses the debounced fetch the FetchController would\n // otherwise schedule in response to text/params changing here — we issue\n // the empty fetch ourselves below so the new session starts immediately.\n // Preserve isFocused: when reset is triggered by an Enter-key submit the\n // DOM editor still has focus, and clearing the store flag would cause the\n // next renderEditableContent to skip caret restoration — leaving later\n // keystrokes inserted at offset 0 (looks like reversed, re-capitalized\n // typing).\n const wasFocused = this.store.get().isFocused;\n this.store.set({\n ...initialInputs(),\n isFocused: wasFocused,\n skipNextFetch: true,\n });\n this.sessionId = crypto.randomUUID();\n this.fetchController.doFetch(\"\", []);\n }\n\n destroy() {\n this.fetchController.dispose();\n this.modeController?.destroy();\n this.timers.clearAll();\n this.emitter.clear();\n for (const unsub of this.unsubscribers) unsub();\n this.unsubscribers = [];\n this.domRefs?.abort.abort();\n this.domRefs = null;\n this.dropdownRefs = null;\n if (this.renderMode !== \"headless\") {\n this.container.innerHTML = \"\";\n }\n }\n\n setMode(mode: AppearanceMode) {\n this.modeController?.setMode(mode);\n }\n\n setValue(text: string) {\n this.store.set({ text });\n }\n\n setCompletedParams(params: CompletedParamState[]) {\n this.store.set({ completedParams: params });\n }\n\n setActivePill(index: number) {\n this.pillsController.setActivePill(index);\n // Park the caret at the end of the text after a pill tap so the dropdown's\n // auto-trigger gate (caret-at-end) opens — and so continued typing filters\n // the newly-active pill's options instead of landing in the middle of a\n // prior token.\n const endOffset = this.store.get().text.length;\n this.store.set({ caretOffset: endOffset, isFocused: true });\n this.scheduleSetCursor(endOffset);\n }\n\n removeLastParam() {\n this.pillsController.removeLastParam();\n }\n\n /**\n * Backspace inside a completed param: drop the param's \"completed\" status so\n * it renders as plain (un-bold) text, AND remove one grapheme before the\n * caret — same single-character delete a normal Backspace would do. The\n * remaining text stays in the input so the user can continue editing what\n * they had typed instead of losing the whole phrase.\n *\n * Returns true when a param was reconciled (caller should `preventDefault`).\n */\n removeParamAtCaret(offset: number): boolean {\n const state = this.store.get();\n const { text, completedParams } = state;\n let pos = 0;\n for (let i = 0; i < completedParams.length; i++) {\n const param = completedParams[i];\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) continue;\n const paramStart = idx;\n const paramEnd = idx + param.text.length;\n if (offset > paramStart && offset <= paramEnd) {\n const deleteStart = previousGraphemeBoundary(text, offset);\n const newText = text.slice(0, deleteStart) + text.slice(offset);\n const newParams = completedParams.filter((_, j) => j !== i);\n this.store.set((s) => ({\n text: newText,\n filterBase: Math.min(s.filterBase, newText.length),\n completedParams: newParams,\n pillTapped: false,\n activeDropdownIndex: -1,\n }));\n this.scheduleSetCursor(deleteStart);\n return true;\n }\n pos = paramEnd;\n }\n return false;\n }\n\n /**\n * Set the editor caret at the given plain-text offset. Uses the core's own\n * `domRefs.input` in \"full\" mode; falls back to the wrapper-provided\n * `setCursor` callback in \"headless\" mode where the wrapper owns the DOM.\n *\n * Also focuses the input — setting a selection range without focus leaves a\n * visible caret that doesn't actually accept typing, and every caller (pill\n * tap, post-promote refocus, re-edit entry, Backspace-into-param) expects\n * the editor to be focused afterwards.\n */\n private scheduleSetCursor(offset: number) {\n queueMicrotask(() => {\n const refs = this.domRefs;\n if (refs) {\n refs.input.focus();\n setCursorOffset(refs.input, offset);\n } else {\n this.opts.setCursor?.(offset);\n }\n });\n }\n\n clearNewParamId() {\n this.store.set({ newParamId: null });\n }\n\n startEditingParam(paramId: string) {\n this.reEdit.start(paramId);\n }\n\n replaceEditingRange(replacement: string): boolean {\n return this.reEdit.replaceRange(replacement);\n }\n\n exitEditMode() {\n this.reEdit.exit();\n }\n\n handleCaretAfterInput(offset: number | null) {\n this.reEdit.caretAfterInput(offset);\n }\n\n handleCaretMove(offset: number | null) {\n this.reEdit.caretMove(offset);\n }\n\n setActiveDropdownIndex(index: number) {\n this.store.set({ activeDropdownIndex: index });\n }\n\n handleTextChange(value: string) {\n this.handleChange(value);\n }\n\n handleKeyDown(e: KeyboardEvent) {\n this.keyboardController.handleKeyDown(e);\n }\n\n setFocused(focused: boolean) {\n if (this.store.get().isFocused === focused) return;\n this.store.set({ isFocused: focused });\n }\n\n /** Subscribe to state changes. Listener receives the full (input + derived) shape. */\n subscribe(listener: (state: CoreState) => void): () => void {\n return this.store.subscribe((next) => listener(next));\n }\n\n getState(): CoreState {\n return this.store.get();\n }\n\n get listboxId(): string {\n return this._listboxId;\n }\n\n get isReady(): boolean {\n return this.store.get().isReady;\n }\n\n /**\n * Subscribe to an event. Multiple listeners may register for the same event;\n * `emit` fans out to all of them. The returned function removes only the\n * listener it registered.\n *\n * Note: `opts.on*` listeners passed at construction are equivalent to calling\n * `on()` once each; their unsubscribe handles are not exposed.\n */\n on<E extends keyof AIAutocompleteEvents>(\n event: E,\n callback: (...args: AIAutocompleteEvents[E]) => void,\n ): () => void {\n return this.emitter.on(event, callback);\n }\n\n update(opts: CoreUpdateOptions) {\n Object.assign(this.opts, opts);\n if (opts.mode !== undefined) {\n this.modeController?.setMode(opts.mode);\n }\n if (opts.optionsPosition !== undefined) {\n this.container.dataset.optionsPosition = opts.optionsPosition;\n }\n if (opts.animations !== undefined) {\n this.container.dataset.animations = opts.animations ? \"on\" : \"off\";\n }\n if (opts.pillPlacement !== undefined) {\n this.container.dataset.pillPlacement = opts.pillPlacement;\n this.store.set({});\n }\n if (\n opts.dropdownTrigger !== undefined ||\n opts.closeDropdownOnBlur !== undefined ||\n opts.showNonTappableOptions !== undefined\n ) {\n // Trigger recompute so isDropdownOpen / filteredOptions update\n this.store.set({});\n }\n if (opts.value !== undefined) {\n this.store.set({ text: opts.value });\n }\n if (opts.completedParams !== undefined) {\n this.store.set({ completedParams: opts.completedParams });\n }\n }\n\n // === Public (for framework wrappers) ===\n\n selectOption(option: SuggestionOption) {\n const state = this.store.get();\n\n // Re-edit path: replace the highlighted bold param's range with the new\n // option. Bypasses the normal selectOption flow entirely.\n if (state.editingParam && state.editingAnchor != null && state.editingTail != null) {\n this.reEdit.selectOption(option);\n return;\n }\n\n const result = computeSelectionPatch(state, option);\n if (!result) return;\n\n this.fireTelemetry(\"option\", {\n raw_query: buildQuery(state.text, state.completedParams).rawQuery,\n selected_option: result.telemetry.selectedOption,\n other_options: result.telemetry.otherOptions,\n });\n\n this.store.set(result.patch);\n this.startSelectionAnimationTimer();\n\n // Suggestion removal timing depends on whether a fetch is firing.\n this.timers.clear(TIMER_SUGGESTION_REMOVAL);\n if (result.remainingActionable > 0) {\n // Cached next-pill exists; no fetch fires. Remove the just-clicked\n // suggestion after the streak animation so the next pill becomes\n // active. The animation keeps playing on the option while it's still\n // in the DOM.\n const consumed = result.consumedSuggestion;\n this.timers.schedule(\n TIMER_SUGGESTION_REMOVAL,\n () => {\n this.store.set((s) => ({\n suggestions: s.suggestions.filter((sg) => sg !== consumed),\n }));\n },\n SELECTION_ANIMATION_MS,\n );\n }\n // When `remainingActionable === 0` a fetch fires. We deliberately keep\n // the just-selected suggestion in state until the response lands — the\n // dropdown then mirrors its pill/option layout (count + widths) as the\n // loading skeleton, instead of falling back to the generic placeholder.\n }\n\n private startSelectionAnimationTimer() {\n this.timers.schedule(\n TIMER_SELECTION_ANIMATION,\n () => this.store.set({ inSelectionAnimation: false }),\n SELECTION_ANIMATION_MS,\n );\n }\n\n private fireTelemetry(type: \"pill\" | \"option\", queryData: Record<string, unknown>) {\n // Vanilla consumers don't pass `source` — derive from renderMode.\n // The React hook always uses renderMode \"headless\" but Tier 1 explicitly\n // passes source: \"full-sdk\" via opts to override.\n const source: TelemetrySource =\n this.opts.source ?? (this.renderMode === \"full\" ? \"full-sdk\" : \"headless-sdk\");\n void sendTelemetry({\n source,\n sessionId: this.sessionId,\n type,\n queryData,\n apiConfig: this.opts.apiConfig,\n });\n }\n\n private setupContainer() {\n this.container.classList.add(\"magicx-aia\");\n // In dropdown mode, pills are always in the dropdown\n this.container.dataset.pillPlacement =\n this.renderMode === \"dropdown\" ? \"dropdown\" : (this.opts.pillPlacement ?? \"dropdown\");\n this.container.dataset.optionsPosition = this.opts.optionsPosition ?? \"below\";\n this.container.dataset.animations = (this.opts.animations ?? true) ? \"on\" : \"off\";\n\n // ModeController\n this.modeController = new ModeController(this.container, this.opts.mode ?? \"auto\");\n }\n\n private buildAndRenderFull() {\n const self = this;\n const renderOpts = {\n store: this.store,\n listboxId: this.listboxId,\n get pillPlacement() {\n return (self.opts.pillPlacement ?? \"dropdown\") as \"inline\" | \"dropdown\";\n },\n get onSubmit() {\n return self.emitter.hasListeners(\"submit\") ? self.emitSubmit : undefined;\n },\n // Tier 1 only (this code path runs only for renderMode === \"full\").\n afterSubmit: () => self.reset(),\n submitButton: this.opts.submitButton,\n autoFocus: this.opts.autoFocus ?? true,\n selectOption: (option: SuggestionOption) => this.selectOption(option),\n setActivePill: (index: number) => this.pillsController.setActivePill(index),\n handleKeyDown: (e: KeyboardEvent) => this.keyboardController.handleKeyDown(e),\n handleChange: (value: string) => this.handleChange(value),\n startEditingParam: (id: string) => this.startEditingParam(id),\n handleCaretAfterInput: (offset: number | null) => this.handleCaretAfterInput(offset),\n handleCaretMove: (offset: number | null) => this.handleCaretMove(offset),\n replaceEditingRange: (replacement: string) => this.replaceEditingRange(replacement),\n };\n\n this.domRefs = buildDOM(this.container, renderOpts);\n\n this.subscribeBatchedRender(() => {\n if (this.domRefs) {\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n }\n });\n\n // Initial render\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n this.subscribeNewParamTimer();\n }\n\n private buildAndRenderDropdown() {\n const dropdownOpts = {\n store: this.store,\n listboxId: this.listboxId,\n selectOption: (option: SuggestionOption) => this.selectOption(option),\n setActivePill: (index: number) => this.pillsController.setActivePill(index),\n };\n\n this.dropdownRefs = buildDropdownOnly(this.container, dropdownOpts);\n\n this.subscribeBatchedRender(() => {\n if (this.dropdownRefs) {\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n }\n });\n\n // Initial render\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n this.subscribeNewParamTimer();\n }\n\n /** Batched render subscriber — coalesces multiple store.set calls into one DOM update. */\n private subscribeBatchedRender(render: () => void) {\n let scheduled = false;\n this.unsubscribers.push(\n this.store.subscribe(() => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n render();\n });\n }),\n );\n }\n\n /** Auto-clear newParamId after shimmer animation. */\n private subscribeNewParamTimer() {\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.newParamId && next.newParamId !== prev.newParamId) {\n this.timers.schedule(\n TIMER_NEW_PARAM,\n () => this.store.set({ newParamId: null }),\n NEW_PARAM_SHIMMER_MS,\n );\n }\n }),\n );\n }\n\n private handleChange(newValue: string) {\n const state = this.store.get();\n this.store.set({\n text: newValue,\n pillTapped: false,\n activeDropdownIndex: -1,\n });\n\n const { valid, invalid } = reconcileParams(newValue, state.completedParams);\n if (invalid.length > 0) {\n this.store.set({ completedParams: valid });\n }\n\n this.maybePromoteExactMatch(newValue);\n }\n\n /**\n * In re-edit mode, once the user has typed enough that no *tappable* options\n * still match (non-tappable options are kept by filterOptions regardless of\n * the query, so they don't count as \"still matching\"), exit re-edit and\n * fire an immediate fetch so the dropdown swaps over to fresh server\n * suggestions instead of staying frozen on a dead filter.\n *\n * Guarded against re-entry: once we exit, editingParam is null and the\n * subscription early-returns on subsequent fires.\n */\n private maybeExitReEditOnNoMatch() {\n const s = this.store.get();\n if (!s.editingParam || s.editingAnchor == null) return;\n // Skip until the user has actually started typing — when the param is\n // still in completedParams, the editQuery is \"\" and matches everything.\n if (s.completedParams.some((p) => p.id === s.editingParam?.id)) return;\n const editCaret = s.caretOffset ?? s.editingAnchor;\n const editQuery = s.text.slice(s.editingAnchor, editCaret);\n const matched = filterOptions(s.editingParam.options, editQuery);\n if (matched.some((o) => o.is_tappable)) return;\n this.reEdit.exit();\n const { rawQuery, completedParams } = buildQuery(s.text, s.completedParams);\n this.fetchController.doFetch(rawQuery, completedParams);\n }\n\n /**\n * When the user has typed text that exactly matches (case-insensitive) one\n * of the active suggestion's options, promote it to a completed param right\n * away. The fetchController does the same check when the debounced fetch\n * lands; doing it instantly here means bold styling appears as soon as the\n * option is fully typed, without waiting 100–300ms for the round-trip.\n */\n private maybePromoteExactMatch(newValue: string) {\n const s = this.store.get();\n const result = tryPromoteExactMatch({\n mode: \"fresh\",\n text: newValue,\n completedParams: s.completedParams,\n suggestions: s.suggestions,\n filterBase: s.filterBase,\n filterInProgress: s.filterInProgress,\n });\n if (!result) return;\n this.store.set(result.patch);\n }\n}\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["TokenManager","config","forceRefresh","result","DEFAULT_API_ORIGIN","DEFAULT_SUGGEST_ENDPOINT","tokenManagers","isAccessTokenConfig","config","getApiKeyConfig","getTokenManager","manager","TokenManager","buildHeaders","apiConfig","buildApiKeyAuthHeader","apiKeyConfig","apiKey","SDK_VERSION","hasWarnedMissingKey","generateRequestId","toWireParam","param","includeText","buildRequestBody","rawQuery","completedParams","sessionId","rawCount","p","contactAccountCount","doFetch","endpoint","headers","token","body","signal","fetchSuggestions","options","apiConfig","buildHeaders","DEFAULT_SUGGEST_ENDPOINT","jsonBody","isAccessTokenConfig","manager","getTokenManager","response","newToken","authHeader","buildApiKeyAuthHeader","buildQuery","text","completedParams","result","typeCounts","updatedParams","param","count","placeholder","index","effectiveFilterBase","text","filterBase","placeholderText","extractFilterQuery","isInProgress","rawRegion","spaceIdx","findPrefixOverlap","prefix","optionText","trimmed","words","optionLower","i","candidate","suffixStart","filterOptions","options","query","lower","o","findExactMatch","applyOptionOverrides","suggestions","overrides","s","fn","extra","existingTexts","o","deduped","DEBOUNCE_MS","SLOW_DEBOUNCE_MS","MIN_CHARS_DIFF","FetchController","store","getApiConfig","getOptionOverrides","getMaskCompletedText","getOnError","getSessionId","callbacks","prevText","prevParams","next","rawQuery","completed","controller","version","textAtRequest","res","fetchSuggestions","newSuggestions","applyOptionOverrides","input","lastInput","currentText","filterBase","filterInProgress","inProgressIdx","active","s","extraParam","query","extractFilterQuery","match","findExactMatch","err","caughtError","attemptFetch","minDiff","placeholderText","sg","effBase","effectiveFilterBase","currentQuery","tappableFiltered","filterOptions","o","hasExactMatch","isInFilterZone","updatedParams","buildQuery","isDeleting","charDiff","NON_EDITABLE_SELECTOR","segmenter","getGraphemeSegmenter","Segmenter","isInsideNonEditable","node","root","n","createTextWalker","extractPlainText","walker","out","plainTextLength","total","getCursorOffset","sel","anchorNode","anchorOffset","el","offset","i","plainTextLengthOfSubtree","offsetBeforeNode","child","target","setCursorOffset","doc","clamped","cumulative","targetOffset","lastNode","len","next","range","strongParent","cursorIsAtEnd","previousGraphemeBoundary","text","seg","slice","last","index","isCursorAtEnd","target","state","cursorIsAtEnd","getEditableCaretOffset","getCursorOffset","KeyboardController","store","ctx","listboxId","getOnSubmit","columns","onSubmit","tappableIndices","above","cursorAtEnd","inEditMode","lastRow","currentPos","nextPos","initialIdx","prevPos","rightNeighbor","editor","tail","setCursorOffset","leftNeighbor","anchor","offset","rawQuery","finalParams","buildQuery","tappableOptionIndices","o","i","firstIdx","delta","bottomRowStart","tappable","buckets","listbox","el","gtc","tracks","index","options","optionEl","placeholders","actionable","remaining","PillsController","store","callbacks","index","state","actionable","s","moved","rest","_","i","placeholders","rawQuery","buildQuery","nextSuggestions","firstTappable","o","deriveSegments","text","completedParams","result","pos","param","idx","remaining","reconcileParams","valid","invalid","computeDropdownVisibility","inputs","opts","trigger","closeOnBlur","hasOptions","focusGate","trimmedEnd","caretAtEnd","deriveAll","inputs","opts","segments","deriveSegments","actionableSuggestions","s","activeSuggestion","overrideFn","placeholderText","clampedFilterBase","effectiveFilterBase","filterQuery","extractFilterQuery","baseOptions","inEditMode","filteredOptions","editingId","paramStillPresent","p","editCaret","editQuery","filterOptions","o","isDropdownOpen","computeDropdownVisibility","isActivePillSelected","tryPromoteExactMatch","ctx","promoteFresh","promoteEdit","text","completedParams","suggestions","filterBase","filterInProgress","active","sg","placeholderText","effBase","effectiveFilterBase","query","extractFilterQuery","match","findExactMatch","matchLower","optionStart","paramStart","paramEnd","optionInText","caretPos","completed","editingParam","editingAnchor","editingTail","p","editQuery","matchStart","newParam","insertAt","scanPos","i","idx","newParams","ReEditManager","deps","paramId","state","param","p","pos","anchor","idx","replacement","editing","tail","newText","newTail","s","offset","patch","option","buildQuery","o","before","after","optionText","needsTrailingSpace","caretPos","newParam","oldIdx","params","insertAt","result","tryPromoteExactMatch","getFooterHint","optionHighlighted","isInputEmpty","KEY_ATTR","reconcileList","parent","items","opts","existing","child","key","used","result","i","item","el","FALLBACK_SKELETON_WIDTHS","getPillOpacity","index","selected","renderPills","container","pills","activePillIndex","onSelectPill","rounded","loading","activeSelected","list","i","width","span","skel","reconcileList","pill","btn","e","el","_pill","classes","clearPills","fadeObservers","renderSuggestionGrid","parent","options","activeIndex","onSelect","onHighlight","listboxId","loading","wrapper","teardownFade","grid","setupFade","renderOptions","updateFade","onScroll","ro","entry","loadingFlag","reconcileList","opt","option","buildOptionElement","el","i","isHighlighted","idx","item","classes","streaks","streaksVert","content","text","tag","FALLBACK_SKELETON_BAR_WIDTHS","createDropdown","listboxId","dropdown","e","renderDropdown","state","filteredOptions","activeIndex","isOpen","isLoading","pills","showPills","isActivePillSelected","onSelect","onHighlight","onPillClick","hasPills","hasOptions","isVisible","stack","wantsPillBar","pillBar","renderPills","renderSuggestionGrid","skeleton","width","bar","footer","createFooter","optionHighlighted","updateFooterHint","getFooterHint","nextKey","nextHint","key","hint","divider","row","hintGroup","brandGroup","brand","badge","buildDropdownOnly","container","opts","dropdown","createDropdown","updateDropdownOnly","refs","state","renderDropdown","renderEditableContent","args","input","segments","newParamId","editingParamId","placeholderText","isFocused","empty","segKey","s","lastSegKey","lastNewParamId","lastEditingParamId","savedOffset","getCursorOffset","doc","frag","newLength","seg","strong","isNew","isEditing","classes","setCursorOffset","SUBMIT_SVG","createSubmitButton","btn","supportsPlaintextOnly","probe","measurePillWrap","input","container","inner","cRect","eRect","buildDOM","opts","listboxId","dropdown","createDropdown","inputWrapper","editor","inlinePillContainer","submitButton","submitTarget","createSubmitButton","abort","signal","composing","lastInputAt","fireInput","raw","extractPlainText","newValue","findEnclosingParamId","sel","anchor","e","getCursorOffset","doc","enclosing","editingId","inputEvent","t","replacement","text","range","node","state","rawQuery","finalParams","buildQuery","ro","updateDOM","refs","pillPlacement","setActivePill","selectOption","store","activeDescendant","canSubmit","previousParamId","justSelected","renderEditableContent","inlineLoading","renderPills","clearPills","setCursorOffset","dropdownPill","dropdownActivePill","renderDropdown","i","computeSelectionPatch","inputs","option","activeSuggestion","base","prefix","inputWasEmpty","inputIsPlaceholderPrefix","overlapChars","findPrefixOverlap","needsSpace","newText","finalText","optionStart","optionInFinal","completed","remainingActionable","o","createStore","initial","state","listeners","patch","resolved","next","prev","l","listener","createDerivedStore","base","derive","cachedInputs","cachedDerived","deriveCached","inputs","full","prevFull","nextFull","injected","injectStyles","style","STYLES","Emitter","event","listener","entry","args","set","TimerScheduler","key","fn","ms","id","ModeController","container","mode","onResolve","resolved","deriveTelemetryEndpoint","suggestEndpoint","DEFAULT_SUGGEST_ENDPOINT","resolveAuthHeader","apiConfig","isAccessTokenConfig","getTokenManager","buildApiKeyAuthHeader","sendTelemetry","event","endpoint","headers","buildHeaders","authHeader","body","TIMER_NEW_PARAM","TIMER_SUGGESTION_REMOVAL","TIMER_SELECTION_ANIMATION","NEW_PARAM_SHIMMER_MS","idCounter","stableId","SELECTION_ANIMATION_MS","initialInputs","AIAutocomplete","container","opts","createStore","TimerScheduler","Emitter","result","err","createDerivedStore","inputs","deriveAll","PillsController","rawQuery","selectedPill","otherPills","ReEditManager","offset","type","data","FetchController","active","matched","KeyboardController","option","next","prev","injectStyles","wasFocused","unsub","mode","text","params","index","endOffset","state","completedParams","pos","i","param","idx","paramStart","paramEnd","deleteStart","previousGraphemeBoundary","newText","newParams","_","j","s","refs","setCursorOffset","paramId","replacement","value","focused","listener","event","callback","computeSelectionPatch","buildQuery","consumed","sg","queryData","source","sendTelemetry","ModeController","self","renderOpts","e","id","buildDOM","updateDOM","dropdownOpts","buildDropdownOnly","updateDropdownOnly","render","scheduled","newValue","valid","invalid","reconcileParams","p","editCaret","editQuery","filterOptions","o","tryPromoteExactMatch"]}
|
|
1
|
+
{"version":3,"sources":["../src/utils/tokenManager.ts","../src/utils/auth.ts","../src/utils/api.ts","../src/utils/buildQuery.ts","../src/utils/filtering.ts","../src/utils/overrides.ts","../src/controllers/fetchController.ts","../src/dom/cursorUtils.ts","../src/controllers/keyboardController.ts","../src/controllers/pillsController.ts","../src/utils/segments.ts","../src/derive/dropdown.ts","../src/derive/state.ts","../src/promotion/promote.ts","../src/reEdit/ReEditManager.ts","../src/utils/footerHint.ts","../src/render/reconcileList.ts","../src/render/renderPills.ts","../src/render/renderSuggestionGrid.ts","../src/render/renderDropdown.ts","../src/render/renderDropdownOnly.ts","../src/render/renderEditable.ts","../src/render/renderSubmitButton.ts","../src/render/renderInput.ts","../src/selection/SelectionFlow.ts","../src/state.ts","../src/styleInjector.ts","../src/util/Emitter.ts","../src/util/TimerScheduler.ts","../src/utils/modeController.ts","../src/utils/telemetry.ts","../src/AIAutocomplete.ts"],"sourcesContent":["import type { AccessTokenConfig } from \"../shared-types\";\n\n/** Refresh 30 seconds before stated expiry to absorb clock drift and request latency. */\nconst REFRESH_SKEW_MS = 30_000;\n\nexport class TokenManager {\n private current: string | null = null;\n private expiresAt: number | null = null;\n private inFlightRefresh: Promise<string> | null = null;\n\n constructor(private config: AccessTokenConfig) {\n if (config.accessToken) {\n this.current = config.accessToken;\n }\n }\n\n /** Returns a valid token, refreshing if needed. Single-flight: concurrent callers share one refresh. */\n async getToken(forceRefresh = false): Promise<string> {\n if (!forceRefresh && this.current && !this.isExpired()) {\n return this.current;\n }\n if (!forceRefresh && this.inFlightRefresh) {\n return this.inFlightRefresh;\n }\n this.inFlightRefresh = this.refresh();\n try {\n return await this.inFlightRefresh;\n } finally {\n this.inFlightRefresh = null;\n }\n }\n\n private async refresh(): Promise<string> {\n const result = await this.config.getAccessToken();\n this.current = result.accessToken;\n this.expiresAt = result.expiresAt ?? null;\n return this.current;\n }\n\n private isExpired(): boolean {\n if (this.expiresAt == null) return false;\n return Date.now() >= this.expiresAt - REFRESH_SKEW_MS;\n }\n}\n","import type { AccessTokenConfig, APIConfig, APIKeyConfig } from \"../shared-types\";\nimport { TokenManager } from \"./tokenManager\";\n\n/** Default backend origin used when apiConfig.endpoint is not set. */\nexport const DEFAULT_API_ORIGIN = \"https://api.ai-autocomplete.com\";\nexport const DEFAULT_SUGGEST_ENDPOINT = `${DEFAULT_API_ORIGIN}/api/suggest`;\n\n// Keyed by getAccessToken function reference (stable across React re-renders)\nconst tokenManagers = new WeakMap<AccessTokenConfig[\"getAccessToken\"], TokenManager>();\n\nexport function isAccessTokenConfig(config?: APIConfig): config is AccessTokenConfig {\n return config?.type === \"accessToken\";\n}\n\nexport function getApiKeyConfig(config?: APIConfig): APIKeyConfig | undefined {\n if (!config || isAccessTokenConfig(config)) return undefined;\n return config;\n}\n\nexport function getTokenManager(config: AccessTokenConfig): TokenManager {\n let manager = tokenManagers.get(config.getAccessToken);\n if (!manager) {\n manager = new TokenManager(config);\n tokenManagers.set(config.getAccessToken, manager);\n }\n return manager;\n}\n\n/**\n * Builds shared (non-auth) request headers used by every backend call.\n * Mirrors what /suggest sends so /api/telemetry/events sees the same envelope.\n */\nexport function buildHeaders(apiConfig?: APIConfig): Record<string, string> {\n return {\n \"Content-Type\": \"application/json\",\n ...(apiConfig?.appIdentifier && { \"X-App-Identifier\": apiConfig.appIdentifier }),\n ...apiConfig?.headers,\n };\n}\n\n/**\n * Returns the Authorization header value for an API-key config, or `null` if no\n * apiKey is set (matches /suggest's behavior of sending the request without an\n * Authorization header in that case — the warn lives in api.ts so it fires only\n * for the user-visible suggest call).\n */\nexport function buildApiKeyAuthHeader(apiConfig?: APIConfig): string | null {\n const apiKeyConfig = getApiKeyConfig(apiConfig);\n const apiKey = apiKeyConfig?.apiKey;\n if (!apiKey) return null;\n const scheme = apiKeyConfig?.authScheme ?? \"Bearer\";\n return scheme === \"Basic\" ? `Basic ${btoa(apiKey)}` : `Bearer ${apiKey}`;\n}\n","import type {\n APIConfig,\n AutocompleteRequest,\n AutocompleteResponse,\n CompletedParam,\n CompletedParamState,\n} from \"../shared-types\";\nimport {\n buildApiKeyAuthHeader,\n buildHeaders,\n DEFAULT_SUGGEST_ENDPOINT,\n getTokenManager,\n isAccessTokenConfig,\n} from \"./auth\";\n\n// Replaced at build time by tsup/vitest `define` config with the package.json version.\ndeclare const __SDK_VERSION__: string;\nconst SDK_VERSION = __SDK_VERSION__;\n\nlet hasWarnedMissingKey = false;\n\nfunction generateRequestId(): string {\n return crypto.randomUUID();\n}\n\nfunction toWireParam(param: CompletedParamState, includeText: boolean): CompletedParam {\n return {\n placeholder: param.placeholder,\n type: param.type,\n ...(includeText && { text: param.text }),\n kind: param.kind,\n };\n}\n\nfunction buildRequestBody(\n rawQuery: string,\n completedParams: CompletedParamState[],\n includeText: boolean,\n sessionId: string,\n): AutocompleteRequest {\n const rawCount = completedParams.find(\n (p) => p.type === \"contact\" && p.metadata?.contact_account_count,\n )?.metadata?.contact_account_count;\n const contactAccountCount = typeof rawCount === \"number\" ? rawCount : undefined;\n\n return {\n data: {\n raw_query: rawQuery,\n completed_params: completedParams.map((p) => toWireParam(p, includeText)),\n ...(contactAccountCount != null && { contact_account_count: contactAccountCount }),\n },\n meta: {\n request_id: generateRequestId(),\n request_at: new Date().toISOString(),\n language: typeof navigator !== \"undefined\" ? navigator.language : \"en-US\",\n client_version: SDK_VERSION,\n session_id: sessionId,\n },\n };\n}\n\nasync function doFetch(\n endpoint: string,\n headers: Record<string, string>,\n token: string,\n body: string,\n signal?: AbortSignal,\n): Promise<Response> {\n return fetch(endpoint, {\n method: \"POST\",\n headers: { ...headers, Authorization: `Bearer ${token}` },\n body,\n signal,\n });\n}\n\nexport async function fetchSuggestions(\n rawQuery: string,\n completedParams: CompletedParamState[],\n options: {\n sessionId: string;\n maskCompletedText?: boolean;\n signal?: AbortSignal;\n apiConfig?: APIConfig;\n },\n): Promise<AutocompleteResponse> {\n const apiConfig = options.apiConfig;\n const includeText = !options.maskCompletedText;\n const body = buildRequestBody(rawQuery, completedParams, includeText, options.sessionId);\n const headers = buildHeaders(apiConfig);\n const endpoint = apiConfig?.endpoint ?? DEFAULT_SUGGEST_ENDPOINT;\n const jsonBody = JSON.stringify(body);\n\n // === Access token mode ===\n if (isAccessTokenConfig(apiConfig)) {\n const manager = getTokenManager(apiConfig);\n const token = await manager.getToken();\n\n let response = await doFetch(endpoint, headers, token, jsonBody, options.signal);\n\n // 401 retry: force-refresh token and retry exactly once\n if (response.status === 401) {\n const newToken = await manager.getToken(true);\n response = await doFetch(endpoint, headers, newToken, jsonBody, options.signal);\n }\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<AutocompleteResponse>;\n }\n\n // === API key mode (default) ===\n const authHeader = buildApiKeyAuthHeader(apiConfig);\n if (!authHeader && !hasWarnedMissingKey) {\n hasWarnedMissingKey = true;\n // biome-ignore lint/suspicious/noConsole: intentional dev warning\n console.warn(\n \"[AIAutocomplete] No apiKey in apiConfig. Requests will be sent without an Authorization header.\",\n );\n }\n if (authHeader) headers.Authorization = authHeader;\n\n const response = await fetch(endpoint, {\n method: \"POST\",\n headers,\n body: jsonBody,\n signal: options.signal,\n });\n\n if (!response.ok) {\n throw new Error(`API error: ${response.status} ${response.statusText}`);\n }\n\n return response.json() as Promise<AutocompleteResponse>;\n}\n","import type { CompletedParamState } from \"../shared-types\";\n\ninterface BuildQueryResult {\n rawQuery: string;\n completedParams: CompletedParamState[];\n}\n\n/**\n * Takes the raw input text and completed params (without placeholders),\n * replaces each completed param's text in the string with a {{TYPE_N}} token,\n * and returns the transformed query + params with placeholders filled in.\n *\n * Replacements happen left-to-right, first occurrence only per param.\n * Counter is per-type (e.g. {{TASK_1}}, {{GOAL_1}}, {{GOAL_2}}).\n */\nexport function buildQuery(text: string, completedParams: CompletedParamState[]): BuildQueryResult {\n let result = text;\n const typeCounts: Record<string, number> = {};\n const updatedParams: CompletedParamState[] = [];\n\n for (const param of completedParams) {\n const count = (typeCounts[param.type] ?? 0) + 1;\n typeCounts[param.type] = count;\n\n const typeKey = param.type.toUpperCase().replace(/\\s+/g, \"_\");\n const placeholder = `{{${typeKey}_${count}}}`;\n\n // Replace first occurrence of the param text in the string\n const index = result.indexOf(param.text);\n if (index !== -1) {\n result = result.slice(0, index) + placeholder + result.slice(index + param.text.length);\n }\n\n updatedParams.push({ ...param, placeholder });\n }\n\n return { rawQuery: result, completedParams: updatedParams };\n}\n","import type { SuggestionOption } from \"../shared-types\";\n\n/**\n * Returns a filter base that respects the server-suggested placeholder. When\n * `filterBase` hasn't been set by a fetch yet (still 0) and `text` starts with\n * `placeholderText`, the placeholder occupies the same role as `filterBase` —\n * the user's filter query is whatever they typed past it. Returns `filterBase`\n * unchanged otherwise.\n */\nexport function effectiveFilterBase(\n text: string,\n filterBase: number,\n placeholderText: string,\n): number {\n if (filterBase > 0 || !placeholderText) return filterBase;\n if (text.toLowerCase().startsWith(placeholderText.toLowerCase())) {\n return placeholderText.length;\n }\n return filterBase;\n}\n\n/**\n * Extracts the effective filter query from the region after filterBase.\n * If the server marked the region as in_progress, the full region is used.\n * Otherwise, filtering only starts after the first space (to avoid filtering\n * mid-word when the user continues typing a word that was already in the input).\n */\nexport function extractFilterQuery(\n text: string,\n filterBase: number,\n isInProgress: boolean,\n): string {\n const rawRegion = text.slice(filterBase);\n if (isInProgress || filterBase === 0 || text[filterBase - 1] === \" \") {\n return rawRegion;\n }\n const spaceIdx = rawRegion.indexOf(\" \");\n return spaceIdx === -1 ? \"\" : rawRegion.slice(spaceIdx + 1);\n}\n\n/**\n * Finds the longest word-boundary-aligned suffix of `prefix` that matches a\n * prefix of `optionText` (case-insensitive). Returns the number of characters\n * to remove from the end of `prefix` to avoid duplication.\n */\nexport function findPrefixOverlap(prefix: string, optionText: string): number {\n // Normalize internal whitespace so words.join(\" \") roundtrips losslessly\n const trimmed = prefix.trimEnd().replace(/\\s+/g, \" \");\n if (trimmed.length === 0 || optionText.length === 0) return 0;\n\n const words = trimmed.split(\" \");\n const optionLower = optionText.toLowerCase();\n\n // Try from longest suffix (all words) to shortest (last word only)\n for (let i = 0; i < words.length; i++) {\n const candidate = words.slice(i).join(\" \");\n if (optionLower.startsWith(candidate.toLowerCase())) {\n const suffixStart = trimmed.length - candidate.length;\n return prefix.length - suffixStart;\n }\n }\n\n return 0;\n}\n\n/**\n * Filters options using partial substring match on the text after the last completed param.\n */\nexport function filterOptions(\n options: SuggestionOption[] | undefined,\n query: string,\n): SuggestionOption[] {\n if (!options) return [];\n const trimmed = query.trimStart();\n if (!trimmed) return options;\n const lower = trimmed.toLowerCase();\n return options.filter((o) => !o.is_tappable || o.text.toLowerCase().includes(lower));\n}\n\n/**\n * Finds an exact match for the trimmed filter query against options.\n */\nexport function findExactMatch(\n options: SuggestionOption[] | undefined,\n query: string,\n): SuggestionOption | null {\n if (!options) return null;\n const trimmed = query.trim();\n if (!trimmed) return null;\n const lower = trimmed.toLowerCase();\n return options.find((o) => o.is_tappable && o.text.toLowerCase() === lower) ?? null;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\n\n/**\n * Applies option overrides by calling the override function with an empty query,\n * then prepending results to suggestions that match by type, deduplicating by text.\n */\nexport function applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: Record<string, (query: string) => SuggestionOption[]>,\n): Suggestion[] {\n if (!overrides) return suggestions;\n return suggestions.map((s) => {\n const fn = overrides[s.type];\n if (!fn) return s;\n const extra = fn(\"\");\n if (extra.length === 0) return s;\n const existingTexts = new Set(extra.map((o) => o.text));\n const deduped = (s.options ?? []).filter((o) => !existingTexts.has(o.text));\n return { ...s, options: [...extra, ...deduped] };\n });\n}\n","import type {\n APIConfig,\n CompletedParamState,\n OptionOverrides,\n Suggestion,\n SuggestionOption,\n} from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { fetchSuggestions } from \"../utils/api\";\nimport { buildQuery } from \"../utils/buildQuery\";\nimport {\n effectiveFilterBase,\n extractFilterQuery,\n filterOptions,\n findExactMatch,\n} from \"../utils/filtering\";\nimport { applyOptionOverrides } from \"../utils/overrides\";\n\nconst DEBOUNCE_MS = 100;\nconst SLOW_DEBOUNCE_MS = 300;\nconst MIN_CHARS_DIFF = 2;\n\nexport interface FetchAutoMatchEvent {\n active: Suggestion;\n matched: SuggestionOption;\n rawQuery: string;\n}\n\nexport interface FetchControllerCallbacks {\n onAutoMatch?: (event: FetchAutoMatchEvent) => void;\n}\n\nexport class FetchController {\n private fetchVersion = 0;\n private abortController: AbortController | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private slowDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n private unsubscribe: (() => void) | null = null;\n\n constructor(\n private store: Store<CoreState>,\n private getApiConfig: () => APIConfig | undefined,\n private getOptionOverrides: () => OptionOverrides | undefined,\n private getMaskCompletedText: () => boolean | undefined,\n private getOnError: () => ((error: Error) => void) | undefined,\n private getSessionId: () => string,\n private callbacks: FetchControllerCallbacks = {},\n ) {}\n\n start() {\n // Initial fetch\n this.doFetch(\"\", []);\n\n // Subscribe to state changes for debounced fetching\n let prevText = this.store.get().text;\n let prevParams = this.store.get().completedParams;\n this.unsubscribe = this.store.subscribe((next) => {\n if (next.text !== prevText || next.completedParams !== prevParams) {\n prevText = next.text;\n prevParams = next.completedParams;\n this.scheduleFetch();\n }\n });\n }\n\n dispose() {\n this.abortController?.abort();\n this.clearTimers();\n this.unsubscribe?.();\n }\n\n async doFetch(rawQuery: string, completed: CompletedParamState[]) {\n this.abortController?.abort();\n const controller = new AbortController();\n this.abortController = controller;\n const version = ++this.fetchVersion;\n const textAtRequest = this.store.get().text.length;\n\n this.store.set({ isLoading: true, error: null });\n\n try {\n const res = await fetchSuggestions(rawQuery, completed, {\n sessionId: this.getSessionId(),\n maskCompletedText: this.getMaskCompletedText(),\n signal: controller.signal,\n apiConfig: this.getApiConfig(),\n });\n\n if (version !== this.fetchVersion) return;\n\n let newSuggestions = applyOptionOverrides(\n res.data.suggestions ?? [],\n this.getOptionOverrides(),\n );\n\n const input = res.data.input ?? [];\n const lastInput = input[input.length - 1];\n const currentText = this.store.get().text;\n let filterBase: number;\n let filterInProgress: boolean;\n\n if (lastInput?.state === \"in_progress\") {\n filterInProgress = true;\n const inProgressIdx = currentText.toLowerCase().lastIndexOf(lastInput.text.toLowerCase());\n filterBase = inProgressIdx !== -1 ? inProgressIdx : textAtRequest;\n } else {\n filterInProgress = false;\n filterBase = textAtRequest;\n }\n\n // Check if user already typed an exact match while waiting\n const actionable = newSuggestions.filter((s) => s.type !== \"placeholder\");\n const active = actionable[0];\n let extraParam: CompletedParamState | null = null;\n if (active) {\n const query = extractFilterQuery(currentText, filterBase, filterInProgress);\n const match = findExactMatch(active.options, query);\n if (match) {\n extraParam = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: active.type,\n text: match.text,\n kind: match.kind,\n suggestionType: active.type,\n suggestionPlaceholder: active.text,\n options: active.options ?? [],\n metadata: match.metadata,\n };\n newSuggestions = newSuggestions.filter((s) => s !== active);\n this.callbacks.onAutoMatch?.({ active, matched: match, rawQuery });\n }\n }\n\n this.store.set((s) => ({\n suggestions: newSuggestions,\n isLoading: false,\n isReady: res.data.is_ready ?? false,\n lastRawQuery: rawQuery,\n activeDropdownIndex: -1,\n filterBase,\n filterInProgress,\n ...(extraParam ? { completedParams: [...s.completedParams, extraParam] } : {}),\n }));\n } catch (err) {\n if (version === this.fetchVersion) {\n const caughtError = err instanceof Error ? err : new Error(String(err));\n this.store.set({ error: caughtError, isLoading: false });\n this.getOnError()?.(caughtError);\n }\n }\n }\n\n private scheduleFetch() {\n this.clearTimers();\n const state = this.store.get();\n\n if (state.skipNextFetch) {\n this.store.set({ skipNextFetch: false });\n return;\n }\n\n const attemptFetch = (minDiff: number): boolean => {\n const s = this.store.get();\n if (!s.text && s.completedParams.length === 0) {\n this.doFetch(\"\", []);\n return true;\n }\n\n const placeholderText = s.suggestions\n .filter((sg: Suggestion) => sg.type === \"placeholder\")\n .map((sg: Suggestion) => sg.text)\n .join(\" \");\n const effBase = effectiveFilterBase(s.text, s.filterBase, placeholderText);\n const currentQuery = extractFilterQuery(s.text, effBase, s.filterInProgress);\n const actionable = s.suggestions.filter((sg: Suggestion) => sg.type !== \"placeholder\");\n const active = actionable[0];\n const currentFiltered = active ? filterOptions(active.options, currentQuery) : [];\n const tappableFiltered = currentFiltered.filter((o: SuggestionOption) => o.is_tappable);\n const hasExactMatch = active ? findExactMatch(active.options, currentQuery) !== null : false;\n\n const isInFilterZone = currentQuery.trim().length > 0;\n if (tappableFiltered.length > 0 && !hasExactMatch && isInFilterZone) return false;\n\n // Mirror the filter-zone behavior for placeholder suggestions: if the user is\n // still typing a prefix of the server-provided placeholder, don't fetch yet.\n if (\n s.completedParams.length === 0 &&\n s.text.length > 0 &&\n placeholderText.length > 0 &&\n placeholderText.toLowerCase().startsWith(s.text.toLowerCase())\n ) {\n return false;\n }\n\n const { rawQuery, completedParams: updatedParams } = buildQuery(s.text, s.completedParams);\n const isDeleting = rawQuery.length < s.lastRawQuery.length;\n const charDiff = Math.abs(rawQuery.length - s.lastRawQuery.length);\n if (isDeleting || charDiff >= minDiff) {\n this.doFetch(rawQuery, updatedParams);\n return true;\n }\n return false;\n };\n\n this.debounceTimer = setTimeout(() => {\n if (attemptFetch(MIN_CHARS_DIFF)) {\n if (this.slowDebounceTimer) clearTimeout(this.slowDebounceTimer);\n }\n }, DEBOUNCE_MS);\n\n this.slowDebounceTimer = setTimeout(() => attemptFetch(1), SLOW_DEBOUNCE_MS);\n }\n\n private clearTimers() {\n if (this.debounceTimer) clearTimeout(this.debounceTimer);\n if (this.slowDebounceTimer) clearTimeout(this.slowDebounceTimer);\n this.debounceTimer = null;\n this.slowDebounceTimer = null;\n }\n}\n","/**\n * Plain-text caret utilities for contentEditable elements.\n *\n * Offsets are measured in plain-text characters that come from the editable\n * region only — subtrees inside `[contenteditable=\"false\"]` (e.g. pills)\n * contribute zero characters. Callers can think in string offsets without\n * touching DOM Ranges directly.\n */\n\nconst NON_EDITABLE_SELECTOR = '[contenteditable=\"false\"]';\n\ninterface GraphemeSegmenter {\n segment(input: string): Iterable<{ index: number; segment: string }>;\n}\n\nlet segmenter: GraphemeSegmenter | null | undefined;\nfunction getGraphemeSegmenter(): GraphemeSegmenter | null {\n if (segmenter !== undefined) return segmenter;\n // Intl.Segmenter is ES2022; lib target is ES2020. Access via globalThis to\n // avoid a hard compile dependency on the newer lib.\n const Segmenter = (globalThis as { Intl: typeof Intl & { Segmenter?: unknown } }).Intl\n .Segmenter as undefined | (new (locale?: string, options?: object) => GraphemeSegmenter);\n if (!Segmenter) {\n segmenter = null;\n return null;\n }\n try {\n segmenter = new Segmenter(undefined, { granularity: \"grapheme\" });\n } catch {\n segmenter = null;\n }\n return segmenter ?? null;\n}\n\nfunction isInsideNonEditable(node: Node, root: HTMLElement): boolean {\n let n: Node | null = node;\n while (n && n !== root) {\n if (n.nodeType === Node.ELEMENT_NODE) {\n const el = n as HTMLElement;\n if (el.matches(NON_EDITABLE_SELECTOR)) return true;\n }\n n = n.parentNode;\n }\n return false;\n}\n\nfunction createTextWalker(root: HTMLElement): TreeWalker {\n return (root.ownerDocument ?? document).createTreeWalker(root, NodeFilter.SHOW_TEXT, {\n acceptNode(node) {\n return isInsideNonEditable(node, root) ? NodeFilter.FILTER_REJECT : NodeFilter.FILTER_ACCEPT;\n },\n });\n}\n\nexport function extractPlainText(root: HTMLElement): string {\n const walker = createTextWalker(root);\n let out = \"\";\n let node = walker.nextNode() as Text | null;\n while (node) {\n out += node.data;\n node = walker.nextNode() as Text | null;\n }\n return out;\n}\n\nexport function plainTextLength(root: HTMLElement): number {\n const walker = createTextWalker(root);\n let total = 0;\n let node = walker.nextNode() as Text | null;\n while (node) {\n total += node.data.length;\n node = walker.nextNode() as Text | null;\n }\n return total;\n}\n\n/**\n * Read the current caret offset (in plain-text characters) within `root`.\n * Returns null when no selection is anchored inside `root`.\n */\nexport function getCursorOffset(root: HTMLElement): number | null {\n const sel = (root.ownerDocument ?? document).getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const anchorNode = sel.anchorNode;\n const anchorOffset = sel.anchorOffset;\n if (!anchorNode || !root.contains(anchorNode)) return null;\n\n // When the anchor is the editable itself (or an element child), interpret\n // anchorOffset as a child index and sum text lengths up to that child.\n if (anchorNode.nodeType === Node.ELEMENT_NODE) {\n const el = anchorNode as Element;\n if (isInsideNonEditable(el, root) && el !== root) return null;\n let offset = 0;\n for (let i = 0; i < anchorOffset && i < el.childNodes.length; i++) {\n offset += plainTextLengthOfSubtree(el.childNodes[i], root);\n }\n // Add lengths of previous siblings + ancestors up to root.\n return offset + offsetBeforeNode(el, root);\n }\n\n if (anchorNode.nodeType !== Node.TEXT_NODE) return null;\n if (isInsideNonEditable(anchorNode, root)) return null;\n\n return offsetBeforeNode(anchorNode, root) + anchorOffset;\n}\n\nfunction plainTextLengthOfSubtree(node: Node, root: HTMLElement): number {\n if (node.nodeType === Node.TEXT_NODE) {\n return isInsideNonEditable(node, root) ? 0 : (node as Text).data.length;\n }\n if (node.nodeType !== Node.ELEMENT_NODE) return 0;\n const el = node as HTMLElement;\n if (el.matches(NON_EDITABLE_SELECTOR)) return 0;\n let total = 0;\n for (const child of Array.from(el.childNodes)) {\n total += plainTextLengthOfSubtree(child, root);\n }\n return total;\n}\n\nfunction offsetBeforeNode(target: Node, root: HTMLElement): number {\n const walker = createTextWalker(root);\n let total = 0;\n let node = walker.nextNode() as Text | null;\n while (node) {\n if (node === target) return total;\n // If target is an ancestor element of this text node, we've already passed it.\n if (target.nodeType === Node.ELEMENT_NODE && (target as Element).contains(node)) {\n return total;\n }\n total += node.data.length;\n node = walker.nextNode() as Text | null;\n }\n return total;\n}\n\n/**\n * Set the caret at the given plain-text offset within `root`.\n *\n * Boundary policy: when the offset falls at the seam between text nodes, the\n * caret is placed at the START of the *following* text node so newly typed\n * characters do not inherit a preceding `<strong>`'s bold styling. When the\n * caret would land at the trailing edge of a text node inside a `<strong>`\n * with no following text node, we use `setStartAfter(strong)` so the caret\n * sits OUTSIDE the bold subtree — otherwise a caret at the end of a strong's\n * text is still \"inside\" the strong, which would falsely trigger re-edit mode.\n */\nexport function setCursorOffset(root: HTMLElement, offset: number): void {\n const doc = root.ownerDocument ?? document;\n const sel = doc.getSelection();\n if (!sel) return;\n\n const clamped = Math.max(0, Math.min(offset, plainTextLength(root)));\n const walker = createTextWalker(root);\n let cumulative = 0;\n let target: Text | null = null;\n let targetOffset = 0;\n let node = walker.nextNode() as Text | null;\n let lastNode: Text | null = null;\n\n while (node) {\n const len = node.data.length;\n if (clamped < cumulative + len) {\n target = node;\n targetOffset = clamped - cumulative;\n break;\n }\n if (clamped === cumulative + len) {\n const next = walker.nextNode() as Text | null;\n if (next) {\n // Prefer the start of the following text node so the caret sits past\n // any preceding `<strong>` boundary.\n target = next;\n targetOffset = 0;\n } else {\n target = node;\n targetOffset = len;\n }\n break;\n }\n cumulative += len;\n lastNode = node;\n node = walker.nextNode() as Text | null;\n }\n\n const range = doc.createRange();\n if (target) {\n // Boundary policy: the caret must never land at the leading or trailing\n // edge of a `<strong>`'s text node — visually it sits AT the boundary\n // but the DOM anchor is still INSIDE the strong, which would falsely\n // trigger re-edit mode. Hop OUT of the strong at those boundaries.\n const strongParent = target.parentElement?.closest<HTMLElement>('strong[data-seg=\"completed\"]');\n if (strongParent && strongParent !== root && root.contains(strongParent)) {\n if (targetOffset === 0) {\n range.setStartBefore(strongParent);\n } else if (targetOffset === target.data.length) {\n range.setStartAfter(strongParent);\n } else {\n range.setStart(target, targetOffset);\n }\n } else {\n range.setStart(target, targetOffset);\n }\n } else if (lastNode) {\n range.setStart(lastNode, lastNode.data.length);\n } else {\n range.setStart(root, 0);\n }\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n}\n\n/**\n * True when the caret offset equals the editable's plain-text length —\n * meaning the only content to the right is non-editable (e.g. trailing pills).\n */\nexport function cursorIsAtEnd(root: HTMLElement): boolean {\n const offset = getCursorOffset(root);\n if (offset == null) return false;\n return offset >= plainTextLength(root);\n}\n\n/**\n * Step back one grapheme from a plain-text offset, falling back to one UTF-16\n * code unit when Intl.Segmenter is unavailable. Used by Backspace handling so\n * emoji and combining marks are deleted as a single user-perceived character.\n */\nexport function previousGraphemeBoundary(text: string, offset: number): number {\n if (offset <= 0) return 0;\n const seg = getGraphemeSegmenter();\n if (!seg) return offset - 1;\n const slice = text.slice(0, offset);\n let last = 0;\n for (const { index } of seg.segment(slice)) {\n if (index < offset) last = index;\n }\n return last;\n}\n","import { cursorIsAtEnd, getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { AutocompleteResult, SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nexport interface KeyboardContext {\n columns: number;\n listboxId: string;\n getOnSubmit: () => ((result: AutocompleteResult) => void) | undefined;\n /**\n * Live read of the dropdown's vertical placement. When \"above\", the vertical\n * arrows are swapped so that pressing toward the dropdown enters/advances and\n * pressing toward the input exits/retreats.\n */\n getOptionsPosition: () => \"above\" | \"below\";\n /** Optional hook invoked after onSubmit fires (used by Tier 1 to auto-reset). */\n afterSubmit?: () => void;\n selectOption: (option: SuggestionOption) => void;\n /**\n * Tier 1 only: remove a completed param at the given caret offset, returning\n * true when a param was removed. Tier 2 consumers can omit this — Backspace\n * falls back to the browser default in their own input element.\n */\n removeParamAtCaret?: (offset: number) => boolean;\n /** Tier 1 only: exit re-edit mode (Escape, arrow-key escape). */\n exitEditMode?: () => void;\n}\n\n/**\n * Returns true when the caret in the event target sits at the end of its\n * editable content. Works for both `<textarea>`/`<input>` (Tier 2 consumer\n * inputs) and our contentEditable Tier 1 editor.\n */\nfunction isCursorAtEnd(target: EventTarget | null, state?: CoreState): boolean {\n if (target instanceof HTMLTextAreaElement || target instanceof HTMLInputElement) {\n return target.selectionStart != null && target.selectionStart === target.value.length;\n }\n if (target instanceof HTMLElement && target.hasAttribute(\"data-aia-input\")) {\n return cursorIsAtEnd(target);\n }\n // Headless consumers driving a custom editor (e.g. a Tiptap / ProseMirror\n // document) whose element the core can't introspect: fall back to the\n // plain-text caret offset they report via handleCaretMove /\n // handleCaretAfterInput. This makes arrow-key entry into the dropdown work\n // without requiring the host to tag its editable with `data-aia-input`.\n if (state?.caretOffset != null) {\n return state.caretOffset >= state.text.length;\n }\n return false;\n}\n\nfunction getEditableCaretOffset(target: EventTarget | null): number | null {\n if (target instanceof HTMLElement && target.hasAttribute(\"data-aia-input\")) {\n return getCursorOffset(target);\n }\n return null;\n}\n\nexport class KeyboardController {\n constructor(\n private store: Store<CoreState>,\n private ctx: KeyboardContext,\n ) {}\n\n handleKeyDown(e: KeyboardEvent) {\n const state = this.store.get();\n const { listboxId, getOnSubmit } = this.ctx;\n const columns = this.getEffectiveColumns();\n const onSubmit = getOnSubmit();\n const tappableIndices = this.getTappableIndices(columns);\n\n // Modifier + arrow means the user wants native text-navigation / selection\n // behavior: Shift extends selection (all platforms), Cmd jumps to line/doc\n // edge (Mac), Ctrl jumps word-by-word (Windows/Linux), Alt/Option jumps\n // word-by-word (Mac). Bail before the switch so the browser handles it.\n if (\n (e.shiftKey || e.metaKey || e.ctrlKey || e.altKey) &&\n (e.key === \"ArrowDown\" ||\n e.key === \"ArrowUp\" ||\n e.key === \"ArrowLeft\" ||\n e.key === \"ArrowRight\")\n ) {\n return;\n }\n\n // The \"toward dropdown\" arrow key opens the dropdown from the input. When\n // the dropdown sits below (default), that's ArrowDown; when it sits above,\n // that's ArrowUp. Within-grid navigation always follows visual direction —\n // ArrowDown moves the highlight down through the grid, ArrowUp moves it\n // up — regardless of where the dropdown is positioned.\n const above = this.ctx.getOptionsPosition() === \"above\";\n\n switch (e.key) {\n case \"ArrowDown\": {\n const cursorAtEnd = isCursorAtEnd(e.target, state);\n // While re-editing a bold param, the dropdown is showing cached\n // options for it — ArrowDown should descend into them regardless of\n // whether the caret is \"at end\" of the input.\n const inEditMode = !!state.editingParam;\n if (!cursorAtEnd && !inEditMode && state.activeDropdownIndex < 0) break;\n\n // Open from the input only when the dropdown sits below. When it's\n // above, the ArrowUp branch handles opening.\n if (state.activeDropdownIndex < 0) {\n if (above) break;\n e.preventDefault();\n if (!state.isDropdownOpen && state.actionableSuggestions.length > 0) {\n this.store.set({ pillTapped: true, activeDropdownIndex: tappableIndices[0] ?? 0 });\n break;\n }\n if (tappableIndices.length === 0) return;\n this.store.set({ activeDropdownIndex: tappableIndices[0] });\n break;\n }\n\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n // Exit past the bottom (last) row of the visual grid back to the\n // input, mirroring the ArrowUp top-row exit below.\n if (state.filteredOptions.length > 0) {\n const lastRow = Math.floor((state.filteredOptions.length - 1) / columns);\n const currentRow = Math.floor(state.activeDropdownIndex / columns);\n if (currentRow === lastRow) {\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\n }\n const currentPos = tappableIndices.indexOf(state.activeDropdownIndex);\n const nextPos = currentPos < tappableIndices.length - 1 ? currentPos + 1 : 0;\n this.store.set({ activeDropdownIndex: tappableIndices[nextPos] });\n break;\n }\n case \"ArrowUp\": {\n // Open from the input only when the dropdown sits above.\n if (state.activeDropdownIndex < 0) {\n if (!above) break;\n const cursorAtEnd = isCursorAtEnd(e.target, state);\n const inEditMode = !!state.editingParam;\n if (!cursorAtEnd && !inEditMode) break;\n e.preventDefault();\n // Land on the option closest to the input — the first tappable in\n // the bottom (last) row of the visual grid. Mirrors how ArrowDown +\n // dropdown-below lands on the top-left (also closest to the input).\n const initialIdx = this.firstTappableInBottomRow(columns) ?? tappableIndices[0] ?? 0;\n if (!state.isDropdownOpen && state.actionableSuggestions.length > 0) {\n this.store.set({ pillTapped: true, activeDropdownIndex: initialIdx });\n break;\n }\n if (tappableIndices.length === 0) return;\n this.store.set({ activeDropdownIndex: initialIdx });\n break;\n }\n if (tappableIndices.length === 0) break;\n e.preventDefault();\n if (state.activeDropdownIndex < columns) {\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\n const currentPos = tappableIndices.indexOf(state.activeDropdownIndex);\n const prevPos = currentPos > 0 ? currentPos - 1 : tappableIndices.length - 1;\n this.store.set({ activeDropdownIndex: tappableIndices[prevPos] });\n break;\n }\n case \"ArrowRight\": {\n // When a dropdown option is highlighted, arrows navigate the grid\n // only — the caret in the editor stays put. Always preventDefault so\n // a press at the rightmost column doesn't fall through to caret\n // movement.\n if (state.activeDropdownIndex >= 0) {\n e.preventDefault();\n const col = state.activeDropdownIndex % columns;\n if (col < columns - 1) {\n const rightNeighbor = state.activeDropdownIndex + 1;\n if (\n rightNeighbor < state.filteredOptions.length &&\n state.filteredOptions[rightNeighbor]?.is_tappable\n ) {\n this.store.set({ activeDropdownIndex: rightNeighbor });\n }\n }\n break;\n }\n // No highlight → arrow keys move the caret. In re-edit mode that\n // means collapse the highlight to the param's trailing edge and exit.\n if (state.editingParam && e.target instanceof HTMLElement && state.editingTail != null) {\n e.preventDefault();\n const editor = e.target.closest<HTMLElement>(\"[data-aia-input]\") ?? e.target;\n const tail = state.editingTail;\n this.ctx.exitEditMode?.();\n setCursorOffset(editor, tail);\n break;\n }\n const atEnd = isCursorAtEnd(e.target, state);\n if (atEnd && state.actionableSuggestions.length >= 1) {\n e.preventDefault();\n this.removeActivePill();\n }\n break;\n }\n case \"ArrowLeft\": {\n // When a dropdown option is highlighted, arrows navigate the grid\n // only — the caret stays put. Always preventDefault so a press at\n // the leftmost column doesn't fall through to caret movement.\n if (state.activeDropdownIndex >= 0) {\n e.preventDefault();\n if (state.activeDropdownIndex % columns > 0) {\n const leftNeighbor = state.activeDropdownIndex - 1;\n if (leftNeighbor >= 0 && state.filteredOptions[leftNeighbor]?.is_tappable) {\n this.store.set({ activeDropdownIndex: leftNeighbor });\n }\n }\n break;\n }\n // No highlight → arrow keys move the caret.\n if (state.editingParam && e.target instanceof HTMLElement && state.editingAnchor != null) {\n e.preventDefault();\n const editor = e.target.closest<HTMLElement>(\"[data-aia-input]\") ?? e.target;\n const anchor = state.editingAnchor;\n this.ctx.exitEditMode?.();\n setCursorOffset(editor, anchor);\n break;\n }\n break;\n }\n case \"Backspace\": {\n // In re-edit mode the entire bold param is selected; the browser's\n // default Backspace handles deletion of the selected range. Skip\n // removeParamAtCaret so we don't double-handle.\n if (state.editingParam) break;\n if (!this.ctx.removeParamAtCaret) break;\n const offset = getEditableCaretOffset(e.target);\n if (offset == null) break;\n if (this.ctx.removeParamAtCaret(offset)) {\n e.preventDefault();\n }\n break;\n }\n case \"Enter\": {\n e.preventDefault();\n if (\n state.activeDropdownIndex >= 0 &&\n state.filteredOptions[state.activeDropdownIndex]?.is_tappable\n ) {\n this.clickOrSelect(state.activeDropdownIndex, state.filteredOptions, listboxId);\n } else if (onSubmit) {\n const { rawQuery, completedParams: finalParams } = buildQuery(\n state.text,\n state.completedParams,\n );\n onSubmit({\n query: state.text.trim(),\n raw_query: rawQuery,\n completed_params: finalParams,\n });\n this.ctx.afterSubmit?.();\n }\n break;\n }\n case \"Tab\": {\n // Tab moves the highlight through the dropdown's tappable options\n // rather than committing one: with nothing highlighted it lands on the\n // first option, and each subsequent press advances to the next\n // (Shift+Tab retreats), wrapping at either end. Enter commits the\n // highlighted option. This holds even while a placeholder is visible —\n // Tab navigates the options rather than filling the placeholder text.\n const tappableOptionIndices = state.filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n if (tappableOptionIndices.length === 0) break;\n\n // Closed dropdown → the first Tab opens it (like ArrowDown) and lands\n // on the first option (or last, for Shift+Tab).\n if (!state.isDropdownOpen) {\n if (state.actionableSuggestions.length === 0) break;\n e.preventDefault();\n const firstIdx = e.shiftKey\n ? tappableOptionIndices[tappableOptionIndices.length - 1]\n : tappableOptionIndices[0];\n this.store.set({\n pillTapped: true,\n activeDropdownIndex: firstIdx,\n });\n break;\n }\n\n e.preventDefault();\n const currentPos = tappableOptionIndices.indexOf(state.activeDropdownIndex);\n let nextPos: number;\n if (currentPos < 0) {\n // Open but nothing highlighted — land on the first (or last, Shift).\n nextPos = e.shiftKey ? tappableOptionIndices.length - 1 : 0;\n } else {\n const delta = e.shiftKey ? -1 : 1;\n nextPos =\n (currentPos + delta + tappableOptionIndices.length) % tappableOptionIndices.length;\n }\n this.store.set({\n activeDropdownIndex: tappableOptionIndices[nextPos],\n });\n break;\n }\n case \"Escape\": {\n if (state.editingParam && e.target instanceof HTMLElement && state.editingTail != null) {\n const editor = e.target.closest<HTMLElement>(\"[data-aia-input]\") ?? e.target;\n const tail = state.editingTail;\n this.ctx.exitEditMode?.();\n setCursorOffset(editor, tail);\n }\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\n }\n }\n\n /**\n * Index of the first tappable option in the bottom (last) row of the visual\n * grid, or null if no tappable option lives in that row. Used when opening\n * the dropdown from the input while it sits above the input — the highlight\n * should land on the row closest to the input.\n */\n private firstTappableInBottomRow(columns: number): number | null {\n const state = this.store.get();\n if (state.filteredOptions.length === 0) return null;\n const lastRow = Math.floor((state.filteredOptions.length - 1) / columns);\n const bottomRowStart = lastRow * columns;\n for (let i = bottomRowStart; i < state.filteredOptions.length; i++) {\n if (state.filteredOptions[i]?.is_tappable) return i;\n }\n return null;\n }\n\n private getTappableIndices(columns: number): number[] {\n const state = this.store.get();\n const tappable = state.filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n const buckets: number[][] = Array.from({ length: columns }, () => []);\n for (const i of tappable) buckets[i % columns].push(i);\n return buckets.flat();\n }\n\n /**\n * The grid uses a container query to switch between 1 and 2 columns based on\n * width, so the prop value can disagree with what's actually rendered. Read\n * the live column count from the grid; fall back to the prop if unavailable.\n *\n * Walks up from the first option, skipping any ancestor whose\n * `gridTemplateColumns` is the default `\"none\"` (e.g. framework wrapper\n * components — Angular renders each option inside an `<aia-suggestion-item>`\n * tag between the `<div>` and the actual grid container). Without the walk,\n * the wrapper would silently collapse left/right navigation to a single\n * column even when the actual grid renders multiple.\n *\n * The walk is bounded by the listbox element itself: any grid further up the\n * tree is unrelated (e.g. a CSS Grid page layout). Without the bound, a page\n * wrapper with `grid-template-columns` set would be picked up first and break\n * left/right navigation in the dropdown.\n */\n private getEffectiveColumns(): number {\n const listbox = document.getElementById(this.ctx.listboxId);\n // No listbox in the DOM yet (e.g. the dropdown's *ngIf hasn't resolved on\n // the first tick) — there's no grid to measure, and without a valid\n // boundary the walk below would climb to the document root and could pick\n // up an unrelated page-level CSS grid. Fall back to the configured columns.\n if (!listbox) return this.ctx.columns;\n const firstOption = document.getElementById(`${this.ctx.listboxId}-option-0`);\n let el: HTMLElement | null = firstOption?.parentElement ?? null;\n while (el) {\n const gtc = getComputedStyle(el).gridTemplateColumns;\n if (gtc && gtc !== \"none\") {\n const tracks = gtc.split(\" \").filter(Boolean).length;\n if (tracks > 0) return tracks;\n }\n if (el === listbox) break;\n el = el.parentElement;\n }\n return this.ctx.columns;\n }\n\n private clickOrSelect(index: number, options: SuggestionOption[], listboxId: string) {\n const optionEl = document.getElementById(`${listboxId}-option-${index}`);\n if (optionEl) {\n optionEl.click();\n } else {\n this.ctx.selectOption(options[index]);\n }\n }\n\n /**\n * Drop the currently active pill (always index 0 of the actionable\n * suggestions) and promote the next pill to active. Used by ArrowRight at\n * the end of the input to skip past a pill. Inlined here rather than going\n * through PillsController to avoid a circular dependency.\n *\n * When the last pill is removed there are no options left to show, so the\n * dropdown closes on its own. We also clear `pillTapped` in that case: in\n * `manual` mode the dropdown then stays closed until the user taps again,\n * while in `auto` mode it reopens by itself once the next fetch returns\n * fresh suggestions.\n */\n private removeActivePill() {\n const state = this.store.get();\n const placeholders = state.suggestions.filter((s) => s.type === \"placeholder\");\n const actionable = state.suggestions.filter((s) => s.type !== \"placeholder\");\n if (actionable.length === 0) return;\n // The next actionable pill (was index 1) lands at index 0 → becomes active.\n const remaining = actionable.slice(1);\n this.store.set({\n suggestions: [...placeholders, ...remaining],\n pillTapped: remaining.length > 0,\n activeDropdownIndex: -1,\n });\n }\n}\n","import type { DerivedStore } from \"../state\";\nimport type { CoreDerivedState, CoreInputState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nexport interface PillSelectedEvent {\n rawQuery: string;\n selectedPill: string;\n otherPills: string[];\n}\n\nexport interface PillsControllerCallbacks {\n onPillSelected?: (event: PillSelectedEvent) => void;\n}\n\nexport class PillsController {\n constructor(\n private store: DerivedStore<CoreInputState, CoreDerivedState>,\n private callbacks: PillsControllerCallbacks = {},\n ) {}\n\n setActivePill(index: number) {\n const state = this.store.get();\n const actionable = state.suggestions.filter((s) => s.type !== \"placeholder\");\n if (index < 0 || index >= actionable.length) return;\n const moved = actionable[index];\n const rest = actionable.filter((_, i) => i !== index);\n const placeholders = state.suggestions.filter((s) => s.type === \"placeholder\");\n\n if (this.callbacks.onPillSelected) {\n const { rawQuery } = buildQuery(state.text, state.completedParams);\n this.callbacks.onPillSelected({\n rawQuery,\n selectedPill: moved.text,\n otherPills: rest.map((s) => s.text),\n });\n }\n\n const nextSuggestions = [...placeholders, moved, ...rest];\n\n // Highlight the moved pill's first tappable option so the pill reads as\n // \"selected\" (full opacity). `peek` derives the filtered options for the\n // not-yet-committed reorder so the highlight ships in the SAME write — no\n // intermediate notification carrying a stale activeDropdownIndex against the\n // new pill's options. -1 when the pill has no tappable option (it stays in\n // the `first` tier). In `hidden` mode the dropdown never opens, so the\n // highlight is inert there and the pill stays `first`.\n const firstTappable = this.store\n .peek({ suggestions: nextSuggestions })\n .filteredOptions.findIndex((o) => o.is_tappable);\n\n this.store.set({\n suggestions: nextSuggestions,\n pillTapped: true,\n activeDropdownIndex: firstTappable,\n });\n }\n\n removeLastParam() {\n const state = this.store.get();\n if (state.completedParams.length === 0) return;\n this.store.set((s) => ({\n completedParams: s.completedParams.slice(0, -1),\n activeDropdownIndex: -1,\n }));\n }\n}\n","import type { CompletedParamState, Segment } from \"../shared-types\";\n\n/**\n * Derives segments for overlay rendering by matching completed params in text.\n */\nexport function deriveSegments(text: string, completedParams: CompletedParamState[]): Segment[] {\n const result: Segment[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) continue;\n if (idx > pos) {\n result.push({ type: \"text\", value: text.slice(pos, idx) });\n }\n result.push({ type: \"completed\", value: param.text, param });\n pos = idx + param.text.length;\n }\n\n const remaining = text.slice(pos);\n if (remaining) {\n result.push({ type: \"text\", value: remaining });\n }\n\n return result;\n}\n\n/**\n * Checks which completed params still exist in the new text.\n */\nexport function reconcileParams(\n text: string,\n completedParams: CompletedParamState[],\n): { valid: CompletedParamState[]; invalid: CompletedParamState[] } {\n const valid: CompletedParamState[] = [];\n const invalid: CompletedParamState[] = [];\n let pos = 0;\n\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) {\n invalid.push(param);\n } else {\n valid.push(param);\n pos = idx + param.text.length;\n }\n }\n\n return { valid, invalid };\n}\n","export interface DropdownVisibilityInputs {\n /** True while re-editing a bold param. Bypasses caret-at-end and manual-trigger gating. */\n inEditMode: boolean;\n /** Number of options the dropdown would render right now (post-filter). */\n filteredOptionsLength: number;\n isFocused: boolean;\n text: string;\n caretOffset: number | null;\n isLoading: boolean;\n /** True after the user tapped a pill in `manual` trigger mode. */\n pillTapped: boolean;\n}\n\nexport interface DropdownVisibilityOpts {\n dropdownTrigger?: \"auto\" | \"manual\" | \"hidden\";\n closeDropdownOnBlur?: boolean;\n}\n\n// Pure: is the dropdown open right now? See call site in deriveAll for the gating rules.\nexport function computeDropdownVisibility(\n inputs: DropdownVisibilityInputs,\n opts: DropdownVisibilityOpts,\n): boolean {\n const trigger = opts.dropdownTrigger ?? \"auto\";\n const closeOnBlur = opts.closeDropdownOnBlur ?? true;\n const hasOptions = inputs.filteredOptionsLength > 0;\n\n if (inputs.inEditMode) {\n // While re-editing, dropdown visibility is governed solely by whether\n // cached options match the query — focus/manual gates don't apply (the\n // user is in a deliberate edit interaction).\n const focusGate = closeOnBlur ? inputs.isFocused : true;\n return hasOptions && focusGate;\n }\n\n if (trigger === \"auto\") {\n const focusGate = closeOnBlur ? inputs.isFocused : true;\n // Outside re-edit, dropdown only opens when the caret is at the end of\n // the input — middle-of-text caret should NOT show suggestions. Trailing\n // whitespace is ignored so the dropdown stays open right after an option\n // selection (which appends a trailing space) or when the user pauses on\n // a word boundary.\n const trimmedEnd = inputs.text.replace(/\\s+$/, \"\").length;\n const caretAtEnd = inputs.caretOffset == null || inputs.caretOffset >= trimmedEnd;\n return (hasOptions || inputs.isLoading) && focusGate && caretAtEnd;\n }\n\n if (trigger === \"manual\") {\n return (hasOptions || inputs.isLoading) && inputs.pillTapped;\n }\n\n return false;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreDerivedState, CoreInputState, CoreOptions } from \"../types\";\nimport { effectiveFilterBase, extractFilterQuery, filterOptions } from \"../utils/filtering\";\nimport { deriveSegments } from \"../utils/segments\";\nimport { computeDropdownVisibility } from \"./dropdown\";\n\n// Pure derive: raw inputs + opts → segments / actionable / filtered / placeholder / isDropdownOpen.\nexport function deriveAll(inputs: CoreInputState, opts: CoreOptions): CoreDerivedState {\n const segments = deriveSegments(inputs.text, inputs.completedParams);\n const actionableSuggestions = inputs.suggestions.filter((s) => s.type !== \"placeholder\");\n const activeSuggestion = actionableSuggestions[0] as Suggestion | undefined;\n const overrideFn = activeSuggestion ? opts.optionOverrides?.[activeSuggestion.type] : undefined;\n\n const placeholderText = inputs.suggestions\n .filter((s) => s.type === \"placeholder\")\n .map((s) => s.text)\n .join(\" \");\n\n // Clamp filterBase so it never exceeds text length. Promote the placeholder\n // to filterBase when the user has typed through it — the filter query should\n // be just the text past the server-suggested prefix.\n const clampedFilterBase = effectiveFilterBase(\n inputs.text,\n Math.min(inputs.filterBase, inputs.text.length),\n placeholderText,\n );\n // When filterBase is 0 and no fetch has set it yet, don't filter by typed\n // text — the user is still typing the initial query and no server context\n // exists.\n const hasServerContext = inputs.lastRawQuery !== \"\" || clampedFilterBase > 0;\n const filterQuery = hasServerContext\n ? extractFilterQuery(inputs.text, clampedFilterBase, inputs.filterInProgress)\n : \"\";\n const baseOptions = activeSuggestion\n ? overrideFn\n ? overrideFn(filterQuery.trim())\n : (activeSuggestion.options ?? [])\n : [];\n\n // Re-edit mode: dropdown filters the edited param's cached options instead\n // of the active suggestion's options. While the bold param's still in the\n // DOM (user just tapped it, hasn't typed yet), show the FULL cached list —\n // filtering by the param's own text would only match itself. Once the user\n // starts typing (param removed from completedParams), filter by what they\n // typed.\n const inEditMode = inputs.editingParam != null && inputs.editingAnchor != null;\n let filteredOptions: SuggestionOption[];\n if (inEditMode && inputs.editingParam && inputs.editingAnchor != null) {\n const editingId = inputs.editingParam.id;\n const paramStillPresent = inputs.completedParams.some((p) => p.id === editingId);\n const editCaret = inputs.caretOffset ?? inputs.editingAnchor;\n const editQuery = paramStillPresent ? \"\" : inputs.text.slice(inputs.editingAnchor, editCaret);\n filteredOptions = filterOptions(inputs.editingParam.options, editQuery);\n } else {\n filteredOptions = filterOptions(baseOptions, filterQuery);\n }\n // Consumer-controlled visibility of non-tappable options. Defaults to true\n // (current behavior). When false, strip them from what the dropdown sees.\n if (opts.showNonTappableOptions === false) {\n filteredOptions = filteredOptions.filter((o) => o.is_tappable);\n }\n\n const isDropdownOpen = computeDropdownVisibility(\n {\n inEditMode,\n filteredOptionsLength: filteredOptions.length,\n isFocused: inputs.isFocused,\n text: inputs.text,\n caretOffset: inputs.caretOffset,\n isLoading: inputs.isLoading,\n pillTapped: inputs.pillTapped,\n },\n {\n dropdownTrigger: opts.dropdownTrigger,\n closeDropdownOnBlur: opts.closeDropdownOnBlur,\n },\n );\n\n // The leading pill renders \"selected\" (full opacity) only while a tappable\n // dropdown option is highlighted; otherwise it shows the de-emphasized\n // `first` tier. This rule is uniform across triggers: in `auto` the highlight\n // comes from keyboard nav / hover / tapping the pill, and in `manual` tapping\n // the pill both opens the dropdown and highlights its first option (see\n // PillsController.setActivePill). In `hidden` the dropdown never opens, so the\n // guard below keeps the pill in `first`.\n const isActivePillSelected =\n isDropdownOpen &&\n inputs.activeDropdownIndex >= 0 &&\n Boolean(filteredOptions[inputs.activeDropdownIndex]?.is_tappable);\n\n return {\n segments,\n actionableSuggestions,\n filteredOptions,\n placeholderText,\n isDropdownOpen,\n isActivePillSelected,\n };\n}\n","import type { CompletedParamState, Suggestion } from \"../shared-types\";\nimport type { CoreState } from \"../types\";\nimport { effectiveFilterBase, extractFilterQuery, findExactMatch } from \"../utils/filtering\";\n\nexport type PromotionContext =\n | {\n mode: \"fresh\";\n text: string;\n completedParams: CompletedParamState[];\n suggestions: Suggestion[];\n filterBase: number;\n filterInProgress: boolean;\n }\n | {\n mode: \"edit\";\n text: string;\n completedParams: CompletedParamState[];\n editingParam: CompletedParamState;\n editingAnchor: number;\n editingTail: number;\n };\n\nexport interface PromotionResult {\n patch: Partial<CoreState>;\n caretPos: number;\n}\n\n// Promote typed text to a bold param when it exact-matches an option; null when no match.\nexport function tryPromoteExactMatch(ctx: PromotionContext): PromotionResult | null {\n if (ctx.mode === \"fresh\") {\n return promoteFresh(ctx);\n }\n return promoteEdit(ctx);\n}\n\nfunction promoteFresh(ctx: Extract<PromotionContext, { mode: \"fresh\" }>): PromotionResult | null {\n const { text, completedParams, suggestions, filterBase, filterInProgress } = ctx;\n const actionable = suggestions.filter((sg) => sg.type !== \"placeholder\");\n const active = actionable[0];\n if (!active?.options) return null;\n\n const placeholderText = suggestions\n .filter((sg) => sg.type === \"placeholder\")\n .map((sg) => sg.text)\n .join(\" \");\n const effBase = effectiveFilterBase(text, filterBase, placeholderText);\n const query = extractFilterQuery(text, effBase, filterInProgress);\n const match = findExactMatch(active.options, query);\n if (!match) return null;\n\n // Preserve the case the user actually typed so deriveSegments (case-sensitive\n // indexOf) can still match the completed param inside the text.\n const matchLower = match.text.toLowerCase();\n const optionStart = text.toLowerCase().lastIndexOf(matchLower);\n const paramStart = optionStart >= 0 ? optionStart : Math.max(0, text.length - match.text.length);\n const paramEnd = paramStart + match.text.length;\n const optionInText = text.slice(paramStart, paramEnd);\n\n const hasTrailingSpace = paramEnd < text.length && text[paramEnd] === \" \";\n const caretPos = hasTrailingSpace ? paramEnd + 1 : paramEnd;\n\n const completed: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: active.type,\n text: optionInText,\n kind: match.kind,\n suggestionType: active.type,\n suggestionPlaceholder: active.text,\n options: active.options ?? [],\n metadata: match.metadata,\n };\n\n return {\n patch: {\n text,\n completedParams: [...completedParams, completed],\n suggestions: suggestions.filter((sg) => sg !== active),\n filterBase: caretPos,\n newParamId: completed.id,\n caretOffset: caretPos,\n activeDropdownIndex: -1,\n },\n caretPos,\n };\n}\n\nfunction promoteEdit(ctx: Extract<PromotionContext, { mode: \"edit\" }>): PromotionResult | null {\n const { text, completedParams, editingParam, editingAnchor, editingTail } = ctx;\n // Skip while the original bold is still in completedParams (user hasn't\n // typed yet — the editQuery would just be the param's own text).\n if (completedParams.some((p) => p.id === editingParam.id)) return null;\n\n const editQuery = text.slice(editingAnchor, editingTail);\n const match = findExactMatch(editingParam.options, editQuery);\n if (!match) return null;\n\n // Locate the matched portion within the edit region so we know where the\n // new param's text sits in the full input. Preserve the user's typed\n // casing so deriveSegments (case-sensitive indexOf) can still find it.\n const matchLower = match.text.toLowerCase();\n const matchStart = editQuery.toLowerCase().lastIndexOf(matchLower);\n const paramStart = editingAnchor + Math.max(0, matchStart);\n const paramEnd = paramStart + match.text.length;\n const optionInText = text.slice(paramStart, paramEnd);\n\n const hasTrailingSpace = paramEnd < text.length && text[paramEnd] === \" \";\n const caretPos = hasTrailingSpace ? paramEnd + 1 : paramEnd;\n\n const newParam: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: editingParam.suggestionType,\n text: optionInText,\n kind: match.kind,\n suggestionType: editingParam.suggestionType,\n suggestionPlaceholder: editingParam.suggestionPlaceholder,\n options: editingParam.options,\n metadata: match.metadata,\n };\n\n // Insert at the correct text-order position. The editing param is already\n // gone from completedParams; walk the remaining params and insert before\n // the first one whose text sits past the edit region.\n let insertAt = completedParams.length;\n let scanPos = 0;\n for (let i = 0; i < completedParams.length; i++) {\n const idx = text.indexOf(completedParams[i].text, scanPos);\n if (idx === -1) continue;\n if (idx >= caretPos) {\n insertAt = i;\n break;\n }\n scanPos = idx + completedParams[i].text.length;\n }\n const newParams = [...completedParams];\n newParams.splice(insertAt, 0, newParam);\n\n return {\n patch: {\n text,\n completedParams: newParams,\n newParamId: newParam.id,\n filterBase: caretPos,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: caretPos,\n activeDropdownIndex: -1,\n },\n caretPos,\n };\n}\n","import { tryPromoteExactMatch } from \"../promotion/promote\";\nimport type { CompletedParamState, SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\n\nexport interface ReEditDeps {\n store: Store<CoreState>;\n scheduleSetCursor: (offset: number) => void;\n fireTelemetry: (type: \"pill\" | \"option\", data: Record<string, unknown>) => void;\n startSelectionAnimationTimer: () => void;\n}\n\n// Re-edit lifecycle: enter / exit / atomic replace / caret tracking / select / promote-back-to-bold.\nexport class ReEditManager {\n constructor(private deps: ReEditDeps) {}\n\n // Caller highlights the param via Selection API; this just sets the state snapshot.\n start(paramId: string): void {\n const state = this.deps.store.get();\n if (state.editingParam?.id === paramId) return;\n const param = state.completedParams.find((p) => p.id === paramId);\n if (!param) return;\n // Find the param's position in text (same approach as deriveSegments).\n let pos = 0;\n let anchor = -1;\n for (const p of state.completedParams) {\n const idx = state.text.indexOf(p.text, pos);\n if (idx === -1) continue;\n if (p.id === paramId) {\n anchor = idx;\n break;\n }\n pos = idx + p.text.length;\n }\n if (anchor < 0) return;\n this.deps.store.set({\n editingParam: param,\n editingAnchor: anchor,\n editingTail: anchor + param.text.length,\n caretOffset: anchor + param.text.length,\n activeDropdownIndex: -1,\n });\n }\n\n /** Clear re-edit state. Selection collapsing is a DOM concern handled by the caller. */\n exit(): void {\n const state = this.deps.store.get();\n if (!state.editingParam) return;\n this.deps.store.set({\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n activeDropdownIndex: -1,\n });\n }\n\n // Atomic swap of `text[anchor..tail]` while the bold is still in completedParams; returns true if applied (caller preventDefaults).\n replaceRange(replacement: string): boolean {\n const state = this.deps.store.get();\n const editing = state.editingParam;\n const anchor = state.editingAnchor;\n const tail = state.editingTail;\n if (!editing || anchor == null || tail == null) return false;\n // Only intercept while the param's strong is still in the DOM (i.e. it\n // hasn't already been replaced by an earlier keystroke).\n if (!state.completedParams.some((p) => p.id === editing.id)) return false;\n const newText = state.text.slice(0, anchor) + replacement + state.text.slice(tail);\n const newTail = anchor + replacement.length;\n this.deps.store.set((s) => ({\n text: newText,\n completedParams: s.completedParams.filter((p) => p.id !== editing.id),\n editingTail: newTail,\n caretOffset: newTail,\n activeDropdownIndex: -1,\n }));\n this.deps.scheduleSetCursor(newTail);\n this.tryPromote();\n return true;\n }\n\n // Post-input: extends editingTail forward; exits if caret backspaced past anchor.\n caretAfterInput(offset: number | null): void {\n const state = this.deps.store.get();\n const patch: Partial<CoreState> = { caretOffset: offset };\n if (state.editingParam && state.editingAnchor != null && offset != null) {\n if (offset < state.editingAnchor) {\n patch.editingParam = null;\n patch.editingAnchor = null;\n patch.editingTail = null;\n patch.activeDropdownIndex = -1;\n } else if (state.editingTail != null) {\n patch.editingTail = Math.max(state.editingTail, offset);\n }\n }\n this.deps.store.set(patch);\n this.tryPromote();\n }\n\n // Caret moved by click/arrows (not typing): exits if outside [anchor, tail].\n caretMove(offset: number | null): void {\n const state = this.deps.store.get();\n if (\n state.editingParam &&\n state.editingAnchor != null &&\n state.editingTail != null &&\n offset != null &&\n (offset < state.editingAnchor || offset > state.editingTail)\n ) {\n this.deps.store.set({\n caretOffset: offset,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n activeDropdownIndex: -1,\n });\n return;\n }\n this.deps.store.set({ caretOffset: offset });\n }\n\n // Select-option flow for re-edit: new param inherits the same cached suggestion metadata.\n selectOption(option: SuggestionOption): void {\n const state = this.deps.store.get();\n const editing = state.editingParam;\n const anchor = state.editingAnchor;\n const tail = state.editingTail;\n if (!editing || anchor == null || tail == null) return;\n\n this.deps.fireTelemetry(\"option\", {\n raw_query: buildQuery(state.text, state.completedParams).rawQuery,\n selected_option: option.text,\n other_options: editing.options.filter((o) => o.text !== option.text).map((o) => o.text),\n });\n\n const before = state.text.slice(0, anchor);\n const after = state.text.slice(tail);\n // Capitalize when replacing at the very start of the input — matches the\n // case-handling in the normal selectOption path for \"first letter of the\n // input is uppercase\".\n const optionText =\n anchor === 0 && option.text.length > 0\n ? option.text[0].toUpperCase() + option.text.slice(1)\n : option.text;\n // Preserve the trailing-space convention: a single space follows the\n // replaced text unless the next char already provides one.\n const needsTrailingSpace = after.length === 0 || after[0] !== \" \";\n const replacement = needsTrailingSpace ? `${optionText} ` : optionText;\n const newText = before + replacement + after;\n // Caret lands AFTER the trailing space — whether we just added it or it\n // was already there from the original text.\n const caretPos = anchor + replacement.length + (needsTrailingSpace ? 0 : 1);\n\n const newParam: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: editing.suggestionType,\n text: optionText,\n kind: option.kind,\n suggestionType: editing.suggestionType,\n suggestionPlaceholder: editing.suggestionPlaceholder,\n options: editing.options,\n metadata: option.metadata,\n };\n const oldIdx = state.completedParams.findIndex((p) => p.id === editing.id);\n const params = state.completedParams.filter((p) => p.id !== editing.id);\n const insertAt = oldIdx >= 0 ? Math.min(oldIdx, params.length) : params.length;\n params.splice(insertAt, 0, newParam);\n\n this.deps.store.set({\n text: newText,\n completedParams: params,\n newParamId: newParam.id,\n filterBase: caretPos,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: caretPos,\n activeDropdownIndex: -1,\n pillTapped: false,\n skipNextFetch: true,\n inSelectionAnimation: true,\n });\n this.deps.startSelectionAnimationTimer();\n // Park caret right after the replacement (not end of text) once the render microtask commits.\n this.deps.scheduleSetCursor(caretPos);\n }\n\n private tryPromote(): void {\n const s = this.deps.store.get();\n if (!s.editingParam || s.editingAnchor == null || s.editingTail == null) return;\n const result = tryPromoteExactMatch({\n mode: \"edit\",\n text: s.text,\n completedParams: s.completedParams,\n editingParam: s.editingParam,\n editingAnchor: s.editingAnchor,\n editingTail: s.editingTail,\n });\n if (!result) return;\n this.deps.store.set(result.patch);\n this.deps.scheduleSetCursor(result.caretPos);\n }\n}\n","/**\n * The dropdown footer's keyboard hint has three states, in priority order:\n * 1. an option is highlighted → \"enter to proceed\" (Enter commits it);\n * 2. otherwise, the input is empty → \"tab to select\" (Tab highlights the first\n * option). This is the ONLY state that shows \"tab to select\";\n * 3. otherwise (the input has text) → \"→ to skip\" — the right arrow skips the\n * active pill.\n *\n * Pure computation — no DOM access. Shared by the vanilla renderer and the\n * React / Angular footer components so the wording can't drift between them.\n */\nexport function getFooterHint(\n optionHighlighted: boolean,\n isInputEmpty: boolean,\n): { key: string; hint: string } {\n if (optionHighlighted) return { key: \"enter\", hint: \"to proceed\" };\n if (isInputEmpty) return { key: \"tab\", hint: \"to select\" };\n return { key: \"→\", hint: \"to skip\" };\n}\n","const KEY_ATTR = \"data-aia-key\";\n\nexport interface ReconcileOptions<T> {\n keyOf: (item: T, index: number) => string;\n create: (item: T, index: number) => HTMLElement;\n update?: (el: HTMLElement, item: T, index: number) => void;\n}\n\n// Keyed DOM reconcile — reuses/repositions/removes `data-aia-key` children; unkeyed children untouched.\nexport function reconcileList<T>(\n parent: Element,\n items: readonly T[],\n opts: ReconcileOptions<T>,\n): HTMLElement[] {\n const existing = new Map<string, HTMLElement>();\n for (const child of Array.from(parent.children)) {\n const key = child.getAttribute(KEY_ATTR);\n if (key != null) existing.set(key, child as HTMLElement);\n }\n\n const used = new Set<string>();\n const result: HTMLElement[] = [];\n for (let i = 0; i < items.length; i++) {\n const item = items[i];\n const key = opts.keyOf(item, i);\n used.add(key);\n let el = existing.get(key);\n if (!el) {\n el = opts.create(item, i);\n el.setAttribute(KEY_ATTR, key);\n }\n opts.update?.(el, item, i);\n if (parent.children[i] !== el) {\n parent.insertBefore(el, parent.children[i] ?? null);\n }\n result.push(el);\n }\n\n for (const [key, el] of existing) {\n if (!used.has(key)) el.remove();\n }\n\n return result;\n}\n","import type { Suggestion } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\nconst FALLBACK_SKELETON_WIDTHS = [125, 69];\n\n// Opacity per pill state. The selected pill (active pill while the dropdown is\n// open on it) is full opacity; otherwise pills take a positional tier:\n// first → next → last.\nfunction getPillOpacity(index: number, selected: boolean): number {\n if (selected) return 1; // Selected\n if (index === 0) return 0.7; // First\n if (index === 1) return 0.4; // Next\n return 0.2; // Last\n}\n\nexport function renderPills(\n container: HTMLElement,\n pills: Suggestion[],\n activePillIndex: number,\n onSelectPill: (index: number) => void,\n rounded = false,\n loading = false,\n /**\n * Whether the active (leading) pill is in its selected state — full opacity\n * instead of the positional `first` tier. Auto trigger ⇒ true while a\n * dropdown option is highlighted; manual trigger ⇒ true after the user taps\n * the pill. See `CoreDerivedState.isActivePillSelected`.\n */\n activeSelected = false,\n) {\n let list = container.querySelector<HTMLElement>(\".magicx-aia-pill-list\");\n if (!list) {\n list = document.createElement(\"span\");\n list.className = \"magicx-aia-pill-list\";\n container.appendChild(list);\n }\n\n // No cached pills + loading → fallback fixed-width placeholders.\n if (loading && pills.length === 0) {\n list.setAttribute(\"data-aia-pill-list-loading\", \"\");\n list.innerHTML = \"\";\n for (let i = 0; i < FALLBACK_SKELETON_WIDTHS.length; i++) {\n const width = FALLBACK_SKELETON_WIDTHS[i];\n const span = document.createElement(\"span\");\n span.setAttribute(\"data-aia-pill-skeleton\", \"\");\n span.className = `magicx-aia-pill magicx-aia-pill--skeleton${rounded ? \" magicx-aia-pill--rounded\" : \"\"}`;\n span.style.width = `${width}px`;\n span.style.opacity = String(getPillOpacity(i, false));\n list.appendChild(span);\n }\n return;\n }\n\n if (loading) {\n list.setAttribute(\"data-aia-pill-list-loading\", \"\");\n } else {\n list.removeAttribute(\"data-aia-pill-list-loading\");\n }\n\n // Drop any leftover fallback placeholders before diffing the real pills.\n for (const skel of list.querySelectorAll<HTMLElement>(\"[data-aia-pill-skeleton]\")) {\n skel.remove();\n }\n\n reconcileList(list, pills, {\n keyOf: (pill) => `${pill.type}-${pill.text}`,\n create: (pill) => {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.tabIndex = -1;\n btn.setAttribute(\"data-aia-pill\", \"\");\n btn.setAttribute(\"contenteditable\", \"false\");\n btn.textContent = pill.text;\n btn.addEventListener(\"mousedown\", (e) => e.preventDefault());\n return btn;\n },\n update: (el, _pill, i) => {\n const btn = el as HTMLButtonElement;\n // Selected = the active pill in its selected state (see activeSelected).\n const selected = activeSelected && i === activePillIndex && !loading;\n const classes = [\"magicx-aia-pill\"];\n if (rounded) classes.push(\"magicx-aia-pill--rounded\");\n if (selected) classes.push(\"magicx-aia-pill--active\");\n if (loading) classes.push(\"magicx-aia-pill--skeleton\");\n btn.className = classes.join(\" \");\n btn.style.width = \"\";\n btn.style.opacity = String(getPillOpacity(i, selected));\n if (loading) {\n btn.setAttribute(\"data-aia-loading\", \"\");\n btn.disabled = true;\n btn.onclick = null;\n } else {\n btn.removeAttribute(\"data-aia-loading\");\n btn.disabled = false;\n btn.onclick = () => onSelectPill(i);\n }\n },\n });\n}\n\nexport function clearPills(container: HTMLElement) {\n container.querySelector(\".magicx-aia-pill-list\")?.remove();\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\n// Per-wrapper scroll/resize wiring, kept so the bottom fade can be torn down\n// when the grid is removed instead of leaking observers across rebuilds.\nconst fadeObservers = new WeakMap<HTMLElement, { ro: ResizeObserver; onScroll: () => void }>();\n\n/**\n * Renders the dropdown's option grid into `parent`: a `.aia-grid-fade` wrapper\n * holding a `.aia-grid` element (the intrinsic Grid layout primitive — auto-fit\n * 250px columns, scrollable with a capped height) with one option element per\n * suggestion. Vanilla analogue of React's `SuggestionGrid` component. The\n * wrapper shows a bottom fade (`data-fade`) while the grid overflows below.\n * Creates the wrapper on first use, reuses it on later renders, and removes it\n * (tearing down its observers) when there are no options.\n */\nexport function renderSuggestionGrid(\n parent: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n onSelect: (option: SuggestionOption) => void,\n onHighlight: (index: number) => void,\n listboxId: string,\n loading: boolean,\n): void {\n let wrapper = parent.querySelector<HTMLElement>(\".aia-grid-fade\");\n if (options.length === 0) {\n if (wrapper) {\n teardownFade(wrapper);\n wrapper.remove();\n }\n return;\n }\n let grid = wrapper?.querySelector<HTMLElement>(\".aia-grid\") ?? null;\n if (!wrapper || !grid) {\n wrapper = document.createElement(\"div\");\n wrapper.className = \"aia-grid-fade\";\n grid = document.createElement(\"div\");\n grid.className = \"aia-grid magicx-aia-grid\";\n grid.setAttribute(\"data-scroll\", \"\");\n grid.style.setProperty(\"--aia-grid-min\", \"250px\");\n grid.style.setProperty(\"--aia-grid-max\", \"250px\");\n grid.style.setProperty(\"--aia-grid-gap\", \"0\");\n wrapper.appendChild(grid);\n setupFade(wrapper, grid);\n parent.appendChild(wrapper);\n }\n renderOptions(grid, options, activeIndex, onSelect, onHighlight, listboxId, loading);\n updateFade(wrapper, grid);\n}\n\n// Show the fade while there's hidden overflow below the grid; hide it once the\n// content fits or the user scrolls to the bottom. Mirrors React's Grid effect.\nfunction updateFade(wrapper: HTMLElement, grid: HTMLElement): void {\n const overflowBelow = grid.scrollHeight - grid.scrollTop - grid.clientHeight > 1;\n if (overflowBelow) wrapper.setAttribute(\"data-fade\", \"\");\n else wrapper.removeAttribute(\"data-fade\");\n}\n\nfunction setupFade(wrapper: HTMLElement, grid: HTMLElement): void {\n const onScroll = () => updateFade(wrapper, grid);\n const ro = new ResizeObserver(onScroll);\n ro.observe(grid);\n grid.addEventListener(\"scroll\", onScroll, { passive: true });\n fadeObservers.set(wrapper, { ro, onScroll });\n}\n\nfunction teardownFade(wrapper: HTMLElement): void {\n const entry = fadeObservers.get(wrapper);\n if (!entry) return;\n entry.ro.disconnect();\n wrapper.querySelector<HTMLElement>(\".aia-grid\")?.removeEventListener(\"scroll\", entry.onScroll);\n fadeObservers.delete(wrapper);\n}\n\nfunction renderOptions(\n grid: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n onSelect: (option: SuggestionOption) => void,\n onHighlight: (index: number) => void,\n listboxId: string,\n loading: boolean,\n) {\n // Loading flips re-key every option so any cached non-loading element\n // is replaced rather than reused with stale loading attrs.\n const loadingFlag = loading ? \"1\" : \"0\";\n\n reconcileList(grid, options, {\n keyOf: (opt) => `${opt.text}\\0${loadingFlag}`,\n create: (option) => buildOptionElement(option, loading),\n update: (el, option, i) => {\n const isHighlighted = i === activeIndex && !loading;\n el.id = `${listboxId}-option-${i}`;\n el.dataset.aiaIndex = String(i);\n el.setAttribute(\"aria-selected\", String(isHighlighted));\n el.classList.toggle(\"magicx-aia-option--highlighted\", isHighlighted);\n // Reassign every render so reused elements don't hold stale closures.\n if (!loading && option.is_tappable) {\n el.onclick = () => {\n el.classList.add(\"magicx-aia-option--pressed\");\n onSelect(option);\n setTimeout(() => el.classList.remove(\"magicx-aia-option--pressed\"), 500);\n };\n el.onmouseenter = () => {\n const idx = Number.parseInt(el.dataset.aiaIndex ?? \"-1\", 10);\n if (idx >= 0) onHighlight(idx);\n };\n } else {\n el.onclick = null;\n el.onmouseenter = null;\n }\n },\n });\n}\n\nfunction buildOptionElement(option: SuggestionOption, loading: boolean): HTMLElement {\n const item = document.createElement(\"div\");\n item.setAttribute(\"role\", \"option\");\n item.setAttribute(\"data-aia-option\", \"\");\n if (loading) item.setAttribute(\"data-aia-loading\", \"\");\n item.tabIndex = loading || !option.is_tappable ? -1 : 0;\n\n const classes = [\"magicx-aia-option\"];\n if (option.is_tappable) {\n classes.push(\"magicx-aia-option--tappable\");\n } else {\n classes.push(\"magicx-aia-option--non-tappable\");\n }\n item.className = classes.join(\" \");\n\n const streaks = document.createElement(\"div\");\n streaks.className = \"magicx-aia-streaks\";\n item.appendChild(streaks);\n\n const streaksVert = document.createElement(\"div\");\n streaksVert.className = \"magicx-aia-streaks-vert\";\n item.appendChild(streaksVert);\n\n const content = document.createElement(\"span\");\n content.className = \"magicx-aia-option-content\";\n\n // Inner inline span so multi-line options render the skeleton as one bar\n // per line. Painting the background on .content directly would collapse\n // all lines into one tall rectangle (flex blockifies it).\n const text = document.createElement(\"span\");\n text.className = \"magicx-aia-option-text\";\n text.textContent = option.icon ? `${option.icon} ${option.text}` : option.text;\n content.appendChild(text);\n\n if (option.tag) {\n const tag = document.createElement(\"span\");\n tag.className = \"magicx-aia-option-tag\";\n tag.textContent = option.tag;\n content.appendChild(tag);\n }\n\n item.appendChild(content);\n\n return item;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\nimport { getFooterHint } from \"../utils/footerHint\";\nimport { renderPills } from \"./renderPills\";\nimport { renderSuggestionGrid } from \"./renderSuggestionGrid\";\n\nconst FALLBACK_SKELETON_BAR_WIDTHS = [159, 119, 164];\n\ninterface DropdownState {\n suggestions: Suggestion[];\n filteredOptions: SuggestionOption[];\n activeIndex: number;\n isOpen: boolean;\n isLoading: boolean;\n listboxId: string;\n pills: Suggestion[];\n showPills: boolean;\n /** Whether the active pill renders selected — see CoreDerivedState.isActivePillSelected. */\n isActivePillSelected: boolean;\n /** Whether the input has no typed text — the sole state that surfaces \"tab to select\" (see getFooterHint). */\n isInputEmpty: boolean;\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n onPillClick: (index: number) => void;\n}\n\nexport function createDropdown(listboxId: string): HTMLElement {\n const dropdown = document.createElement(\"div\");\n dropdown.id = listboxId;\n dropdown.setAttribute(\"role\", \"listbox\");\n dropdown.setAttribute(\"data-aia-dropdown\", \"\");\n dropdown.className = \"magicx-aia-dropdown\";\n dropdown.addEventListener(\"mousedown\", (e) => e.preventDefault());\n return dropdown;\n}\n\nexport function renderDropdown(dropdown: HTMLElement, state: DropdownState) {\n const {\n filteredOptions,\n activeIndex,\n isOpen,\n isLoading,\n pills,\n showPills,\n isActivePillSelected,\n onSelect,\n onHighlight,\n onPillClick,\n } = state;\n\n const hasPills = showPills && pills.length > 0;\n const hasOptions = filteredOptions.length > 0;\n const isVisible = isOpen && (hasOptions || hasPills || isLoading);\n\n if (isVisible) {\n dropdown.classList.add(\"magicx-aia-dropdown--visible\");\n } else {\n dropdown.classList.remove(\"magicx-aia-dropdown--visible\");\n }\n\n if (isLoading) {\n dropdown.setAttribute(\"data-aia-loading\", \"\");\n } else {\n dropdown.removeAttribute(\"data-aia-loading\");\n }\n\n // When the dropdown is closing/closed, leave its last-rendered content in\n // place and let the opacity transition fade the whole populated dropdown out.\n // Tearing the content down here instead would strip the pills/options grid\n // immediately while the always-present footer keeps fading — producing a\n // flash of a footer-only \"no options\" box mid-transition (e.g. when the user\n // skips the last pill with →). Content is rebuilt on the next visible render.\n if (!isVisible) return;\n\n // --- Stack (vertical layout primitive) ---\n // Pill bar, options grid, and skeleton bars stack top-to-bottom inside it.\n let stack = dropdown.querySelector<HTMLElement>(\".aia-stack\");\n if (!stack) {\n stack = document.createElement(\"div\");\n stack.className = \"aia-stack\";\n stack.style.setProperty(\"--aia-stack-space\", \"8px\");\n dropdown.appendChild(stack);\n }\n\n // --- Pill bar (cluster of pills) ---\n // Render the pill bar when we have real pills OR when loading + showPills\n // (so the bar can host the fallback placeholder pills).\n const wantsPillBar = hasPills || (isLoading && showPills);\n let pillBar = stack.querySelector<HTMLElement>(\".magicx-aia-pill-bar\");\n if (wantsPillBar) {\n if (!pillBar) {\n pillBar = document.createElement(\"div\");\n pillBar.className = \"magicx-aia-pill-bar aia-cluster\";\n pillBar.setAttribute(\"data-nowrap\", \"\");\n pillBar.setAttribute(\"data-aia-pillbar\", \"\");\n stack.insertBefore(pillBar, stack.firstChild);\n }\n renderPills(pillBar, pills, 0, onPillClick, true, isLoading, isActivePillSelected);\n } else if (pillBar) {\n pillBar.remove();\n }\n\n // --- Options grid (SuggestionGrid) ---\n // The grid element + option items are managed by renderSuggestionGrid, the\n // vanilla analogue of React's <SuggestionGrid>.\n renderSuggestionGrid(\n stack,\n filteredOptions,\n activeIndex,\n onSelect,\n onHighlight,\n state.listboxId,\n isLoading,\n );\n\n // --- Fallback skeleton bars (when loading with no cached options) ---\n let skeleton = stack.querySelector<HTMLElement>(\".magicx-aia-skeleton-bars\");\n if (isLoading && !hasOptions) {\n if (!skeleton) {\n skeleton = document.createElement(\"div\");\n skeleton.className = \"magicx-aia-skeleton-bars\";\n skeleton.setAttribute(\"data-aia-skeleton-bars\", \"\");\n for (const width of FALLBACK_SKELETON_BAR_WIDTHS) {\n const bar = document.createElement(\"span\");\n bar.className = \"magicx-aia-skeleton-bar\";\n bar.style.width = `${width}px`;\n skeleton.appendChild(bar);\n }\n stack.appendChild(skeleton);\n }\n } else if (skeleton) {\n skeleton.remove();\n }\n\n // --- Footer (chrome — always last; hidden with the dropdown) ---\n // Keep it below a grid/skeleton created later, but only move it when it isn't\n // already the last child — appendChild on an attached node is a DOM mutation\n // that would otherwise fire on every render (spurious Mutation/ResizeObserver\n // callbacks in consumer apps on each keystroke/highlight).\n const footer = stack.querySelector<HTMLElement>(\".magicx-aia-footer\") ?? createFooter();\n const optionHighlighted = activeIndex >= 0 && Boolean(filteredOptions[activeIndex]?.is_tappable);\n updateFooterHint(footer, getFooterHint(optionHighlighted, state.isInputEmpty));\n if (stack.lastElementChild !== footer) stack.appendChild(footer);\n}\n\nfunction updateFooterHint(\n footer: HTMLElement,\n { key: nextKey, hint: nextHint }: ReturnType<typeof getFooterHint>,\n) {\n const key = footer.querySelector<HTMLElement>(\".magicx-aia-footer-key\");\n const hint = footer.querySelector<HTMLElement>(\".magicx-aia-footer-hint\");\n if (!key || !hint) return;\n if (key.textContent !== nextKey) key.textContent = nextKey;\n if (hint.textContent !== nextHint) hint.textContent = nextHint;\n}\n\nfunction createFooter(): HTMLElement {\n const footer = document.createElement(\"footer\");\n footer.className = \"magicx-aia-footer\";\n footer.setAttribute(\"data-aia-footer\", \"\");\n\n const divider = document.createElement(\"div\");\n divider.className = \"magicx-aia-footer-divider\";\n\n const row = document.createElement(\"div\");\n row.className = \"aia-cluster magicx-aia-footer-row\";\n row.setAttribute(\"data-align\", \"center\");\n row.setAttribute(\"data-justify\", \"between\");\n row.setAttribute(\"data-nowrap\", \"\");\n\n const hintGroup = document.createElement(\"div\");\n hintGroup.className = \"aia-cluster magicx-aia-footer-hint-group\";\n hintGroup.setAttribute(\"data-align\", \"center\");\n hintGroup.style.setProperty(\"--aia-cluster-gap\", \"5px\");\n const key = document.createElement(\"kbd\");\n key.className = \"magicx-aia-footer-key\";\n key.textContent = \"tab\";\n const hint = document.createElement(\"span\");\n hint.className = \"magicx-aia-footer-hint\";\n hint.textContent = \"to select\";\n hintGroup.append(key, hint);\n\n const brandGroup = document.createElement(\"a\");\n brandGroup.className = \"aia-cluster magicx-aia-footer-brand-link\";\n brandGroup.setAttribute(\"data-align\", \"center\");\n brandGroup.href = \"https://ai-autocomplete.com\";\n brandGroup.target = \"_blank\";\n brandGroup.rel = \"noopener noreferrer\";\n brandGroup.style.setProperty(\"--aia-cluster-gap\", \"2px\");\n const brand = document.createElement(\"span\");\n brand.className = \"magicx-aia-footer-brand\";\n brand.textContent = \"AI\";\n const badge = document.createElement(\"span\");\n badge.className = \"magicx-aia-footer-badge\";\n badge.textContent = \"Autocomplete\";\n brandGroup.append(brand, badge);\n\n row.append(hintGroup, brandGroup);\n footer.append(divider, row);\n return footer;\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { createDropdown, renderDropdown } from \"./renderDropdown\";\n\ninterface DropdownOnlyRefs {\n dropdown: HTMLElement;\n}\n\ninterface DropdownOnlyOptions {\n store: Store<CoreState>;\n listboxId: string;\n selectOption: (option: SuggestionOption) => void;\n setActivePill: (index: number) => void;\n}\n\nexport function buildDropdownOnly(\n container: HTMLElement,\n opts: DropdownOnlyOptions,\n): DropdownOnlyRefs {\n const dropdown = createDropdown(opts.listboxId);\n container.appendChild(dropdown);\n return { dropdown };\n}\n\nexport function updateDropdownOnly(\n refs: DropdownOnlyRefs,\n state: CoreState,\n opts: DropdownOnlyOptions,\n) {\n renderDropdown(refs.dropdown, {\n suggestions:\n state.actionableSuggestions.length > 0\n ? [{ ...state.actionableSuggestions[0], options: state.filteredOptions }]\n : [],\n filteredOptions: state.filteredOptions,\n activeIndex: state.activeDropdownIndex,\n isOpen: state.isDropdownOpen,\n // Re-edit shows cached options, and the streak animation finishes before\n // we swap in the skeleton.\n isLoading: state.isLoading && !state.editingParam && !state.inSelectionAnimation,\n listboxId: opts.listboxId,\n pills: state.actionableSuggestions,\n showPills: true, // always show pills in dropdown-only mode\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n onSelect: opts.selectOption,\n onHighlight: (i) => opts.store.set({ activeDropdownIndex: i }),\n onPillClick: opts.setActivePill,\n });\n}\n","import { getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { Segment } from \"../shared-types\";\n\ninterface RenderEditableArgs {\n input: HTMLElement;\n segments: Segment[];\n newParamId: string | null;\n /** When set, the matching `<strong>` is decorated with the editing class. */\n editingParamId: string | null;\n placeholderText: string;\n isFocused: boolean;\n}\n\n/**\n * Renders text segments — including bold `<strong>` runs for completed params\n * — into the contentEditable input. Pills are NOT rendered here; they live as\n * a sibling element so the editable's subtree never contains non-editable\n * children. See renderInput.ts for the pill list placement.\n *\n * Skips rebuilds when the segment key is unchanged so an in-flight shimmer\n * animation isn't interrupted by unrelated state churn.\n */\nexport function renderEditableContent(args: RenderEditableArgs) {\n const { input, segments, newParamId, editingParamId, placeholderText, isFocused } = args;\n\n const empty = segments.length === 0;\n input.dataset.aiaEmpty = empty ? \"true\" : \"false\";\n if (empty && placeholderText) {\n input.dataset.placeholder = placeholderText;\n } else {\n delete input.dataset.placeholder;\n }\n\n const segKey = segments.map((s) => `${s.type}:${s.value}`).join(\"\\0\");\n const lastSegKey = input.dataset.segKey ?? \"\";\n const lastNewParamId = input.dataset.newParamId ?? \"\";\n const lastEditingParamId = input.dataset.editingParamId ?? \"\";\n if (\n segKey === lastSegKey &&\n (newParamId ?? \"\") === lastNewParamId &&\n (editingParamId ?? \"\") === lastEditingParamId\n ) {\n return;\n }\n\n const savedOffset = isFocused ? getCursorOffset(input) : null;\n input.dataset.segKey = segKey;\n input.dataset.newParamId = newParamId ?? \"\";\n input.dataset.editingParamId = editingParamId ?? \"\";\n\n const doc = input.ownerDocument ?? document;\n const frag = doc.createDocumentFragment();\n let newLength = 0;\n for (const seg of segments) {\n newLength += seg.value.length;\n if (seg.type === \"completed\") {\n const strong = doc.createElement(\"strong\");\n strong.dataset.seg = \"completed\";\n strong.dataset.paramId = seg.param.id;\n const isNew = seg.param.id === newParamId;\n const isEditing = seg.param.id === editingParamId;\n const classes = [\"magicx-aia-segment\", \"magicx-aia-segment--completed\"];\n if (isNew) classes.push(\"magicx-aia-shimmer-revealed\", \"magicx-aia-shimmer-sweep\");\n if (isEditing) classes.push(\"magicx-aia-segment--editing\");\n strong.className = classes.join(\" \");\n strong.textContent = seg.value;\n frag.appendChild(strong);\n } else {\n frag.appendChild(doc.createTextNode(seg.value));\n }\n }\n input.replaceChildren(frag);\n input.dataset.aiaTextLength = String(newLength);\n\n if (savedOffset != null) {\n // Restore the caret to where the browser left it (clamped to the new\n // text length). Callers that need the caret at a specific position\n // (e.g. Tab-on-placeholder, edit-mode replacement) schedule their own\n // `setCursorOffset` via `queueMicrotask` after the store mutation.\n setCursorOffset(input, Math.max(0, Math.min(savedOffset, newLength)));\n }\n}\n","const SUBMIT_SVG = `<svg width=\"18\" height=\"18\" viewBox=\"0 0 18 18\" fill=\"none\" role=\"img\" aria-label=\"Submit\"><path d=\"M9 14V4M9 4L4 9M9 4L14 9\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/></svg>`;\n\n/**\n * Builds the default circular submit button (up-arrow). Vanilla analogue of\n * React's `<SubmitButton>`. The default/custom/null branching stays in the\n * caller (`renderInput`); this only constructs the built-in button.\n */\nexport function createSubmitButton(): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.className = \"magicx-aia-submit\";\n btn.setAttribute(\"aria-label\", \"Submit\");\n btn.setAttribute(\"data-aia-submit\", \"\");\n btn.innerHTML = SUBMIT_SVG;\n return btn;\n}\n","import { extractPlainText, getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { AutocompleteResult, Suggestion } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { buildQuery } from \"../utils/buildQuery\";\nimport { createDropdown, renderDropdown } from \"./renderDropdown\";\nimport { renderEditableContent } from \"./renderEditable\";\nimport { clearPills, renderPills } from \"./renderPills\";\nimport { createSubmitButton } from \"./renderSubmitButton\";\n\ninterface RenderInputOptions {\n store: Store<CoreState>;\n listboxId: string;\n pillPlacement: \"inline\" | \"dropdown\" | \"hidden\";\n autoFocus?: boolean;\n onSubmit?: (result: AutocompleteResult) => void;\n /** Invoked after onSubmit fires — Tier 1 uses this to auto-reset. */\n afterSubmit?: () => void;\n submitButton?: HTMLElement | null;\n selectOption: (option: import(\"../shared-types\").SuggestionOption) => void;\n setActivePill: (index: number) => void;\n handleKeyDown: (e: KeyboardEvent) => void;\n handleChange: (value: string) => void;\n /** Re-edit entry: triggered when caret enters a bold completed param. */\n startEditingParam: (paramId: string) => void;\n /** Re-edit caret-tracking after an input event (extends edit tail). */\n handleCaretAfterInput: (offset: number | null) => void;\n /** Re-edit caret-tracking on selection-only moves (may exit edit mode). */\n handleCaretMove: (offset: number | null) => void;\n /** Re-edit beforeinput intercept: replace the editing range atomically. */\n replaceEditingRange: (replacement: string) => boolean;\n}\n\nexport interface DOMRefs {\n input: HTMLDivElement;\n /** Non-editable inline pill list rendered as a sibling of the editable. */\n inlinePillContainer: HTMLSpanElement;\n dropdown: HTMLElement;\n /** The default built-in button when no `submitButton` was provided. Null when consumer passed `null` or a custom element. */\n submitButton: HTMLButtonElement | null;\n /** Aborts all listeners attached during buildDOM. */\n abort: AbortController;\n}\n\nfunction supportsPlaintextOnly(): boolean {\n const probe = document.createElement(\"div\");\n probe.setAttribute(\"contenteditable\", \"plaintext-only\");\n return probe.contentEditable === \"plaintext-only\";\n}\n\n/**\n * Toggles `data-aia-pill-wrapped` on the pill list container when it has\n * wrapped to its own line (no preceding text on the same line). CSS uses the\n * attribute to drop the 8px left margin — that margin would otherwise appear\n * as a stray indent at the start of the wrapped line.\n *\n * Wrap detection compares the inner pill row's top with the editor's bottom.\n * If the pill row sits on (or above) the editor's last line, it's NOT\n * wrapped — keep the margin. This holds whether the editor has typed text or\n * just a placeholder; the placeholder still occupies the editor's line box,\n * so pills sitting alongside it should retain the visible gap.\n */\nfunction measurePillWrap(input: HTMLElement, container: HTMLElement): void {\n const inner = container.firstElementChild as HTMLElement | null;\n if (!inner) {\n container.removeAttribute(\"data-aia-pill-wrapped\");\n return;\n }\n const cRect = inner.getBoundingClientRect();\n const eRect = input.getBoundingClientRect();\n const wrapped = cRect.top >= eRect.bottom - 2;\n if (wrapped) container.setAttribute(\"data-aia-pill-wrapped\", \"\");\n else container.removeAttribute(\"data-aia-pill-wrapped\");\n}\n\nexport function buildDOM(container: HTMLElement, opts: RenderInputOptions): DOMRefs {\n const { listboxId } = opts;\n\n const dropdown = createDropdown(listboxId);\n container.appendChild(dropdown);\n\n const inputWrapper = document.createElement(\"div\");\n inputWrapper.className = \"magicx-aia-input-wrapper\";\n container.appendChild(inputWrapper);\n\n const editor = document.createElement(\"div\");\n editor.className = \"magicx-aia-editor\";\n editor.setAttribute(\"data-aia-editor\", \"\");\n inputWrapper.appendChild(editor);\n\n const input = document.createElement(\"div\");\n input.className = \"magicx-aia-input\";\n input.setAttribute(\"data-aia-input\", \"\");\n input.setAttribute(\"contenteditable\", supportsPlaintextOnly() ? \"plaintext-only\" : \"true\");\n input.setAttribute(\"role\", \"combobox\");\n input.setAttribute(\"aria-autocomplete\", \"list\");\n input.setAttribute(\"aria-haspopup\", \"listbox\");\n input.setAttribute(\"aria-controls\", listboxId);\n input.setAttribute(\"aria-expanded\", \"false\");\n input.setAttribute(\"spellcheck\", \"true\");\n input.setAttribute(\"enterkeyhint\", \"send\");\n editor.appendChild(input);\n\n // Pills sit as a sibling of the editable — they're never inside a\n // contentEditable subtree, so there's no risk of them becoming part of the\n // typing surface. Layout (CSS) keeps them visually adjacent to the text.\n const inlinePillContainer = document.createElement(\"span\");\n inlinePillContainer.className = \"magicx-aia-pill-list-container\";\n inlinePillContainer.setAttribute(\"data-aia-pill-list-container\", \"\");\n editor.appendChild(inlinePillContainer);\n\n let submitButton: HTMLButtonElement | null = null;\n let submitTarget: HTMLElement | null = null;\n if (opts.submitButton === undefined) {\n submitButton = createSubmitButton();\n inputWrapper.appendChild(submitButton);\n submitTarget = submitButton;\n } else if (opts.submitButton !== null) {\n submitTarget = opts.submitButton;\n if (!submitTarget.hasAttribute(\"data-aia-submit\")) {\n submitTarget.setAttribute(\"data-aia-submit\", \"\");\n }\n inputWrapper.appendChild(submitTarget);\n }\n\n const abort = new AbortController();\n const { signal } = abort;\n\n let composing = false;\n // Tracks when an input event has just fired so the immediately-following\n // selectionchange knows to extend the edit tail rather than treat the\n // caret move as a navigation event.\n let lastInputAt = 0;\n\n const fireInput = () => {\n const raw = extractPlainText(input);\n const shouldCapitalize = raw.length > 0 && raw[0] !== raw[0].toUpperCase();\n const newValue = shouldCapitalize ? raw[0].toUpperCase() + raw.slice(1) : raw;\n opts.handleChange(newValue);\n };\n\n const findEnclosingParamId = (): string | null => {\n const sel = (input.ownerDocument ?? document).getSelection();\n if (!sel || sel.rangeCount === 0) return null;\n const anchor = sel.anchorNode;\n if (!anchor || !input.contains(anchor)) return null;\n const startEl =\n anchor.nodeType === Node.ELEMENT_NODE ? (anchor as Element) : anchor.parentElement;\n const strong = startEl?.closest<HTMLElement>('strong[data-seg=\"completed\"][data-param-id]');\n return strong?.dataset.paramId ?? null;\n };\n\n inputWrapper.addEventListener(\n \"click\",\n (e) => {\n // Clicking a pill button should activate the pill, not steal focus.\n if ((e.target as HTMLElement | null)?.closest(\"[data-aia-pill]\")) return;\n input.focus();\n },\n { signal },\n );\n\n input.addEventListener(\n \"input\",\n () => {\n if (composing) return;\n lastInputAt = performance.now();\n fireInput();\n // After typing, extend the edit tail to the new caret position so the\n // user can keep typing past the param's original end without exiting.\n opts.handleCaretAfterInput(getCursorOffset(input));\n },\n { signal },\n );\n\n // selectionchange fires on the document (Selection API doesn't bubble to\n // elements). Filter to selections anchored inside our editor, then route\n // the caret state into the core. A short cooldown after `input` events\n // avoids treating the post-typing caret position as a \"move\" that would\n // exit edit mode.\n const doc = input.ownerDocument ?? document;\n doc.addEventListener(\n \"selectionchange\",\n () => {\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n if (!input.contains(sel.anchorNode!)) return;\n const enclosing = findEnclosingParamId();\n const editingId = opts.store.get().editingParam?.id ?? null;\n if (enclosing && enclosing !== editingId) {\n opts.startEditingParam(enclosing);\n return;\n }\n if (performance.now() - lastInputAt < 50) {\n // Just-typed: caret move handled by handleCaretAfterInput above.\n return;\n }\n opts.handleCaretMove(getCursorOffset(input));\n },\n { signal },\n );\n\n input.addEventListener(\n \"compositionstart\",\n () => {\n composing = true;\n },\n { signal },\n );\n input.addEventListener(\n \"compositionend\",\n () => {\n composing = false;\n fireInput();\n },\n { signal },\n );\n\n input.addEventListener(\n \"beforeinput\",\n (e) => {\n const inputEvent = e as InputEvent;\n const t = inputEvent.inputType;\n if (t === \"insertParagraph\" || t === \"insertLineBreak\" || t === \"insertFromDrop\") {\n e.preventDefault();\n return;\n }\n // Re-edit mode atomic-replace: while the edited param's `<strong>` is\n // still in the DOM, swap the whole thing for whatever the user is\n // about to insert (or empty, for deletes). Without this, typing or\n // backspace would land inside the bold span instead of replacing it.\n if (t.startsWith(\"insert\") || t.startsWith(\"delete\")) {\n const replacement = t.startsWith(\"delete\") ? \"\" : (inputEvent.data ?? \"\");\n if (opts.replaceEditingRange(replacement)) {\n e.preventDefault();\n }\n }\n },\n { signal },\n );\n\n input.addEventListener(\n \"paste\",\n (e) => {\n e.preventDefault();\n const text = (e.clipboardData?.getData(\"text/plain\") ?? \"\").replace(/\\r?\\n/g, \" \");\n if (!text) return;\n const doc = input.ownerDocument ?? document;\n const sel = doc.getSelection();\n if (!sel || sel.rangeCount === 0) return;\n const range = sel.getRangeAt(0);\n if (!input.contains(range.startContainer)) return;\n range.deleteContents();\n const node = doc.createTextNode(text);\n range.insertNode(node);\n range.setStartAfter(node);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n fireInput();\n },\n { signal },\n );\n\n input.addEventListener(\"keydown\", (e) => opts.handleKeyDown(e), { signal });\n\n input.addEventListener(\"focus\", () => opts.store.set({ isFocused: true }), { signal });\n input.addEventListener(\"blur\", () => opts.store.set({ isFocused: false }), { signal });\n\n if (submitTarget) {\n submitTarget.addEventListener(\n \"click\",\n (e) => {\n const state = opts.store.get();\n const canSubmit = !!state.text || state.completedParams.length > 0;\n if (!canSubmit || !opts.onSubmit) return;\n e.stopPropagation();\n const { rawQuery, completedParams: finalParams } = buildQuery(\n state.text,\n state.completedParams,\n );\n opts.onSubmit({\n query: state.text.trim(),\n raw_query: rawQuery,\n completed_params: finalParams,\n });\n opts.afterSubmit?.();\n },\n { signal },\n );\n }\n\n if (opts.autoFocus !== false) {\n input.focus();\n }\n\n // Width changes on the editor (window resize, container resize) can flip\n // whether the inline pill list still fits on the editor's last line. Re-run\n // the measurement so the margin attribute stays in sync without typing.\n if (typeof ResizeObserver !== \"undefined\") {\n const ro = new ResizeObserver(() => measurePillWrap(input, inlinePillContainer));\n ro.observe(input);\n abort.signal.addEventListener(\"abort\", () => ro.disconnect(), { once: true });\n }\n\n return { input, inlinePillContainer, dropdown, submitButton, abort };\n}\n\nexport function updateDOM(refs: DOMRefs, state: CoreState, opts: RenderInputOptions) {\n const { input, inlinePillContainer, dropdown, submitButton } = refs;\n const { pillPlacement, setActivePill, selectOption, store } = opts;\n\n input.setAttribute(\"aria-expanded\", String(state.isDropdownOpen));\n const activeDescendant =\n state.activeDropdownIndex >= 0 ? `${opts.listboxId}-option-${state.activeDropdownIndex}` : \"\";\n if (activeDescendant) {\n input.setAttribute(\"aria-activedescendant\", activeDescendant);\n } else {\n input.removeAttribute(\"aria-activedescendant\");\n }\n\n if (submitButton) {\n const canSubmit = !!state.text || state.completedParams.length > 0;\n submitButton.disabled = !canSubmit;\n }\n\n // Detect a fresh option selection BEFORE renderEditableContent mutates the\n // dataset. `newParamId` is set by selectOption() each time a suggestion\n // becomes a completed param. We compare against the last id the DOM saw so\n // the focus/caret jump only fires once per selection (not on every render\n // during the 650ms shimmer window).\n const previousParamId = input.dataset.newParamId ?? \"\";\n const justSelected = state.newParamId !== null && state.newParamId !== previousParamId;\n\n renderEditableContent({\n input,\n segments: state.segments,\n newParamId: state.newParamId,\n editingParamId: state.editingParam?.id ?? null,\n placeholderText: state.placeholderText,\n isFocused: state.isFocused,\n });\n\n if (pillPlacement === \"inline\") {\n const inlineLoading = state.isLoading && !state.editingParam && !state.inSelectionAnimation;\n if (inlineLoading || state.actionableSuggestions.length > 0) {\n renderPills(\n inlinePillContainer,\n state.actionableSuggestions,\n 0,\n setActivePill,\n false,\n inlineLoading,\n state.isActivePillSelected,\n );\n } else {\n clearPills(inlinePillContainer);\n }\n } else {\n clearPills(inlinePillContainer);\n }\n // After pill content changes (and text changes via renderEditableContent\n // above), re-evaluate whether the pill list has wrapped to a new line.\n measurePillWrap(input, inlinePillContainer);\n\n if (justSelected) {\n // After option selection, focus jumped to the clicked dropdown option\n // (or stayed on the editor via the dropdown's mousedown preventDefault).\n // Either way, bring focus to the editable and park the caret at the\n // position the promoter chose — typically right after the new param's\n // trailing space. Falls back to end-of-input for safety.\n input.focus();\n setCursorOffset(input, state.caretOffset ?? state.text.length);\n } else if (state.isFocused) {\n // Controlled-mode setValue / programmatic reset: keep the caret at the\n // end when DOM text diverges from state. Skip when not focused so we\n // don't steal focus on unrelated state churn.\n const domText = extractPlainText(input);\n if (domText !== state.text) {\n setCursorOffset(input, state.text.length);\n }\n }\n\n // In re-edit mode, the dropdown's pill bar shows a synthetic pill built\n // from the edited param's cached suggestion metadata (regardless of the\n // latest server suggestions). Inline pills are unaffected — they still\n // reflect the live actionable suggestions.\n const dropdownPill: Suggestion | null = state.editingParam\n ? {\n type: state.editingParam.suggestionType,\n text: state.editingParam.suggestionPlaceholder,\n required: true,\n options: state.editingParam.options,\n }\n : null;\n const dropdownActivePill = dropdownPill ?? state.actionableSuggestions[0];\n\n renderDropdown(dropdown, {\n suggestions: dropdownActivePill\n ? [{ ...dropdownActivePill, options: state.filteredOptions }]\n : [],\n filteredOptions: state.filteredOptions,\n activeIndex: state.activeDropdownIndex,\n isOpen: state.isDropdownOpen,\n // Re-edit shows cached options, and the streak animation finishes before\n // we swap in the skeleton.\n isLoading: state.isLoading && !state.editingParam && !state.inSelectionAnimation,\n listboxId: opts.listboxId,\n pills: dropdownPill ? [dropdownPill] : state.actionableSuggestions,\n showPills: pillPlacement === \"dropdown\",\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n onSelect: selectOption,\n onHighlight: (i) => store.set({ activeDropdownIndex: i }),\n onPillClick: setActivePill,\n });\n}\n","import type { CompletedParamState, Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreInputState } from \"../types\";\nimport { findPrefixOverlap } from \"../utils/filtering\";\n\nexport interface SelectionInputs {\n text: string;\n completedParams: CompletedParamState[];\n filterBase: number;\n filteredOptions: SuggestionOption[];\n actionableSuggestions: Suggestion[];\n placeholderText: string;\n}\n\nexport interface SelectionTelemetry {\n selectedOption: string;\n otherOptions: string[];\n}\n\nexport interface SelectionResult {\n patch: Partial<CoreInputState>;\n telemetry: SelectionTelemetry;\n /** The suggestion being consumed by this selection (filtered out post-animation when more remain). */\n consumedSuggestion: Suggestion;\n /** Number of actionable suggestions left after this one is consumed. */\n remainingActionable: number;\n}\n\n// Pure: non-edit-mode option selection → patch + telemetry + follow-up; null when no active suggestion.\nexport function computeSelectionPatch(\n inputs: SelectionInputs,\n option: SuggestionOption,\n): SelectionResult | null {\n const activeSuggestion = inputs.actionableSuggestions[0];\n if (!activeSuggestion) return null;\n\n const base = inputs.filterBase;\n let prefix = inputs.text.slice(0, base);\n\n const inputWasEmpty = prefix.length === 0 && inputs.text.length === 0;\n // The user is still typing within the server-provided placeholder (e.g.\n // typed \"Cre\" for placeholder \"Create a\"). Selecting an option here should\n // keep the placeholder rather than discard it — otherwise tapping \"email\"\n // replaces \"Cre\" with just \"email\" instead of \"Create a email\".\n const inputIsPlaceholderPrefix =\n prefix.length === 0 &&\n inputs.text.length > 0 &&\n inputs.placeholderText.length > 0 &&\n inputs.placeholderText.toLowerCase().startsWith(inputs.text.toLowerCase());\n if ((inputWasEmpty || inputIsPlaceholderPrefix) && inputs.placeholderText) {\n prefix = `${inputs.placeholderText} `;\n }\n\n const overlapChars = findPrefixOverlap(prefix, option.text);\n if (overlapChars > 0) {\n prefix = prefix.slice(0, prefix.length - overlapChars);\n }\n\n const needsSpace = prefix.length > 0 && prefix[prefix.length - 1] !== \" \";\n const newText = `${prefix}${needsSpace ? \" \" : \"\"}${option.text} `;\n const finalText =\n (inputWasEmpty || inputIsPlaceholderPrefix) && newText.length > 0\n ? newText[0].toUpperCase() + newText.slice(1)\n : newText;\n\n // Preserve the case of the option as it ends up in the final text (matters\n // when capitalize-first-letter rewrote it) so deriveSegments (case-sensitive\n // indexOf) can locate it.\n const optionStart = finalText.toLowerCase().lastIndexOf(option.text.toLowerCase());\n const optionInFinal =\n optionStart >= 0 ? finalText.slice(optionStart, optionStart + option.text.length) : option.text;\n\n const completed: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: activeSuggestion.type,\n text: optionInFinal,\n kind: option.kind,\n suggestionType: activeSuggestion.type,\n suggestionPlaceholder: activeSuggestion.text,\n options: activeSuggestion.options ?? [],\n metadata: option.metadata,\n };\n\n const remainingActionable = inputs.actionableSuggestions.length - 1;\n\n return {\n patch: {\n text: finalText,\n filterBase: finalText.length,\n completedParams: [...inputs.completedParams, completed],\n newParamId: completed.id,\n caretOffset: finalText.length,\n pillTapped: false,\n activeDropdownIndex: -1,\n skipNextFetch: remainingActionable > 0,\n inSelectionAnimation: true,\n },\n telemetry: {\n selectedOption: option.text,\n otherOptions: inputs.filteredOptions.filter((o) => o.text !== option.text).map((o) => o.text),\n },\n consumedSuggestion: activeSuggestion,\n remainingActionable,\n };\n}\n","export type Listener<S> = (next: S, prev: S) => void;\n\nexport interface Store<S> {\n get: () => S;\n set: (patch: Partial<S> | ((s: S) => Partial<S>)) => void;\n subscribe: (listener: Listener<S>) => () => void;\n}\n\nexport function createStore<S>(initial: S): Store<S> {\n let state = initial;\n const listeners = new Set<Listener<S>>();\n return {\n get: () => state,\n set: (patch) => {\n const resolved = typeof patch === \"function\" ? patch(state) : patch;\n const next = { ...state, ...resolved };\n const prev = state;\n state = next;\n for (const l of listeners) l(next, prev);\n },\n subscribe: (listener) => {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n };\n}\n\n// Narrows set() to Partial<I> only — derived fields are read-only on the\n// store. Callbacks still receive I & D so they can branch on derived state.\nexport interface DerivedStore<I, D> {\n get: () => I & D;\n set: (patch: Partial<I> | ((s: I & D) => Partial<I>)) => void;\n subscribe: (listener: Listener<I & D>) => () => void;\n /**\n * Compute the full (inputs + derived) state as if `patch` were applied to the\n * current inputs, WITHOUT mutating the store or notifying subscribers. Lets a\n * caller read derived fields that depend on a pending write (e.g. the filtered\n * options for a not-yet-committed pill reorder) so it can fold the result back\n * into a single `set()` — avoiding an intermediate notification that would\n * render a stale value.\n */\n peek: (patch: Partial<I>) => I & D;\n}\n\n// Wrap an input-only store with lazily-computed derived fields; cache invalidates on each set.\nexport function createDerivedStore<I extends object, D extends object>(\n base: Store<I>,\n derive: (inputs: I) => D,\n): DerivedStore<I, D> {\n let cachedInputs: I | undefined;\n let cachedDerived: D | undefined;\n\n const deriveCached = (inputs: I): D => {\n if (inputs !== cachedInputs) {\n cachedInputs = inputs;\n cachedDerived = derive(inputs);\n }\n return cachedDerived as D;\n };\n\n return {\n get: () => {\n const inputs = base.get();\n return { ...inputs, ...deriveCached(inputs) } as I & D;\n },\n set: (patch) => {\n // For function-form patches, materialize the full (inputs + derived) state\n // so the callback sees the same shape that `get()` returns — otherwise\n // `s.filteredOptions` etc. would be undefined at runtime despite type-checking.\n if (typeof patch === \"function\") {\n base.set((inputs) => {\n const full = { ...inputs, ...deriveCached(inputs) } as I & D;\n return patch(full);\n });\n } else {\n base.set(patch);\n }\n },\n // Derive on raw `derive` (not the cached path) so a hypothetical peek never\n // pollutes the single-slot memo with inputs that were never committed.\n peek: (patch) => {\n const inputs = { ...base.get(), ...patch } as I;\n return { ...inputs, ...derive(inputs) } as I & D;\n },\n // `prev` order intentional: prev for this notification was last notification's\n // `next` and is still in the single-slot cache → free hit. Computing `next`\n // after warms the cache for the next notification's prev.\n subscribe: (listener) =>\n base.subscribe((next, prev) => {\n const prevFull = { ...prev, ...deriveCached(prev) } as I & D;\n const nextFull = { ...next, ...deriveCached(next) } as I & D;\n listener(nextFull, prevFull);\n }),\n };\n}\n","let injected = false;\n\n/** Inject core + appearance CSS into document.head once. Idempotent. */\nexport function injectStyles() {\n if (injected || typeof document === \"undefined\") return;\n if (document.querySelector(\"style[data-magicx-aia]\")) {\n injected = true;\n return;\n }\n injected = true;\n\n const style = document.createElement(\"style\");\n style.setAttribute(\"data-magicx-aia\", \"\");\n style.textContent = STYLES;\n document.head.appendChild(style);\n}\n\n// Replaced at build time by tsup.config.core.ts / vitest.config.ts\ndeclare const __MAGICX_AC_STYLES__: string;\nconst STYLES = __MAGICX_AC_STYLES__;\n","type EventMap = Record<string, readonly unknown[]>;\ntype Listener<Args extends readonly unknown[]> = (...args: Args) => void;\n\n// Typed multi-listener emitter — on() adds, returned fn removes that listener, emit() fans out.\nexport class Emitter<E extends EventMap> {\n private listeners: { [K in keyof E]?: Set<Listener<E[K]>> } = {};\n\n on<K extends keyof E>(event: K, listener: Listener<E[K]>): () => void {\n // Wrap each registration in its own closure so the same `listener` reference\n // registered twice gets two independent Set slots (and unsubscribing one\n // doesn't take down the other).\n const entry: Listener<E[K]> = (...args) => listener(...args);\n let set = this.listeners[event];\n if (!set) {\n set = new Set();\n this.listeners[event] = set;\n }\n set.add(entry);\n return () => {\n this.listeners[event]?.delete(entry);\n };\n }\n\n emit<K extends keyof E>(event: K, ...args: E[K]): void {\n const set = this.listeners[event];\n if (!set) return;\n for (const listener of set) listener(...args);\n }\n\n hasListeners<K extends keyof E>(event: K): boolean {\n return (this.listeners[event]?.size ?? 0) > 0;\n }\n\n clear(): void {\n this.listeners = {};\n }\n}\n","// Keyed timer registry — schedule() auto-cancels the prior timer under the same key; clearAll() on destroy.\nexport class TimerScheduler {\n private timers = new Map<string, ReturnType<typeof setTimeout>>();\n\n schedule(key: string, fn: () => void, ms: number): void {\n this.clear(key);\n const id = setTimeout(() => {\n this.timers.delete(key);\n fn();\n }, ms);\n this.timers.set(key, id);\n }\n\n clear(key: string): void {\n const id = this.timers.get(key);\n if (id !== undefined) {\n clearTimeout(id);\n this.timers.delete(key);\n }\n }\n\n clearAll(): void {\n for (const id of this.timers.values()) clearTimeout(id);\n this.timers.clear();\n }\n}\n","import type { AppearanceMode } from \"../shared-types\";\n\nexport class ModeController {\n private mediaQuery: MediaQueryList | null = null;\n\n constructor(\n private container: HTMLElement,\n private mode: AppearanceMode = \"auto\",\n // Optional: invoked with the concrete resolved mode whenever it changes\n // (initial apply, setMode, or a prefers-color-scheme switch in \"auto\").\n // Lets a framework wrapper mirror the value into its own state — e.g. the\n // Angular component keeps an `[attr.data-mode]` binding in sync so the\n // attribute is also present during SSR, where this controller never runs.\n private onResolve?: (resolved: \"light\" | \"dark\") => void,\n ) {\n this.apply();\n }\n\n setMode(mode: AppearanceMode) {\n this.detachListener();\n this.mode = mode;\n this.apply();\n }\n\n destroy() {\n this.detachListener();\n }\n\n private apply() {\n if (this.mode === \"auto\") {\n this.mediaQuery ??= window.matchMedia(\"(prefers-color-scheme: dark)\");\n this.mediaQuery.addEventListener(\"change\", this.onSystemChange);\n this.setResolved(this.mediaQuery.matches ? \"dark\" : \"light\");\n } else {\n this.setResolved(this.mode);\n }\n }\n\n private onSystemChange = (e: MediaQueryListEvent) => {\n this.setResolved(e.matches ? \"dark\" : \"light\");\n };\n\n private setResolved(resolved: \"light\" | \"dark\") {\n this.container.dataset.mode = resolved;\n this.onResolve?.(resolved);\n }\n\n private detachListener() {\n this.mediaQuery?.removeEventListener(\"change\", this.onSystemChange);\n }\n}\n","import type { APIConfig } from \"../shared-types\";\nimport {\n buildApiKeyAuthHeader,\n buildHeaders,\n DEFAULT_SUGGEST_ENDPOINT,\n getTokenManager,\n isAccessTokenConfig,\n} from \"./auth\";\n\nexport type TelemetrySource = \"full-sdk\" | \"headless-sdk\" | \"endpoint-direct\";\nexport type TelemetryType = \"pill\" | \"option\";\n\nexport interface TelemetryEvent {\n source: TelemetrySource;\n sessionId: string;\n type: TelemetryType;\n queryData: Record<string, unknown>;\n apiConfig?: APIConfig;\n}\n\n/**\n * Telemetry sits next to /suggest on the server, so we derive the URL by\n * rewriting the trailing `/suggest` segment. This preserves whatever origin\n * and path-prefix the consumer configured — including relative dev-proxy\n * paths like \"/api/suggest\" → \"/api/telemetry/events\" or \"/ac/api/suggest\" →\n * \"/ac/api/telemetry/events\".\n */\nfunction deriveTelemetryEndpoint(suggestEndpoint?: string): string {\n const base = suggestEndpoint ?? DEFAULT_SUGGEST_ENDPOINT;\n return base.replace(/\\/suggest(\\?|#|$)/, \"/telemetry/events$1\");\n}\n\n/**\n * Resolves Authorization header using the same logic as /suggest:\n * - accessToken mode → Bearer from TokenManager\n * - apiKey mode → Bearer/Basic from configured key\n * - neither → null (request is sent without an Authorization header, matching\n * /suggest's behavior)\n */\nasync function resolveAuthHeader(apiConfig?: APIConfig): Promise<string | null> {\n if (isAccessTokenConfig(apiConfig)) {\n const token = await getTokenManager(apiConfig).getToken();\n return `Bearer ${token}`;\n }\n return buildApiKeyAuthHeader(apiConfig);\n}\n\n/**\n * Fire-and-forget telemetry POST. Never throws; failures are swallowed so the\n * UI is never disrupted by analytics. Auth headers are constructed via the\n * same helpers used by /suggest, so credential resolution stays consistent\n * across endpoints.\n */\nexport async function sendTelemetry(event: TelemetryEvent): Promise<void> {\n try {\n const endpoint = deriveTelemetryEndpoint(event.apiConfig?.endpoint);\n const headers = buildHeaders(event.apiConfig);\n const authHeader = await resolveAuthHeader(event.apiConfig);\n if (authHeader) headers.Authorization = authHeader;\n\n const body = JSON.stringify({\n source: event.source,\n session_id: event.sessionId,\n type: event.type,\n at: new Date().toISOString(),\n query_data: event.queryData,\n });\n\n await fetch(endpoint, { method: \"POST\", headers, body });\n } catch {\n // best-effort\n }\n}\n","import { FetchController } from \"./controllers/fetchController\";\nimport { KeyboardController } from \"./controllers/keyboardController\";\nimport { PillsController } from \"./controllers/pillsController\";\nimport { deriveAll } from \"./derive/state\";\nimport { previousGraphemeBoundary, setCursorOffset } from \"./dom/cursorUtils\";\nimport { tryPromoteExactMatch } from \"./promotion/promote\";\nimport { ReEditManager } from \"./reEdit/ReEditManager\";\nimport { buildDropdownOnly, updateDropdownOnly } from \"./render/renderDropdownOnly\";\nimport { buildDOM, type DOMRefs, updateDOM } from \"./render/renderInput\";\nimport { computeSelectionPatch } from \"./selection/SelectionFlow\";\nimport type {\n AppearanceMode,\n AutocompleteResult,\n CompletedParamState,\n SuggestionOption,\n} from \"./shared-types\";\nimport { createDerivedStore, createStore } from \"./state\";\nimport { injectStyles } from \"./styleInjector\";\nimport type { CoreInputState, CoreOptions, CoreState, RenderMode } from \"./types\";\nimport { Emitter } from \"./util/Emitter\";\nimport { TimerScheduler } from \"./util/TimerScheduler\";\nimport { buildQuery } from \"./utils/buildQuery\";\nimport { filterOptions } from \"./utils/filtering\";\nimport { ModeController } from \"./utils/modeController\";\nimport { reconcileParams } from \"./utils/segments\";\nimport { sendTelemetry, type TelemetrySource } from \"./utils/telemetry\";\n\nexport type AIAutocompleteEvents = {\n submit: [result: AutocompleteResult];\n error: [error: Error];\n change: [text: string];\n paramsChange: [params: CompletedParamState[]];\n stateChange: [state: CoreState];\n focus: [];\n blur: [];\n};\n\n// `update()` excludes event-listener props — they're registered once at construction.\n// Callers who need to swap a handler must use `on()` (and call the returned unsubscribe).\nexport type CoreUpdateOptions = Partial<\n Omit<\n CoreOptions,\n \"onSubmit\" | \"onError\" | \"onChange\" | \"onParamsChange\" | \"onStateChange\" | \"onFocus\" | \"onBlur\"\n >\n>;\n\nconst TIMER_NEW_PARAM = \"newParam\";\nconst TIMER_SUGGESTION_REMOVAL = \"suggestionRemoval\";\nconst TIMER_SELECTION_ANIMATION = \"selectionAnimation\";\nconst NEW_PARAM_SHIMMER_MS = 650;\n\nlet idCounter = 0;\nfunction stableId(): string {\n return `:ac-${++idCounter}:`;\n}\n\nconst SELECTION_ANIMATION_MS = 500;\n\nfunction initialInputs(): CoreInputState {\n return {\n text: \"\",\n completedParams: [],\n suggestions: [],\n activeDropdownIndex: -1,\n newParamId: null,\n isLoading: false,\n isReady: false,\n error: null,\n filterBase: 0,\n filterInProgress: false,\n pillTapped: false,\n skipNextFetch: false,\n lastRawQuery: \"\",\n isFocused: false,\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: null,\n inSelectionAnimation: false,\n };\n}\n\nexport class AIAutocomplete {\n private inputStore = createStore<CoreInputState>(initialInputs());\n private store: ReturnType<\n typeof createDerivedStore<CoreInputState, ReturnType<typeof deriveAll>>\n >;\n private _listboxId = stableId();\n private opts: CoreOptions;\n private fetchController: FetchController;\n private keyboardController: KeyboardController;\n private pillsController: PillsController;\n private reEdit: ReEditManager;\n private modeController: ModeController | null = null;\n private container: HTMLElement;\n private unsubscribers: (() => void)[] = [];\n private renderMode: RenderMode;\n private domRefs: DOMRefs | null = null;\n private dropdownRefs: { dropdown: HTMLElement } | null = null;\n private timers = new TimerScheduler();\n private emitter = new Emitter<AIAutocompleteEvents>();\n private sessionId: string = crypto.randomUUID();\n\n // Stable dispatchers — bound once, handed to controllers / renderers when listeners exist.\n // Avoids allocating a fresh arrow per `getOnSubmit()` / error-getter call.\n private readonly emitSubmit = (result: AutocompleteResult) => this.emitter.emit(\"submit\", result);\n private readonly emitError = (err: Error) => this.emitter.emit(\"error\", err);\n\n constructor(container: HTMLElement, opts: CoreOptions = {}) {\n this.container = container;\n this.opts = opts;\n this.renderMode = opts.renderMode ?? \"full\";\n\n // Wrap the raw input store with a lazily-derived layer. `derive` closes\n // over `this.opts` so changes via `update()` are picked up on the next\n // get() (a fresh `store.set({})` invalidates the memoization).\n this.store = createDerivedStore(this.inputStore, (inputs) => deriveAll(inputs, this.opts));\n\n // Register any opts.on* callbacks as the initial listener set. Use `on()`\n // after construction for additional or replacement listeners — `update()`\n // does NOT swap these (intentional, since stable proxies from React refs\n // depend on it).\n if (opts.onSubmit) this.emitter.on(\"submit\", opts.onSubmit);\n if (opts.onError) this.emitter.on(\"error\", opts.onError);\n if (opts.onChange) this.emitter.on(\"change\", opts.onChange);\n if (opts.onParamsChange) this.emitter.on(\"paramsChange\", opts.onParamsChange);\n if (opts.onStateChange) this.emitter.on(\"stateChange\", opts.onStateChange);\n if (opts.onFocus) this.emitter.on(\"focus\", opts.onFocus);\n if (opts.onBlur) this.emitter.on(\"blur\", opts.onBlur);\n\n // Apply controlled initial values\n if (opts.value !== undefined) {\n this.store.set({ text: opts.value });\n }\n if (opts.completedParams !== undefined) {\n this.store.set({ completedParams: opts.completedParams });\n }\n\n // Controllers\n this.pillsController = new PillsController(this.store, {\n onPillSelected: ({ rawQuery, selectedPill, otherPills }) => {\n this.fireTelemetry(\"pill\", {\n raw_query: rawQuery,\n selected_pill: selectedPill,\n other_pills: otherPills,\n });\n },\n });\n\n this.reEdit = new ReEditManager({\n store: this.store,\n scheduleSetCursor: (offset) => this.scheduleSetCursor(offset),\n fireTelemetry: (type, data) => this.fireTelemetry(type, data),\n startSelectionAnimationTimer: () => this.startSelectionAnimationTimer(),\n });\n\n this.fetchController = new FetchController(\n this.store,\n () => this.opts.apiConfig,\n () => this.opts.optionOverrides,\n () => this.opts.maskCompletedText,\n () => (this.emitter.hasListeners(\"error\") ? this.emitError : undefined),\n () => this.sessionId,\n {\n onAutoMatch: ({ active, matched, rawQuery }) => {\n this.fireTelemetry(\"option\", {\n raw_query: rawQuery,\n selected_option: matched.text,\n other_options: (active.options ?? [])\n .filter((o) => o.text !== matched.text)\n .map((o) => o.text),\n });\n },\n },\n );\n\n this.keyboardController = new KeyboardController(this.store, {\n columns: opts.columns ?? 2,\n listboxId: this.listboxId,\n getOnSubmit: () => (this.emitter.hasListeners(\"submit\") ? this.emitSubmit : undefined),\n getOptionsPosition: () => this.opts.optionsPosition ?? \"below\",\n // Tier 1 (full) auto-resets after submit; Tier 2/3 leave it to the consumer.\n afterSubmit: this.renderMode === \"full\" ? () => this.reset() : undefined,\n selectOption: (option) => this.selectOption(option),\n removeParamAtCaret: (offset) => this.removeParamAtCaret(offset),\n exitEditMode: () => this.exitEditMode(),\n });\n\n // Event callbacks (derived state materializes lazily through the wrapper —\n // no recompute subscriber needed).\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.text !== prev.text) this.emitter.emit(\"change\", next.text);\n if (next.completedParams !== prev.completedParams)\n this.emitter.emit(\"paramsChange\", next.completedParams);\n if (next.isFocused !== prev.isFocused) {\n if (next.isFocused) this.emitter.emit(\"focus\");\n else this.emitter.emit(\"blur\");\n }\n this.emitter.emit(\"stateChange\", next);\n }),\n );\n\n // Auto-exit re-edit + fire an immediate fetch when the user has typed past\n // their re-edited param so that the filtered options collapse to zero —\n // staying in re-edit with a dead filter just strands them in stale UI.\n this.unsubscribers.push(this.store.subscribe(() => this.maybeExitReEditOnNoMatch()));\n\n // Setup DOM based on render mode\n if (this.renderMode !== \"headless\") {\n injectStyles();\n this.setupContainer();\n }\n if (this.renderMode === \"full\") {\n this.buildAndRenderFull();\n } else if (this.renderMode === \"dropdown\") {\n this.buildAndRenderDropdown();\n }\n this.fetchController.start();\n }\n\n // === Public API ===\n\n focus() {\n this.domRefs?.input.focus();\n }\n\n blur() {\n this.domRefs?.input.blur();\n }\n\n reset() {\n // skipNextFetch suppresses the debounced fetch the FetchController would\n // otherwise schedule in response to text/params changing here — we issue\n // the empty fetch ourselves below so the new session starts immediately.\n // Preserve isFocused: when reset is triggered by an Enter-key submit the\n // DOM editor still has focus, and clearing the store flag would cause the\n // next renderEditableContent to skip caret restoration — leaving later\n // keystrokes inserted at offset 0 (looks like reversed, re-capitalized\n // typing).\n const wasFocused = this.store.get().isFocused;\n this.store.set({\n ...initialInputs(),\n isFocused: wasFocused,\n skipNextFetch: true,\n });\n this.sessionId = crypto.randomUUID();\n this.fetchController.doFetch(\"\", []);\n }\n\n destroy() {\n this.fetchController.dispose();\n this.modeController?.destroy();\n this.timers.clearAll();\n this.emitter.clear();\n for (const unsub of this.unsubscribers) unsub();\n this.unsubscribers = [];\n this.domRefs?.abort.abort();\n this.domRefs = null;\n this.dropdownRefs = null;\n if (this.renderMode !== \"headless\") {\n this.container.innerHTML = \"\";\n }\n }\n\n setMode(mode: AppearanceMode) {\n this.modeController?.setMode(mode);\n }\n\n setValue(text: string) {\n this.store.set({ text });\n }\n\n setCompletedParams(params: CompletedParamState[]) {\n this.store.set({ completedParams: params });\n }\n\n setActivePill(index: number) {\n this.pillsController.setActivePill(index);\n // Park the caret at the end of the text after a pill tap so the dropdown's\n // auto-trigger gate (caret-at-end) opens — and so continued typing filters\n // the newly-active pill's options instead of landing in the middle of a\n // prior token.\n const endOffset = this.store.get().text.length;\n this.store.set({ caretOffset: endOffset, isFocused: true });\n this.scheduleSetCursor(endOffset);\n }\n\n removeLastParam() {\n this.pillsController.removeLastParam();\n }\n\n /**\n * Backspace inside a completed param: drop the param's \"completed\" status so\n * it renders as plain (un-bold) text, AND remove one grapheme before the\n * caret — same single-character delete a normal Backspace would do. The\n * remaining text stays in the input so the user can continue editing what\n * they had typed instead of losing the whole phrase.\n *\n * Returns true when a param was reconciled (caller should `preventDefault`).\n */\n removeParamAtCaret(offset: number): boolean {\n const state = this.store.get();\n const { text, completedParams } = state;\n let pos = 0;\n for (let i = 0; i < completedParams.length; i++) {\n const param = completedParams[i];\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) continue;\n const paramStart = idx;\n const paramEnd = idx + param.text.length;\n if (offset > paramStart && offset <= paramEnd) {\n const deleteStart = previousGraphemeBoundary(text, offset);\n const newText = text.slice(0, deleteStart) + text.slice(offset);\n const newParams = completedParams.filter((_, j) => j !== i);\n this.store.set((s) => ({\n text: newText,\n filterBase: Math.min(s.filterBase, newText.length),\n completedParams: newParams,\n pillTapped: false,\n activeDropdownIndex: -1,\n }));\n this.scheduleSetCursor(deleteStart);\n return true;\n }\n pos = paramEnd;\n }\n return false;\n }\n\n /**\n * Set the editor caret at the given plain-text offset. Uses the core's own\n * `domRefs.input` in \"full\" mode; falls back to the wrapper-provided\n * `setCursor` callback in \"headless\" mode where the wrapper owns the DOM.\n *\n * Also focuses the input — setting a selection range without focus leaves a\n * visible caret that doesn't actually accept typing, and every caller (pill\n * tap, post-promote refocus, re-edit entry, Backspace-into-param) expects\n * the editor to be focused afterwards.\n */\n private scheduleSetCursor(offset: number) {\n queueMicrotask(() => {\n const refs = this.domRefs;\n if (refs) {\n refs.input.focus();\n setCursorOffset(refs.input, offset);\n } else {\n this.opts.setCursor?.(offset);\n }\n });\n }\n\n clearNewParamId() {\n this.store.set({ newParamId: null });\n }\n\n startEditingParam(paramId: string) {\n this.reEdit.start(paramId);\n }\n\n replaceEditingRange(replacement: string): boolean {\n return this.reEdit.replaceRange(replacement);\n }\n\n exitEditMode() {\n this.reEdit.exit();\n }\n\n handleCaretAfterInput(offset: number | null) {\n this.reEdit.caretAfterInput(offset);\n }\n\n handleCaretMove(offset: number | null) {\n this.reEdit.caretMove(offset);\n }\n\n setActiveDropdownIndex(index: number) {\n this.store.set({ activeDropdownIndex: index });\n }\n\n handleTextChange(value: string) {\n this.handleChange(value);\n }\n\n handleKeyDown(e: KeyboardEvent) {\n this.keyboardController.handleKeyDown(e);\n }\n\n setFocused(focused: boolean) {\n if (this.store.get().isFocused === focused) return;\n this.store.set({ isFocused: focused });\n }\n\n /** Subscribe to state changes. Listener receives the full (input + derived) shape. */\n subscribe(listener: (state: CoreState) => void): () => void {\n return this.store.subscribe((next) => listener(next));\n }\n\n getState(): CoreState {\n return this.store.get();\n }\n\n get listboxId(): string {\n return this._listboxId;\n }\n\n get isReady(): boolean {\n return this.store.get().isReady;\n }\n\n /**\n * Subscribe to an event. Multiple listeners may register for the same event;\n * `emit` fans out to all of them. The returned function removes only the\n * listener it registered.\n *\n * Note: `opts.on*` listeners passed at construction are equivalent to calling\n * `on()` once each; their unsubscribe handles are not exposed.\n */\n on<E extends keyof AIAutocompleteEvents>(\n event: E,\n callback: (...args: AIAutocompleteEvents[E]) => void,\n ): () => void {\n return this.emitter.on(event, callback);\n }\n\n update(opts: CoreUpdateOptions) {\n Object.assign(this.opts, opts);\n if (opts.mode !== undefined) {\n this.modeController?.setMode(opts.mode);\n }\n if (opts.optionsPosition !== undefined) {\n this.container.dataset.optionsPosition = opts.optionsPosition;\n }\n if (opts.animations !== undefined) {\n this.container.dataset.animations = opts.animations ? \"on\" : \"off\";\n }\n if (opts.pillPlacement !== undefined) {\n this.container.dataset.pillPlacement = opts.pillPlacement;\n this.store.set({});\n }\n if (\n opts.dropdownTrigger !== undefined ||\n opts.closeDropdownOnBlur !== undefined ||\n opts.showNonTappableOptions !== undefined\n ) {\n // Trigger recompute so isDropdownOpen / filteredOptions update\n this.store.set({});\n }\n if (opts.value !== undefined) {\n this.store.set({ text: opts.value });\n }\n if (opts.completedParams !== undefined) {\n this.store.set({ completedParams: opts.completedParams });\n }\n }\n\n // === Public (for framework wrappers) ===\n\n selectOption(option: SuggestionOption) {\n const state = this.store.get();\n\n // Re-edit path: replace the highlighted bold param's range with the new\n // option. Bypasses the normal selectOption flow entirely.\n if (state.editingParam && state.editingAnchor != null && state.editingTail != null) {\n this.reEdit.selectOption(option);\n return;\n }\n\n const result = computeSelectionPatch(state, option);\n if (!result) return;\n\n this.fireTelemetry(\"option\", {\n raw_query: buildQuery(state.text, state.completedParams).rawQuery,\n selected_option: result.telemetry.selectedOption,\n other_options: result.telemetry.otherOptions,\n });\n\n this.store.set(result.patch);\n this.startSelectionAnimationTimer();\n\n // Suggestion removal timing depends on whether a fetch is firing.\n this.timers.clear(TIMER_SUGGESTION_REMOVAL);\n if (result.remainingActionable > 0) {\n // Cached next-pill exists; no fetch fires. Remove the just-clicked\n // suggestion after the streak animation so the next pill becomes\n // active. The animation keeps playing on the option while it's still\n // in the DOM.\n const consumed = result.consumedSuggestion;\n this.timers.schedule(\n TIMER_SUGGESTION_REMOVAL,\n () => {\n this.store.set((s) => ({\n suggestions: s.suggestions.filter((sg) => sg !== consumed),\n }));\n },\n SELECTION_ANIMATION_MS,\n );\n }\n // When `remainingActionable === 0` a fetch fires. We deliberately keep\n // the just-selected suggestion in state until the response lands — the\n // dropdown then mirrors its pill/option layout (count + widths) as the\n // loading skeleton, instead of falling back to the generic placeholder.\n }\n\n private startSelectionAnimationTimer() {\n this.timers.schedule(\n TIMER_SELECTION_ANIMATION,\n () => this.store.set({ inSelectionAnimation: false }),\n SELECTION_ANIMATION_MS,\n );\n }\n\n private fireTelemetry(type: \"pill\" | \"option\", queryData: Record<string, unknown>) {\n // Vanilla consumers don't pass `source` — derive from renderMode.\n // The React hook always uses renderMode \"headless\" but Tier 1 explicitly\n // passes source: \"full-sdk\" via opts to override.\n const source: TelemetrySource =\n this.opts.source ?? (this.renderMode === \"full\" ? \"full-sdk\" : \"headless-sdk\");\n void sendTelemetry({\n source,\n sessionId: this.sessionId,\n type,\n queryData,\n apiConfig: this.opts.apiConfig,\n });\n }\n\n private setupContainer() {\n this.container.classList.add(\"magicx-aia\");\n // In dropdown mode, pills are always in the dropdown\n this.container.dataset.pillPlacement =\n this.renderMode === \"dropdown\" ? \"dropdown\" : (this.opts.pillPlacement ?? \"dropdown\");\n this.container.dataset.optionsPosition = this.opts.optionsPosition ?? \"below\";\n this.container.dataset.animations = (this.opts.animations ?? true) ? \"on\" : \"off\";\n\n // ModeController\n this.modeController = new ModeController(this.container, this.opts.mode ?? \"auto\");\n }\n\n private buildAndRenderFull() {\n const self = this;\n const renderOpts = {\n store: this.store,\n listboxId: this.listboxId,\n get pillPlacement() {\n return (self.opts.pillPlacement ?? \"dropdown\") as \"inline\" | \"dropdown\";\n },\n get onSubmit() {\n return self.emitter.hasListeners(\"submit\") ? self.emitSubmit : undefined;\n },\n // Tier 1 only (this code path runs only for renderMode === \"full\").\n afterSubmit: () => self.reset(),\n submitButton: this.opts.submitButton,\n autoFocus: this.opts.autoFocus ?? true,\n selectOption: (option: SuggestionOption) => this.selectOption(option),\n setActivePill: (index: number) => this.pillsController.setActivePill(index),\n handleKeyDown: (e: KeyboardEvent) => this.keyboardController.handleKeyDown(e),\n handleChange: (value: string) => this.handleChange(value),\n startEditingParam: (id: string) => this.startEditingParam(id),\n handleCaretAfterInput: (offset: number | null) => this.handleCaretAfterInput(offset),\n handleCaretMove: (offset: number | null) => this.handleCaretMove(offset),\n replaceEditingRange: (replacement: string) => this.replaceEditingRange(replacement),\n };\n\n this.domRefs = buildDOM(this.container, renderOpts);\n\n this.subscribeBatchedRender(() => {\n if (this.domRefs) {\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n }\n });\n\n // Initial render\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n this.subscribeNewParamTimer();\n }\n\n private buildAndRenderDropdown() {\n const dropdownOpts = {\n store: this.store,\n listboxId: this.listboxId,\n selectOption: (option: SuggestionOption) => this.selectOption(option),\n setActivePill: (index: number) => this.pillsController.setActivePill(index),\n };\n\n this.dropdownRefs = buildDropdownOnly(this.container, dropdownOpts);\n\n this.subscribeBatchedRender(() => {\n if (this.dropdownRefs) {\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n }\n });\n\n // Initial render\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n this.subscribeNewParamTimer();\n }\n\n /** Batched render subscriber — coalesces multiple store.set calls into one DOM update. */\n private subscribeBatchedRender(render: () => void) {\n let scheduled = false;\n this.unsubscribers.push(\n this.store.subscribe(() => {\n if (scheduled) return;\n scheduled = true;\n queueMicrotask(() => {\n scheduled = false;\n render();\n });\n }),\n );\n }\n\n /** Auto-clear newParamId after shimmer animation. */\n private subscribeNewParamTimer() {\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.newParamId && next.newParamId !== prev.newParamId) {\n this.timers.schedule(\n TIMER_NEW_PARAM,\n () => this.store.set({ newParamId: null }),\n NEW_PARAM_SHIMMER_MS,\n );\n }\n }),\n );\n }\n\n private handleChange(newValue: string) {\n const state = this.store.get();\n this.store.set({\n text: newValue,\n pillTapped: false,\n activeDropdownIndex: -1,\n });\n\n const { valid, invalid } = reconcileParams(newValue, state.completedParams);\n if (invalid.length > 0) {\n this.store.set({ completedParams: valid });\n }\n\n this.maybePromoteExactMatch(newValue);\n }\n\n /**\n * In re-edit mode, once the user has typed enough that no *tappable* options\n * still match (non-tappable options are kept by filterOptions regardless of\n * the query, so they don't count as \"still matching\"), exit re-edit and\n * fire an immediate fetch so the dropdown swaps over to fresh server\n * suggestions instead of staying frozen on a dead filter.\n *\n * Guarded against re-entry: once we exit, editingParam is null and the\n * subscription early-returns on subsequent fires.\n */\n private maybeExitReEditOnNoMatch() {\n const s = this.store.get();\n if (!s.editingParam || s.editingAnchor == null) return;\n // Skip until the user has actually started typing — when the param is\n // still in completedParams, the editQuery is \"\" and matches everything.\n if (s.completedParams.some((p) => p.id === s.editingParam?.id)) return;\n const editCaret = s.caretOffset ?? s.editingAnchor;\n const editQuery = s.text.slice(s.editingAnchor, editCaret);\n const matched = filterOptions(s.editingParam.options, editQuery);\n if (matched.some((o) => o.is_tappable)) return;\n this.reEdit.exit();\n const { rawQuery, completedParams } = buildQuery(s.text, s.completedParams);\n this.fetchController.doFetch(rawQuery, completedParams);\n }\n\n /**\n * When the user has typed text that exactly matches (case-insensitive) one\n * of the active suggestion's options, promote it to a completed param right\n * away. The fetchController does the same check when the debounced fetch\n * lands; doing it instantly here means bold styling appears as soon as the\n * option is fully typed, without waiting 100–300ms for the round-trip.\n */\n private maybePromoteExactMatch(newValue: string) {\n const s = this.store.get();\n const result = tryPromoteExactMatch({\n mode: \"fresh\",\n text: newValue,\n completedParams: s.completedParams,\n suggestions: s.suggestions,\n filterBase: s.filterBase,\n filterInProgress: s.filterInProgress,\n });\n if (!result) return;\n this.store.set(result.patch);\n }\n}\n"],"mappings":"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AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","names":["TokenManager","config","forceRefresh","result","DEFAULT_API_ORIGIN","DEFAULT_SUGGEST_ENDPOINT","tokenManagers","isAccessTokenConfig","config","getApiKeyConfig","getTokenManager","manager","TokenManager","buildHeaders","apiConfig","buildApiKeyAuthHeader","apiKeyConfig","apiKey","SDK_VERSION","hasWarnedMissingKey","generateRequestId","toWireParam","param","includeText","buildRequestBody","rawQuery","completedParams","sessionId","rawCount","p","contactAccountCount","doFetch","endpoint","headers","token","body","signal","fetchSuggestions","options","apiConfig","buildHeaders","DEFAULT_SUGGEST_ENDPOINT","jsonBody","isAccessTokenConfig","manager","getTokenManager","response","newToken","authHeader","buildApiKeyAuthHeader","buildQuery","text","completedParams","result","typeCounts","updatedParams","param","count","placeholder","index","effectiveFilterBase","text","filterBase","placeholderText","extractFilterQuery","isInProgress","rawRegion","spaceIdx","findPrefixOverlap","prefix","optionText","trimmed","words","optionLower","i","candidate","suffixStart","filterOptions","options","query","lower","o","findExactMatch","applyOptionOverrides","suggestions","overrides","s","fn","extra","existingTexts","o","deduped","DEBOUNCE_MS","SLOW_DEBOUNCE_MS","MIN_CHARS_DIFF","FetchController","store","getApiConfig","getOptionOverrides","getMaskCompletedText","getOnError","getSessionId","callbacks","prevText","prevParams","next","rawQuery","completed","controller","version","textAtRequest","res","fetchSuggestions","newSuggestions","applyOptionOverrides","input","lastInput","currentText","filterBase","filterInProgress","inProgressIdx","active","s","extraParam","query","extractFilterQuery","match","findExactMatch","err","caughtError","attemptFetch","minDiff","placeholderText","sg","effBase","effectiveFilterBase","currentQuery","tappableFiltered","filterOptions","o","hasExactMatch","isInFilterZone","updatedParams","buildQuery","isDeleting","charDiff","NON_EDITABLE_SELECTOR","segmenter","getGraphemeSegmenter","Segmenter","isInsideNonEditable","node","root","n","createTextWalker","extractPlainText","walker","out","plainTextLength","total","getCursorOffset","sel","anchorNode","anchorOffset","el","offset","i","plainTextLengthOfSubtree","offsetBeforeNode","child","target","setCursorOffset","doc","clamped","cumulative","targetOffset","lastNode","len","next","range","strongParent","cursorIsAtEnd","previousGraphemeBoundary","text","seg","slice","last","index","isCursorAtEnd","target","state","cursorIsAtEnd","getEditableCaretOffset","getCursorOffset","KeyboardController","store","ctx","listboxId","getOnSubmit","columns","onSubmit","tappableIndices","above","cursorAtEnd","inEditMode","lastRow","currentPos","nextPos","initialIdx","prevPos","rightNeighbor","editor","tail","setCursorOffset","leftNeighbor","anchor","offset","rawQuery","finalParams","buildQuery","tappableOptionIndices","o","i","firstIdx","delta","bottomRowStart","tappable","buckets","listbox","el","gtc","tracks","index","options","optionEl","placeholders","actionable","remaining","PillsController","store","callbacks","index","state","actionable","s","moved","rest","_","i","placeholders","rawQuery","buildQuery","nextSuggestions","firstTappable","o","deriveSegments","text","completedParams","result","pos","param","idx","remaining","reconcileParams","valid","invalid","computeDropdownVisibility","inputs","opts","trigger","closeOnBlur","hasOptions","focusGate","trimmedEnd","caretAtEnd","deriveAll","inputs","opts","segments","deriveSegments","actionableSuggestions","s","activeSuggestion","overrideFn","placeholderText","clampedFilterBase","effectiveFilterBase","filterQuery","extractFilterQuery","baseOptions","inEditMode","filteredOptions","editingId","paramStillPresent","p","editCaret","editQuery","filterOptions","o","isDropdownOpen","computeDropdownVisibility","isActivePillSelected","tryPromoteExactMatch","ctx","promoteFresh","promoteEdit","text","completedParams","suggestions","filterBase","filterInProgress","active","sg","placeholderText","effBase","effectiveFilterBase","query","extractFilterQuery","match","findExactMatch","matchLower","optionStart","paramStart","paramEnd","optionInText","caretPos","completed","editingParam","editingAnchor","editingTail","p","editQuery","matchStart","newParam","insertAt","scanPos","i","idx","newParams","ReEditManager","deps","paramId","state","param","p","pos","anchor","idx","replacement","editing","tail","newText","newTail","s","offset","patch","option","buildQuery","o","before","after","optionText","needsTrailingSpace","caretPos","newParam","oldIdx","params","insertAt","result","tryPromoteExactMatch","getFooterHint","optionHighlighted","isInputEmpty","KEY_ATTR","reconcileList","parent","items","opts","existing","child","key","used","result","i","item","el","FALLBACK_SKELETON_WIDTHS","getPillOpacity","index","selected","renderPills","container","pills","activePillIndex","onSelectPill","rounded","loading","activeSelected","list","i","width","span","skel","reconcileList","pill","btn","e","el","_pill","classes","clearPills","fadeObservers","renderSuggestionGrid","parent","options","activeIndex","onSelect","onHighlight","listboxId","loading","wrapper","teardownFade","grid","setupFade","renderOptions","updateFade","onScroll","ro","entry","loadingFlag","reconcileList","opt","option","buildOptionElement","el","i","isHighlighted","idx","item","classes","streaks","streaksVert","content","text","tag","FALLBACK_SKELETON_BAR_WIDTHS","createDropdown","listboxId","dropdown","e","renderDropdown","state","filteredOptions","activeIndex","isOpen","isLoading","pills","showPills","isActivePillSelected","onSelect","onHighlight","onPillClick","hasPills","hasOptions","isVisible","stack","wantsPillBar","pillBar","renderPills","renderSuggestionGrid","skeleton","width","bar","footer","createFooter","optionHighlighted","updateFooterHint","getFooterHint","nextKey","nextHint","key","hint","divider","row","hintGroup","brandGroup","brand","badge","buildDropdownOnly","container","opts","dropdown","createDropdown","updateDropdownOnly","refs","state","renderDropdown","renderEditableContent","args","input","segments","newParamId","editingParamId","placeholderText","isFocused","empty","segKey","s","lastSegKey","lastNewParamId","lastEditingParamId","savedOffset","getCursorOffset","doc","frag","newLength","seg","strong","isNew","isEditing","classes","setCursorOffset","SUBMIT_SVG","createSubmitButton","btn","supportsPlaintextOnly","probe","measurePillWrap","input","container","inner","cRect","eRect","buildDOM","opts","listboxId","dropdown","createDropdown","inputWrapper","editor","inlinePillContainer","submitButton","submitTarget","createSubmitButton","abort","signal","composing","lastInputAt","fireInput","raw","extractPlainText","newValue","findEnclosingParamId","sel","anchor","e","getCursorOffset","doc","enclosing","editingId","inputEvent","t","replacement","text","range","node","state","rawQuery","finalParams","buildQuery","ro","updateDOM","refs","pillPlacement","setActivePill","selectOption","store","activeDescendant","canSubmit","previousParamId","justSelected","renderEditableContent","inlineLoading","renderPills","clearPills","setCursorOffset","dropdownPill","dropdownActivePill","renderDropdown","i","computeSelectionPatch","inputs","option","activeSuggestion","base","prefix","inputWasEmpty","inputIsPlaceholderPrefix","overlapChars","findPrefixOverlap","needsSpace","newText","finalText","optionStart","optionInFinal","completed","remainingActionable","o","createStore","initial","state","listeners","patch","resolved","next","prev","l","listener","createDerivedStore","base","derive","cachedInputs","cachedDerived","deriveCached","inputs","full","prevFull","nextFull","injected","injectStyles","style","STYLES","Emitter","event","listener","entry","args","set","TimerScheduler","key","fn","ms","id","ModeController","container","mode","onResolve","resolved","deriveTelemetryEndpoint","suggestEndpoint","DEFAULT_SUGGEST_ENDPOINT","resolveAuthHeader","apiConfig","isAccessTokenConfig","getTokenManager","buildApiKeyAuthHeader","sendTelemetry","event","endpoint","headers","buildHeaders","authHeader","body","TIMER_NEW_PARAM","TIMER_SUGGESTION_REMOVAL","TIMER_SELECTION_ANIMATION","NEW_PARAM_SHIMMER_MS","idCounter","stableId","SELECTION_ANIMATION_MS","initialInputs","AIAutocomplete","container","opts","createStore","TimerScheduler","Emitter","result","err","createDerivedStore","inputs","deriveAll","PillsController","rawQuery","selectedPill","otherPills","ReEditManager","offset","type","data","FetchController","active","matched","KeyboardController","option","next","prev","injectStyles","wasFocused","unsub","mode","text","params","index","endOffset","state","completedParams","pos","i","param","idx","paramStart","paramEnd","deleteStart","previousGraphemeBoundary","newText","newParams","_","j","s","refs","setCursorOffset","paramId","replacement","value","focused","listener","event","callback","computeSelectionPatch","buildQuery","consumed","sg","queryData","source","sendTelemetry","ModeController","self","renderOpts","e","id","buildDOM","updateDOM","dropdownOpts","buildDropdownOnly","updateDropdownOnly","render","scheduled","newValue","valid","invalid","reconcileParams","p","editCaret","editQuery","filterOptions","o","tryPromoteExactMatch"]}
|