@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/dist/index.d.ts 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 };