@magicx-eng/ai-autocomplete-vanilla 0.23.2 → 0.23.3
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 +5 -1
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +5 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/utils/paramType.ts","../src/date/dateGrid.ts","../src/date/looseDate.ts","../src/date/dateRange.ts","../src/date/formatType.ts","../src/utils/tokenManager.ts","../src/utils/auth.ts","../src/utils/identifiedParams.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/roots.ts","../src/dom/scrollCaretIntoView.ts","../src/dom/cursorUtils.ts","../src/utils/submitResult.ts","../src/controllers/keyboardController.ts","../src/controllers/optionSourceController.ts","../src/controllers/pillsController.ts","../src/utils/matchedItems.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/render/placeholderTypewriter.ts","../src/utils/attribution.ts","../src/utils/footerHint.ts","../src/date/dateSelection.ts","../src/render/reconcileList.ts","../src/render/renderDateGrid.ts","../src/render/renderPills.ts","../src/render/renderProductStrip.ts","../src/derive/optionsEntrance.ts","../src/render/optionIcon.ts","../src/render/renderSuggestionGrid.ts","../src/render/scrollArrow.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// === Datepicker (shared by the React + Angular dropdowns) ===\n// The calendar's math lives here so all three packages read one answer for\n// which day sits in which cell — only the markup is duplicated per package.\nexport type { DateMonthView } from \"./date/dateGrid\";\nexport {\n addMonths,\n buildDateOptions,\n cellDay,\n cellIso,\n formatAbsoluteDate,\n formatDate,\n isoDate,\n monthLabel,\n parseDate,\n selectedIsoFromText,\n WEEKDAY_LABELS,\n} from \"./date/dateGrid\";\nexport type { DateRange } from \"./date/dateRange\";\nexport {\n DATE_RANGE_META_END,\n DATE_RANGE_META_START,\n formatDateRange,\n parseLooseDateRange,\n RANGE_SEPARATOR,\n selectedRangeFor,\n} from \"./date/dateRange\";\nexport type { DateSelection } from \"./date/dateSelection\";\nexport { dateCellMarks, dateSelectionFor, visibleDateRange } from \"./date/dateSelection\";\nexport type { FormatType } from \"./date/formatType\";\nexport { isCalendarFormat, resolveFormatType } from \"./date/formatType\";\nexport type { LooseDateOptions } from \"./date/looseDate\";\nexport { parseLooseDate, resolveIdentifiedDate } from \"./date/looseDate\";\n// === Option-row entrance choreography (shared by the React + Angular dropdowns) ===\nexport {\n OPTION_ENTER_DELAY_VAR,\n OPTION_ENTER_FADE_MS,\n OPTION_ENTER_RISE_MS,\n OPTION_ENTER_RISE_PX,\n OPTION_ENTER_STAGGER_MS,\n optionEnterDelayMs,\n optionsEntranceDurationMs,\n} from \"./derive/optionsEntrance\";\n// === Options-grid layout policy (shared by the React + Angular dropdowns) ===\nexport type { OptionsGridLayout, OptionsGridPlan } from \"./derive/optionsGridLayout\";\nexport {\n computeOptionsGridLayout,\n isOptionsGridMobileViewport,\n measureOptionsGrid,\n needsOptionsGridMeasurement,\n OPTIONS_GRID_MOBILE_QUERY,\n optionsGridTemplateColumns,\n planOptionsGrid,\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 { scrollCaretIntoView } from \"./dom/scrollCaretIntoView\";\nexport { optionLabel, sanitizeOptionIconSvg } from \"./render/optionIcon\";\nexport {\n PLACEHOLDER_FADE_OUT_MS,\n PLACEHOLDER_LEAVING_ATTR,\n PLACEHOLDER_SWAP_GAP_MS,\n PLACEHOLDER_TYPE_MS,\n PLACEHOLDER_WORD_PAUSE_MS,\n} from \"./render/placeholderTypewriter\";\nexport { renderEditableContent } from \"./render/renderEditable\";\n// === \"More below\" scroll arrow for the options grid (shared by the React + Angular dropdowns) ===\nexport type { ScrollArrowArgs, ScrollArrowController } from \"./render/scrollArrow\";\nexport {\n attachScrollArrow,\n SCROLL_ARROW_ATTR,\n SCROLL_ARROW_BOTTOM_VAR,\n SCROLL_ARROW_CLASS,\n SCROLL_ARROW_FADE_MS,\n SCROLL_ARROW_LABEL,\n SCROLL_ARROW_SCROLL_IDLE_MS,\n SCROLL_ARROW_VISIBLE_ATTR,\n} from \"./render/scrollArrow\";\nexport type {\n AccessTokenConfig,\n AccessTokenResult,\n APIConfig,\n APIKeyConfig,\n AppearanceMode,\n AutocompleteRequest,\n AutocompleteResponse,\n AutocompleteResult,\n CompletedParam,\n CompletedParamState,\n CustomFieldsReport,\n ExpressedFilter,\n FilterOp,\n IdentifiedParam,\n IdentifiedParamState,\n InputItem,\n MatchedItem,\n MatchedItems,\n OptionOverride,\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 { toWireIdentifiedParams } from \"./utils/identifiedParams\";\n// === Product strip helpers (for a custom strip fed from `customFields`) ===\nexport {\n PRODUCT_PATH_PREFIX,\n productFromMatchedItem,\n productsFromCustomFields,\n} from \"./utils/matchedItems\";\nexport { ModeController } from \"./utils/modeController\";\nexport { identifiedParamLabel } from \"./utils/paramType\";\nexport { SKIPPED_PARAM_TEXT, withSkippedParams } from \"./utils/skippedParams\";\nexport { buildSubmitResult, type SubmitResultExtras } from \"./utils/submitResult\";\n","/**\n * Reading a server parameter `type` — `end_date`, `travelDate`, `due date` — as\n * the words it is made of.\n *\n * Two callers, one reading: the date-format check asks whether one of those\n * words means \"date\", and the dropdown pill bar shows them to the user when it\n * has nothing better to label a parameter with.\n */\n\n/**\n * Split a suggestion type into lower-cased words.\n *\n * Deliberately not a `\\b`-anchored regex. `\\b` counts `_` as a word character,\n * so `/\\bdate\\b/` silently fails on `end_date` — the single most likely way for\n * a server to name one of these — and on `endDate` too. Both were missed\n * because the first tests only covered space-separated names.\n *\n * Handles `end_date`, `due-date`, `due date`, `endDate` and `DATE` alike, while\n * still refusing `update`, `candidate`, `validate` and `duedate`, where \"date\"\n * is a fragment of a word rather than a word.\n *\n * Two known limits, both accepted rather than patched, because neither is\n * separable without a word list and the format check that reads this is\n * temporary:\n * - `duedate` and `date1` are missed — no separator, no case change.\n * - An adjectival phrase that happens to end in the word, `up-to-date` or\n * `out-of-date`, matches. Parameter types name a value to fill in, so an\n * adjective is an unlikely name for one.\n * Both directions are pinned by tests, so the behaviour is known rather than\n * accidental. A misfire degrades gracefully: the options render instead of a\n * calendar, and the user can still answer.\n */\nexport function tokenizeParamType(type: string): string[] {\n return type\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[^a-zA-Z0-9]+/)\n .filter(Boolean)\n .map((token) => token.toLowerCase());\n}\n\n/**\n * The dropdown pill-bar label for a parameter that has no suggestion behind it.\n *\n * Every pill normally shows its suggestion's `text` — a phrase written to be\n * read, \"Due date\". A date the server identified in the user's own words\n * (\"september 5th\") has no suggestion to borrow one from, only the wire `type`,\n * so the type is read back into words instead: `travel_date` and `travelDate`\n * both become \"travel date\". Without this the raw type is the label, both while\n * the calendar is open over the span and again if that parameter is re-edited\n * after being answered.\n *\n * Lower-casing costs nothing here — every package's pill CSS sets\n * `text-transform: uppercase`, so a label's original case never reaches the\n * screen either way.\n */\nexport function identifiedParamLabel(type: string): string {\n const words = tokenizeParamType(type);\n // `tokenizeParamType` keeps ASCII alphanumerics only, so a type written in\n // another script leaves nothing behind — the raw type beats a blank label.\n return words.length > 0 ? words.join(\" \") : type;\n}\n","import type { SuggestionOption } from \"../shared-types\";\n\n/** A calendar month on screen. `month` is 0-based, matching `Date#getMonth`. */\nexport interface DateMonthView {\n year: number;\n month: number;\n}\n\n/**\n * Month names are fixed English, NOT `Intl`-localized.\n *\n * The committed text goes into `raw_query` and `completed_params` — the server\n * has to read one vocabulary regardless of the user's locale, so a French\n * user's date must still commit as \"March 23\" / \"March 23 2027\". Only the\n * calendar's own chrome (see {@link monthLabel}) is a display string, and it\n * reuses these for the same reason the cells do: one vocabulary, no drift\n * between the label the user reads and the value they commit.\n */\nexport const MONTH_NAMES = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\n/** Sunday-first, matching `Date#getDay`. */\nexport const WEEKDAY_LABELS = [\"S\", \"M\", \"T\", \"W\", \"T\", \"F\", \"S\"];\n\n/** Full weekday names, Sunday-first — fixed English like {@link MONTH_NAMES}. */\nexport const WEEKDAY_NAMES = [\n \"Sunday\",\n \"Monday\",\n \"Tuesday\",\n \"Wednesday\",\n \"Thursday\",\n \"Friday\",\n \"Saturday\",\n];\n\n/** Metadata key holding a cell's `YYYY-MM-DD`. Absent on padding cells. */\nexport const DATE_META_ISO = \"aiaDate\";\n/** Metadata key holding a cell's day-of-month, which is what the cell renders. */\nexport const DATE_META_DAY = \"aiaDay\";\n\nconst pad = (n: number): string => String(n).padStart(2, \"0\");\n\n/**\n * `YYYY-MM-DD` from the date's LOCAL components.\n *\n * Not `toISOString()`, which converts to UTC first: for any user behind UTC\n * that shifts the calendar date back a day, so clicking \"March 23\" west of\n * Greenwich would produce `2026-03-22`.\n */\nexport function isoDate(d: Date): string {\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\n/** Midnight of the date's own day, so day arithmetic ignores the time. */\nfunction startOfDay(d: Date): Date {\n return new Date(d.getFullYear(), d.getMonth(), d.getDate());\n}\n\n/**\n * Whole days between today and `d` (negative for the past). Rounded, not\n * truncated: a DST change makes the raw difference an hour short or long of a\n * whole day, and truncation would then miscount it.\n */\nfunction daysFromToday(d: Date): number {\n const ms = startOfDay(d).getTime() - startOfDay(new Date()).getTime();\n return Math.round(ms / 86_400_000);\n}\n\n/**\n * The committed form of a date — written the way a person would, since the\n * text lands verbatim in the user's own sentence:\n *\n * - today through six days out: the weekday alone, \"Tuesday\". The window\n * stops before day seven so every weekday name still means exactly one\n * date when read back.\n * - further out, same year: \"March 23\" — the year restates what \"this year\"\n * already says.\n * - a different year: \"March 23 2027\".\n */\nexport function formatDate(d: Date): string {\n const diff = daysFromToday(d);\n if (diff >= 0 && diff < 7) return WEEKDAY_NAMES[d.getDay()];\n return formatAbsoluteDate(d);\n}\n\n/**\n * A date written as month and day, with the year only when it isn't this one —\n * {@link formatDate} without the weekday shorthand.\n *\n * The shorthand is what a range must not use. \"Tuesday - Friday\" names four\n * possible spans depending on when it is read, and the two ends of a range are\n * read against each other rather than against today, so the one thing the\n * weekday form is good at — naming a day inside this week unambiguously —\n * stops being true the moment it is half of a span. See `formatDateRange`.\n */\nexport function formatAbsoluteDate(d: Date): string {\n if (d.getFullYear() === new Date().getFullYear()) {\n return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()}`;\n }\n return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()} ${d.getFullYear()}`;\n}\n\n/**\n * Inverse of {@link formatDate}, for re-opening the calendar on the month a\n * completed date param already holds. Reads all three committed shapes — a\n * weekday name resolves to its one date in the next seven days, and a\n * yearless month-day to the current year, mirroring how they were written.\n * Returns null for anything else — the caller falls back to the current\n * month.\n *\n * Deliberately RELATIVE, like the sentence it reads: the committed text is\n * the single source of truth, and \"Schedule a campaign Tuesday\" means the\n * next Tuesday to everyone who reads it — the user re-reading their own\n * sentence, this parser, and the server parsing the submitted query alike.\n * So once the originally-picked Tuesday has gone by, re-editing marks the\n * date the sentence NOW names, a week later — not the stale pick. Anchoring\n * to the pick would make the calendar disagree with the sentence above it\n * (and with what the server will act on), which is the worse wrong. Pinned\n * by the clock-advance test in datepicker.test.ts.\n */\nexport function parseDate(text: string | undefined | null): Date | null {\n if (!text) return null;\n const weekday = WEEKDAY_NAMES.findIndex(\n (name) => name.toLowerCase() === text.trim().toLowerCase(),\n );\n if (weekday >= 0) {\n const now = startOfDay(new Date());\n const delta = (weekday - now.getDay() + 7) % 7;\n return new Date(now.getFullYear(), now.getMonth(), now.getDate() + delta);\n }\n const m = /^\\s*([A-Za-z]+)\\s+(\\d{1,2})(?:\\s+(\\d{4}))?\\s*$/.exec(text);\n if (!m) return null;\n const month = MONTH_NAMES.findIndex((name) => name.toLowerCase() === m[1].toLowerCase());\n if (month < 0) return null;\n const day = Number(m[2]);\n const year = m[3] ? Number(m[3]) : new Date().getFullYear();\n const d = new Date(year, month, day);\n // Rejects overflow like \"February 31 2026\", which `new Date` rolls forward\n // into March rather than failing.\n if (d.getMonth() !== month || d.getDate() !== day) return null;\n return d;\n}\n\n/** \"March 2026\" — the calendar header. */\nexport function monthLabel(view: DateMonthView): string {\n return `${MONTH_NAMES[view.month]} ${view.year}`;\n}\n\nexport function todayView(): DateMonthView {\n const now = new Date();\n return { year: now.getFullYear(), month: now.getMonth() };\n}\n\nexport function viewOf(d: Date): DateMonthView {\n return { year: d.getFullYear(), month: d.getMonth() };\n}\n\n/** Step the view by whole months, rolling the year over at either end. */\nexport function addMonths(view: DateMonthView, delta: number): DateMonthView {\n const d = new Date(view.year, view.month + delta, 1);\n return { year: d.getFullYear(), month: d.getMonth() };\n}\n\nexport function sameView(a: DateMonthView | null, b: DateMonthView | null): boolean {\n if (!a || !b) return a === b;\n return a.year === b.year && a.month === b.month;\n}\n\nfunction blankCell(): SuggestionOption {\n return { text: \"\", is_tappable: false, kind: null };\n}\n\n/**\n * A month laid out as `SuggestionOption`s — leading blanks, one cell per day,\n * trailing blanks to complete the last week. Always a whole number of 7-cell\n * rows.\n *\n * Modelling calendar cells as options is what makes the datepicker cheap: they\n * flow through `filteredOptions`, so the existing keyboard controller, the\n * `${listboxId}-option-${i}` id scheme, `aria-activedescendant` and — above all\n * — `selectOption`'s entire commit path work on them unchanged. At 7 columns\n * the grid's own column-major traversal already *is* calendar navigation\n * (→ = +1 day, ↓ = +7 days).\n *\n * Each day cell's `text` is the committed value (\"March 23\", or \"Tuesday\"\n * inside the next week — see {@link formatDate}), NOT its label:\n * `computeSelectionPatch` commits `option.text` verbatim, so the value has to\n * live there. The renderer reads `metadata.aiaDay` for the \"23\" it paints. Padding cells are non-tappable, which is what stops arrow navigation\n * from landing on them.\n *\n * Days before today stay tappable — the calendar de-emphasizes the past, it\n * does not forbid it. The renderers compare each cell's ISO date against\n * today's and paint past days dimmed; picking one still commits normally.\n */\nexport function buildDateOptions(view: DateMonthView): SuggestionOption[] {\n const lead = new Date(view.year, view.month, 1).getDay();\n const daysInMonth = new Date(view.year, view.month + 1, 0).getDate();\n\n const cells: SuggestionOption[] = [];\n for (let i = 0; i < lead; i++) cells.push(blankCell());\n for (let day = 1; day <= daysInMonth; day++) {\n const date = new Date(view.year, view.month, day);\n cells.push({\n text: formatDate(date),\n is_tappable: true,\n kind: null,\n metadata: { [DATE_META_ISO]: isoDate(date), [DATE_META_DAY]: day },\n });\n }\n while (cells.length % WEEKDAY_LABELS.length !== 0) cells.push(blankCell());\n return cells;\n}\n\n/**\n * The `YYYY-MM-DD` a completed date param already holds, so the calendar can\n * mark that cell as the current answer while it is being re-edited. Null for\n * text that isn't one of our dates.\n */\nexport function selectedIsoFromText(text: string | undefined | null): string | null {\n const d = parseDate(text);\n return d ? isoDate(d) : null;\n}\n\n/** A cell's `YYYY-MM-DD`, or null if it's padding. */\nexport function cellIso(option: SuggestionOption | undefined): string | null {\n const iso = option?.metadata?.[DATE_META_ISO];\n return typeof iso === \"string\" ? iso : null;\n}\n\n/** A cell's day-of-month, or null if it's padding. */\nexport function cellDay(option: SuggestionOption | undefined): number | null {\n const day = option?.metadata?.[DATE_META_DAY];\n return typeof day === \"number\" ? day : null;\n}\n","import { isoDate, MONTH_NAMES } from \"./dateGrid\";\n\n/**\n * Best-effort parse of a date the user typed in their own words.\n *\n * The server identifies a span of the query as a date and returns the literal\n * text the user wrote — not a normalized value — so \"september 5th\", \"5 Sep\"\n * and \"09/05\" all arrive as-is. This turns what it can into a `YYYY-MM-DD` so\n * the calendar can open on the right month with that day already marked.\n *\n * **Returning null is a normal outcome, not a failure.** It only costs the\n * pre-selection: the calendar still opens, on the current month with nothing\n * marked, and whatever the user picks replaces the span with the SDK's own\n * canonical text either way. So the committed answer is correct whether or not\n * the original text could be read.\n *\n * Deliberately conservative — it answers null rather than guess, because a\n * wrong date sitting pre-selected is worse than no pre-selection. A user who\n * doesn't notice it commits the wrong answer; an empty calendar just asks them\n * to pick.\n */\nexport interface LooseDateOptions {\n /** Reference point for resolving a date written without a year. Defaults to now. */\n today?: Date;\n}\n\n/** Strips the ordinal suffix off \"5th\" / \"1st\" so the day can be read as a number. */\nfunction dropOrdinals(text: string): string {\n return text.replace(/(\\d+)(?:st|nd|rd|th)\\b/g, \"$1\");\n}\n\n/** Index of a month named in full or abbreviated (\"sep\", \"sept.\", \"September\"). */\nfunction monthIndex(word: string): number {\n const w = word.toLowerCase().replace(/\\.$/, \"\");\n // Three letters is the shortest unambiguous abbreviation (\"mar\" is March and\n // never May; \"jun\"/\"jul\" separate cleanly). Two would make \"ma\" and \"ju\"\n // coin-flips, so they are refused rather than guessed.\n if (w.length < 3) return -1;\n // MONTH_NAMES is capitalised for display, so both sides are lowered here —\n // comparing against the raw names silently matched nothing at all.\n return MONTH_NAMES.findIndex((m) => m.toLowerCase().startsWith(w));\n}\n\n/**\n * Resolve a month/day with no year to the NEXT occurrence.\n *\n * Someone writing \"september 5th\" in October means next September, not one\n * eleven months gone. Anchoring to the year in progress would open the calendar\n * on a month the user can't have meant.\n *\n * Only this year and the next are tried, which means \"February 29\" written\n * during a run of non-leap years answers null rather than skipping ahead to the\n * next leap year. Deliberate, and the same trade as everywhere else here:\n * refusing costs the pre-selection, guessing three years forward would put a\n * date on screen nobody asked for.\n */\nfunction withResolvedYear(month: number, day: number, today: Date): Date | null {\n const thisYear = new Date(today.getFullYear(), month, day);\n if (thisYear.getMonth() !== month || thisYear.getDate() !== day) return null;\n const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());\n if (thisYear >= startOfToday) return thisYear;\n const nextYear = new Date(today.getFullYear() + 1, month, day);\n return nextYear.getMonth() === month && nextYear.getDate() === day ? nextYear : null;\n}\n\n/** Builds a date from explicit parts, rejecting overflow like February 31. */\nfunction exactDate(year: number, month: number, day: number): Date | null {\n const d = new Date(year, month, day);\n return d.getMonth() === month && d.getDate() === day ? d : null;\n}\n\n/**\n * `YYYY-MM-DD` for a date the user typed however they liked, or null when it\n * can't be read with confidence.\n *\n * Handles, in order: ISO; a month name with a day either side of it, with or\n * without an ordinal suffix or a year; and a numeric pair where only one\n * reading is possible (`13/05` can only be day-first, since 13 is not a month).\n *\n * Deliberately NOT handled:\n * - A numeric pair where both numbers are 12 or under. `09/05` is September\n * 5th to an American and May 9th to everyone else, and nothing in the\n * response says which — so it answers null rather than pick a side.\n * - Relative phrases (\"tomorrow\", \"next friday\"). Readable in principle, but\n * they depend on a reference the user may not share with the server, and\n * getting them subtly wrong is the expensive kind of wrong.\n */\nexport function parseLooseDate(\n text: string | undefined | null,\n opts: LooseDateOptions = {},\n): string | null {\n if (!text) return null;\n const today = opts.today ?? new Date();\n const t = dropOrdinals(text.trim().toLowerCase()).replace(/\\s+/g, \" \");\n\n const iso = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/.exec(t);\n if (iso) {\n const d = exactDate(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));\n return d ? isoDate(d) : null;\n }\n\n // \"september 5\", \"sep 5 2026\", \"September 5, 2026\"\n const monthFirst = /^([a-z]+)\\.? (\\d{1,2})(?:,? (\\d{4}))?$/.exec(t);\n if (monthFirst) {\n const month = monthIndex(monthFirst[1]);\n if (month >= 0) return fromParts(month, Number(monthFirst[2]), monthFirst[3], today);\n }\n\n // \"5 september\", \"5 of September 2026\"\n const dayFirst = /^(\\d{1,2}) (?:of )?([a-z]+)\\.?(?:,? (\\d{4}))?$/.exec(t);\n if (dayFirst) {\n const month = monthIndex(dayFirst[2]);\n if (month >= 0) return fromParts(month, Number(dayFirst[1]), dayFirst[3], today);\n }\n\n // Numeric pair, with or without a year. Only resolvable when one of the two\n // numbers is too large to be a month.\n const numeric = /^(\\d{1,2})[/\\-.](\\d{1,2})(?:[/\\-.](\\d{2,4}))?$/.exec(t);\n if (numeric) {\n const a = Number(numeric[1]);\n const b = Number(numeric[2]);\n if (a > 12 && b <= 12) return fromParts(b - 1, a, numeric[3], today);\n if (b > 12 && a <= 12) return fromParts(a - 1, b, numeric[3], today);\n return null; // both plausible as months — ambiguous, see the doc comment\n }\n\n return null;\n}\n\nfunction fromParts(\n month: number,\n day: number,\n yearText: string | undefined,\n today: Date,\n): string | null {\n if (yearText) {\n // A two-digit year is its own ambiguity (\"05\" — 1905? 2005? 2105?), so it\n // is refused for the same reason an ambiguous numeric pair is.\n if (yearText.length !== 4) return null;\n const d = exactDate(Number(yearText), month, day);\n return d ? isoDate(d) : null;\n }\n const d = withResolvedYear(month, day, today);\n return d ? isoDate(d) : null;\n}\n\n/**\n * The `YYYY-MM-DD` for an identified date param.\n *\n * Reads a normalized value off the param first, if one is ever there. The\n * server sends only the user's literal text today, so in practice this always\n * falls through to {@link parseLooseDate} — but normalizing server-side is the\n * real fix for the ambiguous cases the parser refuses, and when it lands this\n * is the one place that has to change.\n */\nexport function resolveIdentifiedDate(\n param: { text: string; isoDate?: unknown },\n opts: LooseDateOptions = {},\n): string | null {\n const fromServer = param.isoDate;\n if (typeof fromServer === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(fromServer)) return fromServer;\n return parseLooseDate(param.text, opts);\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport { formatAbsoluteDate } from \"./dateGrid\";\nimport { type LooseDateOptions, parseLooseDate } from \"./looseDate\";\n\n/**\n * A span of days, both ends inclusive, as `YYYY-MM-DD`. `start <= end` always —\n * every producer here orders the two.\n */\nexport interface DateRange {\n start: string;\n end: string;\n}\n\n/**\n * What joins the two ends of a committed range.\n *\n * A spaced hyphen, because the committed text lands verbatim in the user's own\n * sentence and is what the server parses back. `parseLooseDateRange` reads far\n * more separators than this one — it has to, since it is also what reads a\n * server's option text and the user's own words — but the SDK only ever writes\n * this one.\n */\nexport const RANGE_SEPARATOR = \" - \";\n\n/** Metadata key holding a committed range's start, as `YYYY-MM-DD`. */\nexport const DATE_RANGE_META_START = \"aiaDateRangeStart\";\n/** Metadata key holding a committed range's end, as `YYYY-MM-DD`. */\nexport const DATE_RANGE_META_END = \"aiaDateRangeEnd\";\n\n/** The two dates as a range, in whichever order puts the earlier one first. */\nexport function orderedRange(a: string, b: string): DateRange {\n // ISO strings order the same way the dates they name do.\n return a <= b ? { start: a, end: b } : { start: b, end: a };\n}\n\nconst ISO_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** `YYYY-MM-DD` → a local `Date`, or null when it isn't one. */\nfunction dateFromIso(iso: string): Date | null {\n if (!ISO_RE.test(iso)) return null;\n const [y, m, d] = iso.split(\"-\").map(Number);\n const date = new Date(y, m - 1, d);\n return date.getMonth() === m - 1 && date.getDate() === d ? date : null;\n}\n\n/**\n * Every way the halves of a range can be written apart, as split points.\n *\n * Word separators are unambiguous. A dash is not — it is also how a date writes\n * itself (`2026-09-11`, `13-05-2026`) — so rather than guess which dash is the\n * separator, EVERY dash is tried as one and the reading is only accepted when\n * exactly one split yields two readable dates (see {@link parseLooseDateRange}).\n * That is what lets \"2026-09-11-2026-10-13\" resolve while \"13-05-2026\" refuses.\n */\nconst WORD_SEPARATOR = /\\s+(?:to|through|thru|until|til|till)\\s+/g;\nconst DASH_SEPARATOR = /[-–—]/g;\n\nfunction splitPoints(text: string): Array<[string, string]> {\n const out: Array<[string, string]> = [];\n for (const re of [WORD_SEPARATOR, DASH_SEPARATOR]) {\n // Fresh lastIndex per call — these are module-level regexes with /g.\n re.lastIndex = 0;\n let match = re.exec(text);\n while (match !== null) {\n out.push([text.slice(0, match.index), text.slice(match.index + match[0].length)]);\n match = re.exec(text);\n }\n }\n return out;\n}\n\n/**\n * Best-effort parse of a span of days written in the user's (or the server's)\n * own words — \"September 11 - October 13\", \"September 11 to November 16\",\n * \"13/05/2026 - 20/05/2026\".\n *\n * Each half goes through {@link parseLooseDate}, so a range reads exactly the\n * date formats a single date does, and refuses exactly what it refuses. Same\n * trade as everywhere in this corner of the SDK: **null is a normal outcome**.\n * It costs the range picker's pre-selection, or keeps a suggestion rendering as\n * a plain option list — never correctness.\n *\n * Refused on purpose:\n * - A backwards span (\"October 13 - September 11\"). Nothing here can tell a\n * typo from a year boundary the writer left implicit, and silently swapping\n * the ends would answer a question the user didn't ask.\n * - A shared-month shorthand (\"September 11 - 13\"). The right half alone is\n * not a date, and inferring the month from the left half is the kind of\n * guess this file exists not to make.\n * - Anything where two different splits both read as a range — the text is\n * then genuinely ambiguous about where its own separator is.\n *\n * A yearless half resolves the way {@link parseLooseDate} resolves one: to its\n * next occurrence. That is what makes \"December 30 - January 5\" read as the\n * turn of the year rather than as a backwards span.\n */\nexport function parseLooseDateRange(\n text: string | undefined | null,\n opts: LooseDateOptions = {},\n): DateRange | null {\n if (!text) return null;\n const found = new Set<string>();\n let range: DateRange | null = null;\n for (const [left, right] of splitPoints(text.trim())) {\n const start = parseLooseDate(left.trim(), opts);\n if (!start) continue;\n const end = parseLooseDate(right.trim(), opts);\n if (!end || end < start) continue;\n found.add(`${start}|${end}`);\n range = { start, end };\n }\n // More than one reading means the text doesn't say where its separator is.\n return found.size === 1 ? range : null;\n}\n\n/**\n * The committed text for a range — \"September 11 - October 13\".\n *\n * Each end is written by {@link formatAbsoluteDate}, never by `formatDate`:\n * the weekday shorthand it would use for a nearby day (\"Tuesday\") names a\n * different date every week it is read, which a single answer can carry and a\n * span cannot.\n *\n * A span of one day commits as that day alone. \"September 11 - September 11\"\n * is not how anyone writes it, and the range is still recoverable from the\n * option's metadata when the answer is re-edited.\n */\nexport function formatDateRange(range: DateRange): string {\n const start = dateFromIso(range.start);\n const end = dateFromIso(range.end);\n if (!start || !end) return \"\";\n if (range.start === range.end) return formatAbsoluteDate(start);\n return `${formatAbsoluteDate(start)}${RANGE_SEPARATOR}${formatAbsoluteDate(end)}`;\n}\n\n/**\n * The synthetic option a completed range commits through.\n *\n * A range is answered in two taps, but it is committed once — so the second tap\n * builds this and hands it to the very same `selectOption` paths a single date\n * takes (fresh pill, re-edit, identified span). Deliberately carries no\n * `aiaDate` metadata: that key is what marks a *calendar cell*, and its absence\n * is what tells the range branch this option is the commit rather than a tap.\n */\nexport function buildRangeOption(a: string, b: string): SuggestionOption {\n const range = orderedRange(a, b);\n return {\n text: formatDateRange(range),\n is_tappable: true,\n kind: null,\n metadata: {\n [DATE_RANGE_META_START]: range.start,\n [DATE_RANGE_META_END]: range.end,\n },\n };\n}\n\n/**\n * The range an already-answered param holds, so both its ends can be marked\n * when it is re-opened.\n *\n * Metadata first — it is what the SDK itself wrote, exact and free of the\n * yearless-date resolution the text form needs. Text second, which is what a\n * range that reached the input any other way (a server-identified span, a\n * consumer's controlled value) has.\n */\nexport function selectedRangeFor(\n param: { text?: string; metadata?: Record<string, unknown> } | undefined | null,\n opts: LooseDateOptions = {},\n): DateRange | null {\n const start = param?.metadata?.[DATE_RANGE_META_START];\n const end = param?.metadata?.[DATE_RANGE_META_END];\n if (\n typeof start === \"string\" &&\n typeof end === \"string\" &&\n ISO_RE.test(start) &&\n ISO_RE.test(end)\n )\n return orderedRange(start, end);\n return parseLooseDateRange(param?.text, opts);\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport { tokenizeParamType } from \"../utils/paramType\";\nimport { parseLooseDateRange } from \"./dateRange\";\nimport { parseLooseDate } from \"./looseDate\";\n\n/**\n * How the dropdown lets the user answer a suggestion.\n *\n * `\"options\"` is every suggestion the server has ever sent: pick one of its\n * options, or type to filter them. `\"date\"` ignores the options entirely and\n * renders a calendar instead. `\"date-range\"` renders the same calendar,\n * answered in two taps — a start and an end — and committed as one span.\n */\nexport type FormatType = \"options\" | \"date\" | \"date-range\";\n\n/** Whether this format is answered by a calendar rather than by a list. */\nexport function isCalendarFormat(format: FormatType): boolean {\n return format === \"date\" || format === \"date-range\";\n}\n\n/** Whole words that mean \"this parameter is a date\". */\nconst DATE_TOKENS = new Set([\"date\", \"dates\"]);\n\n/** Whole words that, next to a date token, mean \"and it is a span of them\". */\nconst RANGE_TOKENS = new Set([\"range\", \"ranges\", \"span\", \"period\", \"window\"]);\n\n/**\n * How many of a suggestion's own options must read as dates before the\n * parameter is treated as one regardless of what it is called.\n *\n * Three, because one or two is the range where a coincidence lives — a version\n * string like \"25.12\" reads as a date on its own — while three unrelated\n * options all parsing is not something that happens by accident. Options that\n * cannot be read confidently (`parseLooseDate` refuses ambiguous numerics and\n * relative phrases) do not count toward it, which keeps the bar honest.\n */\nconst DATE_OPTION_THRESHOLD = 3;\n\ninterface DateOptionCounts {\n /** Options that read as one day — \"September 11\". */\n single: number;\n /** Options that read as a span of days — \"September 11 - October 13\". */\n range: number;\n}\n\n/**\n * How many of these options read as dates, and how many of those are spans.\n *\n * Only tappable options count: the others are labels and headings, not values\n * the user can pick, so they say nothing about what the parameter holds. A\n * span is counted only as a span — the two counts never double-count an\n * option, which is what lets them be compared against each other below.\n */\nfunction countDateOptions(options: SuggestionOption[] | undefined): DateOptionCounts {\n const counts: DateOptionCounts = { single: 0, range: 0 };\n if (!options) return counts;\n for (const option of options) {\n if (!option.is_tappable) continue;\n // Ranges first: a range never parses as a single date, but asking in this\n // order keeps the two categories obviously exclusive rather than relying\n // on that.\n if (parseLooseDateRange(option.text) !== null) counts.range += 1;\n else if (parseLooseDate(option.text) !== null) counts.single += 1;\n }\n return counts;\n}\n\n/**\n * Decide how a suggestion should be answered.\n *\n * The server cannot send a `formatType` yet, so a suggestion that holds a date\n * is detected here — by the options it offers, and failing that by its name —\n * and rendered as a calendar with those options ignored.\n *\n * The detection is deliberately the LAST resort. A `formatType` that *is* on\n * the wire always wins, so the day the server starts sending one this function\n * needs a single block deleted — the datepicker itself, and every gate that\n * consults this, keep working untouched.\n *\n * Options are read before the name because they are the stronger evidence:\n * they are the values themselves, while a name is what someone chose to call\n * them. A parameter called `travel_date` whose options are all spans is a span\n * parameter, whatever its name says.\n *\n * Accepts anything with a `type`, not a `Suggestion`, so re-edit can ask the\n * same question about a completed param via its `suggestionType`.\n */\nexport function resolveFormatType(\n source: { type: string; options?: SuggestionOption[] } | undefined | null,\n): FormatType {\n if (!source) return \"options\";\n const fromServer = (source as { formatType?: unknown }).formatType;\n if (fromServer === \"date\" || fromServer === \"options\" || fromServer === \"date-range\")\n return fromServer;\n\n // A parameter can be a date without saying so in its name — \"when\", \"arrival\",\n // \"expiry\". What it offers gives it away, so the options are read as the\n // first signal. Note this only catches options written as actual dates: a\n // server answering with \"today\" / \"tomorrow\" / \"next week\" scores zero here,\n // because those are exactly the phrases `parseLooseDate` refuses to read.\n const counts = countDateOptions(source.options);\n // Spans outnumbering single days is what makes it a span parameter. A\n // majority rather than a clean sweep, because a server that offers \"Next 7\n // days\" / \"Sep 11 - Oct 13\" / \"Oct 1 - Oct 31\" alongside one bare date is\n // still asking for a span. Both categories count toward the bar here — three\n // options that are all dates of SOME shape is the same evidence either way.\n // A tie falls through deliberately: an even split of days and spans says the\n // options disagree about what the parameter holds, and neither calendar can\n // answer half of them. The option list, which can, is the honest fallback.\n if (counts.single + counts.range >= DATE_OPTION_THRESHOLD && counts.range > counts.single) {\n return \"date-range\";\n }\n\n const tokens = tokenizeParamType(source.type);\n if (tokens.some((token) => DATE_TOKENS.has(token))) {\n return tokens.some((token) => RANGE_TOKENS.has(token)) ? \"date-range\" : \"date\";\n }\n // Single days ONLY, deliberately — the bar for \"replace these options with a\n // one-day calendar\" is unchanged by ranges existing. Counting spans toward it\n // would lower it: two single dates plus one span would newly clear three and\n // throw away an option list the user could previously pick from, span\n // included.\n return counts.single >= DATE_OPTION_THRESHOLD ? \"date\" : \"options\";\n}\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 { IdentifiedParam, IdentifiedParamState } from \"../shared-types\";\n\n/**\n * Projects client-side identified-param state onto its wire shape.\n *\n * The one place the `{ type, value }` form is spelled out. Both the request\n * body (`api.ts`) and the `AutocompleteResult` handed to `onResult` /\n * `onSubmit` go through it, so the params the consumer receives are exactly\n * the ones the server was told about.\n */\nexport function toWireIdentifiedParams(params: IdentifiedParamState[]): IdentifiedParam[] {\n return params.map((p) => ({ type: p.type, value: p.text }));\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 { toWireIdentifiedParams } from \"./identifiedParams\";\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: toWireIdentifiedParams(identifiedParams),\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 { OptionOverrides, Suggestion } from \"../shared-types\";\n\n/**\n * Strips the server's options from every suggestion whose type the consumer\n * overrides. Server options are never shown for an overridden type, mirroring\n * the derive layer, and the consumer answers later — through the\n * `OptionSourceController`, once the pill is active — so the suggestion lands\n * with NO options rather than a list the consumer never approved. Storing the\n * server's list in the meantime would put it on screen and let exact-match\n * promotion answer the pill with it.\n */\nexport function applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: OptionOverrides,\n): Suggestion[] {\n if (!overrides) return suggestions;\n return suggestions.map((s) => (overrides[s.type] ? { ...s, options: [] } : s));\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 { isCalendarFormat, resolveFormatType } from \"../date/formatType\";\nimport type {\n APIConfig,\n CompletedParamState,\n CustomFieldsReport,\n IdentifiedParamState,\n OptionOverrides,\n Product,\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\";\nimport type { OptionSourceTick } from \"./optionSourceController\";\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 * Fired once per `/suggest` response that was committed to the store —\n * after the final write, so the store already holds the response's\n * suggestions, its identified params and any exact-match promotion it\n * triggered. Never fires for a superseded, aborted or failed request.\n *\n * Carries no payload on purpose: the orchestrator reads the store, which is\n * the settled state, rather than a snapshot this controller would have to\n * keep in step with every subscriber that runs inside the write.\n */\n onResponse?: () => void;\n /**\n * What the product strip should show for the response about to be\n * committed, given the catalog report it carried (null when it carried\n * none). Returns `undefined` to leave `products` untouched — the strip is\n * then a custom integration's, fed from `onRequest`. Consulted inside the\n * final write so cards and suggestions land in one notification.\n */\n productsForResponse?: (report: CustomFieldsReport | null) => Product[] | undefined;\n /**\n * Consulted on every debounce tick before the server gates. A consumer\n * `optionOverrides` entry that owns the active pill answers the tick\n * instead of the server — see `OptionSourceController.tick` for the three\n * outcomes. Absent, every tick is the server's.\n */\n consumerTick?: (state: CoreState, minDiff: number) => OptionSourceTick;\n}\n\nexport class FetchController {\n private fetchVersion = 0;\n private abortController: AbortController | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private slowDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n private unsubscribe: (() => void) | null = null;\n\n constructor(\n private store: Store<CoreState>,\n private getApiConfig: () => APIConfig | undefined,\n private getOptionOverrides: () => OptionOverrides | undefined,\n private getMaskCompletedText: () => boolean | undefined,\n private getOnError: () => ((error: Error) => void) | undefined,\n private getSessionId: () => string,\n private 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 // Resolved from the INCOMING suggestion, not from state: this runs as the\n // response lands, so the store still describes the suggestion being\n // replaced. A date pill's options never reach the screen, so nothing the\n // user typed can be an exact match for one.\n if (active && !isCalendarFormat(resolveFormatType(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 icon: match.icon,\n icon_svg: match.icon_svg,\n };\n newSuggestions = newSuggestions.filter((s) => s !== active);\n this.callbacks.onAutoMatch?.({ active, matched: match, rawQuery });\n }\n }\n\n // The catalog report and the cards it feeds. `undefined` on the wire is\n // \"no catalog was read\" and lands as null, so the state always says\n // whether the response on screen carried a block.\n const customFields = res.data.custom_fields ?? null;\n const products = this.callbacks.productsForResponse?.(customFields);\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 customFields,\n ...(products !== undefined ? { products } : {}),\n ...(extraParam ? { completedParams: completedNow } : {}),\n };\n });\n\n // The response is in the store; tell the orchestrator so it can hand\n // the consumer the query as it now stands. Not gated on the version:\n // a subscriber inside the write above may already have started the\n // next fetch, but this response was current when it landed and the\n // state it produced is what the user is looking at.\n this.callbacks.onResponse?.();\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 /**\n * Run the scheduler's gates now, off-timer, with the slow tick's\n * one-character rule. Used when a consumer override has just answered\n * empty for the typed phrase: the server is the fallback for it, and the\n * pending timers are dropped so they don't issue the same request twice.\n */\n attemptNow() {\n this.clearTimers();\n // Same stand-down `scheduleFetch` honours: a selection or reset that set\n // this flag issues its own request. The flag is consumed synchronously by\n // the subscriber that set it, so this is defensive rather than reachable\n // today — but every writer of the flag assumes it is consulted before the\n // next request, and this is a request path.\n if (this.store.get().skipNextFetch) {\n this.store.set({ skipNextFetch: false });\n return;\n }\n this.attemptFetch(1, true);\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 this.debounceTimer = setTimeout(() => {\n if (this.attemptFetch(MIN_CHARS_DIFF)) {\n if (this.slowDebounceTimer) clearTimeout(this.slowDebounceTimer);\n }\n }, DEBOUNCE_MS);\n\n this.slowDebounceTimer = setTimeout(() => this.attemptFetch(1), SLOW_DEBOUNCE_MS);\n }\n\n /**\n * One debounce tick. Returns true when the tick was spent — a request went\n * out, or the consumer took it. `anyChange` relaxes the length-delta rule to\n * \"the raw query differs from what the server last saw\": a consumer miss is\n * an explicit signal, and a same-length replacement (\"zz\" → \"Me\") must not\n * be the one phrase the server never hears about.\n */\n private attemptFetch(minDiff: number, anyChange = false): boolean {\n const s = this.store.get();\n if (!s.text && s.completedParams.length === 0) {\n this.doFetch(\"\", []);\n return true;\n }\n\n // A consumer override that owns the active pill is asked in place of the\n // server, on this same tick. \"owns\" without an ask leaves the slow timer\n // to re-check, exactly as a sub-threshold delta does for the server.\n const consumer = this.callbacks.consumerTick?.(s, minDiff) ?? \"no\";\n if (consumer === \"asked\") return true;\n if (consumer === \"owns\") return false;\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 // A calendar pill — one day or a span — shows a calendar, so typing is not\n // filtering anything, and the gates below have to stop consulting options\n // the user cannot see. Without this the filter-zone gate would suppress the\n // request whenever the typed text happened to match one of the ignored\n // options, and the request would simply never go out.\n const isDate = isCalendarFormat(s.activeFormatType);\n const currentFiltered = active && !isDate ? filterOptions(active.options, currentQuery) : [];\n const tappableFiltered = currentFiltered.filter((o: SuggestionOption) => o.is_tappable);\n const hasExactMatch =\n active && !isDate ? 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 const changed = anyChange && rawQuery !== s.lastRawQuery;\n if (isDeleting || charDiff >= minDiff || changed) {\n this.doFetch(rawQuery, updatedParams);\n return true;\n }\n return false;\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 * Root resolution for a widget that may be mounted inside a shadow root.\n *\n * Everything the SDK touches outside its own subtree — the stylesheet it\n * injects, the selection it reads, the option elements it looks up by id —\n * belongs to a *root*, and that root is the document only when the widget is\n * mounted in the light DOM. Inside a shadow root the document is the wrong\n * root: its stylesheets don't reach the shadow tree, `document.getElementById`\n * can't see shadow content at all, and (in WebKit) `document.getSelection()`\n * retargets its anchor to the host, which silently destroys caret positioning.\n *\n * So: never reach for `document` directly. Resolve the root from a node the\n * widget owns, and ask that.\n */\n\n/** A root that can own stylesheets, a selection and ids: document or shadow root. */\nexport type WidgetRoot = Document | ShadowRoot;\n\n/** `getSelection()` is on `Document` in every engine, on `ShadowRoot` only in Chromium/WebKit. */\ninterface RootWithSelection {\n getSelection?: () => Selection | null;\n}\n\n/** `getElementById` comes from `NonElementParentNode` — `Document` and `DocumentFragment`. */\ninterface RootWithGetElementById {\n getElementById?: (id: string) => HTMLElement | null;\n}\n\nfunction isWidgetRoot(node: Node): node is Node & WidgetRoot {\n if (node.nodeType === Node.DOCUMENT_NODE) return true;\n // A ShadowRoot is a DocumentFragment with a host; a plain fragment (a\n // detached node's root) is neither, and its owner document is the right\n // answer for it.\n return node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && \"host\" in node;\n}\n\n/**\n * The document or shadow root that owns `node`.\n *\n * Falls back to the owning document for a detached node (whose `getRootNode()`\n * is a bare element or fragment) and for DOM shims without `getRootNode`.\n */\nexport function rootOf(node: Node): WidgetRoot {\n const root = typeof node.getRootNode === \"function\" ? node.getRootNode() : null;\n if (root && isWidgetRoot(root)) return root;\n return node.ownerDocument ?? (node as unknown as Document);\n}\n\n/** The document owning `node`, whichever root it lives under. */\nexport function documentOf(node: Node): Document {\n return node.ownerDocument ?? (node as unknown as Document);\n}\n\n/**\n * The `Selection` that reports positions inside `node`'s tree.\n *\n * Chromium and WebKit give each shadow root its own `Selection`; WebKit's\n * document-level one retargets `anchorNode` to the shadow host, so reading it\n * would place the caret on the host instead of the text the user clicked.\n * Firefox has no `ShadowRoot.getSelection()` but its document selection\n * already reports nodes inside an open shadow tree — hence the fallback.\n */\nexport function getSelectionFor(node: Node): Selection | null {\n const root = rootOf(node);\n const selection = (root as RootWithSelection).getSelection?.();\n if (selection) return selection;\n return documentOf(node).getSelection?.() ?? null;\n}\n\n/**\n * Look up an element by id within `node`'s root.\n *\n * `document.getElementById` cannot see into a shadow tree, so an id lookup for\n * an element the widget rendered has to start from the widget's own root.\n * `ShadowRoot` inherits `getElementById` from `DocumentFragment`.\n */\nexport function getElementByIdFor(node: Node | null, id: string): HTMLElement | null {\n const root: (WidgetRoot & RootWithGetElementById) | null = node\n ? rootOf(node)\n : typeof document !== \"undefined\"\n ? document\n : null;\n if (!root) return null;\n if (typeof root.getElementById === \"function\") return root.getElementById(id);\n // Defensive only: every engine the SDK targets implements\n // `NonElementParentNode.getElementById` on `ShadowRoot`. This branch is for a\n // DOM shim that doesn't — and it searches the document, which for a shadow\n // root is the wrong tree, so it will find nothing rather than the right\n // element. Callers already treat a null lookup as \"fall back to the\n // configured value\", which is the correct behaviour there.\n return typeof document !== \"undefined\" ? document.getElementById(id) : null;\n}\n","/**\n * Scroll-to-caret for the editor's own scroll container.\n *\n * The editor is a scroll container (`overflow-y: auto` under a height cap in\n * the core stylesheet; a consumer holding it to one line adds `overflow-x`).\n * Browsers scroll such a box to the caret as the user types, but not when the\n * caret is moved by script — which the SDK does on every render that actually\n * rewrites the editable's content (`renderEditable` early-returns on an\n * unchanged segment key, and restores the caret only while focused).\n * Everything here exists to close that gap.\n */\n\nimport { documentOf, getSelectionFor } from \"./roots\";\n\n/**\n * Kept between the caret and the scroll container's edge (CSS px) after a\n * scroll correction, so the caret never sits flush against the boundary.\n */\nconst CARET_SCROLL_MARGIN = 2;\n\n/** Class marking the widget's own container — the walk never goes past it. */\nconst WIDGET_CONTAINER_CLASS = \"magicx-aia\";\n\nfunction isScrollableOverflow(value: string): boolean {\n // `visible` and `clip` can never be scrolled. `hidden` shows no scrollbar\n // but its scroll offsets are still scriptable, which is all we mutate here.\n return value === \"auto\" || value === \"scroll\" || value === \"hidden\" || value === \"overlay\";\n}\n\n/**\n * Nearest scrollable box, starting at `root` itself and walking up.\n *\n * The walk stops at the widget's own container (and never reaches `<body>` or\n * the document element) so moving the caret can never scroll the consumer's\n * page or layout — only a box the widget lives in.\n */\nfunction findScrollContainer(root: HTMLElement): HTMLElement | null {\n const doc = documentOf(root);\n const win = doc.defaultView;\n if (!win?.getComputedStyle) return null;\n\n let el: HTMLElement | null = root;\n while (el && el !== doc.body && el !== doc.documentElement) {\n // Cheap gate first: a box whose content fits can't need scrolling, and\n // this runs on every content render.\n const overflowsX = el.scrollWidth > el.clientWidth;\n const overflowsY = el.scrollHeight > el.clientHeight;\n if (overflowsX || overflowsY) {\n const style = win.getComputedStyle(el);\n if (\n (overflowsX && isScrollableOverflow(style.overflowX)) ||\n (overflowsY && isScrollableOverflow(style.overflowY))\n ) {\n return el;\n }\n }\n if (el.classList.contains(WIDGET_CONTAINER_CLASS)) return null;\n el = el.parentElement;\n }\n return null;\n}\n\n/**\n * Scroll the caret back into view inside the editor's own scroll container.\n *\n * Browsers do this themselves while the user types, but not when the caret is\n * moved by script — so every programmatic caret placement (option selection,\n * caret restore after a re-render, controlled-mode `setValue`) would otherwise\n * leave the caret, and everything typed next, outside the visible box.\n *\n * Exactly one box is scrolled, by writing its own `scrollLeft` / `scrollTop`:\n * the nearest scrollable ancestor of the editable, which inside the widget's\n * own markup is the editor. `scrollIntoView` is deliberately not used — it\n * would walk the whole ancestor chain and the page with it. When the widget\n * does not own the editable's ancestry (a consumer-rendered editor in\n * `\"dropdown\"` / `\"headless\"` mode) that nearest box may be one of the\n * consumer's, which is what the browser would scroll for typed input anyway;\n * nothing above it moves either way. A no-op when nothing overflows, when the\n * caret is already visible, or when the caret has no measurable box.\n */\nexport function scrollCaretIntoView(root: HTMLElement): void {\n const sel = getSelectionFor(root);\n if (!sel || sel.rangeCount === 0) return;\n\n const range = sel.getRangeAt(0);\n if (!range.collapsed || !root.contains(range.startContainer)) return;\n\n // Find the box to scroll before measuring the caret: this runs on every\n // render that rewrites the editable, and an editor whose content fits needs\n // neither.\n const container = findScrollContainer(root);\n if (!container) return;\n\n // jsdom (and other layout-less DOM shims) don't implement Range geometry at\n // all — nothing to measure there, and consumers run their tests on those.\n if (typeof range.getBoundingClientRect !== \"function\") return;\n\n const caret = range.getBoundingClientRect();\n // A range with no box (detached, display:none, or a boundary the engine\n // can't measure) reports a zero rect at the viewport origin — acting on it\n // would yank the container back to the start.\n if (caret.height === 0) return;\n\n const box = container.getBoundingClientRect();\n\n // Adjust only the axis that actually overflows, and only when the caret is\n // outside the visible box — a caret already in view is never re-clamped.\n // `clientLeft` / `clientTop` skip the border; `clientWidth` / `clientHeight`\n // exclude the border (and any scrollbar gutter) on the far side.\n if (container.scrollWidth > container.clientWidth) {\n const viewLeft = box.left + container.clientLeft;\n const viewRight = viewLeft + container.clientWidth;\n if (caret.right > viewRight) {\n container.scrollLeft += caret.right - viewRight + CARET_SCROLL_MARGIN;\n } else if (caret.left < viewLeft) {\n container.scrollLeft -= viewLeft - caret.left + CARET_SCROLL_MARGIN;\n }\n }\n\n if (container.scrollHeight > container.clientHeight) {\n const viewTop = box.top + container.clientTop;\n const viewBottom = viewTop + container.clientHeight;\n if (caret.bottom > viewBottom) {\n container.scrollTop += caret.bottom - viewBottom + CARET_SCROLL_MARGIN;\n } else if (caret.top < viewTop) {\n container.scrollTop -= viewTop - caret.top + CARET_SCROLL_MARGIN;\n }\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\nimport { documentOf, getSelectionFor } from \"./roots\";\nimport { scrollCaretIntoView } from \"./scrollCaretIntoView\";\n\nconst NON_EDITABLE_SELECTOR = '[contenteditable=\"false\"]';\n/** A completed/identified param rendered as a chip (see renderEditable.ts). */\nconst CHIP_SELECTOR = \"strong[data-param-id]\";\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 documentOf(root).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 = getSelectionFor(root);\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 *\n * Also scrolls the caret back into view (see `scrollCaretIntoView`) — this is\n * the single caret-placement choke point, and a scripted caret move does not\n * get the browser's own scroll-to-caret behaviour.\n */\nexport function setCursorOffset(root: HTMLElement, offset: number): void {\n const doc = documentOf(root);\n const sel = getSelectionFor(root);\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 // Read the caret straight back: an engine that answers with a different\n // position than the one just set has canonicalized it, and the side of a\n // chip boundary the policy above chose is no longer readable from the DOM.\n recordChipBoundary(root, sel, range, clamped);\n\n // The browser scrolls a scroll container to the caret as the user types, but\n // not when the caret is moved by script — do it ourselves, for every caller.\n scrollCaretIntoView(root);\n}\n\n/** The chip enclosing `node`, or null when it sits outside every chip in `root`. */\nfunction chipContaining(node: Node | null, root: HTMLElement): HTMLElement | null {\n if (!node || !root.contains(node)) return null;\n const start = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;\n const chip = start?.closest<HTMLElement>(CHIP_SELECTOR) ?? null;\n // `closest` walks past the editable into the consumer's markup; a chip found\n // out there belongs to someone else.\n return chip && root.contains(chip) ? chip : null;\n}\n\n/**\n * Record a caret the SDK placed OUTSIDE a chip that the engine reports from\n * INSIDE it, as `\"<offset>:<chipId>\"` on the editable.\n *\n * Chromium answers a shadow root's own `Selection` from the *canonicalized*\n * caret position rather than the one that was set, and the canonical form of\n * \"immediately after a chip\" is \"at the end of the chip's own text node\". So\n * the boundary policy above — hop out of the `<strong>` so the caret reads as\n * outside it — is simply not observable inside a shadow root: every position at\n * a chip's edge reads back as a position within the chip, which the\n * `selectionchange` reader would otherwise take for a tap on the chip and\n * answer with re-edit. (Verified in Chromium 149 against a contentEditable in\n * an open shadow root; `document.getSelection()` is no help either — it\n * retargets the anchor to the host.)\n *\n * The light DOM reads back exactly what was set, so nothing is ever recorded\n * there and that path keeps the DOM position as its only source of truth.\n */\nfunction recordChipBoundary(root: HTMLElement, sel: Selection, range: Range, offset: number): void {\n const reported = chipContaining(sel.anchorNode, root);\n if (reported && reported !== chipContaining(range.startContainer, root)) {\n root.dataset.aiaCaretEscapedChip = `${offset}:${reported.dataset.paramId}`;\n return;\n }\n delete root.dataset.aiaCaretEscapedChip;\n}\n\n/**\n * True when the engine reports the caret inside chip `chipId`, but the SDK put\n * it there on purpose *outside* that chip's boundary — see `recordChipBoundary`.\n *\n * Callers asking \"is the caret in a chip?\" from the DOM anchor must consult\n * this, or they will read the SDK's own caret park as a tap on the chip.\n *\n * Only ever true inside a shadow root, and only at the exact offset the SDK\n * last parked at: a caret anywhere else in the chip — which is every ordinary\n * tap on one — still reads as inside it.\n */\nexport function caretEscapedChip(root: HTMLElement, chipId: string): boolean {\n const record = root.dataset.aiaCaretEscapedChip;\n if (!record) return false;\n const offset = getCursorOffset(root);\n return offset !== null && record === `${offset}:${chipId}`;\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 {\n AutocompleteResult,\n CompletedParamState,\n CustomFieldsReport,\n IdentifiedParamState,\n SkippedParamState,\n} from \"../shared-types\";\nimport { buildQuery } from \"./buildQuery\";\nimport { toWireIdentifiedParams } from \"./identifiedParams\";\nimport { withSkippedParams } from \"./skippedParams\";\n\n/**\n * Fields of an {@link AutocompleteResult} that come from the latest server\n * response rather than from the input. Both default to \"nothing yet\".\n */\nexport interface SubmitResultExtras {\n /** Server-identified params (`state.identifiedParams`). Default: none. */\n identifiedParams?: IdentifiedParamState[];\n /** Server's \"query is complete\" verdict (`state.isReady`). Default: false. */\n isReady?: boolean;\n /**\n * The catalog grounding report the latest response carried\n * (`state.customFields`). Default: none, and the result then has no\n * `custom_fields` key at all — mirroring the wire, where the block is absent\n * rather than null when no catalog was read.\n */\n customFields?: CustomFieldsReport | null;\n}\n\n/**\n * Builds the `AutocompleteResult` handed to `onSubmit` and `onResult`: the\n * placeholder-tokenized raw query plus the completed params, with skipped\n * suggestions folded in (see {@link withSkippedParams}), the server-identified\n * params in their wire shape, and the server's readiness verdict.\n *\n * Shared by every submit path — vanilla Enter / submit button, the React Tier 1\n * component, the Angular Tier 1 component — and by the core's per-response\n * `result` event, so none of them can drift on what a result contains.\n */\nexport function buildSubmitResult(\n text: string,\n completedParams: CompletedParamState[],\n skippedParams: SkippedParamState[] = [],\n extras: SubmitResultExtras = {},\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 identified_params: toWireIdentifiedParams(extras.identifiedParams ?? []),\n is_ready: extras.isReady ?? false,\n ...(extras.customFields ? { custom_fields: extras.customFields } : {}),\n };\n}\n","import { cellIso, isoDate } from \"../date/dateGrid\";\nimport { isCalendarFormat } from \"../date/formatType\";\nimport { cursorIsAtEnd, getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport { getElementByIdFor } from \"../dom/roots\";\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 /** Abandon a date pick opened on a span the server identified (Escape). */\n exitEditingIdentified?: () => 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 // The event target roots every id lookup below: the listbox and its\n // options live in the same root as the input the key was pressed in, and\n // `document.getElementById` cannot see into a shadow root. A Tier 2/3\n // consumer could in principle put their input in a different root than the\n // dropdown, in which case the lookups miss and each caller falls back the\n // way it does when the dropdown hasn't rendered yet — the configured\n // column count, and selectOption() instead of clicking the option element.\n const from = e.target instanceof Node ? e.target : null;\n const columns = this.getEffectiveColumns(from);\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 const entry = isCalendarFormat(state.activeFormatType)\n ? this.dateEntryIndex(state, false)\n : (tappableIndices[0] ?? 0);\n if (!state.isDropdownOpen && state.actionableSuggestions.length > 0) {\n this.store.set({ pillTapped: true, activeDropdownIndex: entry });\n break;\n }\n if (tappableIndices.length === 0) return;\n this.store.set({ activeDropdownIndex: entry });\n break;\n }\n\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n // One row down a calendar is one week, always. The generic traversal\n // below only coincides with that while a month's columns are evenly\n // filled, and diverges at both ends of it.\n if (isCalendarFormat(state.activeFormatType)) {\n this.stepDateWeek(state, 7);\n break;\n }\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 = isCalendarFormat(state.activeFormatType)\n ? this.dateEntryIndex(state, true)\n : (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 (isCalendarFormat(state.activeFormatType)) {\n this.stepDateWeek(state, -7);\n break;\n }\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 // A calendar row is a week, so the day after Saturday is the next\n // row's Sunday — the column stop that makes sense for a grid of\n // independent options would strand the caret at each week's end.\n if (isCalendarFormat(state.activeFormatType)) {\n this.stepDateDay(state, 1);\n break;\n }\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 // Mirror of ArrowRight above — and it also keeps the column-0 branch\n // below out of reach on a calendar, where every Sunday sits in column\n // 0 and would otherwise drop the user into re-editing the previous\n // param mid-navigation.\n if (isCalendarFormat(state.activeFormatType)) {\n this.stepDateDay(state, -1);\n break;\n }\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, from);\n } else if (onSubmit) {\n const completed = onSubmit(\n buildSubmitResult(state.text, state.completedParams, state.skippedParams, {\n identifiedParams: state.identifiedParams,\n isReady: state.isReady,\n customFields: state.customFields,\n }),\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 // A half-picked range backs out one step at a time: the first Escape\n // drops the start the user chose, leaving the calendar up so they can\n // start the span again. Anything else would make a mis-tapped first\n // date cost the whole pick.\n if (state.dateRangeStart) {\n this.store.set({ pendingRangeStart: null });\n break;\n }\n // Escape backs out of a date pick the same way it backs out of a\n // re-edit, leaving the span's own words untouched.\n if (state.editingIdentified) {\n this.ctx.exitEditingIdentified?.();\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\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 /**\n * Where the highlight lands when the user arrows into a calendar: today if\n * the visible month holds it, otherwise the month's first or last day\n * depending on which edge the press came from. The grid's own entry point\n * (`tappableIndices[0]`) is column-major, so on a calendar it would land on\n * an arbitrary day in the first column rather than anywhere meaningful.\n */\n private dateEntryIndex(state: CoreState, fromBottom: boolean): number {\n const today = isoDate(new Date());\n const todayIdx = state.filteredOptions.findIndex((o) => cellIso(o) === today);\n if (todayIdx >= 0) return todayIdx;\n const days = state.filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n return (fromBottom ? days[days.length - 1] : days[0]) ?? 0;\n }\n\n /**\n * Move the highlight by whole days, skipping the padding cells that stand in\n * for the days before the 1st and after the last. Stops at the month's edges\n * rather than wrapping — a wrap would silently jump the user four weeks\n * across a calendar they are reading positionally.\n */\n private stepDateDay(state: CoreState, delta: number): void {\n for (\n let i = state.activeDropdownIndex + delta;\n i >= 0 && i < state.filteredOptions.length;\n i += delta\n ) {\n if (state.filteredOptions[i]?.is_tappable) {\n this.store.set({ activeDropdownIndex: i });\n return;\n }\n }\n }\n\n /**\n * Move the highlight one week, which on a 7-column grid is exactly ±7 cells.\n * Landing outside the month returns to the input — the same escape the\n * options grid offers at its top and bottom rows, so a calendar is never a\n * trap the user can only leave with Escape.\n */\n private stepDateWeek(state: CoreState, delta: number): void {\n const next = state.activeDropdownIndex + delta;\n this.store.set({\n activeDropdownIndex: state.filteredOptions[next]?.is_tappable ? next : -1,\n });\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(from: Node | null): number {\n const listbox = getElementByIdFor(from, 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 = getElementByIdFor(from, `${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(\n index: number,\n options: SuggestionOption[],\n listboxId: string,\n from: Node | null,\n ) {\n const optionEl = getElementByIdFor(from, `${listboxId}-option-${index}`);\n if (optionEl) {\n optionEl.click();\n } else {\n this.ctx.selectOption(options[index]);\n }\n }\n}\n","import { isCalendarFormat } from \"../date/formatType\";\nimport type {\n OptionOverride,\n OptionOverrides,\n Suggestion,\n SuggestionOption,\n} from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\n\n/** Which pill an override is answering for. */\nexport type OptionSourceMode = \"pill\" | \"edit\";\n\n/** What a scheduler tick got back — see {@link OptionSourceController.tick}. */\nexport type OptionSourceTick = \"asked\" | \"owns\" | \"no\";\n\nexport interface OptionSourceCallbacks {\n /**\n * A non-empty answer was written to the store. The orchestrator re-runs\n * exact-match promotion: the user may have finished typing an option while\n * the override was still answering.\n */\n onAnswer?: (mode: OptionSourceMode, query: string) => void;\n /**\n * The override answered empty. The pill has nothing to offer for this phrase,\n * and the server is the fallback for it — the orchestrator decides whether a\n * request goes out (it does for a typed phrase on the active pill; a re-edit\n * has its own exit-and-fetch path).\n */\n onMiss?: (mode: OptionSourceMode, query: string) => void;\n}\n\ninterface Target {\n mode: OptionSourceMode;\n /**\n * Identity of the pill on screen: the suggestion object itself, or the id of\n * the completed param being re-edited. A response replaces every suggestion\n * object, so a new key after a response is what \"this pill just became\n * active\" looks like — and it is also why an answer must keep the key it\n * writes under in step (see `commit`).\n */\n key: object | string;\n type: string;\n suggestion: Suggestion;\n query: string;\n}\n\ninterface Ask {\n key: object | string;\n type: string;\n query: string;\n abort: AbortController | null;\n}\n\n/**\n * Drives `optionOverrides`: asks the consumer for a pill's options and writes\n * the answer into that pill.\n *\n * Two triggers, no timer of its own:\n * - **A pill becomes active** (store subscription): asked immediately, with\n * whatever the user has already typed for it. A response landing after the\n * user moved on is the normal case, so \"\" is not assumed.\n * - **The phrase changes** ({@link tick}, called by `FetchController` on its\n * own debounce): the same 100ms / 300ms cadence and the same\n * two-characters-or-wait rule the server request follows, measured against\n * the phrase last asked. One scheduler, so the two can't drift apart.\n *\n * While an override owns the pill on screen the server is not asked — the\n * tick tells the scheduler so. The server becomes the fallback the moment the\n * override answers empty for a phrase, and that phrase is remembered so the\n * consumer isn't asked the same question again when the server's answer\n * re-suggests the very same type (which would otherwise loop: miss → server →\n * same pill → ask → miss → …).\n *\n * Failure is contained by contract: a throw or a rejection is logged at most\n * once per instance and treated as an empty answer.\n */\nexport class OptionSourceController {\n private unsubscribe: (() => void) | null = null;\n /** The ask in flight or last answered for the pill on screen. */\n private current: Ask | null = null;\n private version = 0;\n /** The last (type, phrase) the consumer answered empty for. One slot: only the pill on screen matters. */\n private lastMiss: { type: string; query: string } | null = null;\n private hasLoggedError = false;\n\n constructor(\n private store: Store<CoreState>,\n private getOverrides: () => OptionOverrides | undefined,\n private callbacks: OptionSourceCallbacks = {},\n ) {}\n\n start(): void {\n this.unsubscribe = this.store.subscribe((next) => this.sync(next));\n this.sync(this.store.get());\n }\n\n dispose(): void {\n this.unsubscribe?.();\n this.unsubscribe = null;\n this.cancel();\n this.current = null;\n }\n\n /** A new session: nothing answered, nothing missed. The store's own field is reset by the caller. */\n reset(): void {\n this.cancel();\n this.current = null;\n this.lastMiss = null;\n }\n\n /**\n * The `optionOverrides` record was swapped. Whatever the old record was\n * asked — or answered empty — no longer describes the consumer: an ask in\n * flight is abandoned (its answer would otherwise land under the new\n * record's name), the miss memory is dropped, and the pill on screen is\n * asked afresh through the new record, or released if it no longer has an\n * override.\n */\n refresh(): void {\n this.reset();\n this.sync(this.store.get());\n }\n\n /** Whether `type` has an override at all. */\n hasOverride(type: string): boolean {\n return Boolean(this.getOverrides()?.[type]);\n }\n\n /**\n * Whether the consumer, not the server, is answering `type` for `query`:\n * there is an override and it has not already said there is nothing for\n * this phrase. Read by the fetch scheduler's gate and by the re-edit\n * no-match exit, which must wait for the consumer rather than judge a\n * pending answer by the previous one.\n */\n owns(type: string, query: string): boolean {\n return this.hasOverride(type) && !this.missCovers(type, query);\n }\n\n /**\n * The fetch scheduler's debounce tick. `minDiff` is the scheduler's own\n * character-delta rule for this tick (2 on the fast timer, 1 on the slow).\n *\n * - `\"asked\"`: the override was just asked for the new phrase; the tick is\n * spent and the slow timer can be dropped.\n * - `\"owns\"`: the override owns the pill but this tick didn't warrant a new\n * ask (phrase unchanged, or the delta is under `minDiff` — the slow timer\n * will re-check). No server request either way.\n * - `\"no\"`: no override owns the pill for this phrase; the scheduler's own\n * gates decide.\n */\n tick(state: CoreState, minDiff: number): OptionSourceTick {\n const target = this.resolveTarget(state);\n if (!target) return \"no\";\n if (this.missCovers(target.type, target.query)) return \"no\";\n const current = this.current;\n if (current && current.key === target.key) {\n if (current.query === target.query) return \"owns\";\n // The server gate's shape: the fast tick wants two characters of change\n // and the slow tick takes any change; a deletion always counts.\n const delta = Math.abs(target.query.length - current.query.length);\n const shorter = target.query.length < current.query.length;\n if (minDiff > 1 && !shorter && delta < minDiff) return \"owns\";\n }\n this.ask(target);\n return \"asked\";\n }\n\n /** Store subscription: a pill (or re-edit) coming on screen is asked at once; leaving clears. */\n private sync(state: CoreState): void {\n const target = this.resolveTarget(state);\n if (!target) {\n if (this.current) {\n this.cancel();\n this.current = null;\n }\n if (state.optionSearch) this.store.set({ optionSearch: null });\n return;\n }\n if (this.current && this.current.key === target.key) return;\n this.ask(target);\n }\n\n private resolveTarget(state: CoreState): Target | null {\n // A date — one day or a span — is answered with a calendar; the options,\n // the consumer's or the server's, are never on screen, so there is nothing\n // to ask for.\n if (isCalendarFormat(state.activeFormatType)) return null;\n // An identified span being answered is not a pill and has no options.\n if (state.editingIdentified) return null;\n const editing = state.editingParam;\n if (editing && state.editingAnchor != null) {\n if (!this.hasOverride(editing.suggestionType)) return null;\n return {\n mode: \"edit\",\n key: editing.id,\n type: editing.suggestionType,\n suggestion: {\n type: editing.suggestionType,\n text: editing.suggestionPlaceholder,\n required: true,\n options: editing.options,\n },\n query: state.optionQuery,\n };\n }\n const active = state.actionableSuggestions[0];\n if (!active || !this.hasOverride(active.type)) return null;\n return {\n mode: \"pill\",\n key: active,\n type: active.type,\n suggestion: active,\n query: state.optionQuery,\n };\n }\n\n private missCovers(type: string, query: string): boolean {\n return this.lastMiss != null && this.lastMiss.type === type && this.lastMiss.query === query;\n }\n\n private ask(target: Target): void {\n this.cancel();\n const override = this.getOverrides()?.[target.type];\n const version = ++this.version;\n if (!override || this.missCovers(target.type, target.query)) {\n // Nothing to ask: the consumer already said there is nothing for this\n // phrase. Record the pill as covered so the tick doesn't re-ask, and\n // mark the search settled so the loading state can't linger.\n this.current = { key: target.key, type: target.type, query: target.query, abort: null };\n this.setSearch(target, \"done\");\n return;\n }\n const abort = new AbortController();\n this.current = { key: target.key, type: target.type, query: target.query, abort };\n\n // A synchronous throw and a rejection are the same failure to the caller;\n // routing both through one try lets a plain-array answer skip the\n // loading state entirely — the list is there before this call returns.\n let raw: ReturnType<OptionOverride>;\n try {\n raw = override(target.query, abort.signal, target.suggestion);\n } catch (err) {\n this.logOnce(err);\n this.commit(target, [], version);\n return;\n }\n if (Array.isArray(raw)) {\n this.commit(target, raw, version);\n return;\n }\n this.setSearch(target, \"loading\");\n this.await(target, raw, abort.signal, version);\n }\n\n private async await(\n target: Target,\n pending: Promise<SuggestionOption[]>,\n signal: AbortSignal,\n version: number,\n ): Promise<void> {\n let answer: SuggestionOption[];\n try {\n const raw = await pending;\n // Two guards, not one: `version` catches a newer ask that already went\n // out, `signal.aborted` catches an override that resolved instead of\n // rejecting after we cancelled it.\n if (signal.aborted || version !== this.version) return;\n answer = Array.isArray(raw) ? raw : [];\n } catch (err) {\n // An abort is the SDK cancelling its own ask — not a failure, and the\n // newer ask owns the pill from here.\n if (signal.aborted || version !== this.version || isAbortError(err)) return;\n this.logOnce(err);\n answer = [];\n }\n this.commit(target, answer, version);\n }\n\n private commit(target: Target, answer: SuggestionOption[], version: number): void {\n if (version !== this.version) return;\n const live = this.resolveTarget(this.store.get());\n if (!live || live.key !== target.key) return;\n\n if (answer.length === 0) {\n this.lastMiss = { type: target.type, query: target.query };\n } else if (this.lastMiss?.type === target.type) {\n this.lastMiss = null;\n }\n\n const search = { type: target.type, query: target.query, status: \"done\" as const };\n if (target.mode === \"edit\") {\n this.store.set((s) => ({\n editingParam:\n s.editingParam && s.editingParam.id === target.key\n ? { ...s.editingParam, options: answer }\n : s.editingParam,\n optionSearch: search,\n }));\n } else {\n // The answered pill is a new object, and `current.key` must move with\n // it BEFORE the write: `sync` runs inside this write's notification and\n // would otherwise read the replacement as a pill that just came on\n // screen — and ask again, forever.\n const answered: Suggestion = { ...target.suggestion, options: answer };\n if (this.current && this.current.key === target.key) this.current.key = answered;\n this.store.set((s) => ({\n suggestions: s.suggestions.map((sg) => (sg === target.suggestion ? answered : sg)),\n optionSearch: search,\n }));\n }\n\n if (answer.length === 0) this.callbacks.onMiss?.(target.mode, target.query);\n else this.callbacks.onAnswer?.(target.mode, target.query);\n }\n\n /** Skip no-op writes so an unchanged search doesn't churn subscribers. */\n private setSearch(target: Target, status: \"loading\" | \"done\"): void {\n const prev = this.store.get().optionSearch;\n if (\n prev &&\n prev.type === target.type &&\n prev.query === target.query &&\n prev.status === status\n ) {\n return;\n }\n this.store.set({ optionSearch: { type: target.type, query: target.query, status } });\n }\n\n private cancel(): void {\n this.current?.abort?.abort();\n // Invalidate any ask in flight even when it has no controller to abort —\n // a synchronous answer's version must not be reused by a later commit.\n this.version++;\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] an optionOverrides entry threw or rejected — treated as no options. 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","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 { CustomFieldsReport, MatchedItem, Product } from \"../shared-types\";\n\n/**\n * Where the product strip links a server-matched item to. The server ships a\n * bare `handle` on purpose: the only shop domain it holds is the internal one,\n * and an absolute link built from it would send a shopper off the storefront\n * the widget is running on. A relative path resolves against the page the\n * widget is on, which for a storefront embed is the shop itself.\n */\nexport const PRODUCT_PATH_PREFIX = \"/products/\";\n\n/**\n * Maps one item the `/suggest` response reported onto the strip's `Product`\n * shape. Pure, so a Tier 3 consumer rendering its own strip from\n * `customFields` gets the same cards the built-in one draws.\n *\n * An item without a `handle` yields a card without a `url`: still activatable,\n * still emitting `onProductSelect`, just nothing for cmd-click to open.\n */\nexport function productFromMatchedItem(item: MatchedItem): Product {\n return {\n id: item.source_id,\n title: item.title,\n ...(item.handle && { url: `${PRODUCT_PATH_PREFIX}${encodeURIComponent(item.handle)}` }),\n imageUrl: item.image_url || null,\n ...(item.price && { price: item.price }),\n ...(item.vendor && { vendor: item.vendor }),\n };\n}\n\n/**\n * The strip's contents for a response's catalog report. `null` — no block on\n * the response — is an empty strip rather than \"keep the previous one\": the\n * previous items answered a previous query, and a strip must never show\n * results belonging to an older one.\n */\nexport function productsFromCustomFields(report: CustomFieldsReport | null | undefined): Product[] {\n return (report?.items.matched ?? []).map(productFromMatchedItem);\n}\n","import type { CustomFieldsReport, Product, ProductsConfig } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { productsFromCustomFields } from \"../utils/matchedItems\";\n\n/**\n * Drives the dropdown's product strip from one of two sources.\n *\n * **The `/suggest` response** (the default): every response either carries the\n * items the query narrowed the catalog to (`custom_fields.items`) or carries\n * nothing, and the strip mirrors that — {@link forResponse} maps the block\n * into the same store write that lands the response's suggestions, so cards\n * and options describe one narrowing and never disagree about which query\n * they answer.\n *\n * **A custom integration** (`opts.products`): the consumer's own search owns\n * the strip and the server's items are not rendered. Deliberately owns **no\n * timer and no AbortController of its own**. Every run is kicked off by\n * {@link FetchController} from inside the one request it was already making,\n * and rides that request's debounce, abort signal and version counter. A\n * second scheduler here would drift out of step with `/suggest` (the\n * 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 /** `opts.showProducts !== false`. Off, neither source writes a card. */\n private isEnabled: () => boolean = () => true,\n ) {}\n\n /**\n * The strip's contents for a `/suggest` response about to be committed, or\n * `undefined` to leave `products` as it is because the strip is not the\n * server's to fill — a custom integration owns it and writes it from\n * {@link run}.\n *\n * Consulted from inside the fetch controller's final write rather than\n * after it, so the cards land in the same notification as the suggestions.\n */\n forResponse(report: CustomFieldsReport | null): Product[] | undefined {\n if (!this.isEnabled()) return [];\n if (this.getConfig()) return undefined;\n return productsFromCustomFields(report);\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 // A hidden strip runs no search: `resync` already emptied it, and the\n // response's own items are dropped by `forResponse`.\n if (!this.isEnabled()) return;\n const config = this.getConfig();\n // No integration: the server's items fill the strip when the response\n // lands (`forResponse`); nothing to do at request time.\n if (!config) return;\n\n if (query.trim().length === 0) {\n this.clear(isCurrent);\n return;\n }\n\n // Three guards, not one: `isCurrent` catches a newer query that already\n // went out, `signal.aborted` catches an integration that resolved instead\n // of rejecting after we cancelled it, and `owns` catches the strip having\n // been taken away from this search while it was in flight — `update()`\n // hid it or swapped the integration, and `resync` already settled what it\n // shows. Neither of the first two notices that: a config change bumps no\n // fetch version and aborts no request.\n const stale = () => signal.aborted || !isCurrent() || !this.owns(config);\n\n try {\n const raw = await config.fetch(query, signal);\n if (stale()) 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 (stale()) 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 if (stale()) return;\n this.clear(isCurrent);\n }\n }\n\n /** Whether `config` is still the integration the strip belongs to. */\n private owns(config: ProductsConfig): boolean {\n return this.isEnabled() && this.getConfig() === config;\n }\n\n /**\n * Re-derive the strip after `update()` changed its source. A swapped or\n * newly set integration clears the strip — the next fetch repopulates it\n * from the new config, since the previous one's cards no longer describe\n * anything. A removed integration hands the strip back to the server's\n * items, which are already in the store, so they show at once. A hidden\n * strip empties.\n */\n resync(): void {\n const products = this.forResponse(this.store.get().customFields);\n this.commit(products ?? []);\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;\nconst WEB_TWO_COL_VISIBLE_ROWS = 3;\n\n/** Option count from which web tries the two-column layout (see planOptionsGrid). */\nconst TWO_COLUMN_MIN = 5;\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 (scroll past four).\n *\n * This is the measurement-free baseline (and the only layout mobile ever\n * uses). On web, `planOptionsGrid` upgrades five-plus options to two columns\n * when the rendered rows prove they fit — the upgrade needs the DOM, so it\n * can't live here.\n */\nexport function computeOptionsGridLayout(count: number, isMobile: boolean): OptionsGridLayout {\n if (isMobile) return layout(1, Math.min(count, MOBILE_VISIBLE_ROWS));\n return layout(1, Math.min(count, WEB_VISIBLE_ROWS));\n}\n\nfunction layout(cols: number, rows: number): OptionsGridLayout {\n return {\n cols,\n rows,\n maxHeight: `calc(${rows} * ${ROW_HEIGHT} + ${RESERVED_BAND})`,\n };\n}\n\nexport interface OptionsGridPlan extends OptionsGridLayout {\n /**\n * Value for `grid-template-columns`. Tracks carry no inner spaces (the\n * keyboard controller counts tracks by splitting on spaces).\n */\n template: string;\n /**\n * Indices of the options on the last visible row of a scrollable web grid —\n * their text gets the fade that signals more options below. Empty on mobile\n * and whenever everything is already visible.\n */\n scrollHintIndices: number[];\n}\n\n/**\n * Whether measuring the rendered rows could change the plan at all. Measuring\n * clones every row and forces a layout, and renders happen per keystroke —\n * so the callers skip it whenever `planOptionsGrid` would ignore the result\n * anyway (mobile, or fewer options than the two-column minimum).\n */\nexport function needsOptionsGridMeasurement(count: number, isMobile: boolean): boolean {\n return !isMobile && count >= TWO_COLUMN_MIN;\n}\n\n/**\n * The full layout decision, measurements included.\n *\n * Web, five-plus options: two columns of three visible rows — but only when\n * every option provably fits on one line in half the grid. Rows fill\n * row-major (even indices left, odd right) and the two columns are always the\n * same width, a plain 50/50 split, so the fit check is that the widest option\n * fits in half of `gridWidth`. Content-sized tracks were tried twice (split in\n * proportion to the column maxima, then left column to its content) and both\n * read as columns of \"weird widths\" (2026-09-04); an even split is what the\n * eye expects. When the widest option doesn't fit in half — or there are no\n * usable measurements (SSR, hidden grid, loading skeletons) — the layout stays\n * one scrollable column.\n *\n * `rowWidths` are single-line pixel widths of the rendered rows (see\n * `measureOptionsGrid`); `gridWidth` is the grid's content width.\n */\nexport function planOptionsGrid(\n count: number,\n isMobile: boolean,\n rowWidths: number[] | null,\n gridWidth: number | null,\n): OptionsGridPlan {\n const twoCol = !isMobile && count >= TWO_COLUMN_MIN && fitsTwoColumns(rowWidths, gridWidth);\n if (!twoCol) {\n const base = computeOptionsGridLayout(count, isMobile);\n return {\n ...base,\n template: optionsGridTemplateColumns(base.cols),\n scrollHintIndices: !isMobile && count > base.rows ? [base.rows - 1] : [],\n };\n }\n const visible = WEB_TWO_COL_VISIBLE_ROWS * 2;\n return {\n ...layout(2, WEB_TWO_COL_VISIBLE_ROWS),\n template: optionsGridTemplateColumns(2),\n scrollHintIndices: count > visible ? [visible - 2, visible - 1] : [],\n };\n}\n\n/**\n * Whether the widest row fits in half the grid — the only fit that matters\n * when the two tracks are equal. Rounded up so a fractional width never\n * squeaks through and wraps.\n */\nfunction fitsTwoColumns(rowWidths: number[] | null, gridWidth: number | null): boolean {\n if (!rowWidths || rowWidths.length === 0 || gridWidth == null || gridWidth <= 0) return false;\n // A zero width means the row wasn't really rendered (hidden grid, jsdom) —\n // no evidence it fits, so don't pretend it does.\n if (rowWidths.some((w) => w <= 0)) return false;\n const widest = Math.ceil(Math.max(...rowWidths));\n return widest * 2 <= gridWidth;\n}\n\n/**\n * Measure the rendered option rows for `planOptionsGrid`: each row's\n * single-line width (a hidden `max-content` + `nowrap` clone, so real classes,\n * fonts, icons, tags and padding are all in the number), and the width the\n * columns have to share — the grid's content box less its column gap.\n * Returns null when there's nothing to measure.\n */\nexport function measureOptionsGrid(\n grid: HTMLElement,\n): { rowWidths: number[]; gridWidth: number } | null {\n if (typeof document === \"undefined\") return null;\n const items = Array.from(grid.querySelectorAll<HTMLElement>(\"[data-aia-option]\"));\n if (items.length === 0) return null;\n const probe = document.createElement(\"div\");\n probe.style.cssText = \"position:absolute;visibility:hidden;left:-9999px;top:0;\";\n for (const item of items) {\n const clone = item.cloneNode(true) as HTMLElement;\n // A clone is a width probe, not a row: no duplicate DOM id, and no second\n // option in the accessibility tree, however briefly.\n clone.removeAttribute(\"id\");\n clone.removeAttribute(\"role\");\n clone.removeAttribute(\"aria-selected\");\n clone.style.whiteSpace = \"nowrap\";\n clone.style.width = \"max-content\";\n probe.appendChild(clone);\n }\n // Inside the grid so the clones inherit the same cascade (custom properties,\n // hashed module classes, fonts) as the rows they stand in for.\n grid.appendChild(probe);\n const rowWidths = Array.from(probe.children).map((c) => (c as HTMLElement).offsetWidth);\n probe.remove();\n const cs = getComputedStyle(grid);\n // The column gap comes off the top along with the padding, so `gridWidth` is\n // the width the two columns actually share. `fitsTwoColumns` checks that\n // twice the widest row fits in it, and the two equal `fr` tracks it plans\n // divide exactly this much space — a gap left in the number would let a\n // widest row sizing between `(gridWidth - gap) / 2` and `gridWidth / 2` pass\n // the fit check and then wrap, which is the one thing the measurement exists\n // to prevent. The options grid pins `--aia-grid-gap` to 0 today\n // (renderSuggestionGrid), so this reads 0; subtracting it anyway keeps the\n // check honest if that token ever changes.\n const gridWidth =\n grid.clientWidth -\n (Number.parseFloat(cs.paddingLeft) || 0) -\n (Number.parseFloat(cs.paddingRight) || 0) -\n (Number.parseFloat(cs.columnGap) || 0);\n return { rowWidths, gridWidth };\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 * while `opts.showProducts` is false, 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 {\n buildDateOptions,\n type DateMonthView,\n parseDate,\n todayView,\n viewOf,\n} from \"../date/dateGrid\";\nimport { selectedRangeFor } from \"../date/dateRange\";\nimport { isCalendarFormat, resolveFormatType } from \"../date/formatType\";\nimport type { CompletedParamState, Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreDerivedState, CoreInputState, CoreOptions } 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/**\n * Identity of the pill the datepicker is open for.\n *\n * `inputs.dateViewMonth` is only honoured while its key still matches, so a\n * month the user paged to is scoped to the pill they paged it for. Every flow\n * that swaps the active pill — a selection, a skip, a landing response,\n * entering or leaving re-edit — changes this string as a side effect, which is\n * why none of them has to reset the view explicitly.\n */\nexport function dateViewKey(inputs: {\n editingParam: CompletedParamState | null;\n editingAnchor: number | null;\n editingIdentified: { type: string; anchor: number } | null;\n actionableSuggestions: Suggestion[];\n}): string {\n if (inputs.editingIdentified) {\n // Keyed on where the span sits, not on its id. The id is re-issued by every\n // response that lands mid-pick, so keying on it would throw away a month\n // the user had paged to each time one arrived — undoing the continuity the\n // re-bind in `AIAutocomplete` exists to preserve.\n const { type, anchor } = inputs.editingIdentified;\n return `identified:${type}@${anchor}`;\n }\n const editing = inputs.editingParam != null && inputs.editingAnchor != null;\n return editing\n ? `edit:${inputs.editingParam?.id}`\n : `pill:${inputs.actionableSuggestions[0]?.type ?? \"\"}`;\n}\n\n/**\n * Which month to open on: the month of the date already in the param being\n * re-edited, otherwise the current month.\n */\n/** The month a `YYYY-MM-DD` falls in, or the current month when there isn't one. */\nfunction defaultViewForIso(iso: string | null): DateMonthView {\n if (!iso) return todayView();\n const [y, m] = iso.split(\"-\").map(Number);\n return { year: y, month: m - 1 };\n}\n\nfunction defaultDateView(editingText: string | undefined): DateMonthView {\n const existing = parseDate(editingText);\n return existing ? viewOf(existing) : todayView();\n}\n\n// Pure derive: raw inputs + opts → segments / actionable / filtered / placeholder / isDropdownOpen.\nexport function deriveAll(inputs: CoreInputState, opts: CoreOptions): CoreDerivedState {\n const segments = deriveSegments(inputs.text, inputs.completedParams, inputs.identifiedParams);\n const actionableSuggestions = inputs.suggestions.filter((s) => s.type !== \"placeholder\");\n const activeSuggestion = actionableSuggestions[0] as Suggestion | 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 // No consumer code runs from here. An `optionOverrides` entry answers\n // through the store — `OptionSourceController` writes its result into the\n // suggestion's own `options` — so the derive layer only ever reads state.\n const baseOptions = activeSuggestion?.options ?? [];\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 let optionQuery: string;\n let sourceOptions: SuggestionOption[];\n let sourceType: string | undefined;\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 optionQuery = editQuery.trim();\n sourceOptions = inputs.editingParam.options;\n sourceType = inputs.editingParam.suggestionType;\n filteredOptions = filterOptions(inputs.editingParam.options, editQuery);\n } else {\n optionQuery = filterQuery.trim();\n sourceOptions = baseOptions;\n sourceType = activeSuggestion?.type;\n filteredOptions = filterOptions(baseOptions, filterQuery);\n }\n // A consumer source that has answered for exactly this phrase is shown\n // as-is. Its answer was produced FOR the phrase, and re-filtering it by\n // substring would drop a fuzzy or synonym match (\"St Kilda\" for \"mel\").\n // Any other phrase — the user typed on since — filters the last answer\n // locally until the next one lands, so the list never sits stale for the\n // length of a debounce window.\n const search = inputs.optionSearch;\n const searchMatchesPill = search != null && sourceType != null && search.type === sourceType;\n if (searchMatchesPill && search.status === \"done\" && search.query === optionQuery) {\n filteredOptions = sourceOptions;\n }\n const isSearchingOptions = searchMatchesPill && search.status === \"loading\";\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 // A date suggestion answers with a calendar, so its options — which the\n // server still sends — are replaced wholesale by the visible month's cells.\n // Re-edit asks the same question of the param being edited, via the\n // suggestion type it was completed from, so tapping a bold date re-opens the\n // calendar instead of the cached options it would otherwise filter.\n // An identified-date edit outranks whatever pill is active: the user tapped a\n // specific span in their own text, so that span is what the calendar answers.\n const identifiedEdit = inputs.editingIdentified;\n const formatSource = inEditMode\n ? // The param's cached options are read too, so a re-edited answer whose\n // options were dates re-opens as a calendar rather than as a list.\n inputs.editingParam && {\n type: inputs.editingParam.suggestionType,\n options: inputs.editingParam.options,\n }\n : activeSuggestion;\n // An identified span resolved its own format when the picker opened, and it\n // is carried on the edit rather than re-derived here: it depends on the\n // span's own text (a `travel_date` the user wrote as a span is a range), and\n // only that flow has ever seen it.\n let activeFormatType = identifiedEdit ? identifiedEdit.format : resolveFormatType(formatSource);\n // The span an already-answered param holds, which is both what re-opens the\n // calendar on the right month and — for a param whose type and cached options\n // only said \"date\" — what makes it a range picker at all. Narrowed to params\n // that already resolved to a calendar, so an ordinary option that merely\n // reads like a span (\"September 11 - October 13\" picked from a plain list)\n // still re-opens as the list it came from.\n const editingRange =\n inEditMode && isCalendarFormat(activeFormatType) ? selectedRangeFor(inputs.editingParam) : null;\n if (editingRange) activeFormatType = \"date-range\";\n // Note this runs AFTER the `showNonTappableOptions` filter above and replaces\n // its result wholesale. That is deliberate: a calendar's padding cells are\n // non-tappable but structural — they are what puts the 1st under the right\n // weekday — so honouring that option here would collapse the grid's alignment.\n // The option is about hiding options the user can't pick, and padding is not\n // an option. Pinned by a test so a future tidy-up doesn't \"fix\" it.\n let dateView: DateMonthView | null = null;\n let dateRangeStart: string | null = null;\n if (isCalendarFormat(activeFormatType)) {\n const key = dateViewKey({ ...inputs, actionableSuggestions });\n const paged = inputs.dateViewMonth;\n dateView =\n paged && paged.key === key\n ? { year: paged.year, month: paged.month }\n : identifiedEdit\n ? // The month the span's own text was read as. Null `iso` means the\n // text couldn't be read — the calendar opens on the current month\n // with nothing marked, which costs a page-turn, not correctness.\n defaultViewForIso(identifiedEdit.iso)\n : // A range opens on the month its start falls in: the end is the\n // half being changed more often, but the start is what anchors the\n // span, and paging forward from it is the direction the calendar\n // already reads.\n editingRange\n ? defaultViewForIso(editingRange.start)\n : defaultDateView(inputs.editingParam?.text);\n filteredOptions = buildDateOptions(dateView);\n // Scoped to the pill it was picked for, exactly like the paged month: a\n // half-finished pick belongs to one parameter and must not follow the user\n // to the next one. Scoping is the display rule only — a stale pick is also\n // cleared outright (see `pendingRangeStart`), because a key can come back.\n const pending = inputs.pendingRangeStart;\n if (activeFormatType === \"date-range\" && pending && pending.key === key) {\n dateRangeStart = pending.iso;\n }\n }\n\n // An active pill with a genuinely empty option set (the server or the\n // consumer's override returned no options at all) — distinct from options\n // that were filtered away by the typed query. Keeps the dropdown open so the\n // pill chip stays visible. An override that answered empty for a typed\n // phrase counts as this too: the pill then has nothing to offer for what was\n // typed, and the chip staying up is what tells the user which parameter the\n // text they keep typing is going to.\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 activePillHasNoOptions =\n activeSuggestion != null && baseOptions.filter(countsAsOption).length === 0;\n }\n\n const isDropdownOpen = computeDropdownVisibility(\n {\n // An identified-date edit gates like a re-edit: the caret is parked inside\n // the user's own text, so the caret-at-end rule would close the panel the\n // moment it opened.\n inEditMode: inEditMode || identifiedEdit != null,\n filteredOptionsLength: filteredOptions.length,\n isFocused: inputs.isFocused,\n text: inputs.text,\n caretOffset: inputs.caretOffset,\n // A pending consumer search holds the panel open the way a pending\n // suggest request does: the skeleton it shows needs somewhere to be.\n isLoading: inputs.isLoading || isSearchingOptions,\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 activeFormatType,\n dateView,\n dateRangeStart,\n placeholderText,\n optionQuery,\n isSearchingOptions,\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 icon: match.icon,\n icon_svg: match.icon_svg,\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 // The promoted param ends here in the text — the anchor for keeping\n // completedParams in text order below. The caret itself goes to the END of\n // the input (like ReEditManager.selectOption): committing a re-edited answer\n // mid-text must not strand the caret there, or the caret-at-end gate in the\n // dropdown derive keeps the options box closed for the follow-up suggestion.\n const afterParam = paramEnd < text.length && text[paramEnd] === \" \" ? paramEnd + 1 : paramEnd;\n const caretPos = text.length;\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 icon: match.icon,\n icon_svg: match.icon_svg,\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 >= afterParam) {\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 // Entering a re-edit ends any date pick that was open, the mirror of the\n // `editingParam: null` that starting a pick does. Both set at once is not\n // merely untidy: the derive layer lets an identified pick outrank\n // `editingParam`, so the panel would keep showing the old span's calendar\n // over the param just tapped — and `selectOption` routes an identified\n // pick first, so committing from it would splice text at the PREVIOUS\n // span's offsets and corrupt a part of the query the user never touched.\n editingIdentified: null,\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.promoteIfExact();\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.promoteIfExact();\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 goes to the END of the input, not just past the replacement.\n // Changing an answer immediately re-fetches the next suggestion (see\n // fetchNow below), and the dropdown only opens with the caret at the end\n // of the text — parked mid-text it would close the options box the user\n // is in the middle of using.\n const caretPos = newText.length;\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 icon: option.icon,\n icon_svg: option.icon_svg,\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 at the end of the input 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 /**\n * Promote the typed replacement to a bold param when it exactly matches one\n * of the edited param's options. Runs after every edit keystroke, and again\n * when a consumer override answers for the re-edit — the user may have\n * finished typing the option before the answer arrived.\n */\n promoteIfExact(): void {\n const s = this.deps.store.get();\n if (!s.editingParam || s.editingAnchor == null || s.editingTail == null) return;\n const result = tryPromoteExactMatch({\n mode: \"edit\",\n text: s.text,\n completedParams: s.completedParams,\n editingParam: s.editingParam,\n editingAnchor: s.editingAnchor,\n editingTail: s.editingTail,\n });\n if (!result) return;\n this.deps.store.set(result.patch);\n this.deps.scheduleSetCursor(result.caretPos);\n }\n}\n","/**\n * Types the starting-state placeholder into the input one character at a\n * time instead of popping it in whole.\n *\n * The reference (the RB2B support box, 2026-08-19): ~33 ms per character, no\n * caret, a brief hold on word boundaries; when the phrase changes the old\n * one fades out (~120 ms), the field sits empty for a beat (~50 ms), then the\n * new phrase types in from nothing. Characters appear at full opacity — the\n * only fade is the outgoing phrase.\n *\n * Shared by every package: vanilla, React and Angular all render the\n * placeholder through `renderEditableContent`, which hands the target text\n * to `syncPlaceholder` below. The typed prefix lives in `data-placeholder`\n * (the stylesheets render it via `::before`), so nothing enters the\n * contentEditable's DOM and the core's notion of the placeholder — which\n * drives Tab-to-accept and prefix-typing suppression — stays the full phrase\n * at all times.\n */\n\n/** Delay between characters. */\nexport const PLACEHOLDER_TYPE_MS = 33;\n/** Extra hold after a word boundary (space / hyphen). */\nexport const PLACEHOLDER_WORD_PAUSE_MS = 70;\n/** Fade-out of the outgoing phrase — must match `--aia-placeholder-fade` in the stylesheets. */\nexport const PLACEHOLDER_FADE_OUT_MS = 120;\n/** Empty beat between the fade-out and the next phrase typing in. */\nexport const PLACEHOLDER_SWAP_GAP_MS = 50;\n\n/** Set on the input while the outgoing phrase fades; the stylesheets fade `::before` on it. */\nexport const PLACEHOLDER_LEAVING_ATTR = \"data-aia-placeholder-leaving\";\n\ninterface TypewriterState {\n /** The phrase being typed (or shown). */\n target: string;\n /** How many characters of `target` are on screen. */\n shown: number;\n timer: ReturnType<typeof setTimeout> | null;\n}\n\nconst states = new WeakMap<HTMLElement, TypewriterState>();\n\nfunction clearTimer(state: TypewriterState): void {\n if (state.timer !== null) {\n clearTimeout(state.timer);\n state.timer = null;\n }\n}\n\n/**\n * Whether the host has switched motion off — the SDK's own\n * `animations: false` (`data-animations=\"off\"` on the `.magicx-aia` root) or\n * the OS-level reduced-motion preference. Either way the placeholder appears\n * whole, exactly as it did before the typewriter existed.\n */\nfunction motionDisabled(input: HTMLElement): boolean {\n if (input.closest('[data-animations=\"off\"]')) return true;\n const view = input.ownerDocument?.defaultView;\n return Boolean(view?.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches);\n}\n\nfunction show(input: HTMLElement, state: TypewriterState, count: number): void {\n state.shown = count;\n input.dataset.placeholder = state.target.slice(0, count);\n}\n\nfunction typeNext(input: HTMLElement, state: TypewriterState): void {\n state.timer = null;\n // The element was torn down mid-phrase (unmount / destroy): stop quietly.\n if (!input.isConnected) return;\n if (state.shown >= state.target.length) return;\n show(input, state, state.shown + 1);\n if (state.shown >= state.target.length) return;\n const justTyped = state.target[state.shown - 1];\n const pause = justTyped === \" \" || justTyped === \"-\" ? PLACEHOLDER_WORD_PAUSE_MS : 0;\n state.timer = setTimeout(() => typeNext(input, state), PLACEHOLDER_TYPE_MS + pause);\n}\n\nfunction startTyping(input: HTMLElement, state: TypewriterState): void {\n show(input, state, 0);\n state.timer = setTimeout(() => typeNext(input, state), PLACEHOLDER_TYPE_MS);\n}\n\n/**\n * Bring the input's rendered placeholder in line with `text`.\n *\n * - `\"\"` (no placeholder, or the input has content): remove it at once. The\n * user's own typing must never wait on an animation.\n * - A phrase the input isn't showing yet: type it in. If a different phrase\n * is currently on screen, fade that one out first, then type the new one\n * from nothing.\n * - The phrase already targeted: no-op, so unrelated re-renders never restart\n * or stutter a phrase mid-type.\n *\n * Idempotent and safe to call on every render.\n */\nexport function syncPlaceholder(input: HTMLElement, text: string): void {\n const state = states.get(input);\n\n if (text === \"\") {\n if (state) {\n clearTimer(state);\n states.delete(input);\n }\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n delete input.dataset.placeholder;\n return;\n }\n\n if (state?.target === text) return;\n\n if (motionDisabled(input)) {\n if (state) clearTimer(state);\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n states.set(input, { target: text, shown: text.length, timer: null });\n input.dataset.placeholder = text;\n return;\n }\n\n const next: TypewriterState = { target: text, shown: 0, timer: null };\n const outgoing = state && state.shown > 0 ? state : null;\n if (state) clearTimer(state);\n states.set(input, next);\n\n if (outgoing) {\n // Fade the old phrase out where it stands, then clear and type the new one.\n input.setAttribute(PLACEHOLDER_LEAVING_ATTR, \"\");\n next.timer = setTimeout(() => {\n next.timer = null;\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n if (!input.isConnected) return;\n startTyping(input, next);\n }, PLACEHOLDER_FADE_OUT_MS + PLACEHOLDER_SWAP_GAP_MS);\n return;\n }\n\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n startTyping(input, next);\n}\n\n/** Test / teardown hook: drop any pending timer for this input. */\nexport function cancelPlaceholderTypewriter(input: HTMLElement): void {\n const state = states.get(input);\n if (!state) return;\n clearTimer(state);\n states.delete(input);\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","import { selectedIsoFromText } from \"./dateGrid\";\nimport { type DateRange, orderedRange, selectedRangeFor } from \"./dateRange\";\nimport { type FormatType, isCalendarFormat } from \"./formatType\";\n\n/**\n * What a calendar paints as answered.\n *\n * Three fields rather than one because they answer three different questions,\n * and a range picker shows two of them at once: what is already committed\n * (`selectedIso` for a day, `selectedRange` for a span) and what the user is\n * half-way through picking (`rangeStart`). {@link visibleDateRange} folds the\n * last two into the span actually drawn.\n */\nexport interface DateSelection {\n /** `YYYY-MM-DD` a re-edited single-date param already holds, or null. */\n selectedIso: string | null;\n /** Both ends of a re-edited range param, or null. */\n selectedRange: DateRange | null;\n /** The first end of a range being picked right now, or null. */\n rangeStart: string | null;\n}\n\nconst NOTHING_SELECTED: DateSelection = {\n selectedIso: null,\n selectedRange: null,\n rangeStart: null,\n};\n\n/**\n * The calendar's selection, read off core state.\n *\n * Every package's dropdown needs exactly this, and computing it four times\n * over — vanilla Tier 1, vanilla Tier 2, the React hook, the Angular\n * controller — is how the four copies would come to disagree about which cells\n * a re-edited range fills. Typed structurally so each of them can hand it the\n * state object it already has.\n */\nexport function dateSelectionFor(state: {\n activeFormatType: FormatType;\n editingIdentified: { iso: string | null; isoEnd: string | null } | null;\n editingParam: { text: string; metadata?: Record<string, unknown> } | null;\n dateRangeStart: string | null;\n}): DateSelection {\n // Nothing below is meaningful for a pill answered from a list, and skipping\n // it keeps a plain option whose text happens to read like a span (\"Q1 - Q2\")\n // from being parsed as one on every render.\n if (!isCalendarFormat(state.activeFormatType)) return NOTHING_SELECTED;\n\n const identified = state.editingIdentified;\n if (identified) {\n const { iso, isoEnd } = identified;\n return {\n selectedIso: isoEnd ? null : iso,\n selectedRange: iso && isoEnd ? orderedRange(iso, isoEnd) : null,\n rangeStart: state.dateRangeStart,\n };\n }\n\n const selectedRange = selectedRangeFor(state.editingParam);\n return {\n // A committed span and a committed day are mutually exclusive readings of\n // the same text, and the span is the more specific one — so a range never\n // also reports its start as a single selected date.\n selectedIso: selectedRange ? null : selectedIsoFromText(state.editingParam?.text),\n selectedRange,\n rangeStart: state.dateRangeStart,\n };\n}\n\n/**\n * The span the calendar should paint right now.\n *\n * While a first end is pending, that is the live span between it and whatever\n * the user is pointing at — the preview every range picker shows, which is also\n * what tells them the first tap registered. Otherwise it is the answer already\n * committed, if there is one.\n */\nexport function visibleDateRange(args: {\n pendingStart: string | null;\n highlightedIso: string | null;\n selected: DateRange | null;\n}): DateRange | null {\n if (args.pendingStart) {\n return orderedRange(args.pendingStart, args.highlightedIso ?? args.pendingStart);\n }\n return args.selected;\n}\n\n/**\n * How one cell renders against the current selection: filled at either end of\n * the span, banded in between.\n *\n * Shared by all three packages' calendars so a range can't be painted three\n * subtly different ways. `selectedIso` is the single-date answer, which is\n * mutually exclusive with `range` in practice but costs nothing to fold in\n * here — it keeps every cell's appearance one function call at every call site.\n */\nexport function dateCellMarks(\n iso: string | null,\n args: { selectedIso: string | null; range: DateRange | null },\n): { selected: boolean; inRange: boolean } {\n if (iso == null) return { selected: false, inRange: false };\n const { selectedIso, range } = args;\n const selected =\n iso === selectedIso || (range != null && (iso === range.start || iso === range.end));\n const inRange = range != null && iso > range.start && iso < range.end;\n return { selected, inRange };\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 { DateMonthView } from \"../date/dateGrid\";\nimport { cellDay, cellIso, isoDate, monthLabel, WEEKDAY_LABELS } from \"../date/dateGrid\";\nimport { type DateSelection, dateCellMarks, visibleDateRange } from \"../date/dateSelection\";\nimport type { SuggestionOption } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\nexport interface DateGridCallbacks {\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n onPreviousMonth: () => void;\n onNextMonth: () => void;\n}\n\n/**\n * Renders the datepicker into `parent`, in place of the options grid.\n *\n * The cells ARE `filteredOptions` (see `buildDateOptions`), so they carry the\n * same `${listboxId}-option-${i}` ids, `role=\"option\"` and `aria-selected` that\n * the option grid emits. That is load-bearing, not cosmetic: the keyboard\n * controller navigates by index into `filteredOptions` and commits by clicking\n * `#${listboxId}-option-${i}`, and `getEffectiveColumns` measures the live\n * `grid-template-columns` of option-0's parent — which is why the 7 columns are\n * set on the cell container itself rather than on an ancestor.\n *\n * Creates the picker on first use, reuses it after, and removes it when there\n * is nothing to show.\n */\nexport function renderDateGrid(\n parent: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n listboxId: string,\n view: DateMonthView | null,\n selection: DateSelection,\n callbacks: DateGridCallbacks,\n): void {\n let picker = parent.querySelector<HTMLElement>(\".magicx-aia-datepicker\");\n if (!view || options.length === 0) {\n picker?.remove();\n return;\n }\n if (!picker) {\n picker = buildPicker();\n parent.appendChild(picker);\n }\n\n const label = picker.querySelector<HTMLElement>(\".magicx-aia-datepicker-month\");\n if (label) label.textContent = monthLabel(view);\n\n // Reassigned every render so a reused picker never holds a stale closure —\n // same rule the option grid's handlers follow.\n const prev = picker.querySelector<HTMLButtonElement>(\"[data-aia-date-prev]\");\n const next = picker.querySelector<HTMLButtonElement>(\"[data-aia-date-next]\");\n if (prev) prev.onclick = () => callbacks.onPreviousMonth();\n if (next) next.onclick = () => callbacks.onNextMonth();\n\n const grid = picker.querySelector<HTMLElement>(\".magicx-aia-datepicker-grid\");\n if (grid) renderCells(grid, options, activeIndex, listboxId, selection, callbacks);\n}\n\nfunction renderCells(\n grid: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n listboxId: string,\n selection: DateSelection,\n callbacks: DateGridCallbacks,\n): void {\n const today = isoDate(new Date());\n // While a start is pending, the span runs to whatever cell is highlighted —\n // the hover/arrow preview that tells the user the first tap registered.\n const range = visibleDateRange({\n pendingStart: selection.rangeStart,\n highlightedIso: cellIso(options[activeIndex]),\n selected: selection.selectedRange,\n });\n\n reconcileList(grid, options, {\n // Padding cells have no date of their own, so they key off position. A\n // month change re-keys every real cell anyway, which is what makes the\n // whole grid swap over in one pass.\n keyOf: (option, i) => cellIso(option) ?? `pad-${i}`,\n create: (option) => buildCell(option),\n update: (el, option, i) => {\n const iso = cellIso(option);\n const marks = dateCellMarks(iso, { selectedIso: selection.selectedIso, range });\n const isHighlighted = i === activeIndex && option.is_tappable;\n el.id = `${listboxId}-option-${i}`;\n el.dataset.aiaIndex = String(i);\n el.setAttribute(\"aria-selected\", String(isHighlighted));\n // A day already gone by is de-emphasized, not disabled: it renders\n // dimmed but stays every bit as pickable as the rest of the month.\n el.classList.toggle(\"magicx-aia-date-cell--past\", iso != null && iso < today);\n el.classList.toggle(\"magicx-aia-date-cell--highlighted\", isHighlighted);\n el.classList.toggle(\"magicx-aia-date-cell--today\", iso != null && iso === today);\n el.classList.toggle(\"magicx-aia-date-cell--selected\", marks.selected);\n // Between the two ends of a span — banded rather than filled, so the ends\n // stay the two cells that read as picked.\n el.classList.toggle(\"magicx-aia-date-cell--in-range\", marks.inRange);\n if (option.is_tappable) {\n el.onclick = () => {\n el.classList.add(\"magicx-aia-date-cell--pressed\");\n callbacks.onSelect(option);\n setTimeout(() => el.classList.remove(\"magicx-aia-date-cell--pressed\"), 170);\n };\n el.onmouseenter = () => {\n const idx = Number.parseInt(el.dataset.aiaIndex ?? \"-1\", 10);\n if (idx >= 0) callbacks.onHighlight(idx);\n };\n } else {\n el.onclick = null;\n el.onmouseenter = null;\n }\n },\n });\n}\n\nfunction buildCell(option: SuggestionOption): HTMLElement {\n const cell = document.createElement(\"div\");\n cell.setAttribute(\"role\", \"option\");\n cell.setAttribute(\"data-aia-option\", \"\");\n cell.setAttribute(\"data-aia-date-cell\", \"\");\n\n const day = cellDay(option);\n if (day == null) {\n // Padding for the days before the 1st / after the last. Non-tappable, so\n // arrow navigation skips it; hidden from AT so the month reads as its own\n // days and nothing else.\n cell.className = \"magicx-aia-date-cell magicx-aia-date-cell--blank\";\n cell.setAttribute(\"aria-hidden\", \"true\");\n cell.tabIndex = -1;\n return cell;\n }\n\n cell.className = \"magicx-aia-date-cell magicx-aia-date-cell--day\";\n cell.tabIndex = 0;\n // The cell paints the day number; `option.text` is the full committed value\n // (\"March 23 2026\") and is what AT should announce, since that is what\n // selecting it puts in the input.\n cell.setAttribute(\"aria-label\", option.text);\n const num = document.createElement(\"span\");\n num.className = \"magicx-aia-date-cell-number\";\n num.textContent = String(day);\n cell.appendChild(num);\n return cell;\n}\n\nfunction buildPicker(): HTMLElement {\n const picker = document.createElement(\"div\");\n picker.className = \"magicx-aia-datepicker\";\n picker.setAttribute(\"data-aia-datepicker\", \"\");\n\n const header = document.createElement(\"div\");\n header.className = \"magicx-aia-datepicker-header\";\n\n const month = document.createElement(\"span\");\n month.className = \"magicx-aia-datepicker-month\";\n month.setAttribute(\"data-aia-date-month\", \"\");\n month.setAttribute(\"aria-live\", \"polite\");\n\n header.append(\n buildNavButton(\"prev\", \"Previous month\", \"‹\"),\n month,\n buildNavButton(\"next\", \"Next month\", \"›\"),\n );\n\n const weekdays = document.createElement(\"div\");\n weekdays.className = \"magicx-aia-datepicker-weekdays\";\n // Decorative: the cells already announce their full date, so repeating the\n // column letters to AT adds noise. Duplicate \"T\"/\"S\" letters would be\n // meaningless read aloud anyway.\n weekdays.setAttribute(\"aria-hidden\", \"true\");\n for (const label of WEEKDAY_LABELS) {\n const cell = document.createElement(\"span\");\n cell.className = \"magicx-aia-datepicker-weekday\";\n cell.textContent = label;\n weekdays.appendChild(cell);\n }\n\n const grid = document.createElement(\"div\");\n grid.className = \"magicx-aia-datepicker-grid\";\n grid.setAttribute(\"data-aia-date-grid\", \"\");\n\n picker.append(header, weekdays, grid);\n return picker;\n}\n\nfunction buildNavButton(\n direction: \"prev\" | \"next\",\n label: string,\n glyph: string,\n): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n // Out of the tab order for the same reason the skip button is: Tab is\n // already bound to walking the dropdown's options.\n btn.tabIndex = -1;\n btn.className = \"magicx-aia-datepicker-nav\";\n btn.setAttribute(`data-aia-date-${direction}`, \"\");\n btn.setAttribute(\"aria-label\", label);\n btn.textContent = glyph;\n // Keeps the input focused, which the dropdown's visibility gate depends on.\n btn.addEventListener(\"mousedown\", (e) => e.preventDefault());\n return btn;\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 // No `url`, no `href` — the card is still activatable via the handlers\n // below, and a bare <a> is what \"nothing to open elsewhere\" looks like.\n if (product.url) 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","/**\n * Entrance choreography for the dropdown's option rows.\n *\n * When a new set of options lands, each row fades in while rising into place,\n * and the rows start one after another from the input's side outward — the\n * row nearest the input first. The reference (the RB2B support box's\n * suggestion bubbles, 2026-08-19): opacity 0→1 over ~150 ms, a ~16 px rise\n * easing out over ~280 ms, consecutive rows ~80–110 ms apart.\n *\n * The per-row motion is CSS (the option rule in each package's stylesheet —\n * keep the three copies identical; the parity test compares them). This module\n * owns the *timing numbers* and the per-row delay, so vanilla, React and\n * Angular can't drift onto three different cascades. `OPTION_ENTER_RISE_MS` and\n * `OPTION_ENTER_FADE_MS` must match the durations declared in those rules.\n */\n\n/** Gap between consecutive rows starting their entrance. */\nexport const OPTION_ENTER_STAGGER_MS = 80;\n/** Duration of a row's opacity fade — mirrors the stylesheets' entrance rule. */\nexport const OPTION_ENTER_FADE_MS = 150;\n/** Duration of a row's rise into place — mirrors the stylesheets' entrance rule. */\nexport const OPTION_ENTER_RISE_MS = 280;\n/** Distance a row rises from, in CSS px — mirrors the stylesheets' entrance rule. */\nexport const OPTION_ENTER_RISE_PX = 16;\n\n/** Inline custom property each option carries with its own start delay. */\nexport const OPTION_ENTER_DELAY_VAR = \"--aia-option-enter-delay\";\n\n/**\n * Start delay (ms) for the option at `index` in a grid of `count` options laid\n * out in `cols` columns, when the dropdown sits `optionsPosition` the input.\n *\n * Rows, not cells, are the unit: the cells of one row share a start so a\n * two-column layout reads as rows appearing, not as a zig-zag. \"below\" starts\n * at the top row (the one against the input); \"above\" starts at the bottom row\n * for the same reason — the stack is reversed there and the last row is the\n * one nearest the input.\n */\nexport function optionEnterDelayMs(\n index: number,\n cols: number,\n count: number,\n optionsPosition: \"above\" | \"below\" = \"below\",\n): number {\n const safeCols = Math.max(1, Math.floor(cols));\n const row = Math.floor(Math.max(0, index) / safeCols);\n if (optionsPosition !== \"above\") return row * OPTION_ENTER_STAGGER_MS;\n const rows = Math.max(1, Math.ceil(Math.max(0, count) / safeCols));\n return Math.max(0, rows - 1 - row) * OPTION_ENTER_STAGGER_MS;\n}\n\n/**\n * How long after the options render until the last row has settled — the\n * moment anything that measures the grid (the scroll arrow) can trust its\n * geometry again, since a row mid-rise still extends the scrollable area.\n */\nexport function optionsEntranceDurationMs(count: number, cols: number): number {\n const safeCols = Math.max(1, Math.floor(cols));\n const rows = Math.max(1, Math.ceil(Math.max(0, count) / safeCols));\n return (rows - 1) * OPTION_ENTER_STAGGER_MS + OPTION_ENTER_RISE_MS;\n}\n","const SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/**\n * The only elements an option icon may contain: plain vector drawing. No\n * `<script>`, `<foreignObject>`, `<use>` (external references), `<image>`,\n * `<a>`, `<style>`, `<text>`, animation or filter elements.\n */\nconst ALLOWED_ELEMENTS = new Set([\n \"svg\",\n \"g\",\n \"path\",\n \"circle\",\n \"ellipse\",\n \"line\",\n \"polyline\",\n \"polygon\",\n \"rect\",\n]);\n\n/**\n * Attributes never kept: event handlers, anything that references a URL or\n * another node (`href`, `xlink:href`, and `xml:base`, which would rebase any\n * relative reference under it), inline `style` (which can load resources\n * through `url()`), any presentation attribute — `fill`, `stroke`,\n * `clip-path`, `mask`, `filter` — whose `url()` points outside the document,\n * and `id` / `name`, so an icon can never shadow a page element (the\n * keyboard controller finds option rows by id).\n */\nfunction isDisallowedAttribute(name: string, value: string): boolean {\n const lower = name.toLowerCase();\n return (\n lower.startsWith(\"on\") ||\n lower.includes(\"href\") ||\n lower === \"style\" ||\n lower === \"xml:base\" ||\n lower === \"id\" ||\n lower === \"name\" ||\n /^\\s*javascript:/i.test(value) ||\n /url\\s*\\(\\s*['\"]?\\s*[^#'\"\\s)]/i.test(value)\n );\n}\n\n/**\n * Paint attributes an icon carries are also written onto the element's own\n * `style`. A presentation attribute has no specificity, so a page's\n * `svg { fill: currentColor }` or `path { stroke: red }` — a type selector —\n * would repaint the icon; an inline declaration outranks every selector. Only\n * a value that is plainly a paint is copied — a keyword, a hex or\n * `rgb()` / `hsl()` colour, a number — never one carrying a `url()`, which is\n * what inline `style` is stripped to keep out in the first place.\n */\nconst HARDENED_ATTRIBUTES = [\n \"fill\",\n \"stroke\",\n \"stroke-width\",\n \"stroke-linecap\",\n \"stroke-linejoin\",\n \"fill-rule\",\n \"clip-rule\",\n \"opacity\",\n \"fill-opacity\",\n \"stroke-opacity\",\n];\nconst PLAIN_PAINT = /^[a-zA-Z0-9#.,%\\s()-]+$/;\n\nfunction hardenPaint(el: Element): void {\n const declarations: string[] = [];\n for (const name of HARDENED_ATTRIBUTES) {\n const value = el.getAttribute(name);\n if (value === null || !PLAIN_PAINT.test(value) || /url\\s*\\(/i.test(value)) continue;\n declarations.push(`${name}: ${value.trim()}`);\n }\n if (declarations.length > 0) el.setAttribute(\"style\", declarations.join(\"; \"));\n}\n\nfunction scrub(el: Element): void {\n for (const attr of Array.from(el.attributes)) {\n if (isDisallowedAttribute(attr.name, attr.value)) el.removeAttribute(attr.name);\n }\n hardenPaint(el);\n for (const child of Array.from(el.childNodes)) {\n if (child.nodeType === Node.ELEMENT_NODE) {\n const childEl = child as Element;\n if (!ALLOWED_ELEMENTS.has(childEl.localName) || childEl.namespaceURI !== SVG_NS) {\n childEl.remove();\n } else {\n scrub(childEl);\n }\n } else {\n // Text (indentation between elements, comments): nothing in the\n // allowlist renders text, and a stray text node inside an editable\n // chip would count as characters for the caret. Drop them all.\n child.remove();\n }\n }\n}\n\n/**\n * Reduces an option's `icon_svg` markup to plain vector drawing before it is\n * inserted into the dropdown. The icons come from the server's own curated\n * set, but markup landing in a consumer's page is a trust boundary, so every\n * icon is parsed and filtered through an allowlist rather than inserted raw:\n * only the elements in `ALLOWED_ELEMENTS` survive, stripped of any attribute\n * that could run script or fetch a resource. The root is marked\n * `aria-hidden` / non-focusable — the option's text is its accessible name.\n *\n * Returns the serialized `<svg>` to insert, or `null` when the markup is not\n * a well-formed SVG document (or the environment has no `DOMParser`, as in\n * server rendering), in which case the option renders without an icon.\n */\nexport function sanitizeOptionIconSvg(markup: string): string | null {\n if (typeof DOMParser === \"undefined\" || typeof XMLSerializer === \"undefined\") return null;\n const doc = new DOMParser().parseFromString(markup, \"image/svg+xml\");\n const root = doc.documentElement;\n if (!root || root.localName !== \"svg\" || root.namespaceURI !== SVG_NS) return null;\n if (doc.getElementsByTagName(\"parsererror\").length > 0) return null;\n scrub(root);\n root.setAttribute(\"aria-hidden\", \"true\");\n root.setAttribute(\"focusable\", \"false\");\n return new XMLSerializer().serializeToString(root);\n}\n\n/**\n * The text an option row shows: its `text`, alone. `icon` is the icon's name\n * (`\"apple\"`), an identifier for `data-aia-icon` and consumer CSS, never\n * display text — so it is not prefixed to the option, whether `icon_svg`\n * came with it, failed to draw, or was switched off. A row whose icon cannot\n * draw shows the text by itself rather than the icon's name.\n */\nexport function optionLabel(option: { text: string; icon?: string; icon_svg?: string }): string {\n return option.text;\n}\n","import { OPTION_ENTER_DELAY_VAR, optionEnterDelayMs } from \"../derive/optionsEntrance\";\nimport {\n isOptionsGridMobileViewport,\n measureOptionsGrid,\n needsOptionsGridMeasurement,\n planOptionsGrid,\n} from \"../derive/optionsGridLayout\";\nimport type { SuggestionOption } from \"../shared-types\";\nimport { optionLabel, sanitizeOptionIconSvg } from \"./optionIcon\";\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. Runs after the\n * rows exist so `planOptionsGrid` can measure them (the two-column upgrade\n * needs proof that every row fits on one line). Loading skeletons are never\n * measured — their widths say nothing about the real options — so a loading\n * grid keeps the one-column baseline and no scroll hint. React and Angular\n * apply the same `planOptionsGrid` result to their own grids.\n */\nfunction applyGridLayout(grid: HTMLElement, count: number, loading: boolean): number {\n const isMobile = isOptionsGridMobileViewport();\n const measured =\n !loading && needsOptionsGridMeasurement(count, isMobile) ? measureOptionsGrid(grid) : null;\n const plan = planOptionsGrid(\n count,\n isMobile,\n measured?.rowWidths ?? null,\n measured?.gridWidth ?? null,\n );\n grid.style.gridTemplateColumns = plan.template;\n grid.style.setProperty(\"--aia-grid-max-height\", plan.maxHeight);\n grid.querySelectorAll<HTMLElement>(\"[data-aia-option]\").forEach((el, i) => {\n el.classList.toggle(\n \"magicx-aia-option--scroll-hint\",\n !loading && plan.scrollHintIndices.includes(i),\n );\n });\n // The entrance stagger runs outward from the input across the *rendered*\n // columns, so it needs the count the plan actually settled on — which the\n // two-column upgrade can only decide after measuring the rows.\n return plan.cols;\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 optionsPosition: \"above\" | \"below\" = \"below\",\n showIcons = true,\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 // `data-aia-group` identifies the suggestion whose options are on screen.\n // Stamped BEFORE the rows render: the scroll arrow reads it to tell \"a new\n // set of options is rising in\" from \"the same set re-rendered\".\n const isNewGroup = grid.dataset.aiaGroup !== groupKey;\n grid.dataset.aiaGroup = groupKey;\n renderOptions(grid, options, activeIndex, onSelect, onHighlight, listboxId, loading, showIcons);\n // Measuring needs the rows in the DOM, so the layout lands after the render\n // and the entrance delays after the layout.\n const cols = applyGridLayout(grid, options.length, loading);\n applyEntranceDelays(grid, options.length, cols, loading, optionsPosition);\n // Send the scroll position back to the top whenever the grid starts showing\n // a different suggestion's options (i.e. after a selection), so the next set\n // is read from its first option rather than from wherever the previous list\n // was scrolled to. Filtering within the same suggestion keeps its position.\n if (isNewGroup) 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 showIcons: 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. The group is in\n // the key too: a new suggestion's options are new rows and get the entrance\n // animation even when one of them spells the same as a row already on screen.\n // So is the icon: it is built once, on create, so a row whose icon changed\n // under the same text must be a new row. The name stands in for the markup\n // when there is one — the server's icon set maps each name to one SVG —\n // and the markup itself keys an option that carries only `icon_svg`.\n const loadingFlag = loading ? \"1\" : \"0\";\n const groupKey = grid.dataset.aiaGroup ?? \"\";\n // The icon only exists in the row when icons are on, so a toggle re-keys.\n const iconKey = showIcons ? (opt: SuggestionOption) => opt.icon ?? opt.icon_svg ?? \"\" : () => \"\";\n\n reconcileList(grid, options, {\n keyOf: (opt) => `${opt.text}\\0${iconKey(opt)}\\0${loadingFlag}\\0${groupKey}`,\n create: (option, _i) => {\n const el = buildOptionElement(option, loading, showIcons);\n // Marked, not delayed, here: the stagger runs across the rendered\n // columns, and how many there are is only known once the rows have been\n // measured. `applyEntranceDelays` writes the delay a moment later, onto\n // exactly the rows this pass created — so a re-render of the same rows\n // never restarts their animation.\n if (!loading) el.dataset.aiaEntering = \"\";\n return el;\n },\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\"), 170);\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\n/**\n * Give each row created this pass its entrance delay, now that the column\n * count is settled. Rows start a beat after the one before them, from the\n * input's side outward. Skeleton rows are skipped — they fade in together,\n * being placeholders rather than content arriving.\n */\nfunction applyEntranceDelays(\n grid: HTMLElement,\n count: number,\n cols: number,\n loading: boolean,\n optionsPosition: \"above\" | \"below\",\n): void {\n for (const el of grid.querySelectorAll<HTMLElement>(\"[data-aia-entering]\")) {\n if (!loading) {\n const i = Number.parseInt(el.dataset.aiaIndex ?? \"-1\", 10);\n if (i >= 0) {\n el.style.setProperty(\n OPTION_ENTER_DELAY_VAR,\n `${optionEnterDelayMs(i, cols, count, optionsPosition)}ms`,\n );\n }\n }\n delete el.dataset.aiaEntering;\n }\n}\n\nfunction buildOptionElement(\n option: SuggestionOption,\n loading: boolean,\n showIcons: boolean,\n): 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 content = document.createElement(\"span\");\n content.className = \"magicx-aia-option-content\";\n\n // Server-provided icon, allowlisted before it touches the DOM. The name\n // goes on the row for consumer CSS and tests; the glyph itself is\n // decorative — the text is the option's accessible name.\n const iconHtml = showIcons && option.icon_svg ? sanitizeOptionIconSvg(option.icon_svg) : null;\n if (iconHtml) {\n const icon = document.createElement(\"span\");\n icon.className = \"magicx-aia-option-icon\";\n icon.setAttribute(\"aria-hidden\", \"true\");\n icon.innerHTML = iconHtml;\n content.appendChild(icon);\n if (option.icon) item.dataset.aiaIcon = option.icon;\n }\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 = optionLabel(option);\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 { optionsEntranceDurationMs } from \"../derive/optionsEntrance\";\n\n/**\n * The \"more below\" arrow for the options grid.\n *\n * When the option list is taller than its scroll box, a bare down chevron\n * sits at the bottom-centre of the list and scrolls it a page on click. It\n * never travels with the scroll — every appearance is an opacity fade in\n * place, SCROLL_ARROW_FADE_MS long, and its only motion is the stylesheets'\n * slow idle bob of a few pixels (the stylesheets own both curves):\n *\n * - At rest with more below (the list just opened, or scrolling has stopped\n * short of the end), it fades in.\n * - The moment the list scrolls, it fades out, and stays out while scroll\n * events keep coming.\n * - Once scrolling has been quiet for SCROLL_ARROW_SCROLL_IDLE_MS it fades\n * back in — unless the list is now at its bottom, where it stays out.\n *\n * Earlier versions were a white disc that rose from behind the footer and\n * slid or shrank away; each read as the disc travelling with the scroll\n * (2026-09-03/04), hence the fade-only rule, and on 2026-09-05 the disc was\n * dropped for a plain chevron. The stylesheets key everything off one attribute,\n * `data-aia-visible`: present → opacity 1, absent → opacity 0, both\n * transitioned.\n *\n * The DOM differs per package (vanilla builds the button here; React and\n * Angular render their own markup with the same classes and data attributes),\n * so the *behaviour* lives in this one controller and each package hands it\n * its dropdown, grid and button elements:\n *\n * - `update()` after every render — re-measures where the grid's bottom edge\n * is and re-evaluates visibility. The disc seats itself in the grid's\n * reserved fade band when there is one (below-mode with the footer), and\n * floats a small gap above the grid's edge in layouts that zero the band\n * (`optionsPosition=\"above\"`, product strip). A new\n * option group (`data-aia-group` on the grid changed) keeps the arrow hidden\n * until the rows' entrance animation has settled, because a row mid-rise\n * still extends the scrollable area and would flash the arrow on.\n * - The controller listens for the grid's own `scroll` and size changes itself.\n * - `destroy()` when the grid or dropdown goes away.\n */\n\nexport const SCROLL_ARROW_CLASS = \"magicx-aia-scroll-arrow\";\n/** Stable styling hook (module class names aren't part of the API). */\nexport const SCROLL_ARROW_ATTR = \"data-aia-scroll-arrow\";\n/**\n * Present on the button while it is shown. The stylesheets fade it in on this\n * and back out when it is removed.\n */\nexport const SCROLL_ARROW_VISIBLE_ATTR = \"data-aia-visible\";\n/**\n * Length of the fade, either way. Documents the `opacity` transition in each\n * package's stylesheet, which is what actually runs it — change both together.\n */\nexport const SCROLL_ARROW_FADE_MS = 1000;\n/**\n * Quiet time after the last scroll event before the list counts as \"stopped\"\n * and the arrow may come back. Wheel and trackpad scrolls deliver events a\n * frame or so apart, so this only has to outlast a frame with some slack.\n */\nexport const SCROLL_ARROW_SCROLL_IDLE_MS = 160;\n/** Inline custom property: how far the button's bottom edge sits above the dropdown's padding edge. */\nexport const SCROLL_ARROW_BOTTOM_VAR = \"--aia-scroll-arrow-bottom\";\nexport const SCROLL_ARROW_LABEL = \"Scroll down for more options\";\n\n/** Less remaining scroll than this counts as \"at the bottom\" — sub-pixel and rounding slack. */\nconst AT_BOTTOM_SLACK_PX = 4;\n/** Breathing room above the grid's edge when it has no reserved band to seat the disc in. */\nconst ARROW_GAP_PX = 8;\n/** Length of the page-scroll a click performs. */\nconst PAGE_SCROLL_MS = 240;\n\n/**\n * Scroll the grid by `delta` over PAGE_SCROLL_MS with an ease-out, driven by\n * requestAnimationFrame. Not `scrollBy({ behavior: \"smooth\" })`: that is a\n * hint the browser may ignore (embedded/headless Chromes and some settings\n * drop it entirely), and a click that does nothing is worse than one that\n * jumps. Jumps when motion is off, there is no frame clock, or the document\n * is hidden (browsers pause rAF there, and a scroll that never lands is a bug).\n */\nfunction animateScrollBy(\n grid: HTMLElement,\n delta: number,\n view: Window | null,\n onDone: () => void,\n): void {\n const from = grid.scrollTop;\n const raf = view?.requestAnimationFrame;\n const hidden = grid.ownerDocument?.visibilityState === \"hidden\";\n if (!raf || hidden || motionDisabled(grid)) {\n grid.scrollTop = from + delta;\n onDone();\n return;\n }\n const start = view.performance?.now?.() ?? Date.now();\n const now = () => view.performance?.now?.() ?? Date.now();\n const step = () => {\n const t = Math.min(1, (now() - start) / PAGE_SCROLL_MS);\n const eased = 1 - (1 - t) * (1 - t); // ease-out-quad, matching the rows' rise\n grid.scrollTop = from + delta * eased;\n if (t < 1) raf.call(view, step);\n else onDone();\n };\n raf.call(view, step);\n}\n\n/** The chevron, drawn in the house outline style (single-weight stroke, round caps); 18px, since nothing frames it. */\nexport function buildScrollArrowIcon(doc: Document = document): SVGSVGElement {\n const NS = \"http://www.w3.org/2000/svg\";\n const svg = doc.createElementNS(NS, \"svg\");\n svg.setAttribute(\"viewBox\", \"0 0 16 16\");\n svg.setAttribute(\"width\", \"18\");\n svg.setAttribute(\"height\", \"18\");\n svg.setAttribute(\"fill\", \"none\");\n svg.setAttribute(\"stroke\", \"currentColor\");\n svg.setAttribute(\"stroke-width\", \"1.75\");\n svg.setAttribute(\"stroke-linecap\", \"round\");\n svg.setAttribute(\"stroke-linejoin\", \"round\");\n svg.setAttribute(\"aria-hidden\", \"true\");\n svg.setAttribute(\"focusable\", \"false\");\n const path = doc.createElementNS(NS, \"path\");\n path.setAttribute(\"d\", \"M4 6.5 8 10.5l4-4\");\n svg.appendChild(path);\n return svg;\n}\n\n/** Vanilla's button. React and Angular render the same shape in their own markup. */\nexport function createScrollArrowButton(doc: Document = document): HTMLButtonElement {\n const button = doc.createElement(\"button\");\n button.type = \"button\";\n button.tabIndex = -1;\n button.className = SCROLL_ARROW_CLASS;\n button.setAttribute(SCROLL_ARROW_ATTR, \"\");\n button.setAttribute(\"aria-label\", SCROLL_ARROW_LABEL);\n button.setAttribute(\"aria-hidden\", \"true\");\n button.appendChild(buildScrollArrowIcon(doc));\n return button;\n}\n\nexport interface ScrollArrowController {\n /** Re-measure and re-evaluate. Call after every render of the dropdown. */\n update(): void;\n destroy(): void;\n}\n\nexport interface ScrollArrowArgs {\n /** The dropdown panel — the button's containing block. */\n dropdown: HTMLElement;\n /** The options grid (the scroll box). */\n grid: HTMLElement;\n /** The arrow button, already in the dropdown. */\n button: HTMLElement;\n}\n\nfunction columnCount(grid: HTMLElement): number {\n const tracks = grid.style.gridTemplateColumns.trim();\n if (!tracks) return 1;\n return Math.max(1, tracks.split(/\\s+/).length);\n}\n\nfunction motionDisabled(el: HTMLElement): boolean {\n if (el.closest('[data-animations=\"off\"]')) return true;\n const view = el.ownerDocument?.defaultView;\n return Boolean(view?.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches);\n}\n\nexport function attachScrollArrow({\n dropdown,\n grid,\n button,\n}: ScrollArrowArgs): ScrollArrowController {\n const view = grid.ownerDocument?.defaultView ?? (typeof window !== \"undefined\" ? window : null);\n let destroyed = false;\n let lastGroup: string | null = null;\n let settleTimer: ReturnType<typeof setTimeout> | null = null;\n /** Pending \"scrolling has stopped\" judgement; non-null while the list is scrolling. */\n let idleTimer: ReturnType<typeof setTimeout> | null = null;\n\n const setVisible = (visible: boolean) => {\n if (visible) {\n button.setAttribute(SCROLL_ARROW_VISIBLE_ATTR, \"\");\n button.setAttribute(\"aria-hidden\", \"false\");\n } else {\n button.removeAttribute(SCROLL_ARROW_VISIBLE_ATTR);\n button.setAttribute(\"aria-hidden\", \"true\");\n }\n };\n\n /**\n * Rest the arrow's bottom edge on the grid's lower border edge, so the disc\n * straddles the reserved fade band the rows dissolve into — low against the\n * panel's bottom, the way the reference's disc straddles its composer\n * boundary — whatever sits below the grid in the current layout.\n *\n * Measured with layout metrics (offsetTop / offsetHeight / clientHeight),\n * NOT getBoundingClientRect: rects are post-transform, and this runs in the\n * same pass that starts the panel's unfold — while the dropdown is still\n * scaled to a sliver a rect-based answer is garbage, and nothing re-fires\n * afterwards (ResizeObserver ignores transforms).\n */\n const position = () => {\n // Grid's top within the dropdown's padding box, robust to wrappers.\n let gridTop = 0;\n let el: HTMLElement | null = grid;\n while (el && el !== dropdown) {\n gridTop += el.offsetTop;\n el = el.offsetParent instanceof HTMLElement ? el.offsetParent : null;\n }\n // The grid's bottom padding is its reserved fade band (rows dissolve into\n // the footer there) — the disc's natural seat, so rest right on the\n // border edge and let the disc straddle the band. Layouts that zero the\n // band (optionsPosition=\"above\", product strip) get a small gap instead,\n // so the disc never sits flush against the last row's text.\n const band = view ? Number.parseFloat(view.getComputedStyle(grid).paddingBottom) || 0 : 0;\n const bottom =\n dropdown.clientHeight - (gridTop + grid.offsetHeight) + (band > 0 ? 0 : ARROW_GAP_PX);\n button.style.setProperty(SCROLL_ARROW_BOTTOM_VAR, `${Math.max(0, Math.round(bottom))}px`);\n };\n\n /**\n * Judge from the geometry — but only at rest. While a fresh group's rows are\n * still rising (settleTimer) or the list is mid-scroll (idleTimer) the\n * answer is deferred to whichever timer is pending.\n */\n const evaluate = () => {\n if (destroyed || settleTimer !== null || idleTimer !== null) return;\n // Skeleton rows are placeholders for options that haven't arrived — nothing to scroll to yet.\n const loading = grid.querySelector(\"[data-aia-option][data-aia-loading]\") !== null;\n const remaining = grid.scrollHeight - grid.clientHeight - grid.scrollTop;\n setVisible(!loading && remaining > AT_BOTTOM_SLACK_PX);\n };\n\n const update = () => {\n if (destroyed) return;\n position();\n const group = grid.getAttribute(\"data-aia-group\") ?? \"\";\n if (group !== lastGroup) {\n lastGroup = group;\n // New rows are rising into place: hide until they've settled, then judge.\n setVisible(false);\n if (settleTimer !== null) clearTimeout(settleTimer);\n const count = grid.querySelectorAll(\"[data-aia-option]\").length;\n const wait = motionDisabled(grid) ? 0 : optionsEntranceDurationMs(count, columnCount(grid));\n settleTimer = setTimeout(() => {\n settleTimer = null;\n evaluate();\n }, wait);\n return;\n }\n evaluate();\n };\n\n /**\n * Scrolling: out at once, and stay out while the events keep coming. The\n * quiet period after the last one re-judges — back in if there is still\n * more below, otherwise it stays out. Mid-entrance a scroll changes nothing;\n * the settle timer judges when the rows have landed.\n */\n const onScroll = () => {\n if (destroyed || settleTimer !== null) return;\n setVisible(false);\n if (idleTimer !== null) clearTimeout(idleTimer);\n idleTimer = setTimeout(() => {\n idleTimer = null;\n evaluate();\n }, SCROLL_ARROW_SCROLL_IDLE_MS);\n };\n grid.addEventListener(\"scroll\", onScroll, { passive: true });\n\n const onMouseDown = (e: Event) => e.preventDefault(); // keep focus in the input\n const onClick = () => {\n const pad = view\n ? (Number.parseFloat(view.getComputedStyle(grid).paddingTop) || 0) +\n (Number.parseFloat(view.getComputedStyle(grid).paddingBottom) || 0)\n : 0;\n const page = Math.max(1, grid.clientHeight - pad);\n // A click is a scroll like any other: the disc steps aside while the page\n // travels and comes back once it has landed and gone quiet. Landing feeds\n // the same idle path as the grid's own scroll events rather than judging\n // at once — a browser fires those events asynchronously, so an immediate\n // \"show\" here would be undone a moment later by the event that follows\n // the final scrollTop write, and the disc would blink. In a hidden\n // document (no frames, no events) landing is the only signal, which is\n // why it is fed in explicitly.\n animateScrollBy(grid, page, view, onScroll);\n };\n button.addEventListener(\"mousedown\", onMouseDown);\n button.addEventListener(\"click\", onClick);\n\n let resizeObserver: ResizeObserver | null = null;\n if (view && typeof view.ResizeObserver === \"function\") {\n resizeObserver = new view.ResizeObserver(() => {\n position();\n evaluate();\n });\n resizeObserver.observe(grid);\n }\n\n update();\n\n return {\n update,\n destroy() {\n if (destroyed) return;\n destroyed = true;\n if (settleTimer !== null) clearTimeout(settleTimer);\n settleTimer = null;\n if (idleTimer !== null) clearTimeout(idleTimer);\n idleTimer = null;\n grid.removeEventListener(\"scroll\", onScroll);\n button.removeEventListener(\"mousedown\", onMouseDown);\n button.removeEventListener(\"click\", onClick);\n resizeObserver?.disconnect();\n button.removeAttribute(SCROLL_ARROW_VISIBLE_ATTR);\n button.setAttribute(\"aria-hidden\", \"true\");\n },\n };\n}\n","import type { DateMonthView } from \"../date/dateGrid\";\nimport type { DateRange } from \"../date/dateRange\";\nimport { type FormatType, isCalendarFormat } from \"../date/formatType\";\nimport type { Product, Suggestion, SuggestionOption } from \"../shared-types\";\nimport { buildAttributionUrl } from \"../utils/attribution\";\nimport { getFooterHint } from \"../utils/footerHint\";\nimport { renderDateGrid } from \"./renderDateGrid\";\nimport { renderPills } from \"./renderPills\";\nimport { renderProductStrip, setProductStripFocusable } from \"./renderProductStrip\";\nimport { renderSuggestionGrid } from \"./renderSuggestionGrid\";\nimport {\n attachScrollArrow,\n createScrollArrowButton,\n type ScrollArrowController,\n} from \"./scrollArrow\";\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 — the server's matched items, or a custom integration's results. */\n products: Product[];\n /**\n * How the active pill is answered. `\"date\"` and `\"date-range\"` swap the\n * options grid for the datepicker — `filteredOptions` are then the visible\n * month's cells.\n */\n formatType: FormatType;\n /** The month the datepicker shows. Null unless `formatType` is a calendar one. */\n dateView: DateMonthView | null;\n /** `YYYY-MM-DD` already held by the param being re-edited, so its cell reads as selected. */\n selectedDateIso: string | null;\n /** Both ends of a range param being re-edited, so both cells read as selected. */\n selectedDateRange: DateRange | null;\n /** The first end of a range picked so far, while the user chooses the second. */\n dateRangeStart: string | null;\n /**\n * Which side of the input the dropdown opens on. Drives the option rows'\n * entrance order (the row nearest the input rises first). Default: \"below\".\n */\n optionsPosition?: \"above\" | \"below\";\n /** Whether option rows draw their `icon_svg`. Default: true. */\n showOptionIcons?: boolean;\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 /** Page the datepicker back a month. Never called unless the calendar is showing. */\n onPreviousMonth: () => void;\n /** Page the datepicker forward a month. Never called unless the calendar is showing. */\n onNextMonth: () => 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\ninterface ScrollArrowState {\n button: HTMLButtonElement;\n grid: HTMLElement | null;\n controller: ScrollArrowController | null;\n}\n\n/** Per-dropdown scroll-arrow wiring; the button outlives the grid, the controller doesn't. */\nconst scrollArrows = new WeakMap<HTMLElement, ScrollArrowState>();\n\n/**\n * Keep the \"more below\" arrow bound to whichever grid the dropdown currently\n * shows. The button is created once and appended to the dropdown itself (not\n * the section stack — it's an overlay, not a section); the controller is\n * re-attached whenever the grid element is replaced and torn down when there\n * is no grid. See scrollArrow.ts for the behaviour.\n */\nfunction syncScrollArrow(dropdown: HTMLElement, grid: HTMLElement | null): void {\n let state = scrollArrows.get(dropdown);\n if (!state) {\n state = {\n button: createScrollArrowButton(dropdown.ownerDocument),\n grid: null,\n controller: null,\n };\n dropdown.appendChild(state.button);\n scrollArrows.set(dropdown, state);\n }\n if (state.grid !== grid) {\n state.controller?.destroy();\n state.controller = null;\n state.grid = grid;\n if (grid) {\n state.controller = attachScrollArrow({ dropdown, grid, button: state.button });\n return; // attach() runs the first update itself\n }\n }\n state.controller?.update();\n}\n\n/** Release the dropdown's listeners/timers. Call when the instance is destroyed. */\nexport function destroyDropdown(dropdown: HTMLElement): void {\n const state = scrollArrows.get(dropdown);\n if (!state) return;\n state.controller?.destroy();\n scrollArrows.delete(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 // Exactly one body renders. Each renderer removes its own element when handed\n // nothing, so passing the empty list to the other is what tears the previous\n // body down — no separate cleanup, and no window where both are mounted and\n // two elements answer to `#${listboxId}-option-0`.\n //\n // A calendar is withheld while a request is in flight. The options grid\n // draws its cached options as skeleton rows for that window, but a month of\n // day cells has no skeleton form worth drawing — and a live calendar over a\n // loading pill bar invites an answer against suggestions the response is\n // about to replace. The fallback bars stand in until it lands.\n const isDate = isCalendarFormat(state.formatType);\n const showsDateGrid = isDate && !isLoading;\n\n renderSuggestionGrid(\n stack,\n isDate ? [] : filteredOptions,\n activeIndex,\n onSelect,\n onHighlight,\n state.listboxId,\n isLoading,\n groupKey,\n state.optionsPosition ?? \"below\",\n state.showOptionIcons ?? true,\n );\n syncScrollArrow(dropdown, stack.querySelector<HTMLElement>(\".aia-grid\"));\n\n renderDateGrid(\n stack,\n showsDateGrid ? filteredOptions : [],\n activeIndex,\n state.listboxId,\n showsDateGrid ? state.dateView : null,\n {\n selectedIso: state.selectedDateIso,\n selectedRange: state.selectedDateRange,\n rangeStart: state.dateRangeStart,\n },\n {\n onSelect,\n onHighlight,\n onPreviousMonth: state.onPreviousMonth,\n onNextMonth: state.onNextMonth,\n },\n );\n\n // --- Fallback skeleton bars (when loading with no cached options, or with\n // a calendar's cells, which never draw as skeleton rows) ---\n let skeleton = stack.querySelector<HTMLElement>(\".magicx-aia-skeleton-bars\");\n if (isLoading && (!hasOptions || isDate)) {\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-datepicker\",\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 { dateSelectionFor } from \"../date/dateSelection\";\nimport type { Product, SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { identifiedParamLabel } from \"../utils/paramType\";\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 /** Whether option rows draw their `icon_svg`. Default: true. */\n showOptionIcons?: boolean;\n /** Which side of the input the dropdown opens on. Default: \"below\". */\n optionsPosition?: \"above\" | \"below\";\n selectOption: (option: SuggestionOption) => void;\n setActivePill: (index: number) => void;\n skipActivePill: () => void;\n selectProduct: (product: Product) => void;\n showPreviousMonth: () => void;\n showNextMonth: () => 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 const dateSelection = dateSelectionFor(state);\n renderDropdown(refs.dropdown, {\n // Tier 2's pill bar renders `pills` (the actionable suggestions) rather\n // than this, so the `text` here is not on screen — it only keys the option\n // group for the enter animation. Read into words anyway, so the one string\n // that names an identified parameter is the same in all four places that\n // build it and this stays right if it ever does become visible.\n suggestions: state.editingIdentified\n ? [\n {\n type: state.editingIdentified.type,\n text: identifiedParamLabel(state.editingIdentified.type),\n required: true,\n options: state.filteredOptions,\n },\n ]\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 press animation finishes before\n // we swap in the skeleton. A consumer override still answering shows the\n // skeleton in any mode — its pill has nothing else to show yet.\n isLoading:\n (state.isLoading && !state.editingParam && !state.inSelectionAnimation) ||\n state.isSearchingOptions,\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 && !state.editingIdentified,\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 showOptionIcons: opts.showOptionIcons ?? true,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n formatType: state.activeFormatType,\n dateView: state.dateView,\n selectedDateIso: dateSelection.selectedIso,\n selectedDateRange: dateSelection.selectedRange,\n dateRangeStart: dateSelection.rangeStart,\n optionsPosition: opts.optionsPosition ?? \"below\",\n onSelect: opts.selectOption,\n onHighlight: (i) => opts.store.set({ activeDropdownIndex: i }),\n onPillClick: opts.setActivePill,\n onSkip: opts.skipActivePill,\n onPreviousMonth: opts.showPreviousMonth,\n onNextMonth: opts.showNextMonth,\n onProductSelect: opts.selectProduct,\n onProductFocusChange: (focused) => opts.store.set({ isFocused: focused }),\n });\n}\n","import { getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport { documentOf } from \"../dom/roots\";\nimport type { Segment } from \"../shared-types\";\nimport { sanitizeOptionIconSvg } from \"./optionIcon\";\nimport { syncPlaceholder } from \"./placeholderTypewriter\";\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 /** Whether completed chips draw the icon of the option that answered them. Default: true. */\n showChipIcons?: 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 const showChipIcons = args.showChipIcons ?? true;\n\n const empty = segments.length === 0;\n input.dataset.aiaEmpty = empty ? \"true\" : \"false\";\n // The placeholder types itself in (and clears instantly) — see\n // placeholderTypewriter.ts. `data-placeholder` holds the typed prefix.\n syncPlaceholder(input, empty ? placeholderText : \"\");\n\n // Icons only exist in the chip when they are on, so the toggle re-keys too.\n // A completed chip is built once per render pass, icon included, so a chip\n // whose icon changed under the same text must key differently. The icon\n // name stands in for the markup when there is one — the server maps each\n // name to one SVG — and the markup itself keys a nameless icon.\n const segKey = segments\n .map((s) =>\n s.type === \"completed\"\n ? `${s.type}:${s.value}\\u0001${showChipIcons ? (s.param.icon ?? s.param.icon_svg ?? \"\") : \"\"}`\n : `${s.type}:${s.value}`,\n )\n .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 = documentOf(input);\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 // The picked option's icon, allowlisted before it touches the DOM. Its\n // wrapper opts out of contentEditable so the caret walker skips it: the\n // chip's own text node stays the only text inside the <strong>, and the\n // glyph contributes zero characters to every offset.\n const iconHtml =\n showChipIcons && seg.param.icon_svg ? sanitizeOptionIconSvg(seg.param.icon_svg) : null;\n if (iconHtml) {\n const icon = doc.createElement(\"span\");\n icon.className = \"magicx-aia-segment-icon\";\n icon.setAttribute(\"contenteditable\", \"false\");\n icon.setAttribute(\"aria-hidden\", \"true\");\n icon.innerHTML = iconHtml;\n strong.appendChild(icon);\n if (seg.param.icon) strong.dataset.aiaIcon = seg.param.icon;\n }\n strong.appendChild(doc.createTextNode(seg.value));\n frag.appendChild(strong);\n } else if (seg.type === \"identified\") {\n // Identified spans reuse the completed styling class (per TDB: no\n // distinct styling) and carry data-seg=\"identified\" so consumers can\n // tell the two apart. The click handler deliberately matches BOTH kinds\n // now — the core decides what a tap means, and a span it recognises as a\n // date opens the calendar.\n const strong = doc.createElement(\"strong\");\n strong.dataset.seg = \"identified\";\n strong.dataset.paramId = seg.param.id;\n const identifiedClasses = [\"magicx-aia-segment\", \"magicx-aia-segment--completed\"];\n // Same in-progress affordance a re-edited param gets: while the calendar\n // is open for this span, it should look picked rather than idle.\n if (seg.param.id === editingParamId) identifiedClasses.push(\"magicx-aia-segment--editing\");\n strong.className = identifiedClasses.join(\" \");\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 { dateSelectionFor } from \"../date/dateSelection\";\nimport {\n caretEscapedChip,\n extractPlainText,\n getCursorOffset,\n setCursorOffset,\n} from \"../dom/cursorUtils\";\nimport { documentOf, getSelectionFor } from \"../dom/roots\";\nimport type { AutocompleteResult, Suggestion } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { identifiedParamLabel } from \"../utils/paramType\";\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 /** Whether option rows draw their `icon_svg`. Default: true. */\n showOptionIcons?: boolean;\n /** Whether completed chips draw the icon of the option that answered them. Default: true. */\n showChipIcons?: boolean;\n /** Which side of the input the dropdown opens on. Default: \"below\". */\n optionsPosition?: \"above\" | \"below\";\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 showPreviousMonth: () => void;\n showNextMonth: () => 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 // One of the two roots the consumer-isolation reset in appearance.css scopes\n // to (the dropdown is the other).\n inputWrapper.setAttribute(\"data-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 // The consumer's element: the isolation reset leaves it and its subtree to\n // the consumer's own stylesheet.\n submitTarget.setAttribute(\"data-aia-submit-slot\", \"\");\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 = getSelectionFor(input);\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 // Both chip kinds report here. The core decides what a tap means — a\n // completed param re-opens its options, an identified date opens the\n // calendar, anything else is inert — so this stays a pure \"what was\n // tapped\" lookup rather than encoding the rule three times over.\n const strong = startEl?.closest<HTMLElement>(\"strong[data-param-id]\");\n const paramId = strong?.dataset.paramId ?? null;\n // Inside a shadow root the engine reports the caret from the canonicalized\n // position, and the canonical form of \"just outside a chip\" is a position\n // within the chip's own text — so the caret the SDK parks at a chip's edge\n // (after promoting typed text, after a pick) reads here as a tap on that\n // chip. `caretEscapedChip` is that park's receipt; without it, promoting\n // exactly-typed text drops straight into re-editing what it just completed.\n if (paramId && caretEscapedChip(input, paramId)) return null;\n return paramId;\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 //\n // The document stays the listener target inside a shadow root: the event is\n // dispatched at the document only — not composed, not bubbling out of the\n // shadow root, and not dispatched at the shadow root at all (verified in\n // Chromium against a contenteditable in an open shadow root). Only the\n // *reading* of the selection has to be root-aware, which `getSelectionFor`\n // handles.\n const doc = documentOf(input);\n doc.addEventListener(\n \"selectionchange\",\n () => {\n const sel = getSelectionFor(input);\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 editState = opts.store.get();\n // Either kind counts as \"already open on this chip\" — without the\n // identified id here, every caret tick inside a date span would re-enter\n // the pick it is already in.\n const editingId = editState.editingParam?.id ?? editState.editingIdentified?.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 = documentOf(input);\n const sel = getSelectionFor(input);\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 identifiedParams: state.identifiedParams,\n isReady: state.isReady,\n customFields: state.customFields,\n }),\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 = documentOf(input);\n const sel = getSelectionFor(input);\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 showChipIcons: opts.showChipIcons ?? true,\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 : state.editingIdentified\n ? // Answering a span the user wrote themselves: the pill names the\n // parameter the server matched it to, so the calendar isn't unlabelled.\n {\n type: state.editingIdentified.type,\n text: identifiedParamLabel(state.editingIdentified.type),\n required: true,\n options: [],\n }\n : null;\n const dropdownActivePill = dropdownPill ?? state.actionableSuggestions[0];\n const dateSelection = dateSelectionFor(state);\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 press animation finishes before\n // we swap in the skeleton. A consumer override still answering shows the\n // skeleton in any mode — its pill has nothing else to show yet.\n isLoading:\n (state.isLoading && !state.editingParam && !state.inSelectionAnimation) ||\n state.isSearchingOptions,\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 && !state.editingIdentified,\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 showOptionIcons: opts.showOptionIcons ?? true,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n formatType: state.activeFormatType,\n dateView: state.dateView,\n selectedDateIso: dateSelection.selectedIso,\n selectedDateRange: dateSelection.selectedRange,\n dateRangeStart: dateSelection.rangeStart,\n optionsPosition: opts.optionsPosition ?? \"below\",\n onSelect: selectOption,\n onHighlight: (i) => store.set({ activeDropdownIndex: i }),\n onPillClick: setActivePill,\n onSkip: opts.skipActivePill,\n onPreviousMonth: opts.showPreviousMonth,\n onNextMonth: opts.showNextMonth,\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 icon: option.icon,\n icon_svg: option.icon_svg,\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","import type { WidgetRoot } from \"./dom/roots\";\n\n/**\n * Roots that already carry the SDK stylesheet. Per-root, not a module-global\n * flag: a page can mount one widget in the light DOM and another inside a\n * shadow root, and a global \"already injected\" boolean would style whichever\n * mounted first and leave the other bare.\n */\nconst styledRoots = new WeakSet<WidgetRoot>();\n\n/**\n * One constructable stylesheet per document — the CSS is parsed once and every\n * shadow root on the page adopts that same object. A `CSSStyleSheet` belongs to\n * the document that constructed it (adopting one across documents throws),\n * hence the map.\n */\nconst sheets = new WeakMap<Document, CSSStyleSheet>();\n\nfunction isDocument(root: WidgetRoot): root is Document {\n return root.nodeType === Node.DOCUMENT_NODE;\n}\n\nfunction documentOfRoot(root: WidgetRoot): Document {\n return isDocument(root) ? root : root.ownerDocument;\n}\n\n/** The shared sheet for `doc`, or null where constructable stylesheets aren't supported. */\nfunction constructableSheet(doc: Document): CSSStyleSheet | null {\n const cached = sheets.get(doc);\n if (cached) return cached;\n // Construct through the document's own view: a sheet built from another\n // realm's constructor can't be adopted here.\n const Ctor = doc.defaultView?.CSSStyleSheet;\n if (typeof Ctor !== \"function\") return null;\n try {\n const sheet = new Ctor();\n if (typeof sheet.replaceSync !== \"function\") return null;\n sheet.replaceSync(STYLES);\n sheets.set(doc, sheet);\n return sheet;\n } catch {\n // Older engines expose the constructor but reject `new` on it.\n return null;\n }\n}\n\n/** Append the `<style>` copy of the stylesheet to `host`, unless one is already there. */\nfunction appendStyleElement(doc: Document, host: ParentNode): void {\n // A second copy of the bundle on the page injects its own <style>; one\n // stylesheet per root is enough, and this is the dedupe the module-global\n // flag used to do (globally, which is the bug the per-root guard replaces).\n if (host.querySelector(\"style[data-magicx-aia]\")) return;\n const style = doc.createElement(\"style\");\n style.setAttribute(\"data-magicx-aia\", \"\");\n style.textContent = STYLES;\n host.appendChild(style);\n}\n\n/**\n * Inject core + appearance CSS into a root. Idempotent per root.\n *\n * `root` is the document or shadow root the widget was mounted into — a\n * stylesheet in `document.head` does not cross a shadow boundary, so a widget\n * inside a shadow root has to be styled from that root. Defaults to the\n * document for callers with no element to derive a root from.\n *\n * Shadow roots adopt a single constructable stylesheet (parsed once, however\n * many roots adopt it). The document keeps the `<style>` element it has always\n * had: adopted sheets sort *after* every `<style>`/`<link>` in the document, so\n * moving light-DOM consumers onto one would quietly promote the SDK's rules\n * above any consumer stylesheet injected after mount (a CSS-in-JS runtime, a\n * lazily loaded chunk) that currently overrides them on source order.\n */\nexport function injectStyles(root?: WidgetRoot | null) {\n if (typeof document === \"undefined\") return;\n const target = root ?? document;\n if (styledRoots.has(target)) return;\n\n const doc = documentOfRoot(target);\n if (isDocument(target)) {\n const host = target.head ?? target.documentElement;\n if (!host) return;\n appendStyleElement(doc, host);\n styledRoots.add(target);\n return;\n }\n\n const sheet = constructableSheet(doc);\n const adopted = target.adoptedStyleSheets;\n if (sheet && Array.isArray(adopted)) {\n if (!adopted.includes(sheet)) {\n // Reassign rather than push: the array is frozen in older Chromium.\n target.adoptedStyleSheets = [...adopted, sheet];\n }\n } else {\n appendStyleElement(doc, target);\n }\n styledRoots.add(target);\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 { OptionSourceController } from \"./controllers/optionSourceController\";\nimport { PillsController } from \"./controllers/pillsController\";\nimport { ProductsController } from \"./controllers/productsController\";\nimport { addMonths, cellIso } from \"./date/dateGrid\";\nimport { buildRangeOption, parseLooseDateRange } from \"./date/dateRange\";\nimport { isCalendarFormat, resolveFormatType } from \"./date/formatType\";\nimport { resolveIdentifiedDate } from \"./date/looseDate\";\nimport { OPTIONS_GRID_MOBILE_QUERY } from \"./derive/optionsGridLayout\";\nimport { dateViewKey, deriveAll } from \"./derive/state\";\nimport { previousGraphemeBoundary, setCursorOffset } from \"./dom/cursorUtils\";\nimport { rootOf } from \"./dom/roots\";\nimport { tryPromoteExactMatch } from \"./promotion/promote\";\nimport { ReEditManager } from \"./reEdit/ReEditManager\";\nimport { cancelPlaceholderTypewriter } from \"./render/placeholderTypewriter\";\nimport { destroyDropdown } from \"./render/renderDropdown\";\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 Product,\n SuggestionOption,\n} from \"./shared-types\";\nimport { createDerivedStore, createStore } from \"./state\";\nimport { injectStyles } from \"./styleInjector\";\nimport type { CoreInputState, CoreOptions, CoreState, RenderMode } 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 { identifiedParamLabel } from \"./utils/paramType\";\nimport { coveredEnd, isTrailingCovered, rebaseAnchor } from \"./utils/pendingSpan\";\nimport { reconcileIdentifiedParams, reconcileParams } from \"./utils/segments\";\nimport { buildSubmitResult } from \"./utils/submitResult\";\nimport { sendTelemetry, type TelemetrySource } from \"./utils/telemetry\";\n\nexport type AIAutocompleteEvents = {\n submit: [result: AutocompleteResult];\n result: [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 | \"onResult\"\n | \"onError\"\n | \"onChange\"\n | \"onParamsChange\"\n | \"onStateChange\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onProductSelect\"\n // Mount-time only: the widget's styles are injected once, into the root it\n // was mounted in.\n | \"styleRoot\"\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 = 170;\n\nfunction initialInputs(): CoreInputState {\n return {\n text: \"\",\n completedParams: [],\n identifiedParams: [],\n skippedParams: [],\n pendingSpan: null,\n suggestions: [],\n products: [],\n customFields: null,\n optionSearch: null,\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 dateViewMonth: null,\n pendingRangeStart: null,\n editingIdentified: null,\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 optionSource: OptionSourceController;\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 and `subscribe()`. */\n private boundary = new ConsumerBoundary();\n /** Identity of the raw override record the wrapped copy below was built from. */\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) => deriveAll(inputs, this.opts));\n\n // Register any opts.on* callbacks as the initial listener set. Use `on()`\n // after construction for additional or replacement listeners — `update()`\n // does NOT swap these (intentional, since stable proxies from React refs\n // depend on it).\n if (opts.onSubmit) this.emitter.on(\"submit\", opts.onSubmit);\n if (opts.onResult) this.emitter.on(\"result\", opts.onResult);\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(\n this.store,\n () => this.opts.products,\n () => this.opts.showProducts !== false,\n );\n\n // Consumer-supplied options. Reads opts lazily so `update({ optionOverrides })`\n // swaps the record without rebuilding anything.\n this.optionSource = new OptionSourceController(this.store, () => this.opts.optionOverrides, {\n // The user may have typed the whole option before the answer landed;\n // the keystroke path already ran and found nothing to promote.\n onAnswer: (mode) => {\n if (mode === \"edit\") this.reEdit.promoteIfExact();\n else this.maybePromoteExactMatch(this.store.get().text);\n },\n // The override has nothing for the typed phrase, so the server gets\n // it — through the scheduler's own gates, which is what keeps an empty\n // default list (\"\" answered empty) from re-requesting what the server\n // just sent. A re-edit's miss is handled by `maybeExitReEditOnNoMatch`,\n // which exits the edit and fetches itself.\n onMiss: (mode, query) => {\n if (mode === \"pill\" && query.length > 0) this.fetchController.attemptNow();\n },\n });\n\n this.fetchController = new FetchController(\n this.store,\n () => this.opts.apiConfig,\n () => this.opts.optionOverrides,\n () => this.opts.maskCompletedText,\n () => (this.emitter.hasListeners(\"error\") ? this.emitError : undefined),\n () => this.sessionId,\n () => 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 // Every committed response hands the consumer the query as it now\n // stands. Built from the store AFTER the response's write, not from\n // the request-time snapshot: the response can promote an exact match\n // into a completed param and it replaces the identified set wholesale.\n onResponse: () => {\n if (!this.emitter.hasListeners(\"result\")) return;\n this.emitter.emit(\"result\", this.buildResult());\n },\n // The server's matched items become the strip's cards in the same\n // write as its suggestions — unless a custom integration owns the\n // strip, in which case the response leaves the cards alone.\n productsForResponse: (report) => this.productsController.forResponse(report),\n // An override that owns the active pill takes the debounce tick in\n // place of the server. One scheduler for both, so the consumer's\n // search and the suggest request can't drift onto different cadences.\n consumerTick: (state, minDiff) => this.optionSource.tick(state, minDiff),\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 exitEditingIdentified: () => this.exitEditingIdentified(),\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 // Keep an open date pick pointing at a live identified param.\n //\n // `identifiedParams` is replaced wholesale by every response, and each\n // candidate is minted with a fresh id (see `fetchController`) — so a\n // background fetch landing mid-pick orphans `editingIdentified.id` even\n // when the span itself is untouched. Closing on a missing id would\n // therefore shut the calendar under the user on every debounced fetch,\n // which is why this re-binds to the entry still covering the span and only\n // abandons the pick when the span is genuinely gone (claimed by a\n // completed param, or edited away).\n //\n // Enforced here, once, rather than at each site that writes\n // `identifiedParams`: the fetch response and the reconciliation subscriber\n // both mutate it, and a third writer would otherwise have to remember.\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.identifiedParams === prev.identifiedParams) return;\n const editing = next.editingIdentified;\n if (!editing) return;\n if (next.identifiedParams.some((p) => p.id === editing.id)) return;\n // Located by POSITION, not by text. A bare `find` on text rebinds to\n // the first span with those words, which is the wrong one whenever the\n // same date is written twice (\"meet on monday … call back monday\") and\n // the user is picking for the second: the id would name the first\n // occurrence while `anchor`/`tail` still bound the second, and tapping\n // the first chip would then no-op against a stale identity check.\n // `startEditingIdentified` walks segments for exactly this reason, and\n // so do `findChipSpanAt` and `locateIdentified`.\n let pos = 0;\n let reissued: IdentifiedParamState | undefined;\n for (const seg of next.segments) {\n if (seg.type === \"identified\" && pos === editing.anchor && seg.value === editing.text) {\n reissued = seg.param;\n break;\n }\n pos += seg.value.length;\n }\n if (reissued) {\n this.store.set({ editingIdentified: { ...editing, id: reissued.id } });\n return;\n }\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }),\n );\n\n // Drop a half-finished range pick as soon as the parameter it belongs to\n // stops being the one on screen.\n //\n // The `dateViewKey` scoping in the derive layer only HIDES a stale pick,\n // which is enough for the paged month it was modelled on and is not enough\n // here: every key this can carry reproduces exactly later in a session —\n // the same pill type re-suggested after a skip, the same param re-edited\n // after leaving the edit, the same identified span re-tapped — and a\n // resurrected start would then pair with the user's next tap and commit a\n // span they never chose, silently.\n //\n // Enforced here, once, rather than at each path that abandons a parameter.\n // That list is long and open-ended (skip, a landing response, Escape,\n // exiting a re-edit by click / backspace / arrow / no-match, leaving an\n // identified span) and every one of them would have to remember; this is\n // the same \"one choke point, not N call sites\" reasoning the identified-\n // param reconciler above is written for.\n this.unsubscribers.push(\n this.store.subscribe((next) => {\n const pending = next.pendingRangeStart;\n if (!pending || pending.key === dateViewKey(next)) return;\n this.store.set({ pendingRangeStart: null });\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 // Style the root the widget actually lives in: a stylesheet in\n // `document.head` never crosses into a shadow root, so a shadow-mounted\n // widget would render unstyled. Derived from the container so consumers\n // get this without passing anything; `styleRoot` is the escape hatch for\n // a mount whose root isn't where the styles should land.\n injectStyles(this.opts.styleRoot ?? rootOf(this.container));\n this.setupContainer();\n }\n if (this.renderMode === \"full\") {\n this.buildAndRenderFull();\n } else if (this.renderMode === \"dropdown\") {\n this.buildAndRenderDropdown();\n }\n // Before the first response can land: the controller asks for a pill's\n // options from inside the response's own store write.\n this.optionSource.start();\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 // Before the write, so nothing answered or missed in the old session can\n // cover a pill the new one suggests.\n this.optionSource.reset();\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.optionSource.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 if (this.domRefs) {\n cancelPlaceholderTypewriter(this.domRefs.input);\n destroyDropdown(this.domRefs.dropdown);\n }\n if (this.dropdownRefs) destroyDropdown(this.dropdownRefs.dropdown);\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 const state = this.store.get();\n // A controlled consumer replacing the text moves it out from under an open\n // date pick: the span's offsets would still point into the old string.\n // Guarded on an actual change so controlled re-renders pushing the same\n // value don't close a picker the user is still using.\n if (state.editingIdentified && text !== state.text) {\n this.store.set({ text, editingIdentified: null, activeDropdownIndex: -1 });\n return;\n }\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 /**\n * Open the calendar for a span the server identified as a date.\n *\n * Returns false for anything else — an identified span whose type isn't a\n * date stays inert, exactly as every identified span did before.\n *\n * The span's own text is read for a starting month (`parseLooseDate`), but\n * failing to read it is not a failure: the calendar opens either way, and\n * whatever the user picks replaces the span with the SDK's canonical form. So\n * an unparseable \"sometime next week\" still ends up a clean answer.\n */\n startEditingIdentified(paramId: string): boolean {\n const state = this.store.get();\n if (state.editingIdentified?.id === paramId) return true;\n const param = state.identifiedParams.find((p) => p.id === paramId);\n if (!param) return false;\n const named = resolveFormatType(param);\n if (!isCalendarFormat(named)) return false;\n // The span's own words decide between one date and a span of them: a type\n // the server calls `travel_date` is still a range when what the user wrote\n // under it is \"september 11 to november 16\".\n const range = parseLooseDateRange(param.text);\n const format = named === \"date-range\" || range ? \"date-range\" : \"date\";\n\n // Locate the span the same way the renderer does, so the offsets bound\n // exactly the text the user sees highlighted.\n let pos = 0;\n let anchor = -1;\n for (const seg of state.segments) {\n if (seg.type !== \"text\" && seg.param.id === paramId) {\n anchor = pos;\n break;\n }\n pos += seg.value.length;\n }\n if (anchor < 0) return false;\n\n const tail = anchor + param.text.length;\n this.store.set({\n editingIdentified: {\n id: paramId,\n type: param.type,\n anchor,\n tail,\n text: param.text,\n // A range marks its start; the calendar opens on that month and the\n // user re-picks both ends, which is the same thing a single date does.\n iso: range ? range.start : resolveIdentifiedDate(param),\n isoEnd: range ? range.end : null,\n format,\n },\n // Leaving a completed-param re-edit open alongside this would give two\n // competing answers to \"what is the dropdown for\".\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: tail,\n activeDropdownIndex: -1,\n pillTapped: true,\n });\n return true;\n }\n\n /** Close the calendar opened for an identified span, leaving its text untouched. */\n exitEditingIdentified() {\n if (!this.store.get().editingIdentified) return;\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }\n\n /**\n * Open whatever the chip at `paramId` is answered with.\n *\n * Takes an id from either array so the DOM layers stay dumb: they report\n * which chip was tapped and the core decides what that means. A completed\n * param re-opens its cached options; a span the server identified as a date\n * opens the calendar; anything else is inert, as it has always been.\n */\n startEditingParam(paramId: string) {\n this.reEdit.start(paramId);\n if (this.store.get().editingParam?.id === paramId) return;\n this.startEditingIdentified(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 // Moving the caret out of an identified span ends the pick, the same way\n // it ends a re-edit. Without this the calendar would stay up while the\n // caret sat somewhere else entirely.\n const editing = this.store.get().editingIdentified;\n if (editing && offset != null && (offset < editing.anchor || offset > editing.tail)) {\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }\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 // Editing the span by hand abandons the pick — the text is no longer what\n // the server identified, so the calendar is answering a question that no\n // longer exists. Deliberately before handleChange, whose reconciler drops\n // the identified param on the same keystroke.\n if (this.store.get().editingIdentified) {\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }\n this.handleChange(value);\n }\n\n /** Page the datepicker back one month. No-op unless a date pill is active. */\n showPreviousMonth() {\n this.pageDateView(-1);\n }\n\n /** Page the datepicker forward one month. No-op unless a date pill is active. */\n showNextMonth() {\n this.pageDateView(1);\n }\n\n /**\n * Move the visible month, stamping it with the current pill's key so the\n * derive layer keeps honouring it. Once the active pill changes the key stops\n * matching and the stored view is ignored — a new date pill therefore opens on\n * its own default month with nothing having to clear this.\n */\n private pageDateView(delta: number) {\n const state = this.store.get();\n if (!isCalendarFormat(state.activeFormatType) || !state.dateView) return;\n const next = addMonths(state.dateView, delta);\n this.store.set({\n dateViewMonth: { ...next, key: dateViewKey(state) },\n // The highlight is an index into the month that is going away; carrying\n // it over would land on an unrelated day, or on a padding cell.\n activeDropdownIndex: -1,\n });\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 ~170ms 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 /**\n * The structured query as the SDK understands it right now — the same\n * object `onResult` delivers after each response and `onSubmit` delivers\n * on submit. Read it on demand from a place that has no event handy (a\n * custom submit button, a \"save draft\" action).\n */\n getResult(): AutocompleteResult {\n return this.buildResult();\n }\n\n private buildResult(): AutocompleteResult {\n const state = this.store.get();\n return buildSubmitResult(state.text, state.completedParams, state.skippedParams, {\n identifiedParams: state.identifiedParams,\n isReady: state.isReady,\n customFields: state.customFields,\n });\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 const previousShowProducts = this.opts.showProducts;\n const previousOverrides = this.opts.optionOverrides;\n Object.assign(this.opts, opts);\n if (\"optionOverrides\" in opts && opts.optionOverrides !== previousOverrides) {\n // A swapped record must not be answered by the old one: an ask still in\n // flight is abandoned and the pill on screen is asked again through the\n // new record. Wrappers hand over a stable proxy (React) or forward on\n // reference change (Angular), so this fires for a real swap, not a\n // re-render.\n this.optionSource.refresh();\n }\n if (\n (\"products\" in opts && opts.products !== previousProducts) ||\n (\"showProducts\" in opts && opts.showProducts !== previousShowProducts)\n ) {\n // The strip's source changed. A swapped or added integration clears\n // whatever was on screen (the next fetch repopulates from the new\n // config); a removed one hands the strip back to the server's items, and\n // toggling `showProducts` empties or restores it. `store.set` here also\n // invalidates the derived memo, so isDropdownOpen re-evaluates without a\n // gone strip holding it open.\n this.productsController.resync();\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 and the icons appear/disappear without\n // another mutation.\n opts.showSkipButton !== undefined ||\n opts.showOptionIcons !== undefined ||\n opts.showChipIcons !== 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 // A range is answered in two taps of the same calendar. The first records\n // where the span starts and commits nothing; the second builds the one\n // option that carries the whole span and re-enters here, where every\n // commit path below handles it exactly as it handles a single date. The\n // guard is the cell's own `aiaDate` metadata, which only a calendar cell\n // has — so the synthetic range option falls straight through rather than\n // starting a third tap.\n if (state.activeFormatType === \"date-range\") {\n const iso = cellIso(option);\n if (iso) {\n const pending = state.dateRangeStart;\n if (!pending) {\n this.store.set({ pendingRangeStart: { iso, key: dateViewKey(state) } });\n return;\n }\n this.store.set({ pendingRangeStart: null });\n this.selectOption(buildRangeOption(pending, iso));\n return;\n }\n }\n\n // Picking a date for a span the server identified: swap the user's own\n // words for the canonical form and promote it to a real answer.\n if (state.editingIdentified) {\n this.commitIdentifiedDate(option);\n return;\n }\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 // \"The other options the user passed over\" is a meaningful signal for a\n // list someone read; for a calendar it is the other ~30 days of the\n // month, which says nothing and would dominate the payload.\n other_options: isCalendarFormat(state.activeFormatType) ? [] : 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 press 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 // 170ms press animation 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 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 showOptionIcons() {\n return self.opts.showOptionIcons ?? true;\n },\n get showChipIcons() {\n return self.opts.showChipIcons ?? true;\n },\n get optionsPosition() {\n return self.opts.optionsPosition ?? \"below\";\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 showPreviousMonth: () => this.showPreviousMonth(),\n showNextMonth: () => this.showNextMonth(),\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 get showOptionIcons() {\n return self.opts.showOptionIcons ?? true;\n },\n get optionsPosition() {\n return self.opts.optionsPosition ?? \"below\";\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 showPreviousMonth: () => this.showPreviousMonth(),\n showNextMonth: () => this.showNextMonth(),\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 // A consumer override answers the re-edit too, and its answer for the\n // phrase being typed may still be on its way: judging \"no match\" by the\n // PREVIOUS answer would exit the edit under a search that was about to\n // succeed. Wait for the miss — the override answering empty for this\n // phrase is what makes it the server's turn.\n if (this.optionSource.owns(s.editingParam.suggestionType, s.optionQuery)) 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 /**\n * Replace an identified date span with the day the user picked.\n *\n * The span becomes an ordinary completed param, so from here on it is\n * indistinguishable from a date answered through a pill — same bold\n * rendering, same `{{TYPE_N}}` token in `raw_query`, same `completed_params`\n * entry. The identified param it came from is dropped: its text no longer\n * exists in the input, and leaving it would have the reconciler drop it a\n * beat later anyway.\n */\n private commitIdentifiedDate(option: SuggestionOption) {\n const state = this.store.get();\n const editing = state.editingIdentified;\n if (!editing) return;\n // The offsets were captured when the picker opened. If anything moved the\n // text since, they bound something else now — and splicing at them would\n // rewrite a part of the query the user never touched. Every known path\n // clears this state, so reaching here means a new one appeared; abandon the\n // pick rather than corrupt the input.\n if (state.text.slice(editing.anchor, editing.tail) !== editing.text) {\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n return;\n }\n\n const before = state.text.slice(0, editing.anchor);\n const after = state.text.slice(editing.tail);\n // Same capitalisation and trailing-space conventions the re-edit path uses,\n // so a date committed this way is spaced like every other answer.\n const optionText =\n editing.anchor === 0 && option.text.length > 0\n ? option.text[0].toUpperCase() + option.text.slice(1)\n : option.text;\n const needsTrailingSpace = after.length === 0 || after[0] !== \" \";\n const replacement = needsTrailingSpace ? `${optionText} ` : optionText;\n const newText = before + replacement + after;\n // Caret goes to the END of the input — same rule as the two re-edit\n // commit paths (ReEditManager.selectOption / promoteEdit): fetchNow below\n // asks for the next suggestion, and the dropdown only opens with the\n // caret at the end of the text, so a date answered mid-text must not\n // strand the caret after the replacement.\n const caretPos = newText.length;\n\n const newParam: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: editing.type,\n text: optionText,\n kind: option.kind,\n suggestionType: editing.type,\n suggestionPlaceholder: identifiedParamLabel(editing.type),\n // No cached options to inherit — a calendar is what answers this param,\n // and re-editing it builds a fresh month rather than filtering a list.\n options: [],\n metadata: option.metadata,\n icon: option.icon,\n icon_svg: option.icon_svg,\n };\n\n this.fireTelemetry(\"option\", {\n raw_query: buildQuery(state.text, state.completedParams).rawQuery,\n selected_option: option.text,\n // A calendar's \"other options\" are the rest of the month, which says\n // nothing — same reasoning as the pill-driven date path.\n other_options: [],\n });\n\n this.store.set({\n text: newText,\n completedParams: [...state.completedParams, newParam],\n identifiedParams: state.identifiedParams.filter((p) => p.id !== editing.id),\n newParamId: newParam.id,\n filterBase: caretPos,\n caretOffset: caretPos,\n editingIdentified: null,\n activeDropdownIndex: -1,\n pillTapped: false,\n skipNextFetch: true,\n inSelectionAnimation: true,\n });\n this.startSelectionAnimationTimer();\n this.scheduleSetCursor(caretPos);\n // Answering a parameter goes back to the server on the same terms as any\n // other answer, so what it suggests next accounts for the date just given.\n this.fetchNow();\n }\n\n private maybePromoteExactMatch(newValue: string) {\n const s = this.store.get();\n // A calendar pill's options are on the wire but are never shown — the\n // calendar replaced them, for a span exactly as for a single day. Promoting\n // one because the typed text happens to equal it would answer the pill with\n // something the user never saw.\n if (isCalendarFormat(s.activeFormatType)) return;\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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","DATE_RANGE_META_END","DATE_RANGE_META_START","ModeController","OPTIONS_GRID_MOBILE_QUERY","OPTION_ENTER_DELAY_VAR","OPTION_ENTER_FADE_MS","OPTION_ENTER_RISE_MS","OPTION_ENTER_RISE_PX","OPTION_ENTER_STAGGER_MS","PLACEHOLDER_FADE_OUT_MS","PLACEHOLDER_LEAVING_ATTR","PLACEHOLDER_SWAP_GAP_MS","PLACEHOLDER_TYPE_MS","PLACEHOLDER_WORD_PAUSE_MS","PRODUCT_PATH_PREFIX","RANGE_SEPARATOR","SCROLL_ARROW_ATTR","SCROLL_ARROW_BOTTOM_VAR","SCROLL_ARROW_CLASS","SCROLL_ARROW_FADE_MS","SCROLL_ARROW_LABEL","SCROLL_ARROW_SCROLL_IDLE_MS","SCROLL_ARROW_VISIBLE_ATTR","SKIPPED_PARAM_TEXT","WEEKDAY_LABELS","addMonths","attachScrollArrow","buildAttributionUrl","buildDateOptions","buildQuery","buildSubmitResult","cellDay","cellIso","computeOptionsGridLayout","createStore","cursorIsAtEnd","dateCellMarks","dateSelectionFor","extractPlainText","formatAbsoluteDate","formatDate","formatDateRange","getCursorOffset","getFooterHint","identifiedParamLabel","isCalendarFormat","isOptionsGridMobileViewport","isoDate","measureOptionsGrid","monthLabel","needsOptionsGridMeasurement","optionEnterDelayMs","optionLabel","optionsEntranceDurationMs","optionsGridTemplateColumns","parseDate","parseLooseDate","parseLooseDateRange","plainTextLength","planOptionsGrid","previousGraphemeBoundary","productFromMatchedItem","productsFromCustomFields","renderEditableContent","resolveFormatType","resolveIdentifiedDate","sanitizeOptionIconSvg","scrollCaretIntoView","selectedIsoFromText","selectedRangeFor","setCursorOffset","toWireIdentifiedParams","visibleDateRange","withSkippedParams","__toCommonJS","tokenizeParamType","type","token","identifiedParamLabel","words","MONTH_NAMES","WEEKDAY_LABELS","WEEKDAY_NAMES","DATE_META_ISO","DATE_META_DAY","pad","isoDate","d","startOfDay","daysFromToday","ms","formatDate","diff","formatAbsoluteDate","parseDate","text","weekday","name","now","delta","m","month","day","year","monthLabel","view","todayView","viewOf","addMonths","blankCell","buildDateOptions","view","lead","daysInMonth","cells","i","day","date","formatDate","DATE_META_ISO","isoDate","DATE_META_DAY","WEEKDAY_LABELS","selectedIsoFromText","text","d","parseDate","cellIso","option","iso","cellDay","dropOrdinals","text","monthIndex","word","w","MONTH_NAMES","m","withResolvedYear","month","day","today","thisYear","startOfToday","nextYear","exactDate","year","d","parseLooseDate","opts","t","iso","isoDate","monthFirst","fromParts","dayFirst","numeric","a","b","yearText","resolveIdentifiedDate","param","fromServer","RANGE_SEPARATOR","DATE_RANGE_META_START","DATE_RANGE_META_END","orderedRange","a","b","ISO_RE","dateFromIso","iso","y","m","d","date","WORD_SEPARATOR","DASH_SEPARATOR","splitPoints","text","out","re","match","parseLooseDateRange","opts","found","range","left","right","start","parseLooseDate","end","formatDateRange","formatAbsoluteDate","buildRangeOption","selectedRangeFor","param","isCalendarFormat","format","DATE_TOKENS","RANGE_TOKENS","DATE_OPTION_THRESHOLD","countDateOptions","options","counts","option","parseLooseDateRange","parseLooseDate","resolveFormatType","source","fromServer","tokens","tokenizeParamType","token","TokenManager","config","forceRefresh","result","DEFAULT_API_ORIGIN","DEFAULT_SUGGEST_ENDPOINT","tokenManagers","isAccessTokenConfig","config","getApiKeyConfig","getTokenManager","manager","TokenManager","buildHeaders","apiConfig","buildApiKeyAuthHeader","apiKeyConfig","apiKey","toWireIdentifiedParams","params","p","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","toWireIdentifiedParams","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","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","isCalendarFormat","resolveFormatType","query","extractFilterQuery","match","findExactMatch","customFields","products","completedNow","identifiedParams","reconcileIdentifiedParams","carriedSkips","p","racedSkipTypes","sg","caughtError","minDiff","anyChange","consumer","placeholderText","effBase","effectiveFilterBase","currentQuery","isDate","tappableFiltered","filterOptions","o","hasExactMatch","isInFilterZone","isTypingPlaceholderPrefix","updatedParams","buildQuery","isDeleting","charDiff","changed","isWidgetRoot","node","rootOf","root","documentOf","getSelectionFor","selection","getElementByIdFor","id","CARET_SCROLL_MARGIN","WIDGET_CONTAINER_CLASS","isScrollableOverflow","value","findScrollContainer","root","doc","documentOf","win","el","overflowsX","overflowsY","style","scrollCaretIntoView","sel","getSelectionFor","range","container","caret","box","viewLeft","viewRight","viewTop","viewBottom","NON_EDITABLE_SELECTOR","CHIP_SELECTOR","segmenter","getGraphemeSegmenter","Segmenter","isInsideNonEditable","node","root","n","createTextWalker","documentOf","extractPlainText","walker","out","plainTextLength","total","getCursorOffset","sel","getSelectionFor","anchorNode","anchorOffset","el","offset","i","plainTextLengthOfSubtree","offsetBeforeNode","child","target","setCursorOffset","doc","clamped","cumulative","targetOffset","lastNode","len","next","range","strongParent","recordChipBoundary","scrollCaretIntoView","chipContaining","chip","reported","caretEscapedChip","chipId","record","cursorIsAtEnd","previousGraphemeBoundary","text","seg","slice","last","index","buildSubmitResult","text","completedParams","skippedParams","extras","rawQuery","finalParams","buildQuery","withSkippedParams","toWireIdentifiedParams","isCursorAtEnd","target","state","cursorIsAtEnd","getEditableCaretOffset","getCursorOffset","KeyboardController","store","ctx","listboxId","getOnSubmit","from","columns","onSubmit","tappableIndices","above","cursorAtEnd","inEditMode","entry","isCalendarFormat","lastRow","currentPos","nextPos","initialIdx","prevPos","rightNeighbor","editor","tail","setCursorOffset","leftNeighbor","caret","anchor","offset","buildSubmitResult","tappableOptionIndices","o","i","firstIdx","delta","bottomRowStart","fromBottom","today","isoDate","todayIdx","cellIso","days","next","tappable","buckets","listbox","getElementByIdFor","el","gtc","tracks","index","options","optionEl","OptionSourceController","store","getOverrides","callbacks","next","type","query","state","minDiff","target","current","delta","shorter","isCalendarFormat","editing","active","override","version","abort","raw","err","pending","signal","answer","isAbortError","live","search","answered","s","sg","status","prev","PillsController","store","callbacks","index","state","actionable","s","moved","rest","_","i","placeholders","rawQuery","buildQuery","nextSuggestions","firstTappable","o","PRODUCT_PATH_PREFIX","productFromMatchedItem","item","productsFromCustomFields","report","ProductsController","store","getConfig","isEnabled","report","productsFromCustomFields","query","signal","isCurrent","config","stale","raw","mapped","list","products","err","isAbortError","OPTIONS_GRID_MOBILE_QUERY","ROW_HEIGHT","RESERVED_BAND","computeOptionsGridLayout","count","isMobile","layout","cols","rows","needsOptionsGridMeasurement","planOptionsGrid","rowWidths","gridWidth","fitsTwoColumns","base","optionsGridTemplateColumns","visible","w","measureOptionsGrid","grid","items","probe","item","clone","c","cs","isOptionsGridMobileViewport","OPTIONS_GRID_MOBILE_QUERY","computeDropdownVisibility","inputs","opts","trigger","closeOnBlur","hasContent","focusGate","trimmedEnd","caretAtEnd","dateViewKey","inputs","type","anchor","defaultViewForIso","iso","todayView","y","m","defaultDateView","editingText","existing","parseDate","viewOf","deriveAll","opts","segments","deriveSegments","actionableSuggestions","s","activeSuggestion","placeholderText","clampedFilterBase","effectiveFilterBase","filterQuery","isTypingPlaceholderPrefix","extractFilterQuery","baseOptions","inEditMode","filteredOptions","optionQuery","sourceOptions","sourceType","editingId","paramStillPresent","p","editCaret","editQuery","filterOptions","search","searchMatchesPill","isSearchingOptions","hideNonTappable","o","identifiedEdit","formatSource","activeFormatType","resolveFormatType","editingRange","isCalendarFormat","selectedRangeFor","dateView","dateRangeStart","key","paged","buildDateOptions","pending","countsAsOption","activePillHasNoOptions","editOptions","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","afterParam","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","caretPos","newParam","oldIdx","params","insertAt","result","tryPromoteExactMatch","PLACEHOLDER_TYPE_MS","PLACEHOLDER_WORD_PAUSE_MS","PLACEHOLDER_FADE_OUT_MS","PLACEHOLDER_SWAP_GAP_MS","PLACEHOLDER_LEAVING_ATTR","states","clearTimer","state","motionDisabled","input","show","count","typeNext","justTyped","pause","startTyping","syncPlaceholder","text","next","outgoing","cancelPlaceholderTypewriter","ATTRIBUTION_URL","buildAttributionUrl","base","host","url","getFooterHint","optionHighlighted","isInputEmpty","NOTHING_SELECTED","dateSelectionFor","state","isCalendarFormat","identified","iso","isoEnd","orderedRange","selectedRange","selectedRangeFor","selectedIsoFromText","visibleDateRange","args","dateCellMarks","selectedIso","range","selected","inRange","KEY_ATTR","reconcileList","parent","items","opts","existing","child","key","used","result","i","item","el","renderDateGrid","parent","options","activeIndex","listboxId","view","selection","callbacks","picker","buildPicker","label","monthLabel","prev","next","grid","renderCells","today","isoDate","range","visibleDateRange","cellIso","reconcileList","option","i","buildCell","el","iso","marks","dateCellMarks","isHighlighted","idx","cell","day","cellDay","num","header","month","buildNavButton","weekdays","WEEKDAY_LABELS","direction","glyph","btn","e","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","OPTION_ENTER_STAGGER_MS","OPTION_ENTER_FADE_MS","OPTION_ENTER_RISE_MS","OPTION_ENTER_RISE_PX","OPTION_ENTER_DELAY_VAR","optionEnterDelayMs","index","cols","count","optionsPosition","safeCols","row","rows","optionsEntranceDurationMs","SVG_NS","ALLOWED_ELEMENTS","isDisallowedAttribute","name","value","lower","HARDENED_ATTRIBUTES","PLAIN_PAINT","hardenPaint","el","declarations","scrub","attr","child","childEl","sanitizeOptionIconSvg","markup","doc","root","optionLabel","option","applyGridLayout","grid","count","loading","isMobile","isOptionsGridMobileViewport","measured","needsOptionsGridMeasurement","measureOptionsGrid","plan","planOptionsGrid","el","i","renderSuggestionGrid","parent","options","activeIndex","onSelect","onHighlight","listboxId","groupKey","optionsPosition","showIcons","isNewGroup","renderOptions","cols","applyEntranceDelays","loadingFlag","iconKey","opt","reconcileList","option","_i","buildOptionElement","isHighlighted","idx","OPTION_ENTER_DELAY_VAR","optionEnterDelayMs","item","classes","content","iconHtml","sanitizeOptionIconSvg","icon","text","optionLabel","tag","SCROLL_ARROW_CLASS","SCROLL_ARROW_ATTR","SCROLL_ARROW_VISIBLE_ATTR","SCROLL_ARROW_FADE_MS","SCROLL_ARROW_SCROLL_IDLE_MS","SCROLL_ARROW_BOTTOM_VAR","SCROLL_ARROW_LABEL","AT_BOTTOM_SLACK_PX","ARROW_GAP_PX","PAGE_SCROLL_MS","animateScrollBy","grid","delta","view","onDone","from","raf","hidden","motionDisabled","start","now","step","t","eased","buildScrollArrowIcon","doc","NS","svg","path","createScrollArrowButton","button","columnCount","tracks","el","attachScrollArrow","dropdown","destroyed","lastGroup","settleTimer","idleTimer","setVisible","visible","position","gridTop","band","bottom","evaluate","loading","remaining","update","group","count","wait","optionsEntranceDurationMs","onScroll","onMouseDown","e","onClick","pad","page","resizeObserver","FALLBACK_SKELETON_BAR_WIDTHS","createDropdown","listboxId","dropdown","e","scrollArrows","syncScrollArrow","grid","state","createScrollArrowButton","attachScrollArrow","destroyDropdown","renderDropdown","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","isDate","isCalendarFormat","showsDateGrid","renderSuggestionGrid","renderDateGrid","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","dateSelection","dateSelectionFor","renderDropdown","identifiedParamLabel","i","focused","CHIP_PADDING_X","MAX_TRACKING","chipTracking","textLength","renderEditableContent","args","input","segments","newParamId","editingParamId","placeholderText","isFocused","showChipIcons","empty","syncPlaceholder","segKey","s","lastSegKey","lastNewParamId","lastEditingParamId","savedOffset","getCursorOffset","doc","documentOf","frag","newLength","seg","strong","isNew","isEditing","classes","iconHtml","sanitizeOptionIconSvg","icon","identifiedClasses","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","getSelectionFor","anchor","paramId","caretEscapedChip","e","getCursorOffset","documentOf","enclosing","editState","editingId","inputEvent","t","replacement","text","doc","range","node","state","buildSubmitResult","caretInside","ro","updateDOM","refs","pillPlacement","setActivePill","selectOption","store","activeDescendant","canSubmit","previousParamId","justSelected","renderEditableContent","inlineLoading","renderPills","clearPills","setCursorOffset","dropdownPill","identifiedParamLabel","dropdownActivePill","dateSelection","dateSelectionFor","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","styledRoots","sheets","isDocument","root","documentOfRoot","constructableSheet","doc","cached","Ctor","sheet","STYLES","appendStyleElement","host","style","injectStyles","target","adopted","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","OptionSourceController","mode","query","FetchController","signal","isCurrent","active","matched","o","report","state","minDiff","KeyboardController","option","next","prev","valid","invalid","reconcileIdentifiedParams","editing","p","pos","reissued","seg","pending","dateViewKey","span","anchor","rebased","rebaseAnchor","isTrailingCovered","injectStyles","rootOf","wasFocused","unsub","cancelPlaceholderTypewriter","destroyDropdown","text","params","index","endOffset","start","paramStart","paramEnd","newText","removed","removeChipSpan","s","deleteStart","previousGraphemeBoundary","refs","setCursorOffset","setCursor","paramId","param","named","resolveFormatType","isCalendarFormat","range","parseLooseDateRange","format","tail","resolveIdentifiedDate","replacement","product","value","delta","addMonths","placeholders","actionable","skipped","remaining","alreadySkipped","focused","listener","key","buildSubmitResult","event","callback","previousProducts","previousShowProducts","previousOverrides","iso","cellIso","buildRangeOption","computeSelectionPatch","buildQuery","consumed","sg","queryData","source","sendTelemetry","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","before","after","optionText","caretPos","newParam","identifiedParamLabel","tryPromoteExactMatch"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/utils/paramType.ts","../src/date/dateGrid.ts","../src/date/looseDate.ts","../src/date/dateRange.ts","../src/date/formatType.ts","../src/utils/tokenManager.ts","../src/utils/auth.ts","../src/utils/identifiedParams.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/roots.ts","../src/dom/scrollCaretIntoView.ts","../src/dom/cursorUtils.ts","../src/utils/submitResult.ts","../src/controllers/keyboardController.ts","../src/controllers/optionSourceController.ts","../src/controllers/pillsController.ts","../src/utils/matchedItems.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/render/placeholderTypewriter.ts","../src/utils/attribution.ts","../src/utils/footerHint.ts","../src/date/dateSelection.ts","../src/render/reconcileList.ts","../src/render/renderDateGrid.ts","../src/render/renderPills.ts","../src/render/renderProductStrip.ts","../src/derive/optionsEntrance.ts","../src/render/optionIcon.ts","../src/render/renderSuggestionGrid.ts","../src/render/scrollArrow.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// === Datepicker (shared by the React + Angular dropdowns) ===\n// The calendar's math lives here so all three packages read one answer for\n// which day sits in which cell — only the markup is duplicated per package.\nexport type { DateMonthView } from \"./date/dateGrid\";\nexport {\n addMonths,\n buildDateOptions,\n cellDay,\n cellIso,\n formatAbsoluteDate,\n formatDate,\n isoDate,\n monthLabel,\n parseDate,\n selectedIsoFromText,\n WEEKDAY_LABELS,\n} from \"./date/dateGrid\";\nexport type { DateRange } from \"./date/dateRange\";\nexport {\n DATE_RANGE_META_END,\n DATE_RANGE_META_START,\n formatDateRange,\n parseLooseDateRange,\n RANGE_SEPARATOR,\n selectedRangeFor,\n} from \"./date/dateRange\";\nexport type { DateSelection } from \"./date/dateSelection\";\nexport { dateCellMarks, dateSelectionFor, visibleDateRange } from \"./date/dateSelection\";\nexport type { FormatType } from \"./date/formatType\";\nexport { isCalendarFormat, resolveFormatType } from \"./date/formatType\";\nexport type { LooseDateOptions } from \"./date/looseDate\";\nexport { parseLooseDate, resolveIdentifiedDate } from \"./date/looseDate\";\n// === Option-row entrance choreography (shared by the React + Angular dropdowns) ===\nexport {\n OPTION_ENTER_DELAY_VAR,\n OPTION_ENTER_FADE_MS,\n OPTION_ENTER_RISE_MS,\n OPTION_ENTER_RISE_PX,\n OPTION_ENTER_STAGGER_MS,\n optionEnterDelayMs,\n optionsEntranceDurationMs,\n} from \"./derive/optionsEntrance\";\n// === Options-grid layout policy (shared by the React + Angular dropdowns) ===\nexport type { OptionsGridLayout, OptionsGridPlan } from \"./derive/optionsGridLayout\";\nexport {\n computeOptionsGridLayout,\n isOptionsGridMobileViewport,\n measureOptionsGrid,\n needsOptionsGridMeasurement,\n OPTIONS_GRID_MOBILE_QUERY,\n optionsGridTemplateColumns,\n planOptionsGrid,\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 { scrollCaretIntoView } from \"./dom/scrollCaretIntoView\";\nexport { optionLabel, sanitizeOptionIconSvg } from \"./render/optionIcon\";\nexport {\n PLACEHOLDER_FADE_OUT_MS,\n PLACEHOLDER_LEAVING_ATTR,\n PLACEHOLDER_SWAP_GAP_MS,\n PLACEHOLDER_TYPE_MS,\n PLACEHOLDER_WORD_PAUSE_MS,\n} from \"./render/placeholderTypewriter\";\nexport { renderEditableContent } from \"./render/renderEditable\";\n// === \"More below\" scroll arrow for the options grid (shared by the React + Angular dropdowns) ===\nexport type { ScrollArrowArgs, ScrollArrowController } from \"./render/scrollArrow\";\nexport {\n attachScrollArrow,\n SCROLL_ARROW_ATTR,\n SCROLL_ARROW_BOTTOM_VAR,\n SCROLL_ARROW_CLASS,\n SCROLL_ARROW_FADE_MS,\n SCROLL_ARROW_LABEL,\n SCROLL_ARROW_SCROLL_IDLE_MS,\n SCROLL_ARROW_VISIBLE_ATTR,\n} from \"./render/scrollArrow\";\nexport type {\n AccessTokenConfig,\n AccessTokenResult,\n APIConfig,\n APIKeyConfig,\n AppearanceMode,\n AutocompleteRequest,\n AutocompleteResponse,\n AutocompleteResult,\n CompletedParam,\n CompletedParamState,\n CustomFieldsReport,\n ExpressedFilter,\n FilterOp,\n IdentifiedParam,\n IdentifiedParamState,\n InputItem,\n MatchedItem,\n MatchedItems,\n OptionOverride,\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 { toWireIdentifiedParams } from \"./utils/identifiedParams\";\n// === Product strip helpers (for a custom strip fed from `customFields`) ===\nexport {\n PRODUCT_PATH_PREFIX,\n productFromMatchedItem,\n productsFromCustomFields,\n} from \"./utils/matchedItems\";\nexport { ModeController } from \"./utils/modeController\";\nexport { identifiedParamLabel } from \"./utils/paramType\";\nexport { SKIPPED_PARAM_TEXT, withSkippedParams } from \"./utils/skippedParams\";\nexport { buildSubmitResult, type SubmitResultExtras } from \"./utils/submitResult\";\n","/**\n * Reading a server parameter `type` — `end_date`, `travelDate`, `due date` — as\n * the words it is made of.\n *\n * Two callers, one reading: the date-format check asks whether one of those\n * words means \"date\", and the dropdown pill bar shows them to the user when it\n * has nothing better to label a parameter with.\n */\n\n/**\n * Split a suggestion type into lower-cased words.\n *\n * Deliberately not a `\\b`-anchored regex. `\\b` counts `_` as a word character,\n * so `/\\bdate\\b/` silently fails on `end_date` — the single most likely way for\n * a server to name one of these — and on `endDate` too. Both were missed\n * because the first tests only covered space-separated names.\n *\n * Handles `end_date`, `due-date`, `due date`, `endDate` and `DATE` alike, while\n * still refusing `update`, `candidate`, `validate` and `duedate`, where \"date\"\n * is a fragment of a word rather than a word.\n *\n * Two known limits, both accepted rather than patched, because neither is\n * separable without a word list and the format check that reads this is\n * temporary:\n * - `duedate` and `date1` are missed — no separator, no case change.\n * - An adjectival phrase that happens to end in the word, `up-to-date` or\n * `out-of-date`, matches. Parameter types name a value to fill in, so an\n * adjective is an unlikely name for one.\n * Both directions are pinned by tests, so the behaviour is known rather than\n * accidental. A misfire degrades gracefully: the options render instead of a\n * calendar, and the user can still answer.\n */\nexport function tokenizeParamType(type: string): string[] {\n return type\n .replace(/([a-z0-9])([A-Z])/g, \"$1 $2\")\n .split(/[^a-zA-Z0-9]+/)\n .filter(Boolean)\n .map((token) => token.toLowerCase());\n}\n\n/**\n * The dropdown pill-bar label for a parameter that has no suggestion behind it.\n *\n * Every pill normally shows its suggestion's `text` — a phrase written to be\n * read, \"Due date\". A date the server identified in the user's own words\n * (\"september 5th\") has no suggestion to borrow one from, only the wire `type`,\n * so the type is read back into words instead: `travel_date` and `travelDate`\n * both become \"travel date\". Without this the raw type is the label, both while\n * the calendar is open over the span and again if that parameter is re-edited\n * after being answered.\n *\n * Lower-casing costs nothing here — every package's pill CSS sets\n * `text-transform: uppercase`, so a label's original case never reaches the\n * screen either way.\n */\nexport function identifiedParamLabel(type: string): string {\n const words = tokenizeParamType(type);\n // `tokenizeParamType` keeps ASCII alphanumerics only, so a type written in\n // another script leaves nothing behind — the raw type beats a blank label.\n return words.length > 0 ? words.join(\" \") : type;\n}\n","import type { SuggestionOption } from \"../shared-types\";\n\n/** A calendar month on screen. `month` is 0-based, matching `Date#getMonth`. */\nexport interface DateMonthView {\n year: number;\n month: number;\n}\n\n/**\n * Month names are fixed English, NOT `Intl`-localized.\n *\n * The committed text goes into `raw_query` and `completed_params` — the server\n * has to read one vocabulary regardless of the user's locale, so a French\n * user's date must still commit as \"March 23\" / \"March 23 2027\". Only the\n * calendar's own chrome (see {@link monthLabel}) is a display string, and it\n * reuses these for the same reason the cells do: one vocabulary, no drift\n * between the label the user reads and the value they commit.\n */\nexport const MONTH_NAMES = [\n \"January\",\n \"February\",\n \"March\",\n \"April\",\n \"May\",\n \"June\",\n \"July\",\n \"August\",\n \"September\",\n \"October\",\n \"November\",\n \"December\",\n];\n\n/** Sunday-first, matching `Date#getDay`. */\nexport const WEEKDAY_LABELS = [\"S\", \"M\", \"T\", \"W\", \"T\", \"F\", \"S\"];\n\n/** Full weekday names, Sunday-first — fixed English like {@link MONTH_NAMES}. */\nexport const WEEKDAY_NAMES = [\n \"Sunday\",\n \"Monday\",\n \"Tuesday\",\n \"Wednesday\",\n \"Thursday\",\n \"Friday\",\n \"Saturday\",\n];\n\n/** Metadata key holding a cell's `YYYY-MM-DD`. Absent on padding cells. */\nexport const DATE_META_ISO = \"aiaDate\";\n/** Metadata key holding a cell's day-of-month, which is what the cell renders. */\nexport const DATE_META_DAY = \"aiaDay\";\n\nconst pad = (n: number): string => String(n).padStart(2, \"0\");\n\n/**\n * `YYYY-MM-DD` from the date's LOCAL components.\n *\n * Not `toISOString()`, which converts to UTC first: for any user behind UTC\n * that shifts the calendar date back a day, so clicking \"March 23\" west of\n * Greenwich would produce `2026-03-22`.\n */\nexport function isoDate(d: Date): string {\n return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;\n}\n\n/** Midnight of the date's own day, so day arithmetic ignores the time. */\nfunction startOfDay(d: Date): Date {\n return new Date(d.getFullYear(), d.getMonth(), d.getDate());\n}\n\n/**\n * Whole days between today and `d` (negative for the past). Rounded, not\n * truncated: a DST change makes the raw difference an hour short or long of a\n * whole day, and truncation would then miscount it.\n */\nfunction daysFromToday(d: Date): number {\n const ms = startOfDay(d).getTime() - startOfDay(new Date()).getTime();\n return Math.round(ms / 86_400_000);\n}\n\n/**\n * The committed form of a date — written the way a person would, since the\n * text lands verbatim in the user's own sentence:\n *\n * - today through six days out: the weekday alone, \"Tuesday\". The window\n * stops before day seven so every weekday name still means exactly one\n * date when read back.\n * - further out, same year: \"March 23\" — the year restates what \"this year\"\n * already says.\n * - a different year: \"March 23 2027\".\n */\nexport function formatDate(d: Date): string {\n const diff = daysFromToday(d);\n if (diff >= 0 && diff < 7) return WEEKDAY_NAMES[d.getDay()];\n return formatAbsoluteDate(d);\n}\n\n/**\n * A date written as month and day, with the year only when it isn't this one —\n * {@link formatDate} without the weekday shorthand.\n *\n * The shorthand is what a range must not use. \"Tuesday - Friday\" names four\n * possible spans depending on when it is read, and the two ends of a range are\n * read against each other rather than against today, so the one thing the\n * weekday form is good at — naming a day inside this week unambiguously —\n * stops being true the moment it is half of a span. See `formatDateRange`.\n */\nexport function formatAbsoluteDate(d: Date): string {\n if (d.getFullYear() === new Date().getFullYear()) {\n return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()}`;\n }\n return `${MONTH_NAMES[d.getMonth()]} ${d.getDate()} ${d.getFullYear()}`;\n}\n\n/**\n * Inverse of {@link formatDate}, for re-opening the calendar on the month a\n * completed date param already holds. Reads all three committed shapes — a\n * weekday name resolves to its one date in the next seven days, and a\n * yearless month-day to the current year, mirroring how they were written.\n * Returns null for anything else — the caller falls back to the current\n * month.\n *\n * Deliberately RELATIVE, like the sentence it reads: the committed text is\n * the single source of truth, and \"Schedule a campaign Tuesday\" means the\n * next Tuesday to everyone who reads it — the user re-reading their own\n * sentence, this parser, and the server parsing the submitted query alike.\n * So once the originally-picked Tuesday has gone by, re-editing marks the\n * date the sentence NOW names, a week later — not the stale pick. Anchoring\n * to the pick would make the calendar disagree with the sentence above it\n * (and with what the server will act on), which is the worse wrong. Pinned\n * by the clock-advance test in datepicker.test.ts.\n */\nexport function parseDate(text: string | undefined | null): Date | null {\n if (!text) return null;\n const weekday = WEEKDAY_NAMES.findIndex(\n (name) => name.toLowerCase() === text.trim().toLowerCase(),\n );\n if (weekday >= 0) {\n const now = startOfDay(new Date());\n const delta = (weekday - now.getDay() + 7) % 7;\n return new Date(now.getFullYear(), now.getMonth(), now.getDate() + delta);\n }\n const m = /^\\s*([A-Za-z]+)\\s+(\\d{1,2})(?:\\s+(\\d{4}))?\\s*$/.exec(text);\n if (!m) return null;\n const month = MONTH_NAMES.findIndex((name) => name.toLowerCase() === m[1].toLowerCase());\n if (month < 0) return null;\n const day = Number(m[2]);\n const year = m[3] ? Number(m[3]) : new Date().getFullYear();\n const d = new Date(year, month, day);\n // Rejects overflow like \"February 31 2026\", which `new Date` rolls forward\n // into March rather than failing.\n if (d.getMonth() !== month || d.getDate() !== day) return null;\n return d;\n}\n\n/** \"March 2026\" — the calendar header. */\nexport function monthLabel(view: DateMonthView): string {\n return `${MONTH_NAMES[view.month]} ${view.year}`;\n}\n\nexport function todayView(): DateMonthView {\n const now = new Date();\n return { year: now.getFullYear(), month: now.getMonth() };\n}\n\nexport function viewOf(d: Date): DateMonthView {\n return { year: d.getFullYear(), month: d.getMonth() };\n}\n\n/** Step the view by whole months, rolling the year over at either end. */\nexport function addMonths(view: DateMonthView, delta: number): DateMonthView {\n const d = new Date(view.year, view.month + delta, 1);\n return { year: d.getFullYear(), month: d.getMonth() };\n}\n\nexport function sameView(a: DateMonthView | null, b: DateMonthView | null): boolean {\n if (!a || !b) return a === b;\n return a.year === b.year && a.month === b.month;\n}\n\nfunction blankCell(): SuggestionOption {\n return { text: \"\", is_tappable: false, kind: null };\n}\n\n/**\n * A month laid out as `SuggestionOption`s — leading blanks, one cell per day,\n * trailing blanks to complete the last week. Always a whole number of 7-cell\n * rows.\n *\n * Modelling calendar cells as options is what makes the datepicker cheap: they\n * flow through `filteredOptions`, so the existing keyboard controller, the\n * `${listboxId}-option-${i}` id scheme, `aria-activedescendant` and — above all\n * — `selectOption`'s entire commit path work on them unchanged. At 7 columns\n * the grid's own column-major traversal already *is* calendar navigation\n * (→ = +1 day, ↓ = +7 days).\n *\n * Each day cell's `text` is the committed value (\"March 23\", or \"Tuesday\"\n * inside the next week — see {@link formatDate}), NOT its label:\n * `computeSelectionPatch` commits `option.text` verbatim, so the value has to\n * live there. The renderer reads `metadata.aiaDay` for the \"23\" it paints. Padding cells are non-tappable, which is what stops arrow navigation\n * from landing on them.\n *\n * Days before today stay tappable — the calendar de-emphasizes the past, it\n * does not forbid it. The renderers compare each cell's ISO date against\n * today's and paint past days dimmed; picking one still commits normally.\n */\nexport function buildDateOptions(view: DateMonthView): SuggestionOption[] {\n const lead = new Date(view.year, view.month, 1).getDay();\n const daysInMonth = new Date(view.year, view.month + 1, 0).getDate();\n\n const cells: SuggestionOption[] = [];\n for (let i = 0; i < lead; i++) cells.push(blankCell());\n for (let day = 1; day <= daysInMonth; day++) {\n const date = new Date(view.year, view.month, day);\n cells.push({\n text: formatDate(date),\n is_tappable: true,\n kind: null,\n metadata: { [DATE_META_ISO]: isoDate(date), [DATE_META_DAY]: day },\n });\n }\n while (cells.length % WEEKDAY_LABELS.length !== 0) cells.push(blankCell());\n return cells;\n}\n\n/**\n * The `YYYY-MM-DD` a completed date param already holds, so the calendar can\n * mark that cell as the current answer while it is being re-edited. Null for\n * text that isn't one of our dates.\n */\nexport function selectedIsoFromText(text: string | undefined | null): string | null {\n const d = parseDate(text);\n return d ? isoDate(d) : null;\n}\n\n/** A cell's `YYYY-MM-DD`, or null if it's padding. */\nexport function cellIso(option: SuggestionOption | undefined): string | null {\n const iso = option?.metadata?.[DATE_META_ISO];\n return typeof iso === \"string\" ? iso : null;\n}\n\n/** A cell's day-of-month, or null if it's padding. */\nexport function cellDay(option: SuggestionOption | undefined): number | null {\n const day = option?.metadata?.[DATE_META_DAY];\n return typeof day === \"number\" ? day : null;\n}\n","import { isoDate, MONTH_NAMES } from \"./dateGrid\";\n\n/**\n * Best-effort parse of a date the user typed in their own words.\n *\n * The server identifies a span of the query as a date and returns the literal\n * text the user wrote — not a normalized value — so \"september 5th\", \"5 Sep\"\n * and \"09/05\" all arrive as-is. This turns what it can into a `YYYY-MM-DD` so\n * the calendar can open on the right month with that day already marked.\n *\n * **Returning null is a normal outcome, not a failure.** It only costs the\n * pre-selection: the calendar still opens, on the current month with nothing\n * marked, and whatever the user picks replaces the span with the SDK's own\n * canonical text either way. So the committed answer is correct whether or not\n * the original text could be read.\n *\n * Deliberately conservative — it answers null rather than guess, because a\n * wrong date sitting pre-selected is worse than no pre-selection. A user who\n * doesn't notice it commits the wrong answer; an empty calendar just asks them\n * to pick.\n */\nexport interface LooseDateOptions {\n /** Reference point for resolving a date written without a year. Defaults to now. */\n today?: Date;\n}\n\n/** Strips the ordinal suffix off \"5th\" / \"1st\" so the day can be read as a number. */\nfunction dropOrdinals(text: string): string {\n return text.replace(/(\\d+)(?:st|nd|rd|th)\\b/g, \"$1\");\n}\n\n/** Index of a month named in full or abbreviated (\"sep\", \"sept.\", \"September\"). */\nfunction monthIndex(word: string): number {\n const w = word.toLowerCase().replace(/\\.$/, \"\");\n // Three letters is the shortest unambiguous abbreviation (\"mar\" is March and\n // never May; \"jun\"/\"jul\" separate cleanly). Two would make \"ma\" and \"ju\"\n // coin-flips, so they are refused rather than guessed.\n if (w.length < 3) return -1;\n // MONTH_NAMES is capitalised for display, so both sides are lowered here —\n // comparing against the raw names silently matched nothing at all.\n return MONTH_NAMES.findIndex((m) => m.toLowerCase().startsWith(w));\n}\n\n/**\n * Resolve a month/day with no year to the NEXT occurrence.\n *\n * Someone writing \"september 5th\" in October means next September, not one\n * eleven months gone. Anchoring to the year in progress would open the calendar\n * on a month the user can't have meant.\n *\n * Only this year and the next are tried, which means \"February 29\" written\n * during a run of non-leap years answers null rather than skipping ahead to the\n * next leap year. Deliberate, and the same trade as everywhere else here:\n * refusing costs the pre-selection, guessing three years forward would put a\n * date on screen nobody asked for.\n */\nfunction withResolvedYear(month: number, day: number, today: Date): Date | null {\n const thisYear = new Date(today.getFullYear(), month, day);\n if (thisYear.getMonth() !== month || thisYear.getDate() !== day) return null;\n const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());\n if (thisYear >= startOfToday) return thisYear;\n const nextYear = new Date(today.getFullYear() + 1, month, day);\n return nextYear.getMonth() === month && nextYear.getDate() === day ? nextYear : null;\n}\n\n/** Builds a date from explicit parts, rejecting overflow like February 31. */\nfunction exactDate(year: number, month: number, day: number): Date | null {\n const d = new Date(year, month, day);\n return d.getMonth() === month && d.getDate() === day ? d : null;\n}\n\n/**\n * `YYYY-MM-DD` for a date the user typed however they liked, or null when it\n * can't be read with confidence.\n *\n * Handles, in order: ISO; a month name with a day either side of it, with or\n * without an ordinal suffix or a year; and a numeric pair where only one\n * reading is possible (`13/05` can only be day-first, since 13 is not a month).\n *\n * Deliberately NOT handled:\n * - A numeric pair where both numbers are 12 or under. `09/05` is September\n * 5th to an American and May 9th to everyone else, and nothing in the\n * response says which — so it answers null rather than pick a side.\n * - Relative phrases (\"tomorrow\", \"next friday\"). Readable in principle, but\n * they depend on a reference the user may not share with the server, and\n * getting them subtly wrong is the expensive kind of wrong.\n */\nexport function parseLooseDate(\n text: string | undefined | null,\n opts: LooseDateOptions = {},\n): string | null {\n if (!text) return null;\n const today = opts.today ?? new Date();\n const t = dropOrdinals(text.trim().toLowerCase()).replace(/\\s+/g, \" \");\n\n const iso = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/.exec(t);\n if (iso) {\n const d = exactDate(Number(iso[1]), Number(iso[2]) - 1, Number(iso[3]));\n return d ? isoDate(d) : null;\n }\n\n // \"september 5\", \"sep 5 2026\", \"September 5, 2026\"\n const monthFirst = /^([a-z]+)\\.? (\\d{1,2})(?:,? (\\d{4}))?$/.exec(t);\n if (monthFirst) {\n const month = monthIndex(monthFirst[1]);\n if (month >= 0) return fromParts(month, Number(monthFirst[2]), monthFirst[3], today);\n }\n\n // \"5 september\", \"5 of September 2026\"\n const dayFirst = /^(\\d{1,2}) (?:of )?([a-z]+)\\.?(?:,? (\\d{4}))?$/.exec(t);\n if (dayFirst) {\n const month = monthIndex(dayFirst[2]);\n if (month >= 0) return fromParts(month, Number(dayFirst[1]), dayFirst[3], today);\n }\n\n // Numeric pair, with or without a year. Only resolvable when one of the two\n // numbers is too large to be a month.\n const numeric = /^(\\d{1,2})[/\\-.](\\d{1,2})(?:[/\\-.](\\d{2,4}))?$/.exec(t);\n if (numeric) {\n const a = Number(numeric[1]);\n const b = Number(numeric[2]);\n if (a > 12 && b <= 12) return fromParts(b - 1, a, numeric[3], today);\n if (b > 12 && a <= 12) return fromParts(a - 1, b, numeric[3], today);\n return null; // both plausible as months — ambiguous, see the doc comment\n }\n\n return null;\n}\n\nfunction fromParts(\n month: number,\n day: number,\n yearText: string | undefined,\n today: Date,\n): string | null {\n if (yearText) {\n // A two-digit year is its own ambiguity (\"05\" — 1905? 2005? 2105?), so it\n // is refused for the same reason an ambiguous numeric pair is.\n if (yearText.length !== 4) return null;\n const d = exactDate(Number(yearText), month, day);\n return d ? isoDate(d) : null;\n }\n const d = withResolvedYear(month, day, today);\n return d ? isoDate(d) : null;\n}\n\n/**\n * The `YYYY-MM-DD` for an identified date param.\n *\n * Reads a normalized value off the param first, if one is ever there. The\n * server sends only the user's literal text today, so in practice this always\n * falls through to {@link parseLooseDate} — but normalizing server-side is the\n * real fix for the ambiguous cases the parser refuses, and when it lands this\n * is the one place that has to change.\n */\nexport function resolveIdentifiedDate(\n param: { text: string; isoDate?: unknown },\n opts: LooseDateOptions = {},\n): string | null {\n const fromServer = param.isoDate;\n if (typeof fromServer === \"string\" && /^\\d{4}-\\d{2}-\\d{2}$/.test(fromServer)) return fromServer;\n return parseLooseDate(param.text, opts);\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport { formatAbsoluteDate } from \"./dateGrid\";\nimport { type LooseDateOptions, parseLooseDate } from \"./looseDate\";\n\n/**\n * A span of days, both ends inclusive, as `YYYY-MM-DD`. `start <= end` always —\n * every producer here orders the two.\n */\nexport interface DateRange {\n start: string;\n end: string;\n}\n\n/**\n * What joins the two ends of a committed range.\n *\n * A spaced hyphen, because the committed text lands verbatim in the user's own\n * sentence and is what the server parses back. `parseLooseDateRange` reads far\n * more separators than this one — it has to, since it is also what reads a\n * server's option text and the user's own words — but the SDK only ever writes\n * this one.\n */\nexport const RANGE_SEPARATOR = \" - \";\n\n/** Metadata key holding a committed range's start, as `YYYY-MM-DD`. */\nexport const DATE_RANGE_META_START = \"aiaDateRangeStart\";\n/** Metadata key holding a committed range's end, as `YYYY-MM-DD`. */\nexport const DATE_RANGE_META_END = \"aiaDateRangeEnd\";\n\n/** The two dates as a range, in whichever order puts the earlier one first. */\nexport function orderedRange(a: string, b: string): DateRange {\n // ISO strings order the same way the dates they name do.\n return a <= b ? { start: a, end: b } : { start: b, end: a };\n}\n\nconst ISO_RE = /^\\d{4}-\\d{2}-\\d{2}$/;\n\n/** `YYYY-MM-DD` → a local `Date`, or null when it isn't one. */\nfunction dateFromIso(iso: string): Date | null {\n if (!ISO_RE.test(iso)) return null;\n const [y, m, d] = iso.split(\"-\").map(Number);\n const date = new Date(y, m - 1, d);\n return date.getMonth() === m - 1 && date.getDate() === d ? date : null;\n}\n\n/**\n * Every way the halves of a range can be written apart, as split points.\n *\n * Word separators are unambiguous. A dash is not — it is also how a date writes\n * itself (`2026-09-11`, `13-05-2026`) — so rather than guess which dash is the\n * separator, EVERY dash is tried as one and the reading is only accepted when\n * exactly one split yields two readable dates (see {@link parseLooseDateRange}).\n * That is what lets \"2026-09-11-2026-10-13\" resolve while \"13-05-2026\" refuses.\n */\nconst WORD_SEPARATOR = /\\s+(?:to|through|thru|until|til|till)\\s+/g;\nconst DASH_SEPARATOR = /[-–—]/g;\n\nfunction splitPoints(text: string): Array<[string, string]> {\n const out: Array<[string, string]> = [];\n for (const re of [WORD_SEPARATOR, DASH_SEPARATOR]) {\n // Fresh lastIndex per call — these are module-level regexes with /g.\n re.lastIndex = 0;\n let match = re.exec(text);\n while (match !== null) {\n out.push([text.slice(0, match.index), text.slice(match.index + match[0].length)]);\n match = re.exec(text);\n }\n }\n return out;\n}\n\n/**\n * Best-effort parse of a span of days written in the user's (or the server's)\n * own words — \"September 11 - October 13\", \"September 11 to November 16\",\n * \"13/05/2026 - 20/05/2026\".\n *\n * Each half goes through {@link parseLooseDate}, so a range reads exactly the\n * date formats a single date does, and refuses exactly what it refuses. Same\n * trade as everywhere in this corner of the SDK: **null is a normal outcome**.\n * It costs the range picker's pre-selection, or keeps a suggestion rendering as\n * a plain option list — never correctness.\n *\n * Refused on purpose:\n * - A backwards span (\"October 13 - September 11\"). Nothing here can tell a\n * typo from a year boundary the writer left implicit, and silently swapping\n * the ends would answer a question the user didn't ask.\n * - A shared-month shorthand (\"September 11 - 13\"). The right half alone is\n * not a date, and inferring the month from the left half is the kind of\n * guess this file exists not to make.\n * - Anything where two different splits both read as a range — the text is\n * then genuinely ambiguous about where its own separator is.\n *\n * A yearless half resolves the way {@link parseLooseDate} resolves one: to its\n * next occurrence. That is what makes \"December 30 - January 5\" read as the\n * turn of the year rather than as a backwards span.\n */\nexport function parseLooseDateRange(\n text: string | undefined | null,\n opts: LooseDateOptions = {},\n): DateRange | null {\n if (!text) return null;\n const found = new Set<string>();\n let range: DateRange | null = null;\n for (const [left, right] of splitPoints(text.trim())) {\n const start = parseLooseDate(left.trim(), opts);\n if (!start) continue;\n const end = parseLooseDate(right.trim(), opts);\n if (!end || end < start) continue;\n found.add(`${start}|${end}`);\n range = { start, end };\n }\n // More than one reading means the text doesn't say where its separator is.\n return found.size === 1 ? range : null;\n}\n\n/**\n * The committed text for a range — \"September 11 - October 13\".\n *\n * Each end is written by {@link formatAbsoluteDate}, never by `formatDate`:\n * the weekday shorthand it would use for a nearby day (\"Tuesday\") names a\n * different date every week it is read, which a single answer can carry and a\n * span cannot.\n *\n * A span of one day commits as that day alone. \"September 11 - September 11\"\n * is not how anyone writes it, and the range is still recoverable from the\n * option's metadata when the answer is re-edited.\n */\nexport function formatDateRange(range: DateRange): string {\n const start = dateFromIso(range.start);\n const end = dateFromIso(range.end);\n if (!start || !end) return \"\";\n if (range.start === range.end) return formatAbsoluteDate(start);\n return `${formatAbsoluteDate(start)}${RANGE_SEPARATOR}${formatAbsoluteDate(end)}`;\n}\n\n/**\n * The synthetic option a completed range commits through.\n *\n * A range is answered in two taps, but it is committed once — so the second tap\n * builds this and hands it to the very same `selectOption` paths a single date\n * takes (fresh pill, re-edit, identified span). Deliberately carries no\n * `aiaDate` metadata: that key is what marks a *calendar cell*, and its absence\n * is what tells the range branch this option is the commit rather than a tap.\n */\nexport function buildRangeOption(a: string, b: string): SuggestionOption {\n const range = orderedRange(a, b);\n return {\n text: formatDateRange(range),\n is_tappable: true,\n kind: null,\n metadata: {\n [DATE_RANGE_META_START]: range.start,\n [DATE_RANGE_META_END]: range.end,\n },\n };\n}\n\n/**\n * The range an already-answered param holds, so both its ends can be marked\n * when it is re-opened.\n *\n * Metadata first — it is what the SDK itself wrote, exact and free of the\n * yearless-date resolution the text form needs. Text second, which is what a\n * range that reached the input any other way (a server-identified span, a\n * consumer's controlled value) has.\n */\nexport function selectedRangeFor(\n param: { text?: string; metadata?: Record<string, unknown> } | undefined | null,\n opts: LooseDateOptions = {},\n): DateRange | null {\n const start = param?.metadata?.[DATE_RANGE_META_START];\n const end = param?.metadata?.[DATE_RANGE_META_END];\n if (\n typeof start === \"string\" &&\n typeof end === \"string\" &&\n ISO_RE.test(start) &&\n ISO_RE.test(end)\n )\n return orderedRange(start, end);\n return parseLooseDateRange(param?.text, opts);\n}\n","import type { SuggestionOption } from \"../shared-types\";\nimport { tokenizeParamType } from \"../utils/paramType\";\nimport { parseLooseDateRange } from \"./dateRange\";\nimport { parseLooseDate } from \"./looseDate\";\n\n/**\n * How the dropdown lets the user answer a suggestion.\n *\n * `\"options\"` is every suggestion the server has ever sent: pick one of its\n * options, or type to filter them. `\"date\"` ignores the options entirely and\n * renders a calendar instead. `\"date-range\"` renders the same calendar,\n * answered in two taps — a start and an end — and committed as one span.\n */\nexport type FormatType = \"options\" | \"date\" | \"date-range\";\n\n/** Whether this format is answered by a calendar rather than by a list. */\nexport function isCalendarFormat(format: FormatType): boolean {\n return format === \"date\" || format === \"date-range\";\n}\n\n/** Whole words that mean \"this parameter is a date\". */\nconst DATE_TOKENS = new Set([\"date\", \"dates\"]);\n\n/** Whole words that, next to a date token, mean \"and it is a span of them\". */\nconst RANGE_TOKENS = new Set([\"range\", \"ranges\", \"span\", \"period\", \"window\"]);\n\n/**\n * How many of a suggestion's own options must read as dates before the\n * parameter is treated as one regardless of what it is called.\n *\n * Three, because one or two is the range where a coincidence lives — a version\n * string like \"25.12\" reads as a date on its own — while three unrelated\n * options all parsing is not something that happens by accident. Options that\n * cannot be read confidently (`parseLooseDate` refuses ambiguous numerics and\n * relative phrases) do not count toward it, which keeps the bar honest.\n */\nconst DATE_OPTION_THRESHOLD = 3;\n\ninterface DateOptionCounts {\n /** Options that read as one day — \"September 11\". */\n single: number;\n /** Options that read as a span of days — \"September 11 - October 13\". */\n range: number;\n}\n\n/**\n * How many of these options read as dates, and how many of those are spans.\n *\n * Only tappable options count: the others are labels and headings, not values\n * the user can pick, so they say nothing about what the parameter holds. A\n * span is counted only as a span — the two counts never double-count an\n * option, which is what lets them be compared against each other below.\n */\nfunction countDateOptions(options: SuggestionOption[] | undefined): DateOptionCounts {\n const counts: DateOptionCounts = { single: 0, range: 0 };\n if (!options) return counts;\n for (const option of options) {\n if (!option.is_tappable) continue;\n // Ranges first: a range never parses as a single date, but asking in this\n // order keeps the two categories obviously exclusive rather than relying\n // on that.\n if (parseLooseDateRange(option.text) !== null) counts.range += 1;\n else if (parseLooseDate(option.text) !== null) counts.single += 1;\n }\n return counts;\n}\n\n/**\n * Decide how a suggestion should be answered.\n *\n * The server cannot send a `formatType` yet, so a suggestion that holds a date\n * is detected here — by the options it offers, and failing that by its name —\n * and rendered as a calendar with those options ignored.\n *\n * The detection is deliberately the LAST resort. A `formatType` that *is* on\n * the wire always wins, so the day the server starts sending one this function\n * needs a single block deleted — the datepicker itself, and every gate that\n * consults this, keep working untouched.\n *\n * Options are read before the name because they are the stronger evidence:\n * they are the values themselves, while a name is what someone chose to call\n * them. A parameter called `travel_date` whose options are all spans is a span\n * parameter, whatever its name says.\n *\n * Accepts anything with a `type`, not a `Suggestion`, so re-edit can ask the\n * same question about a completed param via its `suggestionType`.\n */\nexport function resolveFormatType(\n source: { type: string; options?: SuggestionOption[] } | undefined | null,\n): FormatType {\n if (!source) return \"options\";\n const fromServer = (source as { formatType?: unknown }).formatType;\n if (fromServer === \"date\" || fromServer === \"options\" || fromServer === \"date-range\")\n return fromServer;\n\n // A parameter can be a date without saying so in its name — \"when\", \"arrival\",\n // \"expiry\". What it offers gives it away, so the options are read as the\n // first signal. Note this only catches options written as actual dates: a\n // server answering with \"today\" / \"tomorrow\" / \"next week\" scores zero here,\n // because those are exactly the phrases `parseLooseDate` refuses to read.\n const counts = countDateOptions(source.options);\n // Spans outnumbering single days is what makes it a span parameter. A\n // majority rather than a clean sweep, because a server that offers \"Next 7\n // days\" / \"Sep 11 - Oct 13\" / \"Oct 1 - Oct 31\" alongside one bare date is\n // still asking for a span. Both categories count toward the bar here — three\n // options that are all dates of SOME shape is the same evidence either way.\n // A tie falls through deliberately: an even split of days and spans says the\n // options disagree about what the parameter holds, and neither calendar can\n // answer half of them. The option list, which can, is the honest fallback.\n if (counts.single + counts.range >= DATE_OPTION_THRESHOLD && counts.range > counts.single) {\n return \"date-range\";\n }\n\n const tokens = tokenizeParamType(source.type);\n if (tokens.some((token) => DATE_TOKENS.has(token))) {\n return tokens.some((token) => RANGE_TOKENS.has(token)) ? \"date-range\" : \"date\";\n }\n // Single days ONLY, deliberately — the bar for \"replace these options with a\n // one-day calendar\" is unchanged by ranges existing. Counting spans toward it\n // would lower it: two single dates plus one span would newly clear three and\n // throw away an option list the user could previously pick from, span\n // included.\n return counts.single >= DATE_OPTION_THRESHOLD ? \"date\" : \"options\";\n}\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 { IdentifiedParam, IdentifiedParamState } from \"../shared-types\";\n\n/**\n * Projects client-side identified-param state onto its wire shape.\n *\n * The one place the `{ type, value }` form is spelled out. Both the request\n * body (`api.ts`) and the `AutocompleteResult` handed to `onResult` /\n * `onSubmit` go through it, so the params the consumer receives are exactly\n * the ones the server was told about.\n */\nexport function toWireIdentifiedParams(params: IdentifiedParamState[]): IdentifiedParam[] {\n return params.map((p) => ({ type: p.type, value: p.text }));\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 { toWireIdentifiedParams } from \"./identifiedParams\";\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: toWireIdentifiedParams(identifiedParams),\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 { OptionOverrides, Suggestion } from \"../shared-types\";\n\n/**\n * Strips the server's options from every suggestion whose type the consumer\n * overrides. Server options are never shown for an overridden type, mirroring\n * the derive layer, and the consumer answers later — through the\n * `OptionSourceController`, once the pill is active — so the suggestion lands\n * with NO options rather than a list the consumer never approved. Storing the\n * server's list in the meantime would put it on screen and let exact-match\n * promotion answer the pill with it.\n */\nexport function applyOptionOverrides(\n suggestions: Suggestion[],\n overrides?: OptionOverrides,\n): Suggestion[] {\n if (!overrides) return suggestions;\n return suggestions.map((s) => (overrides[s.type] ? { ...s, options: [] } : s));\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 { isCalendarFormat, resolveFormatType } from \"../date/formatType\";\nimport type {\n APIConfig,\n CompletedParamState,\n CustomFieldsReport,\n IdentifiedParamState,\n OptionOverrides,\n Product,\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\";\nimport type { OptionSourceTick } from \"./optionSourceController\";\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 * Fired once per `/suggest` response that was committed to the store —\n * after the final write, so the store already holds the response's\n * suggestions, its identified params and any exact-match promotion it\n * triggered. Never fires for a superseded, aborted or failed request.\n *\n * Carries no payload on purpose: the orchestrator reads the store, which is\n * the settled state, rather than a snapshot this controller would have to\n * keep in step with every subscriber that runs inside the write.\n */\n onResponse?: () => void;\n /**\n * What the product strip should show for the response about to be\n * committed, given the catalog report it carried (null when it carried\n * none). Returns `undefined` to leave `products` untouched — the strip is\n * then a custom integration's, fed from `onRequest`. Consulted inside the\n * final write so cards and suggestions land in one notification.\n */\n productsForResponse?: (report: CustomFieldsReport | null) => Product[] | undefined;\n /**\n * Consulted on every debounce tick before the server gates. A consumer\n * `optionOverrides` entry that owns the active pill answers the tick\n * instead of the server — see `OptionSourceController.tick` for the three\n * outcomes. Absent, every tick is the server's.\n */\n consumerTick?: (state: CoreState, minDiff: number) => OptionSourceTick;\n}\n\nexport class FetchController {\n private fetchVersion = 0;\n private abortController: AbortController | null = null;\n private debounceTimer: ReturnType<typeof setTimeout> | null = null;\n private slowDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n private unsubscribe: (() => void) | null = null;\n\n constructor(\n private store: Store<CoreState>,\n private getApiConfig: () => APIConfig | undefined,\n private getOptionOverrides: () => OptionOverrides | undefined,\n private getMaskCompletedText: () => boolean | undefined,\n private getOnError: () => ((error: Error) => void) | undefined,\n private getSessionId: () => string,\n private 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 // Resolved from the INCOMING suggestion, not from state: this runs as the\n // response lands, so the store still describes the suggestion being\n // replaced. A date pill's options never reach the screen, so nothing the\n // user typed can be an exact match for one.\n if (active && !isCalendarFormat(resolveFormatType(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 icon: match.icon,\n icon_svg: match.icon_svg,\n };\n newSuggestions = newSuggestions.filter((s) => s !== active);\n this.callbacks.onAutoMatch?.({ active, matched: match, rawQuery });\n }\n }\n\n // The catalog report and the cards it feeds. `undefined` on the wire is\n // \"no catalog was read\" and lands as null, so the state always says\n // whether the response on screen carried a block.\n const customFields = res.data.custom_fields ?? null;\n const products = this.callbacks.productsForResponse?.(customFields);\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 customFields,\n ...(products !== undefined ? { products } : {}),\n ...(extraParam ? { completedParams: completedNow } : {}),\n };\n });\n\n // The response is in the store; tell the orchestrator so it can hand\n // the consumer the query as it now stands. Not gated on the version:\n // a subscriber inside the write above may already have started the\n // next fetch, but this response was current when it landed and the\n // state it produced is what the user is looking at.\n this.callbacks.onResponse?.();\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 /**\n * Run the scheduler's gates now, off-timer, with the slow tick's\n * one-character rule. Used when a consumer override has just answered\n * empty for the typed phrase: the server is the fallback for it, and the\n * pending timers are dropped so they don't issue the same request twice.\n */\n attemptNow() {\n this.clearTimers();\n // Same stand-down `scheduleFetch` honours: a selection or reset that set\n // this flag issues its own request. The flag is consumed synchronously by\n // the subscriber that set it, so this is defensive rather than reachable\n // today — but every writer of the flag assumes it is consulted before the\n // next request, and this is a request path.\n if (this.store.get().skipNextFetch) {\n this.store.set({ skipNextFetch: false });\n return;\n }\n this.attemptFetch(1, true);\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 this.debounceTimer = setTimeout(() => {\n if (this.attemptFetch(MIN_CHARS_DIFF)) {\n if (this.slowDebounceTimer) clearTimeout(this.slowDebounceTimer);\n }\n }, DEBOUNCE_MS);\n\n this.slowDebounceTimer = setTimeout(() => this.attemptFetch(1), SLOW_DEBOUNCE_MS);\n }\n\n /**\n * One debounce tick. Returns true when the tick was spent — a request went\n * out, or the consumer took it. `anyChange` relaxes the length-delta rule to\n * \"the raw query differs from what the server last saw\": a consumer miss is\n * an explicit signal, and a same-length replacement (\"zz\" → \"Me\") must not\n * be the one phrase the server never hears about.\n */\n private attemptFetch(minDiff: number, anyChange = false): boolean {\n const s = this.store.get();\n if (!s.text && s.completedParams.length === 0) {\n this.doFetch(\"\", []);\n return true;\n }\n\n // A consumer override that owns the active pill is asked in place of the\n // server, on this same tick. \"owns\" without an ask leaves the slow timer\n // to re-check, exactly as a sub-threshold delta does for the server.\n const consumer = this.callbacks.consumerTick?.(s, minDiff) ?? \"no\";\n if (consumer === \"asked\") return true;\n if (consumer === \"owns\") return false;\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 // A calendar pill — one day or a span — shows a calendar, so typing is not\n // filtering anything, and the gates below have to stop consulting options\n // the user cannot see. Without this the filter-zone gate would suppress the\n // request whenever the typed text happened to match one of the ignored\n // options, and the request would simply never go out.\n const isDate = isCalendarFormat(s.activeFormatType);\n const currentFiltered = active && !isDate ? filterOptions(active.options, currentQuery) : [];\n const tappableFiltered = currentFiltered.filter((o: SuggestionOption) => o.is_tappable);\n const hasExactMatch =\n active && !isDate ? 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 const changed = anyChange && rawQuery !== s.lastRawQuery;\n if (isDeleting || charDiff >= minDiff || changed) {\n this.doFetch(rawQuery, updatedParams);\n return true;\n }\n return false;\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 * Root resolution for a widget that may be mounted inside a shadow root.\n *\n * Everything the SDK touches outside its own subtree — the stylesheet it\n * injects, the selection it reads, the option elements it looks up by id —\n * belongs to a *root*, and that root is the document only when the widget is\n * mounted in the light DOM. Inside a shadow root the document is the wrong\n * root: its stylesheets don't reach the shadow tree, `document.getElementById`\n * can't see shadow content at all, and (in WebKit) `document.getSelection()`\n * retargets its anchor to the host, which silently destroys caret positioning.\n *\n * So: never reach for `document` directly. Resolve the root from a node the\n * widget owns, and ask that.\n */\n\n/** A root that can own stylesheets, a selection and ids: document or shadow root. */\nexport type WidgetRoot = Document | ShadowRoot;\n\n/** `getSelection()` is on `Document` in every engine, on `ShadowRoot` only in Chromium/WebKit. */\ninterface RootWithSelection {\n getSelection?: () => Selection | null;\n}\n\n/** `getElementById` comes from `NonElementParentNode` — `Document` and `DocumentFragment`. */\ninterface RootWithGetElementById {\n getElementById?: (id: string) => HTMLElement | null;\n}\n\nfunction isWidgetRoot(node: Node): node is Node & WidgetRoot {\n if (node.nodeType === Node.DOCUMENT_NODE) return true;\n // A ShadowRoot is a DocumentFragment with a host; a plain fragment (a\n // detached node's root) is neither, and its owner document is the right\n // answer for it.\n return node.nodeType === Node.DOCUMENT_FRAGMENT_NODE && \"host\" in node;\n}\n\n/**\n * The document or shadow root that owns `node`.\n *\n * Falls back to the owning document for a detached node (whose `getRootNode()`\n * is a bare element or fragment) and for DOM shims without `getRootNode`.\n */\nexport function rootOf(node: Node): WidgetRoot {\n const root = typeof node.getRootNode === \"function\" ? node.getRootNode() : null;\n if (root && isWidgetRoot(root)) return root;\n return node.ownerDocument ?? (node as unknown as Document);\n}\n\n/** The document owning `node`, whichever root it lives under. */\nexport function documentOf(node: Node): Document {\n return node.ownerDocument ?? (node as unknown as Document);\n}\n\n/**\n * The `Selection` that reports positions inside `node`'s tree.\n *\n * Chromium and WebKit give each shadow root its own `Selection`; WebKit's\n * document-level one retargets `anchorNode` to the shadow host, so reading it\n * would place the caret on the host instead of the text the user clicked.\n * Firefox has no `ShadowRoot.getSelection()` but its document selection\n * already reports nodes inside an open shadow tree — hence the fallback.\n */\nexport function getSelectionFor(node: Node): Selection | null {\n const root = rootOf(node);\n const selection = (root as RootWithSelection).getSelection?.();\n if (selection) return selection;\n return documentOf(node).getSelection?.() ?? null;\n}\n\n/**\n * Look up an element by id within `node`'s root.\n *\n * `document.getElementById` cannot see into a shadow tree, so an id lookup for\n * an element the widget rendered has to start from the widget's own root.\n * `ShadowRoot` inherits `getElementById` from `DocumentFragment`.\n */\nexport function getElementByIdFor(node: Node | null, id: string): HTMLElement | null {\n const root: (WidgetRoot & RootWithGetElementById) | null = node\n ? rootOf(node)\n : typeof document !== \"undefined\"\n ? document\n : null;\n if (!root) return null;\n if (typeof root.getElementById === \"function\") return root.getElementById(id);\n // Defensive only: every engine the SDK targets implements\n // `NonElementParentNode.getElementById` on `ShadowRoot`. This branch is for a\n // DOM shim that doesn't — and it searches the document, which for a shadow\n // root is the wrong tree, so it will find nothing rather than the right\n // element. Callers already treat a null lookup as \"fall back to the\n // configured value\", which is the correct behaviour there.\n return typeof document !== \"undefined\" ? document.getElementById(id) : null;\n}\n","/**\n * Scroll-to-caret for the editor's own scroll container.\n *\n * The editor is a scroll container (`overflow-y: auto` under a height cap in\n * the core stylesheet; a consumer holding it to one line adds `overflow-x`).\n * Browsers scroll such a box to the caret as the user types, but not when the\n * caret is moved by script — which the SDK does on every render that actually\n * rewrites the editable's content (`renderEditable` early-returns on an\n * unchanged segment key, and restores the caret only while focused).\n * Everything here exists to close that gap.\n */\n\nimport { documentOf, getSelectionFor } from \"./roots\";\n\n/**\n * Kept between the caret and the scroll container's edge (CSS px) after a\n * scroll correction, so the caret never sits flush against the boundary.\n */\nconst CARET_SCROLL_MARGIN = 2;\n\n/** Class marking the widget's own container — the walk never goes past it. */\nconst WIDGET_CONTAINER_CLASS = \"magicx-aia\";\n\nfunction isScrollableOverflow(value: string): boolean {\n // `visible` and `clip` can never be scrolled. `hidden` shows no scrollbar\n // but its scroll offsets are still scriptable, which is all we mutate here.\n return value === \"auto\" || value === \"scroll\" || value === \"hidden\" || value === \"overlay\";\n}\n\n/**\n * Nearest scrollable box, starting at `root` itself and walking up.\n *\n * The walk stops at the widget's own container (and never reaches `<body>` or\n * the document element) so moving the caret can never scroll the consumer's\n * page or layout — only a box the widget lives in.\n */\nfunction findScrollContainer(root: HTMLElement): HTMLElement | null {\n const doc = documentOf(root);\n const win = doc.defaultView;\n if (!win?.getComputedStyle) return null;\n\n let el: HTMLElement | null = root;\n while (el && el !== doc.body && el !== doc.documentElement) {\n // Cheap gate first: a box whose content fits can't need scrolling, and\n // this runs on every content render.\n const overflowsX = el.scrollWidth > el.clientWidth;\n const overflowsY = el.scrollHeight > el.clientHeight;\n if (overflowsX || overflowsY) {\n const style = win.getComputedStyle(el);\n if (\n (overflowsX && isScrollableOverflow(style.overflowX)) ||\n (overflowsY && isScrollableOverflow(style.overflowY))\n ) {\n return el;\n }\n }\n if (el.classList.contains(WIDGET_CONTAINER_CLASS)) return null;\n el = el.parentElement;\n }\n return null;\n}\n\n/**\n * Scroll the caret back into view inside the editor's own scroll container.\n *\n * Browsers do this themselves while the user types, but not when the caret is\n * moved by script — so every programmatic caret placement (option selection,\n * caret restore after a re-render, controlled-mode `setValue`) would otherwise\n * leave the caret, and everything typed next, outside the visible box.\n *\n * Exactly one box is scrolled, by writing its own `scrollLeft` / `scrollTop`:\n * the nearest scrollable ancestor of the editable, which inside the widget's\n * own markup is the editor. `scrollIntoView` is deliberately not used — it\n * would walk the whole ancestor chain and the page with it. When the widget\n * does not own the editable's ancestry (a consumer-rendered editor in\n * `\"dropdown\"` / `\"headless\"` mode) that nearest box may be one of the\n * consumer's, which is what the browser would scroll for typed input anyway;\n * nothing above it moves either way. A no-op when nothing overflows, when the\n * caret is already visible, or when the caret has no measurable box.\n */\nexport function scrollCaretIntoView(root: HTMLElement): void {\n const sel = getSelectionFor(root);\n if (!sel || sel.rangeCount === 0) return;\n\n const range = sel.getRangeAt(0);\n if (!range.collapsed || !root.contains(range.startContainer)) return;\n\n // Find the box to scroll before measuring the caret: this runs on every\n // render that rewrites the editable, and an editor whose content fits needs\n // neither.\n const container = findScrollContainer(root);\n if (!container) return;\n\n // jsdom (and other layout-less DOM shims) don't implement Range geometry at\n // all — nothing to measure there, and consumers run their tests on those.\n if (typeof range.getBoundingClientRect !== \"function\") return;\n\n const caret = range.getBoundingClientRect();\n // A range with no box (detached, display:none, or a boundary the engine\n // can't measure) reports a zero rect at the viewport origin — acting on it\n // would yank the container back to the start.\n if (caret.height === 0) return;\n\n const box = container.getBoundingClientRect();\n\n // Adjust only the axis that actually overflows, and only when the caret is\n // outside the visible box — a caret already in view is never re-clamped.\n // `clientLeft` / `clientTop` skip the border; `clientWidth` / `clientHeight`\n // exclude the border (and any scrollbar gutter) on the far side.\n if (container.scrollWidth > container.clientWidth) {\n const viewLeft = box.left + container.clientLeft;\n const viewRight = viewLeft + container.clientWidth;\n if (caret.right > viewRight) {\n container.scrollLeft += caret.right - viewRight + CARET_SCROLL_MARGIN;\n } else if (caret.left < viewLeft) {\n container.scrollLeft -= viewLeft - caret.left + CARET_SCROLL_MARGIN;\n }\n }\n\n if (container.scrollHeight > container.clientHeight) {\n const viewTop = box.top + container.clientTop;\n const viewBottom = viewTop + container.clientHeight;\n if (caret.bottom > viewBottom) {\n container.scrollTop += caret.bottom - viewBottom + CARET_SCROLL_MARGIN;\n } else if (caret.top < viewTop) {\n container.scrollTop -= viewTop - caret.top + CARET_SCROLL_MARGIN;\n }\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\nimport { documentOf, getSelectionFor } from \"./roots\";\nimport { scrollCaretIntoView } from \"./scrollCaretIntoView\";\n\nconst NON_EDITABLE_SELECTOR = '[contenteditable=\"false\"]';\n/** A completed/identified param rendered as a chip (see renderEditable.ts). */\nconst CHIP_SELECTOR = \"strong[data-param-id]\";\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 documentOf(root).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 = getSelectionFor(root);\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 *\n * Also scrolls the caret back into view (see `scrollCaretIntoView`) — this is\n * the single caret-placement choke point, and a scripted caret move does not\n * get the browser's own scroll-to-caret behaviour.\n */\nexport function setCursorOffset(root: HTMLElement, offset: number): void {\n const doc = documentOf(root);\n const sel = getSelectionFor(root);\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 // Read the caret straight back: an engine that answers with a different\n // position than the one just set has canonicalized it, and the side of a\n // chip boundary the policy above chose is no longer readable from the DOM.\n recordChipBoundary(root, sel, range, clamped);\n\n // The browser scrolls a scroll container to the caret as the user types, but\n // not when the caret is moved by script — do it ourselves, for every caller.\n scrollCaretIntoView(root);\n}\n\n/** The chip enclosing `node`, or null when it sits outside every chip in `root`. */\nfunction chipContaining(node: Node | null, root: HTMLElement): HTMLElement | null {\n if (!node || !root.contains(node)) return null;\n const start = node.nodeType === Node.ELEMENT_NODE ? (node as Element) : node.parentElement;\n const chip = start?.closest<HTMLElement>(CHIP_SELECTOR) ?? null;\n // `closest` walks past the editable into the consumer's markup; a chip found\n // out there belongs to someone else.\n return chip && root.contains(chip) ? chip : null;\n}\n\n/**\n * Record a caret the SDK placed OUTSIDE a chip that the engine reports from\n * INSIDE it, as `\"<offset>:<chipId>\"` on the editable.\n *\n * Chromium answers a shadow root's own `Selection` from the *canonicalized*\n * caret position rather than the one that was set, and the canonical form of\n * \"immediately after a chip\" is \"at the end of the chip's own text node\". So\n * the boundary policy above — hop out of the `<strong>` so the caret reads as\n * outside it — is simply not observable inside a shadow root: every position at\n * a chip's edge reads back as a position within the chip, which the\n * `selectionchange` reader would otherwise take for a tap on the chip and\n * answer with re-edit. (Verified in Chromium 149 against a contentEditable in\n * an open shadow root; `document.getSelection()` is no help either — it\n * retargets the anchor to the host.)\n *\n * The light DOM reads back exactly what was set, so nothing is ever recorded\n * there and that path keeps the DOM position as its only source of truth.\n */\nfunction recordChipBoundary(root: HTMLElement, sel: Selection, range: Range, offset: number): void {\n const reported = chipContaining(sel.anchorNode, root);\n if (reported && reported !== chipContaining(range.startContainer, root)) {\n root.dataset.aiaCaretEscapedChip = `${offset}:${reported.dataset.paramId}`;\n return;\n }\n delete root.dataset.aiaCaretEscapedChip;\n}\n\n/**\n * True when the engine reports the caret inside chip `chipId`, but the SDK put\n * it there on purpose *outside* that chip's boundary — see `recordChipBoundary`.\n *\n * Callers asking \"is the caret in a chip?\" from the DOM anchor must consult\n * this, or they will read the SDK's own caret park as a tap on the chip.\n *\n * Only ever true inside a shadow root, and only at the exact offset the SDK\n * last parked at: a caret anywhere else in the chip — which is every ordinary\n * tap on one — still reads as inside it.\n */\nexport function caretEscapedChip(root: HTMLElement, chipId: string): boolean {\n const record = root.dataset.aiaCaretEscapedChip;\n if (!record) return false;\n const offset = getCursorOffset(root);\n return offset !== null && record === `${offset}:${chipId}`;\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 {\n AutocompleteResult,\n CompletedParamState,\n CustomFieldsReport,\n IdentifiedParamState,\n SkippedParamState,\n} from \"../shared-types\";\nimport { buildQuery } from \"./buildQuery\";\nimport { toWireIdentifiedParams } from \"./identifiedParams\";\nimport { withSkippedParams } from \"./skippedParams\";\n\n/**\n * Fields of an {@link AutocompleteResult} that come from the latest server\n * response rather than from the input. Both default to \"nothing yet\".\n */\nexport interface SubmitResultExtras {\n /** Server-identified params (`state.identifiedParams`). Default: none. */\n identifiedParams?: IdentifiedParamState[];\n /** Server's \"query is complete\" verdict (`state.isReady`). Default: false. */\n isReady?: boolean;\n /**\n * The catalog grounding report the latest response carried\n * (`state.customFields`). Default: none, and the result then has no\n * `custom_fields` key at all — mirroring the wire, where the block is absent\n * rather than null when no catalog was read.\n */\n customFields?: CustomFieldsReport | null;\n}\n\n/**\n * Builds the `AutocompleteResult` handed to `onSubmit` and `onResult`: the\n * placeholder-tokenized raw query plus the completed params, with skipped\n * suggestions folded in (see {@link withSkippedParams}), the server-identified\n * params in their wire shape, and the server's readiness verdict.\n *\n * Shared by every submit path — vanilla Enter / submit button, the React Tier 1\n * component, the Angular Tier 1 component — and by the core's per-response\n * `result` event, so none of them can drift on what a result contains.\n */\nexport function buildSubmitResult(\n text: string,\n completedParams: CompletedParamState[],\n skippedParams: SkippedParamState[] = [],\n extras: SubmitResultExtras = {},\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 identified_params: toWireIdentifiedParams(extras.identifiedParams ?? []),\n is_ready: extras.isReady ?? false,\n ...(extras.customFields ? { custom_fields: extras.customFields } : {}),\n };\n}\n","import { cellIso, isoDate } from \"../date/dateGrid\";\nimport { isCalendarFormat } from \"../date/formatType\";\nimport { cursorIsAtEnd, getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport { getElementByIdFor } from \"../dom/roots\";\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 /** Abandon a date pick opened on a span the server identified (Escape). */\n exitEditingIdentified?: () => 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 // The event target roots every id lookup below: the listbox and its\n // options live in the same root as the input the key was pressed in, and\n // `document.getElementById` cannot see into a shadow root. A Tier 2/3\n // consumer could in principle put their input in a different root than the\n // dropdown, in which case the lookups miss and each caller falls back the\n // way it does when the dropdown hasn't rendered yet — the configured\n // column count, and selectOption() instead of clicking the option element.\n const from = e.target instanceof Node ? e.target : null;\n const columns = this.getEffectiveColumns(from);\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 const entry = isCalendarFormat(state.activeFormatType)\n ? this.dateEntryIndex(state, false)\n : (tappableIndices[0] ?? 0);\n if (!state.isDropdownOpen && state.actionableSuggestions.length > 0) {\n this.store.set({ pillTapped: true, activeDropdownIndex: entry });\n break;\n }\n if (tappableIndices.length === 0) return;\n this.store.set({ activeDropdownIndex: entry });\n break;\n }\n\n e.preventDefault();\n if (tappableIndices.length === 0) return;\n // One row down a calendar is one week, always. The generic traversal\n // below only coincides with that while a month's columns are evenly\n // filled, and diverges at both ends of it.\n if (isCalendarFormat(state.activeFormatType)) {\n this.stepDateWeek(state, 7);\n break;\n }\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 = isCalendarFormat(state.activeFormatType)\n ? this.dateEntryIndex(state, true)\n : (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 (isCalendarFormat(state.activeFormatType)) {\n this.stepDateWeek(state, -7);\n break;\n }\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 // A calendar row is a week, so the day after Saturday is the next\n // row's Sunday — the column stop that makes sense for a grid of\n // independent options would strand the caret at each week's end.\n if (isCalendarFormat(state.activeFormatType)) {\n this.stepDateDay(state, 1);\n break;\n }\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 // Mirror of ArrowRight above — and it also keeps the column-0 branch\n // below out of reach on a calendar, where every Sunday sits in column\n // 0 and would otherwise drop the user into re-editing the previous\n // param mid-navigation.\n if (isCalendarFormat(state.activeFormatType)) {\n this.stepDateDay(state, -1);\n break;\n }\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, from);\n } else if (onSubmit) {\n const completed = onSubmit(\n buildSubmitResult(state.text, state.completedParams, state.skippedParams, {\n identifiedParams: state.identifiedParams,\n isReady: state.isReady,\n customFields: state.customFields,\n }),\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 // A half-picked range backs out one step at a time: the first Escape\n // drops the start the user chose, leaving the calendar up so they can\n // start the span again. Anything else would make a mis-tapped first\n // date cost the whole pick.\n if (state.dateRangeStart) {\n this.store.set({ pendingRangeStart: null });\n break;\n }\n // Escape backs out of a date pick the same way it backs out of a\n // re-edit, leaving the span's own words untouched.\n if (state.editingIdentified) {\n this.ctx.exitEditingIdentified?.();\n this.store.set({ activeDropdownIndex: -1 });\n break;\n }\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 /**\n * Where the highlight lands when the user arrows into a calendar: today if\n * the visible month holds it, otherwise the month's first or last day\n * depending on which edge the press came from. The grid's own entry point\n * (`tappableIndices[0]`) is column-major, so on a calendar it would land on\n * an arbitrary day in the first column rather than anywhere meaningful.\n */\n private dateEntryIndex(state: CoreState, fromBottom: boolean): number {\n const today = isoDate(new Date());\n const todayIdx = state.filteredOptions.findIndex((o) => cellIso(o) === today);\n if (todayIdx >= 0) return todayIdx;\n const days = state.filteredOptions\n .map((o, i) => (o.is_tappable ? i : -1))\n .filter((i) => i !== -1);\n return (fromBottom ? days[days.length - 1] : days[0]) ?? 0;\n }\n\n /**\n * Move the highlight by whole days, skipping the padding cells that stand in\n * for the days before the 1st and after the last. Stops at the month's edges\n * rather than wrapping — a wrap would silently jump the user four weeks\n * across a calendar they are reading positionally.\n */\n private stepDateDay(state: CoreState, delta: number): void {\n for (\n let i = state.activeDropdownIndex + delta;\n i >= 0 && i < state.filteredOptions.length;\n i += delta\n ) {\n if (state.filteredOptions[i]?.is_tappable) {\n this.store.set({ activeDropdownIndex: i });\n return;\n }\n }\n }\n\n /**\n * Move the highlight one week, which on a 7-column grid is exactly ±7 cells.\n * Landing outside the month returns to the input — the same escape the\n * options grid offers at its top and bottom rows, so a calendar is never a\n * trap the user can only leave with Escape.\n */\n private stepDateWeek(state: CoreState, delta: number): void {\n const next = state.activeDropdownIndex + delta;\n this.store.set({\n activeDropdownIndex: state.filteredOptions[next]?.is_tappable ? next : -1,\n });\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(from: Node | null): number {\n const listbox = getElementByIdFor(from, 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 = getElementByIdFor(from, `${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(\n index: number,\n options: SuggestionOption[],\n listboxId: string,\n from: Node | null,\n ) {\n const optionEl = getElementByIdFor(from, `${listboxId}-option-${index}`);\n if (optionEl) {\n optionEl.click();\n } else {\n this.ctx.selectOption(options[index]);\n }\n }\n}\n","import { isCalendarFormat } from \"../date/formatType\";\nimport type {\n OptionOverride,\n OptionOverrides,\n Suggestion,\n SuggestionOption,\n} from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\n\n/** Which pill an override is answering for. */\nexport type OptionSourceMode = \"pill\" | \"edit\";\n\n/** What a scheduler tick got back — see {@link OptionSourceController.tick}. */\nexport type OptionSourceTick = \"asked\" | \"owns\" | \"no\";\n\nexport interface OptionSourceCallbacks {\n /**\n * A non-empty answer was written to the store. The orchestrator re-runs\n * exact-match promotion: the user may have finished typing an option while\n * the override was still answering.\n */\n onAnswer?: (mode: OptionSourceMode, query: string) => void;\n /**\n * The override answered empty. The pill has nothing to offer for this phrase,\n * and the server is the fallback for it — the orchestrator decides whether a\n * request goes out (it does for a typed phrase on the active pill; a re-edit\n * has its own exit-and-fetch path).\n */\n onMiss?: (mode: OptionSourceMode, query: string) => void;\n}\n\ninterface Target {\n mode: OptionSourceMode;\n /**\n * Identity of the pill on screen: the suggestion object itself, or the id of\n * the completed param being re-edited. A response replaces every suggestion\n * object, so a new key after a response is what \"this pill just became\n * active\" looks like — and it is also why an answer must keep the key it\n * writes under in step (see `commit`).\n */\n key: object | string;\n type: string;\n suggestion: Suggestion;\n query: string;\n}\n\ninterface Ask {\n key: object | string;\n type: string;\n query: string;\n abort: AbortController | null;\n}\n\n/**\n * Drives `optionOverrides`: asks the consumer for a pill's options and writes\n * the answer into that pill.\n *\n * Two triggers, no timer of its own:\n * - **A pill becomes active** (store subscription): asked immediately, with\n * whatever the user has already typed for it. A response landing after the\n * user moved on is the normal case, so \"\" is not assumed.\n * - **The phrase changes** ({@link tick}, called by `FetchController` on its\n * own debounce): the same 100ms / 300ms cadence and the same\n * two-characters-or-wait rule the server request follows, measured against\n * the phrase last asked. One scheduler, so the two can't drift apart.\n *\n * While an override owns the pill on screen the server is not asked — the\n * tick tells the scheduler so. The server becomes the fallback the moment the\n * override answers empty for a phrase, and that phrase is remembered so the\n * consumer isn't asked the same question again when the server's answer\n * re-suggests the very same type (which would otherwise loop: miss → server →\n * same pill → ask → miss → …).\n *\n * Failure is contained by contract: a throw or a rejection is logged at most\n * once per instance and treated as an empty answer.\n */\nexport class OptionSourceController {\n private unsubscribe: (() => void) | null = null;\n /** The ask in flight or last answered for the pill on screen. */\n private current: Ask | null = null;\n private version = 0;\n /** The last (type, phrase) the consumer answered empty for. One slot: only the pill on screen matters. */\n private lastMiss: { type: string; query: string } | null = null;\n private hasLoggedError = false;\n\n constructor(\n private store: Store<CoreState>,\n private getOverrides: () => OptionOverrides | undefined,\n private callbacks: OptionSourceCallbacks = {},\n ) {}\n\n start(): void {\n this.unsubscribe = this.store.subscribe((next) => this.sync(next));\n this.sync(this.store.get());\n }\n\n dispose(): void {\n this.unsubscribe?.();\n this.unsubscribe = null;\n this.cancel();\n this.current = null;\n }\n\n /** A new session: nothing answered, nothing missed. The store's own field is reset by the caller. */\n reset(): void {\n this.cancel();\n this.current = null;\n this.lastMiss = null;\n }\n\n /**\n * The `optionOverrides` record was swapped. Whatever the old record was\n * asked — or answered empty — no longer describes the consumer: an ask in\n * flight is abandoned (its answer would otherwise land under the new\n * record's name), the miss memory is dropped, and the pill on screen is\n * asked afresh through the new record, or released if it no longer has an\n * override.\n */\n refresh(): void {\n this.reset();\n this.sync(this.store.get());\n }\n\n /** Whether `type` has an override at all. */\n hasOverride(type: string): boolean {\n return Boolean(this.getOverrides()?.[type]);\n }\n\n /**\n * Whether the consumer, not the server, is answering `type` for `query`:\n * there is an override and it has not already said there is nothing for\n * this phrase. Read by the fetch scheduler's gate and by the re-edit\n * no-match exit, which must wait for the consumer rather than judge a\n * pending answer by the previous one.\n */\n owns(type: string, query: string): boolean {\n return this.hasOverride(type) && !this.missCovers(type, query);\n }\n\n /**\n * The fetch scheduler's debounce tick. `minDiff` is the scheduler's own\n * character-delta rule for this tick (2 on the fast timer, 1 on the slow).\n *\n * - `\"asked\"`: the override was just asked for the new phrase; the tick is\n * spent and the slow timer can be dropped.\n * - `\"owns\"`: the override owns the pill but this tick didn't warrant a new\n * ask (phrase unchanged, or the delta is under `minDiff` — the slow timer\n * will re-check). No server request either way.\n * - `\"no\"`: no override owns the pill for this phrase; the scheduler's own\n * gates decide.\n */\n tick(state: CoreState, minDiff: number): OptionSourceTick {\n const target = this.resolveTarget(state);\n if (!target) return \"no\";\n if (this.missCovers(target.type, target.query)) return \"no\";\n const current = this.current;\n if (current && current.key === target.key) {\n if (current.query === target.query) return \"owns\";\n // The server gate's shape: the fast tick wants two characters of change\n // and the slow tick takes any change; a deletion always counts.\n const delta = Math.abs(target.query.length - current.query.length);\n const shorter = target.query.length < current.query.length;\n if (minDiff > 1 && !shorter && delta < minDiff) return \"owns\";\n }\n this.ask(target);\n return \"asked\";\n }\n\n /** Store subscription: a pill (or re-edit) coming on screen is asked at once; leaving clears. */\n private sync(state: CoreState): void {\n const target = this.resolveTarget(state);\n if (!target) {\n if (this.current) {\n this.cancel();\n this.current = null;\n }\n if (state.optionSearch) this.store.set({ optionSearch: null });\n return;\n }\n if (this.current && this.current.key === target.key) return;\n this.ask(target);\n }\n\n private resolveTarget(state: CoreState): Target | null {\n // A date — one day or a span — is answered with a calendar; the options,\n // the consumer's or the server's, are never on screen, so there is nothing\n // to ask for.\n if (isCalendarFormat(state.activeFormatType)) return null;\n // An identified span being answered is not a pill and has no options.\n if (state.editingIdentified) return null;\n const editing = state.editingParam;\n if (editing && state.editingAnchor != null) {\n if (!this.hasOverride(editing.suggestionType)) return null;\n return {\n mode: \"edit\",\n key: editing.id,\n type: editing.suggestionType,\n suggestion: {\n type: editing.suggestionType,\n text: editing.suggestionPlaceholder,\n required: true,\n options: editing.options,\n },\n query: state.optionQuery,\n };\n }\n const active = state.actionableSuggestions[0];\n if (!active || !this.hasOverride(active.type)) return null;\n return {\n mode: \"pill\",\n key: active,\n type: active.type,\n suggestion: active,\n query: state.optionQuery,\n };\n }\n\n private missCovers(type: string, query: string): boolean {\n return this.lastMiss != null && this.lastMiss.type === type && this.lastMiss.query === query;\n }\n\n private ask(target: Target): void {\n this.cancel();\n const override = this.getOverrides()?.[target.type];\n const version = ++this.version;\n if (!override || this.missCovers(target.type, target.query)) {\n // Nothing to ask: the consumer already said there is nothing for this\n // phrase. Record the pill as covered so the tick doesn't re-ask, and\n // mark the search settled so the loading state can't linger.\n this.current = { key: target.key, type: target.type, query: target.query, abort: null };\n this.setSearch(target, \"done\");\n return;\n }\n const abort = new AbortController();\n this.current = { key: target.key, type: target.type, query: target.query, abort };\n\n // A synchronous throw and a rejection are the same failure to the caller;\n // routing both through one try lets a plain-array answer skip the\n // loading state entirely — the list is there before this call returns.\n let raw: ReturnType<OptionOverride>;\n try {\n raw = override(target.query, abort.signal, target.suggestion);\n } catch (err) {\n this.logOnce(err);\n this.commit(target, [], version);\n return;\n }\n if (Array.isArray(raw)) {\n this.commit(target, raw, version);\n return;\n }\n this.setSearch(target, \"loading\");\n this.await(target, raw, abort.signal, version);\n }\n\n private async await(\n target: Target,\n pending: Promise<SuggestionOption[]>,\n signal: AbortSignal,\n version: number,\n ): Promise<void> {\n let answer: SuggestionOption[];\n try {\n const raw = await pending;\n // Two guards, not one: `version` catches a newer ask that already went\n // out, `signal.aborted` catches an override that resolved instead of\n // rejecting after we cancelled it.\n if (signal.aborted || version !== this.version) return;\n answer = Array.isArray(raw) ? raw : [];\n } catch (err) {\n // An abort is the SDK cancelling its own ask — not a failure, and the\n // newer ask owns the pill from here.\n if (signal.aborted || version !== this.version || isAbortError(err)) return;\n this.logOnce(err);\n answer = [];\n }\n this.commit(target, answer, version);\n }\n\n private commit(target: Target, answer: SuggestionOption[], version: number): void {\n if (version !== this.version) return;\n const live = this.resolveTarget(this.store.get());\n if (!live || live.key !== target.key) return;\n\n if (answer.length === 0) {\n this.lastMiss = { type: target.type, query: target.query };\n } else if (this.lastMiss?.type === target.type) {\n this.lastMiss = null;\n }\n\n const search = { type: target.type, query: target.query, status: \"done\" as const };\n if (target.mode === \"edit\") {\n this.store.set((s) => ({\n editingParam:\n s.editingParam && s.editingParam.id === target.key\n ? { ...s.editingParam, options: answer }\n : s.editingParam,\n optionSearch: search,\n }));\n } else {\n // The answered pill is a new object, and `current.key` must move with\n // it BEFORE the write: `sync` runs inside this write's notification and\n // would otherwise read the replacement as a pill that just came on\n // screen — and ask again, forever.\n const answered: Suggestion = { ...target.suggestion, options: answer };\n if (this.current && this.current.key === target.key) this.current.key = answered;\n this.store.set((s) => ({\n suggestions: s.suggestions.map((sg) => (sg === target.suggestion ? answered : sg)),\n optionSearch: search,\n }));\n }\n\n if (answer.length === 0) this.callbacks.onMiss?.(target.mode, target.query);\n else this.callbacks.onAnswer?.(target.mode, target.query);\n }\n\n /** Skip no-op writes so an unchanged search doesn't churn subscribers. */\n private setSearch(target: Target, status: \"loading\" | \"done\"): void {\n const prev = this.store.get().optionSearch;\n if (\n prev &&\n prev.type === target.type &&\n prev.query === target.query &&\n prev.status === status\n ) {\n return;\n }\n this.store.set({ optionSearch: { type: target.type, query: target.query, status } });\n }\n\n private cancel(): void {\n this.current?.abort?.abort();\n // Invalidate any ask in flight even when it has no controller to abort —\n // a synchronous answer's version must not be reused by a later commit.\n this.version++;\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] an optionOverrides entry threw or rejected — treated as no options. 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","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 { CustomFieldsReport, MatchedItem, Product } from \"../shared-types\";\n\n/**\n * Where the product strip links a server-matched item to. The server ships a\n * bare `handle` on purpose: the only shop domain it holds is the internal one,\n * and an absolute link built from it would send a shopper off the storefront\n * the widget is running on. A relative path resolves against the page the\n * widget is on, which for a storefront embed is the shop itself.\n */\nexport const PRODUCT_PATH_PREFIX = \"/products/\";\n\n/**\n * Maps one item the `/suggest` response reported onto the strip's `Product`\n * shape. Pure, so a Tier 3 consumer rendering its own strip from\n * `customFields` gets the same cards the built-in one draws.\n *\n * An item without a `handle` yields a card without a `url`: still activatable,\n * still emitting `onProductSelect`, just nothing for cmd-click to open.\n */\nexport function productFromMatchedItem(item: MatchedItem): Product {\n return {\n id: item.source_id,\n title: item.title,\n ...(item.handle && { url: `${PRODUCT_PATH_PREFIX}${encodeURIComponent(item.handle)}` }),\n imageUrl: item.image_url || null,\n ...(item.price && { price: item.price }),\n ...(item.vendor && { vendor: item.vendor }),\n };\n}\n\n/**\n * The strip's contents for a response's catalog report. `null` — no block on\n * the response — is an empty strip rather than \"keep the previous one\": the\n * previous items answered a previous query, and a strip must never show\n * results belonging to an older one.\n */\nexport function productsFromCustomFields(report: CustomFieldsReport | null | undefined): Product[] {\n return (report?.items.matched ?? []).map(productFromMatchedItem);\n}\n","import type { CustomFieldsReport, Product, ProductsConfig } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { productsFromCustomFields } from \"../utils/matchedItems\";\n\n/**\n * Drives the dropdown's product strip from one of two sources.\n *\n * **The `/suggest` response** (the default): every response either carries the\n * items the query narrowed the catalog to (`custom_fields.items`) or carries\n * nothing, and the strip mirrors that — {@link forResponse} maps the block\n * into the same store write that lands the response's suggestions, so cards\n * and options describe one narrowing and never disagree about which query\n * they answer.\n *\n * **A custom integration** (`opts.products`): the consumer's own search owns\n * the strip and the server's items are not rendered. Deliberately owns **no\n * timer and no AbortController of its own**. Every run is kicked off by\n * {@link FetchController} from inside the one request it was already making,\n * and rides that request's debounce, abort signal and version counter. A\n * second scheduler here would drift out of step with `/suggest` (the\n * 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 /** `opts.showProducts !== false`. Off, neither source writes a card. */\n private isEnabled: () => boolean = () => true,\n ) {}\n\n /**\n * The strip's contents for a `/suggest` response about to be committed, or\n * `undefined` to leave `products` as it is because the strip is not the\n * server's to fill — a custom integration owns it and writes it from\n * {@link run}.\n *\n * Consulted from inside the fetch controller's final write rather than\n * after it, so the cards land in the same notification as the suggestions.\n */\n forResponse(report: CustomFieldsReport | null): Product[] | undefined {\n if (!this.isEnabled()) return [];\n if (this.getConfig()) return undefined;\n return productsFromCustomFields(report);\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 // A hidden strip runs no search: `resync` already emptied it, and the\n // response's own items are dropped by `forResponse`.\n if (!this.isEnabled()) return;\n const config = this.getConfig();\n // No integration: the server's items fill the strip when the response\n // lands (`forResponse`); nothing to do at request time.\n if (!config) return;\n\n if (query.trim().length === 0) {\n this.clear(isCurrent);\n return;\n }\n\n // Three guards, not one: `isCurrent` catches a newer query that already\n // went out, `signal.aborted` catches an integration that resolved instead\n // of rejecting after we cancelled it, and `owns` catches the strip having\n // been taken away from this search while it was in flight — `update()`\n // hid it or swapped the integration, and `resync` already settled what it\n // shows. Neither of the first two notices that: a config change bumps no\n // fetch version and aborts no request.\n const stale = () => signal.aborted || !isCurrent() || !this.owns(config);\n\n try {\n const raw = await config.fetch(query, signal);\n if (stale()) 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 (stale()) 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 if (stale()) return;\n this.clear(isCurrent);\n }\n }\n\n /** Whether `config` is still the integration the strip belongs to. */\n private owns(config: ProductsConfig): boolean {\n return this.isEnabled() && this.getConfig() === config;\n }\n\n /**\n * Re-derive the strip after `update()` changed its source. A swapped or\n * newly set integration clears the strip — the next fetch repopulates it\n * from the new config, since the previous one's cards no longer describe\n * anything. A removed integration hands the strip back to the server's\n * items, which are already in the store, so they show at once. A hidden\n * strip empties.\n */\n resync(): void {\n const products = this.forResponse(this.store.get().customFields);\n this.commit(products ?? []);\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;\nconst WEB_TWO_COL_VISIBLE_ROWS = 3;\n\n/** Option count from which web tries the two-column layout (see planOptionsGrid). */\nconst TWO_COLUMN_MIN = 5;\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 (scroll past four).\n *\n * This is the measurement-free baseline (and the only layout mobile ever\n * uses). On web, `planOptionsGrid` upgrades five-plus options to two columns\n * when the rendered rows prove they fit — the upgrade needs the DOM, so it\n * can't live here.\n */\nexport function computeOptionsGridLayout(count: number, isMobile: boolean): OptionsGridLayout {\n if (isMobile) return layout(1, Math.min(count, MOBILE_VISIBLE_ROWS));\n return layout(1, Math.min(count, WEB_VISIBLE_ROWS));\n}\n\nfunction layout(cols: number, rows: number): OptionsGridLayout {\n return {\n cols,\n rows,\n maxHeight: `calc(${rows} * ${ROW_HEIGHT} + ${RESERVED_BAND})`,\n };\n}\n\nexport interface OptionsGridPlan extends OptionsGridLayout {\n /**\n * Value for `grid-template-columns`. Tracks carry no inner spaces (the\n * keyboard controller counts tracks by splitting on spaces).\n */\n template: string;\n /**\n * Indices of the options on the last visible row of a scrollable web grid —\n * their text gets the fade that signals more options below. Empty on mobile\n * and whenever everything is already visible.\n */\n scrollHintIndices: number[];\n}\n\n/**\n * Whether measuring the rendered rows could change the plan at all. Measuring\n * clones every row and forces a layout, and renders happen per keystroke —\n * so the callers skip it whenever `planOptionsGrid` would ignore the result\n * anyway (mobile, or fewer options than the two-column minimum).\n */\nexport function needsOptionsGridMeasurement(count: number, isMobile: boolean): boolean {\n return !isMobile && count >= TWO_COLUMN_MIN;\n}\n\n/**\n * The full layout decision, measurements included.\n *\n * Web, five-plus options: two columns of three visible rows — but only when\n * every option provably fits on one line in half the grid. Rows fill\n * row-major (even indices left, odd right) and the two columns are always the\n * same width, a plain 50/50 split, so the fit check is that the widest option\n * fits in half of `gridWidth`. Content-sized tracks were tried twice (split in\n * proportion to the column maxima, then left column to its content) and both\n * read as columns of \"weird widths\" (2026-09-04); an even split is what the\n * eye expects. When the widest option doesn't fit in half — or there are no\n * usable measurements (SSR, hidden grid, loading skeletons) — the layout stays\n * one scrollable column.\n *\n * `rowWidths` are single-line pixel widths of the rendered rows (see\n * `measureOptionsGrid`); `gridWidth` is the grid's content width.\n */\nexport function planOptionsGrid(\n count: number,\n isMobile: boolean,\n rowWidths: number[] | null,\n gridWidth: number | null,\n): OptionsGridPlan {\n const twoCol = !isMobile && count >= TWO_COLUMN_MIN && fitsTwoColumns(rowWidths, gridWidth);\n if (!twoCol) {\n const base = computeOptionsGridLayout(count, isMobile);\n return {\n ...base,\n template: optionsGridTemplateColumns(base.cols),\n scrollHintIndices: !isMobile && count > base.rows ? [base.rows - 1] : [],\n };\n }\n const visible = WEB_TWO_COL_VISIBLE_ROWS * 2;\n return {\n ...layout(2, WEB_TWO_COL_VISIBLE_ROWS),\n template: optionsGridTemplateColumns(2),\n scrollHintIndices: count > visible ? [visible - 2, visible - 1] : [],\n };\n}\n\n/**\n * Whether the widest row fits in half the grid — the only fit that matters\n * when the two tracks are equal. Rounded up so a fractional width never\n * squeaks through and wraps.\n */\nfunction fitsTwoColumns(rowWidths: number[] | null, gridWidth: number | null): boolean {\n if (!rowWidths || rowWidths.length === 0 || gridWidth == null || gridWidth <= 0) return false;\n // A zero width means the row wasn't really rendered (hidden grid, jsdom) —\n // no evidence it fits, so don't pretend it does.\n if (rowWidths.some((w) => w <= 0)) return false;\n const widest = Math.ceil(Math.max(...rowWidths));\n return widest * 2 <= gridWidth;\n}\n\n/**\n * Measure the rendered option rows for `planOptionsGrid`: each row's\n * single-line width (a hidden `max-content` + `nowrap` clone, so real classes,\n * fonts, icons, tags and padding are all in the number), and the width the\n * columns have to share — the grid's content box less its column gap.\n * Returns null when there's nothing to measure.\n */\nexport function measureOptionsGrid(\n grid: HTMLElement,\n): { rowWidths: number[]; gridWidth: number } | null {\n if (typeof document === \"undefined\") return null;\n const items = Array.from(grid.querySelectorAll<HTMLElement>(\"[data-aia-option]\"));\n if (items.length === 0) return null;\n const probe = document.createElement(\"div\");\n probe.style.cssText = \"position:absolute;visibility:hidden;left:-9999px;top:0;\";\n for (const item of items) {\n const clone = item.cloneNode(true) as HTMLElement;\n // A clone is a width probe, not a row: no duplicate DOM id, and no second\n // option in the accessibility tree, however briefly.\n clone.removeAttribute(\"id\");\n clone.removeAttribute(\"role\");\n clone.removeAttribute(\"aria-selected\");\n clone.style.whiteSpace = \"nowrap\";\n clone.style.width = \"max-content\";\n probe.appendChild(clone);\n }\n // Inside the grid so the clones inherit the same cascade (custom properties,\n // hashed module classes, fonts) as the rows they stand in for.\n grid.appendChild(probe);\n const rowWidths = Array.from(probe.children).map((c) => (c as HTMLElement).offsetWidth);\n probe.remove();\n const cs = getComputedStyle(grid);\n // The column gap comes off the top along with the padding, so `gridWidth` is\n // the width the two columns actually share. `fitsTwoColumns` checks that\n // twice the widest row fits in it, and the two equal `fr` tracks it plans\n // divide exactly this much space — a gap left in the number would let a\n // widest row sizing between `(gridWidth - gap) / 2` and `gridWidth / 2` pass\n // the fit check and then wrap, which is the one thing the measurement exists\n // to prevent. The options grid pins `--aia-grid-gap` to 0 today\n // (renderSuggestionGrid), so this reads 0; subtracting it anyway keeps the\n // check honest if that token ever changes.\n const gridWidth =\n grid.clientWidth -\n (Number.parseFloat(cs.paddingLeft) || 0) -\n (Number.parseFloat(cs.paddingRight) || 0) -\n (Number.parseFloat(cs.columnGap) || 0);\n return { rowWidths, gridWidth };\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 * while `opts.showProducts` is false, 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 {\n buildDateOptions,\n type DateMonthView,\n parseDate,\n todayView,\n viewOf,\n} from \"../date/dateGrid\";\nimport { selectedRangeFor } from \"../date/dateRange\";\nimport { isCalendarFormat, resolveFormatType } from \"../date/formatType\";\nimport type { CompletedParamState, Suggestion, SuggestionOption } from \"../shared-types\";\nimport type { CoreDerivedState, CoreInputState, CoreOptions } 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/**\n * Identity of the pill the datepicker is open for.\n *\n * `inputs.dateViewMonth` is only honoured while its key still matches, so a\n * month the user paged to is scoped to the pill they paged it for. Every flow\n * that swaps the active pill — a selection, a skip, a landing response,\n * entering or leaving re-edit — changes this string as a side effect, which is\n * why none of them has to reset the view explicitly.\n */\nexport function dateViewKey(inputs: {\n editingParam: CompletedParamState | null;\n editingAnchor: number | null;\n editingIdentified: { type: string; anchor: number } | null;\n actionableSuggestions: Suggestion[];\n}): string {\n if (inputs.editingIdentified) {\n // Keyed on where the span sits, not on its id. The id is re-issued by every\n // response that lands mid-pick, so keying on it would throw away a month\n // the user had paged to each time one arrived — undoing the continuity the\n // re-bind in `AIAutocomplete` exists to preserve.\n const { type, anchor } = inputs.editingIdentified;\n return `identified:${type}@${anchor}`;\n }\n const editing = inputs.editingParam != null && inputs.editingAnchor != null;\n return editing\n ? `edit:${inputs.editingParam?.id}`\n : `pill:${inputs.actionableSuggestions[0]?.type ?? \"\"}`;\n}\n\n/**\n * Which month to open on: the month of the date already in the param being\n * re-edited, otherwise the current month.\n */\n/** The month a `YYYY-MM-DD` falls in, or the current month when there isn't one. */\nfunction defaultViewForIso(iso: string | null): DateMonthView {\n if (!iso) return todayView();\n const [y, m] = iso.split(\"-\").map(Number);\n return { year: y, month: m - 1 };\n}\n\nfunction defaultDateView(editingText: string | undefined): DateMonthView {\n const existing = parseDate(editingText);\n return existing ? viewOf(existing) : todayView();\n}\n\n// Pure derive: raw inputs + opts → segments / actionable / filtered / placeholder / isDropdownOpen.\nexport function deriveAll(inputs: CoreInputState, opts: CoreOptions): CoreDerivedState {\n const segments = deriveSegments(inputs.text, inputs.completedParams, inputs.identifiedParams);\n const actionableSuggestions = inputs.suggestions.filter((s) => s.type !== \"placeholder\");\n const activeSuggestion = actionableSuggestions[0] as Suggestion | 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 // No consumer code runs from here. An `optionOverrides` entry answers\n // through the store — `OptionSourceController` writes its result into the\n // suggestion's own `options` — so the derive layer only ever reads state.\n const baseOptions = activeSuggestion?.options ?? [];\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 let optionQuery: string;\n let sourceOptions: SuggestionOption[];\n let sourceType: string | undefined;\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 optionQuery = editQuery.trim();\n sourceOptions = inputs.editingParam.options;\n sourceType = inputs.editingParam.suggestionType;\n filteredOptions = filterOptions(inputs.editingParam.options, editQuery);\n } else {\n optionQuery = filterQuery.trim();\n sourceOptions = baseOptions;\n sourceType = activeSuggestion?.type;\n filteredOptions = filterOptions(baseOptions, filterQuery);\n }\n // A consumer source that has answered for exactly this phrase is shown\n // as-is. Its answer was produced FOR the phrase, and re-filtering it by\n // substring would drop a fuzzy or synonym match (\"St Kilda\" for \"mel\").\n // Any other phrase — the user typed on since — filters the last answer\n // locally until the next one lands, so the list never sits stale for the\n // length of a debounce window.\n const search = inputs.optionSearch;\n const searchMatchesPill = search != null && sourceType != null && search.type === sourceType;\n if (searchMatchesPill && search.status === \"done\" && search.query === optionQuery) {\n filteredOptions = sourceOptions;\n }\n const isSearchingOptions = searchMatchesPill && search.status === \"loading\";\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 // A date suggestion answers with a calendar, so its options — which the\n // server still sends — are replaced wholesale by the visible month's cells.\n // Re-edit asks the same question of the param being edited, via the\n // suggestion type it was completed from, so tapping a bold date re-opens the\n // calendar instead of the cached options it would otherwise filter.\n // An identified-date edit outranks whatever pill is active: the user tapped a\n // specific span in their own text, so that span is what the calendar answers.\n const identifiedEdit = inputs.editingIdentified;\n const formatSource = inEditMode\n ? // The param's cached options are read too, so a re-edited answer whose\n // options were dates re-opens as a calendar rather than as a list.\n inputs.editingParam && {\n type: inputs.editingParam.suggestionType,\n options: inputs.editingParam.options,\n }\n : activeSuggestion;\n // An identified span resolved its own format when the picker opened, and it\n // is carried on the edit rather than re-derived here: it depends on the\n // span's own text (a `travel_date` the user wrote as a span is a range), and\n // only that flow has ever seen it.\n let activeFormatType = identifiedEdit ? identifiedEdit.format : resolveFormatType(formatSource);\n // The span an already-answered param holds, which is both what re-opens the\n // calendar on the right month and — for a param whose type and cached options\n // only said \"date\" — what makes it a range picker at all. Narrowed to params\n // that already resolved to a calendar, so an ordinary option that merely\n // reads like a span (\"September 11 - October 13\" picked from a plain list)\n // still re-opens as the list it came from.\n const editingRange =\n inEditMode && isCalendarFormat(activeFormatType) ? selectedRangeFor(inputs.editingParam) : null;\n if (editingRange) activeFormatType = \"date-range\";\n // Note this runs AFTER the `showNonTappableOptions` filter above and replaces\n // its result wholesale. That is deliberate: a calendar's padding cells are\n // non-tappable but structural — they are what puts the 1st under the right\n // weekday — so honouring that option here would collapse the grid's alignment.\n // The option is about hiding options the user can't pick, and padding is not\n // an option. Pinned by a test so a future tidy-up doesn't \"fix\" it.\n let dateView: DateMonthView | null = null;\n let dateRangeStart: string | null = null;\n if (isCalendarFormat(activeFormatType)) {\n const key = dateViewKey({ ...inputs, actionableSuggestions });\n const paged = inputs.dateViewMonth;\n dateView =\n paged && paged.key === key\n ? { year: paged.year, month: paged.month }\n : identifiedEdit\n ? // The month the span's own text was read as. Null `iso` means the\n // text couldn't be read — the calendar opens on the current month\n // with nothing marked, which costs a page-turn, not correctness.\n defaultViewForIso(identifiedEdit.iso)\n : // A range opens on the month its start falls in: the end is the\n // half being changed more often, but the start is what anchors the\n // span, and paging forward from it is the direction the calendar\n // already reads.\n editingRange\n ? defaultViewForIso(editingRange.start)\n : defaultDateView(inputs.editingParam?.text);\n filteredOptions = buildDateOptions(dateView);\n // Scoped to the pill it was picked for, exactly like the paged month: a\n // half-finished pick belongs to one parameter and must not follow the user\n // to the next one. Scoping is the display rule only — a stale pick is also\n // cleared outright (see `pendingRangeStart`), because a key can come back.\n const pending = inputs.pendingRangeStart;\n if (activeFormatType === \"date-range\" && pending && pending.key === key) {\n dateRangeStart = pending.iso;\n }\n }\n\n // An active pill with a genuinely empty option set (the server or the\n // consumer's override returned no options at all) — distinct from options\n // that were filtered away by the typed query. Keeps the dropdown open so the\n // pill chip stays visible. An override that answered empty for a typed\n // phrase counts as this too: the pill then has nothing to offer for what was\n // typed, and the chip staying up is what tells the user which parameter the\n // text they keep typing is going to.\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 activePillHasNoOptions =\n activeSuggestion != null && baseOptions.filter(countsAsOption).length === 0;\n }\n\n const isDropdownOpen = computeDropdownVisibility(\n {\n // An identified-date edit gates like a re-edit: the caret is parked inside\n // the user's own text, so the caret-at-end rule would close the panel the\n // moment it opened.\n inEditMode: inEditMode || identifiedEdit != null,\n filteredOptionsLength: filteredOptions.length,\n isFocused: inputs.isFocused,\n text: inputs.text,\n caretOffset: inputs.caretOffset,\n // A pending consumer search holds the panel open the way a pending\n // suggest request does: the skeleton it shows needs somewhere to be.\n isLoading: inputs.isLoading || isSearchingOptions,\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 activeFormatType,\n dateView,\n dateRangeStart,\n placeholderText,\n optionQuery,\n isSearchingOptions,\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 icon: match.icon,\n icon_svg: match.icon_svg,\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 // The promoted param ends here in the text — the anchor for keeping\n // completedParams in text order below. The caret itself goes to the END of\n // the input (like ReEditManager.selectOption): committing a re-edited answer\n // mid-text must not strand the caret there, or the caret-at-end gate in the\n // dropdown derive keeps the options box closed for the follow-up suggestion.\n const afterParam = paramEnd < text.length && text[paramEnd] === \" \" ? paramEnd + 1 : paramEnd;\n const caretPos = text.length;\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 icon: match.icon,\n icon_svg: match.icon_svg,\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 >= afterParam) {\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 // Entering a re-edit ends any date pick that was open, the mirror of the\n // `editingParam: null` that starting a pick does. Both set at once is not\n // merely untidy: the derive layer lets an identified pick outrank\n // `editingParam`, so the panel would keep showing the old span's calendar\n // over the param just tapped — and `selectOption` routes an identified\n // pick first, so committing from it would splice text at the PREVIOUS\n // span's offsets and corrupt a part of the query the user never touched.\n editingIdentified: null,\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.promoteIfExact();\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.promoteIfExact();\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 goes to the END of the input, not just past the replacement.\n // Changing an answer immediately re-fetches the next suggestion (see\n // fetchNow below), and the dropdown only opens with the caret at the end\n // of the text — parked mid-text it would close the options box the user\n // is in the middle of using.\n const caretPos = newText.length;\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 icon: option.icon,\n icon_svg: option.icon_svg,\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 at the end of the input 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 /**\n * Promote the typed replacement to a bold param when it exactly matches one\n * of the edited param's options. Runs after every edit keystroke, and again\n * when a consumer override answers for the re-edit — the user may have\n * finished typing the option before the answer arrived.\n */\n promoteIfExact(): void {\n const s = this.deps.store.get();\n if (!s.editingParam || s.editingAnchor == null || s.editingTail == null) return;\n const result = tryPromoteExactMatch({\n mode: \"edit\",\n text: s.text,\n completedParams: s.completedParams,\n editingParam: s.editingParam,\n editingAnchor: s.editingAnchor,\n editingTail: s.editingTail,\n });\n if (!result) return;\n this.deps.store.set(result.patch);\n this.deps.scheduleSetCursor(result.caretPos);\n }\n}\n","/**\n * Types the starting-state placeholder into the input one character at a\n * time instead of popping it in whole.\n *\n * The reference (the RB2B support box, 2026-08-19): ~33 ms per character, no\n * caret, a brief hold on word boundaries; when the phrase changes the old\n * one fades out (~120 ms), the field sits empty for a beat (~50 ms), then the\n * new phrase types in from nothing. Characters appear at full opacity — the\n * only fade is the outgoing phrase.\n *\n * Shared by every package: vanilla, React and Angular all render the\n * placeholder through `renderEditableContent`, which hands the target text\n * to `syncPlaceholder` below. The typed prefix lives in `data-placeholder`\n * (the stylesheets render it via `::before`), so nothing enters the\n * contentEditable's DOM and the core's notion of the placeholder — which\n * drives Tab-to-accept and prefix-typing suppression — stays the full phrase\n * at all times.\n */\n\n/** Delay between characters. */\nexport const PLACEHOLDER_TYPE_MS = 33;\n/** Extra hold after a word boundary (space / hyphen). */\nexport const PLACEHOLDER_WORD_PAUSE_MS = 70;\n/** Fade-out of the outgoing phrase — must match `--aia-placeholder-fade` in the stylesheets. */\nexport const PLACEHOLDER_FADE_OUT_MS = 120;\n/** Empty beat between the fade-out and the next phrase typing in. */\nexport const PLACEHOLDER_SWAP_GAP_MS = 50;\n\n/** Set on the input while the outgoing phrase fades; the stylesheets fade `::before` on it. */\nexport const PLACEHOLDER_LEAVING_ATTR = \"data-aia-placeholder-leaving\";\n\ninterface TypewriterState {\n /** The phrase being typed (or shown). */\n target: string;\n /** How many characters of `target` are on screen. */\n shown: number;\n timer: ReturnType<typeof setTimeout> | null;\n}\n\nconst states = new WeakMap<HTMLElement, TypewriterState>();\n\nfunction clearTimer(state: TypewriterState): void {\n if (state.timer !== null) {\n clearTimeout(state.timer);\n state.timer = null;\n }\n}\n\n/**\n * Whether the host has switched motion off — the SDK's own\n * `animations: false` (`data-animations=\"off\"` on the `.magicx-aia` root) or\n * the OS-level reduced-motion preference. Either way the placeholder appears\n * whole, exactly as it did before the typewriter existed.\n */\nfunction motionDisabled(input: HTMLElement): boolean {\n if (input.closest('[data-animations=\"off\"]')) return true;\n const view = input.ownerDocument?.defaultView;\n return Boolean(view?.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches);\n}\n\nfunction show(input: HTMLElement, state: TypewriterState, count: number): void {\n state.shown = count;\n input.dataset.placeholder = state.target.slice(0, count);\n}\n\nfunction typeNext(input: HTMLElement, state: TypewriterState): void {\n state.timer = null;\n // The element was torn down mid-phrase (unmount / destroy): stop quietly.\n if (!input.isConnected) return;\n if (state.shown >= state.target.length) return;\n show(input, state, state.shown + 1);\n if (state.shown >= state.target.length) return;\n const justTyped = state.target[state.shown - 1];\n const pause = justTyped === \" \" || justTyped === \"-\" ? PLACEHOLDER_WORD_PAUSE_MS : 0;\n state.timer = setTimeout(() => typeNext(input, state), PLACEHOLDER_TYPE_MS + pause);\n}\n\nfunction startTyping(input: HTMLElement, state: TypewriterState): void {\n show(input, state, 0);\n state.timer = setTimeout(() => typeNext(input, state), PLACEHOLDER_TYPE_MS);\n}\n\n/**\n * Bring the input's rendered placeholder in line with `text`.\n *\n * - `\"\"` (no placeholder, or the input has content): remove it at once. The\n * user's own typing must never wait on an animation.\n * - A phrase the input isn't showing yet: type it in. If a different phrase\n * is currently on screen, fade that one out first, then type the new one\n * from nothing.\n * - The phrase already targeted: no-op, so unrelated re-renders never restart\n * or stutter a phrase mid-type.\n *\n * Idempotent and safe to call on every render.\n */\nexport function syncPlaceholder(input: HTMLElement, text: string): void {\n const state = states.get(input);\n\n if (text === \"\") {\n if (state) {\n clearTimer(state);\n states.delete(input);\n }\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n delete input.dataset.placeholder;\n return;\n }\n\n if (state?.target === text) return;\n\n if (motionDisabled(input)) {\n if (state) clearTimer(state);\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n states.set(input, { target: text, shown: text.length, timer: null });\n input.dataset.placeholder = text;\n return;\n }\n\n const next: TypewriterState = { target: text, shown: 0, timer: null };\n const outgoing = state && state.shown > 0 ? state : null;\n if (state) clearTimer(state);\n states.set(input, next);\n\n if (outgoing) {\n // Fade the old phrase out where it stands, then clear and type the new one.\n input.setAttribute(PLACEHOLDER_LEAVING_ATTR, \"\");\n next.timer = setTimeout(() => {\n next.timer = null;\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n if (!input.isConnected) return;\n startTyping(input, next);\n }, PLACEHOLDER_FADE_OUT_MS + PLACEHOLDER_SWAP_GAP_MS);\n return;\n }\n\n input.removeAttribute(PLACEHOLDER_LEAVING_ATTR);\n startTyping(input, next);\n}\n\n/** Test / teardown hook: drop any pending timer for this input. */\nexport function cancelPlaceholderTypewriter(input: HTMLElement): void {\n const state = states.get(input);\n if (!state) return;\n clearTimer(state);\n states.delete(input);\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","import { selectedIsoFromText } from \"./dateGrid\";\nimport { type DateRange, orderedRange, selectedRangeFor } from \"./dateRange\";\nimport { type FormatType, isCalendarFormat } from \"./formatType\";\n\n/**\n * What a calendar paints as answered.\n *\n * Three fields rather than one because they answer three different questions,\n * and a range picker shows two of them at once: what is already committed\n * (`selectedIso` for a day, `selectedRange` for a span) and what the user is\n * half-way through picking (`rangeStart`). {@link visibleDateRange} folds the\n * last two into the span actually drawn.\n */\nexport interface DateSelection {\n /** `YYYY-MM-DD` a re-edited single-date param already holds, or null. */\n selectedIso: string | null;\n /** Both ends of a re-edited range param, or null. */\n selectedRange: DateRange | null;\n /** The first end of a range being picked right now, or null. */\n rangeStart: string | null;\n}\n\nconst NOTHING_SELECTED: DateSelection = {\n selectedIso: null,\n selectedRange: null,\n rangeStart: null,\n};\n\n/**\n * The calendar's selection, read off core state.\n *\n * Every package's dropdown needs exactly this, and computing it four times\n * over — vanilla Tier 1, vanilla Tier 2, the React hook, the Angular\n * controller — is how the four copies would come to disagree about which cells\n * a re-edited range fills. Typed structurally so each of them can hand it the\n * state object it already has.\n */\nexport function dateSelectionFor(state: {\n activeFormatType: FormatType;\n editingIdentified: { iso: string | null; isoEnd: string | null } | null;\n editingParam: { text: string; metadata?: Record<string, unknown> } | null;\n dateRangeStart: string | null;\n}): DateSelection {\n // Nothing below is meaningful for a pill answered from a list, and skipping\n // it keeps a plain option whose text happens to read like a span (\"Q1 - Q2\")\n // from being parsed as one on every render.\n if (!isCalendarFormat(state.activeFormatType)) return NOTHING_SELECTED;\n\n const identified = state.editingIdentified;\n if (identified) {\n const { iso, isoEnd } = identified;\n return {\n selectedIso: isoEnd ? null : iso,\n selectedRange: iso && isoEnd ? orderedRange(iso, isoEnd) : null,\n rangeStart: state.dateRangeStart,\n };\n }\n\n const selectedRange = selectedRangeFor(state.editingParam);\n return {\n // A committed span and a committed day are mutually exclusive readings of\n // the same text, and the span is the more specific one — so a range never\n // also reports its start as a single selected date.\n selectedIso: selectedRange ? null : selectedIsoFromText(state.editingParam?.text),\n selectedRange,\n rangeStart: state.dateRangeStart,\n };\n}\n\n/**\n * The span the calendar should paint right now.\n *\n * While a first end is pending, that is the live span between it and whatever\n * the user is pointing at — the preview every range picker shows, which is also\n * what tells them the first tap registered. Otherwise it is the answer already\n * committed, if there is one.\n */\nexport function visibleDateRange(args: {\n pendingStart: string | null;\n highlightedIso: string | null;\n selected: DateRange | null;\n}): DateRange | null {\n if (args.pendingStart) {\n return orderedRange(args.pendingStart, args.highlightedIso ?? args.pendingStart);\n }\n return args.selected;\n}\n\n/**\n * How one cell renders against the current selection: filled at either end of\n * the span, banded in between.\n *\n * Shared by all three packages' calendars so a range can't be painted three\n * subtly different ways. `selectedIso` is the single-date answer, which is\n * mutually exclusive with `range` in practice but costs nothing to fold in\n * here — it keeps every cell's appearance one function call at every call site.\n */\nexport function dateCellMarks(\n iso: string | null,\n args: { selectedIso: string | null; range: DateRange | null },\n): { selected: boolean; inRange: boolean } {\n if (iso == null) return { selected: false, inRange: false };\n const { selectedIso, range } = args;\n const selected =\n iso === selectedIso || (range != null && (iso === range.start || iso === range.end));\n const inRange = range != null && iso > range.start && iso < range.end;\n return { selected, inRange };\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 { DateMonthView } from \"../date/dateGrid\";\nimport { cellDay, cellIso, isoDate, monthLabel, WEEKDAY_LABELS } from \"../date/dateGrid\";\nimport { type DateSelection, dateCellMarks, visibleDateRange } from \"../date/dateSelection\";\nimport type { SuggestionOption } from \"../shared-types\";\nimport { reconcileList } from \"./reconcileList\";\n\nexport interface DateGridCallbacks {\n onSelect: (option: SuggestionOption) => void;\n onHighlight: (index: number) => void;\n onPreviousMonth: () => void;\n onNextMonth: () => void;\n}\n\n/**\n * Renders the datepicker into `parent`, in place of the options grid.\n *\n * The cells ARE `filteredOptions` (see `buildDateOptions`), so they carry the\n * same `${listboxId}-option-${i}` ids, `role=\"option\"` and `aria-selected` that\n * the option grid emits. That is load-bearing, not cosmetic: the keyboard\n * controller navigates by index into `filteredOptions` and commits by clicking\n * `#${listboxId}-option-${i}`, and `getEffectiveColumns` measures the live\n * `grid-template-columns` of option-0's parent — which is why the 7 columns are\n * set on the cell container itself rather than on an ancestor.\n *\n * Creates the picker on first use, reuses it after, and removes it when there\n * is nothing to show.\n */\nexport function renderDateGrid(\n parent: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n listboxId: string,\n view: DateMonthView | null,\n selection: DateSelection,\n callbacks: DateGridCallbacks,\n): void {\n let picker = parent.querySelector<HTMLElement>(\".magicx-aia-datepicker\");\n if (!view || options.length === 0) {\n picker?.remove();\n return;\n }\n if (!picker) {\n picker = buildPicker();\n parent.appendChild(picker);\n }\n\n const label = picker.querySelector<HTMLElement>(\".magicx-aia-datepicker-month\");\n if (label) label.textContent = monthLabel(view);\n\n // Reassigned every render so a reused picker never holds a stale closure —\n // same rule the option grid's handlers follow.\n const prev = picker.querySelector<HTMLButtonElement>(\"[data-aia-date-prev]\");\n const next = picker.querySelector<HTMLButtonElement>(\"[data-aia-date-next]\");\n if (prev) prev.onclick = () => callbacks.onPreviousMonth();\n if (next) next.onclick = () => callbacks.onNextMonth();\n\n const grid = picker.querySelector<HTMLElement>(\".magicx-aia-datepicker-grid\");\n if (grid) renderCells(grid, options, activeIndex, listboxId, selection, callbacks);\n}\n\nfunction renderCells(\n grid: HTMLElement,\n options: SuggestionOption[],\n activeIndex: number,\n listboxId: string,\n selection: DateSelection,\n callbacks: DateGridCallbacks,\n): void {\n const today = isoDate(new Date());\n // While a start is pending, the span runs to whatever cell is highlighted —\n // the hover/arrow preview that tells the user the first tap registered.\n const range = visibleDateRange({\n pendingStart: selection.rangeStart,\n highlightedIso: cellIso(options[activeIndex]),\n selected: selection.selectedRange,\n });\n\n reconcileList(grid, options, {\n // Padding cells have no date of their own, so they key off position. A\n // month change re-keys every real cell anyway, which is what makes the\n // whole grid swap over in one pass.\n keyOf: (option, i) => cellIso(option) ?? `pad-${i}`,\n create: (option) => buildCell(option),\n update: (el, option, i) => {\n const iso = cellIso(option);\n const marks = dateCellMarks(iso, { selectedIso: selection.selectedIso, range });\n const isHighlighted = i === activeIndex && option.is_tappable;\n el.id = `${listboxId}-option-${i}`;\n el.dataset.aiaIndex = String(i);\n el.setAttribute(\"aria-selected\", String(isHighlighted));\n // A day already gone by is de-emphasized, not disabled: it renders\n // dimmed but stays every bit as pickable as the rest of the month.\n el.classList.toggle(\"magicx-aia-date-cell--past\", iso != null && iso < today);\n el.classList.toggle(\"magicx-aia-date-cell--highlighted\", isHighlighted);\n el.classList.toggle(\"magicx-aia-date-cell--today\", iso != null && iso === today);\n el.classList.toggle(\"magicx-aia-date-cell--selected\", marks.selected);\n // Between the two ends of a span — banded rather than filled, so the ends\n // stay the two cells that read as picked.\n el.classList.toggle(\"magicx-aia-date-cell--in-range\", marks.inRange);\n if (option.is_tappable) {\n el.onclick = () => {\n el.classList.add(\"magicx-aia-date-cell--pressed\");\n callbacks.onSelect(option);\n setTimeout(() => el.classList.remove(\"magicx-aia-date-cell--pressed\"), 170);\n };\n el.onmouseenter = () => {\n const idx = Number.parseInt(el.dataset.aiaIndex ?? \"-1\", 10);\n if (idx >= 0) callbacks.onHighlight(idx);\n };\n } else {\n el.onclick = null;\n el.onmouseenter = null;\n }\n },\n });\n}\n\nfunction buildCell(option: SuggestionOption): HTMLElement {\n const cell = document.createElement(\"div\");\n cell.setAttribute(\"role\", \"option\");\n cell.setAttribute(\"data-aia-option\", \"\");\n cell.setAttribute(\"data-aia-date-cell\", \"\");\n\n const day = cellDay(option);\n if (day == null) {\n // Padding for the days before the 1st / after the last. Non-tappable, so\n // arrow navigation skips it; hidden from AT so the month reads as its own\n // days and nothing else.\n cell.className = \"magicx-aia-date-cell magicx-aia-date-cell--blank\";\n cell.setAttribute(\"aria-hidden\", \"true\");\n cell.tabIndex = -1;\n return cell;\n }\n\n cell.className = \"magicx-aia-date-cell magicx-aia-date-cell--day\";\n cell.tabIndex = 0;\n // The cell paints the day number; `option.text` is the full committed value\n // (\"March 23 2026\") and is what AT should announce, since that is what\n // selecting it puts in the input.\n cell.setAttribute(\"aria-label\", option.text);\n const num = document.createElement(\"span\");\n num.className = \"magicx-aia-date-cell-number\";\n num.textContent = String(day);\n cell.appendChild(num);\n return cell;\n}\n\nfunction buildPicker(): HTMLElement {\n const picker = document.createElement(\"div\");\n picker.className = \"magicx-aia-datepicker\";\n picker.setAttribute(\"data-aia-datepicker\", \"\");\n\n const header = document.createElement(\"div\");\n header.className = \"magicx-aia-datepicker-header\";\n\n const month = document.createElement(\"span\");\n month.className = \"magicx-aia-datepicker-month\";\n month.setAttribute(\"data-aia-date-month\", \"\");\n month.setAttribute(\"aria-live\", \"polite\");\n\n header.append(\n buildNavButton(\"prev\", \"Previous month\", \"‹\"),\n month,\n buildNavButton(\"next\", \"Next month\", \"›\"),\n );\n\n const weekdays = document.createElement(\"div\");\n weekdays.className = \"magicx-aia-datepicker-weekdays\";\n // Decorative: the cells already announce their full date, so repeating the\n // column letters to AT adds noise. Duplicate \"T\"/\"S\" letters would be\n // meaningless read aloud anyway.\n weekdays.setAttribute(\"aria-hidden\", \"true\");\n for (const label of WEEKDAY_LABELS) {\n const cell = document.createElement(\"span\");\n cell.className = \"magicx-aia-datepicker-weekday\";\n cell.textContent = label;\n weekdays.appendChild(cell);\n }\n\n const grid = document.createElement(\"div\");\n grid.className = \"magicx-aia-datepicker-grid\";\n grid.setAttribute(\"data-aia-date-grid\", \"\");\n\n picker.append(header, weekdays, grid);\n return picker;\n}\n\nfunction buildNavButton(\n direction: \"prev\" | \"next\",\n label: string,\n glyph: string,\n): HTMLButtonElement {\n const btn = document.createElement(\"button\");\n btn.type = \"button\";\n // Out of the tab order for the same reason the skip button is: Tab is\n // already bound to walking the dropdown's options.\n btn.tabIndex = -1;\n btn.className = \"magicx-aia-datepicker-nav\";\n btn.setAttribute(`data-aia-date-${direction}`, \"\");\n btn.setAttribute(\"aria-label\", label);\n btn.textContent = glyph;\n // Keeps the input focused, which the dropdown's visibility gate depends on.\n btn.addEventListener(\"mousedown\", (e) => e.preventDefault());\n return btn;\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 // No `url`, no `href` — the card is still activatable via the handlers\n // below, and a bare <a> is what \"nothing to open elsewhere\" looks like.\n if (product.url) 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","/**\n * Entrance choreography for the dropdown's option rows.\n *\n * When a new set of options lands, each row fades in while rising into place,\n * and the rows start one after another from the input's side outward — the\n * row nearest the input first. The reference (the RB2B support box's\n * suggestion bubbles, 2026-08-19): opacity 0→1 over ~150 ms, a ~16 px rise\n * easing out over ~280 ms, consecutive rows ~80–110 ms apart.\n *\n * The per-row motion is CSS (the option rule in each package's stylesheet —\n * keep the three copies identical; the parity test compares them). This module\n * owns the *timing numbers* and the per-row delay, so vanilla, React and\n * Angular can't drift onto three different cascades. `OPTION_ENTER_RISE_MS` and\n * `OPTION_ENTER_FADE_MS` must match the durations declared in those rules.\n */\n\n/** Gap between consecutive rows starting their entrance. */\nexport const OPTION_ENTER_STAGGER_MS = 80;\n/** Duration of a row's opacity fade — mirrors the stylesheets' entrance rule. */\nexport const OPTION_ENTER_FADE_MS = 150;\n/** Duration of a row's rise into place — mirrors the stylesheets' entrance rule. */\nexport const OPTION_ENTER_RISE_MS = 280;\n/** Distance a row rises from, in CSS px — mirrors the stylesheets' entrance rule. */\nexport const OPTION_ENTER_RISE_PX = 16;\n\n/** Inline custom property each option carries with its own start delay. */\nexport const OPTION_ENTER_DELAY_VAR = \"--aia-option-enter-delay\";\n\n/**\n * Start delay (ms) for the option at `index` in a grid of `count` options laid\n * out in `cols` columns, when the dropdown sits `optionsPosition` the input.\n *\n * Rows, not cells, are the unit: the cells of one row share a start so a\n * two-column layout reads as rows appearing, not as a zig-zag. \"below\" starts\n * at the top row (the one against the input); \"above\" starts at the bottom row\n * for the same reason — the stack is reversed there and the last row is the\n * one nearest the input.\n */\nexport function optionEnterDelayMs(\n index: number,\n cols: number,\n count: number,\n optionsPosition: \"above\" | \"below\" = \"below\",\n): number {\n const safeCols = Math.max(1, Math.floor(cols));\n const row = Math.floor(Math.max(0, index) / safeCols);\n if (optionsPosition !== \"above\") return row * OPTION_ENTER_STAGGER_MS;\n const rows = Math.max(1, Math.ceil(Math.max(0, count) / safeCols));\n return Math.max(0, rows - 1 - row) * OPTION_ENTER_STAGGER_MS;\n}\n\n/**\n * How long after the options render until the last row has settled — the\n * moment anything that measures the grid (the scroll arrow) can trust its\n * geometry again, since a row mid-rise still extends the scrollable area.\n */\nexport function optionsEntranceDurationMs(count: number, cols: number): number {\n const safeCols = Math.max(1, Math.floor(cols));\n const rows = Math.max(1, Math.ceil(Math.max(0, count) / safeCols));\n return (rows - 1) * OPTION_ENTER_STAGGER_MS + OPTION_ENTER_RISE_MS;\n}\n","const SVG_NS = \"http://www.w3.org/2000/svg\";\n\n/**\n * The only elements an option icon may contain: plain vector drawing. No\n * `<script>`, `<foreignObject>`, `<use>` (external references), `<image>`,\n * `<a>`, `<style>`, `<text>`, animation or filter elements.\n */\nconst ALLOWED_ELEMENTS = new Set([\n \"svg\",\n \"g\",\n \"path\",\n \"circle\",\n \"ellipse\",\n \"line\",\n \"polyline\",\n \"polygon\",\n \"rect\",\n]);\n\n/**\n * Attributes never kept: event handlers, anything that references a URL or\n * another node (`href`, `xlink:href`, and `xml:base`, which would rebase any\n * relative reference under it), inline `style` (which can load resources\n * through `url()`), any presentation attribute — `fill`, `stroke`,\n * `clip-path`, `mask`, `filter` — whose `url()` points outside the document,\n * and `id` / `name`, so an icon can never shadow a page element (the\n * keyboard controller finds option rows by id).\n */\nfunction isDisallowedAttribute(name: string, value: string): boolean {\n const lower = name.toLowerCase();\n return (\n lower.startsWith(\"on\") ||\n lower.includes(\"href\") ||\n lower === \"style\" ||\n lower === \"xml:base\" ||\n lower === \"id\" ||\n lower === \"name\" ||\n /^\\s*javascript:/i.test(value) ||\n /url\\s*\\(\\s*['\"]?\\s*[^#'\"\\s)]/i.test(value)\n );\n}\n\n/**\n * Paint attributes an icon carries are also written onto the element's own\n * `style`. A presentation attribute has no specificity, so a page's\n * `svg { fill: currentColor }` or `path { stroke: red }` — a type selector —\n * would repaint the icon; an inline declaration outranks every selector. Only\n * a value that is plainly a paint is copied — a keyword, a hex or\n * `rgb()` / `hsl()` colour, a number — never one carrying a `url()`, which is\n * what inline `style` is stripped to keep out in the first place.\n */\nconst HARDENED_ATTRIBUTES = [\n \"fill\",\n \"stroke\",\n \"stroke-width\",\n \"stroke-linecap\",\n \"stroke-linejoin\",\n \"fill-rule\",\n \"clip-rule\",\n \"opacity\",\n \"fill-opacity\",\n \"stroke-opacity\",\n];\nconst PLAIN_PAINT = /^[a-zA-Z0-9#.,%\\s()-]+$/;\n\nfunction hardenPaint(el: Element): void {\n const declarations: string[] = [];\n for (const name of HARDENED_ATTRIBUTES) {\n const value = el.getAttribute(name);\n if (value === null || !PLAIN_PAINT.test(value) || /url\\s*\\(/i.test(value)) continue;\n declarations.push(`${name}: ${value.trim()}`);\n }\n if (declarations.length > 0) el.setAttribute(\"style\", declarations.join(\"; \"));\n}\n\nfunction scrub(el: Element): void {\n for (const attr of Array.from(el.attributes)) {\n if (isDisallowedAttribute(attr.name, attr.value)) el.removeAttribute(attr.name);\n }\n hardenPaint(el);\n for (const child of Array.from(el.childNodes)) {\n if (child.nodeType === Node.ELEMENT_NODE) {\n const childEl = child as Element;\n if (!ALLOWED_ELEMENTS.has(childEl.localName) || childEl.namespaceURI !== SVG_NS) {\n childEl.remove();\n } else {\n scrub(childEl);\n }\n } else {\n // Text (indentation between elements, comments): nothing in the\n // allowlist renders text, and a stray text node inside an editable\n // chip would count as characters for the caret. Drop them all.\n child.remove();\n }\n }\n}\n\n/**\n * Reduces an option's `icon_svg` markup to plain vector drawing before it is\n * inserted into the dropdown. The icons come from the server's own curated\n * set, but markup landing in a consumer's page is a trust boundary, so every\n * icon is parsed and filtered through an allowlist rather than inserted raw:\n * only the elements in `ALLOWED_ELEMENTS` survive, stripped of any attribute\n * that could run script or fetch a resource. The root is marked\n * `aria-hidden` / non-focusable — the option's text is its accessible name.\n *\n * Returns the serialized `<svg>` to insert, or `null` when the markup is not\n * a well-formed SVG document (or the environment has no `DOMParser`, as in\n * server rendering), in which case the option renders without an icon.\n */\nexport function sanitizeOptionIconSvg(markup: string): string | null {\n if (typeof DOMParser === \"undefined\" || typeof XMLSerializer === \"undefined\") return null;\n const doc = new DOMParser().parseFromString(markup, \"image/svg+xml\");\n const root = doc.documentElement;\n if (!root || root.localName !== \"svg\" || root.namespaceURI !== SVG_NS) return null;\n if (doc.getElementsByTagName(\"parsererror\").length > 0) return null;\n scrub(root);\n root.setAttribute(\"aria-hidden\", \"true\");\n root.setAttribute(\"focusable\", \"false\");\n return new XMLSerializer().serializeToString(root);\n}\n\n/**\n * The text an option row shows: its `text`, alone. `icon` is the icon's name\n * (`\"apple\"`), an identifier for `data-aia-icon` and consumer CSS, never\n * display text — so it is not prefixed to the option, whether `icon_svg`\n * came with it, failed to draw, or was switched off. A row whose icon cannot\n * draw shows the text by itself rather than the icon's name.\n */\nexport function optionLabel(option: { text: string; icon?: string; icon_svg?: string }): string {\n return option.text;\n}\n","import { OPTION_ENTER_DELAY_VAR, optionEnterDelayMs } from \"../derive/optionsEntrance\";\nimport {\n isOptionsGridMobileViewport,\n measureOptionsGrid,\n needsOptionsGridMeasurement,\n planOptionsGrid,\n} from \"../derive/optionsGridLayout\";\nimport type { SuggestionOption } from \"../shared-types\";\nimport { optionLabel, sanitizeOptionIconSvg } from \"./optionIcon\";\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. Runs after the\n * rows exist so `planOptionsGrid` can measure them (the two-column upgrade\n * needs proof that every row fits on one line). Loading skeletons are never\n * measured — their widths say nothing about the real options — so a loading\n * grid keeps the one-column baseline and no scroll hint. React and Angular\n * apply the same `planOptionsGrid` result to their own grids.\n */\nfunction applyGridLayout(grid: HTMLElement, count: number, loading: boolean): number {\n const isMobile = isOptionsGridMobileViewport();\n const measured =\n !loading && needsOptionsGridMeasurement(count, isMobile) ? measureOptionsGrid(grid) : null;\n const plan = planOptionsGrid(\n count,\n isMobile,\n measured?.rowWidths ?? null,\n measured?.gridWidth ?? null,\n );\n grid.style.gridTemplateColumns = plan.template;\n grid.style.setProperty(\"--aia-grid-max-height\", plan.maxHeight);\n grid.querySelectorAll<HTMLElement>(\"[data-aia-option]\").forEach((el, i) => {\n el.classList.toggle(\n \"magicx-aia-option--scroll-hint\",\n !loading && plan.scrollHintIndices.includes(i),\n );\n });\n // The entrance stagger runs outward from the input across the *rendered*\n // columns, so it needs the count the plan actually settled on — which the\n // two-column upgrade can only decide after measuring the rows.\n return plan.cols;\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 optionsPosition: \"above\" | \"below\" = \"below\",\n showIcons = true,\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 // `data-aia-group` identifies the suggestion whose options are on screen.\n // Stamped BEFORE the rows render: the scroll arrow reads it to tell \"a new\n // set of options is rising in\" from \"the same set re-rendered\".\n const isNewGroup = grid.dataset.aiaGroup !== groupKey;\n grid.dataset.aiaGroup = groupKey;\n renderOptions(grid, options, activeIndex, onSelect, onHighlight, listboxId, loading, showIcons);\n // Measuring needs the rows in the DOM, so the layout lands after the render\n // and the entrance delays after the layout.\n const cols = applyGridLayout(grid, options.length, loading);\n applyEntranceDelays(grid, options.length, cols, loading, optionsPosition);\n // Send the scroll position back to the top whenever the grid starts showing\n // a different suggestion's options (i.e. after a selection), so the next set\n // is read from its first option rather than from wherever the previous list\n // was scrolled to. Filtering within the same suggestion keeps its position.\n if (isNewGroup) 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 showIcons: 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. The group is in\n // the key too: a new suggestion's options are new rows and get the entrance\n // animation even when one of them spells the same as a row already on screen.\n // So is the icon: it is built once, on create, so a row whose icon changed\n // under the same text must be a new row. The name stands in for the markup\n // when there is one — the server's icon set maps each name to one SVG —\n // and the markup itself keys an option that carries only `icon_svg`.\n const loadingFlag = loading ? \"1\" : \"0\";\n const groupKey = grid.dataset.aiaGroup ?? \"\";\n // The icon only exists in the row when icons are on, so a toggle re-keys.\n const iconKey = showIcons ? (opt: SuggestionOption) => opt.icon ?? opt.icon_svg ?? \"\" : () => \"\";\n\n reconcileList(grid, options, {\n keyOf: (opt) => `${opt.text}\\0${iconKey(opt)}\\0${loadingFlag}\\0${groupKey}`,\n create: (option, _i) => {\n const el = buildOptionElement(option, loading, showIcons);\n // Marked, not delayed, here: the stagger runs across the rendered\n // columns, and how many there are is only known once the rows have been\n // measured. `applyEntranceDelays` writes the delay a moment later, onto\n // exactly the rows this pass created — so a re-render of the same rows\n // never restarts their animation.\n if (!loading) el.dataset.aiaEntering = \"\";\n return el;\n },\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\"), 170);\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\n/**\n * Give each row created this pass its entrance delay, now that the column\n * count is settled. Rows start a beat after the one before them, from the\n * input's side outward. Skeleton rows are skipped — they fade in together,\n * being placeholders rather than content arriving.\n */\nfunction applyEntranceDelays(\n grid: HTMLElement,\n count: number,\n cols: number,\n loading: boolean,\n optionsPosition: \"above\" | \"below\",\n): void {\n for (const el of grid.querySelectorAll<HTMLElement>(\"[data-aia-entering]\")) {\n if (!loading) {\n const i = Number.parseInt(el.dataset.aiaIndex ?? \"-1\", 10);\n if (i >= 0) {\n el.style.setProperty(\n OPTION_ENTER_DELAY_VAR,\n `${optionEnterDelayMs(i, cols, count, optionsPosition)}ms`,\n );\n }\n }\n delete el.dataset.aiaEntering;\n }\n}\n\nfunction buildOptionElement(\n option: SuggestionOption,\n loading: boolean,\n showIcons: boolean,\n): 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 content = document.createElement(\"span\");\n content.className = \"magicx-aia-option-content\";\n\n // Server-provided icon, allowlisted before it touches the DOM. The name\n // goes on the row for consumer CSS and tests; the glyph itself is\n // decorative — the text is the option's accessible name.\n const iconHtml = showIcons && option.icon_svg ? sanitizeOptionIconSvg(option.icon_svg) : null;\n if (iconHtml) {\n const icon = document.createElement(\"span\");\n icon.className = \"magicx-aia-option-icon\";\n icon.setAttribute(\"aria-hidden\", \"true\");\n icon.innerHTML = iconHtml;\n content.appendChild(icon);\n if (option.icon) item.dataset.aiaIcon = option.icon;\n }\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 = optionLabel(option);\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 { optionsEntranceDurationMs } from \"../derive/optionsEntrance\";\n\n/**\n * The \"more below\" arrow for the options grid.\n *\n * When the option list is taller than its scroll box, a bare down chevron\n * sits at the bottom-centre of the list and scrolls it a page on click. It\n * never travels with the scroll — every appearance is an opacity fade in\n * place, SCROLL_ARROW_FADE_MS long, and its only motion is the stylesheets'\n * slow idle bob of a few pixels (the stylesheets own both curves):\n *\n * - At rest with more below (the list just opened, or scrolling has stopped\n * short of the end), it fades in.\n * - The moment the list scrolls, it fades out, and stays out while scroll\n * events keep coming.\n * - Once scrolling has been quiet for SCROLL_ARROW_SCROLL_IDLE_MS it fades\n * back in — unless the list is now at its bottom, where it stays out.\n *\n * Earlier versions were a white disc that rose from behind the footer and\n * slid or shrank away; each read as the disc travelling with the scroll\n * (2026-09-03/04), hence the fade-only rule, and on 2026-09-05 the disc was\n * dropped for a plain chevron. The stylesheets key everything off one attribute,\n * `data-aia-visible`: present → opacity 1, absent → opacity 0, both\n * transitioned.\n *\n * The DOM differs per package (vanilla builds the button here; React and\n * Angular render their own markup with the same classes and data attributes),\n * so the *behaviour* lives in this one controller and each package hands it\n * its dropdown, grid and button elements:\n *\n * - `update()` after every render — re-measures where the grid's bottom edge\n * is and re-evaluates visibility. The disc seats itself in the grid's\n * reserved fade band when there is one (below-mode with the footer), and\n * floats a small gap above the grid's edge in layouts that zero the band\n * (`optionsPosition=\"above\"`, product strip). A new\n * option group (`data-aia-group` on the grid changed) keeps the arrow hidden\n * until the rows' entrance animation has settled, because a row mid-rise\n * still extends the scrollable area and would flash the arrow on.\n * - The controller listens for the grid's own `scroll` and size changes itself.\n * - `destroy()` when the grid or dropdown goes away.\n */\n\nexport const SCROLL_ARROW_CLASS = \"magicx-aia-scroll-arrow\";\n/** Stable styling hook (module class names aren't part of the API). */\nexport const SCROLL_ARROW_ATTR = \"data-aia-scroll-arrow\";\n/**\n * Present on the button while it is shown. The stylesheets fade it in on this\n * and back out when it is removed.\n */\nexport const SCROLL_ARROW_VISIBLE_ATTR = \"data-aia-visible\";\n/**\n * Length of the fade, either way. Documents the `opacity` transition in each\n * package's stylesheet, which is what actually runs it — change both together.\n */\nexport const SCROLL_ARROW_FADE_MS = 1000;\n/**\n * Quiet time after the last scroll event before the list counts as \"stopped\"\n * and the arrow may come back. Wheel and trackpad scrolls deliver events a\n * frame or so apart, so this only has to outlast a frame with some slack.\n */\nexport const SCROLL_ARROW_SCROLL_IDLE_MS = 160;\n/** Inline custom property: how far the button's bottom edge sits above the dropdown's padding edge. */\nexport const SCROLL_ARROW_BOTTOM_VAR = \"--aia-scroll-arrow-bottom\";\nexport const SCROLL_ARROW_LABEL = \"Scroll down for more options\";\n\n/** Less remaining scroll than this counts as \"at the bottom\" — sub-pixel and rounding slack. */\nconst AT_BOTTOM_SLACK_PX = 4;\n/** Breathing room above the grid's edge when it has no reserved band to seat the disc in. */\nconst ARROW_GAP_PX = 8;\n/** Length of the page-scroll a click performs. */\nconst PAGE_SCROLL_MS = 240;\n\n/**\n * Scroll the grid by `delta` over PAGE_SCROLL_MS with an ease-out, driven by\n * requestAnimationFrame. Not `scrollBy({ behavior: \"smooth\" })`: that is a\n * hint the browser may ignore (embedded/headless Chromes and some settings\n * drop it entirely), and a click that does nothing is worse than one that\n * jumps. Jumps when motion is off, there is no frame clock, or the document\n * is hidden (browsers pause rAF there, and a scroll that never lands is a bug).\n */\nfunction animateScrollBy(\n grid: HTMLElement,\n delta: number,\n view: Window | null,\n onDone: () => void,\n): void {\n const from = grid.scrollTop;\n const raf = view?.requestAnimationFrame;\n const hidden = grid.ownerDocument?.visibilityState === \"hidden\";\n if (!raf || hidden || motionDisabled(grid)) {\n grid.scrollTop = from + delta;\n onDone();\n return;\n }\n const start = view.performance?.now?.() ?? Date.now();\n const now = () => view.performance?.now?.() ?? Date.now();\n const step = () => {\n const t = Math.min(1, (now() - start) / PAGE_SCROLL_MS);\n const eased = 1 - (1 - t) * (1 - t); // ease-out-quad, matching the rows' rise\n grid.scrollTop = from + delta * eased;\n if (t < 1) raf.call(view, step);\n else onDone();\n };\n raf.call(view, step);\n}\n\n/** The chevron, drawn in the house outline style (single-weight stroke, round caps); 18px, since nothing frames it. */\nexport function buildScrollArrowIcon(doc: Document = document): SVGSVGElement {\n const NS = \"http://www.w3.org/2000/svg\";\n const svg = doc.createElementNS(NS, \"svg\");\n svg.setAttribute(\"viewBox\", \"0 0 16 16\");\n svg.setAttribute(\"width\", \"18\");\n svg.setAttribute(\"height\", \"18\");\n svg.setAttribute(\"fill\", \"none\");\n svg.setAttribute(\"stroke\", \"currentColor\");\n svg.setAttribute(\"stroke-width\", \"1.75\");\n svg.setAttribute(\"stroke-linecap\", \"round\");\n svg.setAttribute(\"stroke-linejoin\", \"round\");\n svg.setAttribute(\"aria-hidden\", \"true\");\n svg.setAttribute(\"focusable\", \"false\");\n const path = doc.createElementNS(NS, \"path\");\n path.setAttribute(\"d\", \"M4 6.5 8 10.5l4-4\");\n svg.appendChild(path);\n return svg;\n}\n\n/** Vanilla's button. React and Angular render the same shape in their own markup. */\nexport function createScrollArrowButton(doc: Document = document): HTMLButtonElement {\n const button = doc.createElement(\"button\");\n button.type = \"button\";\n button.tabIndex = -1;\n button.className = SCROLL_ARROW_CLASS;\n button.setAttribute(SCROLL_ARROW_ATTR, \"\");\n button.setAttribute(\"aria-label\", SCROLL_ARROW_LABEL);\n button.setAttribute(\"aria-hidden\", \"true\");\n button.appendChild(buildScrollArrowIcon(doc));\n return button;\n}\n\nexport interface ScrollArrowController {\n /** Re-measure and re-evaluate. Call after every render of the dropdown. */\n update(): void;\n destroy(): void;\n}\n\nexport interface ScrollArrowArgs {\n /** The dropdown panel — the button's containing block. */\n dropdown: HTMLElement;\n /** The options grid (the scroll box). */\n grid: HTMLElement;\n /** The arrow button, already in the dropdown. */\n button: HTMLElement;\n}\n\nfunction columnCount(grid: HTMLElement): number {\n const tracks = grid.style.gridTemplateColumns.trim();\n if (!tracks) return 1;\n return Math.max(1, tracks.split(/\\s+/).length);\n}\n\nfunction motionDisabled(el: HTMLElement): boolean {\n if (el.closest('[data-animations=\"off\"]')) return true;\n const view = el.ownerDocument?.defaultView;\n return Boolean(view?.matchMedia?.(\"(prefers-reduced-motion: reduce)\").matches);\n}\n\nexport function attachScrollArrow({\n dropdown,\n grid,\n button,\n}: ScrollArrowArgs): ScrollArrowController {\n const view = grid.ownerDocument?.defaultView ?? (typeof window !== \"undefined\" ? window : null);\n let destroyed = false;\n let lastGroup: string | null = null;\n let settleTimer: ReturnType<typeof setTimeout> | null = null;\n /** Pending \"scrolling has stopped\" judgement; non-null while the list is scrolling. */\n let idleTimer: ReturnType<typeof setTimeout> | null = null;\n\n const setVisible = (visible: boolean) => {\n if (visible) {\n button.setAttribute(SCROLL_ARROW_VISIBLE_ATTR, \"\");\n button.setAttribute(\"aria-hidden\", \"false\");\n } else {\n button.removeAttribute(SCROLL_ARROW_VISIBLE_ATTR);\n button.setAttribute(\"aria-hidden\", \"true\");\n }\n };\n\n /**\n * Rest the arrow's bottom edge on the grid's lower border edge, so the disc\n * straddles the reserved fade band the rows dissolve into — low against the\n * panel's bottom, the way the reference's disc straddles its composer\n * boundary — whatever sits below the grid in the current layout.\n *\n * Measured with layout metrics (offsetTop / offsetHeight / clientHeight),\n * NOT getBoundingClientRect: rects are post-transform, and this runs in the\n * same pass that starts the panel's unfold — while the dropdown is still\n * scaled to a sliver a rect-based answer is garbage, and nothing re-fires\n * afterwards (ResizeObserver ignores transforms).\n */\n const position = () => {\n // Grid's top within the dropdown's padding box, robust to wrappers.\n let gridTop = 0;\n let el: HTMLElement | null = grid;\n while (el && el !== dropdown) {\n gridTop += el.offsetTop;\n el = el.offsetParent instanceof HTMLElement ? el.offsetParent : null;\n }\n // The grid's bottom padding is its reserved fade band (rows dissolve into\n // the footer there) — the disc's natural seat, so rest right on the\n // border edge and let the disc straddle the band. Layouts that zero the\n // band (optionsPosition=\"above\", product strip) get a small gap instead,\n // so the disc never sits flush against the last row's text.\n const band = view ? Number.parseFloat(view.getComputedStyle(grid).paddingBottom) || 0 : 0;\n const bottom =\n dropdown.clientHeight - (gridTop + grid.offsetHeight) + (band > 0 ? 0 : ARROW_GAP_PX);\n button.style.setProperty(SCROLL_ARROW_BOTTOM_VAR, `${Math.max(0, Math.round(bottom))}px`);\n };\n\n /**\n * Judge from the geometry — but only at rest. While a fresh group's rows are\n * still rising (settleTimer) or the list is mid-scroll (idleTimer) the\n * answer is deferred to whichever timer is pending.\n */\n const evaluate = () => {\n if (destroyed || settleTimer !== null || idleTimer !== null) return;\n // Skeleton rows are placeholders for options that haven't arrived — nothing to scroll to yet.\n const loading = grid.querySelector(\"[data-aia-option][data-aia-loading]\") !== null;\n const remaining = grid.scrollHeight - grid.clientHeight - grid.scrollTop;\n setVisible(!loading && remaining > AT_BOTTOM_SLACK_PX);\n };\n\n const update = () => {\n if (destroyed) return;\n position();\n const group = grid.getAttribute(\"data-aia-group\") ?? \"\";\n if (group !== lastGroup) {\n lastGroup = group;\n // New rows are rising into place: hide until they've settled, then judge.\n setVisible(false);\n if (settleTimer !== null) clearTimeout(settleTimer);\n const count = grid.querySelectorAll(\"[data-aia-option]\").length;\n const wait = motionDisabled(grid) ? 0 : optionsEntranceDurationMs(count, columnCount(grid));\n settleTimer = setTimeout(() => {\n settleTimer = null;\n evaluate();\n }, wait);\n return;\n }\n evaluate();\n };\n\n /**\n * Scrolling: out at once, and stay out while the events keep coming. The\n * quiet period after the last one re-judges — back in if there is still\n * more below, otherwise it stays out. Mid-entrance a scroll changes nothing;\n * the settle timer judges when the rows have landed.\n */\n const onScroll = () => {\n if (destroyed || settleTimer !== null) return;\n setVisible(false);\n if (idleTimer !== null) clearTimeout(idleTimer);\n idleTimer = setTimeout(() => {\n idleTimer = null;\n evaluate();\n }, SCROLL_ARROW_SCROLL_IDLE_MS);\n };\n grid.addEventListener(\"scroll\", onScroll, { passive: true });\n\n const onMouseDown = (e: Event) => e.preventDefault(); // keep focus in the input\n const onClick = () => {\n const pad = view\n ? (Number.parseFloat(view.getComputedStyle(grid).paddingTop) || 0) +\n (Number.parseFloat(view.getComputedStyle(grid).paddingBottom) || 0)\n : 0;\n const page = Math.max(1, grid.clientHeight - pad);\n // A click is a scroll like any other: the disc steps aside while the page\n // travels and comes back once it has landed and gone quiet. Landing feeds\n // the same idle path as the grid's own scroll events rather than judging\n // at once — a browser fires those events asynchronously, so an immediate\n // \"show\" here would be undone a moment later by the event that follows\n // the final scrollTop write, and the disc would blink. In a hidden\n // document (no frames, no events) landing is the only signal, which is\n // why it is fed in explicitly.\n animateScrollBy(grid, page, view, onScroll);\n };\n button.addEventListener(\"mousedown\", onMouseDown);\n button.addEventListener(\"click\", onClick);\n\n let resizeObserver: ResizeObserver | null = null;\n if (view && typeof view.ResizeObserver === \"function\") {\n resizeObserver = new view.ResizeObserver(() => {\n position();\n evaluate();\n });\n resizeObserver.observe(grid);\n }\n\n update();\n\n return {\n update,\n destroy() {\n if (destroyed) return;\n destroyed = true;\n if (settleTimer !== null) clearTimeout(settleTimer);\n settleTimer = null;\n if (idleTimer !== null) clearTimeout(idleTimer);\n idleTimer = null;\n grid.removeEventListener(\"scroll\", onScroll);\n button.removeEventListener(\"mousedown\", onMouseDown);\n button.removeEventListener(\"click\", onClick);\n resizeObserver?.disconnect();\n button.removeAttribute(SCROLL_ARROW_VISIBLE_ATTR);\n button.setAttribute(\"aria-hidden\", \"true\");\n },\n };\n}\n","import type { DateMonthView } from \"../date/dateGrid\";\nimport type { DateRange } from \"../date/dateRange\";\nimport { type FormatType, isCalendarFormat } from \"../date/formatType\";\nimport type { Product, Suggestion, SuggestionOption } from \"../shared-types\";\nimport { buildAttributionUrl } from \"../utils/attribution\";\nimport { getFooterHint } from \"../utils/footerHint\";\nimport { renderDateGrid } from \"./renderDateGrid\";\nimport { renderPills } from \"./renderPills\";\nimport { renderProductStrip, setProductStripFocusable } from \"./renderProductStrip\";\nimport { renderSuggestionGrid } from \"./renderSuggestionGrid\";\nimport {\n attachScrollArrow,\n createScrollArrowButton,\n type ScrollArrowController,\n} from \"./scrollArrow\";\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 — the server's matched items, or a custom integration's results. */\n products: Product[];\n /**\n * How the active pill is answered. `\"date\"` and `\"date-range\"` swap the\n * options grid for the datepicker — `filteredOptions` are then the visible\n * month's cells.\n */\n formatType: FormatType;\n /** The month the datepicker shows. Null unless `formatType` is a calendar one. */\n dateView: DateMonthView | null;\n /** `YYYY-MM-DD` already held by the param being re-edited, so its cell reads as selected. */\n selectedDateIso: string | null;\n /** Both ends of a range param being re-edited, so both cells read as selected. */\n selectedDateRange: DateRange | null;\n /** The first end of a range picked so far, while the user chooses the second. */\n dateRangeStart: string | null;\n /**\n * Which side of the input the dropdown opens on. Drives the option rows'\n * entrance order (the row nearest the input rises first). Default: \"below\".\n */\n optionsPosition?: \"above\" | \"below\";\n /** Whether option rows draw their `icon_svg`. Default: true. */\n showOptionIcons?: boolean;\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 /** Page the datepicker back a month. Never called unless the calendar is showing. */\n onPreviousMonth: () => void;\n /** Page the datepicker forward a month. Never called unless the calendar is showing. */\n onNextMonth: () => 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\ninterface ScrollArrowState {\n button: HTMLButtonElement;\n grid: HTMLElement | null;\n controller: ScrollArrowController | null;\n}\n\n/** Per-dropdown scroll-arrow wiring; the button outlives the grid, the controller doesn't. */\nconst scrollArrows = new WeakMap<HTMLElement, ScrollArrowState>();\n\n/**\n * Keep the \"more below\" arrow bound to whichever grid the dropdown currently\n * shows. The button is created once and appended to the dropdown itself (not\n * the section stack — it's an overlay, not a section); the controller is\n * re-attached whenever the grid element is replaced and torn down when there\n * is no grid. See scrollArrow.ts for the behaviour.\n */\nfunction syncScrollArrow(dropdown: HTMLElement, grid: HTMLElement | null): void {\n let state = scrollArrows.get(dropdown);\n if (!state) {\n state = {\n button: createScrollArrowButton(dropdown.ownerDocument),\n grid: null,\n controller: null,\n };\n dropdown.appendChild(state.button);\n scrollArrows.set(dropdown, state);\n }\n if (state.grid !== grid) {\n state.controller?.destroy();\n state.controller = null;\n state.grid = grid;\n if (grid) {\n state.controller = attachScrollArrow({ dropdown, grid, button: state.button });\n return; // attach() runs the first update itself\n }\n }\n state.controller?.update();\n}\n\n/** Release the dropdown's listeners/timers. Call when the instance is destroyed. */\nexport function destroyDropdown(dropdown: HTMLElement): void {\n const state = scrollArrows.get(dropdown);\n if (!state) return;\n state.controller?.destroy();\n scrollArrows.delete(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 // Exactly one body renders. Each renderer removes its own element when handed\n // nothing, so passing the empty list to the other is what tears the previous\n // body down — no separate cleanup, and no window where both are mounted and\n // two elements answer to `#${listboxId}-option-0`.\n //\n // A calendar is withheld while a request is in flight. The options grid\n // draws its cached options as skeleton rows for that window, but a month of\n // day cells has no skeleton form worth drawing — and a live calendar over a\n // loading pill bar invites an answer against suggestions the response is\n // about to replace. The fallback bars stand in until it lands.\n const isDate = isCalendarFormat(state.formatType);\n const showsDateGrid = isDate && !isLoading;\n\n renderSuggestionGrid(\n stack,\n isDate ? [] : filteredOptions,\n activeIndex,\n onSelect,\n onHighlight,\n state.listboxId,\n isLoading,\n groupKey,\n state.optionsPosition ?? \"below\",\n state.showOptionIcons ?? true,\n );\n syncScrollArrow(dropdown, stack.querySelector<HTMLElement>(\".aia-grid\"));\n\n renderDateGrid(\n stack,\n showsDateGrid ? filteredOptions : [],\n activeIndex,\n state.listboxId,\n showsDateGrid ? state.dateView : null,\n {\n selectedIso: state.selectedDateIso,\n selectedRange: state.selectedDateRange,\n rangeStart: state.dateRangeStart,\n },\n {\n onSelect,\n onHighlight,\n onPreviousMonth: state.onPreviousMonth,\n onNextMonth: state.onNextMonth,\n },\n );\n\n // --- Fallback skeleton bars (when loading with no cached options, or with\n // a calendar's cells, which never draw as skeleton rows) ---\n let skeleton = stack.querySelector<HTMLElement>(\".magicx-aia-skeleton-bars\");\n if (isLoading && (!hasOptions || isDate)) {\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-datepicker\",\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 { dateSelectionFor } from \"../date/dateSelection\";\nimport type { Product, SuggestionOption } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { identifiedParamLabel } from \"../utils/paramType\";\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 /** Whether option rows draw their `icon_svg`. Default: true. */\n showOptionIcons?: boolean;\n /** Which side of the input the dropdown opens on. Default: \"below\". */\n optionsPosition?: \"above\" | \"below\";\n selectOption: (option: SuggestionOption) => void;\n setActivePill: (index: number) => void;\n skipActivePill: () => void;\n selectProduct: (product: Product) => void;\n showPreviousMonth: () => void;\n showNextMonth: () => 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 const dateSelection = dateSelectionFor(state);\n renderDropdown(refs.dropdown, {\n // Tier 2's pill bar renders `pills` (the actionable suggestions) rather\n // than this, so the `text` here is not on screen — it only keys the option\n // group for the enter animation. Read into words anyway, so the one string\n // that names an identified parameter is the same in all four places that\n // build it and this stays right if it ever does become visible.\n suggestions: state.editingIdentified\n ? [\n {\n type: state.editingIdentified.type,\n text: identifiedParamLabel(state.editingIdentified.type),\n required: true,\n options: state.filteredOptions,\n },\n ]\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 press animation finishes before\n // we swap in the skeleton. A consumer override still answering shows the\n // skeleton in any mode — its pill has nothing else to show yet.\n isLoading:\n (state.isLoading && !state.editingParam && !state.inSelectionAnimation) ||\n state.isSearchingOptions,\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 && !state.editingIdentified,\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 showOptionIcons: opts.showOptionIcons ?? true,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n formatType: state.activeFormatType,\n dateView: state.dateView,\n selectedDateIso: dateSelection.selectedIso,\n selectedDateRange: dateSelection.selectedRange,\n dateRangeStart: dateSelection.rangeStart,\n optionsPosition: opts.optionsPosition ?? \"below\",\n onSelect: opts.selectOption,\n onHighlight: (i) => opts.store.set({ activeDropdownIndex: i }),\n onPillClick: opts.setActivePill,\n onSkip: opts.skipActivePill,\n onPreviousMonth: opts.showPreviousMonth,\n onNextMonth: opts.showNextMonth,\n onProductSelect: opts.selectProduct,\n onProductFocusChange: (focused) => opts.store.set({ isFocused: focused }),\n });\n}\n","import { getCursorOffset, setCursorOffset } from \"../dom/cursorUtils\";\nimport { documentOf } from \"../dom/roots\";\nimport type { Segment } from \"../shared-types\";\nimport { sanitizeOptionIconSvg } from \"./optionIcon\";\nimport { syncPlaceholder } from \"./placeholderTypewriter\";\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 /** Whether completed chips draw the icon of the option that answered them. Default: true. */\n showChipIcons?: 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 const showChipIcons = args.showChipIcons ?? true;\n\n const empty = segments.length === 0;\n input.dataset.aiaEmpty = empty ? \"true\" : \"false\";\n // The placeholder types itself in (and clears instantly) — see\n // placeholderTypewriter.ts. `data-placeholder` holds the typed prefix.\n syncPlaceholder(input, empty ? placeholderText : \"\");\n\n // Icons only exist in the chip when they are on, so the toggle re-keys too.\n // A completed chip is built once per render pass, icon included, so a chip\n // whose icon changed under the same text must key differently. The icon\n // name stands in for the markup when there is one — the server maps each\n // name to one SVG — and the markup itself keys a nameless icon.\n const segKey = segments\n .map((s) =>\n s.type === \"completed\"\n ? `${s.type}:${s.value}\\u0001${showChipIcons ? (s.param.icon ?? s.param.icon_svg ?? \"\") : \"\"}`\n : `${s.type}:${s.value}`,\n )\n .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 = documentOf(input);\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 // The picked option's icon, allowlisted before it touches the DOM. Its\n // wrapper opts out of contentEditable so the caret walker skips it: the\n // chip's own text node stays the only text inside the <strong>, and the\n // glyph contributes zero characters to every offset.\n const iconHtml =\n showChipIcons && seg.param.icon_svg ? sanitizeOptionIconSvg(seg.param.icon_svg) : null;\n if (iconHtml) {\n const icon = doc.createElement(\"span\");\n icon.className = \"magicx-aia-segment-icon\";\n icon.setAttribute(\"contenteditable\", \"false\");\n icon.setAttribute(\"aria-hidden\", \"true\");\n icon.innerHTML = iconHtml;\n strong.appendChild(icon);\n if (seg.param.icon) strong.dataset.aiaIcon = seg.param.icon;\n }\n strong.appendChild(doc.createTextNode(seg.value));\n frag.appendChild(strong);\n } else if (seg.type === \"identified\") {\n // Identified spans reuse the completed styling class (per TDB: no\n // distinct styling) and carry data-seg=\"identified\" so consumers can\n // tell the two apart. The click handler deliberately matches BOTH kinds\n // now — the core decides what a tap means, and a span it recognises as a\n // date opens the calendar.\n const strong = doc.createElement(\"strong\");\n strong.dataset.seg = \"identified\";\n strong.dataset.paramId = seg.param.id;\n const identifiedClasses = [\"magicx-aia-segment\", \"magicx-aia-segment--completed\"];\n // Same in-progress affordance a re-edited param gets: while the calendar\n // is open for this span, it should look picked rather than idle.\n if (seg.param.id === editingParamId) identifiedClasses.push(\"magicx-aia-segment--editing\");\n strong.className = identifiedClasses.join(\" \");\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 { dateSelectionFor } from \"../date/dateSelection\";\nimport {\n caretEscapedChip,\n extractPlainText,\n getCursorOffset,\n setCursorOffset,\n} from \"../dom/cursorUtils\";\nimport { documentOf, getSelectionFor } from \"../dom/roots\";\nimport type { AutocompleteResult, Suggestion } from \"../shared-types\";\nimport type { Store } from \"../state\";\nimport type { CoreState } from \"../types\";\nimport { identifiedParamLabel } from \"../utils/paramType\";\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 /** Whether option rows draw their `icon_svg`. Default: true. */\n showOptionIcons?: boolean;\n /** Whether completed chips draw the icon of the option that answered them. Default: true. */\n showChipIcons?: boolean;\n /** Which side of the input the dropdown opens on. Default: \"below\". */\n optionsPosition?: \"above\" | \"below\";\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 showPreviousMonth: () => void;\n showNextMonth: () => 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 // One of the two roots the consumer-isolation reset in appearance.css scopes\n // to (the dropdown is the other).\n inputWrapper.setAttribute(\"data-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 // The consumer's element: the isolation reset leaves it and its subtree to\n // the consumer's own stylesheet.\n submitTarget.setAttribute(\"data-aia-submit-slot\", \"\");\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 = getSelectionFor(input);\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 // Both chip kinds report here. The core decides what a tap means — a\n // completed param re-opens its options, an identified date opens the\n // calendar, anything else is inert — so this stays a pure \"what was\n // tapped\" lookup rather than encoding the rule three times over.\n const strong = startEl?.closest<HTMLElement>(\"strong[data-param-id]\");\n const paramId = strong?.dataset.paramId ?? null;\n // Inside a shadow root the engine reports the caret from the canonicalized\n // position, and the canonical form of \"just outside a chip\" is a position\n // within the chip's own text — so the caret the SDK parks at a chip's edge\n // (after promoting typed text, after a pick) reads here as a tap on that\n // chip. `caretEscapedChip` is that park's receipt; without it, promoting\n // exactly-typed text drops straight into re-editing what it just completed.\n if (paramId && caretEscapedChip(input, paramId)) return null;\n return paramId;\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 //\n // The document stays the listener target inside a shadow root: the event is\n // dispatched at the document only — not composed, not bubbling out of the\n // shadow root, and not dispatched at the shadow root at all (verified in\n // Chromium against a contenteditable in an open shadow root). Only the\n // *reading* of the selection has to be root-aware, which `getSelectionFor`\n // handles.\n const doc = documentOf(input);\n doc.addEventListener(\n \"selectionchange\",\n () => {\n const sel = getSelectionFor(input);\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 editState = opts.store.get();\n // Either kind counts as \"already open on this chip\" — without the\n // identified id here, every caret tick inside a date span would re-enter\n // the pick it is already in.\n const editingId = editState.editingParam?.id ?? editState.editingIdentified?.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 = documentOf(input);\n const sel = getSelectionFor(input);\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 identifiedParams: state.identifiedParams,\n isReady: state.isReady,\n customFields: state.customFields,\n }),\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 = documentOf(input);\n const sel = getSelectionFor(input);\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 showChipIcons: opts.showChipIcons ?? true,\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 : state.editingIdentified\n ? // Answering a span the user wrote themselves: the pill names the\n // parameter the server matched it to, so the calendar isn't unlabelled.\n {\n type: state.editingIdentified.type,\n text: identifiedParamLabel(state.editingIdentified.type),\n required: true,\n options: [],\n }\n : null;\n const dropdownActivePill = dropdownPill ?? state.actionableSuggestions[0];\n const dateSelection = dateSelectionFor(state);\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 press animation finishes before\n // we swap in the skeleton. A consumer override still answering shows the\n // skeleton in any mode — its pill has nothing else to show yet.\n isLoading:\n (state.isLoading && !state.editingParam && !state.inSelectionAnimation) ||\n state.isSearchingOptions,\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 && !state.editingIdentified,\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 showOptionIcons: opts.showOptionIcons ?? true,\n isActivePillSelected: state.isActivePillSelected,\n isInputEmpty: state.text.trim().length === 0,\n products: state.products,\n formatType: state.activeFormatType,\n dateView: state.dateView,\n selectedDateIso: dateSelection.selectedIso,\n selectedDateRange: dateSelection.selectedRange,\n dateRangeStart: dateSelection.rangeStart,\n optionsPosition: opts.optionsPosition ?? \"below\",\n onSelect: selectOption,\n onHighlight: (i) => store.set({ activeDropdownIndex: i }),\n onPillClick: setActivePill,\n onSkip: opts.skipActivePill,\n onPreviousMonth: opts.showPreviousMonth,\n onNextMonth: opts.showNextMonth,\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 icon: option.icon,\n icon_svg: option.icon_svg,\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","import type { WidgetRoot } from \"./dom/roots\";\n\n/**\n * Roots that already carry the SDK stylesheet. Per-root, not a module-global\n * flag: a page can mount one widget in the light DOM and another inside a\n * shadow root, and a global \"already injected\" boolean would style whichever\n * mounted first and leave the other bare.\n */\nconst styledRoots = new WeakSet<WidgetRoot>();\n\n/**\n * One constructable stylesheet per document — the CSS is parsed once and every\n * shadow root on the page adopts that same object. A `CSSStyleSheet` belongs to\n * the document that constructed it (adopting one across documents throws),\n * hence the map.\n */\nconst sheets = new WeakMap<Document, CSSStyleSheet>();\n\nfunction isDocument(root: WidgetRoot): root is Document {\n return root.nodeType === Node.DOCUMENT_NODE;\n}\n\nfunction documentOfRoot(root: WidgetRoot): Document {\n return isDocument(root) ? root : root.ownerDocument;\n}\n\n/** The shared sheet for `doc`, or null where constructable stylesheets aren't supported. */\nfunction constructableSheet(doc: Document): CSSStyleSheet | null {\n const cached = sheets.get(doc);\n if (cached) return cached;\n // Construct through the document's own view: a sheet built from another\n // realm's constructor can't be adopted here.\n const Ctor = doc.defaultView?.CSSStyleSheet;\n if (typeof Ctor !== \"function\") return null;\n try {\n const sheet = new Ctor();\n if (typeof sheet.replaceSync !== \"function\") return null;\n sheet.replaceSync(STYLES);\n sheets.set(doc, sheet);\n return sheet;\n } catch {\n // Older engines expose the constructor but reject `new` on it.\n return null;\n }\n}\n\n/** Append the `<style>` copy of the stylesheet to `host`, unless one is already there. */\nfunction appendStyleElement(doc: Document, host: ParentNode): void {\n // A second copy of the bundle on the page injects its own <style>; one\n // stylesheet per root is enough, and this is the dedupe the module-global\n // flag used to do (globally, which is the bug the per-root guard replaces).\n if (host.querySelector(\"style[data-magicx-aia]\")) return;\n const style = doc.createElement(\"style\");\n style.setAttribute(\"data-magicx-aia\", \"\");\n style.textContent = STYLES;\n host.appendChild(style);\n}\n\n/**\n * Inject core + appearance CSS into a root. Idempotent per root.\n *\n * `root` is the document or shadow root the widget was mounted into — a\n * stylesheet in `document.head` does not cross a shadow boundary, so a widget\n * inside a shadow root has to be styled from that root. Defaults to the\n * document for callers with no element to derive a root from.\n *\n * Shadow roots adopt a single constructable stylesheet (parsed once, however\n * many roots adopt it). The document keeps the `<style>` element it has always\n * had: adopted sheets sort *after* every `<style>`/`<link>` in the document, so\n * moving light-DOM consumers onto one would quietly promote the SDK's rules\n * above any consumer stylesheet injected after mount (a CSS-in-JS runtime, a\n * lazily loaded chunk) that currently overrides them on source order.\n */\nexport function injectStyles(root?: WidgetRoot | null) {\n if (typeof document === \"undefined\") return;\n const target = root ?? document;\n if (styledRoots.has(target)) return;\n\n const doc = documentOfRoot(target);\n if (isDocument(target)) {\n const host = target.head ?? target.documentElement;\n if (!host) return;\n appendStyleElement(doc, host);\n styledRoots.add(target);\n return;\n }\n\n const sheet = constructableSheet(doc);\n const adopted = target.adoptedStyleSheets;\n if (sheet && Array.isArray(adopted)) {\n if (!adopted.includes(sheet)) {\n // Reassign rather than push: the array is frozen in older Chromium.\n target.adoptedStyleSheets = [...adopted, sheet];\n }\n } else {\n appendStyleElement(doc, target);\n }\n styledRoots.add(target);\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 { OptionSourceController } from \"./controllers/optionSourceController\";\nimport { PillsController } from \"./controllers/pillsController\";\nimport { ProductsController } from \"./controllers/productsController\";\nimport { addMonths, cellIso } from \"./date/dateGrid\";\nimport { buildRangeOption, parseLooseDateRange } from \"./date/dateRange\";\nimport { isCalendarFormat, resolveFormatType } from \"./date/formatType\";\nimport { resolveIdentifiedDate } from \"./date/looseDate\";\nimport { OPTIONS_GRID_MOBILE_QUERY } from \"./derive/optionsGridLayout\";\nimport { dateViewKey, deriveAll } from \"./derive/state\";\nimport { previousGraphemeBoundary, setCursorOffset } from \"./dom/cursorUtils\";\nimport { rootOf } from \"./dom/roots\";\nimport { tryPromoteExactMatch } from \"./promotion/promote\";\nimport { ReEditManager } from \"./reEdit/ReEditManager\";\nimport { cancelPlaceholderTypewriter } from \"./render/placeholderTypewriter\";\nimport { destroyDropdown } from \"./render/renderDropdown\";\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 Product,\n SuggestionOption,\n} from \"./shared-types\";\nimport { createDerivedStore, createStore } from \"./state\";\nimport { injectStyles } from \"./styleInjector\";\nimport type { CoreInputState, CoreOptions, CoreState, RenderMode } 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 { identifiedParamLabel } from \"./utils/paramType\";\nimport { coveredEnd, isTrailingCovered, rebaseAnchor } from \"./utils/pendingSpan\";\nimport { reconcileIdentifiedParams, reconcileParams } from \"./utils/segments\";\nimport { buildSubmitResult } from \"./utils/submitResult\";\nimport { sendTelemetry, type TelemetrySource } from \"./utils/telemetry\";\n\nexport type AIAutocompleteEvents = {\n submit: [result: AutocompleteResult];\n result: [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 | \"onResult\"\n | \"onError\"\n | \"onChange\"\n | \"onParamsChange\"\n | \"onStateChange\"\n | \"onFocus\"\n | \"onBlur\"\n | \"onProductSelect\"\n // Mount-time only: the widget's styles are injected once, into the root it\n // was mounted in.\n | \"styleRoot\"\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 = 170;\n\nfunction initialInputs(): CoreInputState {\n return {\n text: \"\",\n completedParams: [],\n identifiedParams: [],\n skippedParams: [],\n pendingSpan: null,\n suggestions: [],\n products: [],\n customFields: null,\n optionSearch: null,\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 dateViewMonth: null,\n pendingRangeStart: null,\n editingIdentified: null,\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 optionSource: OptionSourceController;\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 and `subscribe()`. */\n private boundary = new ConsumerBoundary();\n /** Identity of the raw override record the wrapped copy below was built from. */\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) => deriveAll(inputs, this.opts));\n\n // Register any opts.on* callbacks as the initial listener set. Use `on()`\n // after construction for additional or replacement listeners — `update()`\n // does NOT swap these (intentional, since stable proxies from React refs\n // depend on it).\n if (opts.onSubmit) this.emitter.on(\"submit\", opts.onSubmit);\n if (opts.onResult) this.emitter.on(\"result\", opts.onResult);\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(\n this.store,\n () => this.opts.products,\n () => this.opts.showProducts !== false,\n );\n\n // Consumer-supplied options. Reads opts lazily so `update({ optionOverrides })`\n // swaps the record without rebuilding anything.\n this.optionSource = new OptionSourceController(this.store, () => this.opts.optionOverrides, {\n // The user may have typed the whole option before the answer landed;\n // the keystroke path already ran and found nothing to promote.\n onAnswer: (mode) => {\n if (mode === \"edit\") this.reEdit.promoteIfExact();\n else this.maybePromoteExactMatch(this.store.get().text);\n },\n // The override has nothing for the typed phrase, so the server gets\n // it — through the scheduler's own gates, which is what keeps an empty\n // default list (\"\" answered empty) from re-requesting what the server\n // just sent. A re-edit's miss is handled by `maybeExitReEditOnNoMatch`,\n // which exits the edit and fetches itself.\n onMiss: (mode, query) => {\n if (mode === \"pill\" && query.length > 0) this.fetchController.attemptNow();\n },\n });\n\n this.fetchController = new FetchController(\n this.store,\n () => this.opts.apiConfig,\n () => this.opts.optionOverrides,\n () => this.opts.maskCompletedText,\n () => (this.emitter.hasListeners(\"error\") ? this.emitError : undefined),\n () => this.sessionId,\n () => 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 // Every committed response hands the consumer the query as it now\n // stands. Built from the store AFTER the response's write, not from\n // the request-time snapshot: the response can promote an exact match\n // into a completed param and it replaces the identified set wholesale.\n onResponse: () => {\n if (!this.emitter.hasListeners(\"result\")) return;\n this.emitter.emit(\"result\", this.buildResult());\n },\n // The server's matched items become the strip's cards in the same\n // write as its suggestions — unless a custom integration owns the\n // strip, in which case the response leaves the cards alone.\n productsForResponse: (report) => this.productsController.forResponse(report),\n // An override that owns the active pill takes the debounce tick in\n // place of the server. One scheduler for both, so the consumer's\n // search and the suggest request can't drift onto different cadences.\n consumerTick: (state, minDiff) => this.optionSource.tick(state, minDiff),\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 exitEditingIdentified: () => this.exitEditingIdentified(),\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 // Keep an open date pick pointing at a live identified param.\n //\n // `identifiedParams` is replaced wholesale by every response, and each\n // candidate is minted with a fresh id (see `fetchController`) — so a\n // background fetch landing mid-pick orphans `editingIdentified.id` even\n // when the span itself is untouched. Closing on a missing id would\n // therefore shut the calendar under the user on every debounced fetch,\n // which is why this re-binds to the entry still covering the span and only\n // abandons the pick when the span is genuinely gone (claimed by a\n // completed param, or edited away).\n //\n // Enforced here, once, rather than at each site that writes\n // `identifiedParams`: the fetch response and the reconciliation subscriber\n // both mutate it, and a third writer would otherwise have to remember.\n this.unsubscribers.push(\n this.store.subscribe((next, prev) => {\n if (next.identifiedParams === prev.identifiedParams) return;\n const editing = next.editingIdentified;\n if (!editing) return;\n if (next.identifiedParams.some((p) => p.id === editing.id)) return;\n // Located by POSITION, not by text. A bare `find` on text rebinds to\n // the first span with those words, which is the wrong one whenever the\n // same date is written twice (\"meet on monday … call back monday\") and\n // the user is picking for the second: the id would name the first\n // occurrence while `anchor`/`tail` still bound the second, and tapping\n // the first chip would then no-op against a stale identity check.\n // `startEditingIdentified` walks segments for exactly this reason, and\n // so do `findChipSpanAt` and `locateIdentified`.\n let pos = 0;\n let reissued: IdentifiedParamState | undefined;\n for (const seg of next.segments) {\n if (seg.type === \"identified\" && pos === editing.anchor && seg.value === editing.text) {\n reissued = seg.param;\n break;\n }\n pos += seg.value.length;\n }\n if (reissued) {\n this.store.set({ editingIdentified: { ...editing, id: reissued.id } });\n return;\n }\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }),\n );\n\n // Drop a half-finished range pick as soon as the parameter it belongs to\n // stops being the one on screen.\n //\n // The `dateViewKey` scoping in the derive layer only HIDES a stale pick,\n // which is enough for the paged month it was modelled on and is not enough\n // here: every key this can carry reproduces exactly later in a session —\n // the same pill type re-suggested after a skip, the same param re-edited\n // after leaving the edit, the same identified span re-tapped — and a\n // resurrected start would then pair with the user's next tap and commit a\n // span they never chose, silently.\n //\n // Enforced here, once, rather than at each path that abandons a parameter.\n // That list is long and open-ended (skip, a landing response, Escape,\n // exiting a re-edit by click / backspace / arrow / no-match, leaving an\n // identified span) and every one of them would have to remember; this is\n // the same \"one choke point, not N call sites\" reasoning the identified-\n // param reconciler above is written for.\n this.unsubscribers.push(\n this.store.subscribe((next) => {\n const pending = next.pendingRangeStart;\n if (!pending || pending.key === dateViewKey(next)) return;\n this.store.set({ pendingRangeStart: null });\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 // Style the root the widget actually lives in: a stylesheet in\n // `document.head` never crosses into a shadow root, so a shadow-mounted\n // widget would render unstyled. Derived from the container so consumers\n // get this without passing anything; `styleRoot` is the escape hatch for\n // a mount whose root isn't where the styles should land.\n injectStyles(this.opts.styleRoot ?? rootOf(this.container));\n this.setupContainer();\n }\n if (this.renderMode === \"full\") {\n this.buildAndRenderFull();\n } else if (this.renderMode === \"dropdown\") {\n this.buildAndRenderDropdown();\n }\n // Before the first response can land: the controller asks for a pill's\n // options from inside the response's own store write.\n this.optionSource.start();\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 // Before the write, so nothing answered or missed in the old session can\n // cover a pill the new one suggests.\n this.optionSource.reset();\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.optionSource.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 if (this.domRefs) {\n cancelPlaceholderTypewriter(this.domRefs.input);\n destroyDropdown(this.domRefs.dropdown);\n }\n if (this.dropdownRefs) destroyDropdown(this.dropdownRefs.dropdown);\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 const state = this.store.get();\n // A controlled consumer replacing the text moves it out from under an open\n // date pick: the span's offsets would still point into the old string.\n // Guarded on an actual change so controlled re-renders pushing the same\n // value don't close a picker the user is still using.\n if (state.editingIdentified && text !== state.text) {\n this.store.set({ text, editingIdentified: null, activeDropdownIndex: -1 });\n return;\n }\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 /**\n * Open the calendar for a span the server identified as a date.\n *\n * Returns false for anything else — an identified span whose type isn't a\n * date stays inert, exactly as every identified span did before.\n *\n * The span's own text is read for a starting month (`parseLooseDate`), but\n * failing to read it is not a failure: the calendar opens either way, and\n * whatever the user picks replaces the span with the SDK's canonical form. So\n * an unparseable \"sometime next week\" still ends up a clean answer.\n */\n startEditingIdentified(paramId: string): boolean {\n const state = this.store.get();\n if (state.editingIdentified?.id === paramId) return true;\n const param = state.identifiedParams.find((p) => p.id === paramId);\n if (!param) return false;\n const named = resolveFormatType(param);\n if (!isCalendarFormat(named)) return false;\n // The span's own words decide between one date and a span of them: a type\n // the server calls `travel_date` is still a range when what the user wrote\n // under it is \"september 11 to november 16\".\n const range = parseLooseDateRange(param.text);\n const format = named === \"date-range\" || range ? \"date-range\" : \"date\";\n\n // Locate the span the same way the renderer does, so the offsets bound\n // exactly the text the user sees highlighted.\n let pos = 0;\n let anchor = -1;\n for (const seg of state.segments) {\n if (seg.type !== \"text\" && seg.param.id === paramId) {\n anchor = pos;\n break;\n }\n pos += seg.value.length;\n }\n if (anchor < 0) return false;\n\n const tail = anchor + param.text.length;\n this.store.set({\n editingIdentified: {\n id: paramId,\n type: param.type,\n anchor,\n tail,\n text: param.text,\n // A range marks its start; the calendar opens on that month and the\n // user re-picks both ends, which is the same thing a single date does.\n iso: range ? range.start : resolveIdentifiedDate(param),\n isoEnd: range ? range.end : null,\n format,\n },\n // Leaving a completed-param re-edit open alongside this would give two\n // competing answers to \"what is the dropdown for\".\n editingParam: null,\n editingAnchor: null,\n editingTail: null,\n caretOffset: tail,\n activeDropdownIndex: -1,\n pillTapped: true,\n });\n return true;\n }\n\n /** Close the calendar opened for an identified span, leaving its text untouched. */\n exitEditingIdentified() {\n if (!this.store.get().editingIdentified) return;\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }\n\n /**\n * Open whatever the chip at `paramId` is answered with.\n *\n * Takes an id from either array so the DOM layers stay dumb: they report\n * which chip was tapped and the core decides what that means. A completed\n * param re-opens its cached options; a span the server identified as a date\n * opens the calendar; anything else is inert, as it has always been.\n */\n startEditingParam(paramId: string) {\n this.reEdit.start(paramId);\n if (this.store.get().editingParam?.id === paramId) return;\n this.startEditingIdentified(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 // Moving the caret out of an identified span ends the pick, the same way\n // it ends a re-edit. Without this the calendar would stay up while the\n // caret sat somewhere else entirely.\n const editing = this.store.get().editingIdentified;\n if (editing && offset != null && (offset < editing.anchor || offset > editing.tail)) {\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }\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 // Editing the span by hand abandons the pick — the text is no longer what\n // the server identified, so the calendar is answering a question that no\n // longer exists. Deliberately before handleChange, whose reconciler drops\n // the identified param on the same keystroke.\n if (this.store.get().editingIdentified) {\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n }\n this.handleChange(value);\n }\n\n /** Page the datepicker back one month. No-op unless a date pill is active. */\n showPreviousMonth() {\n this.pageDateView(-1);\n }\n\n /** Page the datepicker forward one month. No-op unless a date pill is active. */\n showNextMonth() {\n this.pageDateView(1);\n }\n\n /**\n * Move the visible month, stamping it with the current pill's key so the\n * derive layer keeps honouring it. Once the active pill changes the key stops\n * matching and the stored view is ignored — a new date pill therefore opens on\n * its own default month with nothing having to clear this.\n */\n private pageDateView(delta: number) {\n const state = this.store.get();\n if (!isCalendarFormat(state.activeFormatType) || !state.dateView) return;\n const next = addMonths(state.dateView, delta);\n this.store.set({\n dateViewMonth: { ...next, key: dateViewKey(state) },\n // The highlight is an index into the month that is going away; carrying\n // it over would land on an unrelated day, or on a padding cell.\n activeDropdownIndex: -1,\n });\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 ~170ms 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 /**\n * The structured query as the SDK understands it right now — the same\n * object `onResult` delivers after each response and `onSubmit` delivers\n * on submit. Read it on demand from a place that has no event handy (a\n * custom submit button, a \"save draft\" action).\n */\n getResult(): AutocompleteResult {\n return this.buildResult();\n }\n\n private buildResult(): AutocompleteResult {\n const state = this.store.get();\n return buildSubmitResult(state.text, state.completedParams, state.skippedParams, {\n identifiedParams: state.identifiedParams,\n isReady: state.isReady,\n customFields: state.customFields,\n });\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 const previousShowProducts = this.opts.showProducts;\n const previousOverrides = this.opts.optionOverrides;\n Object.assign(this.opts, opts);\n if (\"optionOverrides\" in opts && opts.optionOverrides !== previousOverrides) {\n // A swapped record must not be answered by the old one: an ask still in\n // flight is abandoned and the pill on screen is asked again through the\n // new record. Wrappers hand over a stable proxy (React) or forward on\n // reference change (Angular), so this fires for a real swap, not a\n // re-render.\n this.optionSource.refresh();\n }\n if (\n (\"products\" in opts && opts.products !== previousProducts) ||\n (\"showProducts\" in opts && opts.showProducts !== previousShowProducts)\n ) {\n // The strip's source changed. A swapped or added integration clears\n // whatever was on screen (the next fetch repopulates from the new\n // config); a removed one hands the strip back to the server's items, and\n // toggling `showProducts` empties or restores it. `store.set` here also\n // invalidates the derived memo, so isDropdownOpen re-evaluates without a\n // gone strip holding it open.\n this.productsController.resync();\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 and the icons appear/disappear without\n // another mutation.\n opts.showSkipButton !== undefined ||\n opts.showOptionIcons !== undefined ||\n opts.showChipIcons !== 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 // A range is answered in two taps of the same calendar. The first records\n // where the span starts and commits nothing; the second builds the one\n // option that carries the whole span and re-enters here, where every\n // commit path below handles it exactly as it handles a single date. The\n // guard is the cell's own `aiaDate` metadata, which only a calendar cell\n // has — so the synthetic range option falls straight through rather than\n // starting a third tap.\n if (state.activeFormatType === \"date-range\") {\n const iso = cellIso(option);\n if (iso) {\n const pending = state.dateRangeStart;\n if (!pending) {\n this.store.set({ pendingRangeStart: { iso, key: dateViewKey(state) } });\n return;\n }\n this.store.set({ pendingRangeStart: null });\n this.selectOption(buildRangeOption(pending, iso));\n return;\n }\n }\n\n // Picking a date for a span the server identified: swap the user's own\n // words for the canonical form and promote it to a real answer.\n if (state.editingIdentified) {\n this.commitIdentifiedDate(option);\n return;\n }\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 // \"The other options the user passed over\" is a meaningful signal for a\n // list someone read; for a calendar it is the other ~30 days of the\n // month, which says nothing and would dominate the payload.\n other_options: isCalendarFormat(state.activeFormatType) ? [] : 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 press 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 // 170ms press animation 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 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 showOptionIcons() {\n return self.opts.showOptionIcons ?? true;\n },\n get showChipIcons() {\n return self.opts.showChipIcons ?? true;\n },\n get optionsPosition() {\n return self.opts.optionsPosition ?? \"below\";\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 showPreviousMonth: () => this.showPreviousMonth(),\n showNextMonth: () => this.showNextMonth(),\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 get showOptionIcons() {\n return self.opts.showOptionIcons ?? true;\n },\n get optionsPosition() {\n return self.opts.optionsPosition ?? \"below\";\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 showPreviousMonth: () => this.showPreviousMonth(),\n showNextMonth: () => this.showNextMonth(),\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 // A consumer override answers the re-edit too, and its answer for the\n // phrase being typed may still be on its way: judging \"no match\" by the\n // PREVIOUS answer would exit the edit under a search that was about to\n // succeed. Wait for the miss — the override answering empty for this\n // phrase is what makes it the server's turn.\n if (this.optionSource.owns(s.editingParam.suggestionType, s.optionQuery)) 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 /**\n * Replace an identified date span with the day the user picked.\n *\n * The span becomes an ordinary completed param, so from here on it is\n * indistinguishable from a date answered through a pill — same bold\n * rendering, same `{{TYPE_N}}` token in `raw_query`, same `completed_params`\n * entry. The identified param it came from is dropped: its text no longer\n * exists in the input, and leaving it would have the reconciler drop it a\n * beat later anyway.\n */\n private commitIdentifiedDate(option: SuggestionOption) {\n const state = this.store.get();\n const editing = state.editingIdentified;\n if (!editing) return;\n // The offsets were captured when the picker opened. If anything moved the\n // text since, they bound something else now — and splicing at them would\n // rewrite a part of the query the user never touched. Every known path\n // clears this state, so reaching here means a new one appeared; abandon the\n // pick rather than corrupt the input.\n if (state.text.slice(editing.anchor, editing.tail) !== editing.text) {\n this.store.set({ editingIdentified: null, activeDropdownIndex: -1 });\n return;\n }\n\n const before = state.text.slice(0, editing.anchor);\n const after = state.text.slice(editing.tail);\n // Same capitalisation and trailing-space conventions the re-edit path uses,\n // so a date committed this way is spaced like every other answer.\n const optionText =\n editing.anchor === 0 && option.text.length > 0\n ? option.text[0].toUpperCase() + option.text.slice(1)\n : option.text;\n const needsTrailingSpace = after.length === 0 || after[0] !== \" \";\n const replacement = needsTrailingSpace ? `${optionText} ` : optionText;\n const newText = before + replacement + after;\n // Caret goes to the END of the input — same rule as the two re-edit\n // commit paths (ReEditManager.selectOption / promoteEdit): fetchNow below\n // asks for the next suggestion, and the dropdown only opens with the\n // caret at the end of the text, so a date answered mid-text must not\n // strand the caret after the replacement.\n const caretPos = newText.length;\n\n const newParam: CompletedParamState = {\n id: crypto.randomUUID(),\n placeholder: \"\",\n type: editing.type,\n text: optionText,\n kind: option.kind,\n suggestionType: editing.type,\n suggestionPlaceholder: identifiedParamLabel(editing.type),\n // No cached options to inherit — a calendar is what answers this param,\n // and re-editing it builds a fresh month rather than filtering a list.\n options: [],\n metadata: option.metadata,\n icon: option.icon,\n icon_svg: option.icon_svg,\n };\n\n this.fireTelemetry(\"option\", {\n raw_query: buildQuery(state.text, state.completedParams).rawQuery,\n selected_option: option.text,\n // A calendar's \"other options\" are the rest of the month, which says\n // nothing — same reasoning as the pill-driven date path.\n other_options: [],\n });\n\n this.store.set({\n text: newText,\n completedParams: [...state.completedParams, newParam],\n identifiedParams: state.identifiedParams.filter((p) => p.id !== editing.id),\n newParamId: newParam.id,\n filterBase: caretPos,\n caretOffset: caretPos,\n editingIdentified: null,\n activeDropdownIndex: -1,\n pillTapped: false,\n skipNextFetch: true,\n inSelectionAnimation: true,\n });\n this.startSelectionAnimationTimer();\n this.scheduleSetCursor(caretPos);\n // Answering a parameter goes back to the server on the same terms as any\n // other answer, so what it suggests next accounts for the date just given.\n this.fetchNow();\n }\n\n private maybePromoteExactMatch(newValue: string) {\n const s = this.store.get();\n // A calendar pill's options are on the wire but are never shown — the\n // calendar replaced them, for a span exactly as for a single day. Promoting\n // one because the typed text happens to equal it would answer the pill with\n // something the user never saw.\n if (isCalendarFormat(s.activeFormatType)) return;\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":"ubAAA,IAAAA,GAAA,GAAAC,GAAAD,GAAA,oBAAAE,GAAA,oBAAAC,GAAA,wBAAAC,GAAA,0BAAAC,GAAA,mBAAAC,GAAA,8BAAAC,GAAA,2BAAAC,GAAA,yBAAAC,GAAA,yBAAAC,GAAA,yBAAAC,GAAA,4BAAAC,GAAA,4BAAAC,GAAA,6BAAAC,EAAA,4BAAAC,GAAA,wBAAAC,GAAA,8BAAAC,GAAA,wBAAAC,GAAA,oBAAAC,GAAA,sBAAAC,GAAA,4BAAAC,GAAA,uBAAAC,GAAA,yBAAAC,GAAA,uBAAAC,GAAA,gCAAAC,GAAA,8BAAAC,GAAA,uBAAAC,GAAA,mBAAAC,GAAA,cAAAC,GAAA,sBAAAC,GAAA,wBAAAC,GAAA,qBAAAC,GAAA,eAAAC,EAAA,sBAAAC,EAAA,YAAAC,GAAA,YAAAC,EAAA,6BAAAC,GAAA,gBAAAC,GAAA,kBAAAC,GAAA,kBAAAC,GAAA,qBAAAC,GAAA,qBAAAC,GAAA,uBAAAC,EAAA,eAAAC,GAAA,oBAAAC,GAAA,oBAAAC,EAAA,kBAAAC,GAAA,yBAAAC,EAAA,qBAAAC,EAAA,gCAAAC,GAAA,YAAAC,EAAA,uBAAAC,GAAA,eAAAC,GAAA,gCAAAC,GAAA,uBAAAC,GAAA,gBAAAC,GAAA,8BAAAC,GAAA,+BAAAC,GAAA,cAAAC,GAAA,mBAAAC,EAAA,wBAAAC,EAAA,oBAAAC,GAAA,oBAAAC,GAAA,6BAAAC,GAAA,2BAAAC,GAAA,6BAAAC,GAAA,0BAAAC,GAAA,sBAAAC,EAAA,0BAAAC,GAAA,0BAAAC,GAAA,wBAAAC,GAAA,wBAAAC,GAAA,qBAAAC,EAAA,oBAAAC,EAAA,2BAAAC,EAAA,qBAAAC,GAAA,sBAAAC,IAAA,eAAAC,GAAA9E,ICgCO,SAAS+E,GAAkBC,EAAwB,CACxD,OAAOA,EACJ,QAAQ,qBAAsB,OAAO,EACrC,MAAM,eAAe,EACrB,OAAO,OAAO,EACd,IAAKC,GAAUA,EAAM,YAAY,CAAC,CACvC,CAiBO,SAASC,EAAqBF,EAAsB,CACzD,IAAMG,EAAQJ,GAAkBC,CAAI,EAGpC,OAAOG,EAAM,OAAS,EAAIA,EAAM,KAAK,GAAG,EAAIH,CAC9C,CC1CO,IAAMI,EAAc,CACzB,UACA,WACA,QACA,QACA,MACA,OACA,OACA,SACA,YACA,UACA,WACA,UACF,EAGaC,GAAiB,CAAC,IAAK,IAAK,IAAK,IAAK,IAAK,IAAK,GAAG,EAGnDC,GAAgB,CAC3B,SACA,SACA,UACA,YACA,WACA,SACA,UACF,EAGaC,GAAgB,UAEhBC,GAAgB,SAEvBC,GAAO,GAAsB,OAAO,CAAC,EAAE,SAAS,EAAG,GAAG,EASrD,SAASC,EAAQC,EAAiB,CACvC,MAAO,GAAGA,EAAE,YAAY,CAAC,IAAIF,GAAIE,EAAE,SAAS,EAAI,CAAC,CAAC,IAAIF,GAAIE,EAAE,QAAQ,CAAC,CAAC,EACxE,CAGA,SAASC,GAAWD,EAAe,CACjC,OAAO,IAAI,KAAKA,EAAE,YAAY,EAAGA,EAAE,SAAS,EAAGA,EAAE,QAAQ,CAAC,CAC5D,CAOA,SAASE,GAAcF,EAAiB,CACtC,IAAMG,EAAKF,GAAWD,CAAC,EAAE,QAAQ,EAAIC,GAAW,IAAI,IAAM,EAAE,QAAQ,EACpE,OAAO,KAAK,MAAME,EAAK,KAAU,CACnC,CAaO,SAASC,GAAWJ,EAAiB,CAC1C,IAAMK,EAAOH,GAAcF,CAAC,EAC5B,OAAIK,GAAQ,GAAKA,EAAO,EAAUV,GAAcK,EAAE,OAAO,CAAC,EACnDM,EAAmBN,CAAC,CAC7B,CAYO,SAASM,EAAmBN,EAAiB,CAClD,OAAIA,EAAE,YAAY,IAAM,IAAI,KAAK,EAAE,YAAY,EACtC,GAAGP,EAAYO,EAAE,SAAS,CAAC,CAAC,IAAIA,EAAE,QAAQ,CAAC,GAE7C,GAAGP,EAAYO,EAAE,SAAS,CAAC,CAAC,IAAIA,EAAE,QAAQ,CAAC,IAAIA,EAAE,YAAY,CAAC,EACvE,CAoBO,SAASO,GAAUC,EAA8C,CACtE,GAAI,CAACA,EAAM,OAAO,KAClB,IAAMC,EAAUd,GAAc,UAC3Be,GAASA,EAAK,YAAY,IAAMF,EAAK,KAAK,EAAE,YAAY,CAC3D,EACA,GAAIC,GAAW,EAAG,CAChB,IAAME,EAAMV,GAAW,IAAI,IAAM,EAC3BW,GAASH,EAAUE,EAAI,OAAO,EAAI,GAAK,EAC7C,OAAO,IAAI,KAAKA,EAAI,YAAY,EAAGA,EAAI,SAAS,EAAGA,EAAI,QAAQ,EAAIC,CAAK,CAC1E,CACA,IAAMC,EAAI,iDAAiD,KAAKL,CAAI,EACpE,GAAI,CAACK,EAAG,OAAO,KACf,IAAMC,EAAQrB,EAAY,UAAWiB,GAASA,EAAK,YAAY,IAAMG,EAAE,CAAC,EAAE,YAAY,CAAC,EACvF,GAAIC,EAAQ,EAAG,OAAO,KACtB,IAAMC,EAAM,OAAOF,EAAE,CAAC,CAAC,EACjBG,EAAOH,EAAE,CAAC,EAAI,OAAOA,EAAE,CAAC,CAAC,EAAI,IAAI,KAAK,EAAE,YAAY,EACpDb,EAAI,IAAI,KAAKgB,EAAMF,EAAOC,CAAG,EAGnC,OAAIf,EAAE,SAAS,IAAMc,GAASd,EAAE,QAAQ,IAAMe,EAAY,KACnDf,CACT,CAGO,SAASiB,GAAWC,EAA6B,CACtD,MAAO,GAAGzB,EAAYyB,EAAK,KAAK,CAAC,IAAIA,EAAK,IAAI,EAChD,CAEO,SAASC,IAA2B,CACzC,IAAMR,EAAM,IAAI,KAChB,MAAO,CAAE,KAAMA,EAAI,YAAY,EAAG,MAAOA,EAAI,SAAS,CAAE,CAC1D,CAEO,SAASS,GAAOpB,EAAwB,CAC7C,MAAO,CAAE,KAAMA,EAAE,YAAY,EAAG,MAAOA,EAAE,SAAS,CAAE,CACtD,CAGO,SAASqB,GAAUH,EAAqBN,EAA8B,CAC3E,IAAMZ,EAAI,IAAI,KAAKkB,EAAK,KAAMA,EAAK,MAAQN,EAAO,CAAC,EACnD,MAAO,CAAE,KAAMZ,EAAE,YAAY,EAAG,MAAOA,EAAE,SAAS,CAAE,CACtD,CAOA,SAASsB,IAA8B,CACrC,MAAO,CAAE,KAAM,GAAI,YAAa,GAAO,KAAM,IAAK,CACpD,CAwBO,SAASC,GAAiBC,EAAyC,CACxE,IAAMC,EAAO,IAAI,KAAKD,EAAK,KAAMA,EAAK,MAAO,CAAC,EAAE,OAAO,EACjDE,EAAc,IAAI,KAAKF,EAAK,KAAMA,EAAK,MAAQ,EAAG,CAAC,EAAE,QAAQ,EAE7DG,EAA4B,CAAC,EACnC,QAASC,EAAI,EAAGA,EAAIH,EAAMG,IAAKD,EAAM,KAAKL,GAAU,CAAC,EACrD,QAASO,EAAM,EAAGA,GAAOH,EAAaG,IAAO,CAC3C,IAAMC,EAAO,IAAI,KAAKN,EAAK,KAAMA,EAAK,MAAOK,CAAG,EAChDF,EAAM,KAAK,CACT,KAAMI,GAAWD,CAAI,EACrB,YAAa,GACb,KAAM,KACN,SAAU,CAAE,CAACE,EAAa,EAAGC,EAAQH,CAAI,EAAG,CAACI,EAAa,EAAGL,CAAI,CACnE,CAAC,CACH,CACA,KAAOF,EAAM,OAASQ,GAAe,SAAW,GAAGR,EAAM,KAAKL,GAAU,CAAC,EACzE,OAAOK,CACT,CAOO,SAASS,GAAoBC,EAAgD,CAClF,IAAMC,EAAIC,GAAUF,CAAI,EACxB,OAAOC,EAAIL,EAAQK,CAAC,EAAI,IAC1B,CAGO,SAASE,EAAQC,EAAqD,CAC3E,IAAMC,EAAMD,GAAQ,WAAWT,EAAa,EAC5C,OAAO,OAAOU,GAAQ,SAAWA,EAAM,IACzC,CAGO,SAASC,GAAQF,EAAqD,CAC3E,IAAMZ,EAAMY,GAAQ,WAAWP,EAAa,EAC5C,OAAO,OAAOL,GAAQ,SAAWA,EAAM,IACzC,CC1NA,SAASe,GAAaC,EAAsB,CAC1C,OAAOA,EAAK,QAAQ,0BAA2B,IAAI,CACrD,CAGA,SAASC,GAAWC,EAAsB,CACxC,IAAMC,EAAID,EAAK,YAAY,EAAE,QAAQ,MAAO,EAAE,EAI9C,OAAIC,EAAE,OAAS,EAAU,GAGlBC,EAAY,UAAWC,GAAMA,EAAE,YAAY,EAAE,WAAWF,CAAC,CAAC,CACnE,CAeA,SAASG,GAAiBC,EAAeC,EAAaC,EAA0B,CAC9E,IAAMC,EAAW,IAAI,KAAKD,EAAM,YAAY,EAAGF,EAAOC,CAAG,EACzD,GAAIE,EAAS,SAAS,IAAMH,GAASG,EAAS,QAAQ,IAAMF,EAAK,OAAO,KACxE,IAAMG,EAAe,IAAI,KAAKF,EAAM,YAAY,EAAGA,EAAM,SAAS,EAAGA,EAAM,QAAQ,CAAC,EACpF,GAAIC,GAAYC,EAAc,OAAOD,EACrC,IAAME,EAAW,IAAI,KAAKH,EAAM,YAAY,EAAI,EAAGF,EAAOC,CAAG,EAC7D,OAAOI,EAAS,SAAS,IAAML,GAASK,EAAS,QAAQ,IAAMJ,EAAMI,EAAW,IAClF,CAGA,SAASC,GAAUC,EAAcP,EAAeC,EAA0B,CACxE,IAAMO,EAAI,IAAI,KAAKD,EAAMP,EAAOC,CAAG,EACnC,OAAOO,EAAE,SAAS,IAAMR,GAASQ,EAAE,QAAQ,IAAMP,EAAMO,EAAI,IAC7D,CAkBO,SAASC,EACdhB,EACAiB,EAAyB,CAAC,EACX,CACf,GAAI,CAACjB,EAAM,OAAO,KAClB,IAAMS,EAAQQ,EAAK,OAAS,IAAI,KAC1BC,EAAInB,GAAaC,EAAK,KAAK,EAAE,YAAY,CAAC,EAAE,QAAQ,OAAQ,GAAG,EAE/DmB,EAAM,gCAAgC,KAAKD,CAAC,EAClD,GAAIC,EAAK,CACP,IAAMJ,EAAIF,GAAU,OAAOM,EAAI,CAAC,CAAC,EAAG,OAAOA,EAAI,CAAC,CAAC,EAAI,EAAG,OAAOA,EAAI,CAAC,CAAC,CAAC,EACtE,OAAOJ,EAAIK,EAAQL,CAAC,EAAI,IAC1B,CAGA,IAAMM,EAAa,yCAAyC,KAAKH,CAAC,EAClE,GAAIG,EAAY,CACd,IAAMd,EAAQN,GAAWoB,EAAW,CAAC,CAAC,EACtC,GAAId,GAAS,EAAG,OAAOe,GAAUf,EAAO,OAAOc,EAAW,CAAC,CAAC,EAAGA,EAAW,CAAC,EAAGZ,CAAK,CACrF,CAGA,IAAMc,EAAW,iDAAiD,KAAKL,CAAC,EACxE,GAAIK,EAAU,CACZ,IAAMhB,EAAQN,GAAWsB,EAAS,CAAC,CAAC,EACpC,GAAIhB,GAAS,EAAG,OAAOe,GAAUf,EAAO,OAAOgB,EAAS,CAAC,CAAC,EAAGA,EAAS,CAAC,EAAGd,CAAK,CACjF,CAIA,IAAMe,EAAU,iDAAiD,KAAKN,CAAC,EACvE,GAAIM,EAAS,CACX,IAAMC,EAAI,OAAOD,EAAQ,CAAC,CAAC,EACrBE,EAAI,OAAOF,EAAQ,CAAC,CAAC,EAC3B,OAAIC,EAAI,IAAMC,GAAK,GAAWJ,GAAUI,EAAI,EAAGD,EAAGD,EAAQ,CAAC,EAAGf,CAAK,EAC/DiB,EAAI,IAAMD,GAAK,GAAWH,GAAUG,EAAI,EAAGC,EAAGF,EAAQ,CAAC,EAAGf,CAAK,EAC5D,IACT,CAEA,OAAO,IACT,CAEA,SAASa,GACPf,EACAC,EACAmB,EACAlB,EACe,CACf,GAAIkB,EAAU,CAGZ,GAAIA,EAAS,SAAW,EAAG,OAAO,KAClC,IAAMZ,EAAIF,GAAU,OAAOc,CAAQ,EAAGpB,EAAOC,CAAG,EAChD,OAAOO,EAAIK,EAAQL,CAAC,EAAI,IAC1B,CACA,IAAMA,EAAIT,GAAiBC,EAAOC,EAAKC,CAAK,EAC5C,OAAOM,EAAIK,EAAQL,CAAC,EAAI,IAC1B,CAWO,SAASa,GACdC,EACAZ,EAAyB,CAAC,EACX,CACf,IAAMa,EAAaD,EAAM,QACzB,OAAI,OAAOC,GAAe,UAAY,sBAAsB,KAAKA,CAAU,EAAUA,EAC9Ed,EAAea,EAAM,KAAMZ,CAAI,CACxC,CC5IO,IAAMc,GAAkB,MAGlBC,GAAwB,oBAExBC,GAAsB,kBAG5B,SAASC,GAAaC,EAAWC,EAAsB,CAE5D,OAAOD,GAAKC,EAAI,CAAE,MAAOD,EAAG,IAAKC,CAAE,EAAI,CAAE,MAAOA,EAAG,IAAKD,CAAE,CAC5D,CAEA,IAAME,GAAS,sBAGf,SAASC,GAAYC,EAA0B,CAC7C,GAAI,CAACF,GAAO,KAAKE,CAAG,EAAG,OAAO,KAC9B,GAAM,CAACC,EAAGC,EAAGC,CAAC,EAAIH,EAAI,MAAM,GAAG,EAAE,IAAI,MAAM,EACrCI,EAAO,IAAI,KAAKH,EAAGC,EAAI,EAAGC,CAAC,EACjC,OAAOC,EAAK,SAAS,IAAMF,EAAI,GAAKE,EAAK,QAAQ,IAAMD,EAAIC,EAAO,IACpE,CAWA,IAAMC,GAAiB,4CACjBC,GAAiB,SAEvB,SAASC,GAAYC,EAAuC,CAC1D,IAAMC,EAA+B,CAAC,EACtC,QAAWC,IAAM,CAACL,GAAgBC,EAAc,EAAG,CAEjDI,EAAG,UAAY,EACf,IAAIC,EAAQD,EAAG,KAAKF,CAAI,EACxB,KAAOG,IAAU,MACfF,EAAI,KAAK,CAACD,EAAK,MAAM,EAAGG,EAAM,KAAK,EAAGH,EAAK,MAAMG,EAAM,MAAQA,EAAM,CAAC,EAAE,MAAM,CAAC,CAAC,EAChFA,EAAQD,EAAG,KAAKF,CAAI,CAExB,CACA,OAAOC,CACT,CA2BO,SAASG,EACdJ,EACAK,EAAyB,CAAC,EACR,CAClB,GAAI,CAACL,EAAM,OAAO,KAClB,IAAMM,EAAQ,IAAI,IACdC,EAA0B,KAC9B,OAAW,CAACC,EAAMC,CAAK,IAAKV,GAAYC,EAAK,KAAK,CAAC,EAAG,CACpD,IAAMU,EAAQC,EAAeH,EAAK,KAAK,EAAGH,CAAI,EAC9C,GAAI,CAACK,EAAO,SACZ,IAAME,EAAMD,EAAeF,EAAM,KAAK,EAAGJ,CAAI,EACzC,CAACO,GAAOA,EAAMF,IAClBJ,EAAM,IAAI,GAAGI,CAAK,IAAIE,CAAG,EAAE,EAC3BL,EAAQ,CAAE,MAAAG,EAAO,IAAAE,CAAI,EACvB,CAEA,OAAON,EAAM,OAAS,EAAIC,EAAQ,IACpC,CAcO,SAASM,GAAgBN,EAA0B,CACxD,IAAMG,EAAQnB,GAAYgB,EAAM,KAAK,EAC/BK,EAAMrB,GAAYgB,EAAM,GAAG,EACjC,MAAI,CAACG,GAAS,CAACE,EAAY,GACvBL,EAAM,QAAUA,EAAM,IAAYO,EAAmBJ,CAAK,EACvD,GAAGI,EAAmBJ,CAAK,CAAC,GAAG1B,EAAe,GAAG8B,EAAmBF,CAAG,CAAC,EACjF,CAWO,SAASG,GAAiB3B,EAAWC,EAA6B,CACvE,IAAMkB,EAAQpB,GAAaC,EAAGC,CAAC,EAC/B,MAAO,CACL,KAAMwB,GAAgBN,CAAK,EAC3B,YAAa,GACb,KAAM,KACN,SAAU,CACR,CAACtB,EAAqB,EAAGsB,EAAM,MAC/B,CAACrB,EAAmB,EAAGqB,EAAM,GAC/B,CACF,CACF,CAWO,SAASS,EACdC,EACAZ,EAAyB,CAAC,EACR,CAClB,IAAMK,EAAQO,GAAO,WAAWhC,EAAqB,EAC/C2B,EAAMK,GAAO,WAAW/B,EAAmB,EACjD,OACE,OAAOwB,GAAU,UACjB,OAAOE,GAAQ,UACftB,GAAO,KAAKoB,CAAK,GACjBpB,GAAO,KAAKsB,CAAG,EAERzB,GAAauB,EAAOE,CAAG,EACzBR,EAAoBa,GAAO,KAAMZ,CAAI,CAC9C,CCpKO,SAASa,EAAiBC,EAA6B,CAC5D,OAAOA,IAAW,QAAUA,IAAW,YACzC,CAGA,IAAMC,GAAc,IAAI,IAAI,CAAC,OAAQ,OAAO,CAAC,EAGvCC,GAAe,IAAI,IAAI,CAAC,QAAS,SAAU,OAAQ,SAAU,QAAQ,CAAC,EAYtEC,GAAwB,EAiB9B,SAASC,GAAiBC,EAA2D,CACnF,IAAMC,EAA2B,CAAE,OAAQ,EAAG,MAAO,CAAE,EACvD,GAAI,CAACD,EAAS,OAAOC,EACrB,QAAWC,KAAUF,EACdE,EAAO,cAIRC,EAAoBD,EAAO,IAAI,IAAM,KAAMD,EAAO,OAAS,EACtDG,EAAeF,EAAO,IAAI,IAAM,OAAMD,EAAO,QAAU,IAElE,OAAOA,CACT,CAsBO,SAASI,EACdC,EACY,CACZ,GAAI,CAACA,EAAQ,MAAO,UACpB,IAAMC,EAAcD,EAAoC,WACxD,GAAIC,IAAe,QAAUA,IAAe,WAAaA,IAAe,aACtE,OAAOA,EAOT,IAAMN,EAASF,GAAiBO,EAAO,OAAO,EAS9C,GAAIL,EAAO,OAASA,EAAO,OAASH,IAAyBG,EAAO,MAAQA,EAAO,OACjF,MAAO,aAGT,IAAMO,EAASC,GAAkBH,EAAO,IAAI,EAC5C,OAAIE,EAAO,KAAME,GAAUd,GAAY,IAAIc,CAAK,CAAC,EACxCF,EAAO,KAAME,GAAUb,GAAa,IAAIa,CAAK,CAAC,EAAI,aAAe,OAOnET,EAAO,QAAUH,GAAwB,OAAS,SAC3D,CCtHO,IAAMa,GAAN,KAAmB,CAKxB,YAAoBC,EAA2B,CAA3B,YAAAA,EAJpB,KAAQ,QAAyB,KACjC,KAAQ,UAA2B,KACnC,KAAQ,gBAA0C,KAG5CA,EAAO,cACT,KAAK,QAAUA,EAAO,YAE1B,CAGA,MAAM,SAASC,EAAe,GAAwB,CACpD,GAAI,CAACA,GAAgB,KAAK,SAAW,CAAC,KAAK,UAAU,EACnD,OAAO,KAAK,QAEd,GAAI,CAACA,GAAgB,KAAK,gBACxB,OAAO,KAAK,gBAEd,KAAK,gBAAkB,KAAK,QAAQ,EACpC,GAAI,CACF,OAAO,MAAM,KAAK,eACpB,QAAE,CACA,KAAK,gBAAkB,IACzB,CACF,CAEA,MAAc,SAA2B,CACvC,IAAMC,EAAS,MAAM,KAAK,OAAO,eAAe,EAChD,YAAK,QAAUA,EAAO,YACtB,KAAK,UAAYA,EAAO,WAAa,KAC9B,KAAK,OACd,CAEQ,WAAqB,CAC3B,OAAI,KAAK,WAAa,KAAa,GAC5B,KAAK,IAAI,GAAK,KAAK,UAAY,GACxC,CACF,ECvCO,IAAMC,GAAqB,kCACrBC,GAA2B,GAAGD,EAAkB,eAGvDE,GAAgB,IAAI,QAEnB,SAASC,GAAoBC,EAAiD,CACnF,OAAOA,GAAQ,OAAS,aAC1B,CAEO,SAASC,GAAgBD,EAA8C,CAC5E,GAAI,GAACA,GAAUD,GAAoBC,CAAM,GACzC,OAAOA,CACT,CAEO,SAASE,GAAgBF,EAAyC,CACvE,IAAIG,EAAUL,GAAc,IAAIE,EAAO,cAAc,EACrD,OAAKG,IACHA,EAAU,IAAIC,GAAaJ,CAAM,EACjCF,GAAc,IAAIE,EAAO,eAAgBG,CAAO,GAE3CA,CACT,CAMO,SAASE,GAAaC,EAA+C,CAC1E,MAAO,CACL,eAAgB,mBAChB,GAAIA,GAAW,eAAiB,CAAE,mBAAoBA,EAAU,aAAc,EAC9E,GAAGA,GAAW,OAChB,CACF,CAQO,SAASC,GAAsBD,EAAsC,CAC1E,IAAME,EAAeP,GAAgBK,CAAS,EACxCG,EAASD,GAAc,OAC7B,OAAKC,GACUD,GAAc,YAAc,YACzB,QAAU,SAAS,KAAKC,CAAM,CAAC,GAAK,UAAUA,CAAM,GAFlD,IAGtB,CC1CO,SAASC,EAAuBC,EAAmD,CACxF,OAAOA,EAAO,IAAKC,IAAO,CAAE,KAAMA,EAAE,KAAM,MAAOA,EAAE,IAAK,EAAE,CAC5D,CCLO,IAAMC,GAAqB,UAe3B,SAASC,EACdC,EACAC,EACkB,CAClB,GAAIA,EAAQ,SAAW,EAAG,OAAOD,EACjC,IAAME,EAAc,IAAI,IAAIF,EAAU,IAAKG,GAAMA,EAAE,IAAI,CAAC,EAClDC,EAAUH,EACb,OAAQE,GAAM,CAACD,EAAY,IAAIC,EAAE,IAAI,CAAC,EACtC,IAAqBA,IAAO,CAC3B,YAAa,GACb,KAAMA,EAAE,KACR,KAAML,GACN,KAAM,IACR,EAAE,EACJ,OAAOM,EAAQ,OAAS,EAAI,CAAC,GAAGJ,EAAW,GAAGI,CAAO,EAAIJ,CAC3D,CCfA,IAAMK,GAAc,SAEhBC,GAAsB,GAE1B,SAASC,IAA4B,CACnC,OAAO,OAAO,WAAW,CAC3B,CAEA,SAASC,GAAYC,EAA4BC,EAAsC,CACrF,MAAO,CACL,YAAaD,EAAM,YACnB,KAAMA,EAAM,KACZ,GAAIC,GAAe,CAAE,KAAMD,EAAM,IAAK,EACtC,KAAMA,EAAM,IACd,CACF,CAEA,SAASE,GACPC,EACAC,EACAH,EACAI,EACAC,EACAC,EACAC,EACAC,EACqB,CACrB,IAAMC,EAAWN,EAAgB,KAC9BO,GAAMA,EAAE,OAAS,WAAaA,EAAE,UAAU,qBAC7C,GAAG,UAAU,sBACPC,EAAsB,OAAOF,GAAa,SAAWA,EAAW,OAEtE,MAAO,CACL,KAAM,CACJ,UAAWP,EAGX,iBAAkBU,EAChBT,EAAgB,IAAKO,GAAMZ,GAAYY,EAAGV,CAAW,CAAC,EACtDO,GAAiB,CAAC,CACpB,EACA,GAAIF,GACFA,EAAiB,OAAS,GAAK,CAC7B,kBAAmBQ,EAAuBR,CAAgB,CAC5D,EACF,GAAIC,GACFA,EAAkB,OAAS,GAAK,CAC9B,mBAAoBA,CACtB,EACF,GAAIK,GAAuB,MAAQ,CAAE,sBAAuBA,CAAoB,EAChF,GAAIH,IAAsB,QAAa,CAAE,mBAAoBA,CAAkB,CACjF,EACA,KAAM,CACJ,WAAYX,GAAkB,EAC9B,WAAY,IAAI,KAAK,EAAE,YAAY,EACnC,SAAU,OAAO,UAAc,IAAc,UAAU,SAAW,QAClE,eAAgBF,GAChB,WAAYS,CACd,CACF,CACF,CAEA,eAAeU,GACbC,EACAC,EACAC,EACAC,EACAC,EACmB,CACnB,OAAO,MAAMJ,EAAU,CACrB,OAAQ,OACR,QAAS,CAAE,GAAGC,EAAS,cAAe,UAAUC,CAAK,EAAG,EACxD,KAAAC,EACA,OAAAC,CACF,CAAC,CACH,CAEA,eAAsBC,GACpBlB,EACAC,EACAkB,EAc+B,CAC/B,IAAMC,EAAYD,EAAQ,UACpBrB,EAAc,CAACqB,EAAQ,kBACvBH,EAAOjB,GACXC,EACAC,EACAH,EACAqB,EAAQ,UACRA,EAAQ,iBACRA,EAAQ,kBACRA,EAAQ,cACRA,EAAQ,iBACV,EACML,EAAUO,GAAaD,CAAS,EAChCP,EAAWO,GAAW,UAAYE,GAClCC,EAAW,KAAK,UAAUP,CAAI,EAGpC,GAAIQ,GAAoBJ,CAAS,EAAG,CAClC,IAAMK,EAAUC,GAAgBN,CAAS,EACnCL,EAAQ,MAAMU,EAAQ,SAAS,EAEjCE,EAAW,MAAMf,GAAQC,EAAUC,EAASC,EAAOQ,EAAUJ,EAAQ,MAAM,EAG/E,GAAIQ,EAAS,SAAW,IAAK,CAC3B,IAAMC,EAAW,MAAMH,EAAQ,SAAS,EAAI,EAC5CE,EAAW,MAAMf,GAAQC,EAAUC,EAASc,EAAUL,EAAUJ,EAAQ,MAAM,CAChF,CAEA,GAAI,CAACQ,EAAS,GACZ,MAAM,IAAI,MAAM,cAAcA,EAAS,MAAM,IAAIA,EAAS,UAAU,EAAE,EAGxE,OAAOA,EAAS,KAAK,CACvB,CAGA,IAAME,EAAaC,GAAsBV,CAAS,EAC9C,CAACS,GAAc,CAACnC,KAClBA,GAAsB,GAEtB,QAAQ,KACN,iGACF,GAEEmC,IAAYf,EAAQ,cAAgBe,GAExC,IAAMF,EAAW,MAAM,MAAMd,EAAU,CACrC,OAAQ,OACR,QAAAC,EACA,KAAMS,EACN,OAAQJ,EAAQ,MAClB,CAAC,EAED,GAAI,CAACQ,EAAS,GACZ,MAAM,IAAI,MAAM,cAAcA,EAAS,MAAM,IAAIA,EAAS,UAAU,EAAE,EAGxE,OAAOA,EAAS,KAAK,CACvB,CC5JO,SAASI,EAAWC,EAAcC,EAA0D,CACjG,IAAIC,EAASF,EACPG,EAAqC,CAAC,EACtCC,EAAuC,CAAC,EACxCC,EAAmD,CAAC,EACtDC,EAAM,EAEV,QAAWC,KAASN,EAAiB,CACnC,IAAMO,GAASL,EAAWI,EAAM,IAAI,GAAK,GAAK,EAC9CJ,EAAWI,EAAM,IAAI,EAAIC,EAGzB,IAAMC,EAAc,KADJF,EAAM,KAAK,YAAY,EAAE,QAAQ,OAAQ,GAAG,CAC5B,IAAIC,CAAK,KAInCE,EAAaC,GAAyB,CAC1C,IAAIC,EAAMV,EAAO,QAAQK,EAAM,KAAMI,CAAI,EACzC,KACEC,IAAQ,IACRP,EAAe,KAAMQ,GAAMD,EAAMC,EAAE,KAAOD,EAAML,EAAM,KAAK,OAASM,EAAE,KAAK,GAE3ED,EAAMV,EAAO,QAAQK,EAAM,KAAMK,EAAM,CAAC,EAE1C,OAAOA,CACT,EAGIE,EAAQJ,EAAUJ,CAAG,EAGzB,GAFIQ,IAAU,KAAIA,EAAQJ,EAAU,CAAC,GAEjCI,IAAU,GAAI,CAChBZ,EAASA,EAAO,MAAM,EAAGY,CAAK,EAAIL,EAAcP,EAAO,MAAMY,EAAQP,EAAM,KAAK,MAAM,EACtF,IAAMQ,EAAQN,EAAY,OAASF,EAAM,KAAK,OAG9C,QAAWM,KAAKR,EACVQ,EAAE,OAASC,EAAQP,EAAM,KAAK,SAChCM,EAAE,OAASE,EACXF,EAAE,KAAOE,GAGbV,EAAe,KAAK,CAAE,MAAOS,EAAO,IAAKA,EAAQL,EAAY,MAAO,CAAC,EAIrEH,EAAMQ,GAASR,EAAMQ,EAAQL,EAAY,OAASH,EAAMS,CAC1D,CAEAX,EAAc,KAAK,CAAE,GAAGG,EAAO,YAAAE,CAAY,CAAC,CAC9C,CAEA,MAAO,CAAE,SAAUP,EAAQ,gBAAiBE,CAAc,CAC5D,CChEO,SAASY,EACdC,EACAC,EACAC,EACQ,CACR,OAAID,EAAa,GAAK,CAACC,EAAwBD,EAC3CD,EAAK,YAAY,EAAE,WAAWE,EAAgB,YAAY,CAAC,EACtDA,EAAgB,OAElBD,CACT,CAWO,SAASE,GACdH,EACAI,EACAF,EACS,CACT,OACEE,IAAwB,GACxBJ,EAAK,OAAS,GACdE,EAAgB,OAAS,GACzBA,EAAgB,YAAY,EAAE,WAAWF,EAAK,YAAY,CAAC,CAE/D,CAQO,SAASK,EACdL,EACAC,EACAK,EACQ,CACR,IAAMC,EAAYP,EAAK,MAAMC,CAAU,EACvC,GAAIK,GAAgBL,IAAe,GAAKD,EAAKC,EAAa,CAAC,IAAM,IAC/D,OAAOM,EAET,IAAMC,EAAWD,EAAU,QAAQ,GAAG,EACtC,OAAOC,IAAa,GAAK,GAAKD,EAAU,MAAMC,EAAW,CAAC,CAC5D,CAOO,SAASC,GAAkBC,EAAgBC,EAA4B,CAE5E,IAAMC,EAAUF,EAAO,QAAQ,EAAE,QAAQ,OAAQ,GAAG,EACpD,GAAIE,EAAQ,SAAW,GAAKD,EAAW,SAAW,EAAG,MAAO,GAE5D,IAAME,EAAQD,EAAQ,MAAM,GAAG,EACzBE,EAAcH,EAAW,YAAY,EAG3C,QAASI,EAAI,EAAGA,EAAIF,EAAM,OAAQE,IAAK,CACrC,IAAMC,EAAYH,EAAM,MAAME,CAAC,EAAE,KAAK,GAAG,EACzC,GAAID,EAAY,WAAWE,EAAU,YAAY,CAAC,EAAG,CACnD,IAAMC,EAAcL,EAAQ,OAASI,EAAU,OAC/C,OAAON,EAAO,OAASO,CACzB,CACF,CAEA,MAAO,EACT,CAKO,SAASC,EACdC,EACAC,EACoB,CACpB,GAAI,CAACD,EAAS,MAAO,CAAC,EACtB,IAAMP,EAAUQ,EAAM,UAAU,EAChC,GAAI,CAACR,EAAS,OAAOO,EACrB,IAAME,EAAQT,EAAQ,YAAY,EAClC,OAAOO,EAAQ,OAAQG,GAAM,CAACA,EAAE,aAAeA,EAAE,KAAK,YAAY,EAAE,SAASD,CAAK,CAAC,CACrF,CAKO,SAASE,EACdJ,EACAC,EACyB,CACzB,GAAI,CAACD,EAAS,OAAO,KACrB,IAAMP,EAAUQ,EAAM,KAAK,EAC3B,GAAI,CAACR,EAAS,OAAO,KACrB,IAAMS,EAAQT,EAAQ,YAAY,EAClC,OAAOO,EAAQ,KAAMG,GAAMA,EAAE,aAAeA,EAAE,KAAK,YAAY,IAAMD,CAAK,GAAK,IACjF,CCtGO,SAASG,GACdC,EACAC,EACc,CACd,OAAKA,EACED,EAAY,IAAKE,GAAOD,EAAUC,EAAE,IAAI,EAAI,CAAE,GAAGA,EAAG,QAAS,CAAC,CAAE,EAAIA,CAAE,EADtDF,CAEzB,CCTO,SAASG,GAAWC,EAAqBC,EAA4B,CAC1E,IAAIC,EAAM,EACNC,EAAUF,EACd,QAAWG,KAAOJ,EAChBE,GAAOE,EAAI,MAAM,OACbA,EAAI,OAAS,SAAQD,EAAU,KAAK,IAAIA,EAASD,CAAG,GAE1D,OAAOC,CACT,CAOO,SAASE,GAAkBL,EAAqBM,EAAyB,CAC9E,IAAIJ,EAAM,EACV,QAAWE,KAAOJ,EAAU,CAC1B,IAAMO,EAAML,EAAME,EAAI,MAAM,OAC5B,GAAIA,EAAI,OAAS,QAAUG,EAAMD,GACbF,EAAI,MAAM,MAAM,KAAK,IAAIE,EAASJ,EAAK,CAAC,CAAC,EAC7C,KAAK,EAAE,OAAS,EAAG,MAAO,GAE1CA,EAAMK,CACR,CACA,MAAO,EACT,CAaO,SAASC,GACdC,EACAC,EACqB,CACrB,IAAMC,EAA8B,CAAC,EAC/BC,EAAO,IAAI,IACjB,QAAWC,IAAK,CAAC,GAAGJ,EAAU,GAAGC,CAAO,EAClCG,EAAE,OAAS,eAAiBD,EAAK,IAAIC,EAAE,IAAI,IAC/CD,EAAK,IAAIC,EAAE,IAAI,EACfF,EAAO,KAAK,CAAE,KAAME,EAAE,KAAM,KAAMA,EAAE,IAAK,CAAC,GAE5C,OAAOF,CACT,CAUO,SAASG,GAAaC,EAAkBC,EAAkBV,EAA+B,CAC9F,GAAIS,IAAaC,EAAU,OAAOV,EAClC,IAAMW,EAAS,KAAK,IAAIF,EAAS,OAAQC,EAAS,MAAM,EACpDE,EAAS,EACb,KAAOA,EAASD,GAAUF,EAASG,CAAM,IAAMF,EAASE,CAAM,GAAGA,IACjE,GAAIA,GAAUZ,EAAQ,OAAOA,EAC7B,IAAIa,EAAS,EACb,KACEA,EAASF,EAASC,GAClBH,EAASA,EAAS,OAAS,EAAII,CAAM,IAAMH,EAASA,EAAS,OAAS,EAAIG,CAAM,GAEhFA,IAGF,OAAIJ,EAAS,OAASI,GAAUb,EACvBA,GAAUU,EAAS,OAASD,EAAS,QAEvC,IACT,CCpEA,SAASK,GAAgBC,EAAcC,EAAwC,CAC7E,IAAMC,EAA+B,CAAC,EAChCC,EAAiC,CAAC,EACpCC,EAAM,EACV,QAAWC,KAASJ,EAAiB,CACnC,IAAMK,EAAMN,EAAK,QAAQK,EAAM,KAAMD,CAAG,EACxC,GAAIE,IAAQ,GAAI,CACdH,EAAQ,KAAKE,CAAK,EAClB,QACF,CACAH,EAAQ,KAAK,CAAE,MAAOI,EAAK,IAAKA,EAAMD,EAAM,KAAK,OAAQ,MAAAA,CAAM,CAAC,EAChED,EAAME,EAAMD,EAAM,KAAK,MACzB,CACA,MAAO,CAAE,QAAAH,EAAS,QAAAC,CAAQ,CAC5B,CAOA,SAASI,GACPP,EACAQ,EACAC,EACA,CACA,IAAMP,EAAgC,CAAC,EACjCC,EAAkC,CAAC,EACrCC,EAAM,EACV,QAAWC,KAASI,EAAkB,CACpC,IAAIH,EAAMN,EAAK,QAAQK,EAAM,KAAMD,CAAG,EACtC,KACEE,IAAQ,IACRE,EAAmB,KAAME,GAAMJ,EAAMI,EAAE,KAAOJ,EAAMD,EAAM,KAAK,OAASK,EAAE,KAAK,GAE/EJ,EAAMN,EAAK,QAAQK,EAAM,KAAMC,EAAM,CAAC,EAExC,GAAIA,IAAQ,GAAI,CACdH,EAAQ,KAAKE,CAAK,EAClB,QACF,CACAH,EAAQ,KAAK,CAAE,MAAOI,EAAK,IAAKA,EAAMD,EAAM,KAAK,OAAQ,MAAAA,CAAM,CAAC,EAChED,EAAME,EAAMD,EAAM,KAAK,MACzB,CACA,MAAO,CAAE,QAAAH,EAAS,QAAAC,CAAQ,CAC5B,CAMO,SAASQ,GACdX,EACAC,EACAQ,EAA2C,CAAC,EACjC,CACX,IAAMG,EAAYb,GAAgBC,EAAMC,CAAe,EAAE,QACnDY,EAAaN,GAAiBP,EAAMY,EAAWH,CAAgB,EAAE,QAEjEK,EAA4D,CAChE,GAAGF,EAAU,IAAK,IAAO,CACvB,MAAO,EAAE,MACT,IAAK,EAAE,IACP,QAAS,CAAE,KAAM,YAAa,MAAO,EAAE,MAAM,KAAM,MAAO,EAAE,KAAM,CACpE,EAAE,EACF,GAAGC,EAAW,IAAKE,IAAO,CACxB,MAAOA,EAAE,MACT,IAAKA,EAAE,IACP,QAAS,CAAE,KAAM,aAAc,MAAOA,EAAE,MAAM,KAAM,MAAOA,EAAE,KAAM,CACrE,EAAE,CACJ,EAAE,KAAK,CAACC,EAAGC,IAAMD,EAAE,MAAQC,EAAE,KAAK,EAE5BC,EAAoB,CAAC,EACvBd,EAAM,EACV,QAAWe,KAAQL,EACbK,EAAK,MAAQf,GACfc,EAAO,KAAK,CAAE,KAAM,OAAQ,MAAOlB,EAAK,MAAMI,EAAKe,EAAK,KAAK,CAAE,CAAC,EAElED,EAAO,KAAKC,EAAK,OAAO,EACxBf,EAAMe,EAAK,IAEb,IAAMC,EAAYpB,EAAK,MAAMI,CAAG,EAChC,OAAIgB,GACFF,EAAO,KAAK,CAAE,KAAM,OAAQ,MAAOE,CAAU,CAAC,EAGzCF,CACT,CAKO,SAASG,GACdrB,EACAC,EACkE,CAClE,GAAM,CAAE,QAAAC,EAAS,QAAAC,CAAQ,EAAIJ,GAAgBC,EAAMC,CAAe,EAClE,MAAO,CAAE,MAAOC,EAAQ,IAAKoB,GAAMA,EAAE,KAAK,EAAG,QAASnB,CAAQ,CAChE,CAQO,SAASoB,GACdvB,EACAC,EACAQ,EACoE,CACpE,IAAMG,EAAYb,GAAgBC,EAAMC,CAAe,EAAE,QACnD,CAAE,QAAAC,EAAS,QAAAC,CAAQ,EAAII,GAAiBP,EAAMY,EAAWH,CAAgB,EAC/E,MAAO,CAAE,MAAOP,EAAQ,IAAKoB,GAAMA,EAAE,KAAK,EAAG,QAASnB,CAAQ,CAChE,CCrGA,SAASqB,GAAQC,EAAqB,CACpC,GAAIA,aAAe,MAAO,OAAOA,EACjC,GAAI,CACF,OAAO,IAAI,MAAM,OAAOA,CAAG,CAAC,CAC9B,MAAQ,CACN,OAAO,IAAI,MAAM,eAAe,CAClC,CACF,CAEA,IAAMC,GAAc,IACdC,GAAmB,IACnBC,GAAiB,EAkDVC,GAAN,KAAsB,CAO3B,YACUC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EAAsC,CAAC,EAC/C,CARQ,WAAAP,EACA,kBAAAC,EACA,wBAAAC,EACA,0BAAAC,EACA,gBAAAC,EACA,kBAAAC,EACA,0BAAAC,EACA,eAAAC,EAdV,KAAQ,aAAe,EACvB,KAAQ,gBAA0C,KAClD,KAAQ,cAAsD,KAC9D,KAAQ,kBAA0D,KAClE,KAAQ,YAAmC,IAWxC,CAEH,OAAQ,CAEN,KAAK,QAAQ,GAAI,CAAC,CAAC,EAGnB,IAAIC,EAAW,KAAK,MAAM,IAAI,EAAE,KAC5BC,EAAa,KAAK,MAAM,IAAI,EAAE,gBAClC,KAAK,YAAc,KAAK,MAAM,UAAWC,GAAS,EAC5CA,EAAK,OAASF,GAAYE,EAAK,kBAAoBD,KACrDD,EAAWE,EAAK,KAChBD,EAAaC,EAAK,gBAClB,KAAK,cAAc,EAEvB,CAAC,CACH,CAEA,SAAU,CACR,KAAK,iBAAiB,MAAM,EAC5B,KAAK,YAAY,EACjB,KAAK,cAAc,CACrB,CAEA,MAAM,QAAQC,EAAkBC,EAAkC,CAChE,KAAK,iBAAiB,MAAM,EAC5B,IAAMC,EAAa,IAAI,gBACvB,KAAK,gBAAkBA,EACvB,IAAMC,EAAU,EAAE,KAAK,aACjBC,EAAgB,KAAK,MAAM,IAAI,EAAE,KAAK,OAW5C,GAAI,CACF,KAAK,UAAU,YAAY,CACzB,MAAO,KAAK,MAAM,IAAI,EAAE,KACxB,OAAQF,EAAW,OACnB,UAAW,IAAMC,IAAY,KAAK,YACpC,CAAC,CACH,MAAQ,CAER,CAEA,GAAI,CAKF,KAAK,MAAM,IAAI,CAAE,UAAW,GAAM,MAAO,IAAK,CAAC,EAK/C,IAAME,EAAiB,KAAK,MAAM,IAAI,EAChCC,EAAoBD,EAAe,YACrCE,GACEF,EAAe,YAAY,SAC3BA,EAAe,qBACjB,EACA,OAEEG,EAAM,MAAMC,GAAiBT,EAAUC,EAAW,CACtD,UAAW,KAAK,aAAa,EAC7B,kBAAmB,KAAK,qBAAqB,EAC7C,OAAQC,EAAW,OACnB,UAAW,KAAK,aAAa,EAC7B,iBAAkBG,EAAe,iBACjC,kBAAAC,EACA,cAAeD,EAAe,cAC9B,kBAAmB,KAAK,qBAAqB,CAC/C,CAAC,EAED,GAAIF,IAAY,KAAK,aAAc,OAMnC,IAAMO,GAAgDF,EAAI,KAAK,OAAS,CAAC,GACtE,OAAQG,GAASA,EAAK,SAAW,YAAY,EAC7C,IAAKA,IAAU,CAAE,GAAI,OAAO,WAAW,EAAG,KAAMA,EAAK,KAAM,KAAMA,EAAK,IAAK,EAAE,EAE5EC,EAAiBC,GACnBL,EAAI,KAAK,aAAe,CAAC,EACzB,KAAK,mBAAmB,CAC1B,EAEMM,EAAQN,EAAI,KAAK,OAAS,CAAC,EAC3BO,EAAYD,EAAMA,EAAM,OAAS,CAAC,EAClCE,EAAc,KAAK,MAAM,IAAI,EAAE,KACjCC,EACAC,EAEJ,GAAIH,GAAW,QAAU,cAAe,CACtCG,EAAmB,GACnB,IAAMC,EAAgBH,EAAY,YAAY,EAAE,YAAYD,EAAU,KAAK,YAAY,CAAC,EACxFE,EAAaE,IAAkB,GAAKA,EAAgBf,CACtD,MACEc,EAAmB,GACnBD,EAAab,EAKf,IAAMgB,EADaR,EAAe,OAAQS,GAAMA,EAAE,OAAS,aAAa,EAC9C,CAAC,EACvBC,EAAyC,KAK7C,GAAIF,GAAU,CAACG,EAAiBC,EAAkBJ,CAAM,CAAC,EAAG,CAC1D,IAAMK,EAAQC,EAAmBV,EAAaC,EAAYC,CAAgB,EACpES,EAAQC,EAAeR,EAAO,QAASK,CAAK,EAC9CE,IACFL,EAAa,CACX,GAAI,OAAO,WAAW,EACtB,YAAa,GACb,KAAMF,EAAO,KACb,KAAMO,EAAM,KACZ,KAAMA,EAAM,KACZ,eAAgBP,EAAO,KACvB,sBAAuBA,EAAO,KAC9B,QAASA,EAAO,SAAW,CAAC,EAC5B,SAAUO,EAAM,SAChB,KAAMA,EAAM,KACZ,SAAUA,EAAM,QAClB,EACAf,EAAiBA,EAAe,OAAQS,GAAMA,IAAMD,CAAM,EAC1D,KAAK,UAAU,cAAc,CAAE,OAAAA,EAAQ,QAASO,EAAO,SAAA3B,CAAS,CAAC,EAErE,CAKA,IAAM6B,EAAerB,EAAI,KAAK,eAAiB,KACzCsB,EAAW,KAAK,UAAU,sBAAsBD,CAAY,EAElE,KAAK,MAAM,IAAKR,GAAM,CAIpB,IAAMU,EAAeT,EAAa,CAAC,GAAGD,EAAE,gBAAiBC,CAAU,EAAID,EAAE,gBACnEW,EAAmBC,GACvBZ,EAAE,KACFU,EACArB,CACF,EAAE,MASIwB,EAAe,IAAI,IAAI7B,EAAe,cAAc,IAAK8B,GAAMA,EAAE,EAAE,CAAC,EACpEC,EAAiB,IAAI,IACzBf,EAAE,cAAc,OAAQc,GAAM,CAACD,EAAa,IAAIC,EAAE,EAAE,CAAC,EAAE,IAAKA,GAAMA,EAAE,IAAI,CAC1E,EAOA,MAAO,CACL,YANAC,EAAe,KAAO,EAClBxB,EAAe,OACZyB,GAAOA,EAAG,OAAS,eAAiB,CAACD,EAAe,IAAIC,EAAG,IAAI,CAClE,EACAzB,EAGJ,UAAW,GACX,QAASJ,EAAI,KAAK,UAAY,GAC9B,aAAcR,EACd,oBAAqB,GACrB,WAAAiB,EACA,iBAAAC,EACA,iBAAAc,EACA,aAAAH,EACA,GAAIC,IAAa,OAAY,CAAE,SAAAA,CAAS,EAAI,CAAC,EAC7C,GAAIR,EAAa,CAAE,gBAAiBS,CAAa,EAAI,CAAC,CACxD,CACF,CAAC,EAOD,KAAK,UAAU,aAAa,CAC9B,OAAS/C,EAAK,CAIZ,IAAMsD,EAAcvD,GAAQC,CAAG,EAC3BmB,IAAY,KAAK,eACnB,KAAK,MAAM,IAAI,CAAE,MAAOmC,EAAa,UAAW,EAAM,CAAC,EACvD,KAAK,WAAW,IAAIA,CAAW,EAEnC,QAAE,CAUA,GAAInC,IAAY,KAAK,cAAgB,KAAK,MAAM,IAAI,EAAE,UACpD,GAAI,CACF,KAAK,MAAM,IAAI,CAAE,UAAW,EAAM,CAAC,CACrC,MAAQ,CAKR,CAEJ,CACF,CAQA,YAAa,CAOX,GANA,KAAK,YAAY,EAMb,KAAK,MAAM,IAAI,EAAE,cAAe,CAClC,KAAK,MAAM,IAAI,CAAE,cAAe,EAAM,CAAC,EACvC,MACF,CACA,KAAK,aAAa,EAAG,EAAI,CAC3B,CAEQ,eAAgB,CAItB,GAHA,KAAK,YAAY,EACH,KAAK,MAAM,IAAI,EAEnB,cAAe,CACvB,KAAK,MAAM,IAAI,CAAE,cAAe,EAAM,CAAC,EACvC,MACF,CAEA,KAAK,cAAgB,WAAW,IAAM,CAChC,KAAK,aAAahB,EAAc,GAC9B,KAAK,mBAAmB,aAAa,KAAK,iBAAiB,CAEnE,EAAGF,EAAW,EAEd,KAAK,kBAAoB,WAAW,IAAM,KAAK,aAAa,CAAC,EAAGC,EAAgB,CAClF,CASQ,aAAaqD,EAAiBC,EAAY,GAAgB,CAChE,IAAMnB,EAAI,KAAK,MAAM,IAAI,EACzB,GAAI,CAACA,EAAE,MAAQA,EAAE,gBAAgB,SAAW,EAC1C,YAAK,QAAQ,GAAI,CAAC,CAAC,EACZ,GAMT,IAAMoB,EAAW,KAAK,UAAU,eAAepB,EAAGkB,CAAO,GAAK,KAC9D,GAAIE,IAAa,QAAS,MAAO,GACjC,GAAIA,IAAa,OAAQ,MAAO,GAEhC,IAAMC,EAAkBrB,EAAE,YACvB,OAAQgB,GAAmBA,EAAG,OAAS,aAAa,EACpD,IAAKA,GAAmBA,EAAG,IAAI,EAC/B,KAAK,GAAG,EACLM,EAAUC,EAAoBvB,EAAE,KAAMA,EAAE,WAAYqB,CAAe,EACnEG,EAAenB,EAAmBL,EAAE,KAAMsB,EAAStB,EAAE,gBAAgB,EAErED,EADaC,EAAE,YAAY,OAAQgB,GAAmBA,EAAG,OAAS,aAAa,EAC3D,CAAC,EAMrBS,EAASvB,EAAiBF,EAAE,gBAAgB,EAE5C0B,GADkB3B,GAAU,CAAC0B,EAASE,EAAc5B,EAAO,QAASyB,CAAY,EAAI,CAAC,GAClD,OAAQI,GAAwBA,EAAE,WAAW,EAChFC,EACJ9B,GAAU,CAAC0B,EAASlB,EAAeR,EAAO,QAASyB,CAAY,IAAM,KAAO,GAExEM,EAAiBN,EAAa,KAAK,EAAE,OAAS,EAMpD,GALIE,EAAiB,OAAS,GAAK,CAACG,GAAiBC,GAKjDC,GAA0B/B,EAAE,KAAMA,EAAE,gBAAgB,OAAQqB,CAAe,EAC7E,MAAO,GAGT,GAAM,CAAE,SAAA1C,EAAU,gBAAiBqD,CAAc,EAAIC,EAAWjC,EAAE,KAAMA,EAAE,eAAe,EACnFkC,EAAavD,EAAS,OAASqB,EAAE,aAAa,OAC9CmC,EAAW,KAAK,IAAIxD,EAAS,OAASqB,EAAE,aAAa,MAAM,EAC3DoC,EAAUjB,GAAaxC,IAAaqB,EAAE,aAC5C,OAAIkC,GAAcC,GAAYjB,GAAWkB,GACvC,KAAK,QAAQzD,EAAUqD,CAAa,EAC7B,IAEF,EACT,CAEQ,aAAc,CAChB,KAAK,eAAe,aAAa,KAAK,aAAa,EACnD,KAAK,mBAAmB,aAAa,KAAK,iBAAiB,EAC/D,KAAK,cAAgB,KACrB,KAAK,kBAAoB,IAC3B,CACF,EC5ZA,SAASK,GAAaC,EAAuC,CAC3D,OAAIA,EAAK,WAAa,KAAK,cAAsB,GAI1CA,EAAK,WAAa,KAAK,wBAA0B,SAAUA,CACpE,CAQO,SAASC,GAAOD,EAAwB,CAC7C,IAAME,EAAO,OAAOF,EAAK,aAAgB,WAAaA,EAAK,YAAY,EAAI,KAC3E,OAAIE,GAAQH,GAAaG,CAAI,EAAUA,EAChCF,EAAK,eAAkBA,CAChC,CAGO,SAASG,EAAWH,EAAsB,CAC/C,OAAOA,EAAK,eAAkBA,CAChC,CAWO,SAASI,EAAgBJ,EAA8B,CAE5D,IAAMK,EADOJ,GAAOD,CAAI,EACsB,eAAe,EAC7D,OAAIK,IACGF,EAAWH,CAAI,EAAE,eAAe,GAAK,KAC9C,CASO,SAASM,GAAkBN,EAAmBO,EAAgC,CACnF,IAAML,EAAqDF,EACvDC,GAAOD,CAAI,EACX,OAAO,SAAa,IAClB,SACA,KACN,OAAKE,EACD,OAAOA,EAAK,gBAAmB,WAAmBA,EAAK,eAAeK,CAAE,EAOrE,OAAO,SAAa,IAAc,SAAS,eAAeA,CAAE,EAAI,KARrD,IASpB,CCzEA,IAAMC,GAAsB,EAGtBC,GAAyB,aAE/B,SAASC,GAAqBC,EAAwB,CAGpD,OAAOA,IAAU,QAAUA,IAAU,UAAYA,IAAU,UAAYA,IAAU,SACnF,CASA,SAASC,GAAoBC,EAAuC,CAClE,IAAMC,EAAMC,EAAWF,CAAI,EACrBG,EAAMF,EAAI,YAChB,GAAI,CAACE,GAAK,iBAAkB,OAAO,KAEnC,IAAIC,EAAyBJ,EAC7B,KAAOI,GAAMA,IAAOH,EAAI,MAAQG,IAAOH,EAAI,iBAAiB,CAG1D,IAAMI,EAAaD,EAAG,YAAcA,EAAG,YACjCE,EAAaF,EAAG,aAAeA,EAAG,aACxC,GAAIC,GAAcC,EAAY,CAC5B,IAAMC,EAAQJ,EAAI,iBAAiBC,CAAE,EACrC,GACGC,GAAcR,GAAqBU,EAAM,SAAS,GAClDD,GAAcT,GAAqBU,EAAM,SAAS,EAEnD,OAAOH,CAEX,CACA,GAAIA,EAAG,UAAU,SAASR,EAAsB,EAAG,OAAO,KAC1DQ,EAAKA,EAAG,aACV,CACA,OAAO,IACT,CAoBO,SAASI,GAAoBR,EAAyB,CAC3D,IAAMS,EAAMC,EAAgBV,CAAI,EAChC,GAAI,CAACS,GAAOA,EAAI,aAAe,EAAG,OAElC,IAAME,EAAQF,EAAI,WAAW,CAAC,EAC9B,GAAI,CAACE,EAAM,WAAa,CAACX,EAAK,SAASW,EAAM,cAAc,EAAG,OAK9D,IAAMC,EAAYb,GAAoBC,CAAI,EAK1C,GAJI,CAACY,GAID,OAAOD,EAAM,uBAA0B,WAAY,OAEvD,IAAME,EAAQF,EAAM,sBAAsB,EAI1C,GAAIE,EAAM,SAAW,EAAG,OAExB,IAAMC,EAAMF,EAAU,sBAAsB,EAM5C,GAAIA,EAAU,YAAcA,EAAU,YAAa,CACjD,IAAMG,EAAWD,EAAI,KAAOF,EAAU,WAChCI,EAAYD,EAAWH,EAAU,YACnCC,EAAM,MAAQG,EAChBJ,EAAU,YAAcC,EAAM,MAAQG,EAAYrB,GACzCkB,EAAM,KAAOE,IACtBH,EAAU,YAAcG,EAAWF,EAAM,KAAOlB,GAEpD,CAEA,GAAIiB,EAAU,aAAeA,EAAU,aAAc,CACnD,IAAMK,EAAUH,EAAI,IAAMF,EAAU,UAC9BM,EAAaD,EAAUL,EAAU,aACnCC,EAAM,OAASK,EACjBN,EAAU,WAAaC,EAAM,OAASK,EAAavB,GAC1CkB,EAAM,IAAMI,IACrBL,EAAU,WAAaK,EAAUJ,EAAM,IAAMlB,GAEjD,CACF,CCpHA,IAAMwB,GAAwB,4BAExBC,GAAgB,wBAMlBC,GACJ,SAASC,IAAiD,CACxD,GAAID,KAAc,OAAW,OAAOA,GAGpC,IAAME,EAAa,WAA+D,KAC/E,UACH,GAAI,CAACA,EACH,OAAAF,GAAY,KACL,KAET,GAAI,CACFA,GAAY,IAAIE,EAAU,OAAW,CAAE,YAAa,UAAW,CAAC,CAClE,MAAQ,CACNF,GAAY,IACd,CACA,OAAOA,IAAa,IACtB,CAEA,SAASG,GAAoBC,EAAYC,EAA4B,CACnE,IAAIC,EAAiBF,EACrB,KAAOE,GAAKA,IAAMD,GAAM,CACtB,GAAIC,EAAE,WAAa,KAAK,cACXA,EACJ,QAAQR,EAAqB,EAAG,MAAO,GAEhDQ,EAAIA,EAAE,UACR,CACA,MAAO,EACT,CAEA,SAASC,GAAiBF,EAA+B,CACvD,OAAOG,EAAWH,CAAI,EAAE,iBAAiBA,EAAM,WAAW,UAAW,CACnE,WAAWD,EAAM,CACf,OAAOD,GAAoBC,EAAMC,CAAI,EAAI,WAAW,cAAgB,WAAW,aACjF,CACF,CAAC,CACH,CAEO,SAASI,GAAiBJ,EAA2B,CAC1D,IAAMK,EAASH,GAAiBF,CAAI,EAChCM,EAAM,GACNP,EAAOM,EAAO,SAAS,EAC3B,KAAON,GACLO,GAAOP,EAAK,KACZA,EAAOM,EAAO,SAAS,EAEzB,OAAOC,CACT,CAEO,SAASC,GAAgBP,EAA2B,CACzD,IAAMK,EAASH,GAAiBF,CAAI,EAChCQ,EAAQ,EACRT,EAAOM,EAAO,SAAS,EAC3B,KAAON,GACLS,GAAST,EAAK,KAAK,OACnBA,EAAOM,EAAO,SAAS,EAEzB,OAAOG,CACT,CAMO,SAASC,EAAgBT,EAAkC,CAChE,IAAMU,EAAMC,EAAgBX,CAAI,EAChC,GAAI,CAACU,GAAOA,EAAI,aAAe,EAAG,OAAO,KACzC,IAAME,EAAaF,EAAI,WACjBG,EAAeH,EAAI,aACzB,GAAI,CAACE,GAAc,CAACZ,EAAK,SAASY,CAAU,EAAG,OAAO,KAItD,GAAIA,EAAW,WAAa,KAAK,aAAc,CAC7C,IAAME,EAAKF,EACX,GAAId,GAAoBgB,EAAId,CAAI,GAAKc,IAAOd,EAAM,OAAO,KACzD,IAAIe,EAAS,EACb,QAASC,EAAI,EAAGA,EAAIH,GAAgBG,EAAIF,EAAG,WAAW,OAAQE,IAC5DD,GAAUE,GAAyBH,EAAG,WAAWE,CAAC,EAAGhB,CAAI,EAG3D,OAAOe,EAASG,GAAiBJ,EAAId,CAAI,CAC3C,CAGA,OADIY,EAAW,WAAa,KAAK,WAC7Bd,GAAoBc,EAAYZ,CAAI,EAAU,KAE3CkB,GAAiBN,EAAYZ,CAAI,EAAIa,CAC9C,CAEA,SAASI,GAAyBlB,EAAYC,EAA2B,CACvE,GAAID,EAAK,WAAa,KAAK,UACzB,OAAOD,GAAoBC,EAAMC,CAAI,EAAI,EAAKD,EAAc,KAAK,OAEnE,GAAIA,EAAK,WAAa,KAAK,aAAc,MAAO,GAChD,IAAMe,EAAKf,EACX,GAAIe,EAAG,QAAQrB,EAAqB,EAAG,MAAO,GAC9C,IAAIe,EAAQ,EACZ,QAAWW,KAAS,MAAM,KAAKL,EAAG,UAAU,EAC1CN,GAASS,GAAyBE,EAAOnB,CAAI,EAE/C,OAAOQ,CACT,CAEA,SAASU,GAAiBE,EAAcpB,EAA2B,CACjE,IAAMK,EAASH,GAAiBF,CAAI,EAChCQ,EAAQ,EACRT,EAAOM,EAAO,SAAS,EAC3B,KAAON,GAAM,CAGX,GAFIA,IAASqB,GAETA,EAAO,WAAa,KAAK,cAAiBA,EAAmB,SAASrB,CAAI,EAC5E,OAAOS,EAETA,GAAST,EAAK,KAAK,OACnBA,EAAOM,EAAO,SAAS,CACzB,CACA,OAAOG,CACT,CAiBO,SAASa,EAAgBrB,EAAmBe,EAAsB,CACvE,IAAMO,EAAMnB,EAAWH,CAAI,EACrBU,EAAMC,EAAgBX,CAAI,EAChC,GAAI,CAACU,EAAK,OAEV,IAAMa,EAAU,KAAK,IAAI,EAAG,KAAK,IAAIR,EAAQR,GAAgBP,CAAI,CAAC,CAAC,EAC7DK,EAASH,GAAiBF,CAAI,EAChCwB,EAAa,EACbJ,EAAsB,KACtBK,EAAe,EACf1B,EAAOM,EAAO,SAAS,EACvBqB,EAAwB,KAE5B,KAAO3B,GAAM,CACX,IAAM4B,EAAM5B,EAAK,KAAK,OACtB,GAAIwB,EAAUC,EAAaG,EAAK,CAC9BP,EAASrB,EACT0B,EAAeF,EAAUC,EACzB,KACF,CACA,GAAID,IAAYC,EAAaG,EAAK,CAChC,IAAMC,EAAOvB,EAAO,SAAS,EACzBuB,GAGFR,EAASQ,EACTH,EAAe,IAEfL,EAASrB,EACT0B,EAAeE,GAEjB,KACF,CACAH,GAAcG,EACdD,EAAW3B,EACXA,EAAOM,EAAO,SAAS,CACzB,CAEA,IAAMwB,EAAQP,EAAI,YAAY,EAC9B,GAAIF,EAAQ,CAKV,IAAMU,EAAeV,EAAO,eAAe,QAAqB,8BAA8B,EAC1FU,GAAgBA,IAAiB9B,GAAQA,EAAK,SAAS8B,CAAY,EACjEL,IAAiB,EACnBI,EAAM,eAAeC,CAAY,EACxBL,IAAiBL,EAAO,KAAK,OACtCS,EAAM,cAAcC,CAAY,EAEhCD,EAAM,SAAST,EAAQK,CAAY,EAGrCI,EAAM,SAAST,EAAQK,CAAY,CAEvC,MAAWC,EACTG,EAAM,SAASH,EAAUA,EAAS,KAAK,MAAM,EAE7CG,EAAM,SAAS7B,EAAM,CAAC,EAExB6B,EAAM,SAAS,EAAI,EACnBnB,EAAI,gBAAgB,EACpBA,EAAI,SAASmB,CAAK,EAIlBE,GAAmB/B,EAAMU,EAAKmB,EAAON,CAAO,EAI5CS,GAAoBhC,CAAI,CAC1B,CAGA,SAASiC,GAAelC,EAAmBC,EAAuC,CAChF,GAAI,CAACD,GAAQ,CAACC,EAAK,SAASD,CAAI,EAAG,OAAO,KAE1C,IAAMmC,GADQnC,EAAK,WAAa,KAAK,aAAgBA,EAAmBA,EAAK,gBACzD,QAAqBL,EAAa,GAAK,KAG3D,OAAOwC,GAAQlC,EAAK,SAASkC,CAAI,EAAIA,EAAO,IAC9C,CAoBA,SAASH,GAAmB/B,EAAmBU,EAAgBmB,EAAcd,EAAsB,CACjG,IAAMoB,EAAWF,GAAevB,EAAI,WAAYV,CAAI,EACpD,GAAImC,GAAYA,IAAaF,GAAeJ,EAAM,eAAgB7B,CAAI,EAAG,CACvEA,EAAK,QAAQ,oBAAsB,GAAGe,CAAM,IAAIoB,EAAS,QAAQ,OAAO,GACxE,MACF,CACA,OAAOnC,EAAK,QAAQ,mBACtB,CAaO,SAASoC,GAAiBpC,EAAmBqC,EAAyB,CAC3E,IAAMC,EAAStC,EAAK,QAAQ,oBAC5B,GAAI,CAACsC,EAAQ,MAAO,GACpB,IAAMvB,EAASN,EAAgBT,CAAI,EACnC,OAAOe,IAAW,MAAQuB,IAAW,GAAGvB,CAAM,IAAIsB,CAAM,EAC1D,CAMO,SAASE,GAAcvC,EAA4B,CACxD,IAAMe,EAASN,EAAgBT,CAAI,EACnC,OAAIe,GAAU,KAAa,GACpBA,GAAUR,GAAgBP,CAAI,CACvC,CAOO,SAASwC,GAAyBC,EAAc1B,EAAwB,CAC7E,GAAIA,GAAU,EAAG,MAAO,GACxB,IAAM2B,EAAM9C,GAAqB,EACjC,GAAI,CAAC8C,EAAK,OAAO3B,EAAS,EAC1B,IAAM4B,EAAQF,EAAK,MAAM,EAAG1B,CAAM,EAC9B6B,EAAO,EACX,OAAW,CAAE,MAAAC,CAAM,IAAKH,EAAI,QAAQC,CAAK,EACnCE,EAAQ9B,IAAQ6B,EAAOC,GAE7B,OAAOD,CACT,CC/QO,SAASE,EACdC,EACAC,EACAC,EAAqC,CAAC,EACtCC,EAA6B,CAAC,EACV,CACpB,GAAM,CAAE,SAAAC,EAAU,gBAAiBC,CAAY,EAAIC,EAAWN,EAAMC,CAAe,EACnF,MAAO,CACL,MAAOD,EAAK,KAAK,EACjB,UAAWI,EACX,iBAAkBG,EAAkBF,EAAaH,CAAa,EAC9D,kBAAmBM,EAAuBL,EAAO,kBAAoB,CAAC,CAAC,EACvE,SAAUA,EAAO,SAAW,GAC5B,GAAIA,EAAO,aAAe,CAAE,cAAeA,EAAO,YAAa,EAAI,CAAC,CACtE,CACF,CCQA,SAASM,GAAcC,EAA4BC,EAA4B,CAC7E,OAAID,aAAkB,qBAAuBA,aAAkB,iBACtDA,EAAO,gBAAkB,MAAQA,EAAO,iBAAmBA,EAAO,MAAM,OAE7EA,aAAkB,aAAeA,EAAO,aAAa,gBAAgB,EAChEE,GAAcF,CAAM,EAOzBC,GAAO,aAAe,KACjBA,EAAM,aAAeA,EAAM,KAAK,OAElC,EACT,CAEA,SAASE,GAAuBH,EAA2C,CACzE,OAAIA,aAAkB,aAAeA,EAAO,aAAa,gBAAgB,EAChEI,EAAgBJ,CAAM,EAExB,IACT,CAEO,IAAMK,GAAN,KAAyB,CAC9B,YACUC,EACAC,EACR,CAFQ,WAAAD,EACA,SAAAC,CACP,CAEH,cAAc,EAAkB,CAC9B,IAAMN,EAAQ,KAAK,MAAM,IAAI,EACvB,CAAE,UAAAO,EAAW,YAAAC,CAAY,EAAI,KAAK,IAQlCC,EAAO,EAAE,kBAAkB,KAAO,EAAE,OAAS,KAC7CC,EAAU,KAAK,oBAAoBD,CAAI,EACvCE,EAAWH,EAAY,EACvBI,EAAkB,KAAK,mBAAmBF,CAAO,EAMvD,IACG,EAAE,UAAY,EAAE,SAAW,EAAE,SAAW,EAAE,UAC1C,EAAE,MAAQ,aACT,EAAE,MAAQ,WACV,EAAE,MAAQ,aACV,EAAE,MAAQ,cAEZ,OAQF,IAAMG,EAAQ,KAAK,IAAI,mBAAmB,IAAM,QAEhD,OAAQ,EAAE,IAAK,CACb,IAAK,YAAa,CAChB,IAAMC,EAAchB,GAAc,EAAE,OAAQE,CAAK,EAI3Ce,EAAa,CAAC,CAACf,EAAM,aAC3B,GAAI,CAACc,GAAe,CAACC,GAAcf,EAAM,oBAAsB,EAAG,MAIlE,GAAIA,EAAM,oBAAsB,EAAG,CACjC,GAAIa,EAAO,MACX,EAAE,eAAe,EACjB,IAAMG,EAAQC,EAAiBjB,EAAM,gBAAgB,EACjD,KAAK,eAAeA,EAAO,EAAK,EAC/BY,EAAgB,CAAC,GAAK,EAC3B,GAAI,CAACZ,EAAM,gBAAkBA,EAAM,sBAAsB,OAAS,EAAG,CACnE,KAAK,MAAM,IAAI,CAAE,WAAY,GAAM,oBAAqBgB,CAAM,CAAC,EAC/D,KACF,CACA,GAAIJ,EAAgB,SAAW,EAAG,OAClC,KAAK,MAAM,IAAI,CAAE,oBAAqBI,CAAM,CAAC,EAC7C,KACF,CAGA,GADA,EAAE,eAAe,EACbJ,EAAgB,SAAW,EAAG,OAIlC,GAAIK,EAAiBjB,EAAM,gBAAgB,EAAG,CAC5C,KAAK,aAAaA,EAAO,CAAC,EAC1B,KACF,CAGA,GAAIA,EAAM,gBAAgB,OAAS,EAAG,CACpC,IAAMkB,EAAU,KAAK,OAAOlB,EAAM,gBAAgB,OAAS,GAAKU,CAAO,EAEvE,GADmB,KAAK,MAAMV,EAAM,oBAAsBU,CAAO,IAC9CQ,EAAS,CAC1B,KAAK,MAAM,IAAI,CAAE,oBAAqB,EAAG,CAAC,EAC1C,KACF,CACF,CACA,IAAMC,EAAaP,EAAgB,QAAQZ,EAAM,mBAAmB,EAC9DoB,EAAUD,EAAaP,EAAgB,OAAS,EAAIO,EAAa,EAAI,EAC3E,KAAK,MAAM,IAAI,CAAE,oBAAqBP,EAAgBQ,CAAO,CAAE,CAAC,EAChE,KACF,CACA,IAAK,UAAW,CAEd,GAAIpB,EAAM,oBAAsB,EAAG,CACjC,GAAI,CAACa,EAAO,MACZ,IAAMC,EAAchB,GAAc,EAAE,OAAQE,CAAK,EAC3Ce,EAAa,CAAC,CAACf,EAAM,aAC3B,GAAI,CAACc,GAAe,CAACC,EAAY,MACjC,EAAE,eAAe,EAIjB,IAAMM,EAAaJ,EAAiBjB,EAAM,gBAAgB,EACtD,KAAK,eAAeA,EAAO,EAAI,EAC9B,KAAK,yBAAyBU,CAAO,GAAKE,EAAgB,CAAC,GAAK,EACrE,GAAI,CAACZ,EAAM,gBAAkBA,EAAM,sBAAsB,OAAS,EAAG,CACnE,KAAK,MAAM,IAAI,CAAE,WAAY,GAAM,oBAAqBqB,CAAW,CAAC,EACpE,KACF,CACA,GAAIT,EAAgB,SAAW,EAAG,OAClC,KAAK,MAAM,IAAI,CAAE,oBAAqBS,CAAW,CAAC,EAClD,KACF,CACA,GAAIT,EAAgB,SAAW,EAAG,MAElC,GADA,EAAE,eAAe,EACbK,EAAiBjB,EAAM,gBAAgB,EAAG,CAC5C,KAAK,aAAaA,EAAO,EAAE,EAC3B,KACF,CACA,GAAIA,EAAM,oBAAsBU,EAAS,CACvC,KAAK,MAAM,IAAI,CAAE,oBAAqB,EAAG,CAAC,EAC1C,KACF,CACA,IAAMS,EAAaP,EAAgB,QAAQZ,EAAM,mBAAmB,EAC9DsB,EAAUH,EAAa,EAAIA,EAAa,EAAIP,EAAgB,OAAS,EAC3E,KAAK,MAAM,IAAI,CAAE,oBAAqBA,EAAgBU,CAAO,CAAE,CAAC,EAChE,KACF,CACA,IAAK,aAAc,CAKjB,GAAItB,EAAM,qBAAuB,EAAG,CAKlC,GAJA,EAAE,eAAe,EAIbiB,EAAiBjB,EAAM,gBAAgB,EAAG,CAC5C,KAAK,YAAYA,EAAO,CAAC,EACzB,KACF,CAEA,GADYA,EAAM,oBAAsBU,EAC9BA,EAAU,EAAG,CACrB,IAAMa,EAAgBvB,EAAM,oBAAsB,EAEhDuB,EAAgBvB,EAAM,gBAAgB,QACtCA,EAAM,gBAAgBuB,CAAa,GAAG,aAEtC,KAAK,MAAM,IAAI,CAAE,oBAAqBA,CAAc,CAAC,CAEzD,CACA,KACF,CAGA,GAAIvB,EAAM,cAAgB,EAAE,kBAAkB,aAAeA,EAAM,aAAe,KAAM,CACtF,EAAE,eAAe,EACjB,IAAMwB,EAAS,EAAE,OAAO,QAAqB,kBAAkB,GAAK,EAAE,OAChEC,EAAOzB,EAAM,YACnB,KAAK,IAAI,eAAe,EACxB0B,EAAgBF,EAAQC,CAAI,EAC5B,KACF,CACc3B,GAAc,EAAE,OAAQE,CAAK,GAC9BA,EAAM,sBAAsB,QAAU,IACjD,EAAE,eAAe,EACjB,KAAK,IAAI,eAAe,GAE1B,KACF,CACA,IAAK,YAAa,CAIhB,GAAIA,EAAM,qBAAuB,EAAG,CAMlC,GALA,EAAE,eAAe,EAKbiB,EAAiBjB,EAAM,gBAAgB,EAAG,CAC5C,KAAK,YAAYA,EAAO,EAAE,EAC1B,KACF,CACA,GAAIA,EAAM,oBAAsBU,EAAU,EAAG,CAC3C,IAAMiB,EAAe3B,EAAM,oBAAsB,EAC7C2B,GAAgB,GAAK3B,EAAM,gBAAgB2B,CAAY,GAAG,aAC5D,KAAK,MAAM,IAAI,CAAE,oBAAqBA,CAAa,CAAC,EAEtD,KACF,CASA,GAAI,CAAC3B,EAAM,cAAgB,KAAK,IAAI,yBAA0B,CAC5D,IAAM4B,EAAQ1B,GAAuB,EAAE,MAAM,EACzC0B,GAAS,MAAM,KAAK,IAAI,yBAAyBA,CAAK,CAC5D,CACA,KACF,CAEA,GAAI5B,EAAM,cAAgB,EAAE,kBAAkB,aAAeA,EAAM,eAAiB,KAAM,CACxF,EAAE,eAAe,EACjB,IAAMwB,EAAS,EAAE,OAAO,QAAqB,kBAAkB,GAAK,EAAE,OAChEK,EAAS7B,EAAM,cACrB,KAAK,IAAI,eAAe,EACxB0B,EAAgBF,EAAQK,CAAM,EAC9B,KACF,CAOA,GAAI,KAAK,IAAI,yBAA0B,CACrC,IAAMC,EAAS5B,GAAuB,EAAE,MAAM,EAC1C4B,GAAU,MAAQ,KAAK,IAAI,yBAAyBA,CAAM,GAC5D,EAAE,eAAe,CAErB,CACA,KACF,CACA,IAAK,YAAa,CAKhB,GADI9B,EAAM,cACN,CAAC,KAAK,IAAI,mBAAoB,MAClC,IAAM8B,EAAS5B,GAAuB,EAAE,MAAM,EAC9C,GAAI4B,GAAU,KAAM,MAChB,KAAK,IAAI,mBAAmBA,CAAM,GACpC,EAAE,eAAe,EAEnB,KACF,CACA,IAAK,QAAS,CACZ,EAAE,eAAe,EAEf9B,EAAM,qBAAuB,GAC7BA,EAAM,gBAAgBA,EAAM,mBAAmB,GAAG,YAElD,KAAK,cAAcA,EAAM,oBAAqBA,EAAM,gBAAiBO,EAAWE,CAAI,EAC3EE,GACSA,EAChBoB,EAAkB/B,EAAM,KAAMA,EAAM,gBAAiBA,EAAM,cAAe,CACxE,iBAAkBA,EAAM,iBACxB,QAASA,EAAM,QACf,aAAcA,EAAM,YACtB,CAAC,CACH,GACe,KAAK,IAAI,cAAc,EAExC,KACF,CACA,IAAK,MAAO,CAOV,IAAMgC,EAAwBhC,EAAM,gBACjC,IAAI,CAACiC,EAAGC,IAAOD,EAAE,YAAcC,EAAI,EAAG,EACtC,OAAQA,GAAMA,IAAM,EAAE,EACzB,GAAIF,EAAsB,SAAW,EAAG,MAIxC,GAAI,CAAChC,EAAM,eAAgB,CACzB,GAAIA,EAAM,sBAAsB,SAAW,EAAG,MAC9C,EAAE,eAAe,EACjB,IAAMmC,EAAW,EAAE,SACfH,EAAsBA,EAAsB,OAAS,CAAC,EACtDA,EAAsB,CAAC,EAC3B,KAAK,MAAM,IAAI,CACb,WAAY,GACZ,oBAAqBG,CACvB,CAAC,EACD,KACF,CAEA,EAAE,eAAe,EACjB,IAAMhB,EAAaa,EAAsB,QAAQhC,EAAM,mBAAmB,EACtEoB,EACJ,GAAID,EAAa,EAEfC,EAAU,EAAE,SAAWY,EAAsB,OAAS,EAAI,MACrD,CACL,IAAMI,EAAQ,EAAE,SAAW,GAAK,EAChChB,GACGD,EAAaiB,EAAQJ,EAAsB,QAAUA,EAAsB,MAChF,CACA,KAAK,MAAM,IAAI,CACb,oBAAqBA,EAAsBZ,CAAO,CACpD,CAAC,EACD,KACF,CACA,IAAK,SAAU,CAKb,GAAIpB,EAAM,eAAgB,CACxB,KAAK,MAAM,IAAI,CAAE,kBAAmB,IAAK,CAAC,EAC1C,KACF,CAGA,GAAIA,EAAM,kBAAmB,CAC3B,KAAK,IAAI,wBAAwB,EACjC,KAAK,MAAM,IAAI,CAAE,oBAAqB,EAAG,CAAC,EAC1C,KACF,CACA,GAAIA,EAAM,cAAgB,EAAE,kBAAkB,aAAeA,EAAM,aAAe,KAAM,CACtF,IAAMwB,EAAS,EAAE,OAAO,QAAqB,kBAAkB,GAAK,EAAE,OAChEC,EAAOzB,EAAM,YACnB,KAAK,IAAI,eAAe,EACxB0B,EAAgBF,EAAQC,CAAI,CAC9B,CACA,KAAK,MAAM,IAAI,CAAE,oBAAqB,EAAG,CAAC,EAC1C,KACF,CACF,CACF,CAQQ,yBAAyBf,EAAgC,CAC/D,IAAMV,EAAQ,KAAK,MAAM,IAAI,EAC7B,GAAIA,EAAM,gBAAgB,SAAW,EAAG,OAAO,KAE/C,IAAMqC,EADU,KAAK,OAAOrC,EAAM,gBAAgB,OAAS,GAAKU,CAAO,EACtCA,EACjC,QAASwB,EAAIG,EAAgBH,EAAIlC,EAAM,gBAAgB,OAAQkC,IAC7D,GAAIlC,EAAM,gBAAgBkC,CAAC,GAAG,YAAa,OAAOA,EAEpD,OAAO,IACT,CASQ,eAAelC,EAAkBsC,EAA6B,CACpE,IAAMC,EAAQC,EAAQ,IAAI,IAAM,EAC1BC,EAAWzC,EAAM,gBAAgB,UAAWiC,GAAMS,EAAQT,CAAC,IAAMM,CAAK,EAC5E,GAAIE,GAAY,EAAG,OAAOA,EAC1B,IAAME,EAAO3C,EAAM,gBAChB,IAAI,CAACiC,EAAGC,IAAOD,EAAE,YAAcC,EAAI,EAAG,EACtC,OAAQA,GAAMA,IAAM,EAAE,EACzB,OAAQI,EAAaK,EAAKA,EAAK,OAAS,CAAC,EAAIA,EAAK,CAAC,IAAM,CAC3D,CAQQ,YAAY3C,EAAkBoC,EAAqB,CACzD,QACM,EAAIpC,EAAM,oBAAsBoC,EACpC,GAAK,GAAK,EAAIpC,EAAM,gBAAgB,OACpC,GAAKoC,EAEL,GAAIpC,EAAM,gBAAgB,CAAC,GAAG,YAAa,CACzC,KAAK,MAAM,IAAI,CAAE,oBAAqB,CAAE,CAAC,EACzC,MACF,CAEJ,CAQQ,aAAaA,EAAkBoC,EAAqB,CAC1D,IAAMQ,EAAO5C,EAAM,oBAAsBoC,EACzC,KAAK,MAAM,IAAI,CACb,oBAAqBpC,EAAM,gBAAgB4C,CAAI,GAAG,YAAcA,EAAO,EACzE,CAAC,CACH,CAEQ,mBAAmBlC,EAA2B,CAEpD,IAAMmC,EADQ,KAAK,MAAM,IAAI,EACN,gBACpB,IAAI,CAAC,EAAGX,IAAO,EAAE,YAAcA,EAAI,EAAG,EACtC,OAAQA,GAAMA,IAAM,EAAE,EACnBY,EAAsB,MAAM,KAAK,CAAE,OAAQpC,CAAQ,EAAG,IAAM,CAAC,CAAC,EACpE,QAAWwB,KAAKW,EAAUC,EAAQZ,EAAIxB,CAAO,EAAE,KAAKwB,CAAC,EACrD,OAAOY,EAAQ,KAAK,CACtB,CAmBQ,oBAAoBrC,EAA2B,CACrD,IAAMsC,EAAUC,GAAkBvC,EAAM,KAAK,IAAI,SAAS,EAK1D,GAAI,CAACsC,EAAS,OAAO,KAAK,IAAI,QAE9B,IAAIE,EADgBD,GAAkBvC,EAAM,GAAG,KAAK,IAAI,SAAS,WAAW,GAClC,eAAiB,KAC3D,KAAOwC,GAAI,CACT,IAAMC,EAAM,iBAAiBD,CAAE,EAAE,oBACjC,GAAIC,GAAOA,IAAQ,OAAQ,CACzB,IAAMC,EAASD,EAAI,MAAM,GAAG,EAAE,OAAO,OAAO,EAAE,OAC9C,GAAIC,EAAS,EAAG,OAAOA,CACzB,CACA,GAAIF,IAAOF,EAAS,MACpBE,EAAKA,EAAG,aACV,CACA,OAAO,KAAK,IAAI,OAClB,CAEQ,cACNG,EACAC,EACA9C,EACAE,EACA,CACA,IAAM6C,EAAWN,GAAkBvC,EAAM,GAAGF,CAAS,WAAW6C,CAAK,EAAE,EACnEE,EACFA,EAAS,MAAM,EAEf,KAAK,IAAI,aAAaD,EAAQD,CAAK,CAAC,CAExC,CACF,ECtdO,IAAMG,GAAN,KAA6B,CASlC,YACUC,EACAC,EACAC,EAAmC,CAAC,EAC5C,CAHQ,WAAAF,EACA,kBAAAC,EACA,eAAAC,EAXV,KAAQ,YAAmC,KAE3C,KAAQ,QAAsB,KAC9B,KAAQ,QAAU,EAElB,KAAQ,SAAmD,KAC3D,KAAQ,eAAiB,EAMtB,CAEH,OAAc,CACZ,KAAK,YAAc,KAAK,MAAM,UAAWC,GAAS,KAAK,KAAKA,CAAI,CAAC,EACjE,KAAK,KAAK,KAAK,MAAM,IAAI,CAAC,CAC5B,CAEA,SAAgB,CACd,KAAK,cAAc,EACnB,KAAK,YAAc,KACnB,KAAK,OAAO,EACZ,KAAK,QAAU,IACjB,CAGA,OAAc,CACZ,KAAK,OAAO,EACZ,KAAK,QAAU,KACf,KAAK,SAAW,IAClB,CAUA,SAAgB,CACd,KAAK,MAAM,EACX,KAAK,KAAK,KAAK,MAAM,IAAI,CAAC,CAC5B,CAGA,YAAYC,EAAuB,CACjC,MAAO,EAAQ,KAAK,aAAa,IAAIA,CAAI,CAC3C,CASA,KAAKA,EAAcC,EAAwB,CACzC,OAAO,KAAK,YAAYD,CAAI,GAAK,CAAC,KAAK,WAAWA,EAAMC,CAAK,CAC/D,CAcA,KAAKC,EAAkBC,EAAmC,CACxD,IAAMC,EAAS,KAAK,cAAcF,CAAK,EAEvC,GADI,CAACE,GACD,KAAK,WAAWA,EAAO,KAAMA,EAAO,KAAK,EAAG,MAAO,KACvD,IAAMC,EAAU,KAAK,QACrB,GAAIA,GAAWA,EAAQ,MAAQD,EAAO,IAAK,CACzC,GAAIC,EAAQ,QAAUD,EAAO,MAAO,MAAO,OAG3C,IAAME,EAAQ,KAAK,IAAIF,EAAO,MAAM,OAASC,EAAQ,MAAM,MAAM,EAC3DE,EAAUH,EAAO,MAAM,OAASC,EAAQ,MAAM,OACpD,GAAIF,EAAU,GAAK,CAACI,GAAWD,EAAQH,EAAS,MAAO,MACzD,CACA,YAAK,IAAIC,CAAM,EACR,OACT,CAGQ,KAAKF,EAAwB,CACnC,IAAME,EAAS,KAAK,cAAcF,CAAK,EACvC,GAAI,CAACE,EAAQ,CACP,KAAK,UACP,KAAK,OAAO,EACZ,KAAK,QAAU,MAEbF,EAAM,cAAc,KAAK,MAAM,IAAI,CAAE,aAAc,IAAK,CAAC,EAC7D,MACF,CACI,KAAK,SAAW,KAAK,QAAQ,MAAQE,EAAO,KAChD,KAAK,IAAIA,CAAM,CACjB,CAEQ,cAAcF,EAAiC,CAMrD,GAFIM,EAAiBN,EAAM,gBAAgB,GAEvCA,EAAM,kBAAmB,OAAO,KACpC,IAAMO,EAAUP,EAAM,aACtB,GAAIO,GAAWP,EAAM,eAAiB,KACpC,OAAK,KAAK,YAAYO,EAAQ,cAAc,EACrC,CACL,KAAM,OACN,IAAKA,EAAQ,GACb,KAAMA,EAAQ,eACd,WAAY,CACV,KAAMA,EAAQ,eACd,KAAMA,EAAQ,sBACd,SAAU,GACV,QAASA,EAAQ,OACnB,EACA,MAAOP,EAAM,WACf,EAZsD,KAcxD,IAAMQ,EAASR,EAAM,sBAAsB,CAAC,EAC5C,MAAI,CAACQ,GAAU,CAAC,KAAK,YAAYA,EAAO,IAAI,EAAU,KAC/C,CACL,KAAM,OACN,IAAKA,EACL,KAAMA,EAAO,KACb,WAAYA,EACZ,MAAOR,EAAM,WACf,CACF,CAEQ,WAAWF,EAAcC,EAAwB,CACvD,OAAO,KAAK,UAAY,MAAQ,KAAK,SAAS,OAASD,GAAQ,KAAK,SAAS,QAAUC,CACzF,CAEQ,IAAIG,EAAsB,CAChC,KAAK,OAAO,EACZ,IAAMO,EAAW,KAAK,aAAa,IAAIP,EAAO,IAAI,EAC5CQ,EAAU,EAAE,KAAK,QACvB,GAAI,CAACD,GAAY,KAAK,WAAWP,EAAO,KAAMA,EAAO,KAAK,EAAG,CAI3D,KAAK,QAAU,CAAE,IAAKA,EAAO,IAAK,KAAMA,EAAO,KAAM,MAAOA,EAAO,MAAO,MAAO,IAAK,EACtF,KAAK,UAAUA,EAAQ,MAAM,EAC7B,MACF,CACA,IAAMS,EAAQ,IAAI,gBAClB,KAAK,QAAU,CAAE,IAAKT,EAAO,IAAK,KAAMA,EAAO,KAAM,MAAOA,EAAO,MAAO,MAAAS,CAAM,EAKhF,IAAIC,EACJ,GAAI,CACFA,EAAMH,EAASP,EAAO,MAAOS,EAAM,OAAQT,EAAO,UAAU,CAC9D,OAASW,EAAK,CACZ,KAAK,QAAQA,CAAG,EAChB,KAAK,OAAOX,EAAQ,CAAC,EAAGQ,CAAO,EAC/B,MACF,CACA,GAAI,MAAM,QAAQE,CAAG,EAAG,CACtB,KAAK,OAAOV,EAAQU,EAAKF,CAAO,EAChC,MACF,CACA,KAAK,UAAUR,EAAQ,SAAS,EAChC,KAAK,MAAMA,EAAQU,EAAKD,EAAM,OAAQD,CAAO,CAC/C,CAEA,MAAc,MACZR,EACAY,EACAC,EACAL,EACe,CACf,IAAIM,EACJ,GAAI,CACF,IAAMJ,EAAM,MAAME,EAIlB,GAAIC,EAAO,SAAWL,IAAY,KAAK,QAAS,OAChDM,EAAS,MAAM,QAAQJ,CAAG,EAAIA,EAAM,CAAC,CACvC,OAASC,EAAK,CAGZ,GAAIE,EAAO,SAAWL,IAAY,KAAK,SAAWO,GAAaJ,CAAG,EAAG,OACrE,KAAK,QAAQA,CAAG,EAChBG,EAAS,CAAC,CACZ,CACA,KAAK,OAAOd,EAAQc,EAAQN,CAAO,CACrC,CAEQ,OAAOR,EAAgBc,EAA4BN,EAAuB,CAChF,GAAIA,IAAY,KAAK,QAAS,OAC9B,IAAMQ,EAAO,KAAK,cAAc,KAAK,MAAM,IAAI,CAAC,EAChD,GAAI,CAACA,GAAQA,EAAK,MAAQhB,EAAO,IAAK,OAElCc,EAAO,SAAW,EACpB,KAAK,SAAW,CAAE,KAAMd,EAAO,KAAM,MAAOA,EAAO,KAAM,EAChD,KAAK,UAAU,OAASA,EAAO,OACxC,KAAK,SAAW,MAGlB,IAAMiB,EAAS,CAAE,KAAMjB,EAAO,KAAM,MAAOA,EAAO,MAAO,OAAQ,MAAgB,EACjF,GAAIA,EAAO,OAAS,OAClB,KAAK,MAAM,IAAK,IAAO,CACrB,aACE,EAAE,cAAgB,EAAE,aAAa,KAAOA,EAAO,IAC3C,CAAE,GAAG,EAAE,aAAc,QAASc,CAAO,EACrC,EAAE,aACR,aAAcG,CAChB,EAAE,MACG,CAKL,IAAMC,EAAuB,CAAE,GAAGlB,EAAO,WAAY,QAASc,CAAO,EACjE,KAAK,SAAW,KAAK,QAAQ,MAAQd,EAAO,MAAK,KAAK,QAAQ,IAAMkB,GACxE,KAAK,MAAM,IAAKC,IAAO,CACrB,YAAaA,EAAE,YAAY,IAAKC,GAAQA,IAAOpB,EAAO,WAAakB,EAAWE,CAAG,EACjF,aAAcH,CAChB,EAAE,CACJ,CAEIH,EAAO,SAAW,EAAG,KAAK,UAAU,SAASd,EAAO,KAAMA,EAAO,KAAK,EACrE,KAAK,UAAU,WAAWA,EAAO,KAAMA,EAAO,KAAK,CAC1D,CAGQ,UAAUA,EAAgBqB,EAAkC,CAClE,IAAMC,EAAO,KAAK,MAAM,IAAI,EAAE,aAE5BA,GACAA,EAAK,OAAStB,EAAO,MACrBsB,EAAK,QAAUtB,EAAO,OACtBsB,EAAK,SAAWD,GAIlB,KAAK,MAAM,IAAI,CAAE,aAAc,CAAE,KAAMrB,EAAO,KAAM,MAAOA,EAAO,MAAO,OAAAqB,CAAO,CAAE,CAAC,CACrF,CAEQ,QAAe,CACrB,KAAK,SAAS,OAAO,MAAM,EAG3B,KAAK,SACP,CAEQ,QAAQV,EAAoB,CAC9B,KAAK,iBACT,KAAK,eAAiB,GAEtB,QAAQ,KACN,4IACAA,CACF,EACF,CACF,EAEA,SAASI,GAAaJ,EAAuB,CAC3C,OAAOA,aAAe,OAASA,EAAI,OAAS,YAC9C,CCjVO,IAAMY,GAAN,KAAsB,CAC3B,YACUC,EACAC,EAAsC,CAAC,EAC/C,CAFQ,WAAAD,EACA,eAAAC,CACP,CAEH,cAAcC,EAAe,CAC3B,IAAMC,EAAQ,KAAK,MAAM,IAAI,EACvBC,EAAaD,EAAM,YAAY,OAAQE,GAAMA,EAAE,OAAS,aAAa,EAC3E,GAAIH,EAAQ,GAAKA,GAASE,EAAW,OAAQ,OAC7C,IAAME,EAAQF,EAAWF,CAAK,EACxBK,EAAOH,EAAW,OAAO,CAACI,EAAGC,IAAMA,IAAMP,CAAK,EAC9CQ,EAAeP,EAAM,YAAY,OAAQE,GAAMA,EAAE,OAAS,aAAa,EAE7E,GAAI,KAAK,UAAU,eAAgB,CACjC,GAAM,CAAE,SAAAM,CAAS,EAAIC,EAAWT,EAAM,KAAMA,EAAM,eAAe,EACjE,KAAK,UAAU,eAAe,CAC5B,SAAAQ,EACA,aAAcL,EAAM,KACpB,WAAYC,EAAK,IAAKF,GAAMA,EAAE,IAAI,CACpC,CAAC,CACH,CAEA,IAAMQ,EAAkB,CAAC,GAAGH,EAAcJ,EAAO,GAAGC,CAAI,EASlDO,EAAgB,KAAK,MACxB,KAAK,CAAE,YAAaD,CAAgB,CAAC,EACrC,gBAAgB,UAAWE,GAAMA,EAAE,WAAW,EAEjD,KAAK,MAAM,IAAI,CACb,YAAaF,EACb,WAAY,GACZ,oBAAqBC,CACvB,CAAC,CACH,CAEA,iBAAkB,CACF,KAAK,MAAM,IAAI,EACnB,gBAAgB,SAAW,GACrC,KAAK,MAAM,IAAKT,IAAO,CACrB,gBAAiBA,EAAE,gBAAgB,MAAM,EAAG,EAAE,EAC9C,oBAAqB,EACvB,EAAE,CACJ,CACF,ECxDO,IAAMW,GAAsB,aAU5B,SAASC,GAAuBC,EAA4B,CACjE,MAAO,CACL,GAAIA,EAAK,UACT,MAAOA,EAAK,MACZ,GAAIA,EAAK,QAAU,CAAE,IAAK,GAAGF,EAAmB,GAAG,mBAAmBE,EAAK,MAAM,CAAC,EAAG,EACrF,SAAUA,EAAK,WAAa,KAC5B,GAAIA,EAAK,OAAS,CAAE,MAAOA,EAAK,KAAM,EACtC,GAAIA,EAAK,QAAU,CAAE,OAAQA,EAAK,MAAO,CAC3C,CACF,CAQO,SAASC,GAAyBC,EAA0D,CACjG,OAAQA,GAAQ,MAAM,SAAW,CAAC,GAAG,IAAIH,EAAsB,CACjE,CCRO,IAAMI,GAAN,KAAyB,CAQ9B,YACUC,EACAC,EAEAC,EAA2B,IAAM,GACzC,CAJQ,WAAAF,EACA,eAAAC,EAEA,eAAAC,EANV,KAAQ,eAAiB,EAOtB,CAWH,YAAYC,EAA0D,CACpE,GAAI,CAAC,KAAK,UAAU,EAAG,MAAO,CAAC,EAC/B,GAAI,MAAK,UAAU,EACnB,OAAOC,GAAyBD,CAAM,CACxC,CAaA,MAAM,IAAIE,EAAeC,EAAqBC,EAAyC,CAGrF,GAAI,CAAC,KAAK,UAAU,EAAG,OACvB,IAAMC,EAAS,KAAK,UAAU,EAG9B,GAAI,CAACA,EAAQ,OAEb,GAAIH,EAAM,KAAK,EAAE,SAAW,EAAG,CAC7B,KAAK,MAAME,CAAS,EACpB,MACF,CASA,IAAME,EAAQ,IAAMH,EAAO,SAAW,CAACC,EAAU,GAAK,CAAC,KAAK,KAAKC,CAAM,EAEvE,GAAI,CACF,IAAME,EAAM,MAAMF,EAAO,MAAMH,EAAOC,CAAM,EAC5C,GAAIG,EAAM,EAAG,OAEb,IAAME,EAASH,EAAO,UAAUE,CAAG,EAC7BE,EAAO,MAAM,QAAQD,CAAM,EAAIA,EAAS,CAAC,EACzCE,EAAWL,EAAO,OAAS,KAAOI,EAAK,MAAM,EAAGJ,EAAO,KAAK,EAAII,EACtE,GAAIH,EAAM,EAAG,OACb,KAAK,OAAOI,CAAQ,CACtB,OAASC,EAAK,CAKZ,GAFIR,EAAO,SAAWS,GAAaD,CAAG,IACtC,KAAK,QAAQA,CAAG,EACZL,EAAM,GAAG,OACb,KAAK,MAAMF,CAAS,CACtB,CACF,CAGQ,KAAKC,EAAiC,CAC5C,OAAO,KAAK,UAAU,GAAK,KAAK,UAAU,IAAMA,CAClD,CAUA,QAAe,CACb,IAAMK,EAAW,KAAK,YAAY,KAAK,MAAM,IAAI,EAAE,YAAY,EAC/D,KAAK,OAAOA,GAAY,CAAC,CAAC,CAC5B,CAEQ,MAAMN,EAAgC,CACvCA,EAAU,GACf,KAAK,OAAO,CAAC,CAAC,CAChB,CAGQ,OAAOM,EAA2B,CACpCA,EAAS,SAAW,GAAK,KAAK,MAAM,IAAI,EAAE,SAAS,SAAW,GAClE,KAAK,MAAM,IAAI,CAAE,SAAAA,CAAS,CAAC,CAC7B,CAEQ,QAAQC,EAAoB,CAC9B,KAAK,iBACT,KAAK,eAAiB,GAEtB,QAAQ,KACN,uIACAA,CACF,EACF,CACF,EAEA,SAASC,GAAaD,EAAuB,CAC3C,OAAOA,aAAe,OAASA,EAAI,OAAS,YAC9C,CChJO,IAAME,GAA4B,qBAiBzC,IAAMC,GAAa,qCAUbC,GAAgB,uEAoBf,SAASC,GAAyBC,EAAeC,EAAsC,CAC5F,OAAIA,EAAiBC,GAAO,EAAG,KAAK,IAAIF,EAAO,CAAmB,CAAC,EAC5DE,GAAO,EAAG,KAAK,IAAIF,EAAO,CAAgB,CAAC,CACpD,CAEA,SAASE,GAAOC,EAAcC,EAAiC,CAC7D,MAAO,CACL,KAAAD,EACA,KAAAC,EACA,UAAW,QAAQA,CAAI,MAAMP,EAAU,MAAMC,EAAa,GAC5D,CACF,CAsBO,SAASO,GAA4BL,EAAeC,EAA4B,CACrF,MAAO,CAACA,GAAYD,GAAS,CAC/B,CAmBO,SAASM,GACdN,EACAC,EACAM,EACAC,EACiB,CAEjB,GAAI,EADW,CAACP,GAAYD,GAAS,GAAkBS,GAAeF,EAAWC,CAAS,GAC7E,CACX,IAAME,EAAOX,GAAyBC,EAAOC,CAAQ,EACrD,MAAO,CACL,GAAGS,EACH,SAAUC,GAA2BD,EAAK,IAAI,EAC9C,kBAAmB,CAACT,GAAYD,EAAQU,EAAK,KAAO,CAACA,EAAK,KAAO,CAAC,EAAI,CAAC,CACzE,CACF,CACA,IAAME,EAAU,EAChB,MAAO,CACL,GAAGV,GAAO,EAAG,CAAwB,EACrC,SAAUS,GAA2B,CAAC,EACtC,kBAAmBX,EAAQY,EAAU,CAACA,EAAU,EAAGA,EAAU,CAAC,EAAI,CAAC,CACrE,CACF,CAOA,SAASH,GAAeF,EAA4BC,EAAmC,CAIrF,MAHI,CAACD,GAAaA,EAAU,SAAW,GAAKC,GAAa,MAAQA,GAAa,GAG1ED,EAAU,KAAMM,GAAMA,GAAK,CAAC,EAAU,GAC3B,KAAK,KAAK,KAAK,IAAI,GAAGN,CAAS,CAAC,EAC/B,GAAKC,CACvB,CASO,SAASM,GACdC,EACmD,CACnD,GAAI,OAAO,SAAa,IAAa,OAAO,KAC5C,IAAMC,EAAQ,MAAM,KAAKD,EAAK,iBAA8B,mBAAmB,CAAC,EAChF,GAAIC,EAAM,SAAW,EAAG,OAAO,KAC/B,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,MAAM,QAAU,0DACtB,QAAWC,KAAQF,EAAO,CACxB,IAAMG,EAAQD,EAAK,UAAU,EAAI,EAGjCC,EAAM,gBAAgB,IAAI,EAC1BA,EAAM,gBAAgB,MAAM,EAC5BA,EAAM,gBAAgB,eAAe,EACrCA,EAAM,MAAM,WAAa,SACzBA,EAAM,MAAM,MAAQ,cACpBF,EAAM,YAAYE,CAAK,CACzB,CAGAJ,EAAK,YAAYE,CAAK,EACtB,IAAMV,EAAY,MAAM,KAAKU,EAAM,QAAQ,EAAE,IAAKG,GAAOA,EAAkB,WAAW,EACtFH,EAAM,OAAO,EACb,IAAMI,EAAK,iBAAiBN,CAAI,EAU1BP,EACJO,EAAK,aACJ,OAAO,WAAWM,EAAG,WAAW,GAAK,IACrC,OAAO,WAAWA,EAAG,YAAY,GAAK,IACtC,OAAO,WAAWA,EAAG,SAAS,GAAK,GACtC,MAAO,CAAE,UAAAd,EAAW,UAAAC,CAAU,CAChC,CAQO,SAASG,GAA2BR,EAAsB,CAC/D,OAAO,MAAM,KAAK,CAAE,OAAQA,CAAK,EAAG,IAAM,eAAe,EAAE,KAAK,GAAG,CACrE,CAGO,SAASmB,IAAuC,CACrD,OACE,OAAO,OAAW,KAClB,OAAO,OAAO,YAAe,YAC7B,OAAO,WAAWC,EAAyB,EAAE,OAEjD,CCpLO,SAASC,GACdC,EACAC,EACS,CACT,IAAMC,EAAUD,EAAK,iBAAmB,OAClCE,EAAcF,EAAK,qBAAuB,GAe1CG,EAdaJ,EAAO,sBAAwB,GAKbA,EAAO,wBASPA,EAAO,YAE5C,GAAIA,EAAO,WAAY,CAIrB,IAAMK,EAAYF,EAAcH,EAAO,UAAY,GACnD,OAAOI,GAAcC,CACvB,CAEA,GAAIH,IAAY,OAAQ,CACtB,IAAMG,EAAYF,EAAcH,EAAO,UAAY,GAM7CM,EAAaN,EAAO,KAAK,QAAQ,OAAQ,EAAE,EAAE,OAC7CO,EAAaP,EAAO,aAAe,MAAQA,EAAO,aAAeM,EACvE,OAAQF,GAAcJ,EAAO,YAAcK,GAAaE,CAC1D,CAEA,OAAIL,IAAY,UACNE,GAAcJ,EAAO,YAAcA,EAAO,WAG7C,EACT,CCrDO,SAASQ,GAAYC,EAKjB,CACT,GAAIA,EAAO,kBAAmB,CAK5B,GAAM,CAAE,KAAAC,EAAM,OAAAC,CAAO,EAAIF,EAAO,kBAChC,MAAO,cAAcC,CAAI,IAAIC,CAAM,EACrC,CAEA,OADgBF,EAAO,cAAgB,MAAQA,EAAO,eAAiB,KAEnE,QAAQA,EAAO,cAAc,EAAE,GAC/B,QAAQA,EAAO,sBAAsB,CAAC,GAAG,MAAQ,EAAE,EACzD,CAOA,SAASG,GAAkBC,EAAmC,CAC5D,GAAI,CAACA,EAAK,OAAOC,GAAU,EAC3B,GAAM,CAACC,EAAGC,CAAC,EAAIH,EAAI,MAAM,GAAG,EAAE,IAAI,MAAM,EACxC,MAAO,CAAE,KAAME,EAAG,MAAOC,EAAI,CAAE,CACjC,CAEA,SAASC,GAAgBC,EAAgD,CACvE,IAAMC,EAAWC,GAAUF,CAAW,EACtC,OAAOC,EAAWE,GAAOF,CAAQ,EAAIL,GAAU,CACjD,CAGO,SAASQ,GAAUb,EAAwBc,EAAqC,CACrF,IAAMC,EAAWC,GAAehB,EAAO,KAAMA,EAAO,gBAAiBA,EAAO,gBAAgB,EACtFiB,EAAwBjB,EAAO,YAAY,OAAQkB,GAAMA,EAAE,OAAS,aAAa,EACjFC,EAAmBF,EAAsB,CAAC,EAE1CG,EAAkBpB,EAAO,YAC5B,OAAQkB,GAAMA,EAAE,OAAS,aAAa,EACtC,IAAKA,GAAMA,EAAE,IAAI,EACjB,KAAK,GAAG,EAKLG,EAAoBC,EACxBtB,EAAO,KACP,KAAK,IAAIA,EAAO,WAAYA,EAAO,KAAK,MAAM,EAC9CoB,CACF,EAgBMG,EAFJF,IAAsB,GACtBG,GAA0BxB,EAAO,KAAMA,EAAO,gBAAgB,OAAQoB,CAAe,EAEnF,GACAK,EAAmBzB,EAAO,KAAMqB,EAAmBrB,EAAO,gBAAgB,EAIxE0B,EAAcP,GAAkB,SAAW,CAAC,EAQ5CQ,EAAa3B,EAAO,cAAgB,MAAQA,EAAO,eAAiB,KACtE4B,EACAC,EACAC,EACAC,EACJ,GAAIJ,GAAc3B,EAAO,cAAgBA,EAAO,eAAiB,KAAM,CACrE,IAAMgC,EAAYhC,EAAO,aAAa,GAChCiC,EAAoBjC,EAAO,gBAAgB,KAAMkC,IAAMA,GAAE,KAAOF,CAAS,EACzEG,GAAYnC,EAAO,aAAeA,EAAO,cACzCoC,GAAYH,EAAoB,GAAKjC,EAAO,KAAK,MAAMA,EAAO,cAAemC,EAAS,EAC5FN,EAAcO,GAAU,KAAK,EAC7BN,EAAgB9B,EAAO,aAAa,QACpC+B,EAAa/B,EAAO,aAAa,eACjC4B,EAAkBS,EAAcrC,EAAO,aAAa,QAASoC,EAAS,CACxE,MACEP,EAAcN,EAAY,KAAK,EAC/BO,EAAgBJ,EAChBK,EAAaZ,GAAkB,KAC/BS,EAAkBS,EAAcX,EAAaH,CAAW,EAQ1D,IAAMe,EAAStC,EAAO,aAChBuC,EAAoBD,GAAU,MAAQP,GAAc,MAAQO,EAAO,OAASP,EAC9EQ,GAAqBD,EAAO,SAAW,QAAUA,EAAO,QAAUT,IACpED,EAAkBE,GAEpB,IAAMU,EAAqBD,GAAqBD,EAAO,SAAW,UAG5DG,EAAkB3B,EAAK,yBAA2B,GACpD2B,IACFb,EAAkBA,EAAgB,OAAQc,GAAMA,EAAE,WAAW,GAU/D,IAAMC,EAAiB3C,EAAO,kBACxB4C,EAAejB,EAGjB3B,EAAO,cAAgB,CACrB,KAAMA,EAAO,aAAa,eAC1B,QAASA,EAAO,aAAa,OAC/B,EACAmB,EAKA0B,EAAmBF,EAAiBA,EAAe,OAASG,EAAkBF,CAAY,EAOxFG,EACJpB,GAAcqB,EAAiBH,CAAgB,EAAII,EAAiBjD,EAAO,YAAY,EAAI,KACzF+C,IAAcF,EAAmB,cAOrC,IAAIK,EAAiC,KACjCC,EAAgC,KACpC,GAAIH,EAAiBH,CAAgB,EAAG,CACtC,IAAMO,EAAMrD,GAAY,CAAE,GAAGC,EAAQ,sBAAAiB,CAAsB,CAAC,EACtDoC,EAAQrD,EAAO,cACrBkD,EACEG,GAASA,EAAM,MAAQD,EACnB,CAAE,KAAMC,EAAM,KAAM,MAAOA,EAAM,KAAM,EACvCV,EAIExC,GAAkBwC,EAAe,GAAG,EAKpCI,EACE5C,GAAkB4C,EAAa,KAAK,EACpCvC,GAAgBR,EAAO,cAAc,IAAI,EACnD4B,EAAkB0B,GAAiBJ,CAAQ,EAK3C,IAAMK,GAAUvD,EAAO,kBACnB6C,IAAqB,cAAgBU,IAAWA,GAAQ,MAAQH,IAClED,EAAiBI,GAAQ,IAE7B,CAcA,IAAMC,EAAkBd,GAAkCD,EAAkBC,EAAE,YAAc,GACxFe,EACJ,GAAI9B,EAAY,CACd,IAAM+B,EAAc1D,EAAO,cAAc,SAAW,CAAC,EACrDyD,EACEzD,EAAO,cAAgB,MAAQ0D,EAAY,OAAOF,CAAc,EAAE,SAAW,CACjF,MACEC,EACEtC,GAAoB,MAAQO,EAAY,OAAO8B,CAAc,EAAE,SAAW,EAG9E,IAAMG,EAAiBC,GACrB,CAIE,WAAYjC,GAAcgB,GAAkB,KAC5C,sBAAuBf,EAAgB,OACvC,UAAW5B,EAAO,UAClB,KAAMA,EAAO,KACb,YAAaA,EAAO,YAGpB,UAAWA,EAAO,WAAawC,EAC/B,WAAYxC,EAAO,WACnB,uBAAAyD,EACA,YAAazD,EAAO,SAAS,OAAS,CACxC,EACA,CACE,gBAAiBc,EAAK,gBACtB,oBAAqBA,EAAK,mBAC5B,CACF,EASM+C,GACJF,GACA3D,EAAO,qBAAuB,GAC9B,EAAQ4B,EAAgB5B,EAAO,mBAAmB,GAAG,YAEvD,MAAO,CACL,SAAAe,EACA,sBAAAE,EACA,gBAAAW,EACA,iBAAAiB,EACA,SAAAK,EACA,eAAAC,EACA,gBAAA/B,EACA,YAAAS,EACA,mBAAAW,EACA,eAAAmB,EACA,qBAAAE,EACF,CACF,CCtQO,SAASC,GAAqBC,EAA+C,CAClF,OAAIA,EAAI,OAAS,QACRC,GAAaD,CAAG,EAElBE,GAAYF,CAAG,CACxB,CAEA,SAASC,GAAaD,EAA2E,CAC/F,GAAM,CAAE,KAAAG,EAAM,gBAAAC,EAAiB,YAAAC,EAAa,WAAAC,EAAY,iBAAAC,CAAiB,EAAIP,EAEvEQ,EADaH,EAAY,OAAQI,GAAOA,EAAG,OAAS,aAAa,EAC7C,CAAC,EAC3B,GAAI,CAACD,GAAQ,QAAS,OAAO,KAE7B,IAAME,EAAkBL,EACrB,OAAQI,GAAOA,EAAG,OAAS,aAAa,EACxC,IAAKA,GAAOA,EAAG,IAAI,EACnB,KAAK,GAAG,EACLE,EAAUC,EAAoBT,EAAMG,EAAYI,CAAe,EAC/DG,EAAQC,EAAmBX,EAAMQ,EAASJ,CAAgB,EAC1DQ,EAAQC,EAAeR,EAAO,QAASK,CAAK,EAClD,GAAI,CAACE,EAAO,OAAO,KAInB,IAAME,EAAaF,EAAM,KAAK,YAAY,EACpCG,EAAcf,EAAK,YAAY,EAAE,YAAYc,CAAU,EACvDE,EAAaD,GAAe,EAAIA,EAAc,KAAK,IAAI,EAAGf,EAAK,OAASY,EAAM,KAAK,MAAM,EACzFK,EAAWD,EAAaJ,EAAM,KAAK,OACnCM,EAAelB,EAAK,MAAMgB,EAAYC,CAAQ,EAG9CE,EADmBF,EAAWjB,EAAK,QAAUA,EAAKiB,CAAQ,IAAM,IAClCA,EAAW,EAAIA,EAE7CG,EAAiC,CACrC,GAAI,OAAO,WAAW,EACtB,YAAa,GACb,KAAMf,EAAO,KACb,KAAMa,EACN,KAAMN,EAAM,KACZ,eAAgBP,EAAO,KACvB,sBAAuBA,EAAO,KAC9B,QAASA,EAAO,SAAW,CAAC,EAC5B,SAAUO,EAAM,SAChB,KAAMA,EAAM,KACZ,SAAUA,EAAM,QAClB,EAEA,MAAO,CACL,MAAO,CACL,KAAAZ,EACA,gBAAiB,CAAC,GAAGC,EAAiBmB,CAAS,EAC/C,YAAalB,EAAY,OAAQI,GAAOA,IAAOD,CAAM,EACrD,WAAYc,EACZ,WAAYC,EAAU,GACtB,YAAaD,EACb,oBAAqB,EACvB,EACA,SAAAA,CACF,CACF,CAEA,SAASpB,GAAYF,EAA0E,CAC7F,GAAM,CAAE,KAAAG,EAAM,gBAAAC,EAAiB,aAAAoB,EAAc,cAAAC,EAAe,YAAAC,CAAY,EAAI1B,EAG5E,GAAII,EAAgB,KAAMuB,GAAMA,EAAE,KAAOH,EAAa,EAAE,EAAG,OAAO,KAElE,IAAMI,EAAYzB,EAAK,MAAMsB,EAAeC,CAAW,EACjDX,EAAQC,EAAeQ,EAAa,QAASI,CAAS,EAC5D,GAAI,CAACb,EAAO,OAAO,KAKnB,IAAME,EAAaF,EAAM,KAAK,YAAY,EACpCc,EAAaD,EAAU,YAAY,EAAE,YAAYX,CAAU,EAC3DE,EAAaM,EAAgB,KAAK,IAAI,EAAGI,CAAU,EACnDT,EAAWD,EAAaJ,EAAM,KAAK,OACnCM,EAAelB,EAAK,MAAMgB,EAAYC,CAAQ,EAO9CU,EAAaV,EAAWjB,EAAK,QAAUA,EAAKiB,CAAQ,IAAM,IAAMA,EAAW,EAAIA,EAC/EE,EAAWnB,EAAK,OAEhB4B,EAAgC,CACpC,GAAI,OAAO,WAAW,EACtB,YAAa,GACb,KAAMP,EAAa,eACnB,KAAMH,EACN,KAAMN,EAAM,KACZ,eAAgBS,EAAa,eAC7B,sBAAuBA,EAAa,sBACpC,QAASA,EAAa,QACtB,SAAUT,EAAM,SAChB,KAAMA,EAAM,KACZ,SAAUA,EAAM,QAClB,EAKIiB,EAAW5B,EAAgB,OAC3B6B,EAAU,EACd,QAASC,EAAI,EAAGA,EAAI9B,EAAgB,OAAQ8B,IAAK,CAC/C,IAAMC,EAAMhC,EAAK,QAAQC,EAAgB8B,CAAC,EAAE,KAAMD,CAAO,EACzD,GAAIE,IAAQ,GACZ,IAAIA,GAAOL,EAAY,CACrBE,EAAWE,EACX,KACF,CACAD,EAAUE,EAAM/B,EAAgB8B,CAAC,EAAE,KAAK,OAC1C,CACA,IAAME,EAAY,CAAC,GAAGhC,CAAe,EACrC,OAAAgC,EAAU,OAAOJ,EAAU,EAAGD,CAAQ,EAE/B,CACL,MAAO,CACL,KAAA5B,EACA,gBAAiBiC,EACjB,WAAYL,EAAS,GACrB,WAAYT,EACZ,aAAc,KACd,cAAe,KACf,YAAa,KACb,YAAaA,EACb,oBAAqB,EACvB,EACA,SAAAA,CACF,CACF,CC/IO,SAASe,GACdC,EACAC,EACAC,EACmC,CACnC,IAAMC,EAASH,EAAK,MAAM,EAAGC,CAAK,EAC9BG,EAAQJ,EAAK,MAAME,CAAG,EACpBG,GAAaF,IAAW,IAAMA,EAAO,SAAS,GAAG,IAAMC,EAAM,WAAW,GAAG,EACjF,OAAIC,IAAWD,EAAQA,EAAM,MAAM,CAAC,GAC7B,CAAE,KAAMD,EAASC,EAAO,QAASF,EAAMD,GAASI,EAAY,EAAI,EAAG,CAC5E,CCXO,IAAMC,GAAN,KAAoB,CACzB,YAAoBC,EAAkB,CAAlB,UAAAA,CAAmB,CAGvC,MAAMC,EAAuB,CAC3B,IAAMC,EAAQ,KAAK,KAAK,MAAM,IAAI,EAClC,GAAIA,EAAM,cAAc,KAAOD,EAAS,OACxC,IAAME,EAAQD,EAAM,gBAAgB,KAAME,GAAMA,EAAE,KAAOH,CAAO,EAChE,GAAI,CAACE,EAAO,OAEZ,IAAIE,EAAM,EACNC,EAAS,GACb,QAAWF,KAAKF,EAAM,gBAAiB,CACrC,IAAMK,EAAML,EAAM,KAAK,QAAQE,EAAE,KAAMC,CAAG,EAC1C,GAAIE,IAAQ,GACZ,IAAIH,EAAE,KAAOH,EAAS,CACpBK,EAASC,EACT,KACF,CACAF,EAAME,EAAMH,EAAE,KAAK,OACrB,CACIE,EAAS,GACb,KAAK,KAAK,MAAM,IAAI,CAClB,aAAcH,EACd,cAAeG,EACf,YAAaA,EAASH,EAAM,KAAK,OACjC,YAAaG,EAASH,EAAM,KAAK,OACjC,oBAAqB,GAQrB,kBAAmB,IACrB,CAAC,CACH,CAGA,MAAa,CACG,KAAK,KAAK,MAAM,IAAI,EACvB,cACX,KAAK,KAAK,MAAM,IAAI,CAClB,aAAc,KACd,cAAe,KACf,YAAa,KACb,oBAAqB,EACvB,CAAC,CACH,CAGA,aAAaK,EAA8B,CACzC,IAAMN,EAAQ,KAAK,KAAK,MAAM,IAAI,EAC5BO,EAAUP,EAAM,aAChBI,EAASJ,EAAM,cACfQ,EAAOR,EAAM,YAInB,GAHI,CAACO,GAAWH,GAAU,MAAQI,GAAQ,MAGtC,CAACR,EAAM,gBAAgB,KAAME,GAAMA,EAAE,KAAOK,EAAQ,EAAE,EAAG,MAAO,GAMpE,GAAM,CAAE,KAAME,CAAQ,EACpBH,IAAgB,GACZI,GAAeV,EAAM,KAAMI,EAAQI,CAAI,EACvC,CAAE,KAAMR,EAAM,KAAK,MAAM,EAAGI,CAAM,EAAIE,EAAcN,EAAM,KAAK,MAAMQ,CAAI,CAAE,EAC3EG,EAAUP,EAASE,EAAY,OACrC,YAAK,KAAK,MAAM,IAAKM,IAAO,CAC1B,KAAMH,EACN,gBAAiBG,EAAE,gBAAgB,OAAQV,GAAMA,EAAE,KAAOK,EAAQ,EAAE,EACpE,YAAaI,EACb,YAAaA,EACb,oBAAqB,EACvB,EAAE,EACF,KAAK,KAAK,kBAAkBA,CAAO,EACnC,KAAK,eAAe,EACb,EACT,CAGA,gBAAgBE,EAA6B,CAC3C,IAAMb,EAAQ,KAAK,KAAK,MAAM,IAAI,EAC5Bc,EAA4B,CAAE,YAAaD,CAAO,EACpDb,EAAM,cAAgBA,EAAM,eAAiB,MAAQa,GAAU,OAC7DA,EAASb,EAAM,eACjBc,EAAM,aAAe,KACrBA,EAAM,cAAgB,KACtBA,EAAM,YAAc,KACpBA,EAAM,oBAAsB,IACnBd,EAAM,aAAe,OAC9Bc,EAAM,YAAc,KAAK,IAAId,EAAM,YAAaa,CAAM,IAG1D,KAAK,KAAK,MAAM,IAAIC,CAAK,EACzB,KAAK,eAAe,CACtB,CAGA,UAAUD,EAA6B,CACrC,IAAMb,EAAQ,KAAK,KAAK,MAAM,IAAI,EAClC,GACEA,EAAM,cACNA,EAAM,eAAiB,MACvBA,EAAM,aAAe,MACrBa,GAAU,OACTA,EAASb,EAAM,eAAiBa,EAASb,EAAM,aAChD,CACA,KAAK,KAAK,MAAM,IAAI,CAClB,YAAaa,EACb,aAAc,KACd,cAAe,KACf,YAAa,KACb,oBAAqB,EACvB,CAAC,EACD,MACF,CACA,KAAK,KAAK,MAAM,IAAI,CAAE,YAAaA,CAAO,CAAC,CAC7C,CAGA,aAAaE,EAAgC,CAC3C,IAAMf,EAAQ,KAAK,KAAK,MAAM,IAAI,EAC5BO,EAAUP,EAAM,aAChBI,EAASJ,EAAM,cACfQ,EAAOR,EAAM,YACnB,GAAI,CAACO,GAAWH,GAAU,MAAQI,GAAQ,KAAM,OAEhD,KAAK,KAAK,cAAc,SAAU,CAChC,UAAWQ,EAAWhB,EAAM,KAAMA,EAAM,eAAe,EAAE,SACzD,gBAAiBe,EAAO,KACxB,cAAeR,EAAQ,QAAQ,OAAQU,GAAMA,EAAE,OAASF,EAAO,IAAI,EAAE,IAAKE,GAAMA,EAAE,IAAI,CACxF,CAAC,EAED,IAAMC,EAASlB,EAAM,KAAK,MAAM,EAAGI,CAAM,EACnCe,EAAQnB,EAAM,KAAK,MAAMQ,CAAI,EAI7BY,EACJhB,IAAW,GAAKW,EAAO,KAAK,OAAS,EACjCA,EAAO,KAAK,CAAC,EAAE,YAAY,EAAIA,EAAO,KAAK,MAAM,CAAC,EAClDA,EAAO,KAIPT,EADqBa,EAAM,SAAW,GAAKA,EAAM,CAAC,IAAM,IACrB,GAAGC,CAAU,IAAMA,EACtDX,EAAUS,EAASZ,EAAca,EAMjCE,EAAWZ,EAAQ,OAEnBa,EAAgC,CACpC,GAAI,OAAO,WAAW,EACtB,YAAa,GACb,KAAMf,EAAQ,eACd,KAAMa,EACN,KAAML,EAAO,KACb,eAAgBR,EAAQ,eACxB,sBAAuBA,EAAQ,sBAC/B,QAASA,EAAQ,QACjB,SAAUQ,EAAO,SACjB,KAAMA,EAAO,KACb,SAAUA,EAAO,QACnB,EACMQ,EAASvB,EAAM,gBAAgB,UAAW,GAAM,EAAE,KAAOO,EAAQ,EAAE,EACnEiB,EAASxB,EAAM,gBAAgB,OAAQ,GAAM,EAAE,KAAOO,EAAQ,EAAE,EAChEkB,EAAWF,GAAU,EAAI,KAAK,IAAIA,EAAQC,EAAO,MAAM,EAAIA,EAAO,OACxEA,EAAO,OAAOC,EAAU,EAAGH,CAAQ,EAEnC,KAAK,KAAK,MAAM,IAAI,CAClB,KAAMb,EACN,gBAAiBe,EACjB,WAAYF,EAAS,GACrB,WAAYD,EACZ,aAAc,KACd,cAAe,KACf,YAAa,KACb,YAAaA,EACb,oBAAqB,GACrB,WAAY,GACZ,cAAe,GACf,qBAAsB,EACxB,CAAC,EACD,KAAK,KAAK,6BAA6B,EAEvC,KAAK,KAAK,kBAAkBA,CAAQ,EAUpC,KAAK,KAAK,SAAS,CACrB,CAQA,gBAAuB,CACrB,IAAMT,EAAI,KAAK,KAAK,MAAM,IAAI,EAC9B,GAAI,CAACA,EAAE,cAAgBA,EAAE,eAAiB,MAAQA,EAAE,aAAe,KAAM,OACzE,IAAMc,EAASC,GAAqB,CAClC,KAAM,OACN,KAAMf,EAAE,KACR,gBAAiBA,EAAE,gBACnB,aAAcA,EAAE,aAChB,cAAeA,EAAE,cACjB,YAAaA,EAAE,WACjB,CAAC,EACIc,IACL,KAAK,KAAK,MAAM,IAAIA,EAAO,KAAK,EAChC,KAAK,KAAK,kBAAkBA,EAAO,QAAQ,EAC7C,CACF,EC/NO,IAAME,GAAsB,GAEtBC,GAA4B,GAE5BC,GAA0B,IAE1BC,GAA0B,GAG1BC,EAA2B,+BAUlCC,GAAS,IAAI,QAEnB,SAASC,GAAWC,EAA8B,CAC5CA,EAAM,QAAU,OAClB,aAAaA,EAAM,KAAK,EACxBA,EAAM,MAAQ,KAElB,CAQA,SAASC,GAAeC,EAA6B,CACnD,OAAIA,EAAM,QAAQ,yBAAyB,EAAU,GAE9C,EADMA,EAAM,eAAe,aACb,aAAa,kCAAkC,EAAE,OACxE,CAEA,SAASC,GAAKD,EAAoBF,EAAwBI,EAAqB,CAC7EJ,EAAM,MAAQI,EACdF,EAAM,QAAQ,YAAcF,EAAM,OAAO,MAAM,EAAGI,CAAK,CACzD,CAEA,SAASC,GAASH,EAAoBF,EAA8B,CAMlE,GALAA,EAAM,MAAQ,KAEV,CAACE,EAAM,aACPF,EAAM,OAASA,EAAM,OAAO,SAChCG,GAAKD,EAAOF,EAAOA,EAAM,MAAQ,CAAC,EAC9BA,EAAM,OAASA,EAAM,OAAO,QAAQ,OACxC,IAAMM,EAAYN,EAAM,OAAOA,EAAM,MAAQ,CAAC,EACxCO,EAAQD,IAAc,KAAOA,IAAc,IAAM,GAA4B,EACnFN,EAAM,MAAQ,WAAW,IAAMK,GAASH,EAAOF,CAAK,EAAG,GAAsBO,CAAK,CACpF,CAEA,SAASC,GAAYN,EAAoBF,EAA8B,CACrEG,GAAKD,EAAOF,EAAO,CAAC,EACpBA,EAAM,MAAQ,WAAW,IAAMK,GAASH,EAAOF,CAAK,EAAG,EAAmB,CAC5E,CAeO,SAASS,GAAgBP,EAAoBQ,EAAoB,CACtE,IAAMV,EAAQF,GAAO,IAAII,CAAK,EAE9B,GAAIQ,IAAS,GAAI,CACXV,IACFD,GAAWC,CAAK,EAChBF,GAAO,OAAOI,CAAK,GAErBA,EAAM,gBAAgBL,CAAwB,EAC9C,OAAOK,EAAM,QAAQ,YACrB,MACF,CAEA,GAAIF,GAAO,SAAWU,EAAM,OAE5B,GAAIT,GAAeC,CAAK,EAAG,CACrBF,GAAOD,GAAWC,CAAK,EAC3BE,EAAM,gBAAgBL,CAAwB,EAC9CC,GAAO,IAAII,EAAO,CAAE,OAAQQ,EAAM,MAAOA,EAAK,OAAQ,MAAO,IAAK,CAAC,EACnER,EAAM,QAAQ,YAAcQ,EAC5B,MACF,CAEA,IAAMC,EAAwB,CAAE,OAAQD,EAAM,MAAO,EAAG,MAAO,IAAK,EAC9DE,EAAWZ,GAASA,EAAM,MAAQ,EAAIA,EAAQ,KAIpD,GAHIA,GAAOD,GAAWC,CAAK,EAC3BF,GAAO,IAAII,EAAOS,CAAI,EAElBC,EAAU,CAEZV,EAAM,aAAaL,EAA0B,EAAE,EAC/Cc,EAAK,MAAQ,WAAW,IAAM,CAC5BA,EAAK,MAAQ,KACbT,EAAM,gBAAgBL,CAAwB,EACzCK,EAAM,aACXM,GAAYN,EAAOS,CAAI,CACzB,EAAG,GAAiD,EACpD,MACF,CAEAT,EAAM,gBAAgBL,CAAwB,EAC9CW,GAAYN,EAAOS,CAAI,CACzB,CAGO,SAASE,GAA4BX,EAA0B,CACpE,IAAMF,EAAQF,GAAO,IAAII,CAAK,EACzBF,IACLD,GAAWC,CAAK,EAChBF,GAAO,OAAOI,CAAK,EACrB,CChJO,IAAMY,GAAkB,8BAWxB,SAASC,GAAoBC,EAAeF,GAAyB,CAC1E,GAAI,CACF,GAAI,OAAO,OAAW,KAAe,CAAC,OAAO,SAAU,OAAOE,EAC9D,IAAMC,EAAO,OAAO,SAAS,SAC7B,GAAI,CAACA,EAAM,OAAOD,EAClB,IAAME,EAAM,IAAI,IAAIF,CAAI,EACxB,OAAAE,EAAI,aAAa,IAAI,aAAcD,CAAI,EAChCC,EAAI,SAAS,CACtB,MAAQ,CACN,OAAOF,CACT,CACF,CCZO,SAASG,GACdC,EACAC,EAC+B,CAC/B,OAAID,EAA0B,CAAE,IAAK,QAAS,KAAM,YAAa,EAC7DC,EAAqB,CAAE,IAAK,MAAO,KAAM,WAAY,EAClD,CAAE,IAAK,SAAK,KAAM,SAAU,CACrC,CCIA,IAAMC,GAAkC,CACtC,YAAa,KACb,cAAe,KACf,WAAY,IACd,EAWO,SAASC,GAAiBC,EAKf,CAIhB,GAAI,CAACC,EAAiBD,EAAM,gBAAgB,EAAG,OAAOF,GAEtD,IAAMI,EAAaF,EAAM,kBACzB,GAAIE,EAAY,CACd,GAAM,CAAE,IAAAC,EAAK,OAAAC,CAAO,EAAIF,EACxB,MAAO,CACL,YAAaE,EAAS,KAAOD,EAC7B,cAAeA,GAAOC,EAASC,GAAaF,EAAKC,CAAM,EAAI,KAC3D,WAAYJ,EAAM,cACpB,CACF,CAEA,IAAMM,EAAgBC,EAAiBP,EAAM,YAAY,EACzD,MAAO,CAIL,YAAaM,EAAgB,KAAOE,GAAoBR,EAAM,cAAc,IAAI,EAChF,cAAAM,EACA,WAAYN,EAAM,cACpB,CACF,CAUO,SAASS,GAAiBC,EAIZ,CACnB,OAAIA,EAAK,aACAL,GAAaK,EAAK,aAAcA,EAAK,gBAAkBA,EAAK,YAAY,EAE1EA,EAAK,QACd,CAWO,SAASC,GACdR,EACAO,EACyC,CACzC,GAAIP,GAAO,KAAM,MAAO,CAAE,SAAU,GAAO,QAAS,EAAM,EAC1D,GAAM,CAAE,YAAAS,EAAa,MAAAC,CAAM,EAAIH,EACzBI,EACJX,IAAQS,GAAgBC,GAAS,OAASV,IAAQU,EAAM,OAASV,IAAQU,EAAM,KAC3EE,EAAUF,GAAS,MAAQV,EAAMU,EAAM,OAASV,EAAMU,EAAM,IAClE,MAAO,CAAE,SAAAC,EAAU,QAAAC,CAAQ,CAC7B,CC3GA,IAAMC,GAAW,eASV,SAASC,EACdC,EACAC,EACAC,EACe,CACf,IAAMC,EAAW,IAAI,IACrB,QAAWC,KAAS,MAAM,KAAKJ,EAAO,QAAQ,EAAG,CAC/C,IAAMK,EAAMD,EAAM,aAAaN,EAAQ,EACnCO,GAAO,MAAMF,EAAS,IAAIE,EAAKD,CAAoB,CACzD,CAEA,IAAME,EAAO,IAAI,IACXC,EAAwB,CAAC,EAC/B,QAASC,EAAI,EAAGA,EAAIP,EAAM,OAAQO,IAAK,CACrC,IAAMC,EAAOR,EAAMO,CAAC,EACdH,EAAMH,EAAK,MAAMO,EAAMD,CAAC,EAC9BF,EAAK,IAAID,CAAG,EACZ,IAAIK,EAAKP,EAAS,IAAIE,CAAG,EACpBK,IACHA,EAAKR,EAAK,OAAOO,EAAMD,CAAC,EACxBE,EAAG,aAAaZ,GAAUO,CAAG,GAE/BH,EAAK,SAASQ,EAAID,EAAMD,CAAC,EACrBR,EAAO,SAASQ,CAAC,IAAME,GACzBV,EAAO,aAAaU,EAAIV,EAAO,SAASQ,CAAC,GAAK,IAAI,EAEpDD,EAAO,KAAKG,CAAE,CAChB,CAEA,OAAW,CAACL,EAAKK,CAAE,IAAKP,EACjBG,EAAK,IAAID,CAAG,GAAGK,EAAG,OAAO,EAGhC,OAAOH,CACT,CChBO,SAASI,GACdC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAC,EACM,CACN,IAAIC,EAASP,EAAO,cAA2B,wBAAwB,EACvE,GAAI,CAACI,GAAQH,EAAQ,SAAW,EAAG,CACjCM,GAAQ,OAAO,EACf,MACF,CACKA,IACHA,EAASC,GAAY,EACrBR,EAAO,YAAYO,CAAM,GAG3B,IAAME,EAAQF,EAAO,cAA2B,8BAA8B,EAC1EE,IAAOA,EAAM,YAAcC,GAAWN,CAAI,GAI9C,IAAMO,EAAOJ,EAAO,cAAiC,sBAAsB,EACrEK,EAAOL,EAAO,cAAiC,sBAAsB,EACvEI,IAAMA,EAAK,QAAU,IAAML,EAAU,gBAAgB,GACrDM,IAAMA,EAAK,QAAU,IAAMN,EAAU,YAAY,GAErD,IAAMO,EAAON,EAAO,cAA2B,6BAA6B,EACxEM,GAAMC,GAAYD,EAAMZ,EAASC,EAAaC,EAAWE,EAAWC,CAAS,CACnF,CAEA,SAASQ,GACPD,EACAZ,EACAC,EACAC,EACAE,EACAC,EACM,CACN,IAAMS,EAAQC,EAAQ,IAAI,IAAM,EAG1BC,EAAQC,GAAiB,CAC7B,aAAcb,EAAU,WACxB,eAAgBc,EAAQlB,EAAQC,CAAW,CAAC,EAC5C,SAAUG,EAAU,aACtB,CAAC,EAEDe,EAAcP,EAAMZ,EAAS,CAI3B,MAAO,CAACoB,EAAQC,IAAMH,EAAQE,CAAM,GAAK,OAAOC,CAAC,GACjD,OAASD,GAAWE,GAAUF,CAAM,EACpC,OAAQ,CAACG,EAAIH,EAAQC,IAAM,CACzB,IAAMG,EAAMN,EAAQE,CAAM,EACpBK,EAAQC,GAAcF,EAAK,CAAE,YAAapB,EAAU,YAAa,MAAAY,CAAM,CAAC,EACxEW,EAAgBN,IAAMpB,GAAemB,EAAO,YAClDG,EAAG,GAAK,GAAGrB,CAAS,WAAWmB,CAAC,GAChCE,EAAG,QAAQ,SAAW,OAAOF,CAAC,EAC9BE,EAAG,aAAa,gBAAiB,OAAOI,CAAa,CAAC,EAGtDJ,EAAG,UAAU,OAAO,6BAA8BC,GAAO,MAAQA,EAAMV,CAAK,EAC5ES,EAAG,UAAU,OAAO,oCAAqCI,CAAa,EACtEJ,EAAG,UAAU,OAAO,8BAA+BC,GAAO,MAAQA,IAAQV,CAAK,EAC/ES,EAAG,UAAU,OAAO,iCAAkCE,EAAM,QAAQ,EAGpEF,EAAG,UAAU,OAAO,iCAAkCE,EAAM,OAAO,EAC/DL,EAAO,aACTG,EAAG,QAAU,IAAM,CACjBA,EAAG,UAAU,IAAI,+BAA+B,EAChDlB,EAAU,SAASe,CAAM,EACzB,WAAW,IAAMG,EAAG,UAAU,OAAO,+BAA+B,EAAG,GAAG,CAC5E,EACAA,EAAG,aAAe,IAAM,CACtB,IAAMK,EAAM,OAAO,SAASL,EAAG,QAAQ,UAAY,KAAM,EAAE,EACvDK,GAAO,GAAGvB,EAAU,YAAYuB,CAAG,CACzC,IAEAL,EAAG,QAAU,KACbA,EAAG,aAAe,KAEtB,CACF,CAAC,CACH,CAEA,SAASD,GAAUF,EAAuC,CACxD,IAAMS,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,aAAa,OAAQ,QAAQ,EAClCA,EAAK,aAAa,kBAAmB,EAAE,EACvCA,EAAK,aAAa,qBAAsB,EAAE,EAE1C,IAAMC,EAAMC,GAAQX,CAAM,EAC1B,GAAIU,GAAO,KAIT,OAAAD,EAAK,UAAY,mDACjBA,EAAK,aAAa,cAAe,MAAM,EACvCA,EAAK,SAAW,GACTA,EAGTA,EAAK,UAAY,iDACjBA,EAAK,SAAW,EAIhBA,EAAK,aAAa,aAAcT,EAAO,IAAI,EAC3C,IAAMY,EAAM,SAAS,cAAc,MAAM,EACzC,OAAAA,EAAI,UAAY,8BAChBA,EAAI,YAAc,OAAOF,CAAG,EAC5BD,EAAK,YAAYG,CAAG,EACbH,CACT,CAEA,SAAStB,IAA2B,CAClC,IAAMD,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,wBACnBA,EAAO,aAAa,sBAAuB,EAAE,EAE7C,IAAM2B,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,+BAEnB,IAAMC,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,8BAClBA,EAAM,aAAa,sBAAuB,EAAE,EAC5CA,EAAM,aAAa,YAAa,QAAQ,EAExCD,EAAO,OACLE,GAAe,OAAQ,iBAAkB,QAAG,EAC5CD,EACAC,GAAe,OAAQ,aAAc,QAAG,CAC1C,EAEA,IAAMC,EAAW,SAAS,cAAc,KAAK,EAC7CA,EAAS,UAAY,iCAIrBA,EAAS,aAAa,cAAe,MAAM,EAC3C,QAAW5B,KAAS6B,GAAgB,CAClC,IAAMR,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,UAAY,gCACjBA,EAAK,YAAcrB,EACnB4B,EAAS,YAAYP,CAAI,CAC3B,CAEA,IAAMjB,EAAO,SAAS,cAAc,KAAK,EACzC,OAAAA,EAAK,UAAY,6BACjBA,EAAK,aAAa,qBAAsB,EAAE,EAE1CN,EAAO,OAAO2B,EAAQG,EAAUxB,CAAI,EAC7BN,CACT,CAEA,SAAS6B,GACPG,EACA9B,EACA+B,EACmB,CACnB,IAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3C,OAAAA,EAAI,KAAO,SAGXA,EAAI,SAAW,GACfA,EAAI,UAAY,4BAChBA,EAAI,aAAa,iBAAiBF,CAAS,GAAI,EAAE,EACjDE,EAAI,aAAa,aAAchC,CAAK,EACpCgC,EAAI,YAAcD,EAElBC,EAAI,iBAAiB,YAAcC,GAAMA,EAAE,eAAe,CAAC,EACpDD,CACT,CCzMA,IAAME,GAA2B,CAAC,IAAK,EAAE,EAKzC,SAASC,GAAeC,EAAeC,EAA2B,CAChE,OAAIA,EAAiB,EACjBD,IAAU,EAAU,GACpBA,IAAU,EAAU,GACjB,EACT,CAEO,SAASE,GACdC,EACAC,EACAC,EACAC,EACAC,EAAU,GACVC,EAAU,GAOVC,EAAiB,GACjB,CACA,IAAIC,EAAOP,EAAU,cAA2B,uBAAuB,EAQvE,GAPKO,IACHA,EAAO,SAAS,cAAc,MAAM,EACpCA,EAAK,UAAY,uBACjBP,EAAU,YAAYO,CAAI,GAIxBF,GAAWJ,EAAM,SAAW,EAAG,CACjCM,EAAK,aAAa,6BAA8B,EAAE,EAClDA,EAAK,UAAY,GACjB,QAASC,EAAI,EAAGA,EAAIb,GAAyB,OAAQa,IAAK,CACxD,IAAMC,EAAQd,GAAyBa,CAAC,EAClCE,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,aAAa,yBAA0B,EAAE,EAC9CA,EAAK,UAAY,4CAA4CN,EAAU,4BAA8B,EAAE,GACvGM,EAAK,MAAM,MAAQ,GAAGD,CAAK,KAC3BC,EAAK,MAAM,QAAU,OAAOd,GAAeY,EAAG,EAAK,CAAC,EACpDD,EAAK,YAAYG,CAAI,CACvB,CACA,MACF,CAEIL,EACFE,EAAK,aAAa,6BAA8B,EAAE,EAElDA,EAAK,gBAAgB,4BAA4B,EAInD,QAAWI,KAAQJ,EAAK,iBAA8B,0BAA0B,EAC9EI,EAAK,OAAO,EAGdC,EAAcL,EAAMN,EAAO,CACzB,MAAQY,GAAS,GAAGA,EAAK,IAAI,IAAIA,EAAK,IAAI,GAC1C,OAASA,GAAS,CAChB,IAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3C,OAAAA,EAAI,KAAO,SACXA,EAAI,SAAW,GACfA,EAAI,aAAa,gBAAiB,EAAE,EACpCA,EAAI,aAAa,kBAAmB,OAAO,EAC3CA,EAAI,YAAcD,EAAK,KACvBC,EAAI,iBAAiB,YAAcC,GAAMA,EAAE,eAAe,CAAC,EACpDD,CACT,EACA,OAAQ,CAACE,EAAIC,EAAOT,IAAM,CACxB,IAAMM,EAAME,EAENlB,EAAWQ,GAAkBE,IAAMN,GAAmB,CAACG,EACvDa,EAAU,CAAC,iBAAiB,EAC9Bd,GAASc,EAAQ,KAAK,0BAA0B,EAChDb,GAASa,EAAQ,KAAK,2BAA2B,EACrDJ,EAAI,UAAYI,EAAQ,KAAK,GAAG,EAChCJ,EAAI,MAAM,MAAQ,GAClBA,EAAI,MAAM,QAAU,OAAOlB,GAAeY,EAAGV,CAAQ,CAAC,EAClDO,GACFS,EAAI,aAAa,mBAAoB,EAAE,EACvCA,EAAI,SAAW,GACfA,EAAI,QAAU,OAEdA,EAAI,gBAAgB,kBAAkB,EACtCA,EAAI,SAAW,GACfA,EAAI,QAAU,IAAMX,EAAaK,CAAC,EAEtC,CACF,CAAC,CACH,CAEO,SAASW,GAAWnB,EAAwB,CACjDA,EAAU,cAAc,uBAAuB,GAAG,OAAO,CAC3D,CClGA,IAAMoB,GAAgB,WAwBf,SAASC,GACdC,EACAC,EACAC,EACAC,EACAC,EACM,CACN,IAAIC,EAAUL,EAAO,cAA2B,sBAAsB,EAEtE,GAAIC,EAAS,SAAW,EAAG,CACzBI,GAAS,OAAO,EAChB,MACF,CAEA,GAAI,CAACA,EAAS,CACZA,EAAU,SAAS,cAAc,SAAS,EAC1CA,EAAQ,UAAY,sBACpBA,EAAQ,aAAa,oBAAqB,EAAE,EAG5CA,EAAQ,aAAa,OAAQ,OAAO,EACpCA,EAAQ,aAAa,kBAAmB,GAAGH,CAAS,iBAAiB,EAErE,IAAMI,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAY,4BAClBA,EAAM,GAAK,GAAGJ,CAAS,kBACvBI,EAAM,YAAcR,GAEpB,IAAMS,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAY,0BAChBA,EAAI,aAAa,wBAAyB,EAAE,EAE5CF,EAAQ,OAAOC,EAAOC,CAAG,EACzBP,EAAO,YAAYK,CAAO,CAC5B,CAEA,IAAME,EAAMF,EAAQ,cAA2B,0BAA0B,EACpEE,GAELC,EAAcD,EAAKN,EAAU,CAK3B,MAAQQ,GAAYC,GAAQD,CAAO,EACnC,OAASA,GAAYE,GAAUF,EAASN,EAAUC,CAAa,EAC/D,OAAQ,CAACQ,EAAIC,EAAUC,IAAM,CAC3BF,EAAG,GAAK,GAAGV,CAAS,YAAYY,CAAC,GACjCF,EAAG,QAAQ,SAAW,OAAOE,CAAC,CAChC,CACF,CAAC,CACH,CAQO,SAASC,GAAyBC,EAAmBC,EAA0B,CACpF,IAAMC,EAAQF,EAAK,iBAA8B,oBAAoB,EACrE,QAAWG,KAAQD,EAAOC,EAAK,SAAWF,EAAY,EAAI,EAC5D,CAEA,SAASP,GAAQD,EAA0B,CACzC,MAAO,CAACA,EAAQ,GAAIA,EAAQ,MAAOA,EAAQ,IAAKA,EAAQ,SAAUA,EAAQ,MAAOA,EAAQ,MAAM,EAC5F,IAAKW,GAAUA,GAAS,EAAE,EAC1B,KAAK,IAAI,CACd,CAEA,SAAST,GACPF,EACAN,EACAC,EACa,CAMb,IAAMe,EAAO,SAAS,cAAc,GAAG,EACvCA,EAAK,UAAY,qBACjBA,EAAK,aAAa,mBAAoB,EAAE,EACxCA,EAAK,aAAa,OAAQ,QAAQ,EAClCA,EAAK,aAAa,gBAAiB,OAAO,EAGtCV,EAAQ,MAAKU,EAAK,KAAOV,EAAQ,KACrCU,EAAK,SAAW,EAEhB,IAAME,EAAQ,SAAS,cAAc,MAAM,EAE3C,GADAA,EAAM,UAAY,2BACdZ,EAAQ,SAAU,CACpB,IAAMa,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAY,2BAChBA,EAAI,IAAMb,EAAQ,SAGlBa,EAAI,IAAM,GACVA,EAAI,QAAU,OACdA,EAAI,SAAW,QACfD,EAAM,YAAYC,CAAG,CACvB,MAGED,EAAM,aAAa,+BAAgC,EAAE,EAEvDF,EAAK,YAAYE,CAAK,EAEtB,IAAME,EAAO,SAAS,cAAc,MAAM,EAK1C,GAJAA,EAAK,UAAY,0BAIbd,EAAQ,OAAQ,CAClB,IAAMe,EAAS,SAAS,cAAc,MAAM,EAC5CA,EAAO,UAAY,4BACnBA,EAAO,YAAcf,EAAQ,OAC7Bc,EAAK,YAAYC,CAAM,CACzB,CAEA,IAAMC,EAAQ,SAAS,cAAc,MAAM,EAK3C,GAJAA,EAAM,UAAY,2BAClBA,EAAM,YAAchB,EAAQ,MAC5Bc,EAAK,YAAYE,CAAK,EAElBhB,EAAQ,MAAO,CACjB,IAAMiB,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,2BAClBA,EAAM,YAAcjB,EAAQ,MAC5Bc,EAAK,YAAYG,CAAK,CACxB,CAEA,OAAAP,EAAK,YAAYI,CAAI,EAErBJ,EAAK,iBAAiB,QAAUQ,GAAM,CAIhCA,EAAE,SAAWA,EAAE,SAAWA,EAAE,UAAYA,EAAE,QAAUA,EAAE,SAAW,IACrEA,EAAE,eAAe,EACjBxB,EAASM,CAAO,EAClB,CAAC,EAEDU,EAAK,iBAAiB,UAAYQ,GAAM,CAClCA,EAAE,MAAQ,SAAWA,EAAE,MAAQ,MACnCA,EAAE,eAAe,EACjBxB,EAASM,CAAO,EAClB,CAAC,EAKDU,EAAK,iBAAiB,QAAS,IAAMf,EAAc,EAAI,CAAC,EACxDe,EAAK,iBAAiB,OAASQ,GAAM,CACtBA,EAAE,eACL,QAAQ,qBAAqB,GACvCvB,EAAc,EAAK,CACrB,CAAC,EAEMe,CACT,CC3KO,IAAMS,GAA0B,GAE1BC,GAAuB,IAEvBC,GAAuB,IAEvBC,GAAuB,GAGvBC,GAAyB,2BAY/B,SAASC,GACdC,EACAC,EACAC,EACAC,EAAqC,QAC7B,CACR,IAAMC,EAAW,KAAK,IAAI,EAAG,KAAK,MAAMH,CAAI,CAAC,EACvCI,EAAM,KAAK,MAAM,KAAK,IAAI,EAAGL,CAAK,EAAII,CAAQ,EACpD,GAAID,IAAoB,QAAS,OAAOE,EAAM,GAC9C,IAAMC,EAAO,KAAK,IAAI,EAAG,KAAK,KAAK,KAAK,IAAI,EAAGJ,CAAK,EAAIE,CAAQ,CAAC,EACjE,OAAO,KAAK,IAAI,EAAGE,EAAO,EAAID,CAAG,EAAI,EACvC,CAOO,SAASE,GAA0BL,EAAeD,EAAsB,CAC7E,IAAMG,EAAW,KAAK,IAAI,EAAG,KAAK,MAAMH,CAAI,CAAC,EAE7C,OADa,KAAK,IAAI,EAAG,KAAK,KAAK,KAAK,IAAI,EAAGC,CAAK,EAAIE,CAAQ,CAAC,EAClD,GAAK,GAA0B,GAChD,CC5DA,IAAMI,GAAS,6BAOTC,GAAmB,IAAI,IAAI,CAC/B,MACA,IACA,OACA,SACA,UACA,OACA,WACA,UACA,MACF,CAAC,EAWD,SAASC,GAAsBC,EAAcC,EAAwB,CACnE,IAAMC,EAAQF,EAAK,YAAY,EAC/B,OACEE,EAAM,WAAW,IAAI,GACrBA,EAAM,SAAS,MAAM,GACrBA,IAAU,SACVA,IAAU,YACVA,IAAU,MACVA,IAAU,QACV,mBAAmB,KAAKD,CAAK,GAC7B,gCAAgC,KAAKA,CAAK,CAE9C,CAWA,IAAME,GAAsB,CAC1B,OACA,SACA,eACA,iBACA,kBACA,YACA,YACA,UACA,eACA,gBACF,EACMC,GAAc,0BAEpB,SAASC,GAAYC,EAAmB,CACtC,IAAMC,EAAyB,CAAC,EAChC,QAAWP,KAAQG,GAAqB,CACtC,IAAMF,EAAQK,EAAG,aAAaN,CAAI,EAC9BC,IAAU,MAAQ,CAACG,GAAY,KAAKH,CAAK,GAAK,YAAY,KAAKA,CAAK,GACxEM,EAAa,KAAK,GAAGP,CAAI,KAAKC,EAAM,KAAK,CAAC,EAAE,CAC9C,CACIM,EAAa,OAAS,GAAGD,EAAG,aAAa,QAASC,EAAa,KAAK,IAAI,CAAC,CAC/E,CAEA,SAASC,GAAMF,EAAmB,CAChC,QAAWG,KAAQ,MAAM,KAAKH,EAAG,UAAU,EACrCP,GAAsBU,EAAK,KAAMA,EAAK,KAAK,GAAGH,EAAG,gBAAgBG,EAAK,IAAI,EAEhFJ,GAAYC,CAAE,EACd,QAAWI,KAAS,MAAM,KAAKJ,EAAG,UAAU,EAC1C,GAAII,EAAM,WAAa,KAAK,aAAc,CACxC,IAAMC,EAAUD,EACZ,CAACZ,GAAiB,IAAIa,EAAQ,SAAS,GAAKA,EAAQ,eAAiBd,GACvEc,EAAQ,OAAO,EAEfH,GAAMG,CAAO,CAEjB,MAIED,EAAM,OAAO,CAGnB,CAeO,SAASE,GAAsBC,EAA+B,CACnE,GAAI,OAAO,UAAc,KAAe,OAAO,cAAkB,IAAa,OAAO,KACrF,IAAMC,EAAM,IAAI,UAAU,EAAE,gBAAgBD,EAAQ,eAAe,EAC7DE,EAAOD,EAAI,gBAEjB,MADI,CAACC,GAAQA,EAAK,YAAc,OAASA,EAAK,eAAiBlB,IAC3DiB,EAAI,qBAAqB,aAAa,EAAE,OAAS,EAAU,MAC/DN,GAAMO,CAAI,EACVA,EAAK,aAAa,cAAe,MAAM,EACvCA,EAAK,aAAa,YAAa,OAAO,EAC/B,IAAI,cAAc,EAAE,kBAAkBA,CAAI,EACnD,CASO,SAASC,GAAYC,EAAoE,CAC9F,OAAOA,EAAO,IAChB,CC/GA,SAASC,GAAgBC,EAAmBC,EAAeC,EAA0B,CACnF,IAAMC,EAAWC,GAA4B,EACvCC,EACJ,CAACH,GAAWI,GAA4BL,EAAOE,CAAQ,EAAII,GAAmBP,CAAI,EAAI,KAClFQ,EAAOC,GACXR,EACAE,EACAE,GAAU,WAAa,KACvBA,GAAU,WAAa,IACzB,EACA,OAAAL,EAAK,MAAM,oBAAsBQ,EAAK,SACtCR,EAAK,MAAM,YAAY,wBAAyBQ,EAAK,SAAS,EAC9DR,EAAK,iBAA8B,mBAAmB,EAAE,QAAQ,CAACU,EAAIC,IAAM,CACzED,EAAG,UAAU,OACX,iCACA,CAACR,GAAWM,EAAK,kBAAkB,SAASG,CAAC,CAC/C,CACF,CAAC,EAIMH,EAAK,IACd,CAcO,SAASI,GACdC,EACAC,EACAC,EACAC,EACAC,EACAC,EACAhB,EACAiB,EAAW,GACXC,EAAqC,QACrCC,EAAY,GACN,CACN,IAAIrB,EAAOa,EAAO,cAA2B,WAAW,EACxD,GAAIC,EAAQ,SAAW,EAAG,CACxBd,GAAM,OAAO,EACb,MACF,CACKA,IACHA,EAAO,SAAS,cAAc,KAAK,EACnCA,EAAK,UAAY,2BACjBA,EAAK,aAAa,cAAe,EAAE,EACnCA,EAAK,MAAM,YAAY,iBAAkB,OAAO,EAGhDA,EAAK,MAAM,YAAY,iBAAkB,KAAK,EAC9CA,EAAK,MAAM,YAAY,iBAAkB,GAAG,EAC5Ca,EAAO,YAAYb,CAAI,GAKzB,IAAMsB,EAAatB,EAAK,QAAQ,WAAamB,EAC7CnB,EAAK,QAAQ,SAAWmB,EACxBI,GAAcvB,EAAMc,EAASC,EAAaC,EAAUC,EAAaC,EAAWhB,EAASmB,CAAS,EAG9F,IAAMG,EAAOzB,GAAgBC,EAAMc,EAAQ,OAAQZ,CAAO,EAC1DuB,GAAoBzB,EAAMc,EAAQ,OAAQU,EAAMtB,EAASkB,CAAe,EAKpEE,IAAYtB,EAAK,UAAY,EACnC,CAEA,SAASuB,GACPvB,EACAc,EACAC,EACAC,EACAC,EACAC,EACAhB,EACAmB,EACA,CASA,IAAMK,EAAcxB,EAAU,IAAM,IAC9BiB,EAAWnB,EAAK,QAAQ,UAAY,GAEpC2B,EAAUN,EAAaO,GAA0BA,EAAI,MAAQA,EAAI,UAAY,GAAK,IAAM,GAE9FC,EAAc7B,EAAMc,EAAS,CAC3B,MAAQc,GAAQ,GAAGA,EAAI,IAAI,KAAKD,EAAQC,CAAG,CAAC,KAAKF,CAAW,KAAKP,CAAQ,GACzE,OAAQ,CAACW,EAAQC,IAAO,CACtB,IAAMrB,EAAKsB,GAAmBF,EAAQ5B,EAASmB,CAAS,EAMxD,OAAKnB,IAASQ,EAAG,QAAQ,YAAc,IAChCA,CACT,EACA,OAAQ,CAACA,EAAIoB,EAAQnB,IAAM,CACzB,IAAMsB,EAAgBtB,IAAMI,GAAe,CAACb,EAC5CQ,EAAG,GAAK,GAAGQ,CAAS,WAAWP,CAAC,GAChCD,EAAG,QAAQ,SAAW,OAAOC,CAAC,EAC9BD,EAAG,aAAa,gBAAiB,OAAOuB,CAAa,CAAC,EACtDvB,EAAG,UAAU,OAAO,iCAAkCuB,CAAa,EAE/D,CAAC/B,GAAW4B,EAAO,aACrBpB,EAAG,QAAU,IAAM,CACjBA,EAAG,UAAU,IAAI,4BAA4B,EAC7CM,EAASc,CAAM,EACf,WAAW,IAAMpB,EAAG,UAAU,OAAO,4BAA4B,EAAG,GAAG,CACzE,EACAA,EAAG,aAAe,IAAM,CACtB,IAAMwB,EAAM,OAAO,SAASxB,EAAG,QAAQ,UAAY,KAAM,EAAE,EACvDwB,GAAO,GAAGjB,EAAYiB,CAAG,CAC/B,IAEAxB,EAAG,QAAU,KACbA,EAAG,aAAe,KAEtB,CACF,CAAC,CACH,CAQA,SAASe,GACPzB,EACAC,EACAuB,EACAtB,EACAkB,EACM,CACN,QAAWV,KAAMV,EAAK,iBAA8B,qBAAqB,EAAG,CAC1E,GAAI,CAACE,EAAS,CACZ,IAAMS,EAAI,OAAO,SAASD,EAAG,QAAQ,UAAY,KAAM,EAAE,EACrDC,GAAK,GACPD,EAAG,MAAM,YACPyB,GACA,GAAGC,GAAmBzB,EAAGa,EAAMvB,EAAOmB,CAAe,CAAC,IACxD,CAEJ,CACA,OAAOV,EAAG,QAAQ,WACpB,CACF,CAEA,SAASsB,GACPF,EACA5B,EACAmB,EACa,CACb,IAAMgB,EAAO,SAAS,cAAc,KAAK,EACzCA,EAAK,aAAa,OAAQ,QAAQ,EAClCA,EAAK,aAAa,kBAAmB,EAAE,EACnCnC,GAASmC,EAAK,aAAa,mBAAoB,EAAE,EACrDA,EAAK,SAAWnC,GAAW,CAAC4B,EAAO,YAAc,GAAK,EAEtD,IAAMQ,EAAU,CAAC,mBAAmB,EAChCR,EAAO,YACTQ,EAAQ,KAAK,6BAA6B,EAE1CA,EAAQ,KAAK,iCAAiC,EAEhDD,EAAK,UAAYC,EAAQ,KAAK,GAAG,EAEjC,IAAMC,EAAU,SAAS,cAAc,MAAM,EAC7CA,EAAQ,UAAY,4BAKpB,IAAMC,EAAWnB,GAAaS,EAAO,SAAWW,GAAsBX,EAAO,QAAQ,EAAI,KACzF,GAAIU,EAAU,CACZ,IAAME,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,UAAY,yBACjBA,EAAK,aAAa,cAAe,MAAM,EACvCA,EAAK,UAAYF,EACjBD,EAAQ,YAAYG,CAAI,EACpBZ,EAAO,OAAMO,EAAK,QAAQ,QAAUP,EAAO,KACjD,CAKA,IAAMa,EAAO,SAAS,cAAc,MAAM,EAK1C,GAJAA,EAAK,UAAY,yBACjBA,EAAK,YAAcC,GAAYd,CAAM,EACrCS,EAAQ,YAAYI,CAAI,EAEpBb,EAAO,IAAK,CACd,IAAMe,EAAM,SAAS,cAAc,MAAM,EACzCA,EAAI,UAAY,wBAChBA,EAAI,YAAcf,EAAO,IACzBS,EAAQ,YAAYM,CAAG,CACzB,CAEA,OAAAR,EAAK,YAAYE,CAAO,EAEjBF,CACT,CCvMO,IAAMS,GAAqB,0BAErBC,GAAoB,wBAKpBC,GAA4B,mBAK5BC,GAAuB,IAMvBC,GAA8B,IAE9BC,GAA0B,4BAC1BC,GAAqB,+BAG5BC,GAAqB,EAErBC,GAAe,EAEfC,GAAiB,IAUvB,SAASC,GACPC,EACAC,EACAC,EACAC,EACM,CACN,IAAMC,EAAOJ,EAAK,UACZK,EAAMH,GAAM,sBACZI,EAASN,EAAK,eAAe,kBAAoB,SACvD,GAAI,CAACK,GAAOC,GAAUC,GAAeP,CAAI,EAAG,CAC1CA,EAAK,UAAYI,EAAOH,EACxBE,EAAO,EACP,MACF,CACA,IAAMK,EAAQN,EAAK,aAAa,MAAM,GAAK,KAAK,IAAI,EAC9CO,EAAM,IAAMP,EAAK,aAAa,MAAM,GAAK,KAAK,IAAI,EAClDQ,EAAO,IAAM,CACjB,IAAMC,EAAI,KAAK,IAAI,GAAIF,EAAI,EAAID,GAASV,EAAc,EAChDc,EAAQ,GAAK,EAAID,IAAM,EAAIA,GACjCX,EAAK,UAAYI,EAAOH,EAAQW,EAC5BD,EAAI,EAAGN,EAAI,KAAKH,EAAMQ,CAAI,EACzBP,EAAO,CACd,EACAE,EAAI,KAAKH,EAAMQ,CAAI,CACrB,CAGO,SAASG,GAAqBC,EAAgB,SAAyB,CAC5E,IAAMC,EAAK,6BACLC,EAAMF,EAAI,gBAAgBC,EAAI,KAAK,EACzCC,EAAI,aAAa,UAAW,WAAW,EACvCA,EAAI,aAAa,QAAS,IAAI,EAC9BA,EAAI,aAAa,SAAU,IAAI,EAC/BA,EAAI,aAAa,OAAQ,MAAM,EAC/BA,EAAI,aAAa,SAAU,cAAc,EACzCA,EAAI,aAAa,eAAgB,MAAM,EACvCA,EAAI,aAAa,iBAAkB,OAAO,EAC1CA,EAAI,aAAa,kBAAmB,OAAO,EAC3CA,EAAI,aAAa,cAAe,MAAM,EACtCA,EAAI,aAAa,YAAa,OAAO,EACrC,IAAMC,EAAOH,EAAI,gBAAgBC,EAAI,MAAM,EAC3C,OAAAE,EAAK,aAAa,IAAK,mBAAmB,EAC1CD,EAAI,YAAYC,CAAI,EACbD,CACT,CAGO,SAASE,GAAwBJ,EAAgB,SAA6B,CACnF,IAAMK,EAASL,EAAI,cAAc,QAAQ,EACzC,OAAAK,EAAO,KAAO,SACdA,EAAO,SAAW,GAClBA,EAAO,UAAY9B,GACnB8B,EAAO,aAAa7B,GAAmB,EAAE,EACzC6B,EAAO,aAAa,aAAcxB,EAAkB,EACpDwB,EAAO,aAAa,cAAe,MAAM,EACzCA,EAAO,YAAYN,GAAqBC,CAAG,CAAC,EACrCK,CACT,CAiBA,SAASC,GAAYpB,EAA2B,CAC9C,IAAMqB,EAASrB,EAAK,MAAM,oBAAoB,KAAK,EACnD,OAAKqB,EACE,KAAK,IAAI,EAAGA,EAAO,MAAM,KAAK,EAAE,MAAM,EADzB,CAEtB,CAEA,SAASd,GAAee,EAA0B,CAChD,OAAIA,EAAG,QAAQ,yBAAyB,EAAU,GAE3C,EADMA,EAAG,eAAe,aACV,aAAa,kCAAkC,EAAE,OACxE,CAEO,SAASC,GAAkB,CAChC,SAAAC,EACA,KAAAxB,EACA,OAAAmB,CACF,EAA2C,CACzC,IAAMjB,EAAOF,EAAK,eAAe,cAAgB,OAAO,OAAW,IAAc,OAAS,MACtFyB,EAAY,GACZC,EAA2B,KAC3BC,EAAoD,KAEpDC,EAAkD,KAEhDC,EAAcC,GAAqB,CACnCA,GACFX,EAAO,aAAa5B,GAA2B,EAAE,EACjD4B,EAAO,aAAa,cAAe,OAAO,IAE1CA,EAAO,gBAAgB5B,EAAyB,EAChD4B,EAAO,aAAa,cAAe,MAAM,EAE7C,EAcMY,EAAW,IAAM,CAErB,IAAIC,EAAU,EACVV,EAAyBtB,EAC7B,KAAOsB,GAAMA,IAAOE,GAClBQ,GAAWV,EAAG,UACdA,EAAKA,EAAG,wBAAwB,YAAcA,EAAG,aAAe,KAOlE,IAAMW,EAAO/B,GAAO,OAAO,WAAWA,EAAK,iBAAiBF,CAAI,EAAE,aAAa,GAAK,EAC9EkC,EACJV,EAAS,cAAgBQ,EAAUhC,EAAK,eAAiBiC,EAAO,EAAI,EAAIpC,IAC1EsB,EAAO,MAAM,YAAYzB,GAAyB,GAAG,KAAK,IAAI,EAAG,KAAK,MAAMwC,CAAM,CAAC,CAAC,IAAI,CAC1F,EAOMC,EAAW,IAAM,CACrB,GAAIV,GAAaE,IAAgB,MAAQC,IAAc,KAAM,OAE7D,IAAMQ,EAAUpC,EAAK,cAAc,qCAAqC,IAAM,KACxEqC,EAAYrC,EAAK,aAAeA,EAAK,aAAeA,EAAK,UAC/D6B,EAAW,CAACO,GAAWC,EAAYzC,EAAkB,CACvD,EAEM0C,EAAS,IAAM,CACnB,GAAIb,EAAW,OACfM,EAAS,EACT,IAAMQ,EAAQvC,EAAK,aAAa,gBAAgB,GAAK,GACrD,GAAIuC,IAAUb,EAAW,CACvBA,EAAYa,EAEZV,EAAW,EAAK,EACZF,IAAgB,MAAM,aAAaA,CAAW,EAClD,IAAMa,EAAQxC,EAAK,iBAAiB,mBAAmB,EAAE,OACnDyC,EAAOlC,GAAeP,CAAI,EAAI,EAAI0C,GAA0BF,EAAOpB,GAAYpB,CAAI,CAAC,EAC1F2B,EAAc,WAAW,IAAM,CAC7BA,EAAc,KACdQ,EAAS,CACX,EAAGM,CAAI,EACP,MACF,CACAN,EAAS,CACX,EAQMQ,EAAW,IAAM,CACjBlB,GAAaE,IAAgB,OACjCE,EAAW,EAAK,EACZD,IAAc,MAAM,aAAaA,CAAS,EAC9CA,EAAY,WAAW,IAAM,CAC3BA,EAAY,KACZO,EAAS,CACX,EAAG1C,EAA2B,EAChC,EACAO,EAAK,iBAAiB,SAAU2C,EAAU,CAAE,QAAS,EAAK,CAAC,EAE3D,IAAMC,EAAeC,GAAaA,EAAE,eAAe,EAC7CC,EAAU,IAAM,CACpB,IAAMC,EAAM7C,GACP,OAAO,WAAWA,EAAK,iBAAiBF,CAAI,EAAE,UAAU,GAAK,IAC7D,OAAO,WAAWE,EAAK,iBAAiBF,CAAI,EAAE,aAAa,GAAK,GACjE,EACEgD,EAAO,KAAK,IAAI,EAAGhD,EAAK,aAAe+C,CAAG,EAShDhD,GAAgBC,EAAMgD,EAAM9C,EAAMyC,CAAQ,CAC5C,EACAxB,EAAO,iBAAiB,YAAayB,CAAW,EAChDzB,EAAO,iBAAiB,QAAS2B,CAAO,EAExC,IAAIG,EAAwC,KAC5C,OAAI/C,GAAQ,OAAOA,EAAK,gBAAmB,aACzC+C,EAAiB,IAAI/C,EAAK,eAAe,IAAM,CAC7C6B,EAAS,EACTI,EAAS,CACX,CAAC,EACDc,EAAe,QAAQjD,CAAI,GAG7BsC,EAAO,EAEA,CACL,OAAAA,EACA,SAAU,CACJb,IACJA,EAAY,GACRE,IAAgB,MAAM,aAAaA,CAAW,EAClDA,EAAc,KACVC,IAAc,MAAM,aAAaA,CAAS,EAC9CA,EAAY,KACZ5B,EAAK,oBAAoB,SAAU2C,CAAQ,EAC3CxB,EAAO,oBAAoB,YAAayB,CAAW,EACnDzB,EAAO,oBAAoB,QAAS2B,CAAO,EAC3CG,GAAgB,WAAW,EAC3B9B,EAAO,gBAAgB5B,EAAyB,EAChD4B,EAAO,aAAa,cAAe,MAAM,EAC3C,CACF,CACF,CC7SA,IAAM+B,GAA+B,CAAC,IAAK,IAAK,GAAG,EAkE5C,SAASC,GAAeC,EAAgC,CAC7D,IAAMC,EAAW,SAAS,cAAc,KAAK,EAC7C,OAAAA,EAAS,GAAKD,EACdC,EAAS,aAAa,OAAQ,SAAS,EACvCA,EAAS,aAAa,oBAAqB,EAAE,EAC7CA,EAAS,UAAY,sBACrBA,EAAS,iBAAiB,YAAcC,GAAMA,EAAE,eAAe,CAAC,EACzDD,CACT,CASA,IAAME,GAAe,IAAI,QASzB,SAASC,GAAgBH,EAAuBI,EAAgC,CAC9E,IAAIC,EAAQH,GAAa,IAAIF,CAAQ,EAUrC,GATKK,IACHA,EAAQ,CACN,OAAQC,GAAwBN,EAAS,aAAa,EACtD,KAAM,KACN,WAAY,IACd,EACAA,EAAS,YAAYK,EAAM,MAAM,EACjCH,GAAa,IAAIF,EAAUK,CAAK,GAE9BA,EAAM,OAASD,IACjBC,EAAM,YAAY,QAAQ,EAC1BA,EAAM,WAAa,KACnBA,EAAM,KAAOD,EACTA,GAAM,CACRC,EAAM,WAAaE,GAAkB,CAAE,SAAAP,EAAU,KAAAI,EAAM,OAAQC,EAAM,MAAO,CAAC,EAC7E,MACF,CAEFA,EAAM,YAAY,OAAO,CAC3B,CAGO,SAASG,GAAgBR,EAA6B,CAC3D,IAAMK,EAAQH,GAAa,IAAIF,CAAQ,EAClCK,IACLA,EAAM,YAAY,QAAQ,EAC1BH,GAAa,OAAOF,CAAQ,EAC9B,CAEO,SAASS,GAAeT,EAAuBK,EAAsB,CAC1E,GAAM,CACJ,gBAAAK,EACA,YAAAC,EACA,OAAAC,EACA,UAAAC,EACA,MAAAC,EACA,UAAAC,EACA,qBAAAC,EACA,SAAAC,EACA,YAAAC,EACA,YAAAC,EACA,OAAAC,CACF,EAAIf,EAEEgB,EAAeP,EAAM,OAAS,EAC9BQ,EAAWP,GAAaM,EACxBE,EAAab,EAAgB,OAAS,EACtCc,EAAcnB,EAAM,SAAS,OAAS,EAGtCoB,EAAYb,IAAWW,GAAcD,GAAYT,GAAaW,GAuBpE,GArBIC,EACFzB,EAAS,UAAU,IAAI,8BAA8B,EAErDA,EAAS,UAAU,OAAO,8BAA8B,EAGtDa,EACFb,EAAS,aAAa,mBAAoB,EAAE,EAE5CA,EAAS,gBAAgB,kBAAkB,EAYzC,CAACyB,EAAW,CACdC,GAAyB1B,EAAU,EAAK,EACxC,MACF,CAaIwB,EACFxB,EAAS,aAAa,wBAAyB,EAAE,EAEjDA,EAAS,gBAAgB,uBAAuB,EAKlD,IAAI2B,EAAQ3B,EAAS,cAA2B,YAAY,EACvD2B,IACHA,EAAQ,SAAS,cAAc,KAAK,EACpCA,EAAM,UAAY,YAClBA,EAAM,MAAM,YAAY,oBAAqB,KAAK,EAClD3B,EAAS,YAAY2B,CAAK,GAc5B,IAAMC,EAAYvB,EAAM,gBAAkBgB,GAAgB,CAAChB,EAAM,aAC3DwB,EAAeP,GAAaT,GAAaE,GAAca,EACzDE,EAAUH,EAAM,cAA2B,sBAAsB,EACrE,GAAIE,EAAc,CACXC,IACHA,EAAU,SAAS,cAAc,KAAK,EACtCA,EAAQ,UAAY,kCACpBA,EAAQ,aAAa,cAAe,EAAE,EACtCA,EAAQ,aAAa,mBAAoB,EAAE,EAC3CH,EAAM,aAAaG,EAASH,EAAM,UAAU,GAO9C,IAAII,EAAaD,EAAQ,cAA2B,yBAAyB,EACxEC,IACHA,EAAa,SAAS,cAAc,MAAM,EAC1CA,EAAW,UAAY,yBAIvBA,EAAW,aAAa,uBAAwB,EAAE,EAClDD,EAAQ,aAAaC,EAAYD,EAAQ,UAAU,GAKrDE,GACED,EACAhB,EAAYD,EAAQ,CAAC,EACrB,EACAK,EACA,GACAN,GAAaE,EACbC,CACF,EAIAiB,GAAiBH,EAASF,EAAWf,GAAaR,EAAM,aAAcS,EAAM,CAAC,EAAGM,CAAM,CACxF,MAAWU,GACTA,EAAQ,OAAO,EAQjB,IAAMI,EAAmB7B,EAAM,YAAY,CAAC,EACtC8B,EAAWD,EAAmB,GAAGA,EAAiB,IAAI,IAAIA,EAAiB,IAAI,GAAK,GAYpFE,EAASC,EAAiBhC,EAAM,UAAU,EAC1CiC,EAAgBF,GAAU,CAACvB,EAEjC0B,GACEZ,EACAS,EAAS,CAAC,EAAI1B,EACdC,EACAM,EACAC,EACAb,EAAM,UACNQ,EACAsB,EACA9B,EAAM,iBAAmB,QACzBA,EAAM,iBAAmB,EAC3B,EACAF,GAAgBH,EAAU2B,EAAM,cAA2B,WAAW,CAAC,EAEvEa,GACEb,EACAW,EAAgB5B,EAAkB,CAAC,EACnCC,EACAN,EAAM,UACNiC,EAAgBjC,EAAM,SAAW,KACjC,CACE,YAAaA,EAAM,gBACnB,cAAeA,EAAM,kBACrB,WAAYA,EAAM,cACpB,EACA,CACE,SAAAY,EACA,YAAAC,EACA,gBAAiBb,EAAM,gBACvB,YAAaA,EAAM,WACrB,CACF,EAIA,IAAIoC,EAAWd,EAAM,cAA2B,2BAA2B,EAC3E,GAAId,IAAc,CAACU,GAAca,IAC/B,GAAI,CAACK,EAAU,CACbA,EAAW,SAAS,cAAc,KAAK,EACvCA,EAAS,UAAY,2BACrBA,EAAS,aAAa,yBAA0B,EAAE,EAClD,QAAWC,KAAS7C,GAA8B,CAChD,IAAM8C,EAAM,SAAS,cAAc,MAAM,EACzCA,EAAI,UAAY,0BAChBA,EAAI,MAAM,MAAQ,GAAGD,CAAK,KAC1BD,EAAS,YAAYE,CAAG,CAC1B,CACAhB,EAAM,YAAYc,CAAQ,CAC5B,OACSA,GACTA,EAAS,OAAO,EAIlBG,GACEjB,EACAtB,EAAM,SACNA,EAAM,UACNA,EAAM,gBACNA,EAAM,oBACR,EACAqB,GAAyB1B,EAAU,EAAI,EAGvC,IAAM6C,EAASlB,EAAM,cAA2B,oBAAoB,GAAKmB,GAAa,EAChFC,GAAoBpC,GAAe,GAAK,EAAQD,EAAgBC,CAAW,GAAG,YACpFqC,GAAiBH,EAAQI,GAAcF,GAAmB1C,EAAM,YAAY,CAAC,EACxEwC,EAAO,aAAalB,EAAM,YAAYkB,CAAM,EAEjDK,GAAcvB,EAAO,CACnB,uBACA,YACA,yBACA,4BACA,uBACA,oBACF,CAAC,CACH,CAcA,SAASuB,GAAcvB,EAAoBwB,EAA2B,CACpE,IAAMC,EAAWD,EACd,IAAKE,GAAa1B,EAAM,cAA2B,YAAY0B,CAAQ,EAAE,CAAC,EAC1E,OAAQC,GAA0BA,IAAO,IAAI,EAEhD,QAAS,EAAI,EAAG,EAAIF,EAAS,OAAQ,IAC/BzB,EAAM,SAAS,CAAC,IAAMyB,EAAS,CAAC,GAClCzB,EAAM,aAAayB,EAAS,CAAC,EAAGzB,EAAM,SAAS,CAAC,GAAK,IAAI,CAG/D,CASA,SAASM,GACPH,EACAyB,EACAC,EACAC,EACArC,EACA,CACA,IAAIsC,EAAM5B,EAAQ,cAAiC,kBAAkB,EACrE,GAAI,CAACyB,EAAS,CACZG,GAAK,OAAO,EACZ,MACF,CACKA,IACHA,EAAM,SAAS,cAAc,QAAQ,EACrCA,EAAI,KAAO,SACXA,EAAI,SAAW,GACfA,EAAI,UAAY,kBAChBA,EAAI,aAAa,gBAAiB,EAAE,EACpCA,EAAI,YAAc,OAClBA,EAAI,iBAAiB,YAAczD,GAAMA,EAAE,eAAe,CAAC,EAC3D6B,EAAQ,YAAY4B,CAAG,GAGzBA,EAAI,aAAa,aAAcD,EAAa,QAAQA,EAAW,IAAI,GAAK,MAAM,EAC9EC,EAAI,SAAWF,EACfE,EAAI,QAAUF,EAAU,KAAO,IAAMpC,EAAO,CAC9C,CAEA,SAAS4B,GACPH,EACA,CAAE,IAAKc,EAAS,KAAMC,CAAS,EAC/B,CACA,IAAMC,EAAMhB,EAAO,cAA2B,wBAAwB,EAChEiB,EAAOjB,EAAO,cAA2B,yBAAyB,EACpE,CAACgB,GAAO,CAACC,IACTD,EAAI,cAAgBF,IAASE,EAAI,YAAcF,GAC/CG,EAAK,cAAgBF,IAAUE,EAAK,YAAcF,GACxD,CAEA,SAASd,IAA4B,CACnC,IAAMD,EAAS,SAAS,cAAc,QAAQ,EAC9CA,EAAO,UAAY,oBACnBA,EAAO,aAAa,kBAAmB,EAAE,EAEzC,IAAMkB,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAY,oCAChBA,EAAI,aAAa,aAAc,QAAQ,EACvCA,EAAI,aAAa,eAAgB,SAAS,EAC1CA,EAAI,aAAa,cAAe,EAAE,EAElC,IAAMC,EAAY,SAAS,cAAc,KAAK,EAC9CA,EAAU,UAAY,2CACtBA,EAAU,aAAa,aAAc,QAAQ,EAC7CA,EAAU,MAAM,YAAY,oBAAqB,KAAK,EACtD,IAAMH,EAAM,SAAS,cAAc,KAAK,EACxCA,EAAI,UAAY,wBAChBA,EAAI,YAAc,MAClB,IAAMC,EAAO,SAAS,cAAc,MAAM,EAC1CA,EAAK,UAAY,yBACjBA,EAAK,YAAc,YACnBE,EAAU,OAAOH,EAAKC,CAAI,EAE1B,IAAMG,EAAa,SAAS,cAAc,GAAG,EAC7CA,EAAW,UAAY,2CACvBA,EAAW,aAAa,aAAc,QAAQ,EAC9CA,EAAW,KAAOC,GAAoB,EACtCD,EAAW,OAAS,SACpBA,EAAW,IAAM,sBACjBA,EAAW,MAAM,YAAY,oBAAqB,KAAK,EACvD,IAAME,EAAQ,SAAS,cAAc,MAAM,EAC3CA,EAAM,UAAY,0BAClBA,EAAM,YAAc,KACpB,IAAMC,EAAQ,SAAS,cAAc,MAAM,EAC3C,OAAAA,EAAM,UAAY,0BAClBA,EAAM,YAAc,eACpBH,EAAW,OAAOE,EAAOC,CAAK,EAE9BL,EAAI,OAAOC,EAAWC,CAAU,EAChCpB,EAAO,OAAOkB,CAAG,EACVlB,CACT,CCvcO,SAASwB,GACdC,EACAC,EACkB,CAClB,IAAMC,EAAWC,GAAeF,EAAK,SAAS,EAC9C,OAAAD,EAAU,YAAYE,CAAQ,EACvB,CAAE,SAAAA,CAAS,CACpB,CAEO,SAASE,GACdC,EACAC,EACAL,EACA,CACA,IAAMM,EAAgBC,GAAiBF,CAAK,EAC5CG,GAAeJ,EAAK,SAAU,CAM5B,YAAaC,EAAM,kBACf,CACE,CACE,KAAMA,EAAM,kBAAkB,KAC9B,KAAMI,EAAqBJ,EAAM,kBAAkB,IAAI,EACvD,SAAU,GACV,QAASA,EAAM,eACjB,CACF,EACAA,EAAM,sBAAsB,OAAS,EACnC,CAAC,CAAE,GAAGA,EAAM,sBAAsB,CAAC,EAAG,QAASA,EAAM,eAAgB,CAAC,EACtE,CAAC,EACP,gBAAiBA,EAAM,gBACvB,YAAaA,EAAM,oBACnB,OAAQA,EAAM,eAId,UACGA,EAAM,WAAa,CAACA,EAAM,cAAgB,CAACA,EAAM,sBAClDA,EAAM,mBACR,UAAWL,EAAK,UAChB,MAAOK,EAAM,sBACb,UAAW,GAEX,eAAgBL,EAAK,gBAAkB,CAACK,EAAM,cAAgB,CAACA,EAAM,kBAGrE,aAAcA,EAAM,qBACpB,gBAAiBL,EAAK,iBAAmB,GACzC,qBAAsBK,EAAM,qBAC5B,aAAcA,EAAM,KAAK,KAAK,EAAE,SAAW,EAC3C,SAAUA,EAAM,SAChB,WAAYA,EAAM,iBAClB,SAAUA,EAAM,SAChB,gBAAiBC,EAAc,YAC/B,kBAAmBA,EAAc,cACjC,eAAgBA,EAAc,WAC9B,gBAAiBN,EAAK,iBAAmB,QACzC,SAAUA,EAAK,aACf,YAAcU,GAAMV,EAAK,MAAM,IAAI,CAAE,oBAAqBU,CAAE,CAAC,EAC7D,YAAaV,EAAK,cAClB,OAAQA,EAAK,eACb,gBAAiBA,EAAK,kBACtB,YAAaA,EAAK,cAClB,gBAAiBA,EAAK,cACtB,qBAAuBW,GAAYX,EAAK,MAAM,IAAI,CAAE,UAAWW,CAAQ,CAAC,CAC1E,CAAC,CACH,CCxFA,IAAMC,GAAiB,EAGjBC,GAAe,GASrB,SAASC,GAAaC,EAA4B,CAChD,OAAIA,GAAc,EAAU,MAErB,IAAI,CADK,KAAK,IAAIF,GAAe,EAAID,GAAkBG,CAAU,GACnD,QAAQ,CAAC,CAAC,IACjC,CA2BO,SAASC,GAAsBC,EAA0B,CAC9D,GAAM,CAAE,MAAAC,EAAO,SAAAC,EAAU,WAAAC,EAAY,eAAAC,EAAgB,gBAAAC,EAAiB,UAAAC,CAAU,EAAIN,EAC9EO,EAAgBP,EAAK,eAAiB,GAEtCQ,EAAQN,EAAS,SAAW,EAClCD,EAAM,QAAQ,SAAWO,EAAQ,OAAS,QAG1CC,GAAgBR,EAAOO,EAAQH,EAAkB,EAAE,EAOnD,IAAMK,EAASR,EACZ,IAAKS,GACJA,EAAE,OAAS,YACP,GAAGA,EAAE,IAAI,IAAIA,EAAE,KAAK,IAASJ,EAAiBI,EAAE,MAAM,MAAQA,EAAE,MAAM,UAAY,GAAM,EAAE,GAC1F,GAAGA,EAAE,IAAI,IAAIA,EAAE,KAAK,EAC1B,EACC,KAAK,IAAI,EACNC,EAAaX,EAAM,QAAQ,QAAU,GACrCY,EAAiBZ,EAAM,QAAQ,YAAc,GAC7Ca,EAAqBb,EAAM,QAAQ,gBAAkB,GAC3D,GACES,IAAWE,IACVT,GAAc,MAAQU,IACtBT,GAAkB,MAAQU,EAE3B,OAGF,IAAMC,EAAcT,EAAYU,EAAgBf,CAAK,EAAI,KACzDA,EAAM,QAAQ,OAASS,EACvBT,EAAM,QAAQ,WAAaE,GAAc,GACzCF,EAAM,QAAQ,eAAiBG,GAAkB,GAEjD,IAAMa,EAAMC,EAAWjB,CAAK,EACtBkB,EAAOF,EAAI,uBAAuB,EACpCG,EAAY,EAChB,QAAWC,KAAOnB,EAEhB,GADAkB,GAAaC,EAAI,MAAM,OACnBA,EAAI,OAAS,YAAa,CAC5B,IAAMC,EAASL,EAAI,cAAc,QAAQ,EACzCK,EAAO,QAAQ,IAAM,YACrBA,EAAO,QAAQ,QAAUD,EAAI,MAAM,GACnC,IAAME,EAAQF,EAAI,MAAM,KAAOlB,EACzBqB,EAAYH,EAAI,MAAM,KAAOjB,EAC7BqB,EAAU,CAAC,qBAAsB,+BAA+B,EAClEF,GAAOE,EAAQ,KAAK,8BAA+B,0BAA0B,EAC7ED,GAAWC,EAAQ,KAAK,6BAA6B,EACzDH,EAAO,UAAYG,EAAQ,KAAK,GAAG,EACnCH,EAAO,MAAM,cAAgBzB,GAAawB,EAAI,MAAM,MAAM,EAK1D,IAAMK,EACJnB,GAAiBc,EAAI,MAAM,SAAWM,GAAsBN,EAAI,MAAM,QAAQ,EAAI,KACpF,GAAIK,EAAU,CACZ,IAAME,EAAOX,EAAI,cAAc,MAAM,EACrCW,EAAK,UAAY,0BACjBA,EAAK,aAAa,kBAAmB,OAAO,EAC5CA,EAAK,aAAa,cAAe,MAAM,EACvCA,EAAK,UAAYF,EACjBJ,EAAO,YAAYM,CAAI,EACnBP,EAAI,MAAM,OAAMC,EAAO,QAAQ,QAAUD,EAAI,MAAM,KACzD,CACAC,EAAO,YAAYL,EAAI,eAAeI,EAAI,KAAK,CAAC,EAChDF,EAAK,YAAYG,CAAM,CACzB,SAAWD,EAAI,OAAS,aAAc,CAMpC,IAAMC,EAASL,EAAI,cAAc,QAAQ,EACzCK,EAAO,QAAQ,IAAM,aACrBA,EAAO,QAAQ,QAAUD,EAAI,MAAM,GACnC,IAAMQ,EAAoB,CAAC,qBAAsB,+BAA+B,EAG5ER,EAAI,MAAM,KAAOjB,GAAgByB,EAAkB,KAAK,6BAA6B,EACzFP,EAAO,UAAYO,EAAkB,KAAK,GAAG,EAC7CP,EAAO,MAAM,cAAgBzB,GAAawB,EAAI,MAAM,MAAM,EAC1DC,EAAO,YAAcD,EAAI,MACzBF,EAAK,YAAYG,CAAM,CACzB,MACEH,EAAK,YAAYF,EAAI,eAAeI,EAAI,KAAK,CAAC,EAGlDpB,EAAM,gBAAgBkB,CAAI,EAC1BlB,EAAM,QAAQ,cAAgB,OAAOmB,CAAS,EAE1CL,GAAe,MAKjBe,EAAgB7B,EAAO,KAAK,IAAI,EAAG,KAAK,IAAIc,EAAaK,CAAS,CAAC,CAAC,CAExE,CC1JA,IAAMW,GAAa,8NAOZ,SAASC,IAAwC,CACtD,IAAMC,EAAM,SAAS,cAAc,QAAQ,EAC3C,OAAAA,EAAI,KAAO,SACXA,EAAI,UAAY,oBAChBA,EAAI,aAAa,aAAc,QAAQ,EACvCA,EAAI,aAAa,kBAAmB,EAAE,EACtCA,EAAI,UAAYF,GACTE,CACT,CCsDA,SAASC,IAAiC,CACxC,IAAMC,EAAQ,SAAS,cAAc,KAAK,EAC1C,OAAAA,EAAM,aAAa,kBAAmB,gBAAgB,EAC/CA,EAAM,kBAAoB,gBACnC,CAcA,SAASC,GAAgBC,EAAoBC,EAA8B,CACzE,IAAMC,EAAQD,EAAU,kBACxB,GAAI,CAACC,EAAO,CACVD,EAAU,gBAAgB,uBAAuB,EACjD,MACF,CACA,IAAME,EAAQD,EAAM,sBAAsB,EACpCE,EAAQJ,EAAM,sBAAsB,EAC1BG,EAAM,KAAOC,EAAM,OAAS,EAC/BH,EAAU,aAAa,wBAAyB,EAAE,EAC1DA,EAAU,gBAAgB,uBAAuB,CACxD,CAEO,SAASI,GAASJ,EAAwBK,EAAmC,CAClF,GAAM,CAAE,UAAAC,CAAU,EAAID,EAEhBE,EAAWC,GAAeF,CAAS,EACzCN,EAAU,YAAYO,CAAQ,EAE9B,IAAME,EAAe,SAAS,cAAc,KAAK,EACjDA,EAAa,UAAY,2BAGzBA,EAAa,aAAa,yBAA0B,EAAE,EACtDT,EAAU,YAAYS,CAAY,EAElC,IAAMC,EAAS,SAAS,cAAc,KAAK,EAC3CA,EAAO,UAAY,oBACnBA,EAAO,aAAa,kBAAmB,EAAE,EACzCD,EAAa,YAAYC,CAAM,EAE/B,IAAMX,EAAQ,SAAS,cAAc,KAAK,EAC1CA,EAAM,UAAY,mBAClBA,EAAM,aAAa,iBAAkB,EAAE,EACvCA,EAAM,aAAa,kBAAmBH,GAAsB,EAAI,iBAAmB,MAAM,EACzFG,EAAM,aAAa,OAAQ,UAAU,EACrCA,EAAM,aAAa,oBAAqB,MAAM,EAC9CA,EAAM,aAAa,gBAAiB,SAAS,EAC7CA,EAAM,aAAa,gBAAiBO,CAAS,EAC7CP,EAAM,aAAa,gBAAiB,OAAO,EAC3CA,EAAM,aAAa,aAAc,MAAM,EACvCA,EAAM,aAAa,eAAgB,MAAM,EACzCW,EAAO,YAAYX,CAAK,EAKxB,IAAMY,EAAsB,SAAS,cAAc,MAAM,EACzDA,EAAoB,UAAY,iCAChCA,EAAoB,aAAa,+BAAgC,EAAE,EACnED,EAAO,YAAYC,CAAmB,EAEtC,IAAIC,EAAyC,KACzCC,EAAmC,KACnCR,EAAK,eAAiB,QACxBO,EAAeE,GAAmB,EAClCL,EAAa,YAAYG,CAAY,EACrCC,EAAeD,GACNP,EAAK,eAAiB,OAC/BQ,EAAeR,EAAK,aACfQ,EAAa,aAAa,iBAAiB,GAC9CA,EAAa,aAAa,kBAAmB,EAAE,EAIjDA,EAAa,aAAa,uBAAwB,EAAE,EACpDJ,EAAa,YAAYI,CAAY,GAGvC,IAAME,EAAQ,IAAI,gBACZ,CAAE,OAAAC,CAAO,EAAID,EAEfE,EAAY,GAIZC,EAAc,EAEZC,EAAY,IAAM,CACtB,IAAMC,EAAMC,GAAiBtB,CAAK,EAE5BuB,EADmBF,EAAI,OAAS,GAAKA,EAAI,CAAC,IAAMA,EAAI,CAAC,EAAE,YAAY,EACrCA,EAAI,CAAC,EAAE,YAAY,EAAIA,EAAI,MAAM,CAAC,EAAIA,EAC1Ef,EAAK,aAAaiB,CAAQ,CAC5B,EAEMC,EAAuB,IAAqB,CAChD,IAAMC,EAAMC,EAAgB1B,CAAK,EACjC,GAAI,CAACyB,GAAOA,EAAI,aAAe,EAAG,OAAO,KACzC,IAAME,EAASF,EAAI,WACnB,GAAI,CAACE,GAAU,CAAC3B,EAAM,SAAS2B,CAAM,EAAG,OAAO,KAQ/C,IAAMC,GANJD,EAAO,WAAa,KAAK,aAAgBA,EAAqBA,EAAO,gBAK/C,QAAqB,uBAAuB,GAC5C,QAAQ,SAAW,KAO3C,OAAIC,GAAWC,GAAiB7B,EAAO4B,CAAO,EAAU,KACjDA,CACT,EA4JA,GA1JAlB,EAAa,iBACX,QACCoB,GAAM,CAEAA,EAAE,QAA+B,QAAQ,iBAAiB,GAC/D9B,EAAM,MAAM,CACd,EACA,CAAE,OAAAiB,CAAO,CACX,EAEAjB,EAAM,iBACJ,QACA,IAAM,CACAkB,IACJC,EAAc,YAAY,IAAI,EAC9BC,EAAU,EAGVd,EAAK,sBAAsByB,EAAgB/B,CAAK,CAAC,EACnD,EACA,CAAE,OAAAiB,CAAO,CACX,EAcYe,EAAWhC,CAAK,EACxB,iBACF,kBACA,IAAM,CACJ,IAAMyB,EAAMC,EAAgB1B,CAAK,EAEjC,GADI,CAACyB,GAAOA,EAAI,aAAe,GAC3B,CAACzB,EAAM,SAASyB,EAAI,UAAW,EAAG,OAMtC,IAAMQ,EAAYR,EAAI,YAAcD,EAAqB,EAAI,KACvDU,EAAY5B,EAAK,MAAM,IAAI,EAI3B6B,EAAYD,EAAU,cAAc,IAAMA,EAAU,mBAAmB,IAAM,KACnF,GAAID,GAAaA,IAAcE,EAAW,CACxC7B,EAAK,kBAAkB2B,CAAS,EAChC,MACF,CACI,YAAY,IAAI,EAAId,EAAc,IAItCb,EAAK,gBAAgByB,EAAgB/B,CAAK,CAAC,CAC7C,EACA,CAAE,OAAAiB,CAAO,CACX,EAEAjB,EAAM,iBACJ,mBACA,IAAM,CACJkB,EAAY,EACd,EACA,CAAE,OAAAD,CAAO,CACX,EACAjB,EAAM,iBACJ,iBACA,IAAM,CACJkB,EAAY,GACZE,EAAU,CACZ,EACA,CAAE,OAAAH,CAAO,CACX,EAEAjB,EAAM,iBACJ,cACC8B,GAAM,CACL,IAAMM,EAAaN,EACbO,EAAID,EAAW,UACrB,GAAIC,IAAM,mBAAqBA,IAAM,mBAAqBA,IAAM,iBAAkB,CAChFP,EAAE,eAAe,EACjB,MACF,CAKA,GAAIO,EAAE,WAAW,QAAQ,GAAKA,EAAE,WAAW,QAAQ,EAAG,CACpD,IAAMC,EAAcD,EAAE,WAAW,QAAQ,EAAI,GAAMD,EAAW,MAAQ,GAClE9B,EAAK,oBAAoBgC,CAAW,GACtCR,EAAE,eAAe,CAErB,CACF,EACA,CAAE,OAAAb,CAAO,CACX,EAEAjB,EAAM,iBACJ,QACC8B,GAAM,CACLA,EAAE,eAAe,EACjB,IAAMS,GAAQT,EAAE,eAAe,QAAQ,YAAY,GAAK,IAAI,QAAQ,SAAU,GAAG,EACjF,GAAI,CAACS,EAAM,OACX,IAAMC,EAAMR,EAAWhC,CAAK,EACtByB,EAAMC,EAAgB1B,CAAK,EACjC,GAAI,CAACyB,GAAOA,EAAI,aAAe,EAAG,OAClC,IAAMgB,EAAQhB,EAAI,WAAW,CAAC,EAC9B,GAAI,CAACzB,EAAM,SAASyC,EAAM,cAAc,EAAG,OAC3CA,EAAM,eAAe,EACrB,IAAMC,EAAOF,EAAI,eAAeD,CAAI,EACpCE,EAAM,WAAWC,CAAI,EACrBD,EAAM,cAAcC,CAAI,EACxBD,EAAM,SAAS,EAAI,EACnBhB,EAAI,gBAAgB,EACpBA,EAAI,SAASgB,CAAK,EAClBrB,EAAU,CACZ,EACA,CAAE,OAAAH,CAAO,CACX,EAEAjB,EAAM,iBAAiB,UAAY8B,GAAMxB,EAAK,cAAcwB,CAAC,EAAG,CAAE,OAAAb,CAAO,CAAC,EAE1EjB,EAAM,iBAAiB,QAAS,IAAMM,EAAK,MAAM,IAAI,CAAE,UAAW,EAAK,CAAC,EAAG,CAAE,OAAAW,CAAO,CAAC,EACrFjB,EAAM,iBAAiB,OAAQ,IAAMM,EAAK,MAAM,IAAI,CAAE,UAAW,EAAM,CAAC,EAAG,CAAE,OAAAW,CAAO,CAAC,EAEjFH,GACFA,EAAa,iBACX,QACCgB,GAAM,CACL,IAAMa,EAAQrC,EAAK,MAAM,IAAI,EAE7B,GAAI,EADc,CAAC,CAACqC,EAAM,MAAQA,EAAM,gBAAgB,OAAS,IAC/C,CAACrC,EAAK,SAAU,OAClCwB,EAAE,gBAAgB,EACAxB,EAAK,SACrBsC,EAAkBD,EAAM,KAAMA,EAAM,gBAAiBA,EAAM,cAAe,CACxE,iBAAkBA,EAAM,iBACxB,QAASA,EAAM,QACf,aAAcA,EAAM,YACtB,CAAC,CACH,GACerC,EAAK,cAAc,CACpC,EACA,CAAE,OAAAW,CAAO,CACX,EAGEX,EAAK,YAAc,GAAO,CAC5BN,EAAM,MAAM,EAIZ,IAAMwC,EAAMR,EAAWhC,CAAK,EACtByB,EAAMC,EAAgB1B,CAAK,EAC3B6C,EAAcpB,GAAOA,EAAI,WAAa,GAAKzB,EAAM,SAASyB,EAAI,UAAU,EAC9E,GAAIA,GAAO,CAACoB,EAAa,CACvB,IAAMJ,EAAQD,EAAI,YAAY,EAC9BC,EAAM,mBAAmBzC,CAAK,EAC9ByC,EAAM,SAAS,EAAI,EACnBhB,EAAI,gBAAgB,EACpBA,EAAI,SAASgB,CAAK,CACpB,CACF,CAKA,GAAI,OAAO,eAAmB,IAAa,CACzC,IAAMK,EAAK,IAAI,eAAe,IAAM/C,GAAgBC,EAAOY,CAAmB,CAAC,EAC/EkC,EAAG,QAAQ9C,CAAK,EAChBgB,EAAM,OAAO,iBAAiB,QAAS,IAAM8B,EAAG,WAAW,EAAG,CAAE,KAAM,EAAK,CAAC,CAC9E,CAEA,MAAO,CAAE,MAAA9C,EAAO,oBAAAY,EAAqB,SAAAJ,EAAU,aAAAK,EAAc,MAAAG,CAAM,CACrE,CAEO,SAAS+B,GAAUC,EAAeL,EAAkBrC,EAA0B,CACnF,GAAM,CAAE,MAAAN,EAAO,oBAAAY,EAAqB,SAAAJ,EAAU,aAAAK,CAAa,EAAImC,EACzD,CAAE,cAAAC,EAAe,cAAAC,EAAe,aAAAC,EAAc,MAAAC,CAAM,EAAI9C,EAE9DN,EAAM,aAAa,gBAAiB,OAAO2C,EAAM,cAAc,CAAC,EAChE,IAAMU,EACJV,EAAM,qBAAuB,EAAI,GAAGrC,EAAK,SAAS,WAAWqC,EAAM,mBAAmB,GAAK,GAO7F,GANIU,EACFrD,EAAM,aAAa,wBAAyBqD,CAAgB,EAE5DrD,EAAM,gBAAgB,uBAAuB,EAG3Ca,EAAc,CAChB,IAAMyC,EAAY,CAAC,CAACX,EAAM,MAAQA,EAAM,gBAAgB,OAAS,EACjE9B,EAAa,SAAW,CAACyC,CAC3B,CAOA,IAAMC,EAAkBvD,EAAM,QAAQ,YAAc,GAC9CwD,EAAeb,EAAM,aAAe,MAAQA,EAAM,aAAeY,EAYvE,GAVAE,GAAsB,CACpB,MAAAzD,EACA,SAAU2C,EAAM,SAChB,WAAYA,EAAM,WAClB,eAAgBA,EAAM,cAAc,IAAM,KAC1C,gBAAiBA,EAAM,gBACvB,UAAWA,EAAM,UACjB,cAAerC,EAAK,eAAiB,EACvC,CAAC,EAEG2C,IAAkB,SAAU,CAC9B,IAAMS,EAAgBf,EAAM,WAAa,CAACA,EAAM,cAAgB,CAACA,EAAM,qBACnEe,GAAiBf,EAAM,sBAAsB,OAAS,EACxDgB,GACE/C,EACA+B,EAAM,sBACN,EACAO,EACA,GACAQ,EACAf,EAAM,oBACR,EAEAiB,GAAWhD,CAAmB,CAElC,MACEgD,GAAWhD,CAAmB,EAIhCb,GAAgBC,EAAOY,CAAmB,EAEtC4C,GAMFxD,EAAM,MAAM,EACZ6D,EAAgB7D,EAAO2C,EAAM,aAAeA,EAAM,KAAK,MAAM,GACpDA,EAAM,WAICrB,GAAiBtB,CAAK,IACtB2C,EAAM,MACpBkB,EAAgB7D,EAAO2C,EAAM,KAAK,MAAM,EAQ5C,IAAMmB,EAAkCnB,EAAM,aAC1C,CACE,KAAMA,EAAM,aAAa,eACzB,KAAMA,EAAM,aAAa,sBACzB,SAAU,GACV,QAASA,EAAM,aAAa,OAC9B,EACAA,EAAM,kBAGJ,CACE,KAAMA,EAAM,kBAAkB,KAC9B,KAAMoB,EAAqBpB,EAAM,kBAAkB,IAAI,EACvD,SAAU,GACV,QAAS,CAAC,CACZ,EACA,KACAqB,EAAqBF,GAAgBnB,EAAM,sBAAsB,CAAC,EAClEsB,EAAgBC,GAAiBvB,CAAK,EAE5CwB,GAAe3D,EAAU,CACvB,YAAawD,EACT,CAAC,CAAE,GAAGA,EAAoB,QAASrB,EAAM,eAAgB,CAAC,EAC1D,CAAC,EACL,gBAAiBA,EAAM,gBACvB,YAAaA,EAAM,oBACnB,OAAQA,EAAM,eAId,UACGA,EAAM,WAAa,CAACA,EAAM,cAAgB,CAACA,EAAM,sBAClDA,EAAM,mBACR,UAAWrC,EAAK,UAChB,MAAOwD,EAAe,CAACA,CAAY,EAAInB,EAAM,sBAC7C,UAAWM,IAAkB,WAG7B,eAAgB3C,EAAK,gBAAkB,CAACqC,EAAM,cAAgB,CAACA,EAAM,kBAGrE,aAAcA,EAAM,qBACpB,gBAAiBrC,EAAK,iBAAmB,GACzC,qBAAsBqC,EAAM,qBAC5B,aAAcA,EAAM,KAAK,KAAK,EAAE,SAAW,EAC3C,SAAUA,EAAM,SAChB,WAAYA,EAAM,iBAClB,SAAUA,EAAM,SAChB,gBAAiBsB,EAAc,YAC/B,kBAAmBA,EAAc,cACjC,eAAgBA,EAAc,WAC9B,gBAAiB3D,EAAK,iBAAmB,QACzC,SAAU6C,EACV,YAAciB,GAAMhB,EAAM,IAAI,CAAE,oBAAqBgB,CAAE,CAAC,EACxD,YAAalB,EACb,OAAQ5C,EAAK,eACb,gBAAiBA,EAAK,kBACtB,YAAaA,EAAK,cAClB,gBAAiBA,EAAK,cAGtB,qBAAuB+D,GAAYjB,EAAM,IAAI,CAAE,UAAWiB,CAAQ,CAAC,CACrE,CAAC,CACH,CC7eO,SAASC,GACdC,EACAC,EACwB,CACxB,IAAMC,EAAmBF,EAAO,sBAAsB,CAAC,EACvD,GAAI,CAACE,EAAkB,OAAO,KAE9B,IAAMC,EAAOH,EAAO,WAChBI,EAASJ,EAAO,KAAK,MAAM,EAAGG,CAAI,EAEhCE,EAAgBD,EAAO,SAAW,GAAKJ,EAAO,KAAK,SAAW,EAK9DM,EACJF,EAAO,SAAW,GAClBJ,EAAO,KAAK,OAAS,GACrBA,EAAO,gBAAgB,OAAS,GAChCA,EAAO,gBAAgB,YAAY,EAAE,WAAWA,EAAO,KAAK,YAAY,CAAC,GACtEK,GAAiBC,IAA6BN,EAAO,kBACxDI,EAAS,GAAGJ,EAAO,eAAe,KAGpC,IAAMO,EAAeC,GAAkBJ,EAAQH,EAAO,IAAI,EACtDM,EAAe,IACjBH,EAASA,EAAO,MAAM,EAAGA,EAAO,OAASG,CAAY,GAGvD,IAAME,EAAaL,EAAO,OAAS,GAAKA,EAAOA,EAAO,OAAS,CAAC,IAAM,IAChEM,EAAU,GAAGN,CAAM,GAAGK,EAAa,IAAM,EAAE,GAAGR,EAAO,IAAI,IACzDU,GACHN,GAAiBC,IAA6BI,EAAQ,OAAS,EAC5DA,EAAQ,CAAC,EAAE,YAAY,EAAIA,EAAQ,MAAM,CAAC,EAC1CA,EAKAE,EAAcD,EAAU,YAAY,EAAE,YAAYV,EAAO,KAAK,YAAY,CAAC,EAC3EY,EACJD,GAAe,EAAID,EAAU,MAAMC,EAAaA,EAAcX,EAAO,KAAK,MAAM,EAAIA,EAAO,KAEvFa,EAAiC,CACrC,GAAI,OAAO,WAAW,EACtB,YAAa,GACb,KAAMZ,EAAiB,KACvB,KAAMW,EACN,KAAMZ,EAAO,KACb,eAAgBC,EAAiB,KACjC,sBAAuBA,EAAiB,KACxC,QAASA,EAAiB,SAAW,CAAC,EACtC,SAAUD,EAAO,SACjB,KAAMA,EAAO,KACb,SAAUA,EAAO,QACnB,EAEMc,EAAsBf,EAAO,sBAAsB,OAAS,EAElE,MAAO,CACL,MAAO,CACL,KAAMW,EACN,WAAYA,EAAU,OACtB,gBAAiB,CAAC,GAAGX,EAAO,gBAAiBc,CAAS,EACtD,WAAYA,EAAU,GACtB,YAAaH,EAAU,OACvB,WAAY,GACZ,oBAAqB,GAWrB,cAAe,GACf,qBAAsB,GAGtB,YAAa,IACf,EACA,UAAW,CACT,eAAgBV,EAAO,KACvB,aAAcD,EAAO,gBAAgB,OAAQgB,GAAMA,EAAE,OAASf,EAAO,IAAI,EAAE,IAAKe,GAAMA,EAAE,IAAI,CAC9F,EACA,mBAAoBd,EACpB,oBAAAa,CACF,CACF,CC3GO,SAASE,GAAeC,EAAsB,CACnD,IAAIC,EAAQD,EACNE,EAAY,IAAI,IAchBC,EAAgC,CAAC,EACnCC,EAAY,GAChB,MAAO,CACL,IAAK,IAAMH,EACX,IAAMI,GAAU,CACd,IAAMC,EAAW,OAAOD,GAAU,WAAaA,EAAMJ,CAAK,EAAII,EACxDE,EAAON,EAGb,GAFAA,EAAQ,CAAE,GAAGA,EAAO,GAAGK,CAAS,EAChCH,EAAQ,KAAK,CAACF,EAAOM,CAAI,CAAC,EACtB,CAAAH,EACJ,CAAAA,EAAY,GACZ,GAAI,CACF,IAAII,EAAU,EACd,QAASC,EAAQN,EAAQ,MAAM,EAAGM,EAAOA,EAAQN,EAAQ,MAAM,EAAG,CAKhE,GAAI,EAAEK,EAAU,IACd,MAAAL,EAAQ,OAAS,EACX,IAAI,MACR,kIACF,EAEF,GAAM,CAACO,EAAMC,CAAQ,EAAIF,EACzB,QAAWG,KAAKV,EAAWU,EAAEF,EAAMC,CAAQ,CAC7C,CACF,OAASE,EAAK,CAIZ,MAAAV,EAAQ,OAAS,EACXU,CACR,QAAE,CACAT,EAAY,EACd,EACF,EACA,UAAYU,IACVZ,EAAU,IAAIY,CAAQ,EACf,IAAM,CACXZ,EAAU,OAAOY,CAAQ,CAC3B,EAEJ,CACF,CAoBO,SAASC,GACdC,EACAC,EACoB,CACpB,IAAIC,EACAC,EAEEC,EAAgBC,IAChBA,IAAWH,IACbA,EAAeG,EACfF,EAAgBF,EAAOI,CAAM,GAExBF,GAGT,MAAO,CACL,IAAK,IAAM,CACT,IAAME,EAASL,EAAK,IAAI,EACxB,MAAO,CAAE,GAAGK,EAAQ,GAAGD,EAAaC,CAAM,CAAE,CAC9C,EACA,IAAMhB,GAAU,CAIV,OAAOA,GAAU,WACnBW,EAAK,IAAKK,GAAW,CACnB,IAAMC,EAAO,CAAE,GAAGD,EAAQ,GAAGD,EAAaC,CAAM,CAAE,EAClD,OAAOhB,EAAMiB,CAAI,CACnB,CAAC,EAEDN,EAAK,IAAIX,CAAK,CAElB,EAGA,KAAOA,GAAU,CACf,IAAMgB,EAAS,CAAE,GAAGL,EAAK,IAAI,EAAG,GAAGX,CAAM,EACzC,MAAO,CAAE,GAAGgB,EAAQ,GAAGJ,EAAOI,CAAM,CAAE,CACxC,EAIA,UAAYP,GACVE,EAAK,UAAU,CAACN,EAAMH,IAAS,CAC7B,IAAMgB,EAAW,CAAE,GAAGhB,EAAM,GAAGa,EAAab,CAAI,CAAE,EAC5CiB,EAAW,CAAE,GAAGd,EAAM,GAAGU,EAAaV,CAAI,CAAE,EAClDI,EAASU,EAAUD,CAAQ,CAC7B,CAAC,CACL,CACF,CCrIA,IAAME,GAAc,IAAI,QAQlBC,GAAS,IAAI,QAEnB,SAASC,GAAWC,EAAoC,CACtD,OAAOA,EAAK,WAAa,KAAK,aAChC,CAEA,SAASC,GAAeD,EAA4B,CAClD,OAAOD,GAAWC,CAAI,EAAIA,EAAOA,EAAK,aACxC,CAGA,SAASE,GAAmBC,EAAqC,CAC/D,IAAMC,EAASN,GAAO,IAAIK,CAAG,EAC7B,GAAIC,EAAQ,OAAOA,EAGnB,IAAMC,EAAOF,EAAI,aAAa,cAC9B,GAAI,OAAOE,GAAS,WAAY,OAAO,KACvC,GAAI,CACF,IAAMC,EAAQ,IAAID,EAClB,OAAI,OAAOC,EAAM,aAAgB,WAAmB,MACpDA,EAAM,YAAYC,EAAM,EACxBT,GAAO,IAAIK,EAAKG,CAAK,EACdA,EACT,MAAQ,CAEN,OAAO,IACT,CACF,CAGA,SAASE,GAAmBL,EAAeM,EAAwB,CAIjE,GAAIA,EAAK,cAAc,wBAAwB,EAAG,OAClD,IAAMC,EAAQP,EAAI,cAAc,OAAO,EACvCO,EAAM,aAAa,kBAAmB,EAAE,EACxCA,EAAM,YAAcH,GACpBE,EAAK,YAAYC,CAAK,CACxB,CAiBO,SAASC,GAAaX,EAA0B,CACrD,GAAI,OAAO,SAAa,IAAa,OACrC,IAAMY,EAASZ,GAAQ,SACvB,GAAIH,GAAY,IAAIe,CAAM,EAAG,OAE7B,IAAMT,EAAMF,GAAeW,CAAM,EACjC,GAAIb,GAAWa,CAAM,EAAG,CACtB,IAAMH,EAAOG,EAAO,MAAQA,EAAO,gBACnC,GAAI,CAACH,EAAM,OACXD,GAAmBL,EAAKM,CAAI,EAC5BZ,GAAY,IAAIe,CAAM,EACtB,MACF,CAEA,IAAMN,EAAQJ,GAAmBC,CAAG,EAC9BU,EAAUD,EAAO,mBACnBN,GAAS,MAAM,QAAQO,CAAO,EAC3BA,EAAQ,SAASP,CAAK,IAEzBM,EAAO,mBAAqB,CAAC,GAAGC,EAASP,CAAK,GAGhDE,GAAmBL,EAAKS,CAAM,EAEhCf,GAAY,IAAIe,CAAM,CACxB,CAIA,IAAML,GAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;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","DATE_RANGE_META_END","DATE_RANGE_META_START","ModeController","OPTIONS_GRID_MOBILE_QUERY","OPTION_ENTER_DELAY_VAR","OPTION_ENTER_FADE_MS","OPTION_ENTER_RISE_MS","OPTION_ENTER_RISE_PX","OPTION_ENTER_STAGGER_MS","PLACEHOLDER_FADE_OUT_MS","PLACEHOLDER_LEAVING_ATTR","PLACEHOLDER_SWAP_GAP_MS","PLACEHOLDER_TYPE_MS","PLACEHOLDER_WORD_PAUSE_MS","PRODUCT_PATH_PREFIX","RANGE_SEPARATOR","SCROLL_ARROW_ATTR","SCROLL_ARROW_BOTTOM_VAR","SCROLL_ARROW_CLASS","SCROLL_ARROW_FADE_MS","SCROLL_ARROW_LABEL","SCROLL_ARROW_SCROLL_IDLE_MS","SCROLL_ARROW_VISIBLE_ATTR","SKIPPED_PARAM_TEXT","WEEKDAY_LABELS","addMonths","attachScrollArrow","buildAttributionUrl","buildDateOptions","buildQuery","buildSubmitResult","cellDay","cellIso","computeOptionsGridLayout","createStore","cursorIsAtEnd","dateCellMarks","dateSelectionFor","extractPlainText","formatAbsoluteDate","formatDate","formatDateRange","getCursorOffset","getFooterHint","identifiedParamLabel","isCalendarFormat","isOptionsGridMobileViewport","isoDate","measureOptionsGrid","monthLabel","needsOptionsGridMeasurement","optionEnterDelayMs","optionLabel","optionsEntranceDurationMs","optionsGridTemplateColumns","parseDate","parseLooseDate","parseLooseDateRange","plainTextLength","planOptionsGrid","previousGraphemeBoundary","productFromMatchedItem","productsFromCustomFields","renderEditableContent","resolveFormatType","resolveIdentifiedDate","sanitizeOptionIconSvg","scrollCaretIntoView","selectedIsoFromText","selectedRangeFor","setCursorOffset","toWireIdentifiedParams","visibleDateRange","withSkippedParams","__toCommonJS","tokenizeParamType","type","token","identifiedParamLabel","words","MONTH_NAMES","WEEKDAY_LABELS","WEEKDAY_NAMES","DATE_META_ISO","DATE_META_DAY","pad","isoDate","d","startOfDay","daysFromToday","ms","formatDate","diff","formatAbsoluteDate","parseDate","text","weekday","name","now","delta","m","month","day","year","monthLabel","view","todayView","viewOf","addMonths","blankCell","buildDateOptions","view","lead","daysInMonth","cells","i","day","date","formatDate","DATE_META_ISO","isoDate","DATE_META_DAY","WEEKDAY_LABELS","selectedIsoFromText","text","d","parseDate","cellIso","option","iso","cellDay","dropOrdinals","text","monthIndex","word","w","MONTH_NAMES","m","withResolvedYear","month","day","today","thisYear","startOfToday","nextYear","exactDate","year","d","parseLooseDate","opts","t","iso","isoDate","monthFirst","fromParts","dayFirst","numeric","a","b","yearText","resolveIdentifiedDate","param","fromServer","RANGE_SEPARATOR","DATE_RANGE_META_START","DATE_RANGE_META_END","orderedRange","a","b","ISO_RE","dateFromIso","iso","y","m","d","date","WORD_SEPARATOR","DASH_SEPARATOR","splitPoints","text","out","re","match","parseLooseDateRange","opts","found","range","left","right","start","parseLooseDate","end","formatDateRange","formatAbsoluteDate","buildRangeOption","selectedRangeFor","param","isCalendarFormat","format","DATE_TOKENS","RANGE_TOKENS","DATE_OPTION_THRESHOLD","countDateOptions","options","counts","option","parseLooseDateRange","parseLooseDate","resolveFormatType","source","fromServer","tokens","tokenizeParamType","token","TokenManager","config","forceRefresh","result","DEFAULT_API_ORIGIN","DEFAULT_SUGGEST_ENDPOINT","tokenManagers","isAccessTokenConfig","config","getApiKeyConfig","getTokenManager","manager","TokenManager","buildHeaders","apiConfig","buildApiKeyAuthHeader","apiKeyConfig","apiKey","toWireIdentifiedParams","params","p","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","toWireIdentifiedParams","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","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","isCalendarFormat","resolveFormatType","query","extractFilterQuery","match","findExactMatch","customFields","products","completedNow","identifiedParams","reconcileIdentifiedParams","carriedSkips","p","racedSkipTypes","sg","caughtError","minDiff","anyChange","consumer","placeholderText","effBase","effectiveFilterBase","currentQuery","isDate","tappableFiltered","filterOptions","o","hasExactMatch","isInFilterZone","isTypingPlaceholderPrefix","updatedParams","buildQuery","isDeleting","charDiff","changed","isWidgetRoot","node","rootOf","root","documentOf","getSelectionFor","selection","getElementByIdFor","id","CARET_SCROLL_MARGIN","WIDGET_CONTAINER_CLASS","isScrollableOverflow","value","findScrollContainer","root","doc","documentOf","win","el","overflowsX","overflowsY","style","scrollCaretIntoView","sel","getSelectionFor","range","container","caret","box","viewLeft","viewRight","viewTop","viewBottom","NON_EDITABLE_SELECTOR","CHIP_SELECTOR","segmenter","getGraphemeSegmenter","Segmenter","isInsideNonEditable","node","root","n","createTextWalker","documentOf","extractPlainText","walker","out","plainTextLength","total","getCursorOffset","sel","getSelectionFor","anchorNode","anchorOffset","el","offset","i","plainTextLengthOfSubtree","offsetBeforeNode","child","target","setCursorOffset","doc","clamped","cumulative","targetOffset","lastNode","len","next","range","strongParent","recordChipBoundary","scrollCaretIntoView","chipContaining","chip","reported","caretEscapedChip","chipId","record","cursorIsAtEnd","previousGraphemeBoundary","text","seg","slice","last","index","buildSubmitResult","text","completedParams","skippedParams","extras","rawQuery","finalParams","buildQuery","withSkippedParams","toWireIdentifiedParams","isCursorAtEnd","target","state","cursorIsAtEnd","getEditableCaretOffset","getCursorOffset","KeyboardController","store","ctx","listboxId","getOnSubmit","from","columns","onSubmit","tappableIndices","above","cursorAtEnd","inEditMode","entry","isCalendarFormat","lastRow","currentPos","nextPos","initialIdx","prevPos","rightNeighbor","editor","tail","setCursorOffset","leftNeighbor","caret","anchor","offset","buildSubmitResult","tappableOptionIndices","o","i","firstIdx","delta","bottomRowStart","fromBottom","today","isoDate","todayIdx","cellIso","days","next","tappable","buckets","listbox","getElementByIdFor","el","gtc","tracks","index","options","optionEl","OptionSourceController","store","getOverrides","callbacks","next","type","query","state","minDiff","target","current","delta","shorter","isCalendarFormat","editing","active","override","version","abort","raw","err","pending","signal","answer","isAbortError","live","search","answered","s","sg","status","prev","PillsController","store","callbacks","index","state","actionable","s","moved","rest","_","i","placeholders","rawQuery","buildQuery","nextSuggestions","firstTappable","o","PRODUCT_PATH_PREFIX","productFromMatchedItem","item","productsFromCustomFields","report","ProductsController","store","getConfig","isEnabled","report","productsFromCustomFields","query","signal","isCurrent","config","stale","raw","mapped","list","products","err","isAbortError","OPTIONS_GRID_MOBILE_QUERY","ROW_HEIGHT","RESERVED_BAND","computeOptionsGridLayout","count","isMobile","layout","cols","rows","needsOptionsGridMeasurement","planOptionsGrid","rowWidths","gridWidth","fitsTwoColumns","base","optionsGridTemplateColumns","visible","w","measureOptionsGrid","grid","items","probe","item","clone","c","cs","isOptionsGridMobileViewport","OPTIONS_GRID_MOBILE_QUERY","computeDropdownVisibility","inputs","opts","trigger","closeOnBlur","hasContent","focusGate","trimmedEnd","caretAtEnd","dateViewKey","inputs","type","anchor","defaultViewForIso","iso","todayView","y","m","defaultDateView","editingText","existing","parseDate","viewOf","deriveAll","opts","segments","deriveSegments","actionableSuggestions","s","activeSuggestion","placeholderText","clampedFilterBase","effectiveFilterBase","filterQuery","isTypingPlaceholderPrefix","extractFilterQuery","baseOptions","inEditMode","filteredOptions","optionQuery","sourceOptions","sourceType","editingId","paramStillPresent","p","editCaret","editQuery","filterOptions","search","searchMatchesPill","isSearchingOptions","hideNonTappable","o","identifiedEdit","formatSource","activeFormatType","resolveFormatType","editingRange","isCalendarFormat","selectedRangeFor","dateView","dateRangeStart","key","paged","buildDateOptions","pending","countsAsOption","activePillHasNoOptions","editOptions","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","afterParam","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","caretPos","newParam","oldIdx","params","insertAt","result","tryPromoteExactMatch","PLACEHOLDER_TYPE_MS","PLACEHOLDER_WORD_PAUSE_MS","PLACEHOLDER_FADE_OUT_MS","PLACEHOLDER_SWAP_GAP_MS","PLACEHOLDER_LEAVING_ATTR","states","clearTimer","state","motionDisabled","input","show","count","typeNext","justTyped","pause","startTyping","syncPlaceholder","text","next","outgoing","cancelPlaceholderTypewriter","ATTRIBUTION_URL","buildAttributionUrl","base","host","url","getFooterHint","optionHighlighted","isInputEmpty","NOTHING_SELECTED","dateSelectionFor","state","isCalendarFormat","identified","iso","isoEnd","orderedRange","selectedRange","selectedRangeFor","selectedIsoFromText","visibleDateRange","args","dateCellMarks","selectedIso","range","selected","inRange","KEY_ATTR","reconcileList","parent","items","opts","existing","child","key","used","result","i","item","el","renderDateGrid","parent","options","activeIndex","listboxId","view","selection","callbacks","picker","buildPicker","label","monthLabel","prev","next","grid","renderCells","today","isoDate","range","visibleDateRange","cellIso","reconcileList","option","i","buildCell","el","iso","marks","dateCellMarks","isHighlighted","idx","cell","day","cellDay","num","header","month","buildNavButton","weekdays","WEEKDAY_LABELS","direction","glyph","btn","e","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","OPTION_ENTER_STAGGER_MS","OPTION_ENTER_FADE_MS","OPTION_ENTER_RISE_MS","OPTION_ENTER_RISE_PX","OPTION_ENTER_DELAY_VAR","optionEnterDelayMs","index","cols","count","optionsPosition","safeCols","row","rows","optionsEntranceDurationMs","SVG_NS","ALLOWED_ELEMENTS","isDisallowedAttribute","name","value","lower","HARDENED_ATTRIBUTES","PLAIN_PAINT","hardenPaint","el","declarations","scrub","attr","child","childEl","sanitizeOptionIconSvg","markup","doc","root","optionLabel","option","applyGridLayout","grid","count","loading","isMobile","isOptionsGridMobileViewport","measured","needsOptionsGridMeasurement","measureOptionsGrid","plan","planOptionsGrid","el","i","renderSuggestionGrid","parent","options","activeIndex","onSelect","onHighlight","listboxId","groupKey","optionsPosition","showIcons","isNewGroup","renderOptions","cols","applyEntranceDelays","loadingFlag","iconKey","opt","reconcileList","option","_i","buildOptionElement","isHighlighted","idx","OPTION_ENTER_DELAY_VAR","optionEnterDelayMs","item","classes","content","iconHtml","sanitizeOptionIconSvg","icon","text","optionLabel","tag","SCROLL_ARROW_CLASS","SCROLL_ARROW_ATTR","SCROLL_ARROW_VISIBLE_ATTR","SCROLL_ARROW_FADE_MS","SCROLL_ARROW_SCROLL_IDLE_MS","SCROLL_ARROW_BOTTOM_VAR","SCROLL_ARROW_LABEL","AT_BOTTOM_SLACK_PX","ARROW_GAP_PX","PAGE_SCROLL_MS","animateScrollBy","grid","delta","view","onDone","from","raf","hidden","motionDisabled","start","now","step","t","eased","buildScrollArrowIcon","doc","NS","svg","path","createScrollArrowButton","button","columnCount","tracks","el","attachScrollArrow","dropdown","destroyed","lastGroup","settleTimer","idleTimer","setVisible","visible","position","gridTop","band","bottom","evaluate","loading","remaining","update","group","count","wait","optionsEntranceDurationMs","onScroll","onMouseDown","e","onClick","pad","page","resizeObserver","FALLBACK_SKELETON_BAR_WIDTHS","createDropdown","listboxId","dropdown","e","scrollArrows","syncScrollArrow","grid","state","createScrollArrowButton","attachScrollArrow","destroyDropdown","renderDropdown","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","isDate","isCalendarFormat","showsDateGrid","renderSuggestionGrid","renderDateGrid","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","dateSelection","dateSelectionFor","renderDropdown","identifiedParamLabel","i","focused","CHIP_PADDING_X","MAX_TRACKING","chipTracking","textLength","renderEditableContent","args","input","segments","newParamId","editingParamId","placeholderText","isFocused","showChipIcons","empty","syncPlaceholder","segKey","s","lastSegKey","lastNewParamId","lastEditingParamId","savedOffset","getCursorOffset","doc","documentOf","frag","newLength","seg","strong","isNew","isEditing","classes","iconHtml","sanitizeOptionIconSvg","icon","identifiedClasses","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","getSelectionFor","anchor","paramId","caretEscapedChip","e","getCursorOffset","documentOf","enclosing","editState","editingId","inputEvent","t","replacement","text","doc","range","node","state","buildSubmitResult","caretInside","ro","updateDOM","refs","pillPlacement","setActivePill","selectOption","store","activeDescendant","canSubmit","previousParamId","justSelected","renderEditableContent","inlineLoading","renderPills","clearPills","setCursorOffset","dropdownPill","identifiedParamLabel","dropdownActivePill","dateSelection","dateSelectionFor","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","styledRoots","sheets","isDocument","root","documentOfRoot","constructableSheet","doc","cached","Ctor","sheet","STYLES","appendStyleElement","host","style","injectStyles","target","adopted","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","OptionSourceController","mode","query","FetchController","signal","isCurrent","active","matched","o","report","state","minDiff","KeyboardController","option","next","prev","valid","invalid","reconcileIdentifiedParams","editing","p","pos","reissued","seg","pending","dateViewKey","span","anchor","rebased","rebaseAnchor","isTrailingCovered","injectStyles","rootOf","wasFocused","unsub","cancelPlaceholderTypewriter","destroyDropdown","text","params","index","endOffset","start","paramStart","paramEnd","newText","removed","removeChipSpan","s","deleteStart","previousGraphemeBoundary","refs","setCursorOffset","setCursor","paramId","param","named","resolveFormatType","isCalendarFormat","range","parseLooseDateRange","format","tail","resolveIdentifiedDate","replacement","product","value","delta","addMonths","placeholders","actionable","skipped","remaining","alreadySkipped","focused","listener","key","buildSubmitResult","event","callback","previousProducts","previousShowProducts","previousOverrides","iso","cellIso","buildRangeOption","computeSelectionPatch","buildQuery","consumed","sg","queryData","source","sendTelemetry","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","before","after","optionText","caretPos","newParam","identifiedParamLabel","tryPromoteExactMatch"]}
|