@magicx-eng/ai-autocomplete-vanilla 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/utils/tokenManager.ts","../src/utils/auth.ts","../src/utils/skippedParams.ts","../src/utils/api.ts","../src/utils/buildQuery.ts","../src/utils/filtering.ts","../src/utils/overrides.ts","../src/utils/pendingSpan.ts","../src/utils/segments.ts","../src/controllers/fetchController.ts","../src/dom/cursorUtils.ts","../src/utils/submitResult.ts","../src/controllers/keyboardController.ts","../src/controllers/pillsController.ts","../src/controllers/productsController.ts","../src/derive/optionsGridLayout.ts","../src/derive/dropdown.ts","../src/derive/state.ts","../src/promotion/promote.ts","../src/utils/chipSpan.ts","../src/reEdit/ReEditManager.ts","../src/utils/attribution.ts","../src/utils/footerHint.ts","../src/render/reconcileList.ts","../src/render/renderPills.ts","../src/render/renderProductStrip.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/consumerBoundary.ts","../src/util/Emitter.ts","../src/util/TimerScheduler.ts","../src/utils/modeController.ts","../src/utils/telemetry.ts","../src/AIAutocomplete.ts"],"sourcesContent":["// === Wire / shared types ===\n\n// === Core class ===\nexport { AIAutocomplete } from \"./AIAutocomplete\";\n// === Options-grid layout policy (shared by the React + Angular dropdowns) ===\nexport type { OptionsGridLayout } from \"./derive/optionsGridLayout\";\nexport {\n computeOptionsGridLayout,\n isOptionsGridMobileViewport,\n OPTIONS_GRID_MOBILE_QUERY,\n optionsGridTemplateColumns,\n} from \"./derive/optionsGridLayout\";\n// === Editor helpers (consumed by React's contentEditable shell) ===\nexport {\n cursorIsAtEnd,\n extractPlainText,\n getCursorOffset,\n plainTextLength,\n previousGraphemeBoundary,\n setCursorOffset,\n} from \"./dom/cursorUtils\";\nexport { renderEditableContent } from \"./render/renderEditable\";\nexport type {\n AccessTokenConfig,\n AccessTokenResult,\n APIConfig,\n APIKeyConfig,\n AppearanceMode,\n AutocompleteRequest,\n AutocompleteResponse,\n AutocompleteResult,\n CompletedParam,\n CompletedParamState,\n IdentifiedParam,\n IdentifiedParamState,\n InputItem,\n OptionOverrides,\n Product,\n ProductsConfig,\n RecentlySuggested,\n Segment,\n SkippedParamState,\n Suggestion,\n SuggestionOption,\n TaskKind,\n} from \"./shared-types\";\n// === Store primitives ===\nexport type { Store } from \"./state\";\nexport { createStore } from \"./state\";\n// === Core types ===\nexport type { CoreOptions, CoreState, RenderMode } from \"./types\";\n\n// === Utility helpers (consumed by React's Tier 1 component) ===\nexport { ATTRIBUTION_URL, buildAttributionUrl } from \"./utils/attribution\";\nexport { buildQuery } from \"./utils/buildQuery\";\nexport { getFooterHint } from \"./utils/footerHint\";\nexport { ModeController } from \"./utils/modeController\";\nexport { SKIPPED_PARAM_TEXT, withSkippedParams } from \"./utils/skippedParams\";\nexport { buildSubmitResult } from \"./utils/submitResult\";\n","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 { CompletedParam, SkippedParamState } from \"../shared-types\";\n\n/**\n * Sentinel `text` marking a `completed_params` entry the user skipped (→)\n * rather than filled. Sent regardless of `maskCompletedText` — it's a fixed\n * marker, never user-entered content.\n */\nexport const SKIPPED_PARAM_TEXT = \"skipped\";\n\n/**\n * Folds skipped suggestions into a wire `completed_params` array so the server\n * learns which parameters the user dismissed and can stop re-suggesting them.\n *\n * Skipped entries carry no placeholder: nothing was substituted into\n * `raw_query`, so a `{{TYPE_N}}` token would point at text that doesn't exist.\n * They're appended after the real params for the same reason — they have no\n * position in the query.\n *\n * A skip is dropped when a param of the same type ends up filled anyway (the\n * user skipped `goal`, then typed one): sending both would tell the server the\n * parameter is simultaneously answered and declined.\n */\nexport function withSkippedParams(\n completed: CompletedParam[],\n skipped: SkippedParamState[],\n): CompletedParam[] {\n if (skipped.length === 0) return completed;\n const filledTypes = new Set(completed.map((p) => p.type));\n const entries = skipped\n .filter((p) => !filledTypes.has(p.type))\n .map<CompletedParam>((p) => ({\n placeholder: \"\",\n type: p.type,\n text: SKIPPED_PARAM_TEXT,\n kind: null,\n }));\n return entries.length > 0 ? [...completed, ...entries] : completed;\n}\n","import type {\n APIConfig,\n AutocompleteRequest,\n AutocompleteResponse,\n CompletedParam,\n CompletedParamState,\n IdentifiedParamState,\n RecentlySuggested,\n SkippedParamState,\n} from \"../shared-types\";\nimport {\n buildApiKeyAuthHeader,\n buildHeaders,\n DEFAULT_SUGGEST_ENDPOINT,\n getTokenManager,\n isAccessTokenConfig,\n} from \"./auth\";\nimport { withSkippedParams } from \"./skippedParams\";\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 identifiedParams?: IdentifiedParamState[],\n recentlySuggested?: RecentlySuggested[],\n skippedParams?: SkippedParamState[],\n additionalContext?: Record<string, unknown>,\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 // Skipped suggestions ride in the same array, appended, marked with\n // `text: \"skipped\"` — see `withSkippedParams`.\n completed_params: withSkippedParams(\n completedParams.map((p) => toWireParam(p, includeText)),\n skippedParams ?? [],\n ),\n ...(identifiedParams &&\n identifiedParams.length > 0 && {\n identified_params: identifiedParams.map((p) => ({ type: p.type, value: p.text })),\n }),\n ...(recentlySuggested &&\n recentlySuggested.length > 0 && {\n recently_suggested: recentlySuggested,\n }),\n ...(contactAccountCount != null && { contact_account_count: contactAccountCount }),\n ...(additionalContext !== undefined && { additional_context: additionalContext }),\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 /** Echo of the latest response's identified set. Omitted from the body when empty. */\n identifiedParams?: IdentifiedParamState[];\n /** Pending-span hint: suggestions on screen when the unresolved trailing text began. Omitted when absent/empty. */\n recentlySuggested?: RecentlySuggested[];\n /** Suggestions the user skipped (→). Appended to `completed_params` as `text: \"skipped\"`. */\n skippedParams?: SkippedParamState[];\n /** Optional user context, included to personalize suggested parameters and options. */\n additionalContext?: Record<string, unknown>;\n },\n): Promise<AutocompleteResponse> {\n const apiConfig = options.apiConfig;\n const includeText = !options.maskCompletedText;\n const body = buildRequestBody(\n rawQuery,\n completedParams,\n includeText,\n options.sessionId,\n options.identifiedParams,\n options.recentlySuggested,\n options.skippedParams,\n options.additionalContext,\n );\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 advance a position cursor (params are appended in text order in\n * normal flows), so a short param value (e.g. quantity \"1\") can never match\n * INSIDE an already-inserted placeholder (e.g. the \"1\" in \"{{SIZE_1}}\") and\n * splice into it. Out-of-order params fall back to a from-zero rescan that\n * rejects any match overlapping a previously inserted placeholder; with no\n * clean match the param is left unreplaced (same as the text-not-found path).\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 const insertedRanges: { start: number; end: number }[] = [];\n let pos = 0;\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 // Find the first occurrence at/after `from` that doesn't overlap a\n // previously inserted placeholder.\n const findClean = (from: number): number => {\n let idx = result.indexOf(param.text, from);\n while (\n idx !== -1 &&\n insertedRanges.some((r) => idx < r.end && idx + param.text.length > r.start)\n ) {\n idx = result.indexOf(param.text, idx + 1);\n }\n return idx;\n };\n\n // Cursor search first; from-zero fallback for out-of-order params.\n let index = findClean(pos);\n if (index === -1) index = findClean(0);\n\n if (index !== -1) {\n result = result.slice(0, index) + placeholder + result.slice(index + param.text.length);\n const delta = placeholder.length - param.text.length;\n // A clean match never overlaps an inserted range, so every range is\n // entirely before or entirely after the match — shift the latter.\n for (const r of insertedRanges) {\n if (r.start >= index + param.text.length) {\n r.start += delta;\n r.end += delta;\n }\n }\n insertedRanges.push({ start: index, end: index + placeholder.length });\n // In-order match: advance the cursor past the insertion. Fallback match\n // before the cursor: keep the cursor at the same logical position,\n // shifted by the length change.\n pos = index >= pos ? index + placeholder.length : pos + delta;\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 * True while the user is still typing a prefix of the server-provided\n * placeholder and hasn't completed a param yet. In that window the typed text\n * is the user retyping the suggested lead-in, NOT a filter query — so the\n * dropdown must not filter by it and the scheduler must not fetch on it.\n *\n * Shared by `deriveAll` and `FetchController.scheduleFetch` so the two can't\n * disagree about whether the user is filtering.\n */\nexport function isTypingPlaceholderPrefix(\n text: string,\n completedParamCount: number,\n placeholderText: string,\n): boolean {\n return (\n completedParamCount === 0 &&\n text.length > 0 &&\n placeholderText.length > 0 &&\n placeholderText.toLowerCase().startsWith(text.toLowerCase())\n );\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 { SafeOptionOverrides, Suggestion } from \"../shared-types\";\n\n/**\n * Replaces a suggestion's server options with the consumer's override result\n * when an override function exists for the suggestion's type. The override is\n * invoked with an empty query — its unfiltered list — because the result is\n * stored as the suggestion's option set (exact-match promotion, fetch\n * suppression, and the re-edit cache all read it). Server options are ignored\n * entirely for overridden types, mirroring the derive layer, which never shows\n * them; an override returning [] means the suggestion genuinely has no options.\n */\nexport function applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: SafeOptionOverrides,\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 overridden = fn(\"\");\n // `undefined` only ever comes from the boundary containing a throw — a\n // successful override returning `[]` still means \"no options\" and is\n // honoured. Keep the server's options in that case rather than blanking\n // the suggestion on a consumer bug.\n return overridden ? { ...s, options: overridden } : s;\n });\n}\n","import type { RecentlySuggested, Segment, Suggestion } from \"../shared-types\";\n\n/**\n * The last text offset covered by server context or a pill: the maximum of\n * `filterBase` (callers pass the clamped/effective value) and the end offset\n * of the last completed/identified pill segment. Text past this offset is the\n * unresolved trailing text a pending span tracks.\n */\nexport function coveredEnd(segments: Segment[], filterBase: number): number {\n let pos = 0;\n let covered = filterBase;\n for (const seg of segments) {\n pos += seg.value.length;\n if (seg.type !== \"text\") covered = Math.max(covered, pos);\n }\n return covered;\n}\n\n/**\n * True when every non-whitespace character at/after `anchor` sits inside a\n * completed or identified pill segment — i.e. the span's trailing text has\n * been fully resolved.\n */\nexport function isTrailingCovered(segments: Segment[], anchor: number): boolean {\n let pos = 0;\n for (const seg of segments) {\n const end = pos + seg.value.length;\n if (seg.type === \"text\" && end > anchor) {\n const uncovered = seg.value.slice(Math.max(anchor - pos, 0));\n if (uncovered.trim().length > 0) return false;\n }\n pos = end;\n }\n return true;\n}\n\n/**\n * Builds the `recently_suggested` payload: span snapshot ∪ current on-screen\n * suggestions, snapshot entries first, deduped by type. Placeholder\n * suggestions are never actionable and are excluded from both sides.\n *\n * Deliberately uncapped: both sides are already bounded by what the server\n * chose to send in a single response, so the deduped union can't grow beyond\n * two responses' worth of suggestions. A client-side cap would only be a\n * second copy of the server's limit — one that silently clips the hint the\n * day the server raises it.\n */\nexport function buildRecentlySuggested(\n snapshot: Suggestion[],\n current: Suggestion[],\n): RecentlySuggested[] {\n const result: RecentlySuggested[] = [];\n const seen = new Set<string>();\n for (const s of [...snapshot, ...current]) {\n if (s.type === \"placeholder\" || seen.has(s.type)) continue;\n seen.add(s.type);\n result.push({ type: s.type, text: s.text });\n }\n return result;\n}\n\n/**\n * Re-bases a span anchor across a single contiguous text splice (typing,\n * re-edit replacement, Backspace-into-pill all splice one region):\n * - edit at/after the anchor → anchor unchanged\n * - edit entirely before the anchor → anchor shifted by the length delta\n * - edit straddling the anchor → `null` (the anchor can no longer be located;\n * callers should close the span rather than track a stale offset)\n */\nexport function rebaseAnchor(prevText: string, nextText: string, anchor: number): number | null {\n if (prevText === nextText) return anchor;\n const minLen = Math.min(prevText.length, nextText.length);\n let prefix = 0;\n while (prefix < minLen && prevText[prefix] === nextText[prefix]) prefix++;\n if (prefix >= anchor) return anchor;\n let suffix = 0;\n while (\n suffix < minLen - prefix &&\n prevText[prevText.length - 1 - suffix] === nextText[nextText.length - 1 - suffix]\n ) {\n suffix++;\n }\n // Edit region in the previous text: [prefix, prevText.length - suffix).\n if (prevText.length - suffix <= anchor) {\n return anchor + (nextText.length - prevText.length);\n }\n return null;\n}\n","import type { CompletedParamState, IdentifiedParamState, Segment } from \"../shared-types\";\n\ninterface CompletedInterval {\n start: number;\n end: number;\n param: CompletedParamState;\n}\n\ninterface IdentifiedInterval {\n start: number;\n end: number;\n param: IdentifiedParamState;\n}\n\n/**\n * Locates completed params in text left-to-right, first occurrence each,\n * advancing past every match — the shared walk behind deriveSegments and\n * reconcileParams.\n */\nfunction locateCompleted(text: string, completedParams: CompletedParamState[]) {\n const located: CompletedInterval[] = [];\n const missing: CompletedParamState[] = [];\n let pos = 0;\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) {\n missing.push(param);\n continue;\n }\n located.push({ start: idx, end: idx + param.text.length, param });\n pos = idx + param.text.length;\n }\n return { located, missing };\n}\n\n/**\n * Locates identified params in text left-to-right, skipping any occurrence\n * that overlaps completed-param coverage — identified params NEVER override\n * or overlap completed params. Params that don't cleanly locate are dropped.\n */\nfunction locateIdentified(\n text: string,\n completedIntervals: CompletedInterval[],\n identifiedParams: IdentifiedParamState[],\n) {\n const located: IdentifiedInterval[] = [];\n const missing: IdentifiedParamState[] = [];\n let pos = 0;\n for (const param of identifiedParams) {\n let idx = text.indexOf(param.text, pos);\n while (\n idx !== -1 &&\n completedIntervals.some((c) => idx < c.end && idx + param.text.length > c.start)\n ) {\n idx = text.indexOf(param.text, idx + 1);\n }\n if (idx === -1) {\n missing.push(param);\n continue;\n }\n located.push({ start: idx, end: idx + param.text.length, param });\n pos = idx + param.text.length;\n }\n return { located, missing };\n}\n\n/**\n * Derives segments for overlay rendering by matching completed params (then\n * identified params, in the remaining uncovered text) against the text.\n */\nexport function deriveSegments(\n text: string,\n completedParams: CompletedParamState[],\n identifiedParams: IdentifiedParamState[] = [],\n): Segment[] {\n const completed = locateCompleted(text, completedParams).located;\n const identified = locateIdentified(text, completed, identifiedParams).located;\n\n const pills: { start: number; end: number; segment: Segment }[] = [\n ...completed.map((c) => ({\n start: c.start,\n end: c.end,\n segment: { type: \"completed\", value: c.param.text, param: c.param } as Segment,\n })),\n ...identified.map((i) => ({\n start: i.start,\n end: i.end,\n segment: { type: \"identified\", value: i.param.text, param: i.param } as Segment,\n })),\n ].sort((a, b) => a.start - b.start);\n\n const result: Segment[] = [];\n let pos = 0;\n for (const pill of pills) {\n if (pill.start > pos) {\n result.push({ type: \"text\", value: text.slice(pos, pill.start) });\n }\n result.push(pill.segment);\n pos = pill.end;\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 { located, missing } = locateCompleted(text, completedParams);\n return { valid: located.map((l) => l.param), invalid: missing };\n}\n\n/**\n * Checks which identified params still locate in the new text outside\n * completed-param coverage. Mirrors reconcileParams for identified state:\n * a param whose text was edited away — or now only occurs inside a completed\n * param — is invalid and must be dropped (its plain text stays in the input).\n */\nexport function reconcileIdentifiedParams(\n text: string,\n completedParams: CompletedParamState[],\n identifiedParams: IdentifiedParamState[],\n): { valid: IdentifiedParamState[]; invalid: IdentifiedParamState[] } {\n const completed = locateCompleted(text, completedParams).located;\n const { located, missing } = locateIdentified(text, completed, identifiedParams);\n return { valid: located.map((l) => l.param), invalid: missing };\n}\n","import type {\n APIConfig,\n CompletedParamState,\n IdentifiedParamState,\n SafeOptionOverrides,\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 isTypingPlaceholderPrefix,\n} from \"../utils/filtering\";\nimport { applyOptionOverrides } from \"../utils/overrides\";\nimport { buildRecentlySuggested } from \"../utils/pendingSpan\";\nimport { reconcileIdentifiedParams } from \"../utils/segments\";\n\n/**\n * Total error coercion. `String(err)` can itself throw — a rejection value\n * whose `toString` throws — and this runs on the path that clears the loading\n * flag, so it must not be the thing that strands it.\n */\nfunction toError(err: unknown): Error {\n if (err instanceof Error) return err;\n try {\n return new Error(String(err));\n } catch {\n return new Error(\"Unknown error\");\n }\n}\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 * Fired once per outbound `/suggest` request, before it is awaited.\n *\n * The single point where side-channel work (today: the product strip) joins\n * the SDK's fetch cadence. It gets this request's own abort signal and a\n * staleness check bound to the same version counter, so it inherits the\n * debounce, the cancellation and the out-of-order protection instead of\n * running a second scheduler beside them. Whatever it returns is never\n * awaited here — the suggestions round-trip must not wait on it, and its\n * failures must not reach this controller's catch.\n */\n onRequest?: (ctx: { query: string; signal: AbortSignal; isCurrent: () => boolean }) => 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: () => SafeOptionOverrides | undefined,\n private getMaskCompletedText: () => boolean | undefined,\n private getOnError: () => ((error: Error) => void) | undefined,\n private getSessionId: () => string,\n private getAdditionalContext: () => Record<string, unknown> | undefined,\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 // Side-channel work rides this request. `rawQuery` carries\n // `{{PLACEHOLDER}}` tokens, which are meaningless to a product search, so\n // hand over the plain text the user actually typed.\n //\n // Wrapped because this call sits outside the try below and before the\n // request is even issued: a synchronous throw from a listener would reject\n // doFetch() — which every caller invokes un-awaited — leaving an unhandled\n // rejection and no suggestions request at all. Isolation is the entire\n // point of the hook, so it is enforced here rather than trusted.\n try {\n this.callbacks.onRequest?.({\n query: this.store.get().text,\n signal: controller.signal,\n isCurrent: () => version === this.fetchVersion,\n });\n } catch {\n // Fire-and-forget by contract — never take the suggestions half down.\n }\n\n try {\n // Inside the try, not before it: this write notifies subscribers, and a\n // throw from one of them must not escape past the request. A stranded\n // `isLoading: true` with `error: null` and no request on the wire is\n // indistinguishable from a slow network on the consumer's side.\n this.store.set({ isLoading: true, error: null });\n\n // With a span open, hint the server about what was on screen when the\n // user started typing the unresolved trailing text: span snapshot ∪\n // current on-screen actionable suggestions. No span → no field.\n const stateAtRequest = this.store.get();\n const recentlySuggested = stateAtRequest.pendingSpan\n ? buildRecentlySuggested(\n stateAtRequest.pendingSpan.snapshot,\n stateAtRequest.actionableSuggestions,\n )\n : undefined;\n\n const res = await fetchSuggestions(rawQuery, completed, {\n sessionId: this.getSessionId(),\n maskCompletedText: this.getMaskCompletedText(),\n signal: controller.signal,\n apiConfig: this.getApiConfig(),\n identifiedParams: stateAtRequest.identifiedParams,\n recentlySuggested,\n skippedParams: stateAtRequest.skippedParams,\n additionalContext: this.getAdditionalContext(),\n });\n\n if (version !== this.fetchVersion) return;\n\n // Identified tokens from the response become candidate identified\n // params. They're validated against the CURRENT text below (inside the\n // final store.set), since the text may have changed while the request\n // was in flight.\n const identifiedCandidates: IdentifiedParamState[] = (res.data.input ?? [])\n .filter((item) => item.source === \"identified\")\n .map((item) => ({ id: crypto.randomUUID(), type: item.type, text: item.text }));\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 // Positionally validate candidates against the current text, outside\n // completed-param coverage (including a just-auto-matched param), and\n // REPLACE the identified set wholesale — latest response wins.\n const completedNow = extraParam ? [...s.completedParams, extraParam] : s.completedParams;\n const identifiedParams = reconcileIdentifiedParams(\n s.text,\n completedNow,\n identifiedCandidates,\n ).valid;\n // A skip performed while this request was on the wire isn't in its\n // `completed_params`, so the response may still suggest the type the\n // user just dismissed — writing it wholesale would visually resurrect\n // the skipped pill while `skippedParams` says otherwise. Drop ONLY\n // the types skipped after the request was issued (present now, absent\n // from the request-time snapshot). A skip the request DID carry is\n // different: if the server re-suggests that type anyway (a required\n // param it refuses to drop), the server wins and the pill comes back.\n const carriedSkips = new Set(stateAtRequest.skippedParams.map((p) => p.id));\n const racedSkipTypes = new Set(\n s.skippedParams.filter((p) => !carriedSkips.has(p.id)).map((p) => p.type),\n );\n const reconciledSuggestions =\n racedSkipTypes.size > 0\n ? newSuggestions.filter(\n (sg) => sg.type === \"placeholder\" || !racedSkipTypes.has(sg.type),\n )\n : newSuggestions;\n return {\n suggestions: reconciledSuggestions,\n isLoading: false,\n isReady: res.data.is_ready ?? false,\n lastRawQuery: rawQuery,\n activeDropdownIndex: -1,\n filterBase,\n filterInProgress,\n identifiedParams,\n ...(extraParam ? { completedParams: completedNow } : {}),\n };\n });\n } catch (err) {\n // Coerced before the guard, not inside the write below: `String(err)` on\n // a rejection value with a throwing `toString` would otherwise throw\n // between entering this block and clearing the flag.\n const caughtError = toError(err);\n if (version === this.fetchVersion) {\n this.store.set({ error: caughtError, isLoading: false });\n this.getOnError()?.(caughtError);\n }\n } finally {\n // Defense in depth for the invariant the spinner depends on: whichever\n // fetch is the current one owns `isLoading` and must not exit with it\n // still set. Every exit above already clears it — the success write, the\n // catch's write — and a superseded fetch (version bumped) deliberately\n // leaves the flag to the fetch that replaced it. So this should not fire;\n // it exists because the cost of being wrong is a permanent spinner with\n // `error: null`, which reads to the consumer as a slow network. Any\n // future exit added above inherits the guarantee instead of having to\n // remember it.\n if (version === this.fetchVersion && this.store.get().isLoading) {\n try {\n this.store.set({ isLoading: false });\n } catch {\n // The flag is already applied to state (the store mutates before it\n // notifies); only the notification failed. Nothing further to do —\n // swallowing here keeps a throwing subscriber from masking whatever\n // error is already propagating out of this frame.\n }\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 // Same predicate `deriveAll` uses to decide the text isn't a filter query.\n if (isTypingPlaceholderPrefix(s.text, s.completedParams.length, placeholderText)) {\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 type { AutocompleteResult, CompletedParamState, SkippedParamState } from \"../shared-types\";\nimport { buildQuery } from \"./buildQuery\";\nimport { withSkippedParams } from \"./skippedParams\";\n\n/**\n * Builds the `AutocompleteResult` handed to `onSubmit`: the placeholder-\n * tokenized raw query plus the completed params, with skipped suggestions\n * folded in (see {@link withSkippedParams}).\n *\n * Shared by every submit path — vanilla Enter / submit button, the React Tier 1\n * component, the Angular Tier 1 component — so they can't drift on what a\n * result contains.\n */\nexport function buildSubmitResult(\n text: string,\n completedParams: CompletedParamState[],\n skippedParams: SkippedParamState[] = [],\n): AutocompleteResult {\n const { rawQuery, completedParams: finalParams } = buildQuery(text, completedParams);\n return {\n query: text.trim(),\n raw_query: rawQuery,\n completed_params: withSkippedParams(finalParams, skippedParams),\n };\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 { buildSubmitResult } from \"../utils/submitResult\";\n\nexport interface KeyboardContext {\n columns: number;\n listboxId: string;\n /**\n * Returns the submit dispatcher, or undefined when nothing is listening. The\n * dispatcher reports whether every handler completed — see `afterSubmit`.\n */\n getOnSubmit: () => ((result: AutocompleteResult) => boolean) | 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 /**\n * Optional hook invoked after onSubmit fires (used by Tier 1 to auto-reset).\n *\n * Skipped when a submit handler threw: the reset clears the user's typed\n * query, and a consumer whose handler failed — a validation guard that\n * throws, say — must not silently lose it. Their error is contained and\n * reported by the ConsumerBoundary either way.\n */\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 /**\n * Tier 1 only: ArrowLeft onto a pill's trailing edge selects that pill —\n * re-edit on, options shown — instead of stepping the caret inside it.\n * Returns true when re-edit started.\n */\n startEditingParamAtCaret?: (offset: number) => boolean;\n /** Tier 1 only: exit re-edit mode (Escape, arrow-key escape). */\n exitEditMode?: () => void;\n /**\n * Skip the active pill (ArrowRight at the end of the input). Same action the\n * dropdown's skip button invokes — the core owns the logic so both entry\n * points share one implementation. See AIAutocomplete.skipActivePill.\n */\n skipActivePill: () => 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.ctx.skipActivePill();\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 break;\n }\n // At the grid's left edge there's nowhere left to go, so rather than\n // swallow the press, use it to select the pill the caret sits\n // against — merely hovering an option sets the highlight, so this is\n // a common state to be in. Only that: it deliberately does NOT fall\n // into the caret-movement branches below, because ArrowLeft from\n // column 0 while re-editing (reachable via ArrowLeft → ArrowDown\n // into the grid) would otherwise collapse the edit session the user\n // is browsing options for.\n if (!state.editingParam && this.ctx.startEditingParamAtCaret) {\n const caret = getEditableCaretOffset(e.target);\n if (caret != null) this.ctx.startEditingParamAtCaret(caret);\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 // A pill is atomic: pressing left while the caret already sits at its\n // trailing edge selects the whole pill (re-edit + its options) rather\n // than moving the caret into it. The caret offset is read before the\n // key takes effect, so from one character further right this is the\n // second press, not the first. preventDefault keeps the caret outside\n // the `<strong>`.\n if (this.ctx.startEditingParamAtCaret) {\n const offset = getEditableCaretOffset(e.target);\n if (offset != null && this.ctx.startEditingParamAtCaret(offset)) {\n e.preventDefault();\n }\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 completed = onSubmit(\n buildSubmitResult(state.text, state.completedParams, state.skippedParams),\n );\n if (completed) 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","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 { Product, ProductsConfig } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\n\n/**\n * Drives the dropdown's product strip.\n *\n * Deliberately owns **no timer and no AbortController of its own**. Every run\n * is kicked off by {@link FetchController} from inside the one request it was\n * already making, and rides that request's debounce, abort signal and version\n * counter. A second scheduler here would drift out of step with `/suggest`\n * (the prototype ran its own 180ms debounce alongside it and the two could\n * desynchronise) — so if you ever need products to refresh on some other\n * trigger, widen the existing scheduler rather than adding one here.\n *\n * Failure is silent by contract: a rejected fetch or a throwing transform\n * clears the strip, logs at most once per instance, and never touches\n * `isLoading` / `error` — those belong to the suggestions half, which must be\n * completely unaffected.\n */\nexport class ProductsController {\n /**\n * Per-instance, not module-level: two autocompletes on one page have two\n * integrations, and one's transient failure must not permanently silence\n * the diagnostic for the other's genuinely broken transform.\n */\n private hasLoggedError = false;\n\n constructor(\n private store: Store<CoreState>,\n private getConfig: () => ProductsConfig | undefined,\n ) {}\n\n /**\n * Run one product search alongside an outbound `/suggest` request.\n *\n * @param query what the user has typed. Empty queries never reach the\n * integration — the strip just clears.\n * @param signal the suggest request's signal; aborted when a newer query\n * supersedes this one.\n * @param isCurrent staleness check bound to the same fetch version the\n * suggest request uses, so an out-of-order response is\n * dropped rather than rendered.\n */\n async run(query: string, signal: AbortSignal, isCurrent: () => boolean): Promise<void> {\n const config = this.getConfig();\n // Unconfigured: not one observable side effect, not even a state write.\n if (!config) return;\n\n if (query.trim().length === 0) {\n this.clear(isCurrent);\n return;\n }\n\n try {\n const raw = await config.fetch(query, signal);\n // Two guards, not one: `isCurrent` catches a newer query that already\n // went out, `signal.aborted` catches an integration that resolved\n // instead of rejecting after we cancelled it.\n if (signal.aborted || !isCurrent()) return;\n\n const mapped = config.transform(raw);\n const list = Array.isArray(mapped) ? mapped : [];\n const products = config.limit != null ? list.slice(0, config.limit) : list;\n if (signal.aborted || !isCurrent()) return;\n this.commit(products);\n } catch (err) {\n // An abort is the SDK cancelling its own request — not a failure, and\n // the newer run owns the strip from here.\n if (signal.aborted || isAbortError(err)) return;\n this.logOnce(err);\n this.clear(isCurrent);\n }\n }\n\n /** Drop the strip's contents. Used when `update()` swaps the integration. */\n clearNow(): void {\n this.commit([]);\n }\n\n private clear(isCurrent: () => boolean): void {\n if (!isCurrent()) return;\n this.commit([]);\n }\n\n /** Skip no-op writes so an unchanged strip doesn't churn subscribers. */\n private commit(products: Product[]): void {\n if (products.length === 0 && this.store.get().products.length === 0) return;\n this.store.set({ products });\n }\n\n private logOnce(err: unknown): void {\n if (this.hasLoggedError) return;\n this.hasLoggedError = true;\n // biome-ignore lint/suspicious/noConsole: one-time integration diagnostic\n console.warn(\n \"[AIAutocomplete] products.fetch/transform failed — the product strip is hidden. Later failures on this instance are not logged.\",\n err,\n );\n }\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n","/**\n * Rows/columns policy for the dropdown's options grid.\n *\n * All three packages render the same grid and must agree on the policy, so it\n * lives here (in the core) and is imported by the React and Angular shells\n * rather than reimplemented per package — the way the rest of the shared logic\n * works. Duplicating it left the row-height and footer-band constants free to\n * drift three ways with three green suites.\n */\n\n/** The viewport width the grid treats as \"phone\" — same breakpoint as the mobile footer. */\nexport const OPTIONS_GRID_MOBILE_QUERY = \"(max-width: 768px)\";\n\n/** Rows visible before the list scrolls, per viewport. */\nconst MOBILE_VISIBLE_ROWS = 5;\nconst WEB_VISIBLE_ROWS = 4;\n\n/** Option counts that get two balanced columns on web instead of one tall list. */\nconst TWO_COLUMN_MIN = 5;\nconst TWO_COLUMN_MAX = 6;\n\n/**\n * Fallback height of a single (unwrapped) option row: its line-height plus its\n * vertical padding, as declared by the option rule in each package's\n * stylesheet. Exposed as `--aia-option-row-height` so a consumer restyling the\n * option row can keep the cap in step; an option long enough to wrap is taller\n * than this, so the cap is an estimate for those.\n */\nconst ROW_HEIGHT = \"var(--aia-option-row-height, 37px)\";\n\n/**\n * The grid reserves the footer band as its own padding (`--aia-grid-scroll-top`\n * / `-bottom` in appearance.css) and the box is `border-box`, so the cap has to\n * carry the band on top of the rows or it eats one. Reading the same custom\n * properties the padding reads keeps the two in step — including when the\n * product strip zeroes the band, where a hardcoded band would have overshot by\n * its own height and shown a sliver of an extra row.\n */\nconst RESERVED_BAND = \"var(--aia-grid-scroll-top, 0px) + var(--aia-grid-scroll-bottom, 0px)\";\n\nexport interface OptionsGridLayout {\n /** Column count for `grid-template-columns`. */\n cols: number;\n /** Rows visible before the list scrolls. */\n rows: number;\n /** Value for `--aia-grid-max-height` — caps the scroll box at `rows` rows. */\n maxHeight: string;\n}\n\n/**\n * - Mobile: one column, five rows visible (scroll past five).\n * - Web: one column, four rows visible by default — but when there are exactly\n * five or six options, two balanced columns (so 5/6 fit without scrolling).\n * Columns fill row-major, so the two columns end up 3/2 (five) or 3/3 (six).\n */\nexport function computeOptionsGridLayout(count: number, isMobile: boolean): OptionsGridLayout {\n const layout = (cols: number, rows: number): OptionsGridLayout => ({\n cols,\n rows,\n maxHeight: `calc(${rows} * ${ROW_HEIGHT} + ${RESERVED_BAND})`,\n });\n if (isMobile) return layout(1, Math.min(count, MOBILE_VISIBLE_ROWS));\n if (count >= TWO_COLUMN_MIN && count <= TWO_COLUMN_MAX) {\n return layout(2, Math.ceil(count / 2));\n }\n return layout(1, Math.min(count, WEB_VISIBLE_ROWS));\n}\n\n/**\n * `grid-template-columns` for a fixed column count. Space-separated\n * `minmax(0,1fr)` tracks (no `repeat()`, no inner spaces) so rows fill\n * row-major — which the keyboard controller assumes — and the track count reads\n * back correctly wherever computed styles aren't fully resolved.\n */\nexport function optionsGridTemplateColumns(cols: number): string {\n return Array.from({ length: cols }, () => \"minmax(0,1fr)\").join(\" \");\n}\n\n/** Whether the current viewport is phone-width. `false` when there's no DOM. */\nexport function isOptionsGridMobileViewport(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(OPTIONS_GRID_MOBILE_QUERY).matches\n );\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 * True when there's an active pill whose underlying option set is empty (the\n * server returned a suggestion with no options to choose from) — as opposed\n * to options that exist but were filtered away by the typed query. The\n * dropdown stays open in this case so the pill chip remains visible even\n * though there's nothing to pick.\n */\n activePillHasNoOptions: boolean;\n /**\n * True when the product strip has cards to show. The panel's two halves are\n * independent: products hold it open on their own, so option filtering\n * emptying the list no longer takes the strip down with it. Always false\n * when `opts.products` is unconfigured, which keeps every gate below exactly\n * as it was.\n */\n hasProducts: 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 // Keep the dropdown open when the active pill simply has no options (the\n // server returned a suggestion with an empty option set) so the pill chip\n // stays visible — as opposed to options that exist but were filtered away by\n // the typed query, which still closes the dropdown.\n const hasPillContent = hasOptions || inputs.activePillHasNoOptions;\n // Either half is enough to keep the panel open. Without this, filtering the\n // options down to zero closed the panel and took the product strip with it —\n // the exact gate the Shopify prototype had to render outside of.\n //\n // Only the *content* test widens: the trigger gates below are unchanged, so\n // `manual` still waits for a pill tap and `hidden` still never opens. Both\n // are explicit consumer opt-outs of an auto-opening panel, and a product\n // result is not a reason to overrule them.\n const hasContent = hasPillContent || inputs.hasProducts;\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 hasContent && 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 (hasContent || inputs.isLoading) && focusGate && caretAtEnd;\n }\n\n if (trigger === \"manual\") {\n return (hasContent || inputs.isLoading) && inputs.pillTapped;\n }\n\n return false;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreDerivedState, CoreDeriveOptions, CoreInputState } from \"../types\";\nimport {\n effectiveFilterBase,\n extractFilterQuery,\n filterOptions,\n isTypingPlaceholderPrefix,\n} 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: CoreDeriveOptions): CoreDerivedState {\n const segments = deriveSegments(inputs.text, inputs.completedParams, inputs.identifiedParams);\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 // What the user typed IS the filter query, with one exception: retyping the\n // placeholder's own lead-in (\"Cre\" of \"Create a\") is not filtering. That's\n // the same rule the fetch scheduler applies before it suppresses a request,\n // shared so the two can't disagree about whether the user is filtering.\n //\n // There's deliberately no \"has a response landed yet\" guard. It would be\n // dead code: `filterQuery` only ever reaches the active suggestion's options\n // (below), so with no suggestions there is nothing to filter either way. The\n // guard that used to sit here keyed off `lastRawQuery !== \"\"`, which the\n // mount fetch's empty raw query could never satisfy — so filtering from an\n // empty input never switched on at all and every option stayed on screen\n // while the user typed a prefix of one.\n const typingPlaceholderPrefix =\n clampedFilterBase === 0 &&\n isTypingPlaceholderPrefix(inputs.text, inputs.completedParams.length, placeholderText);\n const filterQuery = typingPlaceholderPrefix\n ? \"\"\n : extractFilterQuery(inputs.text, clampedFilterBase, inputs.filterInProgress);\n // `undefined` is the boundary's signal that this override threw; fall back\n // to the server's options for the suggestion. Every other result, `[]`\n // included, is the consumer's answer and is honoured as-is. See\n // `SafeOptionOverrides`.\n const baseOptions = activeSuggestion\n ? overrideFn\n ? (overrideFn(filterQuery.trim()) ?? activeSuggestion.options ?? [])\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 const hideNonTappable = opts.showNonTappableOptions === false;\n if (hideNonTappable) {\n filteredOptions = filteredOptions.filter((o) => o.is_tappable);\n }\n\n // An active pill with a genuinely empty option set (the server/override\n // returned no options at all) — distinct from options that were filtered away\n // by the typed query. Keeps the dropdown open so the pill chip stays visible.\n // Outside edit mode we measure the pill's UNFILTERED option source: for\n // overrides that's the override's result for an EMPTY query, not `baseOptions`\n // (already filtered by the typed query). An override returning [] for \"xyz\"\n // means \"nothing matched\", NOT \"this pill has no options\" — asking for the\n // unfiltered list tells the two apart. The override is only invoked here in\n // the non-edit branch, so edit mode pays no extra `overrideFn(\"\")` call.\n // When `showNonTappableOptions === false` the dropdown hides non-tappable\n // options, so the pill's *effective* option set is only its tappable ones. A\n // pill whose options are all non-tappable then has nothing to render and must\n // be treated as having no options, otherwise the dropdown closes and the pill\n // chip disappears.\n const countsAsOption = (o: SuggestionOption): boolean => (hideNonTappable ? o.is_tappable : true);\n let activePillHasNoOptions: boolean;\n if (inEditMode) {\n const editOptions = inputs.editingParam?.options ?? [];\n activePillHasNoOptions =\n inputs.editingParam != null && editOptions.filter(countsAsOption).length === 0;\n } else {\n const activePillSourceOptions = activeSuggestion\n ? overrideFn\n ? (overrideFn(\"\") ?? activeSuggestion.options ?? [])\n : (activeSuggestion.options ?? [])\n : [];\n activePillHasNoOptions =\n activeSuggestion != null && activePillSourceOptions.filter(countsAsOption).length === 0;\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 activePillHasNoOptions,\n hasProducts: inputs.products.length > 0,\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","/**\n * Removes the text span `[start, end)` that a chip occupied, together with the\n * separating space the chip leaves stranded.\n *\n * The space that separated the chip has nothing left to separate once the chip\n * is gone: \"a <chip> b\" would leave \"a b\", and a LEADING chip would leave\n * \" b\". Dropping it matters beyond cosmetics — a stranded \" \" is non-empty\n * text, so the placeholder stays suppressed (`renderEditableContent` keys off\n * `segments.length`) and the next request goes out as `raw_query: \" \"` instead\n * of taking `scheduleFetch`'s empty-session restart.\n *\n * Shared by the two routes that delete a whole chip — Backspace at the chip's\n * trailing edge (`removeParamAtCaret`) and a delete while the chip is selected\n * (`ReEditManager.replaceRange`) — so they can't disagree about the result.\n *\n * `removed` is how many characters came out in total; callers shift offsets\n * that sat after the chip (e.g. `filterBase`) by it.\n */\nexport function removeChipSpan(\n text: string,\n start: number,\n end: number,\n): { text: string; removed: number } {\n const before = text.slice(0, start);\n let after = text.slice(end);\n const dropsSeam = (before === \"\" || before.endsWith(\" \")) && after.startsWith(\" \");\n if (dropsSeam) after = after.slice(1);\n return { text: before + after, removed: end - start + (dropsSeam ? 1 : 0) };\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\";\nimport { removeChipSpan } from \"../utils/chipSpan\";\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 /** Immediate, undebounced fetch for the current text + params. See `selectOption`. */\n fetchNow: () => 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 // An empty replacement is a delete of the whole selected chip — the other\n // route to the same outcome as Backspace at the chip's trailing edge, so it\n // drops the stranded separating space the same way (see `removeChipSpan`\n // for why a lone \" \" is worse than cosmetic). A non-empty replacement keeps\n // the separator: the new text still needs it.\n const { text: newText } =\n replacement === \"\"\n ? removeChipSpan(state.text, anchor, tail)\n : { text: 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 // Changing an answer is answering: everything the server suggested after\n // this param was conditioned on the value just replaced, so it goes back\n // for a fresh set on the same terms as a first-time selection. Fired here\n // rather than left to the scheduler for the same reason — `skipNextFetch`\n // above stands the debounced path down, and its raw-query length gate\n // can't be relied on for a swap that may not change the query's length at\n // all. Deliberately after the replacement is committed, never during the\n // pick: the re-edit dropdown shows this param's own cached options, and a\n // response arriving mid-interaction would swap them under the user.\n this.deps.fetchNow();\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","/** Base URL for the \"AI Autocomplete\" branding/attribution link in the dropdown footer. */\nexport const ATTRIBUTION_URL = \"https://ai-autocomplete.com\";\n\n/**\n * Builds the attribution link URL, appending a `utm_source` query param derived\n * from the current page's hostname. This lets analytics attribute the referral to\n * the embedding site even when that site sends a `no-referrer` policy (which\n * otherwise strips the Referer header and surfaces the visit as `$direct`).\n *\n * Falls back to the bare base URL when there is no browser `location` (SSR /\n * non-browser env) or the hostname can't be read.\n */\nexport function buildAttributionUrl(base: string = ATTRIBUTION_URL): string {\n try {\n if (typeof window === \"undefined\" || !window.location) return base;\n const host = window.location.hostname;\n if (!host) return base;\n const url = new URL(base);\n url.searchParams.set(\"utm_source\", host);\n return url.toString();\n } catch {\n return base;\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 (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 { Product } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\nconst SECTION_LABEL = \"Products\";\n\n/**\n * Renders the dropdown's product strip: a labelled section holding a\n * horizontally scrolling row of product cards. Sits below the options grid and\n * above the footer, and renders only when there are products — the two halves\n * of the panel are independent, so an empty strip leaves no gap and an empty\n * options grid leaves the strip alone.\n *\n * Layout notes that are easy to get wrong (both cost the Shopify prototype a\n * round trip before this moved into the SDK):\n *\n * - The dropdown paints a background but never sets `color`, so anything here\n * relying on `color: inherit` would pick up the *host page's* text colour —\n * black text on a dark panel. Every text node in the strip resolves its\n * colour through the same `--aia-option-*` chain the options use (see\n * styles.css); none of it inherits.\n * - The options grid carries a deliberate negative bottom margin so the footer\n * rides up over its reserved band. Whatever follows the grid inherits that\n * pull. Rather than cancelling it from here, the dropdown is marked with\n * `data-aia-has-products` and appearance.css zeroes the band at its source —\n * with a strip in between, the footer no longer sits over the scrolling list\n * and there is nothing to reserve.\n */\nexport function renderProductStrip(\n parent: HTMLElement,\n products: Product[],\n listboxId: string,\n onSelect: (product: Product) => void,\n onFocusChange: (focused: boolean) => void,\n): void {\n let section = parent.querySelector<HTMLElement>(\".magicx-aia-products\");\n\n if (products.length === 0) {\n section?.remove();\n return;\n }\n\n if (!section) {\n section = document.createElement(\"section\");\n section.className = \"magicx-aia-products\";\n section.setAttribute(\"data-aia-products\", \"\");\n // `group` is the one role a listbox accepts around a set of options, so\n // the strip stays inside the listbox without breaking its content model.\n section.setAttribute(\"role\", \"group\");\n section.setAttribute(\"aria-labelledby\", `${listboxId}-products-label`);\n\n const label = document.createElement(\"div\");\n label.className = \"magicx-aia-products-label\";\n label.id = `${listboxId}-products-label`;\n label.textContent = SECTION_LABEL;\n\n const row = document.createElement(\"div\");\n row.className = \"magicx-aia-products-row\";\n row.setAttribute(\"data-aia-products-row\", \"\");\n\n section.append(label, row);\n parent.appendChild(section);\n }\n\n const row = section.querySelector<HTMLElement>(\".magicx-aia-products-row\");\n if (!row) return;\n\n reconcileList(row, products, {\n // Identity *and* content: a card built for one product holds that product\n // in its click closure, so a same-id result whose fields changed must be\n // rebuilt rather than reused with stale text and a stale handler. Cards\n // are otherwise reused across the (frequent) unrelated re-renders.\n keyOf: (product) => cardKey(product),\n create: (product) => buildCard(product, onSelect, onFocusChange),\n update: (el, _product, i) => {\n el.id = `${listboxId}-product-${i}`;\n el.dataset.aiaIndex = String(i);\n },\n });\n}\n\n/**\n * Cards are focusable, so while the strip is hidden they would still be\n * reachable by Tab. `renderDropdown` calls this on the way out of a closed\n * render (which deliberately leaves the last content in place so the panel can\n * fade out) to take them back out of the tab order.\n */\nexport function setProductStripFocusable(root: HTMLElement, focusable: boolean): void {\n const cards = root.querySelectorAll<HTMLElement>(\"[data-aia-product]\");\n for (const card of cards) card.tabIndex = focusable ? 0 : -1;\n}\n\nfunction cardKey(product: Product): string {\n return [product.id, product.title, product.url, product.imageUrl, product.price, product.vendor]\n .map((field) => field ?? \"\")\n .join(\"\\0\");\n}\n\nfunction buildCard(\n product: Product,\n onSelect: (product: Product) => void,\n onFocusChange: (focused: boolean) => void,\n): HTMLElement {\n // A real <a href> rather than a <div>: it keeps the browser's own link\n // affordances — cmd/ctrl-click for a new tab, middle-click, right-click →\n // \"copy link address\", and a visible target on hover. `role=\"option\"`\n // overrides how assistive tech announces it, which is the right call inside\n // a listbox; the deliberate trade is that AT no longer calls it a link.\n const card = document.createElement(\"a\");\n card.className = \"magicx-aia-product\";\n card.setAttribute(\"data-aia-product\", \"\");\n card.setAttribute(\"role\", \"option\");\n card.setAttribute(\"aria-selected\", \"false\");\n card.href = product.url;\n card.tabIndex = 0;\n\n const media = document.createElement(\"span\");\n media.className = \"magicx-aia-product-media\";\n if (product.imageUrl) {\n const img = document.createElement(\"img\");\n img.className = \"magicx-aia-product-image\";\n img.src = product.imageUrl;\n // The title is already in the card's accessible name; repeating it on the\n // image would have a screen reader read it twice.\n img.alt = \"\";\n img.loading = \"lazy\";\n img.decoding = \"async\";\n media.appendChild(img);\n } else {\n // Catalogues without images are common enough that the placeholder is a\n // first-class state, not an error state.\n media.setAttribute(\"data-aia-product-placeholder\", \"\");\n }\n card.appendChild(media);\n\n const body = document.createElement(\"span\");\n body.className = \"magicx-aia-product-body\";\n\n // Every field but the title is optional, so absent ones produce no element\n // at all — the column gap can't leave a hole for a box that isn't there.\n if (product.vendor) {\n const vendor = document.createElement(\"span\");\n vendor.className = \"magicx-aia-product-vendor\";\n vendor.textContent = product.vendor;\n body.appendChild(vendor);\n }\n\n const title = document.createElement(\"span\");\n title.className = \"magicx-aia-product-title\";\n title.textContent = product.title;\n body.appendChild(title);\n\n if (product.price) {\n const price = document.createElement(\"span\");\n price.className = \"magicx-aia-product-price\";\n price.textContent = product.price;\n body.appendChild(price);\n }\n\n card.appendChild(body);\n\n card.addEventListener(\"click\", (e) => {\n // Let the browser keep the clicks that mean \"open this somewhere else\" —\n // intercepting them would be the one thing an <a> was chosen for. Those\n // are navigations the user asked for explicitly, so they don't emit.\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;\n e.preventDefault();\n onSelect(product);\n });\n\n card.addEventListener(\"keydown\", (e) => {\n if (e.key !== \"Enter\" && e.key !== \" \") return;\n e.preventDefault();\n onSelect(product);\n });\n\n // The panel closes on blur (default `closeDropdownOnBlur`), and the input\n // blurs the moment focus lands on a card. Report focus as still-inside so\n // tabbing into the strip doesn't shut the panel out from under it.\n card.addEventListener(\"focus\", () => onFocusChange(true));\n card.addEventListener(\"blur\", (e) => {\n const next = e.relatedTarget as HTMLElement | null;\n if (next?.closest(\"[data-aia-dropdown]\")) return;\n onFocusChange(false);\n });\n\n return card;\n}\n","import {\n computeOptionsGridLayout,\n isOptionsGridMobileViewport,\n optionsGridTemplateColumns,\n} from \"../derive/optionsGridLayout\";\nimport type { SuggestionOption } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\n/**\n * Applies the shared rows/columns policy inline on each render — the option\n * count drives it, so it can change between suggestion groups. React and\n * Angular apply the same `computeOptionsGridLayout` result to their own grids.\n */\nfunction applyGridLayout(grid: HTMLElement, count: number): void {\n const { cols, maxHeight } = computeOptionsGridLayout(count, isOptionsGridMobileViewport());\n grid.style.gridTemplateColumns = optionsGridTemplateColumns(cols);\n grid.style.setProperty(\"--aia-grid-max-height\", maxHeight);\n}\n\n/**\n * Renders the dropdown's option grid into `parent`: a `.aia-grid` element (the\n * intrinsic Grid layout primitive — columns auto-fit at 250px minimum and\n * stretch to share out the full width, scrollable with a capped height) with\n * one option element per suggestion. Vanilla analogue of React's\n * `SuggestionGrid` component. The grid reserves a band at its bottom edge and\n * pulls the footer up over it (see `--aia-grid-scroll-bottom` /\n * `--aia-grid-overlap-bottom` in appearance.css), so the list scrolls under\n * the footer and dissolves into its gradient background. Creates the grid on\n * first use, reuses it on later renders, and removes it when there are no\n * 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 groupKey = \"\",\n): void {\n let grid = parent.querySelector<HTMLElement>(\".aia-grid\");\n if (options.length === 0) {\n grid?.remove();\n return;\n }\n if (!grid) {\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 // 1fr (not a fixed 250px) so the columns share out the full width: a lone\n // column spans the whole options box instead of stopping at 250px.\n grid.style.setProperty(\"--aia-grid-max\", \"1fr\");\n grid.style.setProperty(\"--aia-grid-gap\", \"0\");\n parent.appendChild(grid);\n }\n applyGridLayout(grid, options.length);\n renderOptions(grid, options, activeIndex, onSelect, onHighlight, listboxId, loading);\n resetScrollOnNewGroup(grid, groupKey);\n}\n\n/**\n * Send the scroll position back to the top whenever the grid starts showing a\n * different suggestion's options (i.e. after a selection), so the next set is\n * read from its first option rather than from wherever the previous list was\n * scrolled to. Filtering within the same suggestion keeps its scroll position.\n */\nfunction resetScrollOnNewGroup(grid: HTMLElement, groupKey: string): void {\n if (grid.dataset.aiaGroup === groupKey) return;\n grid.dataset.aiaGroup = groupKey;\n grid.scrollTop = 0;\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 { Product, Suggestion, SuggestionOption } from \"../shared-types\";\nimport { buildAttributionUrl } from \"../utils/attribution\";\nimport { getFooterHint } from \"../utils/footerHint\";\nimport { renderPills } from \"./renderPills\";\nimport { renderProductStrip, setProductStripFocusable } from \"./renderProductStrip\";\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 /**\n * Whether the pill bar ends in the \"skip\" trailing button. Callers pass\n * false while re-editing a completed param — the bar then shows the param\n * being re-edited, which isn't skippable.\n */\n showSkipButton: boolean;\n /**\n * Extra disabled gate for the skip button beyond `isLoading`. Callers pass\n * `inSelectionAnimation`: the UI-facing loading flag is deliberately\n * suppressed during that window (no skeleton flicker while the answered\n * pill animates out), but the core's skipActivePill() no-ops in it — the\n * button must render disabled rather than swallow clicks silently.\n */\n skipDisabled: 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 /** Product strip contents. Always empty unless `opts.products` is configured. */\n products: Product[];\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n onPillClick: (index: number) => void;\n /** Skip the active pill — same action as ArrowRight at the end of the input. */\n onSkip: () => void;\n onProductSelect: (product: Product) => void;\n /** Focus moved into / out of the strip — keeps the panel open while a card holds focus. */\n onProductFocusChange: (focused: boolean) => 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 onSkip,\n } = state;\n\n const hasRealPills = pills.length > 0;\n const hasPills = showPills && hasRealPills;\n const hasOptions = filteredOptions.length > 0;\n const hasProducts = state.products.length > 0;\n // Either half is enough to keep the panel up: products render with no\n // suggestions, suggestions render with no products.\n const isVisible = isOpen && (hasOptions || hasPills || isLoading || hasProducts);\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 //\n // The one thing that can't be left as-is: focusable product cards inside a\n // hidden panel would still answer Tab.\n if (!isVisible) {\n setProductStripFocusable(dropdown, false);\n return;\n }\n\n // The band the footer rides up over only makes sense when the footer sits\n // directly on the scrolling option list. With a strip between them the\n // attribute tells appearance.css to zero it at the source — see\n // renderProductStrip's header comment.\n //\n // Deliberately *after* the early return above: a closing panel keeps its\n // last-rendered content for the fade, so removing the attribute here would\n // re-arm the grid's negative margin underneath a strip that is still on\n // screen and drop the footer onto it for the length of the transition. The\n // attribute has to describe the content the panel is showing, which is what\n // React and Angular do by reading their frozen snapshot.\n if (hasProducts) {\n dropdown.setAttribute(\"data-aia-has-products\", \"\");\n } else {\n dropdown.removeAttribute(\"data-aia-has-products\");\n }\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). The skip button\n // wants the bar too even when pills render elsewhere (pillPlacement\n // \"inline\"/\"hidden\" ⇒ showPills false): the bar then holds only the button,\n // pinned at its trailing edge — a skip-only row.\n //\n // Empty input hides the button: the footer hint doesn't advertise \"→ to\n // skip\" until the user has typed (it shows \"tab to select\" instead), so the\n // pristine starting state stays free of the affordance too. The keyboard\n // path (→) still works there.\n const wantsSkip = state.showSkipButton && hasRealPills && !state.isInputEmpty;\n const wantsPillBar = hasPills || (isLoading && showPills) || wantsSkip;\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 // Pills render inside a masked scroll wrapper, NOT the bar itself: the\n // trailing skip button is the bar's other child, and overflowing pills\n // must scroll and fade under the wrapper's right edge instead of pushing\n // the button out of the clipped dropdown. Mirrors React's .pillScroll and\n // Angular's .magicx-aia-pill-scroll.\n let pillScroll = pillBar.querySelector<HTMLElement>(\".magicx-aia-pill-scroll\");\n if (!pillScroll) {\n pillScroll = document.createElement(\"span\");\n pillScroll.className = \"magicx-aia-pill-scroll\";\n // Public styling hook: the scroll + fade moved off [data-aia-pillbar]\n // onto this wrapper, so consumers overriding the mask need a stable\n // selector for it.\n pillScroll.setAttribute(\"data-aia-pill-scroll\", \"\");\n pillBar.insertBefore(pillScroll, pillBar.firstChild);\n }\n // In a skip-only row the pill list stays empty (pills render inline in\n // the input), and the loading flag is withheld so the empty list doesn't\n // grow fallback skeleton pills the inline placement already shows.\n renderPills(\n pillScroll,\n showPills ? pills : [],\n 0,\n onPillClick,\n true,\n isLoading && showPills,\n isActivePillSelected,\n );\n // The trailing \"skip\" button only makes sense next to a real, skippable\n // pill — the fallback skeleton bar (loading with no cached pills) has\n // nothing to skip.\n renderSkipButton(pillBar, wantsSkip, isLoading || state.skipDisabled, pills[0], onSkip);\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 // Identity of the suggestion whose options are on screen — when it changes\n // (a selection moved us to the next parameter) the grid scrolls back to top.\n const activeSuggestion = state.suggestions[0];\n const groupKey = activeSuggestion ? `${activeSuggestion.type} ${activeSuggestion.text}` : \"\";\n\n renderSuggestionGrid(\n stack,\n filteredOptions,\n activeIndex,\n onSelect,\n onHighlight,\n state.listboxId,\n isLoading,\n groupKey,\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 // --- Product strip (below the options grid, above the footer) ---\n renderProductStrip(\n stack,\n state.products,\n state.listboxId,\n state.onProductSelect,\n state.onProductFocusChange,\n );\n setProductStripFocusable(dropdown, true);\n\n // --- Footer (chrome — always last; hidden with the dropdown) ---\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 (!footer.isConnected) stack.appendChild(footer);\n\n orderSections(stack, [\n \".magicx-aia-pill-bar\",\n \".aia-grid\",\n \".magicx-aia-skeleton-bars\",\n \".magicx-aia-products\",\n \".magicx-aia-footer\",\n ]);\n}\n\n/**\n * Puts the stack's sections back into their canonical top-to-bottom order.\n *\n * Each section appends itself on creation, and they come and go independently\n * across renders — a grid can be created after the product strip already\n * exists, landing below it. Appending in the right order once is therefore not\n * enough; the order has to be asserted every render.\n *\n * Only nodes actually out of place are moved: `insertBefore` on an attached\n * node is a DOM mutation, and doing it unconditionally would fire consumer\n * Mutation/ResizeObservers on every keystroke and every highlight change.\n */\nfunction orderSections(stack: HTMLElement, selectors: string[]): void {\n const sections = selectors\n .map((selector) => stack.querySelector<HTMLElement>(`:scope > ${selector}`))\n .filter((el): el is HTMLElement => el !== null);\n\n for (let i = 0; i < sections.length; i++) {\n if (stack.children[i] !== sections[i]) {\n stack.insertBefore(sections[i], stack.children[i] ?? null);\n }\n }\n}\n\n/**\n * The pill bar's trailing \"skip\" button. `margin-inline-start: auto` in its\n * rule pushes it to the bar's far edge — visually top-right when the dropdown\n * opens below the input; when `optionsPosition` is \"above\" the stack reverses\n * and the bar (button included) lands bottom-right. Disabled while loading,\n * matching the pills it sits beside.\n */\nfunction renderSkipButton(\n pillBar: HTMLElement,\n visible: boolean,\n loading: boolean,\n activePill: Suggestion | undefined,\n onSkip: () => void,\n) {\n let btn = pillBar.querySelector<HTMLButtonElement>(\".magicx-aia-skip\");\n if (!visible) {\n btn?.remove();\n return;\n }\n if (!btn) {\n btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.tabIndex = -1;\n btn.className = \"magicx-aia-skip\";\n btn.setAttribute(\"data-aia-skip\", \"\");\n btn.textContent = \"skip\";\n btn.addEventListener(\"mousedown\", (e) => e.preventDefault());\n pillBar.appendChild(btn);\n }\n // \"skip\" alone doesn't say what gets skipped — name the active pill for AT.\n btn.setAttribute(\"aria-label\", activePill ? `Skip ${activePill.text}` : \"Skip\");\n btn.disabled = loading;\n btn.onclick = loading ? null : () => onSkip();\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 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 = buildAttributionUrl();\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(row);\n return footer;\n}\n","import type { Product, 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 /** Whether the dropdown's pill bar ends in the \"skip\" trailing button. */\n showSkipButton: boolean;\n selectOption: (option: SuggestionOption) => void;\n setActivePill: (index: number) => void;\n skipActivePill: () => void;\n selectProduct: (product: Product) => 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 // Hidden while re-editing: an already answered param isn't skippable.\n showSkipButton: opts.showSkipButton && !state.editingParam,\n // The isLoading passed above is suppressed during the selection animation,\n // but skipActivePill() no-ops in that window — mirror the guard visually.\n skipDisabled: state.inSelectionAnimation,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n onSelect: opts.selectOption,\n onHighlight: (i) => opts.store.set({ activeDropdownIndex: i }),\n onPillClick: opts.setActivePill,\n onSkip: opts.skipActivePill,\n onProductSelect: opts.selectProduct,\n onProductFocusChange: (focused) => opts.store.set({ isFocused: focused }),\n });\n}\n","import { getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { Segment } from \"../shared-types\";\n\n// Horizontal padding (px, per side) applied to a completed-param chip by the\n// stylesheets. Kept here so the tracking compensation below stays in step with\n// it — change both together.\nconst CHIP_PADDING_X = 6;\n// Never tighten past this (px per character gap); a crushed chip reads worse\n// than a slightly-wide one, so keep the compensation subtle.\nconst MAX_TRACKING = 0.3;\n\n/**\n * Negative letter-spacing that offsets a chip's horizontal padding, so a\n * recognized phrase occupies about the same width as the same words in plain\n * text (no reflow when text becomes a chip). letter-spacing adds one gap per\n * character; spreading the 2×padding across them cancels it, clamped so short\n * chips aren't over-tightened.\n */\nfunction chipTracking(textLength: number): string {\n if (textLength <= 0) return \"0px\";\n const perChar = Math.min(MAX_TRACKING, (2 * CHIP_PADDING_X) / textLength);\n return `${(-perChar).toFixed(3)}px`;\n}\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 into the contentEditable input. Completed params are\n * emitted as `<strong data-seg=\"completed\">` runs carrying the\n * `magicx-aia-segment--completed` class, which the stylesheets restyle as\n * inline pills (rounded chips). The tag stays a plain editable `<strong>` — the\n * caret system counts its text as part of the editable plain text, so it must\n * NOT become non-editable. The unfilled-suggestion pills are NOT rendered here;\n * they live as a sibling element so the editable's subtree never contains\n * non-editable children. See renderInput.ts for the pill list placement.\n *\n * Skips rebuilds when the segment key is unchanged so an in-flight reveal\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.style.letterSpacing = chipTracking(seg.value.length);\n strong.textContent = seg.value;\n frag.appendChild(strong);\n } else if (seg.type === \"identified\") {\n // Identified pills reuse the completed styling class for now (per TDB:\n // no distinct styling) but carry data-seg=\"identified\" so the re-edit\n // click handler (which targets data-seg=\"completed\") never fires.\n const strong = doc.createElement(\"strong\");\n strong.dataset.seg = \"identified\";\n strong.dataset.paramId = seg.param.id;\n strong.className = \"magicx-aia-segment magicx-aia-segment--completed\";\n strong.style.letterSpacing = chipTracking(seg.value.length);\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 { buildSubmitResult } from \"../utils/submitResult\";\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 /** Whether the dropdown's pill bar ends in the \"skip\" trailing button. */\n showSkipButton: boolean;\n autoFocus?: boolean;\n /** Submit dispatcher. Reports whether every handler completed — see `afterSubmit`. */\n onSubmit?: (result: AutocompleteResult) => boolean;\n /**\n * Invoked after onSubmit fires — Tier 1 uses this to auto-reset. Skipped\n * when a handler threw, so a failed submit doesn't clear the user's query.\n */\n afterSubmit?: () => void;\n submitButton?: HTMLElement | null;\n selectOption: (option: import(\"../shared-types\").SuggestionOption) => void;\n setActivePill: (index: number) => void;\n /** Skip the active pill — the dropdown's skip button routes here. */\n skipActivePill: () => void;\n selectProduct: (product: import(\"../shared-types\").Product) => 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 // Only a COLLAPSED selection means \"the caret landed in a chip\". A range\n // selection (Cmd+A, shift-arrow, drag) anchors at its start, which lands\n // inside the first `<strong>` whenever the input begins with a chip —\n // starting re-edit there parks the caret on that chip and destroys the\n // user's selection.\n const enclosing = sel.isCollapsed ? findEnclosingParamId() : null;\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 completed = opts.onSubmit(\n buildSubmitResult(state.text, state.completedParams, state.skippedParams),\n );\n if (completed) opts.afterSubmit?.();\n },\n { signal },\n );\n }\n\n if (opts.autoFocus !== false) {\n input.focus();\n // Focusing an empty contentEditable doesn't always create a selection\n // Range, so no caret blinks until the user clicks. Place a collapsed caret\n // at the start so the field is visibly ready immediately.\n const doc = input.ownerDocument ?? document;\n const sel = doc.getSelection();\n const caretInside = sel && sel.rangeCount > 0 && input.contains(sel.anchorNode);\n if (sel && !caretInside) {\n const range = doc.createRange();\n range.selectNodeContents(input);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\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 // Re-edit shows the param being re-edited in the pill bar — an already\n // answered param isn't skippable, so the button hides for the duration.\n showSkipButton: opts.showSkipButton && !state.editingParam,\n // The isLoading passed above is suppressed during the selection animation,\n // but skipActivePill() no-ops in that window — mirror the guard visually.\n skipDisabled: state.inSelectionAnimation,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n onSelect: selectOption,\n onHighlight: (i) => store.set({ activeDropdownIndex: i }),\n onPillClick: setActivePill,\n onSkip: opts.skipActivePill,\n onProductSelect: opts.selectProduct,\n // A card taking focus blurs the input, which would otherwise close the\n // panel out from under the card the user just tabbed to.\n onProductFocusChange: (focused) => store.set({ isFocused: focused }),\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 // Every answered suggestion goes back to the server, so the next\n // parameter it suggests is conditioned on the answer just given rather\n // than replayed from the cache the previous response shipped. The\n // debounced scheduler must not be the thing that issues it: it gates on\n // a raw-query length delta (>= 2 chars, or >= 1 on the slow timer), and\n // a selection can move that length by less than that — buildQuery swaps\n // the option's text for a `{{TYPE_N}}` token, so a long option can leave\n // the query barely longer, or exactly the same length, than before.\n // `selectOption` fires the request itself (undebounced); this flag keeps\n // the scheduler from issuing a second, redundant one.\n skipNextFetch: true,\n inSelectionAnimation: true,\n // The selection consumes any open pending span: the typed trailing text\n // is replaced by the selected option (now a completed param).\n pendingSpan: null,\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\n// Runaway-cascade backstop for the notification drain, set far above any real\n// chain of reconcilers.\nconst MAX_DRAIN = 10_000;\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 // Notifications awaiting delivery, as (next, prev) pairs. A `set` called\n // from inside a listener applies to `state` immediately — `get()` is never\n // stale — but its notification is QUEUED rather than delivered inline.\n // Delivering it inline would finish the whole listener set for the newer\n // state and then resume the outer loop, handing every listener registered\n // after the caller a `next` that has already been superseded. Draining in\n // order instead means each listener sees a monotonic chain of pairs\n // (S0→S1, then S1→S2), so a stale snapshot can never arrive last and\n // overwrite a decision made on newer state.\n //\n // One knock-on: a throw from a downstream listener now surfaces at the\n // OUTERMOST set() rather than at the nested one, so a listener can no\n // longer try/catch around its own set() to contain a downstream failure.\n const pending: [next: S, prev: S][] = [];\n let notifying = false;\n return {\n get: () => state,\n set: (patch) => {\n const resolved = typeof patch === \"function\" ? patch(state) : patch;\n const prev = state;\n state = { ...state, ...resolved };\n pending.push([state, prev]);\n if (notifying) return;\n notifying = true;\n try {\n let drained = 0;\n for (let entry = pending.shift(); entry; entry = pending.shift()) {\n // A listener that sets unconditionally used to blow the stack with a\n // RangeError, since delivery recursed. The drain loop is flat, so the\n // same bug would spin forever and freeze the tab instead. Keep the\n // failure loud and bounded. Legitimate cascades settle in 2-3 hops.\n if (++drained > MAX_DRAIN) {\n pending.length = 0;\n throw new Error(\n `createStore: notifications did not settle after ${MAX_DRAIN} deliveries — a listener is likely calling set() on every notification`,\n );\n }\n const [next, previous] = entry;\n for (const l of listeners) l(next, previous);\n }\n } catch (err) {\n // A throwing listener aborts delivery, as it always has. Drop what's\n // still queued rather than replaying it later against a state it no\n // longer describes.\n pending.length = 0;\n throw err;\n } finally {\n notifying = false;\n }\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","/**\n * The single isolation boundary between the SDK and consumer-supplied\n * functions.\n *\n * Every function-typed option a consumer can pass — the `on*` events,\n * `subscribe()` listeners, `optionOverrides` — is SDK-invoked code running on\n * the SDK's stack, and most of those stacks originate inside a `store.set`.\n * `store.set` notifies synchronously and lets a throwing listener propagate\n * (see `state.ts`), so an un-isolated consumer throw does not merely fail\n * itself: it unwinds whatever internal operation triggered the notification,\n * aborts the notification drain, and discards every queued notification with\n * it. That is how a throwing `onStateChange` used to leave `doFetch` pinned at\n * `isLoading: true` with no request issued, and how a throwing\n * `optionOverrides` entry could make `setValue()` throw at its caller.\n *\n * So: consumer code runs through here, never called directly. A throw is\n * contained, reported once, and turned into a documented fallback value.\n * Internal SDK code is deliberately NOT routed through this — an SDK bug\n * should fail loudly rather than degrade quietly.\n *\n * One consumer surface predates this and keeps its own equivalent:\n * `ProductsController` contains `products.fetch` / `products.transform` itself\n * because it also has to clear the strip on failure, which this class knows\n * nothing about. It follows the same log-once rule.\n *\n * One instance per `AIAutocomplete`, not module-level, for the reason\n * `ProductsController` gives: two autocompletes on one page have two sets of\n * consumer callbacks, and one's broken listener must not silence the\n * diagnostic for the other's.\n */\nexport class ConsumerBoundary {\n /** Labels already reported. Keyed per label so distinct callbacks each get one line. */\n private reported = new Set<string>();\n\n /**\n * Invoke consumer code that returns a value. Returns `undefined` if it threw\n * — callers treat that as \"no answer from the consumer\" and fall back to\n * whatever they would have used without the callback.\n */\n run<T>(label: string, fn: () => T): T | undefined {\n try {\n return fn();\n } catch (err) {\n this.report(label, err, label);\n return undefined;\n }\n }\n\n /**\n * Invoke a consumer listener for its side effects. Returns whether it\n * completed, so a caller whose next step is only valid if the consumer\n * succeeded (Tier 1's auto-reset after `onSubmit`) can skip it.\n */\n runListener(label: string, fn: () => void, dedupeKey = label): boolean {\n try {\n fn();\n return true;\n } catch (err) {\n this.report(label, err, dedupeKey);\n return false;\n }\n }\n\n /**\n * Once per dedupe key per instance, matching `ProductsController.logOnce`: a\n * listener that throws deterministically throws on every keystroke, and\n * hundreds of identical stacks would bury the consumer's own console.\n *\n * The key is separate from the label because one label can cover many\n * distinct callbacks — several `subscribe()` listeners, or several handlers\n * on one event. Keying on the label alone would report the first broken one\n * and hide every other, which is the opposite of what a diagnostic is for.\n * `label` is what the consumer reads; the key is what budgets the reporting.\n */\n private report(label: string, err: unknown, dedupeKey: string): void {\n if (this.reported.has(dedupeKey)) return;\n this.reported.add(dedupeKey);\n // biome-ignore lint/suspicious/noConsole: consumer-callback diagnostic\n console.error(\n `[AIAutocomplete] \"${label}\" threw. The error is contained — SDK state is unaffected. Later failures of \"${label}\" on this instance are not logged.`,\n err,\n );\n }\n}\n","import type { ConsumerBoundary } from \"./consumerBoundary\";\n\ntype 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 /** Per-registration dedupe keys for the boundary's log-once budget. */\n private keys = new WeakMap<Listener<never>, string>();\n private registrationCount = 0;\n\n /**\n * @param boundary isolation for the consumer callbacks registered here — see\n * {@link ConsumerBoundary}. Shared with the rest of the instance so a\n * listener that throws on every keystroke is reported once, not per event.\n */\n constructor(private boundary: ConsumerBoundary) {}\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 // Each registration gets its own dedupe key so a second broken handler on\n // the same event is still reported — see `ConsumerBoundary.report`.\n const key = `${String(event)}#${++this.registrationCount}`;\n const entry: Listener<E[K]> = (...args) => listener(...args);\n this.keys.set(entry, key);\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 /**\n * Fan out to every listener, containing any that throw.\n *\n * @returns whether all of them completed. Callers whose next step is only\n * valid if the consumer succeeded — Tier 1's auto-reset after `onSubmit` —\n * must gate on this: clearing the user's input after their submit handler\n * failed loses work they can't get back.\n */\n emit<K extends keyof E>(event: K, ...args: E[K]): boolean {\n const set = this.listeners[event];\n if (!set) return true;\n let allCompleted = true;\n for (const listener of set) {\n // Containment, not a swallow: each listener still runs even if an\n // earlier one threw, and the failure is reported by the boundary.\n const completed = this.boundary.runListener(\n String(event),\n () => listener(...args),\n this.keys.get(listener as Listener<never>) ?? String(event),\n );\n if (!completed) allCompleted = false;\n }\n return allCompleted;\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 { ProductsController } from \"./controllers/productsController\";\nimport { OPTIONS_GRID_MOBILE_QUERY } from \"./derive/optionsGridLayout\";\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 IdentifiedParamState,\n OptionOverrides,\n Product,\n SafeOptionOverrides,\n SuggestionOption,\n} from \"./shared-types\";\nimport { createDerivedStore, createStore } from \"./state\";\nimport { injectStyles } from \"./styleInjector\";\nimport type {\n CoreDeriveOptions,\n CoreInputState,\n CoreOptions,\n CoreState,\n RenderMode,\n} from \"./types\";\nimport { ConsumerBoundary } from \"./util/consumerBoundary\";\nimport { Emitter } from \"./util/Emitter\";\nimport { TimerScheduler } from \"./util/TimerScheduler\";\nimport { buildQuery } from \"./utils/buildQuery\";\nimport { removeChipSpan } from \"./utils/chipSpan\";\nimport { effectiveFilterBase, filterOptions } from \"./utils/filtering\";\nimport { ModeController } from \"./utils/modeController\";\nimport { coveredEnd, isTrailingCovered, rebaseAnchor } from \"./utils/pendingSpan\";\nimport { reconcileIdentifiedParams, 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 productSelect: [product: Product];\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\"\n | \"onError\"\n | \"onChange\"\n | \"onParamsChange\"\n | \"onStateChange\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onProductSelect\"\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 identifiedParams: [],\n skippedParams: [],\n pendingSpan: null,\n suggestions: [],\n products: [],\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 productsController: ProductsController;\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 /** One per instance — see {@link ConsumerBoundary}. Shared by the emitter, `subscribe()` and `optionOverrides`. */\n private boundary = new ConsumerBoundary();\n /** Identity of the raw override record the wrapped copy below was built from. */\n private rawOverrides: OptionOverrides | undefined;\n private wrappedOverrides: SafeOptionOverrides | undefined;\n private subscriberCount = 0;\n private emitter = new Emitter<AIAutocompleteEvents>(this.boundary);\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 // `emitSubmit` relays whether every handler completed so Tier 1 can skip its\n // auto-reset when the consumer's `onSubmit` threw.\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) =>\n deriveAll(inputs, this.deriveOpts()),\n );\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 if (opts.onProductSelect) this.emitter.on(\"productSelect\", opts.onProductSelect);\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 fetchNow: () => this.fetchNow(),\n });\n\n // Reads opts lazily so `update({ products })` swaps the integration\n // without rebuilding anything.\n this.productsController = new ProductsController(this.store, () => this.opts.products);\n\n this.fetchController = new FetchController(\n this.store,\n () => this.opts.apiConfig,\n // Wrapped, not raw: `applyOptionOverrides` runs the consumer's function\n // against every response, so this call site needs the same boundary the\n // derive layer gets. Raw, a throwing override discarded the whole\n // response and surfaced as a fetch `error` + `onError`.\n () => this.deriveOpts().optionOverrides,\n () => this.opts.maskCompletedText,\n () => (this.emitter.hasListeners(\"error\") ? this.emitError : undefined),\n () => this.sessionId,\n () => this.opts.additionalContext,\n {\n // The product strip joins the SDK's single fetch cadence here — same\n // debounce, same AbortController, same version guard. Not awaited: a\n // slow or failing product search must not hold up or affect\n // suggestions.\n onRequest: ({ query, signal, isCurrent }) => {\n // `.catch` rather than `void`: run() catches its own fetch/transform\n // failures, but a throwing store subscriber would escape as an\n // unhandled rejection.\n this.productsController.run(query, signal, isCurrent).catch(() => {});\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 startEditingParamAtCaret: (offset) => this.startEditingParamAtCaret(offset),\n exitEditMode: () => this.exitEditMode(),\n skipActivePill: () => this.skipActivePill(),\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 // Keep identified params valid against every text / completedParams\n // mutation, wherever it originates: typing, Backspace-into-pill, re-edit\n // replacement, selection, exact-match promotion, and the controlled\n // `setValue` / `setCompletedParams` entry points all mutate the store\n // outside handleChange, and any of them can edit an identified param's\n // text away or claim the span it occupied. Re-validate here — the single\n // choke point every mutation passes through — and drop what no longer\n // locates cleanly. Idempotent, so re-running after the fetch response\n // (which reconciles its own candidates) is harmless.\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.text === prev.text && next.completedParams === prev.completedParams) return;\n if (next.identifiedParams.length === 0) return;\n const { valid, invalid } = reconcileIdentifiedParams(\n next.text,\n next.completedParams,\n next.identifiedParams,\n );\n if (invalid.length > 0) this.store.set({ identifiedParams: valid });\n }),\n );\n\n // Close the pending span when its trailing text is resolved: covered by a\n // completed/identified param (checked after each fetch response and each\n // params change) or deleted back to/behind the anchor.\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n const span = next.pendingSpan;\n if (!span) return;\n if (\n next.text === prev.text &&\n next.completedParams === prev.completedParams &&\n next.identifiedParams === prev.identifiedParams\n ) {\n return;\n }\n // The anchor is a raw offset: an edit before it (re-edit replacement,\n // Backspace-into-pill) shifts the trailing text, so re-base it across\n // the splice first. A splice straddling the anchor makes it\n // untrackable — close rather than evaluate a stale offset.\n let anchor = span.anchor;\n if (next.text !== prev.text) {\n const rebased = rebaseAnchor(prev.text, next.text, anchor);\n if (rebased === null) {\n this.store.set({ pendingSpan: null });\n return;\n }\n anchor = rebased;\n }\n const deleted = next.text.slice(anchor).trim().length === 0;\n if (deleted || isTrailingCovered(next.segments, anchor)) {\n this.store.set({ pendingSpan: null });\n } else if (anchor !== span.anchor) {\n this.store.set({ pendingSpan: { anchor, snapshot: span.snapshot } });\n }\n }),\n );\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 * Locate the chip whose rendered text covers `offset`.\n *\n * Walks the derived `segments` — the exact thing the editor renders — rather\n * than re-deriving param positions here. That keeps chip hit-testing in step\n * with `deriveSegments` by construction (including params whose text repeats\n * earlier in the input, and identified params that had to dodge completed\n * coverage) instead of hand-mirroring its walk in a third place.\n *\n * Both chip kinds are returned: completed and identified render as visually\n * identical chips, so both must behave atomically under Backspace. Re-edit\n * remains completed-only — see `startEditingParamAtCaret`.\n */\n private findChipSpanAt(offset: number): {\n kind: \"completed\" | \"identified\";\n param: CompletedParamState | IdentifiedParamState;\n start: number;\n end: number;\n } | null {\n let pos = 0;\n for (const seg of this.store.get().segments) {\n const start = pos;\n pos += seg.value.length;\n if (seg.type === \"text\") continue;\n if (offset > start && offset <= pos) {\n return { kind: seg.type, param: seg.param, start, end: pos };\n }\n }\n return null;\n }\n\n /** Drop a located chip from whichever param array owns it. */\n private withoutChip(\n state: CoreState,\n span: { kind: \"completed\" | \"identified\"; param: { id: string } },\n ): Partial<CoreInputState> {\n return span.kind === \"completed\"\n ? { completedParams: state.completedParams.filter((p) => p.id !== span.param.id) }\n : { identifiedParams: state.identifiedParams.filter((p) => p.id !== span.param.id) };\n }\n\n /**\n * Backspace at the caret. A chip is atomic: the caret sits beside it, never\n * within it, so the two positions mean different things.\n *\n * - Caret exactly at the chip's trailing edge (the position a Backspace over\n * the following space leaves you in): delete the WHOLE chip, the way a\n * chip-style token behaves. Collapses the space seam it leaves behind so\n * the surrounding words don't end up double-spaced.\n * - Caret strictly inside the chip (only reachable by clicking into it):\n * drop the param so its text renders plain, and remove one grapheme — the\n * user keeps the phrase they had and can edit it by hand.\n *\n * Applies to both chip kinds. Completed and identified params render\n * identically, so they must delete identically; only the array the param is\n * dropped from differs.\n *\n * Returns true when a param was reconciled (caller should `preventDefault`).\n */\n removeParamAtCaret(offset: number): boolean {\n const span = this.findChipSpanAt(offset);\n if (!span) return false;\n const { text } = this.store.get();\n const { start: paramStart, end: paramEnd } = span;\n\n if (offset === paramEnd) {\n const { text: newText, removed } = removeChipSpan(text, paramStart, paramEnd);\n this.store.set((s) => ({\n text: newText,\n // Shift, don't merely clamp: every offset past the pill moved left by\n // `removed`. Clamping alone leaves a filterBase that pointed after the\n // pill aimed at the wrong word (or at the end of the text, which reads\n // as an empty filter query) until the next response resets it.\n filterBase: Math.min(\n s.filterBase > paramStart ? Math.max(paramStart, s.filterBase - removed) : s.filterBase,\n newText.length,\n ),\n ...this.withoutChip(s, span),\n pillTapped: false,\n activeDropdownIndex: -1,\n }));\n this.scheduleSetCursor(paramStart);\n return true;\n }\n\n const deleteStart = previousGraphemeBoundary(text, offset);\n const newText = text.slice(0, deleteStart) + text.slice(offset);\n this.store.set((s) => ({\n text: newText,\n // Clamped rather than shifted, unlike the whole-chip branch above: this\n // removes a single grapheme, so a stale filterBase is off by one rather\n // than by a whole chip, and the next response resets it. Kept as-is to\n // leave this long-standing path's behaviour untouched.\n filterBase: Math.min(s.filterBase, newText.length),\n ...this.withoutChip(s, span),\n pillTapped: false,\n activeDropdownIndex: -1,\n }));\n this.scheduleSetCursor(deleteStart);\n return true;\n }\n\n /**\n * ArrowLeft while the caret sits at a completed pill's trailing edge selects\n * the pill rather than moving the caret into it: re-edit turns on, the pill\n * renders highlighted, and the dropdown shows its cached options. The caret\n * stays put at the trailing edge — it never enters the pill.\n *\n * Identified chips are excluded: they carry no cached options and are\n * deliberately not re-editable (see `renderEditable`), even though Backspace\n * treats them atomically like any other chip.\n *\n * Returns true when re-edit started (caller should `preventDefault` so the\n * browser doesn't step the caret inside the `<strong>`).\n */\n startEditingParamAtCaret(offset: number): boolean {\n const span = this.findChipSpanAt(offset);\n if (!span || span.kind !== \"completed\" || span.end !== offset) return false;\n this.reEdit.start(span.param.id);\n return this.store.get().editingParam?.id === span.param.id;\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 // Consumer code (Tier 2/3 wrappers own the DOM). Isolated like every\n // other callback: it can't strand SDK state from inside a microtask,\n // but an uncaught error there is precisely the failure this boundary\n // exists to avoid — it lands as an unhandled rejection and fails\n // whichever unrelated test is in flight in a consumer's suite.\n const setCursor = this.opts.setCursor;\n if (setCursor) this.boundary.runListener(\"setCursor\", () => 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 /**\n * Announce a product selection. The rendered cards call this on activation;\n * headless consumers rendering their own strip call it themselves.\n *\n * Emitting is the entire behaviour — the SDK deliberately does not navigate\n * to `product.url`, because only the integration knows whether a selection\n * means \"open the PDP\", \"add to cart\" or \"drop the title into the input\".\n */\n selectProduct(product: Product) {\n this.emitter.emit(\"productSelect\", product);\n }\n\n handleTextChange(value: string) {\n this.handleChange(value);\n }\n\n /**\n * Skip the currently active pill (always index 0 of the actionable\n * suggestions) and promote the next pill to active. Invoked by ArrowRight at\n * the end of the input and by the dropdown's skip button; headless consumers\n * rendering their own skip affordance call it directly.\n *\n * The skipped suggestion is recorded in `skippedParams` so every subsequent\n * request (and the submit result) carries it as a `completed_params` entry\n * with `text: \"skipped\"` — otherwise the server has no way to know the user\n * declined it and keeps suggesting the same parameter. Deduped by type: the\n * same type skipped twice is one entry.\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 fetch we fire here\n * returns fresh suggestions. While cached pills remain we don't fetch — the\n * next pill is shown from cache and the skip rides along on whatever request\n * goes out next. Unlike an option selection, which fetches on every answer so\n * the next parameter is conditioned on it, a skip does not fetch on its own:\n * a decline carries less signal than an answer, and skipping through several\n * pills would otherwise cost a round-trip each.\n */\n skipActivePill() {\n const state = this.store.get();\n // Guarded in the core, not just the views: (a) during re-edit the visible\n // pill is an already answered param — the built-in UIs hide their skip\n // affordances, but a headless consumer's custom control must be safe too;\n // (b) for ~500ms after a selection (inSelectionAnimation) the answered\n // suggestion is still at index 0 while the fetch it triggered is in\n // flight — skipping in that window would record the just-given answer as\n // skipped. No-op in both states, matching selectOption's own re-edit\n // branching.\n if (state.editingParam || state.inSelectionAnimation) return;\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 const skipped = actionable[0];\n // The next actionable pill (was index 1) lands at index 0 → becomes active.\n const remaining = actionable.slice(1);\n const alreadySkipped = state.skippedParams.some((p) => p.type === skipped.type);\n this.store.set({\n suggestions: [...placeholders, ...remaining],\n pillTapped: remaining.length > 0,\n activeDropdownIndex: -1,\n ...(alreadySkipped\n ? {}\n : {\n skippedParams: [\n ...state.skippedParams,\n {\n id: crypto.randomUUID(),\n type: skipped.type,\n suggestionPlaceholder: skipped.text,\n },\n ],\n }),\n });\n if (remaining.length === 0) this.fetchNow();\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 /**\n * Subscribe to state changes. Listener receives the full (input + derived) shape.\n *\n * Consumer code, so it gets the same isolation as the `on*` events: a throw\n * is contained and reported rather than unwinding into the `store.set` that\n * triggered the notification. See {@link ConsumerBoundary}.\n */\n subscribe(listener: (state: CoreState) => void): () => void {\n // Own dedupe key per registration: several subscribers is the normal case,\n // and one broken one must not consume the reporting budget for the rest.\n const key = `subscribe#${++this.subscriberCount}`;\n return this.store.subscribe((next) => {\n this.boundary.runListener(\"subscribe\", () => listener(next), key);\n });\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 const previousProducts = this.opts.products;\n Object.assign(this.opts, opts);\n if (\"products\" in opts && opts.products !== previousProducts) {\n // A swapped (or removed) integration invalidates whatever the previous\n // one put on screen. Clear now; the next fetch repopulates from the new\n // config. `store.set` here also invalidates the derived memo, so\n // isDropdownOpen re-evaluates without the strip holding it open.\n this.productsController.clearNow();\n }\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 // Not derived state, but the empty set forces a re-render so the\n // dropdown's skip button appears/disappears without another mutation.\n opts.showSkipButton !== 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 cached pill can stand in\n // while the request below is in flight. A fetch fires either way.\n this.timers.clear(TIMER_SUGGESTION_REMOVAL);\n if (result.remainingActionable > 0) {\n // A cached next-pill exists. Remove the just-clicked suggestion after\n // the streak animation so that pill becomes active and carries the\n // loading state until the response replaces the whole set. The\n // animation keeps playing on the option while it's still in the DOM.\n //\n // Identity-based, so it has nothing to do once the response has landed\n // and swapped in fresh suggestion objects. Bail before the write rather\n // than relying on the filter to no-op: `filter` allocates a new array\n // either way, so an unguarded `set` would notify every subscriber and\n // re-render for a state that didn't change. That is now the COMMON path —\n // a fetch fires on every selection, and any response quicker than the\n // 500ms streak gets here first.\n const consumed = result.consumedSuggestion;\n this.timers.schedule(\n TIMER_SUGGESTION_REMOVAL,\n () => {\n if (!this.store.get().suggestions.includes(consumed)) return;\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` there's nothing cached to fall back to,\n // so we deliberately keep the just-selected suggestion in state until the\n // response lands — the dropdown then mirrors its pill/option layout\n // (count + widths) as the loading skeleton, instead of falling back to the\n // generic placeholder.\n\n // Answering a suggestion is itself the signal the server needs to pick the\n // next one, so the request goes out on every selection rather than only\n // once the cached pills are exhausted. Undebounced and issued here because\n // the scheduler's length-delta gate can't be trusted to fire for a\n // selection — see the `skipNextFetch` comment in computeSelectionPatch.\n //\n // After the store.set above, so the request carries the param just\n // completed. Subscribers are drained synchronously, which means the\n // scheduler has already seen `skipNextFetch` and stood down by this point.\n this.fetchNow();\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 /**\n * `this.opts` with every `optionOverrides` entry wrapped in the instance's\n * {@link ConsumerBoundary}.\n *\n * The derive layer calls these functions on the SDK's stack — from\n * `getState()`, and from inside the store's notification drain — so an\n * un-wrapped throw would unwind whatever internal operation triggered the\n * derive and abort delivery of every queued notification with it, taking the\n * instance down rather than just the override. Wrapped, a failed override\n * answers `undefined` and each call site falls back to the server's options.\n *\n * Memoized on the raw record's identity so a swapped integration is\n * re-wrapped while a stable one isn't re-wrapped on every derive. Note\n * `update({ optionOverrides })` only becomes visible on the next store write\n * — the derived layer memoizes on inputs identity, and `update` doesn't\n * invalidate it for this key. Pre-existing, and unchanged by the wrapping.\n */\n private deriveOpts(): CoreDeriveOptions {\n const raw = this.opts.optionOverrides;\n if (!raw) return this.opts;\n if (raw !== this.rawOverrides) {\n this.rawOverrides = raw;\n const wrapped: SafeOptionOverrides = {};\n for (const [type, fn] of Object.entries(raw)) {\n wrapped[type] = (query: string) =>\n this.boundary.run(`optionOverrides.${type}`, () => fn(query));\n }\n this.wrappedOverrides = wrapped;\n }\n return { ...this.opts, optionOverrides: this.wrappedOverrides };\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 showSkipButton() {\n return self.opts.showSkipButton ?? true;\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 skipActivePill: () => this.skipActivePill(),\n selectProduct: (product: Product) => this.selectProduct(product),\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 const render = () => {\n if (this.domRefs) {\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n }\n };\n this.subscribeBatchedRender(render);\n this.subscribeViewportBreakpoint(render);\n\n // Initial render\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n this.subscribeNewParamTimer();\n }\n\n private buildAndRenderDropdown() {\n const self = this;\n const dropdownOpts = {\n store: this.store,\n listboxId: this.listboxId,\n get showSkipButton() {\n return self.opts.showSkipButton ?? true;\n },\n selectOption: (option: SuggestionOption) => this.selectOption(option),\n setActivePill: (index: number) => this.pillsController.setActivePill(index),\n skipActivePill: () => this.skipActivePill(),\n selectProduct: (product: Product) => this.selectProduct(product),\n };\n\n this.dropdownRefs = buildDropdownOnly(this.container, dropdownOpts);\n\n const render = () => {\n if (this.dropdownRefs) {\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n }\n };\n this.subscribeBatchedRender(render);\n this.subscribeViewportBreakpoint(render);\n\n // Initial render\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n this.subscribeNewParamTimer();\n }\n\n /**\n * Re-render when the viewport crosses the mobile breakpoint, so the options\n * grid's mobile/web column policy (see computeOptionsGridLayout) updates live\n * while the dropdown is open and otherwise idle. Mirrors the matchMedia\n * listener in the React grid and the window:resize HostListener in Angular.\n * The unsubscribe is registered for cleanup in destroy().\n */\n private subscribeViewportBreakpoint(render: () => void) {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return;\n const mq = window.matchMedia(OPTIONS_GRID_MOBILE_QUERY);\n const onChange = () => render();\n mq.addEventListener(\"change\", onChange);\n this.unsubscribers.push(() => mq.removeEventListener(\"change\", onChange));\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 // Identified params reconcile the same way completed ones do — when the\n // user edits an identified pill's text the param is dropped and its\n // (edited) plain text remains — but that happens in the store subscriber\n // above, which the `text` set at the top of this method already drove.\n\n this.maybePromoteExactMatch(newValue);\n this.maybeOpenPendingSpan();\n }\n\n /**\n * Opens the pending span on the first keystroke past covered text: there is\n * unresolved trailing text beyond the covered offset (filterBase / last pill\n * end), actionable suggestions are on screen, and no span is already open.\n * The span snapshots those suggestions so `recently_suggested` can carry\n * them even after later responses replace what's on screen.\n */\n private maybeOpenPendingSpan() {\n const s = this.store.get();\n if (s.pendingSpan) return;\n if (s.actionableSuggestions.length === 0) return;\n const base = effectiveFilterBase(\n s.text,\n Math.min(s.filterBase, s.text.length),\n s.placeholderText,\n );\n const anchor = coveredEnd(s.segments, base);\n if (s.text.slice(anchor).trim().length === 0) return;\n this.store.set({ pendingSpan: { anchor, snapshot: s.actionableSuggestions } });\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 this.fetchNow();\n }\n\n /** Fire an immediate (undebounced) fetch for the current text + params. */\n private fetchNow() {\n const s = this.store.get();\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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":["index_exports","__export","AIAutocomplete","ATTRIBUTION_URL","ModeController","OPTIONS_GRID_MOBILE_QUERY","SKIPPED_PARAM_TEXT","buildAttributionUrl","buildQuery","buildSubmitResult","computeOptionsGridLayout","createStore","cursorIsAtEnd","extractPlainText","getCursorOffset","getFooterHint","isOptionsGridMobileViewport","optionsGridTemplateColumns","plainTextLength","previousGraphemeBoundary","renderEditableContent","setCursorOffset","withSkippedParams","__toCommonJS","TokenManager","config","forceRefresh","result","DEFAULT_API_ORIGIN","DEFAULT_SUGGEST_ENDPOINT","tokenManagers","isAccessTokenConfig","config","getApiKeyConfig","getTokenManager","manager","TokenManager","buildHeaders","apiConfig","buildApiKeyAuthHeader","apiKeyConfig","apiKey","SKIPPED_PARAM_TEXT","withSkippedParams","completed","skipped","filledTypes","p","entries","SDK_VERSION","hasWarnedMissingKey","generateRequestId","toWireParam","param","includeText","buildRequestBody","rawQuery","completedParams","sessionId","identifiedParams","recentlySuggested","skippedParams","additionalContext","rawCount","p","contactAccountCount","withSkippedParams","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","insertedRanges","pos","param","count","placeholder","findClean","from","idx","r","index","delta","effectiveFilterBase","text","filterBase","placeholderText","isTypingPlaceholderPrefix","completedParamCount","extractFilterQuery","isInProgress","rawRegion","spaceIdx","findPrefixOverlap","prefix","optionText","trimmed","words","optionLower","i","candidate","suffixStart","filterOptions","options","query","lower","o","findExactMatch","applyOptionOverrides","suggestions","overrides","s","fn","overridden","coveredEnd","segments","filterBase","pos","covered","seg","isTrailingCovered","anchor","end","buildRecentlySuggested","snapshot","current","result","seen","s","rebaseAnchor","prevText","nextText","minLen","prefix","suffix","locateCompleted","text","completedParams","located","missing","pos","param","idx","locateIdentified","completedIntervals","identifiedParams","c","deriveSegments","completed","identified","pills","i","a","b","result","pill","remaining","reconcileParams","l","reconcileIdentifiedParams","toError","err","DEBOUNCE_MS","SLOW_DEBOUNCE_MS","MIN_CHARS_DIFF","FetchController","store","getApiConfig","getOptionOverrides","getMaskCompletedText","getOnError","getSessionId","getAdditionalContext","callbacks","prevText","prevParams","next","rawQuery","completed","controller","version","textAtRequest","stateAtRequest","recentlySuggested","buildRecentlySuggested","res","fetchSuggestions","identifiedCandidates","item","newSuggestions","applyOptionOverrides","input","lastInput","currentText","filterBase","filterInProgress","inProgressIdx","active","s","extraParam","query","extractFilterQuery","match","findExactMatch","completedNow","identifiedParams","reconcileIdentifiedParams","carriedSkips","p","racedSkipTypes","sg","caughtError","attemptFetch","minDiff","placeholderText","effBase","effectiveFilterBase","currentQuery","tappableFiltered","filterOptions","o","hasExactMatch","isInFilterZone","isTypingPlaceholderPrefix","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","buildSubmitResult","text","completedParams","skippedParams","rawQuery","finalParams","buildQuery","withSkippedParams","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","caret","anchor","offset","buildSubmitResult","tappableOptionIndices","o","i","firstIdx","delta","bottomRowStart","tappable","buckets","listbox","el","gtc","tracks","index","options","optionEl","PillsController","store","callbacks","index","state","actionable","s","moved","rest","_","i","placeholders","rawQuery","buildQuery","nextSuggestions","firstTappable","o","ProductsController","store","getConfig","query","signal","isCurrent","config","raw","mapped","list","products","err","isAbortError","OPTIONS_GRID_MOBILE_QUERY","ROW_HEIGHT","RESERVED_BAND","computeOptionsGridLayout","count","isMobile","layout","cols","rows","optionsGridTemplateColumns","isOptionsGridMobileViewport","OPTIONS_GRID_MOBILE_QUERY","computeDropdownVisibility","inputs","opts","trigger","closeOnBlur","hasContent","focusGate","trimmedEnd","caretAtEnd","deriveAll","inputs","opts","segments","deriveSegments","actionableSuggestions","s","activeSuggestion","overrideFn","placeholderText","clampedFilterBase","effectiveFilterBase","filterQuery","isTypingPlaceholderPrefix","extractFilterQuery","baseOptions","inEditMode","filteredOptions","editingId","paramStillPresent","p","editCaret","editQuery","filterOptions","hideNonTappable","o","countsAsOption","activePillHasNoOptions","editOptions","activePillSourceOptions","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","removeChipSpan","text","start","end","before","after","dropsSeam","ReEditManager","deps","paramId","state","param","p","pos","anchor","idx","replacement","editing","tail","newText","removeChipSpan","newTail","s","offset","patch","option","buildQuery","o","before","after","optionText","needsTrailingSpace","caretPos","newParam","oldIdx","params","insertAt","result","tryPromoteExactMatch","ATTRIBUTION_URL","buildAttributionUrl","base","host","url","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","SECTION_LABEL","renderProductStrip","parent","products","listboxId","onSelect","onFocusChange","section","label","row","reconcileList","product","cardKey","buildCard","el","_product","i","setProductStripFocusable","root","focusable","cards","card","field","media","img","body","vendor","title","price","e","applyGridLayout","grid","count","cols","maxHeight","computeOptionsGridLayout","isOptionsGridMobileViewport","optionsGridTemplateColumns","renderSuggestionGrid","parent","options","activeIndex","onSelect","onHighlight","listboxId","loading","groupKey","renderOptions","resetScrollOnNewGroup","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","onSkip","hasRealPills","hasPills","hasOptions","hasProducts","isVisible","setProductStripFocusable","stack","wantsSkip","wantsPillBar","pillBar","pillScroll","renderPills","renderSkipButton","activeSuggestion","groupKey","renderSuggestionGrid","skeleton","width","bar","renderProductStrip","footer","createFooter","optionHighlighted","updateFooterHint","getFooterHint","orderSections","selectors","sections","selector","el","visible","loading","activePill","btn","nextKey","nextHint","key","hint","row","hintGroup","brandGroup","buildAttributionUrl","brand","badge","buildDropdownOnly","container","opts","dropdown","createDropdown","updateDropdownOnly","refs","state","renderDropdown","focused","CHIP_PADDING_X","MAX_TRACKING","chipTracking","textLength","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","buildSubmitResult","caretInside","ro","updateDOM","refs","pillPlacement","setActivePill","selectOption","store","activeDescendant","canSubmit","previousParamId","justSelected","renderEditableContent","inlineLoading","renderPills","clearPills","setCursorOffset","dropdownPill","dropdownActivePill","renderDropdown","i","focused","computeSelectionPatch","inputs","option","activeSuggestion","base","prefix","inputWasEmpty","inputIsPlaceholderPrefix","overlapChars","findPrefixOverlap","needsSpace","newText","finalText","optionStart","optionInFinal","completed","remainingActionable","o","createStore","initial","state","listeners","pending","notifying","patch","resolved","prev","drained","entry","next","previous","l","err","listener","createDerivedStore","base","derive","cachedInputs","cachedDerived","deriveCached","inputs","full","prevFull","nextFull","injected","injectStyles","style","STYLES","ConsumerBoundary","label","fn","err","dedupeKey","Emitter","boundary","event","listener","key","entry","args","set","allCompleted","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","ConsumerBoundary","Emitter","result","err","createDerivedStore","inputs","deriveAll","PillsController","rawQuery","selectedPill","otherPills","ReEditManager","offset","type","data","ProductsController","FetchController","query","signal","isCurrent","active","matched","o","KeyboardController","option","next","prev","valid","invalid","reconcileIdentifiedParams","span","anchor","rebased","rebaseAnchor","isTrailingCovered","injectStyles","wasFocused","unsub","mode","text","params","index","endOffset","pos","seg","start","state","p","paramStart","paramEnd","newText","removed","removeChipSpan","s","deleteStart","previousGraphemeBoundary","refs","setCursorOffset","setCursor","paramId","replacement","product","value","placeholders","actionable","skipped","remaining","alreadySkipped","focused","listener","key","event","callback","previousProducts","computeSelectionPatch","buildQuery","consumed","sg","queryData","source","sendTelemetry","raw","wrapped","fn","ModeController","self","renderOpts","e","id","buildDOM","render","updateDOM","dropdownOpts","buildDropdownOnly","updateDropdownOnly","mq","OPTIONS_GRID_MOBILE_QUERY","onChange","scheduled","newValue","reconcileParams","base","effectiveFilterBase","coveredEnd","editCaret","editQuery","filterOptions","completedParams","tryPromoteExactMatch"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/utils/tokenManager.ts","../src/utils/auth.ts","../src/utils/skippedParams.ts","../src/utils/api.ts","../src/utils/buildQuery.ts","../src/utils/filtering.ts","../src/utils/overrides.ts","../src/utils/pendingSpan.ts","../src/utils/segments.ts","../src/controllers/fetchController.ts","../src/dom/cursorUtils.ts","../src/utils/submitResult.ts","../src/controllers/keyboardController.ts","../src/controllers/pillsController.ts","../src/controllers/productsController.ts","../src/derive/optionsGridLayout.ts","../src/derive/dropdown.ts","../src/derive/state.ts","../src/promotion/promote.ts","../src/utils/chipSpan.ts","../src/reEdit/ReEditManager.ts","../src/utils/attribution.ts","../src/utils/footerHint.ts","../src/render/reconcileList.ts","../src/render/renderPills.ts","../src/render/renderProductStrip.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/consumerBoundary.ts","../src/util/Emitter.ts","../src/util/TimerScheduler.ts","../src/utils/modeController.ts","../src/utils/telemetry.ts","../src/AIAutocomplete.ts"],"sourcesContent":["// === Wire / shared types ===\n\n// === Core class ===\nexport { AIAutocomplete } from \"./AIAutocomplete\";\n// === Options-grid layout policy (shared by the React + Angular dropdowns) ===\nexport type { OptionsGridLayout } from \"./derive/optionsGridLayout\";\nexport {\n computeOptionsGridLayout,\n isOptionsGridMobileViewport,\n OPTIONS_GRID_MOBILE_QUERY,\n optionsGridTemplateColumns,\n} from \"./derive/optionsGridLayout\";\n// === Editor helpers (consumed by React's contentEditable shell) ===\nexport {\n cursorIsAtEnd,\n extractPlainText,\n getCursorOffset,\n plainTextLength,\n previousGraphemeBoundary,\n setCursorOffset,\n} from \"./dom/cursorUtils\";\nexport { renderEditableContent } from \"./render/renderEditable\";\nexport type {\n AccessTokenConfig,\n AccessTokenResult,\n APIConfig,\n APIKeyConfig,\n AppearanceMode,\n AutocompleteRequest,\n AutocompleteResponse,\n AutocompleteResult,\n CompletedParam,\n CompletedParamState,\n IdentifiedParam,\n IdentifiedParamState,\n InputItem,\n OptionOverrides,\n Product,\n ProductsConfig,\n RecentlySuggested,\n Segment,\n SkippedParamState,\n Suggestion,\n SuggestionOption,\n TaskKind,\n} from \"./shared-types\";\n// === Store primitives ===\nexport type { Store } from \"./state\";\nexport { createStore } from \"./state\";\n// === Core types ===\nexport type { CoreOptions, CoreState, RenderMode } from \"./types\";\n\n// === Utility helpers (consumed by React's Tier 1 component) ===\nexport { ATTRIBUTION_URL, buildAttributionUrl } from \"./utils/attribution\";\nexport { buildQuery } from \"./utils/buildQuery\";\nexport { getFooterHint } from \"./utils/footerHint\";\nexport { ModeController } from \"./utils/modeController\";\nexport { SKIPPED_PARAM_TEXT, withSkippedParams } from \"./utils/skippedParams\";\nexport { buildSubmitResult } from \"./utils/submitResult\";\n","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 { CompletedParam, SkippedParamState } from \"../shared-types\";\n\n/**\n * Sentinel `text` marking a `completed_params` entry the user skipped (→)\n * rather than filled. Sent regardless of `maskCompletedText` — it's a fixed\n * marker, never user-entered content.\n */\nexport const SKIPPED_PARAM_TEXT = \"skipped\";\n\n/**\n * Folds skipped suggestions into a wire `completed_params` array so the server\n * learns which parameters the user dismissed and can stop re-suggesting them.\n *\n * Skipped entries carry no placeholder: nothing was substituted into\n * `raw_query`, so a `{{TYPE_N}}` token would point at text that doesn't exist.\n * They're appended after the real params for the same reason — they have no\n * position in the query.\n *\n * A skip is dropped when a param of the same type ends up filled anyway (the\n * user skipped `goal`, then typed one): sending both would tell the server the\n * parameter is simultaneously answered and declined.\n */\nexport function withSkippedParams(\n completed: CompletedParam[],\n skipped: SkippedParamState[],\n): CompletedParam[] {\n if (skipped.length === 0) return completed;\n const filledTypes = new Set(completed.map((p) => p.type));\n const entries = skipped\n .filter((p) => !filledTypes.has(p.type))\n .map<CompletedParam>((p) => ({\n placeholder: \"\",\n type: p.type,\n text: SKIPPED_PARAM_TEXT,\n kind: null,\n }));\n return entries.length > 0 ? [...completed, ...entries] : completed;\n}\n","import type {\n APIConfig,\n AutocompleteRequest,\n AutocompleteResponse,\n CompletedParam,\n CompletedParamState,\n IdentifiedParamState,\n RecentlySuggested,\n SkippedParamState,\n} from \"../shared-types\";\nimport {\n buildApiKeyAuthHeader,\n buildHeaders,\n DEFAULT_SUGGEST_ENDPOINT,\n getTokenManager,\n isAccessTokenConfig,\n} from \"./auth\";\nimport { withSkippedParams } from \"./skippedParams\";\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 identifiedParams?: IdentifiedParamState[],\n recentlySuggested?: RecentlySuggested[],\n skippedParams?: SkippedParamState[],\n additionalContext?: Record<string, unknown>,\n generateStartingStateOptions?: boolean,\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 // Skipped suggestions ride in the same array, appended, marked with\n // `text: \"skipped\"` — see `withSkippedParams`.\n completed_params: withSkippedParams(\n completedParams.map((p) => toWireParam(p, includeText)),\n skippedParams ?? [],\n ),\n ...(identifiedParams &&\n identifiedParams.length > 0 && {\n identified_params: identifiedParams.map((p) => ({ type: p.type, value: p.text })),\n }),\n ...(recentlySuggested &&\n recentlySuggested.length > 0 && {\n recently_suggested: recentlySuggested,\n }),\n ...(contactAccountCount != null && { contact_account_count: contactAccountCount }),\n ...(additionalContext !== undefined && { additional_context: additionalContext }),\n ...(generateStartingStateOptions !== undefined && {\n generate_starting_state_options: generateStartingStateOptions,\n }),\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 /** Echo of the latest response's identified set. Omitted from the body when empty. */\n identifiedParams?: IdentifiedParamState[];\n /** Pending-span hint: suggestions on screen when the unresolved trailing text began. Omitted when absent/empty. */\n recentlySuggested?: RecentlySuggested[];\n /** Suggestions the user skipped (→). Appended to `completed_params` as `text: \"skipped\"`. */\n skippedParams?: SkippedParamState[];\n /** Optional user context, included to personalize suggested parameters and options. */\n additionalContext?: Record<string, unknown>;\n /**\n * When true, an empty query's initial suggestions are generated fresh\n * instead of the fixed defaults.\n */\n generateStartingStateOptions?: boolean;\n },\n): Promise<AutocompleteResponse> {\n const apiConfig = options.apiConfig;\n const includeText = !options.maskCompletedText;\n const body = buildRequestBody(\n rawQuery,\n completedParams,\n includeText,\n options.sessionId,\n options.identifiedParams,\n options.recentlySuggested,\n options.skippedParams,\n options.additionalContext,\n options.generateStartingStateOptions,\n );\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 advance a position cursor (params are appended in text order in\n * normal flows), so a short param value (e.g. quantity \"1\") can never match\n * INSIDE an already-inserted placeholder (e.g. the \"1\" in \"{{SIZE_1}}\") and\n * splice into it. Out-of-order params fall back to a from-zero rescan that\n * rejects any match overlapping a previously inserted placeholder; with no\n * clean match the param is left unreplaced (same as the text-not-found path).\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 const insertedRanges: { start: number; end: number }[] = [];\n let pos = 0;\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 // Find the first occurrence at/after `from` that doesn't overlap a\n // previously inserted placeholder.\n const findClean = (from: number): number => {\n let idx = result.indexOf(param.text, from);\n while (\n idx !== -1 &&\n insertedRanges.some((r) => idx < r.end && idx + param.text.length > r.start)\n ) {\n idx = result.indexOf(param.text, idx + 1);\n }\n return idx;\n };\n\n // Cursor search first; from-zero fallback for out-of-order params.\n let index = findClean(pos);\n if (index === -1) index = findClean(0);\n\n if (index !== -1) {\n result = result.slice(0, index) + placeholder + result.slice(index + param.text.length);\n const delta = placeholder.length - param.text.length;\n // A clean match never overlaps an inserted range, so every range is\n // entirely before or entirely after the match — shift the latter.\n for (const r of insertedRanges) {\n if (r.start >= index + param.text.length) {\n r.start += delta;\n r.end += delta;\n }\n }\n insertedRanges.push({ start: index, end: index + placeholder.length });\n // In-order match: advance the cursor past the insertion. Fallback match\n // before the cursor: keep the cursor at the same logical position,\n // shifted by the length change.\n pos = index >= pos ? index + placeholder.length : pos + delta;\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 * True while the user is still typing a prefix of the server-provided\n * placeholder and hasn't completed a param yet. In that window the typed text\n * is the user retyping the suggested lead-in, NOT a filter query — so the\n * dropdown must not filter by it and the scheduler must not fetch on it.\n *\n * Shared by `deriveAll` and `FetchController.scheduleFetch` so the two can't\n * disagree about whether the user is filtering.\n */\nexport function isTypingPlaceholderPrefix(\n text: string,\n completedParamCount: number,\n placeholderText: string,\n): boolean {\n return (\n completedParamCount === 0 &&\n text.length > 0 &&\n placeholderText.length > 0 &&\n placeholderText.toLowerCase().startsWith(text.toLowerCase())\n );\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 { SafeOptionOverrides, Suggestion } from \"../shared-types\";\n\n/**\n * Replaces a suggestion's server options with the consumer's override result\n * when an override function exists for the suggestion's type. The override is\n * invoked with an empty query — its unfiltered list — because the result is\n * stored as the suggestion's option set (exact-match promotion, fetch\n * suppression, and the re-edit cache all read it). Server options are ignored\n * entirely for overridden types, mirroring the derive layer, which never shows\n * them; an override returning [] means the suggestion genuinely has no options.\n */\nexport function applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: SafeOptionOverrides,\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 overridden = fn(\"\");\n // `undefined` only ever comes from the boundary containing a throw — a\n // successful override returning `[]` still means \"no options\" and is\n // honoured. Keep the server's options in that case rather than blanking\n // the suggestion on a consumer bug.\n return overridden ? { ...s, options: overridden } : s;\n });\n}\n","import type { RecentlySuggested, Segment, Suggestion } from \"../shared-types\";\n\n/**\n * The last text offset covered by server context or a pill: the maximum of\n * `filterBase` (callers pass the clamped/effective value) and the end offset\n * of the last completed/identified pill segment. Text past this offset is the\n * unresolved trailing text a pending span tracks.\n */\nexport function coveredEnd(segments: Segment[], filterBase: number): number {\n let pos = 0;\n let covered = filterBase;\n for (const seg of segments) {\n pos += seg.value.length;\n if (seg.type !== \"text\") covered = Math.max(covered, pos);\n }\n return covered;\n}\n\n/**\n * True when every non-whitespace character at/after `anchor` sits inside a\n * completed or identified pill segment — i.e. the span's trailing text has\n * been fully resolved.\n */\nexport function isTrailingCovered(segments: Segment[], anchor: number): boolean {\n let pos = 0;\n for (const seg of segments) {\n const end = pos + seg.value.length;\n if (seg.type === \"text\" && end > anchor) {\n const uncovered = seg.value.slice(Math.max(anchor - pos, 0));\n if (uncovered.trim().length > 0) return false;\n }\n pos = end;\n }\n return true;\n}\n\n/**\n * Builds the `recently_suggested` payload: span snapshot ∪ current on-screen\n * suggestions, snapshot entries first, deduped by type. Placeholder\n * suggestions are never actionable and are excluded from both sides.\n *\n * Deliberately uncapped: both sides are already bounded by what the server\n * chose to send in a single response, so the deduped union can't grow beyond\n * two responses' worth of suggestions. A client-side cap would only be a\n * second copy of the server's limit — one that silently clips the hint the\n * day the server raises it.\n */\nexport function buildRecentlySuggested(\n snapshot: Suggestion[],\n current: Suggestion[],\n): RecentlySuggested[] {\n const result: RecentlySuggested[] = [];\n const seen = new Set<string>();\n for (const s of [...snapshot, ...current]) {\n if (s.type === \"placeholder\" || seen.has(s.type)) continue;\n seen.add(s.type);\n result.push({ type: s.type, text: s.text });\n }\n return result;\n}\n\n/**\n * Re-bases a span anchor across a single contiguous text splice (typing,\n * re-edit replacement, Backspace-into-pill all splice one region):\n * - edit at/after the anchor → anchor unchanged\n * - edit entirely before the anchor → anchor shifted by the length delta\n * - edit straddling the anchor → `null` (the anchor can no longer be located;\n * callers should close the span rather than track a stale offset)\n */\nexport function rebaseAnchor(prevText: string, nextText: string, anchor: number): number | null {\n if (prevText === nextText) return anchor;\n const minLen = Math.min(prevText.length, nextText.length);\n let prefix = 0;\n while (prefix < minLen && prevText[prefix] === nextText[prefix]) prefix++;\n if (prefix >= anchor) return anchor;\n let suffix = 0;\n while (\n suffix < minLen - prefix &&\n prevText[prevText.length - 1 - suffix] === nextText[nextText.length - 1 - suffix]\n ) {\n suffix++;\n }\n // Edit region in the previous text: [prefix, prevText.length - suffix).\n if (prevText.length - suffix <= anchor) {\n return anchor + (nextText.length - prevText.length);\n }\n return null;\n}\n","import type { CompletedParamState, IdentifiedParamState, Segment } from \"../shared-types\";\n\ninterface CompletedInterval {\n start: number;\n end: number;\n param: CompletedParamState;\n}\n\ninterface IdentifiedInterval {\n start: number;\n end: number;\n param: IdentifiedParamState;\n}\n\n/**\n * Locates completed params in text left-to-right, first occurrence each,\n * advancing past every match — the shared walk behind deriveSegments and\n * reconcileParams.\n */\nfunction locateCompleted(text: string, completedParams: CompletedParamState[]) {\n const located: CompletedInterval[] = [];\n const missing: CompletedParamState[] = [];\n let pos = 0;\n for (const param of completedParams) {\n const idx = text.indexOf(param.text, pos);\n if (idx === -1) {\n missing.push(param);\n continue;\n }\n located.push({ start: idx, end: idx + param.text.length, param });\n pos = idx + param.text.length;\n }\n return { located, missing };\n}\n\n/**\n * Locates identified params in text left-to-right, skipping any occurrence\n * that overlaps completed-param coverage — identified params NEVER override\n * or overlap completed params. Params that don't cleanly locate are dropped.\n */\nfunction locateIdentified(\n text: string,\n completedIntervals: CompletedInterval[],\n identifiedParams: IdentifiedParamState[],\n) {\n const located: IdentifiedInterval[] = [];\n const missing: IdentifiedParamState[] = [];\n let pos = 0;\n for (const param of identifiedParams) {\n let idx = text.indexOf(param.text, pos);\n while (\n idx !== -1 &&\n completedIntervals.some((c) => idx < c.end && idx + param.text.length > c.start)\n ) {\n idx = text.indexOf(param.text, idx + 1);\n }\n if (idx === -1) {\n missing.push(param);\n continue;\n }\n located.push({ start: idx, end: idx + param.text.length, param });\n pos = idx + param.text.length;\n }\n return { located, missing };\n}\n\n/**\n * Derives segments for overlay rendering by matching completed params (then\n * identified params, in the remaining uncovered text) against the text.\n */\nexport function deriveSegments(\n text: string,\n completedParams: CompletedParamState[],\n identifiedParams: IdentifiedParamState[] = [],\n): Segment[] {\n const completed = locateCompleted(text, completedParams).located;\n const identified = locateIdentified(text, completed, identifiedParams).located;\n\n const pills: { start: number; end: number; segment: Segment }[] = [\n ...completed.map((c) => ({\n start: c.start,\n end: c.end,\n segment: { type: \"completed\", value: c.param.text, param: c.param } as Segment,\n })),\n ...identified.map((i) => ({\n start: i.start,\n end: i.end,\n segment: { type: \"identified\", value: i.param.text, param: i.param } as Segment,\n })),\n ].sort((a, b) => a.start - b.start);\n\n const result: Segment[] = [];\n let pos = 0;\n for (const pill of pills) {\n if (pill.start > pos) {\n result.push({ type: \"text\", value: text.slice(pos, pill.start) });\n }\n result.push(pill.segment);\n pos = pill.end;\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 { located, missing } = locateCompleted(text, completedParams);\n return { valid: located.map((l) => l.param), invalid: missing };\n}\n\n/**\n * Checks which identified params still locate in the new text outside\n * completed-param coverage. Mirrors reconcileParams for identified state:\n * a param whose text was edited away — or now only occurs inside a completed\n * param — is invalid and must be dropped (its plain text stays in the input).\n */\nexport function reconcileIdentifiedParams(\n text: string,\n completedParams: CompletedParamState[],\n identifiedParams: IdentifiedParamState[],\n): { valid: IdentifiedParamState[]; invalid: IdentifiedParamState[] } {\n const completed = locateCompleted(text, completedParams).located;\n const { located, missing } = locateIdentified(text, completed, identifiedParams);\n return { valid: located.map((l) => l.param), invalid: missing };\n}\n","import type {\n APIConfig,\n CompletedParamState,\n IdentifiedParamState,\n SafeOptionOverrides,\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 isTypingPlaceholderPrefix,\n} from \"../utils/filtering\";\nimport { applyOptionOverrides } from \"../utils/overrides\";\nimport { buildRecentlySuggested } from \"../utils/pendingSpan\";\nimport { reconcileIdentifiedParams } from \"../utils/segments\";\n\n/**\n * Total error coercion. `String(err)` can itself throw — a rejection value\n * whose `toString` throws — and this runs on the path that clears the loading\n * flag, so it must not be the thing that strands it.\n */\nfunction toError(err: unknown): Error {\n if (err instanceof Error) return err;\n try {\n return new Error(String(err));\n } catch {\n return new Error(\"Unknown error\");\n }\n}\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 * Fired once per outbound `/suggest` request, before it is awaited.\n *\n * The single point where side-channel work (today: the product strip) joins\n * the SDK's fetch cadence. It gets this request's own abort signal and a\n * staleness check bound to the same version counter, so it inherits the\n * debounce, the cancellation and the out-of-order protection instead of\n * running a second scheduler beside them. Whatever it returns is never\n * awaited here — the suggestions round-trip must not wait on it, and its\n * failures must not reach this controller's catch.\n */\n onRequest?: (ctx: { query: string; signal: AbortSignal; isCurrent: () => boolean }) => 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: () => SafeOptionOverrides | undefined,\n private getMaskCompletedText: () => boolean | undefined,\n private getOnError: () => ((error: Error) => void) | undefined,\n private getSessionId: () => string,\n private getAdditionalContext: () => Record<string, unknown> | undefined,\n private getGenerateStartingStateOptions: () => boolean | undefined,\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 // Side-channel work rides this request. `rawQuery` carries\n // `{{PLACEHOLDER}}` tokens, which are meaningless to a product search, so\n // hand over the plain text the user actually typed.\n //\n // Wrapped because this call sits outside the try below and before the\n // request is even issued: a synchronous throw from a listener would reject\n // doFetch() — which every caller invokes un-awaited — leaving an unhandled\n // rejection and no suggestions request at all. Isolation is the entire\n // point of the hook, so it is enforced here rather than trusted.\n try {\n this.callbacks.onRequest?.({\n query: this.store.get().text,\n signal: controller.signal,\n isCurrent: () => version === this.fetchVersion,\n });\n } catch {\n // Fire-and-forget by contract — never take the suggestions half down.\n }\n\n try {\n // Inside the try, not before it: this write notifies subscribers, and a\n // throw from one of them must not escape past the request. A stranded\n // `isLoading: true` with `error: null` and no request on the wire is\n // indistinguishable from a slow network on the consumer's side.\n this.store.set({ isLoading: true, error: null });\n\n // With a span open, hint the server about what was on screen when the\n // user started typing the unresolved trailing text: span snapshot ∪\n // current on-screen actionable suggestions. No span → no field.\n const stateAtRequest = this.store.get();\n const recentlySuggested = stateAtRequest.pendingSpan\n ? buildRecentlySuggested(\n stateAtRequest.pendingSpan.snapshot,\n stateAtRequest.actionableSuggestions,\n )\n : undefined;\n\n const res = await fetchSuggestions(rawQuery, completed, {\n sessionId: this.getSessionId(),\n maskCompletedText: this.getMaskCompletedText(),\n signal: controller.signal,\n apiConfig: this.getApiConfig(),\n identifiedParams: stateAtRequest.identifiedParams,\n recentlySuggested,\n skippedParams: stateAtRequest.skippedParams,\n additionalContext: this.getAdditionalContext(),\n generateStartingStateOptions: this.getGenerateStartingStateOptions(),\n });\n\n if (version !== this.fetchVersion) return;\n\n // Identified tokens from the response become candidate identified\n // params. They're validated against the CURRENT text below (inside the\n // final store.set), since the text may have changed while the request\n // was in flight.\n const identifiedCandidates: IdentifiedParamState[] = (res.data.input ?? [])\n .filter((item) => item.source === \"identified\")\n .map((item) => ({ id: crypto.randomUUID(), type: item.type, text: item.text }));\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 // Positionally validate candidates against the current text, outside\n // completed-param coverage (including a just-auto-matched param), and\n // REPLACE the identified set wholesale — latest response wins.\n const completedNow = extraParam ? [...s.completedParams, extraParam] : s.completedParams;\n const identifiedParams = reconcileIdentifiedParams(\n s.text,\n completedNow,\n identifiedCandidates,\n ).valid;\n // A skip performed while this request was on the wire isn't in its\n // `completed_params`, so the response may still suggest the type the\n // user just dismissed — writing it wholesale would visually resurrect\n // the skipped pill while `skippedParams` says otherwise. Drop ONLY\n // the types skipped after the request was issued (present now, absent\n // from the request-time snapshot). A skip the request DID carry is\n // different: if the server re-suggests that type anyway (a required\n // param it refuses to drop), the server wins and the pill comes back.\n const carriedSkips = new Set(stateAtRequest.skippedParams.map((p) => p.id));\n const racedSkipTypes = new Set(\n s.skippedParams.filter((p) => !carriedSkips.has(p.id)).map((p) => p.type),\n );\n const reconciledSuggestions =\n racedSkipTypes.size > 0\n ? newSuggestions.filter(\n (sg) => sg.type === \"placeholder\" || !racedSkipTypes.has(sg.type),\n )\n : newSuggestions;\n return {\n suggestions: reconciledSuggestions,\n isLoading: false,\n isReady: res.data.is_ready ?? false,\n lastRawQuery: rawQuery,\n activeDropdownIndex: -1,\n filterBase,\n filterInProgress,\n identifiedParams,\n ...(extraParam ? { completedParams: completedNow } : {}),\n };\n });\n } catch (err) {\n // Coerced before the guard, not inside the write below: `String(err)` on\n // a rejection value with a throwing `toString` would otherwise throw\n // between entering this block and clearing the flag.\n const caughtError = toError(err);\n if (version === this.fetchVersion) {\n this.store.set({ error: caughtError, isLoading: false });\n this.getOnError()?.(caughtError);\n }\n } finally {\n // Defense in depth for the invariant the spinner depends on: whichever\n // fetch is the current one owns `isLoading` and must not exit with it\n // still set. Every exit above already clears it — the success write, the\n // catch's write — and a superseded fetch (version bumped) deliberately\n // leaves the flag to the fetch that replaced it. So this should not fire;\n // it exists because the cost of being wrong is a permanent spinner with\n // `error: null`, which reads to the consumer as a slow network. Any\n // future exit added above inherits the guarantee instead of having to\n // remember it.\n if (version === this.fetchVersion && this.store.get().isLoading) {\n try {\n this.store.set({ isLoading: false });\n } catch {\n // The flag is already applied to state (the store mutates before it\n // notifies); only the notification failed. Nothing further to do —\n // swallowing here keeps a throwing subscriber from masking whatever\n // error is already propagating out of this frame.\n }\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 // Same predicate `deriveAll` uses to decide the text isn't a filter query.\n if (isTypingPlaceholderPrefix(s.text, s.completedParams.length, placeholderText)) {\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 type { AutocompleteResult, CompletedParamState, SkippedParamState } from \"../shared-types\";\nimport { buildQuery } from \"./buildQuery\";\nimport { withSkippedParams } from \"./skippedParams\";\n\n/**\n * Builds the `AutocompleteResult` handed to `onSubmit`: the placeholder-\n * tokenized raw query plus the completed params, with skipped suggestions\n * folded in (see {@link withSkippedParams}).\n *\n * Shared by every submit path — vanilla Enter / submit button, the React Tier 1\n * component, the Angular Tier 1 component — so they can't drift on what a\n * result contains.\n */\nexport function buildSubmitResult(\n text: string,\n completedParams: CompletedParamState[],\n skippedParams: SkippedParamState[] = [],\n): AutocompleteResult {\n const { rawQuery, completedParams: finalParams } = buildQuery(text, completedParams);\n return {\n query: text.trim(),\n raw_query: rawQuery,\n completed_params: withSkippedParams(finalParams, skippedParams),\n };\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 { buildSubmitResult } from \"../utils/submitResult\";\n\nexport interface KeyboardContext {\n columns: number;\n listboxId: string;\n /**\n * Returns the submit dispatcher, or undefined when nothing is listening. The\n * dispatcher reports whether every handler completed — see `afterSubmit`.\n */\n getOnSubmit: () => ((result: AutocompleteResult) => boolean) | 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 /**\n * Optional hook invoked after onSubmit fires (used by Tier 1 to auto-reset).\n *\n * Skipped when a submit handler threw: the reset clears the user's typed\n * query, and a consumer whose handler failed — a validation guard that\n * throws, say — must not silently lose it. Their error is contained and\n * reported by the ConsumerBoundary either way.\n */\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 /**\n * Tier 1 only: ArrowLeft onto a pill's trailing edge selects that pill —\n * re-edit on, options shown — instead of stepping the caret inside it.\n * Returns true when re-edit started.\n */\n startEditingParamAtCaret?: (offset: number) => boolean;\n /** Tier 1 only: exit re-edit mode (Escape, arrow-key escape). */\n exitEditMode?: () => void;\n /**\n * Skip the active pill (ArrowRight at the end of the input). Same action the\n * dropdown's skip button invokes — the core owns the logic so both entry\n * points share one implementation. See AIAutocomplete.skipActivePill.\n */\n skipActivePill: () => 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.ctx.skipActivePill();\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 break;\n }\n // At the grid's left edge there's nowhere left to go, so rather than\n // swallow the press, use it to select the pill the caret sits\n // against — merely hovering an option sets the highlight, so this is\n // a common state to be in. Only that: it deliberately does NOT fall\n // into the caret-movement branches below, because ArrowLeft from\n // column 0 while re-editing (reachable via ArrowLeft → ArrowDown\n // into the grid) would otherwise collapse the edit session the user\n // is browsing options for.\n if (!state.editingParam && this.ctx.startEditingParamAtCaret) {\n const caret = getEditableCaretOffset(e.target);\n if (caret != null) this.ctx.startEditingParamAtCaret(caret);\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 // A pill is atomic: pressing left while the caret already sits at its\n // trailing edge selects the whole pill (re-edit + its options) rather\n // than moving the caret into it. The caret offset is read before the\n // key takes effect, so from one character further right this is the\n // second press, not the first. preventDefault keeps the caret outside\n // the `<strong>`.\n if (this.ctx.startEditingParamAtCaret) {\n const offset = getEditableCaretOffset(e.target);\n if (offset != null && this.ctx.startEditingParamAtCaret(offset)) {\n e.preventDefault();\n }\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 completed = onSubmit(\n buildSubmitResult(state.text, state.completedParams, state.skippedParams),\n );\n if (completed) 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","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 { Product, ProductsConfig } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\n\n/**\n * Drives the dropdown's product strip.\n *\n * Deliberately owns **no timer and no AbortController of its own**. Every run\n * is kicked off by {@link FetchController} from inside the one request it was\n * already making, and rides that request's debounce, abort signal and version\n * counter. A second scheduler here would drift out of step with `/suggest`\n * (the prototype ran its own 180ms debounce alongside it and the two could\n * desynchronise) — so if you ever need products to refresh on some other\n * trigger, widen the existing scheduler rather than adding one here.\n *\n * Failure is silent by contract: a rejected fetch or a throwing transform\n * clears the strip, logs at most once per instance, and never touches\n * `isLoading` / `error` — those belong to the suggestions half, which must be\n * completely unaffected.\n */\nexport class ProductsController {\n /**\n * Per-instance, not module-level: two autocompletes on one page have two\n * integrations, and one's transient failure must not permanently silence\n * the diagnostic for the other's genuinely broken transform.\n */\n private hasLoggedError = false;\n\n constructor(\n private store: Store<CoreState>,\n private getConfig: () => ProductsConfig | undefined,\n ) {}\n\n /**\n * Run one product search alongside an outbound `/suggest` request.\n *\n * @param query what the user has typed. Empty queries never reach the\n * integration — the strip just clears.\n * @param signal the suggest request's signal; aborted when a newer query\n * supersedes this one.\n * @param isCurrent staleness check bound to the same fetch version the\n * suggest request uses, so an out-of-order response is\n * dropped rather than rendered.\n */\n async run(query: string, signal: AbortSignal, isCurrent: () => boolean): Promise<void> {\n const config = this.getConfig();\n // Unconfigured: not one observable side effect, not even a state write.\n if (!config) return;\n\n if (query.trim().length === 0) {\n this.clear(isCurrent);\n return;\n }\n\n try {\n const raw = await config.fetch(query, signal);\n // Two guards, not one: `isCurrent` catches a newer query that already\n // went out, `signal.aborted` catches an integration that resolved\n // instead of rejecting after we cancelled it.\n if (signal.aborted || !isCurrent()) return;\n\n const mapped = config.transform(raw);\n const list = Array.isArray(mapped) ? mapped : [];\n const products = config.limit != null ? list.slice(0, config.limit) : list;\n if (signal.aborted || !isCurrent()) return;\n this.commit(products);\n } catch (err) {\n // An abort is the SDK cancelling its own request — not a failure, and\n // the newer run owns the strip from here.\n if (signal.aborted || isAbortError(err)) return;\n this.logOnce(err);\n this.clear(isCurrent);\n }\n }\n\n /** Drop the strip's contents. Used when `update()` swaps the integration. */\n clearNow(): void {\n this.commit([]);\n }\n\n private clear(isCurrent: () => boolean): void {\n if (!isCurrent()) return;\n this.commit([]);\n }\n\n /** Skip no-op writes so an unchanged strip doesn't churn subscribers. */\n private commit(products: Product[]): void {\n if (products.length === 0 && this.store.get().products.length === 0) return;\n this.store.set({ products });\n }\n\n private logOnce(err: unknown): void {\n if (this.hasLoggedError) return;\n this.hasLoggedError = true;\n // biome-ignore lint/suspicious/noConsole: one-time integration diagnostic\n console.warn(\n \"[AIAutocomplete] products.fetch/transform failed — the product strip is hidden. Later failures on this instance are not logged.\",\n err,\n );\n }\n}\n\nfunction isAbortError(err: unknown): boolean {\n return err instanceof Error && err.name === \"AbortError\";\n}\n","/**\n * Rows/columns policy for the dropdown's options grid.\n *\n * All three packages render the same grid and must agree on the policy, so it\n * lives here (in the core) and is imported by the React and Angular shells\n * rather than reimplemented per package — the way the rest of the shared logic\n * works. Duplicating it left the row-height and footer-band constants free to\n * drift three ways with three green suites.\n */\n\n/** The viewport width the grid treats as \"phone\" — same breakpoint as the mobile footer. */\nexport const OPTIONS_GRID_MOBILE_QUERY = \"(max-width: 768px)\";\n\n/** Rows visible before the list scrolls, per viewport. */\nconst MOBILE_VISIBLE_ROWS = 5;\nconst WEB_VISIBLE_ROWS = 4;\n\n/** Option counts that get two balanced columns on web instead of one tall list. */\nconst TWO_COLUMN_MIN = 5;\nconst TWO_COLUMN_MAX = 6;\n\n/**\n * Fallback height of a single (unwrapped) option row: its line-height plus its\n * vertical padding, as declared by the option rule in each package's\n * stylesheet. Exposed as `--aia-option-row-height` so a consumer restyling the\n * option row can keep the cap in step; an option long enough to wrap is taller\n * than this, so the cap is an estimate for those.\n */\nconst ROW_HEIGHT = \"var(--aia-option-row-height, 37px)\";\n\n/**\n * The grid reserves the footer band as its own padding (`--aia-grid-scroll-top`\n * / `-bottom` in appearance.css) and the box is `border-box`, so the cap has to\n * carry the band on top of the rows or it eats one. Reading the same custom\n * properties the padding reads keeps the two in step — including when the\n * product strip zeroes the band, where a hardcoded band would have overshot by\n * its own height and shown a sliver of an extra row.\n */\nconst RESERVED_BAND = \"var(--aia-grid-scroll-top, 0px) + var(--aia-grid-scroll-bottom, 0px)\";\n\nexport interface OptionsGridLayout {\n /** Column count for `grid-template-columns`. */\n cols: number;\n /** Rows visible before the list scrolls. */\n rows: number;\n /** Value for `--aia-grid-max-height` — caps the scroll box at `rows` rows. */\n maxHeight: string;\n}\n\n/**\n * - Mobile: one column, five rows visible (scroll past five).\n * - Web: one column, four rows visible by default — but when there are exactly\n * five or six options, two balanced columns (so 5/6 fit without scrolling).\n * Columns fill row-major, so the two columns end up 3/2 (five) or 3/3 (six).\n */\nexport function computeOptionsGridLayout(count: number, isMobile: boolean): OptionsGridLayout {\n const layout = (cols: number, rows: number): OptionsGridLayout => ({\n cols,\n rows,\n maxHeight: `calc(${rows} * ${ROW_HEIGHT} + ${RESERVED_BAND})`,\n });\n if (isMobile) return layout(1, Math.min(count, MOBILE_VISIBLE_ROWS));\n if (count >= TWO_COLUMN_MIN && count <= TWO_COLUMN_MAX) {\n return layout(2, Math.ceil(count / 2));\n }\n return layout(1, Math.min(count, WEB_VISIBLE_ROWS));\n}\n\n/**\n * `grid-template-columns` for a fixed column count. Space-separated\n * `minmax(0,1fr)` tracks (no `repeat()`, no inner spaces) so rows fill\n * row-major — which the keyboard controller assumes — and the track count reads\n * back correctly wherever computed styles aren't fully resolved.\n */\nexport function optionsGridTemplateColumns(cols: number): string {\n return Array.from({ length: cols }, () => \"minmax(0,1fr)\").join(\" \");\n}\n\n/** Whether the current viewport is phone-width. `false` when there's no DOM. */\nexport function isOptionsGridMobileViewport(): boolean {\n return (\n typeof window !== \"undefined\" &&\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(OPTIONS_GRID_MOBILE_QUERY).matches\n );\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 * True when there's an active pill whose underlying option set is empty (the\n * server returned a suggestion with no options to choose from) — as opposed\n * to options that exist but were filtered away by the typed query. The\n * dropdown stays open in this case so the pill chip remains visible even\n * though there's nothing to pick.\n */\n activePillHasNoOptions: boolean;\n /**\n * True when the product strip has cards to show. The panel's two halves are\n * independent: products hold it open on their own, so option filtering\n * emptying the list no longer takes the strip down with it. Always false\n * when `opts.products` is unconfigured, which keeps every gate below exactly\n * as it was.\n */\n hasProducts: 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 // Keep the dropdown open when the active pill simply has no options (the\n // server returned a suggestion with an empty option set) so the pill chip\n // stays visible — as opposed to options that exist but were filtered away by\n // the typed query, which still closes the dropdown.\n const hasPillContent = hasOptions || inputs.activePillHasNoOptions;\n // Either half is enough to keep the panel open. Without this, filtering the\n // options down to zero closed the panel and took the product strip with it —\n // the exact gate the Shopify prototype had to render outside of.\n //\n // Only the *content* test widens: the trigger gates below are unchanged, so\n // `manual` still waits for a pill tap and `hidden` still never opens. Both\n // are explicit consumer opt-outs of an auto-opening panel, and a product\n // result is not a reason to overrule them.\n const hasContent = hasPillContent || inputs.hasProducts;\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 hasContent && 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 (hasContent || inputs.isLoading) && focusGate && caretAtEnd;\n }\n\n if (trigger === \"manual\") {\n return (hasContent || inputs.isLoading) && inputs.pillTapped;\n }\n\n return false;\n}\n","import type { Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreDerivedState, CoreDeriveOptions, CoreInputState } from \"../types\";\nimport {\n effectiveFilterBase,\n extractFilterQuery,\n filterOptions,\n isTypingPlaceholderPrefix,\n} 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: CoreDeriveOptions): CoreDerivedState {\n const segments = deriveSegments(inputs.text, inputs.completedParams, inputs.identifiedParams);\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 // What the user typed IS the filter query, with one exception: retyping the\n // placeholder's own lead-in (\"Cre\" of \"Create a\") is not filtering. That's\n // the same rule the fetch scheduler applies before it suppresses a request,\n // shared so the two can't disagree about whether the user is filtering.\n //\n // There's deliberately no \"has a response landed yet\" guard. It would be\n // dead code: `filterQuery` only ever reaches the active suggestion's options\n // (below), so with no suggestions there is nothing to filter either way. The\n // guard that used to sit here keyed off `lastRawQuery !== \"\"`, which the\n // mount fetch's empty raw query could never satisfy — so filtering from an\n // empty input never switched on at all and every option stayed on screen\n // while the user typed a prefix of one.\n const typingPlaceholderPrefix =\n clampedFilterBase === 0 &&\n isTypingPlaceholderPrefix(inputs.text, inputs.completedParams.length, placeholderText);\n const filterQuery = typingPlaceholderPrefix\n ? \"\"\n : extractFilterQuery(inputs.text, clampedFilterBase, inputs.filterInProgress);\n // `undefined` is the boundary's signal that this override threw; fall back\n // to the server's options for the suggestion. Every other result, `[]`\n // included, is the consumer's answer and is honoured as-is. See\n // `SafeOptionOverrides`.\n const baseOptions = activeSuggestion\n ? overrideFn\n ? (overrideFn(filterQuery.trim()) ?? activeSuggestion.options ?? [])\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 const hideNonTappable = opts.showNonTappableOptions === false;\n if (hideNonTappable) {\n filteredOptions = filteredOptions.filter((o) => o.is_tappable);\n }\n\n // An active pill with a genuinely empty option set (the server/override\n // returned no options at all) — distinct from options that were filtered away\n // by the typed query. Keeps the dropdown open so the pill chip stays visible.\n // Outside edit mode we measure the pill's UNFILTERED option source: for\n // overrides that's the override's result for an EMPTY query, not `baseOptions`\n // (already filtered by the typed query). An override returning [] for \"xyz\"\n // means \"nothing matched\", NOT \"this pill has no options\" — asking for the\n // unfiltered list tells the two apart. The override is only invoked here in\n // the non-edit branch, so edit mode pays no extra `overrideFn(\"\")` call.\n // When `showNonTappableOptions === false` the dropdown hides non-tappable\n // options, so the pill's *effective* option set is only its tappable ones. A\n // pill whose options are all non-tappable then has nothing to render and must\n // be treated as having no options, otherwise the dropdown closes and the pill\n // chip disappears.\n const countsAsOption = (o: SuggestionOption): boolean => (hideNonTappable ? o.is_tappable : true);\n let activePillHasNoOptions: boolean;\n if (inEditMode) {\n const editOptions = inputs.editingParam?.options ?? [];\n activePillHasNoOptions =\n inputs.editingParam != null && editOptions.filter(countsAsOption).length === 0;\n } else {\n const activePillSourceOptions = activeSuggestion\n ? overrideFn\n ? (overrideFn(\"\") ?? activeSuggestion.options ?? [])\n : (activeSuggestion.options ?? [])\n : [];\n activePillHasNoOptions =\n activeSuggestion != null && activePillSourceOptions.filter(countsAsOption).length === 0;\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 activePillHasNoOptions,\n hasProducts: inputs.products.length > 0,\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","/**\n * Removes the text span `[start, end)` that a chip occupied, together with the\n * separating space the chip leaves stranded.\n *\n * The space that separated the chip has nothing left to separate once the chip\n * is gone: \"a <chip> b\" would leave \"a b\", and a LEADING chip would leave\n * \" b\". Dropping it matters beyond cosmetics — a stranded \" \" is non-empty\n * text, so the placeholder stays suppressed (`renderEditableContent` keys off\n * `segments.length`) and the next request goes out as `raw_query: \" \"` instead\n * of taking `scheduleFetch`'s empty-session restart.\n *\n * Shared by the two routes that delete a whole chip — Backspace at the chip's\n * trailing edge (`removeParamAtCaret`) and a delete while the chip is selected\n * (`ReEditManager.replaceRange`) — so they can't disagree about the result.\n *\n * `removed` is how many characters came out in total; callers shift offsets\n * that sat after the chip (e.g. `filterBase`) by it.\n */\nexport function removeChipSpan(\n text: string,\n start: number,\n end: number,\n): { text: string; removed: number } {\n const before = text.slice(0, start);\n let after = text.slice(end);\n const dropsSeam = (before === \"\" || before.endsWith(\" \")) && after.startsWith(\" \");\n if (dropsSeam) after = after.slice(1);\n return { text: before + after, removed: end - start + (dropsSeam ? 1 : 0) };\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\";\nimport { removeChipSpan } from \"../utils/chipSpan\";\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 /** Immediate, undebounced fetch for the current text + params. See `selectOption`. */\n fetchNow: () => 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 // An empty replacement is a delete of the whole selected chip — the other\n // route to the same outcome as Backspace at the chip's trailing edge, so it\n // drops the stranded separating space the same way (see `removeChipSpan`\n // for why a lone \" \" is worse than cosmetic). A non-empty replacement keeps\n // the separator: the new text still needs it.\n const { text: newText } =\n replacement === \"\"\n ? removeChipSpan(state.text, anchor, tail)\n : { text: 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 // Changing an answer is answering: everything the server suggested after\n // this param was conditioned on the value just replaced, so it goes back\n // for a fresh set on the same terms as a first-time selection. Fired here\n // rather than left to the scheduler for the same reason — `skipNextFetch`\n // above stands the debounced path down, and its raw-query length gate\n // can't be relied on for a swap that may not change the query's length at\n // all. Deliberately after the replacement is committed, never during the\n // pick: the re-edit dropdown shows this param's own cached options, and a\n // response arriving mid-interaction would swap them under the user.\n this.deps.fetchNow();\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","/** Base URL for the \"AI Autocomplete\" branding/attribution link in the dropdown footer. */\nexport const ATTRIBUTION_URL = \"https://ai-autocomplete.com\";\n\n/**\n * Builds the attribution link URL, appending a `utm_source` query param derived\n * from the current page's hostname. This lets analytics attribute the referral to\n * the embedding site even when that site sends a `no-referrer` policy (which\n * otherwise strips the Referer header and surfaces the visit as `$direct`).\n *\n * Falls back to the bare base URL when there is no browser `location` (SSR /\n * non-browser env) or the hostname can't be read.\n */\nexport function buildAttributionUrl(base: string = ATTRIBUTION_URL): string {\n try {\n if (typeof window === \"undefined\" || !window.location) return base;\n const host = window.location.hostname;\n if (!host) return base;\n const url = new URL(base);\n url.searchParams.set(\"utm_source\", host);\n return url.toString();\n } catch {\n return base;\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 (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 { Product } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\nconst SECTION_LABEL = \"Products\";\n\n/**\n * Renders the dropdown's product strip: a labelled section holding a\n * horizontally scrolling row of product cards. Sits below the options grid and\n * above the footer, and renders only when there are products — the two halves\n * of the panel are independent, so an empty strip leaves no gap and an empty\n * options grid leaves the strip alone.\n *\n * Layout notes that are easy to get wrong (both cost the Shopify prototype a\n * round trip before this moved into the SDK):\n *\n * - The dropdown paints a background but never sets `color`, so anything here\n * relying on `color: inherit` would pick up the *host page's* text colour —\n * black text on a dark panel. Every text node in the strip resolves its\n * colour through the same `--aia-option-*` chain the options use (see\n * styles.css); none of it inherits.\n * - The options grid carries a deliberate negative bottom margin so the footer\n * rides up over its reserved band. Whatever follows the grid inherits that\n * pull. Rather than cancelling it from here, the dropdown is marked with\n * `data-aia-has-products` and appearance.css zeroes the band at its source —\n * with a strip in between, the footer no longer sits over the scrolling list\n * and there is nothing to reserve.\n */\nexport function renderProductStrip(\n parent: HTMLElement,\n products: Product[],\n listboxId: string,\n onSelect: (product: Product) => void,\n onFocusChange: (focused: boolean) => void,\n): void {\n let section = parent.querySelector<HTMLElement>(\".magicx-aia-products\");\n\n if (products.length === 0) {\n section?.remove();\n return;\n }\n\n if (!section) {\n section = document.createElement(\"section\");\n section.className = \"magicx-aia-products\";\n section.setAttribute(\"data-aia-products\", \"\");\n // `group` is the one role a listbox accepts around a set of options, so\n // the strip stays inside the listbox without breaking its content model.\n section.setAttribute(\"role\", \"group\");\n section.setAttribute(\"aria-labelledby\", `${listboxId}-products-label`);\n\n const label = document.createElement(\"div\");\n label.className = \"magicx-aia-products-label\";\n label.id = `${listboxId}-products-label`;\n label.textContent = SECTION_LABEL;\n\n const row = document.createElement(\"div\");\n row.className = \"magicx-aia-products-row\";\n row.setAttribute(\"data-aia-products-row\", \"\");\n\n section.append(label, row);\n parent.appendChild(section);\n }\n\n const row = section.querySelector<HTMLElement>(\".magicx-aia-products-row\");\n if (!row) return;\n\n reconcileList(row, products, {\n // Identity *and* content: a card built for one product holds that product\n // in its click closure, so a same-id result whose fields changed must be\n // rebuilt rather than reused with stale text and a stale handler. Cards\n // are otherwise reused across the (frequent) unrelated re-renders.\n keyOf: (product) => cardKey(product),\n create: (product) => buildCard(product, onSelect, onFocusChange),\n update: (el, _product, i) => {\n el.id = `${listboxId}-product-${i}`;\n el.dataset.aiaIndex = String(i);\n },\n });\n}\n\n/**\n * Cards are focusable, so while the strip is hidden they would still be\n * reachable by Tab. `renderDropdown` calls this on the way out of a closed\n * render (which deliberately leaves the last content in place so the panel can\n * fade out) to take them back out of the tab order.\n */\nexport function setProductStripFocusable(root: HTMLElement, focusable: boolean): void {\n const cards = root.querySelectorAll<HTMLElement>(\"[data-aia-product]\");\n for (const card of cards) card.tabIndex = focusable ? 0 : -1;\n}\n\nfunction cardKey(product: Product): string {\n return [product.id, product.title, product.url, product.imageUrl, product.price, product.vendor]\n .map((field) => field ?? \"\")\n .join(\"\\0\");\n}\n\nfunction buildCard(\n product: Product,\n onSelect: (product: Product) => void,\n onFocusChange: (focused: boolean) => void,\n): HTMLElement {\n // A real <a href> rather than a <div>: it keeps the browser's own link\n // affordances — cmd/ctrl-click for a new tab, middle-click, right-click →\n // \"copy link address\", and a visible target on hover. `role=\"option\"`\n // overrides how assistive tech announces it, which is the right call inside\n // a listbox; the deliberate trade is that AT no longer calls it a link.\n const card = document.createElement(\"a\");\n card.className = \"magicx-aia-product\";\n card.setAttribute(\"data-aia-product\", \"\");\n card.setAttribute(\"role\", \"option\");\n card.setAttribute(\"aria-selected\", \"false\");\n card.href = product.url;\n card.tabIndex = 0;\n\n const media = document.createElement(\"span\");\n media.className = \"magicx-aia-product-media\";\n if (product.imageUrl) {\n const img = document.createElement(\"img\");\n img.className = \"magicx-aia-product-image\";\n img.src = product.imageUrl;\n // The title is already in the card's accessible name; repeating it on the\n // image would have a screen reader read it twice.\n img.alt = \"\";\n img.loading = \"lazy\";\n img.decoding = \"async\";\n media.appendChild(img);\n } else {\n // Catalogues without images are common enough that the placeholder is a\n // first-class state, not an error state.\n media.setAttribute(\"data-aia-product-placeholder\", \"\");\n }\n card.appendChild(media);\n\n const body = document.createElement(\"span\");\n body.className = \"magicx-aia-product-body\";\n\n // Every field but the title is optional, so absent ones produce no element\n // at all — the column gap can't leave a hole for a box that isn't there.\n if (product.vendor) {\n const vendor = document.createElement(\"span\");\n vendor.className = \"magicx-aia-product-vendor\";\n vendor.textContent = product.vendor;\n body.appendChild(vendor);\n }\n\n const title = document.createElement(\"span\");\n title.className = \"magicx-aia-product-title\";\n title.textContent = product.title;\n body.appendChild(title);\n\n if (product.price) {\n const price = document.createElement(\"span\");\n price.className = \"magicx-aia-product-price\";\n price.textContent = product.price;\n body.appendChild(price);\n }\n\n card.appendChild(body);\n\n card.addEventListener(\"click\", (e) => {\n // Let the browser keep the clicks that mean \"open this somewhere else\" —\n // intercepting them would be the one thing an <a> was chosen for. Those\n // are navigations the user asked for explicitly, so they don't emit.\n if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button !== 0) return;\n e.preventDefault();\n onSelect(product);\n });\n\n card.addEventListener(\"keydown\", (e) => {\n if (e.key !== \"Enter\" && e.key !== \" \") return;\n e.preventDefault();\n onSelect(product);\n });\n\n // The panel closes on blur (default `closeDropdownOnBlur`), and the input\n // blurs the moment focus lands on a card. Report focus as still-inside so\n // tabbing into the strip doesn't shut the panel out from under it.\n card.addEventListener(\"focus\", () => onFocusChange(true));\n card.addEventListener(\"blur\", (e) => {\n const next = e.relatedTarget as HTMLElement | null;\n if (next?.closest(\"[data-aia-dropdown]\")) return;\n onFocusChange(false);\n });\n\n return card;\n}\n","import {\n computeOptionsGridLayout,\n isOptionsGridMobileViewport,\n optionsGridTemplateColumns,\n} from \"../derive/optionsGridLayout\";\nimport type { SuggestionOption } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\n/**\n * Applies the shared rows/columns policy inline on each render — the option\n * count drives it, so it can change between suggestion groups. React and\n * Angular apply the same `computeOptionsGridLayout` result to their own grids.\n */\nfunction applyGridLayout(grid: HTMLElement, count: number): void {\n const { cols, maxHeight } = computeOptionsGridLayout(count, isOptionsGridMobileViewport());\n grid.style.gridTemplateColumns = optionsGridTemplateColumns(cols);\n grid.style.setProperty(\"--aia-grid-max-height\", maxHeight);\n}\n\n/**\n * Renders the dropdown's option grid into `parent`: a `.aia-grid` element (the\n * intrinsic Grid layout primitive — columns auto-fit at 250px minimum and\n * stretch to share out the full width, scrollable with a capped height) with\n * one option element per suggestion. Vanilla analogue of React's\n * `SuggestionGrid` component. The grid reserves a band at its bottom edge and\n * pulls the footer up over it (see `--aia-grid-scroll-bottom` /\n * `--aia-grid-overlap-bottom` in appearance.css), so the list scrolls under\n * the footer and dissolves into its gradient background. Creates the grid on\n * first use, reuses it on later renders, and removes it when there are no\n * 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 groupKey = \"\",\n): void {\n let grid = parent.querySelector<HTMLElement>(\".aia-grid\");\n if (options.length === 0) {\n grid?.remove();\n return;\n }\n if (!grid) {\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 // 1fr (not a fixed 250px) so the columns share out the full width: a lone\n // column spans the whole options box instead of stopping at 250px.\n grid.style.setProperty(\"--aia-grid-max\", \"1fr\");\n grid.style.setProperty(\"--aia-grid-gap\", \"0\");\n parent.appendChild(grid);\n }\n applyGridLayout(grid, options.length);\n renderOptions(grid, options, activeIndex, onSelect, onHighlight, listboxId, loading);\n resetScrollOnNewGroup(grid, groupKey);\n}\n\n/**\n * Send the scroll position back to the top whenever the grid starts showing a\n * different suggestion's options (i.e. after a selection), so the next set is\n * read from its first option rather than from wherever the previous list was\n * scrolled to. Filtering within the same suggestion keeps its scroll position.\n */\nfunction resetScrollOnNewGroup(grid: HTMLElement, groupKey: string): void {\n if (grid.dataset.aiaGroup === groupKey) return;\n grid.dataset.aiaGroup = groupKey;\n grid.scrollTop = 0;\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 { Product, Suggestion, SuggestionOption } from \"../shared-types\";\nimport { buildAttributionUrl } from \"../utils/attribution\";\nimport { getFooterHint } from \"../utils/footerHint\";\nimport { renderPills } from \"./renderPills\";\nimport { renderProductStrip, setProductStripFocusable } from \"./renderProductStrip\";\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 /**\n * Whether the pill bar ends in the \"skip\" trailing button. Callers pass\n * false while re-editing a completed param — the bar then shows the param\n * being re-edited, which isn't skippable.\n */\n showSkipButton: boolean;\n /**\n * Extra disabled gate for the skip button beyond `isLoading`. Callers pass\n * `inSelectionAnimation`: the UI-facing loading flag is deliberately\n * suppressed during that window (no skeleton flicker while the answered\n * pill animates out), but the core's skipActivePill() no-ops in it — the\n * button must render disabled rather than swallow clicks silently.\n */\n skipDisabled: 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 /** Product strip contents. Always empty unless `opts.products` is configured. */\n products: Product[];\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n onPillClick: (index: number) => void;\n /** Skip the active pill — same action as ArrowRight at the end of the input. */\n onSkip: () => void;\n onProductSelect: (product: Product) => void;\n /** Focus moved into / out of the strip — keeps the panel open while a card holds focus. */\n onProductFocusChange: (focused: boolean) => 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 onSkip,\n } = state;\n\n const hasRealPills = pills.length > 0;\n const hasPills = showPills && hasRealPills;\n const hasOptions = filteredOptions.length > 0;\n const hasProducts = state.products.length > 0;\n // Either half is enough to keep the panel up: products render with no\n // suggestions, suggestions render with no products.\n const isVisible = isOpen && (hasOptions || hasPills || isLoading || hasProducts);\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 //\n // The one thing that can't be left as-is: focusable product cards inside a\n // hidden panel would still answer Tab.\n if (!isVisible) {\n setProductStripFocusable(dropdown, false);\n return;\n }\n\n // The band the footer rides up over only makes sense when the footer sits\n // directly on the scrolling option list. With a strip between them the\n // attribute tells appearance.css to zero it at the source — see\n // renderProductStrip's header comment.\n //\n // Deliberately *after* the early return above: a closing panel keeps its\n // last-rendered content for the fade, so removing the attribute here would\n // re-arm the grid's negative margin underneath a strip that is still on\n // screen and drop the footer onto it for the length of the transition. The\n // attribute has to describe the content the panel is showing, which is what\n // React and Angular do by reading their frozen snapshot.\n if (hasProducts) {\n dropdown.setAttribute(\"data-aia-has-products\", \"\");\n } else {\n dropdown.removeAttribute(\"data-aia-has-products\");\n }\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). The skip button\n // wants the bar too even when pills render elsewhere (pillPlacement\n // \"inline\"/\"hidden\" ⇒ showPills false): the bar then holds only the button,\n // pinned at its trailing edge — a skip-only row.\n //\n // Empty input hides the button: the footer hint doesn't advertise \"→ to\n // skip\" until the user has typed (it shows \"tab to select\" instead), so the\n // pristine starting state stays free of the affordance too. The keyboard\n // path (→) still works there.\n const wantsSkip = state.showSkipButton && hasRealPills && !state.isInputEmpty;\n const wantsPillBar = hasPills || (isLoading && showPills) || wantsSkip;\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 // Pills render inside a masked scroll wrapper, NOT the bar itself: the\n // trailing skip button is the bar's other child, and overflowing pills\n // must scroll and fade under the wrapper's right edge instead of pushing\n // the button out of the clipped dropdown. Mirrors React's .pillScroll and\n // Angular's .magicx-aia-pill-scroll.\n let pillScroll = pillBar.querySelector<HTMLElement>(\".magicx-aia-pill-scroll\");\n if (!pillScroll) {\n pillScroll = document.createElement(\"span\");\n pillScroll.className = \"magicx-aia-pill-scroll\";\n // Public styling hook: the scroll + fade moved off [data-aia-pillbar]\n // onto this wrapper, so consumers overriding the mask need a stable\n // selector for it.\n pillScroll.setAttribute(\"data-aia-pill-scroll\", \"\");\n pillBar.insertBefore(pillScroll, pillBar.firstChild);\n }\n // In a skip-only row the pill list stays empty (pills render inline in\n // the input), and the loading flag is withheld so the empty list doesn't\n // grow fallback skeleton pills the inline placement already shows.\n renderPills(\n pillScroll,\n showPills ? pills : [],\n 0,\n onPillClick,\n true,\n isLoading && showPills,\n isActivePillSelected,\n );\n // The trailing \"skip\" button only makes sense next to a real, skippable\n // pill — the fallback skeleton bar (loading with no cached pills) has\n // nothing to skip.\n renderSkipButton(pillBar, wantsSkip, isLoading || state.skipDisabled, pills[0], onSkip);\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 // Identity of the suggestion whose options are on screen — when it changes\n // (a selection moved us to the next parameter) the grid scrolls back to top.\n const activeSuggestion = state.suggestions[0];\n const groupKey = activeSuggestion ? `${activeSuggestion.type} ${activeSuggestion.text}` : \"\";\n\n renderSuggestionGrid(\n stack,\n filteredOptions,\n activeIndex,\n onSelect,\n onHighlight,\n state.listboxId,\n isLoading,\n groupKey,\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 // --- Product strip (below the options grid, above the footer) ---\n renderProductStrip(\n stack,\n state.products,\n state.listboxId,\n state.onProductSelect,\n state.onProductFocusChange,\n );\n setProductStripFocusable(dropdown, true);\n\n // --- Footer (chrome — always last; hidden with the dropdown) ---\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 (!footer.isConnected) stack.appendChild(footer);\n\n orderSections(stack, [\n \".magicx-aia-pill-bar\",\n \".aia-grid\",\n \".magicx-aia-skeleton-bars\",\n \".magicx-aia-products\",\n \".magicx-aia-footer\",\n ]);\n}\n\n/**\n * Puts the stack's sections back into their canonical top-to-bottom order.\n *\n * Each section appends itself on creation, and they come and go independently\n * across renders — a grid can be created after the product strip already\n * exists, landing below it. Appending in the right order once is therefore not\n * enough; the order has to be asserted every render.\n *\n * Only nodes actually out of place are moved: `insertBefore` on an attached\n * node is a DOM mutation, and doing it unconditionally would fire consumer\n * Mutation/ResizeObservers on every keystroke and every highlight change.\n */\nfunction orderSections(stack: HTMLElement, selectors: string[]): void {\n const sections = selectors\n .map((selector) => stack.querySelector<HTMLElement>(`:scope > ${selector}`))\n .filter((el): el is HTMLElement => el !== null);\n\n for (let i = 0; i < sections.length; i++) {\n if (stack.children[i] !== sections[i]) {\n stack.insertBefore(sections[i], stack.children[i] ?? null);\n }\n }\n}\n\n/**\n * The pill bar's trailing \"skip\" button. `margin-inline-start: auto` in its\n * rule pushes it to the bar's far edge — visually top-right when the dropdown\n * opens below the input; when `optionsPosition` is \"above\" the stack reverses\n * and the bar (button included) lands bottom-right. Disabled while loading,\n * matching the pills it sits beside.\n */\nfunction renderSkipButton(\n pillBar: HTMLElement,\n visible: boolean,\n loading: boolean,\n activePill: Suggestion | undefined,\n onSkip: () => void,\n) {\n let btn = pillBar.querySelector<HTMLButtonElement>(\".magicx-aia-skip\");\n if (!visible) {\n btn?.remove();\n return;\n }\n if (!btn) {\n btn = document.createElement(\"button\");\n btn.type = \"button\";\n btn.tabIndex = -1;\n btn.className = \"magicx-aia-skip\";\n btn.setAttribute(\"data-aia-skip\", \"\");\n btn.textContent = \"skip\";\n btn.addEventListener(\"mousedown\", (e) => e.preventDefault());\n pillBar.appendChild(btn);\n }\n // \"skip\" alone doesn't say what gets skipped — name the active pill for AT.\n btn.setAttribute(\"aria-label\", activePill ? `Skip ${activePill.text}` : \"Skip\");\n btn.disabled = loading;\n btn.onclick = loading ? null : () => onSkip();\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 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 = buildAttributionUrl();\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(row);\n return footer;\n}\n","import type { Product, 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 /** Whether the dropdown's pill bar ends in the \"skip\" trailing button. */\n showSkipButton: boolean;\n selectOption: (option: SuggestionOption) => void;\n setActivePill: (index: number) => void;\n skipActivePill: () => void;\n selectProduct: (product: Product) => 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 // Hidden while re-editing: an already answered param isn't skippable.\n showSkipButton: opts.showSkipButton && !state.editingParam,\n // The isLoading passed above is suppressed during the selection animation,\n // but skipActivePill() no-ops in that window — mirror the guard visually.\n skipDisabled: state.inSelectionAnimation,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n onSelect: opts.selectOption,\n onHighlight: (i) => opts.store.set({ activeDropdownIndex: i }),\n onPillClick: opts.setActivePill,\n onSkip: opts.skipActivePill,\n onProductSelect: opts.selectProduct,\n onProductFocusChange: (focused) => opts.store.set({ isFocused: focused }),\n });\n}\n","import { getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport type { Segment } from \"../shared-types\";\n\n// Horizontal padding (px, per side) applied to a completed-param chip by the\n// stylesheets. Kept here so the tracking compensation below stays in step with\n// it — change both together.\nconst CHIP_PADDING_X = 6;\n// Never tighten past this (px per character gap); a crushed chip reads worse\n// than a slightly-wide one, so keep the compensation subtle.\nconst MAX_TRACKING = 0.3;\n\n/**\n * Negative letter-spacing that offsets a chip's horizontal padding, so a\n * recognized phrase occupies about the same width as the same words in plain\n * text (no reflow when text becomes a chip). letter-spacing adds one gap per\n * character; spreading the 2×padding across them cancels it, clamped so short\n * chips aren't over-tightened.\n */\nfunction chipTracking(textLength: number): string {\n if (textLength <= 0) return \"0px\";\n const perChar = Math.min(MAX_TRACKING, (2 * CHIP_PADDING_X) / textLength);\n return `${(-perChar).toFixed(3)}px`;\n}\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 into the contentEditable input. Completed params are\n * emitted as `<strong data-seg=\"completed\">` runs carrying the\n * `magicx-aia-segment--completed` class, which the stylesheets restyle as\n * inline pills (rounded chips). The tag stays a plain editable `<strong>` — the\n * caret system counts its text as part of the editable plain text, so it must\n * NOT become non-editable. The unfilled-suggestion pills are NOT rendered here;\n * they live as a sibling element so the editable's subtree never contains\n * non-editable children. See renderInput.ts for the pill list placement.\n *\n * Skips rebuilds when the segment key is unchanged so an in-flight reveal\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.style.letterSpacing = chipTracking(seg.value.length);\n strong.textContent = seg.value;\n frag.appendChild(strong);\n } else if (seg.type === \"identified\") {\n // Identified pills reuse the completed styling class for now (per TDB:\n // no distinct styling) but carry data-seg=\"identified\" so the re-edit\n // click handler (which targets data-seg=\"completed\") never fires.\n const strong = doc.createElement(\"strong\");\n strong.dataset.seg = \"identified\";\n strong.dataset.paramId = seg.param.id;\n strong.className = \"magicx-aia-segment magicx-aia-segment--completed\";\n strong.style.letterSpacing = chipTracking(seg.value.length);\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 { buildSubmitResult } from \"../utils/submitResult\";\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 /** Whether the dropdown's pill bar ends in the \"skip\" trailing button. */\n showSkipButton: boolean;\n autoFocus?: boolean;\n /** Submit dispatcher. Reports whether every handler completed — see `afterSubmit`. */\n onSubmit?: (result: AutocompleteResult) => boolean;\n /**\n * Invoked after onSubmit fires — Tier 1 uses this to auto-reset. Skipped\n * when a handler threw, so a failed submit doesn't clear the user's query.\n */\n afterSubmit?: () => void;\n submitButton?: HTMLElement | null;\n selectOption: (option: import(\"../shared-types\").SuggestionOption) => void;\n setActivePill: (index: number) => void;\n /** Skip the active pill — the dropdown's skip button routes here. */\n skipActivePill: () => void;\n selectProduct: (product: import(\"../shared-types\").Product) => 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 // Only a COLLAPSED selection means \"the caret landed in a chip\". A range\n // selection (Cmd+A, shift-arrow, drag) anchors at its start, which lands\n // inside the first `<strong>` whenever the input begins with a chip —\n // starting re-edit there parks the caret on that chip and destroys the\n // user's selection.\n const enclosing = sel.isCollapsed ? findEnclosingParamId() : null;\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 completed = opts.onSubmit(\n buildSubmitResult(state.text, state.completedParams, state.skippedParams),\n );\n if (completed) opts.afterSubmit?.();\n },\n { signal },\n );\n }\n\n if (opts.autoFocus !== false) {\n input.focus();\n // Focusing an empty contentEditable doesn't always create a selection\n // Range, so no caret blinks until the user clicks. Place a collapsed caret\n // at the start so the field is visibly ready immediately.\n const doc = input.ownerDocument ?? document;\n const sel = doc.getSelection();\n const caretInside = sel && sel.rangeCount > 0 && input.contains(sel.anchorNode);\n if (sel && !caretInside) {\n const range = doc.createRange();\n range.selectNodeContents(input);\n range.collapse(true);\n sel.removeAllRanges();\n sel.addRange(range);\n }\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 // Re-edit shows the param being re-edited in the pill bar — an already\n // answered param isn't skippable, so the button hides for the duration.\n showSkipButton: opts.showSkipButton && !state.editingParam,\n // The isLoading passed above is suppressed during the selection animation,\n // but skipActivePill() no-ops in that window — mirror the guard visually.\n skipDisabled: state.inSelectionAnimation,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n onSelect: selectOption,\n onHighlight: (i) => store.set({ activeDropdownIndex: i }),\n onPillClick: setActivePill,\n onSkip: opts.skipActivePill,\n onProductSelect: opts.selectProduct,\n // A card taking focus blurs the input, which would otherwise close the\n // panel out from under the card the user just tabbed to.\n onProductFocusChange: (focused) => store.set({ isFocused: focused }),\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 // Every answered suggestion goes back to the server, so the next\n // parameter it suggests is conditioned on the answer just given rather\n // than replayed from the cache the previous response shipped. The\n // debounced scheduler must not be the thing that issues it: it gates on\n // a raw-query length delta (>= 2 chars, or >= 1 on the slow timer), and\n // a selection can move that length by less than that — buildQuery swaps\n // the option's text for a `{{TYPE_N}}` token, so a long option can leave\n // the query barely longer, or exactly the same length, than before.\n // `selectOption` fires the request itself (undebounced); this flag keeps\n // the scheduler from issuing a second, redundant one.\n skipNextFetch: true,\n inSelectionAnimation: true,\n // The selection consumes any open pending span: the typed trailing text\n // is replaced by the selected option (now a completed param).\n pendingSpan: null,\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\n// Runaway-cascade backstop for the notification drain, set far above any real\n// chain of reconcilers.\nconst MAX_DRAIN = 10_000;\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 // Notifications awaiting delivery, as (next, prev) pairs. A `set` called\n // from inside a listener applies to `state` immediately — `get()` is never\n // stale — but its notification is QUEUED rather than delivered inline.\n // Delivering it inline would finish the whole listener set for the newer\n // state and then resume the outer loop, handing every listener registered\n // after the caller a `next` that has already been superseded. Draining in\n // order instead means each listener sees a monotonic chain of pairs\n // (S0→S1, then S1→S2), so a stale snapshot can never arrive last and\n // overwrite a decision made on newer state.\n //\n // One knock-on: a throw from a downstream listener now surfaces at the\n // OUTERMOST set() rather than at the nested one, so a listener can no\n // longer try/catch around its own set() to contain a downstream failure.\n const pending: [next: S, prev: S][] = [];\n let notifying = false;\n return {\n get: () => state,\n set: (patch) => {\n const resolved = typeof patch === \"function\" ? patch(state) : patch;\n const prev = state;\n state = { ...state, ...resolved };\n pending.push([state, prev]);\n if (notifying) return;\n notifying = true;\n try {\n let drained = 0;\n for (let entry = pending.shift(); entry; entry = pending.shift()) {\n // A listener that sets unconditionally used to blow the stack with a\n // RangeError, since delivery recursed. The drain loop is flat, so the\n // same bug would spin forever and freeze the tab instead. Keep the\n // failure loud and bounded. Legitimate cascades settle in 2-3 hops.\n if (++drained > MAX_DRAIN) {\n pending.length = 0;\n throw new Error(\n `createStore: notifications did not settle after ${MAX_DRAIN} deliveries — a listener is likely calling set() on every notification`,\n );\n }\n const [next, previous] = entry;\n for (const l of listeners) l(next, previous);\n }\n } catch (err) {\n // A throwing listener aborts delivery, as it always has. Drop what's\n // still queued rather than replaying it later against a state it no\n // longer describes.\n pending.length = 0;\n throw err;\n } finally {\n notifying = false;\n }\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","/**\n * The single isolation boundary between the SDK and consumer-supplied\n * functions.\n *\n * Every function-typed option a consumer can pass — the `on*` events,\n * `subscribe()` listeners, `optionOverrides` — is SDK-invoked code running on\n * the SDK's stack, and most of those stacks originate inside a `store.set`.\n * `store.set` notifies synchronously and lets a throwing listener propagate\n * (see `state.ts`), so an un-isolated consumer throw does not merely fail\n * itself: it unwinds whatever internal operation triggered the notification,\n * aborts the notification drain, and discards every queued notification with\n * it. That is how a throwing `onStateChange` used to leave `doFetch` pinned at\n * `isLoading: true` with no request issued, and how a throwing\n * `optionOverrides` entry could make `setValue()` throw at its caller.\n *\n * So: consumer code runs through here, never called directly. A throw is\n * contained, reported once, and turned into a documented fallback value.\n * Internal SDK code is deliberately NOT routed through this — an SDK bug\n * should fail loudly rather than degrade quietly.\n *\n * One consumer surface predates this and keeps its own equivalent:\n * `ProductsController` contains `products.fetch` / `products.transform` itself\n * because it also has to clear the strip on failure, which this class knows\n * nothing about. It follows the same log-once rule.\n *\n * One instance per `AIAutocomplete`, not module-level, for the reason\n * `ProductsController` gives: two autocompletes on one page have two sets of\n * consumer callbacks, and one's broken listener must not silence the\n * diagnostic for the other's.\n */\nexport class ConsumerBoundary {\n /** Labels already reported. Keyed per label so distinct callbacks each get one line. */\n private reported = new Set<string>();\n\n /**\n * Invoke consumer code that returns a value. Returns `undefined` if it threw\n * — callers treat that as \"no answer from the consumer\" and fall back to\n * whatever they would have used without the callback.\n */\n run<T>(label: string, fn: () => T): T | undefined {\n try {\n return fn();\n } catch (err) {\n this.report(label, err, label);\n return undefined;\n }\n }\n\n /**\n * Invoke a consumer listener for its side effects. Returns whether it\n * completed, so a caller whose next step is only valid if the consumer\n * succeeded (Tier 1's auto-reset after `onSubmit`) can skip it.\n */\n runListener(label: string, fn: () => void, dedupeKey = label): boolean {\n try {\n fn();\n return true;\n } catch (err) {\n this.report(label, err, dedupeKey);\n return false;\n }\n }\n\n /**\n * Once per dedupe key per instance, matching `ProductsController.logOnce`: a\n * listener that throws deterministically throws on every keystroke, and\n * hundreds of identical stacks would bury the consumer's own console.\n *\n * The key is separate from the label because one label can cover many\n * distinct callbacks — several `subscribe()` listeners, or several handlers\n * on one event. Keying on the label alone would report the first broken one\n * and hide every other, which is the opposite of what a diagnostic is for.\n * `label` is what the consumer reads; the key is what budgets the reporting.\n */\n private report(label: string, err: unknown, dedupeKey: string): void {\n if (this.reported.has(dedupeKey)) return;\n this.reported.add(dedupeKey);\n // biome-ignore lint/suspicious/noConsole: consumer-callback diagnostic\n console.error(\n `[AIAutocomplete] \"${label}\" threw. The error is contained — SDK state is unaffected. Later failures of \"${label}\" on this instance are not logged.`,\n err,\n );\n }\n}\n","import type { ConsumerBoundary } from \"./consumerBoundary\";\n\ntype 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 /** Per-registration dedupe keys for the boundary's log-once budget. */\n private keys = new WeakMap<Listener<never>, string>();\n private registrationCount = 0;\n\n /**\n * @param boundary isolation for the consumer callbacks registered here — see\n * {@link ConsumerBoundary}. Shared with the rest of the instance so a\n * listener that throws on every keystroke is reported once, not per event.\n */\n constructor(private boundary: ConsumerBoundary) {}\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 // Each registration gets its own dedupe key so a second broken handler on\n // the same event is still reported — see `ConsumerBoundary.report`.\n const key = `${String(event)}#${++this.registrationCount}`;\n const entry: Listener<E[K]> = (...args) => listener(...args);\n this.keys.set(entry, key);\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 /**\n * Fan out to every listener, containing any that throw.\n *\n * @returns whether all of them completed. Callers whose next step is only\n * valid if the consumer succeeded — Tier 1's auto-reset after `onSubmit` —\n * must gate on this: clearing the user's input after their submit handler\n * failed loses work they can't get back.\n */\n emit<K extends keyof E>(event: K, ...args: E[K]): boolean {\n const set = this.listeners[event];\n if (!set) return true;\n let allCompleted = true;\n for (const listener of set) {\n // Containment, not a swallow: each listener still runs even if an\n // earlier one threw, and the failure is reported by the boundary.\n const completed = this.boundary.runListener(\n String(event),\n () => listener(...args),\n this.keys.get(listener as Listener<never>) ?? String(event),\n );\n if (!completed) allCompleted = false;\n }\n return allCompleted;\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 { ProductsController } from \"./controllers/productsController\";\nimport { OPTIONS_GRID_MOBILE_QUERY } from \"./derive/optionsGridLayout\";\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 IdentifiedParamState,\n OptionOverrides,\n Product,\n SafeOptionOverrides,\n SuggestionOption,\n} from \"./shared-types\";\nimport { createDerivedStore, createStore } from \"./state\";\nimport { injectStyles } from \"./styleInjector\";\nimport type {\n CoreDeriveOptions,\n CoreInputState,\n CoreOptions,\n CoreState,\n RenderMode,\n} from \"./types\";\nimport { ConsumerBoundary } from \"./util/consumerBoundary\";\nimport { Emitter } from \"./util/Emitter\";\nimport { TimerScheduler } from \"./util/TimerScheduler\";\nimport { buildQuery } from \"./utils/buildQuery\";\nimport { removeChipSpan } from \"./utils/chipSpan\";\nimport { effectiveFilterBase, filterOptions } from \"./utils/filtering\";\nimport { ModeController } from \"./utils/modeController\";\nimport { coveredEnd, isTrailingCovered, rebaseAnchor } from \"./utils/pendingSpan\";\nimport { reconcileIdentifiedParams, 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 productSelect: [product: Product];\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\"\n | \"onError\"\n | \"onChange\"\n | \"onParamsChange\"\n | \"onStateChange\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onProductSelect\"\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 identifiedParams: [],\n skippedParams: [],\n pendingSpan: null,\n suggestions: [],\n products: [],\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 productsController: ProductsController;\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 /** One per instance — see {@link ConsumerBoundary}. Shared by the emitter, `subscribe()` and `optionOverrides`. */\n private boundary = new ConsumerBoundary();\n /** Identity of the raw override record the wrapped copy below was built from. */\n private rawOverrides: OptionOverrides | undefined;\n private wrappedOverrides: SafeOptionOverrides | undefined;\n private subscriberCount = 0;\n private emitter = new Emitter<AIAutocompleteEvents>(this.boundary);\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 // `emitSubmit` relays whether every handler completed so Tier 1 can skip its\n // auto-reset when the consumer's `onSubmit` threw.\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) =>\n deriveAll(inputs, this.deriveOpts()),\n );\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 if (opts.onProductSelect) this.emitter.on(\"productSelect\", opts.onProductSelect);\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 fetchNow: () => this.fetchNow(),\n });\n\n // Reads opts lazily so `update({ products })` swaps the integration\n // without rebuilding anything.\n this.productsController = new ProductsController(this.store, () => this.opts.products);\n\n this.fetchController = new FetchController(\n this.store,\n () => this.opts.apiConfig,\n // Wrapped, not raw: `applyOptionOverrides` runs the consumer's function\n // against every response, so this call site needs the same boundary the\n // derive layer gets. Raw, a throwing override discarded the whole\n // response and surfaced as a fetch `error` + `onError`.\n () => this.deriveOpts().optionOverrides,\n () => this.opts.maskCompletedText,\n () => (this.emitter.hasListeners(\"error\") ? this.emitError : undefined),\n () => this.sessionId,\n () => this.opts.additionalContext,\n () => this.opts.generateStartingStateOptions,\n {\n // The product strip joins the SDK's single fetch cadence here — same\n // debounce, same AbortController, same version guard. Not awaited: a\n // slow or failing product search must not hold up or affect\n // suggestions.\n onRequest: ({ query, signal, isCurrent }) => {\n // `.catch` rather than `void`: run() catches its own fetch/transform\n // failures, but a throwing store subscriber would escape as an\n // unhandled rejection.\n this.productsController.run(query, signal, isCurrent).catch(() => {});\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 startEditingParamAtCaret: (offset) => this.startEditingParamAtCaret(offset),\n exitEditMode: () => this.exitEditMode(),\n skipActivePill: () => this.skipActivePill(),\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 // Keep identified params valid against every text / completedParams\n // mutation, wherever it originates: typing, Backspace-into-pill, re-edit\n // replacement, selection, exact-match promotion, and the controlled\n // `setValue` / `setCompletedParams` entry points all mutate the store\n // outside handleChange, and any of them can edit an identified param's\n // text away or claim the span it occupied. Re-validate here — the single\n // choke point every mutation passes through — and drop what no longer\n // locates cleanly. Idempotent, so re-running after the fetch response\n // (which reconciles its own candidates) is harmless.\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.text === prev.text && next.completedParams === prev.completedParams) return;\n if (next.identifiedParams.length === 0) return;\n const { valid, invalid } = reconcileIdentifiedParams(\n next.text,\n next.completedParams,\n next.identifiedParams,\n );\n if (invalid.length > 0) this.store.set({ identifiedParams: valid });\n }),\n );\n\n // Close the pending span when its trailing text is resolved: covered by a\n // completed/identified param (checked after each fetch response and each\n // params change) or deleted back to/behind the anchor.\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n const span = next.pendingSpan;\n if (!span) return;\n if (\n next.text === prev.text &&\n next.completedParams === prev.completedParams &&\n next.identifiedParams === prev.identifiedParams\n ) {\n return;\n }\n // The anchor is a raw offset: an edit before it (re-edit replacement,\n // Backspace-into-pill) shifts the trailing text, so re-base it across\n // the splice first. A splice straddling the anchor makes it\n // untrackable — close rather than evaluate a stale offset.\n let anchor = span.anchor;\n if (next.text !== prev.text) {\n const rebased = rebaseAnchor(prev.text, next.text, anchor);\n if (rebased === null) {\n this.store.set({ pendingSpan: null });\n return;\n }\n anchor = rebased;\n }\n const deleted = next.text.slice(anchor).trim().length === 0;\n if (deleted || isTrailingCovered(next.segments, anchor)) {\n this.store.set({ pendingSpan: null });\n } else if (anchor !== span.anchor) {\n this.store.set({ pendingSpan: { anchor, snapshot: span.snapshot } });\n }\n }),\n );\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 * Locate the chip whose rendered text covers `offset`.\n *\n * Walks the derived `segments` — the exact thing the editor renders — rather\n * than re-deriving param positions here. That keeps chip hit-testing in step\n * with `deriveSegments` by construction (including params whose text repeats\n * earlier in the input, and identified params that had to dodge completed\n * coverage) instead of hand-mirroring its walk in a third place.\n *\n * Both chip kinds are returned: completed and identified render as visually\n * identical chips, so both must behave atomically under Backspace. Re-edit\n * remains completed-only — see `startEditingParamAtCaret`.\n */\n private findChipSpanAt(offset: number): {\n kind: \"completed\" | \"identified\";\n param: CompletedParamState | IdentifiedParamState;\n start: number;\n end: number;\n } | null {\n let pos = 0;\n for (const seg of this.store.get().segments) {\n const start = pos;\n pos += seg.value.length;\n if (seg.type === \"text\") continue;\n if (offset > start && offset <= pos) {\n return { kind: seg.type, param: seg.param, start, end: pos };\n }\n }\n return null;\n }\n\n /** Drop a located chip from whichever param array owns it. */\n private withoutChip(\n state: CoreState,\n span: { kind: \"completed\" | \"identified\"; param: { id: string } },\n ): Partial<CoreInputState> {\n return span.kind === \"completed\"\n ? { completedParams: state.completedParams.filter((p) => p.id !== span.param.id) }\n : { identifiedParams: state.identifiedParams.filter((p) => p.id !== span.param.id) };\n }\n\n /**\n * Backspace at the caret. A chip is atomic: the caret sits beside it, never\n * within it, so the two positions mean different things.\n *\n * - Caret exactly at the chip's trailing edge (the position a Backspace over\n * the following space leaves you in): delete the WHOLE chip, the way a\n * chip-style token behaves. Collapses the space seam it leaves behind so\n * the surrounding words don't end up double-spaced.\n * - Caret strictly inside the chip (only reachable by clicking into it):\n * drop the param so its text renders plain, and remove one grapheme — the\n * user keeps the phrase they had and can edit it by hand.\n *\n * Applies to both chip kinds. Completed and identified params render\n * identically, so they must delete identically; only the array the param is\n * dropped from differs.\n *\n * Returns true when a param was reconciled (caller should `preventDefault`).\n */\n removeParamAtCaret(offset: number): boolean {\n const span = this.findChipSpanAt(offset);\n if (!span) return false;\n const { text } = this.store.get();\n const { start: paramStart, end: paramEnd } = span;\n\n if (offset === paramEnd) {\n const { text: newText, removed } = removeChipSpan(text, paramStart, paramEnd);\n this.store.set((s) => ({\n text: newText,\n // Shift, don't merely clamp: every offset past the pill moved left by\n // `removed`. Clamping alone leaves a filterBase that pointed after the\n // pill aimed at the wrong word (or at the end of the text, which reads\n // as an empty filter query) until the next response resets it.\n filterBase: Math.min(\n s.filterBase > paramStart ? Math.max(paramStart, s.filterBase - removed) : s.filterBase,\n newText.length,\n ),\n ...this.withoutChip(s, span),\n pillTapped: false,\n activeDropdownIndex: -1,\n }));\n this.scheduleSetCursor(paramStart);\n return true;\n }\n\n const deleteStart = previousGraphemeBoundary(text, offset);\n const newText = text.slice(0, deleteStart) + text.slice(offset);\n this.store.set((s) => ({\n text: newText,\n // Clamped rather than shifted, unlike the whole-chip branch above: this\n // removes a single grapheme, so a stale filterBase is off by one rather\n // than by a whole chip, and the next response resets it. Kept as-is to\n // leave this long-standing path's behaviour untouched.\n filterBase: Math.min(s.filterBase, newText.length),\n ...this.withoutChip(s, span),\n pillTapped: false,\n activeDropdownIndex: -1,\n }));\n this.scheduleSetCursor(deleteStart);\n return true;\n }\n\n /**\n * ArrowLeft while the caret sits at a completed pill's trailing edge selects\n * the pill rather than moving the caret into it: re-edit turns on, the pill\n * renders highlighted, and the dropdown shows its cached options. The caret\n * stays put at the trailing edge — it never enters the pill.\n *\n * Identified chips are excluded: they carry no cached options and are\n * deliberately not re-editable (see `renderEditable`), even though Backspace\n * treats them atomically like any other chip.\n *\n * Returns true when re-edit started (caller should `preventDefault` so the\n * browser doesn't step the caret inside the `<strong>`).\n */\n startEditingParamAtCaret(offset: number): boolean {\n const span = this.findChipSpanAt(offset);\n if (!span || span.kind !== \"completed\" || span.end !== offset) return false;\n this.reEdit.start(span.param.id);\n return this.store.get().editingParam?.id === span.param.id;\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 // Consumer code (Tier 2/3 wrappers own the DOM). Isolated like every\n // other callback: it can't strand SDK state from inside a microtask,\n // but an uncaught error there is precisely the failure this boundary\n // exists to avoid — it lands as an unhandled rejection and fails\n // whichever unrelated test is in flight in a consumer's suite.\n const setCursor = this.opts.setCursor;\n if (setCursor) this.boundary.runListener(\"setCursor\", () => 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 /**\n * Announce a product selection. The rendered cards call this on activation;\n * headless consumers rendering their own strip call it themselves.\n *\n * Emitting is the entire behaviour — the SDK deliberately does not navigate\n * to `product.url`, because only the integration knows whether a selection\n * means \"open the PDP\", \"add to cart\" or \"drop the title into the input\".\n */\n selectProduct(product: Product) {\n this.emitter.emit(\"productSelect\", product);\n }\n\n handleTextChange(value: string) {\n this.handleChange(value);\n }\n\n /**\n * Skip the currently active pill (always index 0 of the actionable\n * suggestions) and promote the next pill to active. Invoked by ArrowRight at\n * the end of the input and by the dropdown's skip button; headless consumers\n * rendering their own skip affordance call it directly.\n *\n * The skipped suggestion is recorded in `skippedParams` so every subsequent\n * request (and the submit result) carries it as a `completed_params` entry\n * with `text: \"skipped\"` — otherwise the server has no way to know the user\n * declined it and keeps suggesting the same parameter. Deduped by type: the\n * same type skipped twice is one entry.\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 fetch we fire here\n * returns fresh suggestions. While cached pills remain we don't fetch — the\n * next pill is shown from cache and the skip rides along on whatever request\n * goes out next. Unlike an option selection, which fetches on every answer so\n * the next parameter is conditioned on it, a skip does not fetch on its own:\n * a decline carries less signal than an answer, and skipping through several\n * pills would otherwise cost a round-trip each.\n */\n skipActivePill() {\n const state = this.store.get();\n // Guarded in the core, not just the views: (a) during re-edit the visible\n // pill is an already answered param — the built-in UIs hide their skip\n // affordances, but a headless consumer's custom control must be safe too;\n // (b) for ~500ms after a selection (inSelectionAnimation) the answered\n // suggestion is still at index 0 while the fetch it triggered is in\n // flight — skipping in that window would record the just-given answer as\n // skipped. No-op in both states, matching selectOption's own re-edit\n // branching.\n if (state.editingParam || state.inSelectionAnimation) return;\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 const skipped = actionable[0];\n // The next actionable pill (was index 1) lands at index 0 → becomes active.\n const remaining = actionable.slice(1);\n const alreadySkipped = state.skippedParams.some((p) => p.type === skipped.type);\n this.store.set({\n suggestions: [...placeholders, ...remaining],\n pillTapped: remaining.length > 0,\n activeDropdownIndex: -1,\n ...(alreadySkipped\n ? {}\n : {\n skippedParams: [\n ...state.skippedParams,\n {\n id: crypto.randomUUID(),\n type: skipped.type,\n suggestionPlaceholder: skipped.text,\n },\n ],\n }),\n });\n if (remaining.length === 0) this.fetchNow();\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 /**\n * Subscribe to state changes. Listener receives the full (input + derived) shape.\n *\n * Consumer code, so it gets the same isolation as the `on*` events: a throw\n * is contained and reported rather than unwinding into the `store.set` that\n * triggered the notification. See {@link ConsumerBoundary}.\n */\n subscribe(listener: (state: CoreState) => void): () => void {\n // Own dedupe key per registration: several subscribers is the normal case,\n // and one broken one must not consume the reporting budget for the rest.\n const key = `subscribe#${++this.subscriberCount}`;\n return this.store.subscribe((next) => {\n this.boundary.runListener(\"subscribe\", () => listener(next), key);\n });\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 const previousProducts = this.opts.products;\n Object.assign(this.opts, opts);\n if (\"products\" in opts && opts.products !== previousProducts) {\n // A swapped (or removed) integration invalidates whatever the previous\n // one put on screen. Clear now; the next fetch repopulates from the new\n // config. `store.set` here also invalidates the derived memo, so\n // isDropdownOpen re-evaluates without the strip holding it open.\n this.productsController.clearNow();\n }\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 // Not derived state, but the empty set forces a re-render so the\n // dropdown's skip button appears/disappears without another mutation.\n opts.showSkipButton !== 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 cached pill can stand in\n // while the request below is in flight. A fetch fires either way.\n this.timers.clear(TIMER_SUGGESTION_REMOVAL);\n if (result.remainingActionable > 0) {\n // A cached next-pill exists. Remove the just-clicked suggestion after\n // the streak animation so that pill becomes active and carries the\n // loading state until the response replaces the whole set. The\n // animation keeps playing on the option while it's still in the DOM.\n //\n // Identity-based, so it has nothing to do once the response has landed\n // and swapped in fresh suggestion objects. Bail before the write rather\n // than relying on the filter to no-op: `filter` allocates a new array\n // either way, so an unguarded `set` would notify every subscriber and\n // re-render for a state that didn't change. That is now the COMMON path —\n // a fetch fires on every selection, and any response quicker than the\n // 500ms streak gets here first.\n const consumed = result.consumedSuggestion;\n this.timers.schedule(\n TIMER_SUGGESTION_REMOVAL,\n () => {\n if (!this.store.get().suggestions.includes(consumed)) return;\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` there's nothing cached to fall back to,\n // so we deliberately keep the just-selected suggestion in state until the\n // response lands — the dropdown then mirrors its pill/option layout\n // (count + widths) as the loading skeleton, instead of falling back to the\n // generic placeholder.\n\n // Answering a suggestion is itself the signal the server needs to pick the\n // next one, so the request goes out on every selection rather than only\n // once the cached pills are exhausted. Undebounced and issued here because\n // the scheduler's length-delta gate can't be trusted to fire for a\n // selection — see the `skipNextFetch` comment in computeSelectionPatch.\n //\n // After the store.set above, so the request carries the param just\n // completed. Subscribers are drained synchronously, which means the\n // scheduler has already seen `skipNextFetch` and stood down by this point.\n this.fetchNow();\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 /**\n * `this.opts` with every `optionOverrides` entry wrapped in the instance's\n * {@link ConsumerBoundary}.\n *\n * The derive layer calls these functions on the SDK's stack — from\n * `getState()`, and from inside the store's notification drain — so an\n * un-wrapped throw would unwind whatever internal operation triggered the\n * derive and abort delivery of every queued notification with it, taking the\n * instance down rather than just the override. Wrapped, a failed override\n * answers `undefined` and each call site falls back to the server's options.\n *\n * Memoized on the raw record's identity so a swapped integration is\n * re-wrapped while a stable one isn't re-wrapped on every derive. Note\n * `update({ optionOverrides })` only becomes visible on the next store write\n * — the derived layer memoizes on inputs identity, and `update` doesn't\n * invalidate it for this key. Pre-existing, and unchanged by the wrapping.\n */\n private deriveOpts(): CoreDeriveOptions {\n const raw = this.opts.optionOverrides;\n if (!raw) return this.opts;\n if (raw !== this.rawOverrides) {\n this.rawOverrides = raw;\n const wrapped: SafeOptionOverrides = {};\n for (const [type, fn] of Object.entries(raw)) {\n wrapped[type] = (query: string) =>\n this.boundary.run(`optionOverrides.${type}`, () => fn(query));\n }\n this.wrappedOverrides = wrapped;\n }\n return { ...this.opts, optionOverrides: this.wrappedOverrides };\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 showSkipButton() {\n return self.opts.showSkipButton ?? true;\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 skipActivePill: () => this.skipActivePill(),\n selectProduct: (product: Product) => this.selectProduct(product),\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 const render = () => {\n if (this.domRefs) {\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n }\n };\n this.subscribeBatchedRender(render);\n this.subscribeViewportBreakpoint(render);\n\n // Initial render\n updateDOM(this.domRefs, this.store.get(), renderOpts);\n this.subscribeNewParamTimer();\n }\n\n private buildAndRenderDropdown() {\n const self = this;\n const dropdownOpts = {\n store: this.store,\n listboxId: this.listboxId,\n get showSkipButton() {\n return self.opts.showSkipButton ?? true;\n },\n selectOption: (option: SuggestionOption) => this.selectOption(option),\n setActivePill: (index: number) => this.pillsController.setActivePill(index),\n skipActivePill: () => this.skipActivePill(),\n selectProduct: (product: Product) => this.selectProduct(product),\n };\n\n this.dropdownRefs = buildDropdownOnly(this.container, dropdownOpts);\n\n const render = () => {\n if (this.dropdownRefs) {\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n }\n };\n this.subscribeBatchedRender(render);\n this.subscribeViewportBreakpoint(render);\n\n // Initial render\n updateDropdownOnly(this.dropdownRefs, this.store.get(), dropdownOpts);\n this.subscribeNewParamTimer();\n }\n\n /**\n * Re-render when the viewport crosses the mobile breakpoint, so the options\n * grid's mobile/web column policy (see computeOptionsGridLayout) updates live\n * while the dropdown is open and otherwise idle. Mirrors the matchMedia\n * listener in the React grid and the window:resize HostListener in Angular.\n * The unsubscribe is registered for cleanup in destroy().\n */\n private subscribeViewportBreakpoint(render: () => void) {\n if (typeof window === \"undefined\" || typeof window.matchMedia !== \"function\") return;\n const mq = window.matchMedia(OPTIONS_GRID_MOBILE_QUERY);\n const onChange = () => render();\n mq.addEventListener(\"change\", onChange);\n this.unsubscribers.push(() => mq.removeEventListener(\"change\", onChange));\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 // Identified params reconcile the same way completed ones do — when the\n // user edits an identified pill's text the param is dropped and its\n // (edited) plain text remains — but that happens in the store subscriber\n // above, which the `text` set at the top of this method already drove.\n\n this.maybePromoteExactMatch(newValue);\n this.maybeOpenPendingSpan();\n }\n\n /**\n * Opens the pending span on the first keystroke past covered text: there is\n * unresolved trailing text beyond the covered offset (filterBase / last pill\n * end), actionable suggestions are on screen, and no span is already open.\n * The span snapshots those suggestions so `recently_suggested` can carry\n * them even after later responses replace what's on screen.\n */\n private maybeOpenPendingSpan() {\n const s = this.store.get();\n if (s.pendingSpan) return;\n if (s.actionableSuggestions.length === 0) return;\n const base = effectiveFilterBase(\n s.text,\n Math.min(s.filterBase, s.text.length),\n s.placeholderText,\n );\n const anchor = coveredEnd(s.segments, base);\n if (s.text.slice(anchor).trim().length === 0) return;\n this.store.set({ pendingSpan: { anchor, snapshot: s.actionableSuggestions } });\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 this.fetchNow();\n }\n\n /** Fire an immediate (undebounced) fetch for the current text + params. */\n private fetchNow() {\n const s = this.store.get();\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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":["index_exports","__export","AIAutocomplete","ATTRIBUTION_URL","ModeController","OPTIONS_GRID_MOBILE_QUERY","SKIPPED_PARAM_TEXT","buildAttributionUrl","buildQuery","buildSubmitResult","computeOptionsGridLayout","createStore","cursorIsAtEnd","extractPlainText","getCursorOffset","getFooterHint","isOptionsGridMobileViewport","optionsGridTemplateColumns","plainTextLength","previousGraphemeBoundary","renderEditableContent","setCursorOffset","withSkippedParams","__toCommonJS","TokenManager","config","forceRefresh","result","DEFAULT_API_ORIGIN","DEFAULT_SUGGEST_ENDPOINT","tokenManagers","isAccessTokenConfig","config","getApiKeyConfig","getTokenManager","manager","TokenManager","buildHeaders","apiConfig","buildApiKeyAuthHeader","apiKeyConfig","apiKey","SKIPPED_PARAM_TEXT","withSkippedParams","completed","skipped","filledTypes","p","entries","SDK_VERSION","hasWarnedMissingKey","generateRequestId","toWireParam","param","includeText","buildRequestBody","rawQuery","completedParams","sessionId","identifiedParams","recentlySuggested","skippedParams","additionalContext","generateStartingStateOptions","rawCount","p","contactAccountCount","withSkippedParams","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","insertedRanges","pos","param","count","placeholder","findClean","from","idx","r","index","delta","effectiveFilterBase","text","filterBase","placeholderText","isTypingPlaceholderPrefix","completedParamCount","extractFilterQuery","isInProgress","rawRegion","spaceIdx","findPrefixOverlap","prefix","optionText","trimmed","words","optionLower","i","candidate","suffixStart","filterOptions","options","query","lower","o","findExactMatch","applyOptionOverrides","suggestions","overrides","s","fn","overridden","coveredEnd","segments","filterBase","pos","covered","seg","isTrailingCovered","anchor","end","buildRecentlySuggested","snapshot","current","result","seen","s","rebaseAnchor","prevText","nextText","minLen","prefix","suffix","locateCompleted","text","completedParams","located","missing","pos","param","idx","locateIdentified","completedIntervals","identifiedParams","c","deriveSegments","completed","identified","pills","i","a","b","result","pill","remaining","reconcileParams","l","reconcileIdentifiedParams","toError","err","DEBOUNCE_MS","SLOW_DEBOUNCE_MS","MIN_CHARS_DIFF","FetchController","store","getApiConfig","getOptionOverrides","getMaskCompletedText","getOnError","getSessionId","getAdditionalContext","getGenerateStartingStateOptions","callbacks","prevText","prevParams","next","rawQuery","completed","controller","version","textAtRequest","stateAtRequest","recentlySuggested","buildRecentlySuggested","res","fetchSuggestions","identifiedCandidates","item","newSuggestions","applyOptionOverrides","input","lastInput","currentText","filterBase","filterInProgress","inProgressIdx","active","s","extraParam","query","extractFilterQuery","match","findExactMatch","completedNow","identifiedParams","reconcileIdentifiedParams","carriedSkips","p","racedSkipTypes","sg","caughtError","attemptFetch","minDiff","placeholderText","effBase","effectiveFilterBase","currentQuery","tappableFiltered","filterOptions","o","hasExactMatch","isInFilterZone","isTypingPlaceholderPrefix","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","buildSubmitResult","text","completedParams","skippedParams","rawQuery","finalParams","buildQuery","withSkippedParams","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","caret","anchor","offset","buildSubmitResult","tappableOptionIndices","o","i","firstIdx","delta","bottomRowStart","tappable","buckets","listbox","el","gtc","tracks","index","options","optionEl","PillsController","store","callbacks","index","state","actionable","s","moved","rest","_","i","placeholders","rawQuery","buildQuery","nextSuggestions","firstTappable","o","ProductsController","store","getConfig","query","signal","isCurrent","config","raw","mapped","list","products","err","isAbortError","OPTIONS_GRID_MOBILE_QUERY","ROW_HEIGHT","RESERVED_BAND","computeOptionsGridLayout","count","isMobile","layout","cols","rows","optionsGridTemplateColumns","isOptionsGridMobileViewport","OPTIONS_GRID_MOBILE_QUERY","computeDropdownVisibility","inputs","opts","trigger","closeOnBlur","hasContent","focusGate","trimmedEnd","caretAtEnd","deriveAll","inputs","opts","segments","deriveSegments","actionableSuggestions","s","activeSuggestion","overrideFn","placeholderText","clampedFilterBase","effectiveFilterBase","filterQuery","isTypingPlaceholderPrefix","extractFilterQuery","baseOptions","inEditMode","filteredOptions","editingId","paramStillPresent","p","editCaret","editQuery","filterOptions","hideNonTappable","o","countsAsOption","activePillHasNoOptions","editOptions","activePillSourceOptions","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","removeChipSpan","text","start","end","before","after","dropsSeam","ReEditManager","deps","paramId","state","param","p","pos","anchor","idx","replacement","editing","tail","newText","removeChipSpan","newTail","s","offset","patch","option","buildQuery","o","before","after","optionText","needsTrailingSpace","caretPos","newParam","oldIdx","params","insertAt","result","tryPromoteExactMatch","ATTRIBUTION_URL","buildAttributionUrl","base","host","url","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","SECTION_LABEL","renderProductStrip","parent","products","listboxId","onSelect","onFocusChange","section","label","row","reconcileList","product","cardKey","buildCard","el","_product","i","setProductStripFocusable","root","focusable","cards","card","field","media","img","body","vendor","title","price","e","applyGridLayout","grid","count","cols","maxHeight","computeOptionsGridLayout","isOptionsGridMobileViewport","optionsGridTemplateColumns","renderSuggestionGrid","parent","options","activeIndex","onSelect","onHighlight","listboxId","loading","groupKey","renderOptions","resetScrollOnNewGroup","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","onSkip","hasRealPills","hasPills","hasOptions","hasProducts","isVisible","setProductStripFocusable","stack","wantsSkip","wantsPillBar","pillBar","pillScroll","renderPills","renderSkipButton","activeSuggestion","groupKey","renderSuggestionGrid","skeleton","width","bar","renderProductStrip","footer","createFooter","optionHighlighted","updateFooterHint","getFooterHint","orderSections","selectors","sections","selector","el","visible","loading","activePill","btn","nextKey","nextHint","key","hint","row","hintGroup","brandGroup","buildAttributionUrl","brand","badge","buildDropdownOnly","container","opts","dropdown","createDropdown","updateDropdownOnly","refs","state","renderDropdown","focused","CHIP_PADDING_X","MAX_TRACKING","chipTracking","textLength","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","buildSubmitResult","caretInside","ro","updateDOM","refs","pillPlacement","setActivePill","selectOption","store","activeDescendant","canSubmit","previousParamId","justSelected","renderEditableContent","inlineLoading","renderPills","clearPills","setCursorOffset","dropdownPill","dropdownActivePill","renderDropdown","i","focused","computeSelectionPatch","inputs","option","activeSuggestion","base","prefix","inputWasEmpty","inputIsPlaceholderPrefix","overlapChars","findPrefixOverlap","needsSpace","newText","finalText","optionStart","optionInFinal","completed","remainingActionable","o","createStore","initial","state","listeners","pending","notifying","patch","resolved","prev","drained","entry","next","previous","l","err","listener","createDerivedStore","base","derive","cachedInputs","cachedDerived","deriveCached","inputs","full","prevFull","nextFull","injected","injectStyles","style","STYLES","ConsumerBoundary","label","fn","err","dedupeKey","Emitter","boundary","event","listener","key","entry","args","set","allCompleted","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","ConsumerBoundary","Emitter","result","err","createDerivedStore","inputs","deriveAll","PillsController","rawQuery","selectedPill","otherPills","ReEditManager","offset","type","data","ProductsController","FetchController","query","signal","isCurrent","active","matched","o","KeyboardController","option","next","prev","valid","invalid","reconcileIdentifiedParams","span","anchor","rebased","rebaseAnchor","isTrailingCovered","injectStyles","wasFocused","unsub","mode","text","params","index","endOffset","pos","seg","start","state","p","paramStart","paramEnd","newText","removed","removeChipSpan","s","deleteStart","previousGraphemeBoundary","refs","setCursorOffset","setCursor","paramId","replacement","product","value","placeholders","actionable","skipped","remaining","alreadySkipped","focused","listener","key","event","callback","previousProducts","computeSelectionPatch","buildQuery","consumed","sg","queryData","source","sendTelemetry","raw","wrapped","fn","ModeController","self","renderOpts","e","id","buildDOM","render","updateDOM","dropdownOpts","buildDropdownOnly","updateDropdownOnly","mq","OPTIONS_GRID_MOBILE_QUERY","onChange","scheduled","newValue","reconcileParams","base","effectiveFilterBase","coveredEnd","editCaret","editQuery","filterOptions","completedParams","tryPromoteExactMatch"]}