@magicx-eng/ai-autocomplete-vanilla 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +99 -3
- package/dist/index.d.mts +242 -14
- package/dist/index.d.ts +242 -14
- package/dist/index.js +145 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +145 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ A framework-agnostic vanilla JS/TypeScript library that provides a guided AI-pow
|
|
|
14
14
|
- **Access token auth** — short-lived tokens with automatic refresh, single-flight deduplication, and 401 retry
|
|
15
15
|
- **Keyboard navigation** — arrow keys, enter to submit, tab to autocomplete, backspace to un-bold the last completed param
|
|
16
16
|
- **Client-side filtering** — instant substring filtering on every keystroke
|
|
17
|
+
- **Datepicker** — date parameters are answered with a calendar instead of an option list. Click a day or navigate with the arrow keys; the date is committed as `MONTH DAY YEAR` (e.g. `March 23 2026`). Tapping a committed date re-opens the calendar on the month it holds.
|
|
17
18
|
- **Option overrides** — inject or dynamically generate client-side options per suggestion type
|
|
18
19
|
- **Product strip (opt-in)** — plug in any platform's product search and the dropdown renders a horizontal row of product cards below the options; the SDK owns the UI, your integration owns only `fetch` and `transform`
|
|
19
20
|
- **Controlled & uncontrolled** — works out of the box or integrates with external state
|
|
@@ -76,7 +77,6 @@ const ac = new AIAutocomplete(container, {
|
|
|
76
77
|
columns: 2,
|
|
77
78
|
maskCompletedText: false, // when true, omits completed params' text from API requests (PII masking)
|
|
78
79
|
additionalContext: { tier: "gold" }, // optional user context, to personalize suggestions and options
|
|
79
|
-
generateStartingStateOptions: false, // true = each time the query is empty, generate fresh initial suggestions (personalized when additionalContext is set) instead of the fixed defaults
|
|
80
80
|
|
|
81
81
|
// Appearance
|
|
82
82
|
mode: "auto", // "light" | "dark" | "auto"
|
|
@@ -261,6 +261,72 @@ new AIAutocomplete(container, {
|
|
|
261
261
|
new AIAutocomplete(container, { ..., submitButton: null });
|
|
262
262
|
```
|
|
263
263
|
|
|
264
|
+
### Datepicker
|
|
265
|
+
|
|
266
|
+
When the next parameter is a date, the dropdown shows a calendar instead of a
|
|
267
|
+
list of options. A parameter counts as a date when its name says so, or when at
|
|
268
|
+
least three of the options it offers are themselves written as dates — so a
|
|
269
|
+
parameter called `when` or `arrival` still gets a calendar. Options written as
|
|
270
|
+
relative phrases (`today`, `next week`) are not read as dates; a parameter
|
|
271
|
+
offering those needs a name that says date.
|
|
272
|
+
|
|
273
|
+
Nothing is required to switch it on — a date parameter renders this way
|
|
274
|
+
automatically, and every other parameter is unaffected. Tier 1 and Tier 2 render
|
|
275
|
+
the calendar for you; in Tier 3 the same data reaches your own renderer (see
|
|
276
|
+
[Tier 3](#tier-3-headless)).
|
|
277
|
+
|
|
278
|
+
**What the user can do**
|
|
279
|
+
|
|
280
|
+
| Input | Result |
|
|
281
|
+
|---|---|
|
|
282
|
+
| Click a day | Commits that date |
|
|
283
|
+
| <kbd>↓</kbd> | Moves into the calendar, starting on today |
|
|
284
|
+
| <kbd>←</kbd> / <kbd>→</kbd> | Previous / next day, crossing week boundaries |
|
|
285
|
+
| <kbd>↑</kbd> / <kbd>↓</kbd> | Previous / next week. Past either end of the month, focus returns to the input |
|
|
286
|
+
| <kbd>Enter</kbd> | Commits the highlighted day |
|
|
287
|
+
| Month arrows | Page the calendar. Paging never commits anything |
|
|
288
|
+
| <kbd>→</kbd> at the end of the input | Skips the parameter, same as any other pill |
|
|
289
|
+
|
|
290
|
+
**The committed value** is always `MONTH DAY YEAR` in English — `March 23 2026`
|
|
291
|
+
— regardless of the visitor's locale, so the value you receive has one shape
|
|
292
|
+
everywhere. It arrives as an ordinary completed parameter: bold in the input,
|
|
293
|
+
and present in `completed_params` on submit like any other answer.
|
|
294
|
+
|
|
295
|
+
**Re-editing** a committed date re-opens the calendar on that date's month with
|
|
296
|
+
the day marked, so changing an answer takes one click.
|
|
297
|
+
|
|
298
|
+
**Reading the value back.** The date arrives as text, so if you need a `Date`
|
|
299
|
+
object, `parseDate` is exported for it:
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
import { parseDate } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
303
|
+
|
|
304
|
+
const due = parseDate("March 23 2026"); // Date, or null if the text isn't one of ours
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`parseLooseDate` is exported too, for the looser shapes a person types into a
|
|
308
|
+
query — it reads `september 5th`, `5th September`, `Sept 5` and `2026-09-05`,
|
|
309
|
+
and answers `null` for anything ambiguous rather than guessing.
|
|
310
|
+
|
|
311
|
+
**Dates the user writes themselves.** When someone types a date into their query
|
|
312
|
+
— "fly to vegas on september 5th" — the parameter it answers is recognised in
|
|
313
|
+
place and the date becomes tappable. Tapping it opens the calendar on that
|
|
314
|
+
month with the day already marked, so confirming or changing it takes one tap,
|
|
315
|
+
and the picked date replaces their words with the canonical form.
|
|
316
|
+
|
|
317
|
+
The written date is read where it can be: `september 5th`, `5th September`,
|
|
318
|
+
`Sept 5`, `September 5, 2026` and `2026-09-05` all resolve, in any of those
|
|
319
|
+
orders and with or without the year (a date with no year means its next
|
|
320
|
+
occurrence). Two cases deliberately don't pre-select — a numeric date like
|
|
321
|
+
`09/05`, which is September 5th in the US and May 9th elsewhere with nothing to
|
|
322
|
+
say which, and phrases like `next friday`. Those still open the calendar, just
|
|
323
|
+
on the current month with nothing marked, so the user picks rather than being
|
|
324
|
+
shown a guess that might be wrong.
|
|
325
|
+
|
|
326
|
+
**Typing** while the calendar is open does not filter it. The text is treated as
|
|
327
|
+
a new query, so suggestions refresh as the user types — useful when someone
|
|
328
|
+
would rather describe what they want than pick a day.
|
|
329
|
+
|
|
264
330
|
---
|
|
265
331
|
|
|
266
332
|
## Tier 2: Dropdown Only
|
|
@@ -345,6 +411,8 @@ unsub();
|
|
|
345
411
|
|
|
346
412
|
> **Custom (rich-text) editors:** the wiring above assumes `myInput` is a `<textarea>`/`<input>`, whose caret the library reads directly. If you're driving a contentEditable or rich-text editor instead, also call `ac.handleCaretMove(offset)` on selection changes (with the caret as a plain-text offset) so arrow keys can move from the input into the dropdown.
|
|
347
413
|
|
|
414
|
+
|
|
415
|
+
> **Datepicker in a custom UI.** For a date parameter, `state.activeFormatType` is `"date"` and `state.filteredOptions` holds that month's day cells. Each cell's `text` is the date it commits (`"March 23 2026"`), so rendering them as a plain list already works — `selectOption(cell)` behaves exactly as it does for an option. To draw an actual calendar, read `state.dateView` for the month on show, `cellDay(cell)` for the number to paint, and call `showPreviousMonth()` / `showNextMonth()` to page. Cells that pad the start and end of the month have `is_tappable: false`.
|
|
348
416
|
### State shape (`CoreState`)
|
|
349
417
|
|
|
350
418
|
| Field | Type | Description |
|
|
@@ -353,7 +421,9 @@ unsub();
|
|
|
353
421
|
| `completedParams` | `CompletedParamState[]` | Filled parameters |
|
|
354
422
|
| `suggestions` | `Suggestion[]` | All suggestions from server (including placeholder type) |
|
|
355
423
|
| `actionableSuggestions` | `Suggestion[]` | Non-placeholder suggestions (the pills) |
|
|
356
|
-
| `filteredOptions` | `SuggestionOption[]` | Options for the active suggestion, filtered by current query |
|
|
424
|
+
| `filteredOptions` | `SuggestionOption[]` | Options for the active suggestion, filtered by current query. For a date parameter these are the visible month's day cells instead — each cell's `text` is the date it commits (`"March 23 2026"`), and cells padding the start/end of the month have `is_tappable: false`. |
|
|
425
|
+
| `activeFormatType` | `"options" \| "date"` | How the active parameter is answered: pick an option, or pick a date. `"date"` shows a calendar instead of the option list. Typing does not filter it — the text is treated as a new query. |
|
|
426
|
+
| `dateView` | `DateMonthView \| null` | The month the calendar is showing (`{ year, month }`, 0-based month). Null unless `activeFormatType` is `"date"`. |
|
|
357
427
|
| `segments` | `Segment[]` | Input text split into typed text vs completed params — completed segments render as bold `<strong>` runs inside the editor |
|
|
358
428
|
| `placeholderText` | `string` | Placeholder text from server suggestions (joined `placeholder`-type suggestion texts) |
|
|
359
429
|
| `activeDropdownIndex` | `number` | Highlighted option index (-1 = none) |
|
|
@@ -371,6 +441,7 @@ unsub();
|
|
|
371
441
|
| Method | Description |
|
|
372
442
|
|---|---|
|
|
373
443
|
| `handleTextChange(value)` | Call when user types. Handles capitalization, param reconciliation, filter updates. |
|
|
444
|
+
| `showPreviousMonth()` / `showNextMonth()` | Page the calendar a month at a time; paging never commits anything. Tier 1 and Tier 2 wire the built-in month arrows to these, so you only need them for a hand-rolled calendar. No-ops unless the active parameter is a date. Moving on to a different parameter resets the calendar to the current month. |
|
|
374
445
|
| `handleKeyDown(event)` | Forward keyboard events. Handles arrow nav, Enter, Tab, Escape. |
|
|
375
446
|
| `setFocused(focused)` | Notify the library that the input has focus. Required for `dropdownTrigger: "auto"` in Tier 2/3 — the dropdown only opens while focused. |
|
|
376
447
|
| `handleCaretMove(offset)` | Report the caret position (plain-text offset). For a **custom editor** that isn't a `<textarea>`/`<input>` (e.g. contentEditable / rich-text), this is what lets arrow keys move into the dropdown. The library reads a `<textarea>`/`<input>`'s caret directly, so you only need this for custom editors. |
|
|
@@ -528,6 +599,13 @@ Override these on the container element. All built-in defaults use `:where()` (z
|
|
|
528
599
|
| `--aia-product-price-font-size` | `11px` | `11px` | Product price font size. |
|
|
529
600
|
| `--aia-product-vendor-font-size` | `10px` | `10px` | Product vendor font size. |
|
|
530
601
|
| `--aia-products-label-font-size` | `11px` | `11px` | Section label font size. |
|
|
602
|
+
| `--aia-date-cell-size` | `36px` | `36px` | Size of a day's square — the box that carries the highlight, the today ring and the selected fill. |
|
|
603
|
+
| `--aia-date-row-height` | `40px` | `40px` | Height of one week row. Lower it to fit a 6-week month in a shorter dropdown. |
|
|
604
|
+
| `--aia-date-cell-font-size` | `14px` | `14px` | Day-number font size. |
|
|
605
|
+
| `--aia-date-month-font-size` | `14px` | `14px` | Font size of the "March 2026" header. |
|
|
606
|
+
| `--aia-date-weekday-font-size` | `11px` | `11px` | Font size of the S/M/T/W/T/F/S column letters. |
|
|
607
|
+
| `--aia-date-today-ring` | `--aia-option-color` | `--aia-option-color` | Outline drawn around today's date. |
|
|
608
|
+
| `--aia-date-selected-bg` | white at 12% | white at 12% | Fill behind the date a re-edited parameter already holds. |
|
|
531
609
|
| `--aia-skeleton-bg` | `rgba(189, 189, 189, 0.25)` | `#1a1b1d` | Fill color for the loading skeleton bars and the masked text in cached pills/options. |
|
|
532
610
|
|
|
533
611
|
### Per-mode Overrides
|
|
@@ -564,6 +642,11 @@ For styling beyond the CSS variables, target these stable `data-aia-*` attribute
|
|
|
564
642
|
| `[data-aia-skip]` | The pill bar's trailing "skip" button. Tune via `--aia-skip-font-size` / `--aia-skip-color` / `--aia-skip-color-hover` / `--aia-skip-hover-bg` |
|
|
565
643
|
| `[data-aia-option]` | Each suggestion option |
|
|
566
644
|
| `[data-aia-dropdown]` | The dropdown root (listbox). Carries `data-aia-has-products` while the product strip has cards. |
|
|
645
|
+
| `[data-aia-datepicker]` | The calendar, rendered in place of the option list for a date parameter |
|
|
646
|
+
| `[data-aia-date-month]` | The "March 2026" header label |
|
|
647
|
+
| `[data-aia-date-prev]` / `[data-aia-date-next]` | The month arrows |
|
|
648
|
+
| `[data-aia-date-grid]` | The 7-column grid of day cells |
|
|
649
|
+
| `[data-aia-date-cell]` | Each day cell. Also carries `[data-aia-option]`, so option-level styling applies to both bodies |
|
|
567
650
|
| `[data-aia-products]` | Product strip section (label + row) |
|
|
568
651
|
| `[data-aia-products-row]` | The horizontally scrolling row of cards |
|
|
569
652
|
| `[data-aia-product]` | Each product card |
|
|
@@ -591,7 +674,20 @@ Every `/api/suggest` request carries a `meta.session_id` UUID. A session runs fr
|
|
|
591
674
|
- **Tier 1 (`renderMode: "full"`)** — the SDK auto-resets after both Enter key and built-in submit-button clicks. Your `onSubmit` runs first, then the SDK clears the input and starts a new session.
|
|
592
675
|
- **Tier 2/3 (`renderMode: "dropdown" | "headless"`)** — you own the input and submit flow, so you must call `ac.reset()` yourself from your `onSubmit` handler (or from your custom submit button after firing `onSubmit`).
|
|
593
676
|
|
|
594
|
-
> **Why it matters:**
|
|
677
|
+
> **Why it matters:** suggestions get sharper as a query develops. Each one
|
|
678
|
+
> takes account of what the user has already answered, so the parameters offered
|
|
679
|
+
> late in a query are shaped by the choices made early in it — that is what makes
|
|
680
|
+
> the experience feel guided rather than like a static list. `session_id` is what
|
|
681
|
+
> ties those requests together into one query.
|
|
682
|
+
>
|
|
683
|
+
> So `reset()` is not bookkeeping: it is how you say "that query is finished".
|
|
684
|
+
> Skip it and the next query is treated as a continuation of the last one, and
|
|
685
|
+
> its suggestions keep being shaped by answers the user has already moved on
|
|
686
|
+
> from — the failure is quiet, and shows up as steadily less relevant options
|
|
687
|
+
> rather than as an error.
|
|
688
|
+
|
|
689
|
+
The id is a plain UUID. Log it next to your own request logs if you want to
|
|
690
|
+
correlate a user's report with the query that produced it.
|
|
595
691
|
|
|
596
692
|
```ts
|
|
597
693
|
// Tier 2/3 — call reset() yourself
|
package/dist/index.d.mts
CHANGED
|
@@ -47,12 +47,6 @@ interface AutocompleteRequest {
|
|
|
47
47
|
recently_suggested?: RecentlySuggested[];
|
|
48
48
|
contact_account_count?: number;
|
|
49
49
|
additional_context?: Record<string, unknown>;
|
|
50
|
-
/**
|
|
51
|
-
* When true, an empty `raw_query`'s initial suggestions are generated
|
|
52
|
-
* fresh instead of the fixed defaults. Absent or false keeps the default
|
|
53
|
-
* behavior.
|
|
54
|
-
*/
|
|
55
|
-
generate_starting_state_options?: boolean;
|
|
56
50
|
};
|
|
57
51
|
meta: {
|
|
58
52
|
request_id: string;
|
|
@@ -220,6 +214,91 @@ interface AutocompleteResult {
|
|
|
220
214
|
completed_params: CompletedParam[];
|
|
221
215
|
}
|
|
222
216
|
|
|
217
|
+
/** A calendar month on screen. `month` is 0-based, matching `Date#getMonth`. */
|
|
218
|
+
interface DateMonthView {
|
|
219
|
+
year: number;
|
|
220
|
+
month: number;
|
|
221
|
+
}
|
|
222
|
+
/** Sunday-first, matching `Date#getDay`. */
|
|
223
|
+
declare const WEEKDAY_LABELS: string[];
|
|
224
|
+
/**
|
|
225
|
+
* `YYYY-MM-DD` from the date's LOCAL components.
|
|
226
|
+
*
|
|
227
|
+
* Not `toISOString()`, which converts to UTC first: for any user behind UTC
|
|
228
|
+
* that shifts the calendar date back a day, so clicking "March 23" west of
|
|
229
|
+
* Greenwich would produce `2026-03-22`.
|
|
230
|
+
*/
|
|
231
|
+
declare function isoDate(d: Date): string;
|
|
232
|
+
/** The committed form of a date: "March 23 2026". */
|
|
233
|
+
declare function formatDate(d: Date): string;
|
|
234
|
+
/**
|
|
235
|
+
* Inverse of {@link formatDate}, for re-opening the calendar on the month a
|
|
236
|
+
* completed date param already holds. Returns null for anything else — the
|
|
237
|
+
* caller falls back to the current month.
|
|
238
|
+
*/
|
|
239
|
+
declare function parseDate(text: string | undefined | null): Date | null;
|
|
240
|
+
/** "March 2026" — the calendar header. */
|
|
241
|
+
declare function monthLabel(view: DateMonthView): string;
|
|
242
|
+
/** Step the view by whole months, rolling the year over at either end. */
|
|
243
|
+
declare function addMonths(view: DateMonthView, delta: number): DateMonthView;
|
|
244
|
+
/**
|
|
245
|
+
* A month laid out as `SuggestionOption`s — leading blanks, one cell per day,
|
|
246
|
+
* trailing blanks to complete the last week. Always a whole number of 7-cell
|
|
247
|
+
* rows.
|
|
248
|
+
*
|
|
249
|
+
* Modelling calendar cells as options is what makes the datepicker cheap: they
|
|
250
|
+
* flow through `filteredOptions`, so the existing keyboard controller, the
|
|
251
|
+
* `${listboxId}-option-${i}` id scheme, `aria-activedescendant` and — above all
|
|
252
|
+
* — `selectOption`'s entire commit path work on them unchanged. At 7 columns
|
|
253
|
+
* the grid's own column-major traversal already *is* calendar navigation
|
|
254
|
+
* (→ = +1 day, ↓ = +7 days).
|
|
255
|
+
*
|
|
256
|
+
* Each day cell's `text` is the committed value ("March 23 2026"), NOT its
|
|
257
|
+
* label: `computeSelectionPatch` commits `option.text` verbatim, so the value
|
|
258
|
+
* has to live there. The renderer reads `metadata.aiaDay` for the "23" it
|
|
259
|
+
* paints. Padding cells are non-tappable, which is what stops arrow navigation
|
|
260
|
+
* from landing on them.
|
|
261
|
+
*/
|
|
262
|
+
declare function buildDateOptions(view: DateMonthView): SuggestionOption[];
|
|
263
|
+
/**
|
|
264
|
+
* The `YYYY-MM-DD` a completed date param already holds, so the calendar can
|
|
265
|
+
* mark that cell as the current answer while it is being re-edited. Null for
|
|
266
|
+
* text that isn't one of our dates.
|
|
267
|
+
*/
|
|
268
|
+
declare function selectedIsoFromText(text: string | undefined | null): string | null;
|
|
269
|
+
/** A cell's `YYYY-MM-DD`, or null if it's padding. */
|
|
270
|
+
declare function cellIso(option: SuggestionOption | undefined): string | null;
|
|
271
|
+
/** A cell's day-of-month, or null if it's padding. */
|
|
272
|
+
declare function cellDay(option: SuggestionOption | undefined): number | null;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* How the dropdown lets the user answer a suggestion.
|
|
276
|
+
*
|
|
277
|
+
* `"options"` is every suggestion the server has ever sent: pick one of its
|
|
278
|
+
* options, or type to filter them. `"date"` ignores the options entirely and
|
|
279
|
+
* renders a calendar instead.
|
|
280
|
+
*/
|
|
281
|
+
type FormatType = "options" | "date";
|
|
282
|
+
/**
|
|
283
|
+
* Decide how a suggestion should be answered.
|
|
284
|
+
*
|
|
285
|
+
* The server cannot send a `formatType` yet, so a suggestion whose `type` names
|
|
286
|
+
* a date is detected here by its name and rendered as a calendar with its
|
|
287
|
+
* options ignored.
|
|
288
|
+
*
|
|
289
|
+
* That name check is deliberately the LAST resort. A `formatType` that *is* on
|
|
290
|
+
* the wire always wins, so the day the server starts sending one this function
|
|
291
|
+
* needs a single line deleted — the datepicker itself, and every gate that
|
|
292
|
+
* consults this, keep working untouched.
|
|
293
|
+
*
|
|
294
|
+
* Accepts anything with a `type`, not a `Suggestion`, so re-edit can ask the
|
|
295
|
+
* same question about a completed param via its `suggestionType`.
|
|
296
|
+
*/
|
|
297
|
+
declare function resolveFormatType(source: {
|
|
298
|
+
type: string;
|
|
299
|
+
options?: SuggestionOption[];
|
|
300
|
+
} | undefined | null): FormatType;
|
|
301
|
+
|
|
223
302
|
/**
|
|
224
303
|
* Raw, user/network/internal-driven fields. The store holds *only* these —
|
|
225
304
|
* derived state is recomputed lazily on read. See {@link CoreDerivedState}.
|
|
@@ -292,6 +371,48 @@ interface CoreInputState {
|
|
|
292
371
|
* Set by selectOption / ReEditManager.selectOption, cleared by a timer.
|
|
293
372
|
*/
|
|
294
373
|
inSelectionAnimation: boolean;
|
|
374
|
+
/**
|
|
375
|
+
* Which month the datepicker is showing, when one is showing at all.
|
|
376
|
+
*
|
|
377
|
+
* `key` scopes the view to the pill it was opened for (see `dateViewKey` in
|
|
378
|
+
* `derive/state.ts`). A stale key is ignored rather than cleared, so moving
|
|
379
|
+
* to a different date pill starts at the default month without any of the
|
|
380
|
+
* flows that change the active pill — selection, skip, a landing response,
|
|
381
|
+
* re-edit — having to remember to reset this.
|
|
382
|
+
*/
|
|
383
|
+
dateViewMonth: (DateMonthView & {
|
|
384
|
+
key: string;
|
|
385
|
+
}) | null;
|
|
386
|
+
/**
|
|
387
|
+
* Set while the user is picking a date for a span the server identified as
|
|
388
|
+
* one — "september 5th" in "fly to vegas on september 5th".
|
|
389
|
+
*
|
|
390
|
+
* Held apart from `editingParam` rather than folded into it. An identified
|
|
391
|
+
* span is not a completed param: it has no cached options, it lives in
|
|
392
|
+
* `identifiedParams`, and the re-edit paths that read `editingParam` all
|
|
393
|
+
* assume otherwise. Reusing that field would have meant auditing every one
|
|
394
|
+
* of them for a shape they were never written for.
|
|
395
|
+
*
|
|
396
|
+
* `iso` is the date the span's text was read as, or null when it could not be
|
|
397
|
+
* read with confidence (see `parseLooseDate`). Null costs only the
|
|
398
|
+
* pre-selection — the calendar still opens.
|
|
399
|
+
*/
|
|
400
|
+
editingIdentified: {
|
|
401
|
+
id: string;
|
|
402
|
+
/** The identified param's type, which the committed param inherits. */
|
|
403
|
+
type: string;
|
|
404
|
+
/** Plain-text offsets bounding the span being replaced. */
|
|
405
|
+
anchor: number;
|
|
406
|
+
tail: number;
|
|
407
|
+
/**
|
|
408
|
+
* The span's text when the picker opened. Re-checked against those offsets
|
|
409
|
+
* before anything is spliced — if the input changed underneath, they no
|
|
410
|
+
* longer bound the span and writing at them would corrupt an unrelated
|
|
411
|
+
* part of the query.
|
|
412
|
+
*/
|
|
413
|
+
text: string;
|
|
414
|
+
iso: string | null;
|
|
415
|
+
} | null;
|
|
295
416
|
}
|
|
296
417
|
/**
|
|
297
418
|
* Derived fields recomputed from {@link CoreInputState} + {@link CoreOptions}.
|
|
@@ -301,7 +422,23 @@ interface CoreInputState {
|
|
|
301
422
|
interface CoreDerivedState {
|
|
302
423
|
segments: Segment[];
|
|
303
424
|
actionableSuggestions: Suggestion[];
|
|
425
|
+
/**
|
|
426
|
+
* What the dropdown renders. Options for the active pill, filtered by what
|
|
427
|
+
* the user typed — EXCEPT when `activeFormatType` is `"date"`, where these
|
|
428
|
+
* are the visible month's calendar cells and the server's own options for
|
|
429
|
+
* that suggestion are ignored. See {@link buildDateOptions}.
|
|
430
|
+
*/
|
|
304
431
|
filteredOptions: SuggestionOption[];
|
|
432
|
+
/**
|
|
433
|
+
* How the active pill is answered: pick an option, or pick a date. Anything
|
|
434
|
+
* that reasons about options has to consult this — the server still *sends*
|
|
435
|
+
* options for a date suggestion, so code that only looks at `filteredOptions`
|
|
436
|
+
* would keep acting on options the user can't see (see the filter-zone and
|
|
437
|
+
* exact-match gates in `fetchController`).
|
|
438
|
+
*/
|
|
439
|
+
activeFormatType: FormatType;
|
|
440
|
+
/** The month the datepicker is showing. Null unless `activeFormatType` is `"date"`. */
|
|
441
|
+
dateView: DateMonthView | null;
|
|
305
442
|
placeholderText: string;
|
|
306
443
|
isDropdownOpen: boolean;
|
|
307
444
|
/**
|
|
@@ -340,13 +477,6 @@ interface CoreOptions {
|
|
|
340
477
|
* parameters and options to them.
|
|
341
478
|
*/
|
|
342
479
|
additionalContext?: Record<string, unknown>;
|
|
343
|
-
/**
|
|
344
|
-
* When true, each time the query is empty, the initial suggestions are
|
|
345
|
-
* generated fresh — personalized to `additionalContext` when set —
|
|
346
|
-
* instead of the fixed defaults. The initial placeholder text itself is
|
|
347
|
-
* unchanged. Default: false.
|
|
348
|
-
*/
|
|
349
|
-
generateStartingStateOptions?: boolean;
|
|
350
480
|
columns?: number;
|
|
351
481
|
/** Where pills render in "full" mode. Default: "dropdown". Ignored in "dropdown" mode (always in dropdown). */
|
|
352
482
|
pillPlacement?: "inline" | "dropdown" | "hidden";
|
|
@@ -532,6 +662,28 @@ declare class AIAutocomplete {
|
|
|
532
662
|
*/
|
|
533
663
|
private scheduleSetCursor;
|
|
534
664
|
clearNewParamId(): void;
|
|
665
|
+
/**
|
|
666
|
+
* Open the calendar for a span the server identified as a date.
|
|
667
|
+
*
|
|
668
|
+
* Returns false for anything else — an identified span whose type isn't a
|
|
669
|
+
* date stays inert, exactly as every identified span did before.
|
|
670
|
+
*
|
|
671
|
+
* The span's own text is read for a starting month (`parseLooseDate`), but
|
|
672
|
+
* failing to read it is not a failure: the calendar opens either way, and
|
|
673
|
+
* whatever the user picks replaces the span with the SDK's canonical form. So
|
|
674
|
+
* an unparseable "sometime next week" still ends up a clean answer.
|
|
675
|
+
*/
|
|
676
|
+
startEditingIdentified(paramId: string): boolean;
|
|
677
|
+
/** Close the calendar opened for an identified span, leaving its text untouched. */
|
|
678
|
+
exitEditingIdentified(): void;
|
|
679
|
+
/**
|
|
680
|
+
* Open whatever the chip at `paramId` is answered with.
|
|
681
|
+
*
|
|
682
|
+
* Takes an id from either array so the DOM layers stay dumb: they report
|
|
683
|
+
* which chip was tapped and the core decides what that means. A completed
|
|
684
|
+
* param re-opens its cached options; a span the server identified as a date
|
|
685
|
+
* opens the calendar; anything else is inert, as it has always been.
|
|
686
|
+
*/
|
|
535
687
|
startEditingParam(paramId: string): void;
|
|
536
688
|
replaceEditingRange(replacement: string): boolean;
|
|
537
689
|
exitEditMode(): void;
|
|
@@ -548,6 +700,17 @@ declare class AIAutocomplete {
|
|
|
548
700
|
*/
|
|
549
701
|
selectProduct(product: Product): void;
|
|
550
702
|
handleTextChange(value: string): void;
|
|
703
|
+
/** Page the datepicker back one month. No-op unless a date pill is active. */
|
|
704
|
+
showPreviousMonth(): void;
|
|
705
|
+
/** Page the datepicker forward one month. No-op unless a date pill is active. */
|
|
706
|
+
showNextMonth(): void;
|
|
707
|
+
/**
|
|
708
|
+
* Move the visible month, stamping it with the current pill's key so the
|
|
709
|
+
* derive layer keeps honouring it. Once the active pill changes the key stops
|
|
710
|
+
* matching and the stored view is ignored — a new date pill therefore opens on
|
|
711
|
+
* its own default month with nothing having to clear this.
|
|
712
|
+
*/
|
|
713
|
+
private pageDateView;
|
|
551
714
|
/**
|
|
552
715
|
* Skip the currently active pill (always index 0 of the actionable
|
|
553
716
|
* suggestions) and promote the next pill to active. Invoked by ArrowRight at
|
|
@@ -660,9 +823,74 @@ declare class AIAutocomplete {
|
|
|
660
823
|
* lands; doing it instantly here means bold styling appears as soon as the
|
|
661
824
|
* option is fully typed, without waiting 100–300ms for the round-trip.
|
|
662
825
|
*/
|
|
826
|
+
/**
|
|
827
|
+
* Replace an identified date span with the day the user picked.
|
|
828
|
+
*
|
|
829
|
+
* The span becomes an ordinary completed param, so from here on it is
|
|
830
|
+
* indistinguishable from a date answered through a pill — same bold
|
|
831
|
+
* rendering, same `{{TYPE_N}}` token in `raw_query`, same `completed_params`
|
|
832
|
+
* entry. The identified param it came from is dropped: its text no longer
|
|
833
|
+
* exists in the input, and leaving it would have the reconciler drop it a
|
|
834
|
+
* beat later anyway.
|
|
835
|
+
*/
|
|
836
|
+
private commitIdentifiedDate;
|
|
663
837
|
private maybePromoteExactMatch;
|
|
664
838
|
}
|
|
665
839
|
|
|
840
|
+
/**
|
|
841
|
+
* Best-effort parse of a date the user typed in their own words.
|
|
842
|
+
*
|
|
843
|
+
* The server identifies a span of the query as a date and returns the literal
|
|
844
|
+
* text the user wrote — not a normalized value — so "september 5th", "5 Sep"
|
|
845
|
+
* and "09/05" all arrive as-is. This turns what it can into a `YYYY-MM-DD` so
|
|
846
|
+
* the calendar can open on the right month with that day already marked.
|
|
847
|
+
*
|
|
848
|
+
* **Returning null is a normal outcome, not a failure.** It only costs the
|
|
849
|
+
* pre-selection: the calendar still opens, on the current month with nothing
|
|
850
|
+
* marked, and whatever the user picks replaces the span with the SDK's own
|
|
851
|
+
* canonical text either way. So the committed answer is correct whether or not
|
|
852
|
+
* the original text could be read.
|
|
853
|
+
*
|
|
854
|
+
* Deliberately conservative — it answers null rather than guess, because a
|
|
855
|
+
* wrong date sitting pre-selected is worse than no pre-selection. A user who
|
|
856
|
+
* doesn't notice it commits the wrong answer; an empty calendar just asks them
|
|
857
|
+
* to pick.
|
|
858
|
+
*/
|
|
859
|
+
interface LooseDateOptions {
|
|
860
|
+
/** Reference point for resolving a date written without a year. Defaults to now. */
|
|
861
|
+
today?: Date;
|
|
862
|
+
}
|
|
863
|
+
/**
|
|
864
|
+
* `YYYY-MM-DD` for a date the user typed however they liked, or null when it
|
|
865
|
+
* can't be read with confidence.
|
|
866
|
+
*
|
|
867
|
+
* Handles, in order: ISO; a month name with a day either side of it, with or
|
|
868
|
+
* without an ordinal suffix or a year; and a numeric pair where only one
|
|
869
|
+
* reading is possible (`13/05` can only be day-first, since 13 is not a month).
|
|
870
|
+
*
|
|
871
|
+
* Deliberately NOT handled:
|
|
872
|
+
* - A numeric pair where both numbers are 12 or under. `09/05` is September
|
|
873
|
+
* 5th to an American and May 9th to everyone else, and nothing in the
|
|
874
|
+
* response says which — so it answers null rather than pick a side.
|
|
875
|
+
* - Relative phrases ("tomorrow", "next friday"). Readable in principle, but
|
|
876
|
+
* they depend on a reference the user may not share with the server, and
|
|
877
|
+
* getting them subtly wrong is the expensive kind of wrong.
|
|
878
|
+
*/
|
|
879
|
+
declare function parseLooseDate(text: string | undefined | null, opts?: LooseDateOptions): string | null;
|
|
880
|
+
/**
|
|
881
|
+
* The `YYYY-MM-DD` for an identified date param.
|
|
882
|
+
*
|
|
883
|
+
* Reads a normalized value off the param first, if one is ever there. The
|
|
884
|
+
* server sends only the user's literal text today, so in practice this always
|
|
885
|
+
* falls through to {@link parseLooseDate} — but normalizing server-side is the
|
|
886
|
+
* real fix for the ambiguous cases the parser refuses, and when it lands this
|
|
887
|
+
* is the one place that has to change.
|
|
888
|
+
*/
|
|
889
|
+
declare function resolveIdentifiedDate(param: {
|
|
890
|
+
text: string;
|
|
891
|
+
isoDate?: unknown;
|
|
892
|
+
}, opts?: LooseDateOptions): string | null;
|
|
893
|
+
|
|
666
894
|
/**
|
|
667
895
|
* Rows/columns policy for the dropdown's options grid.
|
|
668
896
|
*
|
|
@@ -864,4 +1092,4 @@ declare function withSkippedParams(completed: CompletedParam[], skipped: Skipped
|
|
|
864
1092
|
*/
|
|
865
1093
|
declare function buildSubmitResult(text: string, completedParams: CompletedParamState[], skippedParams?: SkippedParamState[]): AutocompleteResult;
|
|
866
1094
|
|
|
867
|
-
export { AIAutocomplete, type APIConfig, type APIKeyConfig, ATTRIBUTION_URL, type AccessTokenConfig, type AccessTokenResult, type AppearanceMode, type AutocompleteRequest, type AutocompleteResponse, type AutocompleteResult, type CompletedParam, type CompletedParamState, type CoreOptions, type CoreState, type IdentifiedParam, type IdentifiedParamState, type InputItem, ModeController, OPTIONS_GRID_MOBILE_QUERY, type OptionOverrides, type OptionsGridLayout, type Product, type ProductsConfig, type RecentlySuggested, type RenderMode, SKIPPED_PARAM_TEXT, type Segment, type SkippedParamState, type Store, type Suggestion, type SuggestionOption, type TaskKind, buildAttributionUrl, buildQuery, buildSubmitResult, computeOptionsGridLayout, createStore, cursorIsAtEnd, extractPlainText, getCursorOffset, getFooterHint, isOptionsGridMobileViewport, optionsGridTemplateColumns, plainTextLength, previousGraphemeBoundary, renderEditableContent, setCursorOffset, withSkippedParams };
|
|
1095
|
+
export { AIAutocomplete, type APIConfig, type APIKeyConfig, ATTRIBUTION_URL, type AccessTokenConfig, type AccessTokenResult, type AppearanceMode, type AutocompleteRequest, type AutocompleteResponse, type AutocompleteResult, type CompletedParam, type CompletedParamState, type CoreOptions, type CoreState, type DateMonthView, type FormatType, type IdentifiedParam, type IdentifiedParamState, type InputItem, type LooseDateOptions, ModeController, OPTIONS_GRID_MOBILE_QUERY, type OptionOverrides, type OptionsGridLayout, type Product, type ProductsConfig, type RecentlySuggested, type RenderMode, SKIPPED_PARAM_TEXT, type Segment, type SkippedParamState, type Store, type Suggestion, type SuggestionOption, type TaskKind, WEEKDAY_LABELS, addMonths, buildAttributionUrl, buildDateOptions, buildQuery, buildSubmitResult, cellDay, cellIso, computeOptionsGridLayout, createStore, cursorIsAtEnd, extractPlainText, formatDate, getCursorOffset, getFooterHint, isOptionsGridMobileViewport, isoDate, monthLabel, optionsGridTemplateColumns, parseDate, parseLooseDate, plainTextLength, previousGraphemeBoundary, renderEditableContent, resolveFormatType, resolveIdentifiedDate, selectedIsoFromText, setCursorOffset, withSkippedParams };
|