@magicx-eng/ai-autocomplete-vanilla 0.13.1 → 0.15.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 +150 -8
- package/dist/index.d.mts +497 -5
- package/dist/index.d.ts +497 -5
- package/dist/index.js +307 -231
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +307 -231
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -214,6 +214,118 @@ interface AutocompleteResult {
|
|
|
214
214
|
completed_params: CompletedParam[];
|
|
215
215
|
}
|
|
216
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
|
+
/**
|
|
233
|
+
* The committed form of a date — written the way a person would, since the
|
|
234
|
+
* text lands verbatim in the user's own sentence:
|
|
235
|
+
*
|
|
236
|
+
* - today through six days out: the weekday alone, "Tuesday". The window
|
|
237
|
+
* stops before day seven so every weekday name still means exactly one
|
|
238
|
+
* date when read back.
|
|
239
|
+
* - further out, same year: "March 23" — the year restates what "this year"
|
|
240
|
+
* already says.
|
|
241
|
+
* - a different year: "March 23 2027".
|
|
242
|
+
*/
|
|
243
|
+
declare function formatDate(d: Date): string;
|
|
244
|
+
/**
|
|
245
|
+
* Inverse of {@link formatDate}, for re-opening the calendar on the month a
|
|
246
|
+
* completed date param already holds. Reads all three committed shapes — a
|
|
247
|
+
* weekday name resolves to its one date in the next seven days, and a
|
|
248
|
+
* yearless month-day to the current year, mirroring how they were written.
|
|
249
|
+
* Returns null for anything else — the caller falls back to the current
|
|
250
|
+
* month.
|
|
251
|
+
*
|
|
252
|
+
* Deliberately RELATIVE, like the sentence it reads: the committed text is
|
|
253
|
+
* the single source of truth, and "Schedule a campaign Tuesday" means the
|
|
254
|
+
* next Tuesday to everyone who reads it — the user re-reading their own
|
|
255
|
+
* sentence, this parser, and the server parsing the submitted query alike.
|
|
256
|
+
* So once the originally-picked Tuesday has gone by, re-editing marks the
|
|
257
|
+
* date the sentence NOW names, a week later — not the stale pick. Anchoring
|
|
258
|
+
* to the pick would make the calendar disagree with the sentence above it
|
|
259
|
+
* (and with what the server will act on), which is the worse wrong. Pinned
|
|
260
|
+
* by the clock-advance test in datepicker.test.ts.
|
|
261
|
+
*/
|
|
262
|
+
declare function parseDate(text: string | undefined | null): Date | null;
|
|
263
|
+
/** "March 2026" — the calendar header. */
|
|
264
|
+
declare function monthLabel(view: DateMonthView): string;
|
|
265
|
+
/** Step the view by whole months, rolling the year over at either end. */
|
|
266
|
+
declare function addMonths(view: DateMonthView, delta: number): DateMonthView;
|
|
267
|
+
/**
|
|
268
|
+
* A month laid out as `SuggestionOption`s — leading blanks, one cell per day,
|
|
269
|
+
* trailing blanks to complete the last week. Always a whole number of 7-cell
|
|
270
|
+
* rows.
|
|
271
|
+
*
|
|
272
|
+
* Modelling calendar cells as options is what makes the datepicker cheap: they
|
|
273
|
+
* flow through `filteredOptions`, so the existing keyboard controller, the
|
|
274
|
+
* `${listboxId}-option-${i}` id scheme, `aria-activedescendant` and — above all
|
|
275
|
+
* — `selectOption`'s entire commit path work on them unchanged. At 7 columns
|
|
276
|
+
* the grid's own column-major traversal already *is* calendar navigation
|
|
277
|
+
* (→ = +1 day, ↓ = +7 days).
|
|
278
|
+
*
|
|
279
|
+
* Each day cell's `text` is the committed value ("March 23", or "Tuesday"
|
|
280
|
+
* inside the next week — see {@link formatDate}), NOT its label:
|
|
281
|
+
* `computeSelectionPatch` commits `option.text` verbatim, so the value has to
|
|
282
|
+
* live there. The renderer reads `metadata.aiaDay` for the "23" it paints. Padding cells are non-tappable, which is what stops arrow navigation
|
|
283
|
+
* from landing on them.
|
|
284
|
+
*
|
|
285
|
+
* Days before today stay tappable — the calendar de-emphasizes the past, it
|
|
286
|
+
* does not forbid it. The renderers compare each cell's ISO date against
|
|
287
|
+
* today's and paint past days dimmed; picking one still commits normally.
|
|
288
|
+
*/
|
|
289
|
+
declare function buildDateOptions(view: DateMonthView): SuggestionOption[];
|
|
290
|
+
/**
|
|
291
|
+
* The `YYYY-MM-DD` a completed date param already holds, so the calendar can
|
|
292
|
+
* mark that cell as the current answer while it is being re-edited. Null for
|
|
293
|
+
* text that isn't one of our dates.
|
|
294
|
+
*/
|
|
295
|
+
declare function selectedIsoFromText(text: string | undefined | null): string | null;
|
|
296
|
+
/** A cell's `YYYY-MM-DD`, or null if it's padding. */
|
|
297
|
+
declare function cellIso(option: SuggestionOption | undefined): string | null;
|
|
298
|
+
/** A cell's day-of-month, or null if it's padding. */
|
|
299
|
+
declare function cellDay(option: SuggestionOption | undefined): number | null;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* How the dropdown lets the user answer a suggestion.
|
|
303
|
+
*
|
|
304
|
+
* `"options"` is every suggestion the server has ever sent: pick one of its
|
|
305
|
+
* options, or type to filter them. `"date"` ignores the options entirely and
|
|
306
|
+
* renders a calendar instead.
|
|
307
|
+
*/
|
|
308
|
+
type FormatType = "options" | "date";
|
|
309
|
+
/**
|
|
310
|
+
* Decide how a suggestion should be answered.
|
|
311
|
+
*
|
|
312
|
+
* The server cannot send a `formatType` yet, so a suggestion whose `type` names
|
|
313
|
+
* a date is detected here by its name and rendered as a calendar with its
|
|
314
|
+
* options ignored.
|
|
315
|
+
*
|
|
316
|
+
* That name check is deliberately the LAST resort. A `formatType` that *is* on
|
|
317
|
+
* the wire always wins, so the day the server starts sending one this function
|
|
318
|
+
* needs a single line deleted — the datepicker itself, and every gate that
|
|
319
|
+
* consults this, keep working untouched.
|
|
320
|
+
*
|
|
321
|
+
* Accepts anything with a `type`, not a `Suggestion`, so re-edit can ask the
|
|
322
|
+
* same question about a completed param via its `suggestionType`.
|
|
323
|
+
*/
|
|
324
|
+
declare function resolveFormatType(source: {
|
|
325
|
+
type: string;
|
|
326
|
+
options?: SuggestionOption[];
|
|
327
|
+
} | undefined | null): FormatType;
|
|
328
|
+
|
|
217
329
|
/**
|
|
218
330
|
* Raw, user/network/internal-driven fields. The store holds *only* these —
|
|
219
331
|
* derived state is recomputed lazily on read. See {@link CoreDerivedState}.
|
|
@@ -281,11 +393,53 @@ interface CoreInputState {
|
|
|
281
393
|
/** Current caret offset within the editor. Tracked via DOM `selectionchange`. */
|
|
282
394
|
caretOffset: number | null;
|
|
283
395
|
/**
|
|
284
|
-
* True for ~500ms after a user-initiated option selection so the
|
|
396
|
+
* True for ~500ms after a user-initiated option selection so the press
|
|
285
397
|
* animation can finish before the dropdown switches to its loading skeleton.
|
|
286
398
|
* Set by selectOption / ReEditManager.selectOption, cleared by a timer.
|
|
287
399
|
*/
|
|
288
400
|
inSelectionAnimation: boolean;
|
|
401
|
+
/**
|
|
402
|
+
* Which month the datepicker is showing, when one is showing at all.
|
|
403
|
+
*
|
|
404
|
+
* `key` scopes the view to the pill it was opened for (see `dateViewKey` in
|
|
405
|
+
* `derive/state.ts`). A stale key is ignored rather than cleared, so moving
|
|
406
|
+
* to a different date pill starts at the default month without any of the
|
|
407
|
+
* flows that change the active pill — selection, skip, a landing response,
|
|
408
|
+
* re-edit — having to remember to reset this.
|
|
409
|
+
*/
|
|
410
|
+
dateViewMonth: (DateMonthView & {
|
|
411
|
+
key: string;
|
|
412
|
+
}) | null;
|
|
413
|
+
/**
|
|
414
|
+
* Set while the user is picking a date for a span the server identified as
|
|
415
|
+
* one — "september 5th" in "fly to vegas on september 5th".
|
|
416
|
+
*
|
|
417
|
+
* Held apart from `editingParam` rather than folded into it. An identified
|
|
418
|
+
* span is not a completed param: it has no cached options, it lives in
|
|
419
|
+
* `identifiedParams`, and the re-edit paths that read `editingParam` all
|
|
420
|
+
* assume otherwise. Reusing that field would have meant auditing every one
|
|
421
|
+
* of them for a shape they were never written for.
|
|
422
|
+
*
|
|
423
|
+
* `iso` is the date the span's text was read as, or null when it could not be
|
|
424
|
+
* read with confidence (see `parseLooseDate`). Null costs only the
|
|
425
|
+
* pre-selection — the calendar still opens.
|
|
426
|
+
*/
|
|
427
|
+
editingIdentified: {
|
|
428
|
+
id: string;
|
|
429
|
+
/** The identified param's type, which the committed param inherits. */
|
|
430
|
+
type: string;
|
|
431
|
+
/** Plain-text offsets bounding the span being replaced. */
|
|
432
|
+
anchor: number;
|
|
433
|
+
tail: number;
|
|
434
|
+
/**
|
|
435
|
+
* The span's text when the picker opened. Re-checked against those offsets
|
|
436
|
+
* before anything is spliced — if the input changed underneath, they no
|
|
437
|
+
* longer bound the span and writing at them would corrupt an unrelated
|
|
438
|
+
* part of the query.
|
|
439
|
+
*/
|
|
440
|
+
text: string;
|
|
441
|
+
iso: string | null;
|
|
442
|
+
} | null;
|
|
289
443
|
}
|
|
290
444
|
/**
|
|
291
445
|
* Derived fields recomputed from {@link CoreInputState} + {@link CoreOptions}.
|
|
@@ -295,7 +449,23 @@ interface CoreInputState {
|
|
|
295
449
|
interface CoreDerivedState {
|
|
296
450
|
segments: Segment[];
|
|
297
451
|
actionableSuggestions: Suggestion[];
|
|
452
|
+
/**
|
|
453
|
+
* What the dropdown renders. Options for the active pill, filtered by what
|
|
454
|
+
* the user typed — EXCEPT when `activeFormatType` is `"date"`, where these
|
|
455
|
+
* are the visible month's calendar cells and the server's own options for
|
|
456
|
+
* that suggestion are ignored. See {@link buildDateOptions}.
|
|
457
|
+
*/
|
|
298
458
|
filteredOptions: SuggestionOption[];
|
|
459
|
+
/**
|
|
460
|
+
* How the active pill is answered: pick an option, or pick a date. Anything
|
|
461
|
+
* that reasons about options has to consult this — the server still *sends*
|
|
462
|
+
* options for a date suggestion, so code that only looks at `filteredOptions`
|
|
463
|
+
* would keep acting on options the user can't see (see the filter-zone and
|
|
464
|
+
* exact-match gates in `fetchController`).
|
|
465
|
+
*/
|
|
466
|
+
activeFormatType: FormatType;
|
|
467
|
+
/** The month the datepicker is showing. Null unless `activeFormatType` is `"date"`. */
|
|
468
|
+
dateView: DateMonthView | null;
|
|
299
469
|
placeholderText: string;
|
|
300
470
|
isDropdownOpen: boolean;
|
|
301
471
|
/**
|
|
@@ -519,6 +689,28 @@ declare class AIAutocomplete {
|
|
|
519
689
|
*/
|
|
520
690
|
private scheduleSetCursor;
|
|
521
691
|
clearNewParamId(): void;
|
|
692
|
+
/**
|
|
693
|
+
* Open the calendar for a span the server identified as a date.
|
|
694
|
+
*
|
|
695
|
+
* Returns false for anything else — an identified span whose type isn't a
|
|
696
|
+
* date stays inert, exactly as every identified span did before.
|
|
697
|
+
*
|
|
698
|
+
* The span's own text is read for a starting month (`parseLooseDate`), but
|
|
699
|
+
* failing to read it is not a failure: the calendar opens either way, and
|
|
700
|
+
* whatever the user picks replaces the span with the SDK's canonical form. So
|
|
701
|
+
* an unparseable "sometime next week" still ends up a clean answer.
|
|
702
|
+
*/
|
|
703
|
+
startEditingIdentified(paramId: string): boolean;
|
|
704
|
+
/** Close the calendar opened for an identified span, leaving its text untouched. */
|
|
705
|
+
exitEditingIdentified(): void;
|
|
706
|
+
/**
|
|
707
|
+
* Open whatever the chip at `paramId` is answered with.
|
|
708
|
+
*
|
|
709
|
+
* Takes an id from either array so the DOM layers stay dumb: they report
|
|
710
|
+
* which chip was tapped and the core decides what that means. A completed
|
|
711
|
+
* param re-opens its cached options; a span the server identified as a date
|
|
712
|
+
* opens the calendar; anything else is inert, as it has always been.
|
|
713
|
+
*/
|
|
522
714
|
startEditingParam(paramId: string): void;
|
|
523
715
|
replaceEditingRange(replacement: string): boolean;
|
|
524
716
|
exitEditMode(): void;
|
|
@@ -535,6 +727,17 @@ declare class AIAutocomplete {
|
|
|
535
727
|
*/
|
|
536
728
|
selectProduct(product: Product): void;
|
|
537
729
|
handleTextChange(value: string): void;
|
|
730
|
+
/** Page the datepicker back one month. No-op unless a date pill is active. */
|
|
731
|
+
showPreviousMonth(): void;
|
|
732
|
+
/** Page the datepicker forward one month. No-op unless a date pill is active. */
|
|
733
|
+
showNextMonth(): void;
|
|
734
|
+
/**
|
|
735
|
+
* Move the visible month, stamping it with the current pill's key so the
|
|
736
|
+
* derive layer keeps honouring it. Once the active pill changes the key stops
|
|
737
|
+
* matching and the stored view is ignored — a new date pill therefore opens on
|
|
738
|
+
* its own default month with nothing having to clear this.
|
|
739
|
+
*/
|
|
740
|
+
private pageDateView;
|
|
538
741
|
/**
|
|
539
742
|
* Skip the currently active pill (always index 0 of the actionable
|
|
540
743
|
* suggestions) and promote the next pill to active. Invoked by ArrowRight at
|
|
@@ -647,9 +850,117 @@ declare class AIAutocomplete {
|
|
|
647
850
|
* lands; doing it instantly here means bold styling appears as soon as the
|
|
648
851
|
* option is fully typed, without waiting 100–300ms for the round-trip.
|
|
649
852
|
*/
|
|
853
|
+
/**
|
|
854
|
+
* Replace an identified date span with the day the user picked.
|
|
855
|
+
*
|
|
856
|
+
* The span becomes an ordinary completed param, so from here on it is
|
|
857
|
+
* indistinguishable from a date answered through a pill — same bold
|
|
858
|
+
* rendering, same `{{TYPE_N}}` token in `raw_query`, same `completed_params`
|
|
859
|
+
* entry. The identified param it came from is dropped: its text no longer
|
|
860
|
+
* exists in the input, and leaving it would have the reconciler drop it a
|
|
861
|
+
* beat later anyway.
|
|
862
|
+
*/
|
|
863
|
+
private commitIdentifiedDate;
|
|
650
864
|
private maybePromoteExactMatch;
|
|
651
865
|
}
|
|
652
866
|
|
|
867
|
+
/**
|
|
868
|
+
* Best-effort parse of a date the user typed in their own words.
|
|
869
|
+
*
|
|
870
|
+
* The server identifies a span of the query as a date and returns the literal
|
|
871
|
+
* text the user wrote — not a normalized value — so "september 5th", "5 Sep"
|
|
872
|
+
* and "09/05" all arrive as-is. This turns what it can into a `YYYY-MM-DD` so
|
|
873
|
+
* the calendar can open on the right month with that day already marked.
|
|
874
|
+
*
|
|
875
|
+
* **Returning null is a normal outcome, not a failure.** It only costs the
|
|
876
|
+
* pre-selection: the calendar still opens, on the current month with nothing
|
|
877
|
+
* marked, and whatever the user picks replaces the span with the SDK's own
|
|
878
|
+
* canonical text either way. So the committed answer is correct whether or not
|
|
879
|
+
* the original text could be read.
|
|
880
|
+
*
|
|
881
|
+
* Deliberately conservative — it answers null rather than guess, because a
|
|
882
|
+
* wrong date sitting pre-selected is worse than no pre-selection. A user who
|
|
883
|
+
* doesn't notice it commits the wrong answer; an empty calendar just asks them
|
|
884
|
+
* to pick.
|
|
885
|
+
*/
|
|
886
|
+
interface LooseDateOptions {
|
|
887
|
+
/** Reference point for resolving a date written without a year. Defaults to now. */
|
|
888
|
+
today?: Date;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* `YYYY-MM-DD` for a date the user typed however they liked, or null when it
|
|
892
|
+
* can't be read with confidence.
|
|
893
|
+
*
|
|
894
|
+
* Handles, in order: ISO; a month name with a day either side of it, with or
|
|
895
|
+
* without an ordinal suffix or a year; and a numeric pair where only one
|
|
896
|
+
* reading is possible (`13/05` can only be day-first, since 13 is not a month).
|
|
897
|
+
*
|
|
898
|
+
* Deliberately NOT handled:
|
|
899
|
+
* - A numeric pair where both numbers are 12 or under. `09/05` is September
|
|
900
|
+
* 5th to an American and May 9th to everyone else, and nothing in the
|
|
901
|
+
* response says which — so it answers null rather than pick a side.
|
|
902
|
+
* - Relative phrases ("tomorrow", "next friday"). Readable in principle, but
|
|
903
|
+
* they depend on a reference the user may not share with the server, and
|
|
904
|
+
* getting them subtly wrong is the expensive kind of wrong.
|
|
905
|
+
*/
|
|
906
|
+
declare function parseLooseDate(text: string | undefined | null, opts?: LooseDateOptions): string | null;
|
|
907
|
+
/**
|
|
908
|
+
* The `YYYY-MM-DD` for an identified date param.
|
|
909
|
+
*
|
|
910
|
+
* Reads a normalized value off the param first, if one is ever there. The
|
|
911
|
+
* server sends only the user's literal text today, so in practice this always
|
|
912
|
+
* falls through to {@link parseLooseDate} — but normalizing server-side is the
|
|
913
|
+
* real fix for the ambiguous cases the parser refuses, and when it lands this
|
|
914
|
+
* is the one place that has to change.
|
|
915
|
+
*/
|
|
916
|
+
declare function resolveIdentifiedDate(param: {
|
|
917
|
+
text: string;
|
|
918
|
+
isoDate?: unknown;
|
|
919
|
+
}, opts?: LooseDateOptions): string | null;
|
|
920
|
+
|
|
921
|
+
/**
|
|
922
|
+
* Entrance choreography for the dropdown's option rows.
|
|
923
|
+
*
|
|
924
|
+
* When a new set of options lands, each row fades in while rising into place,
|
|
925
|
+
* and the rows start one after another from the input's side outward — the
|
|
926
|
+
* row nearest the input first. The reference (the RB2B support box's
|
|
927
|
+
* suggestion bubbles, 2026-08-19): opacity 0→1 over ~150 ms, a ~16 px rise
|
|
928
|
+
* easing out over ~280 ms, consecutive rows ~80–110 ms apart.
|
|
929
|
+
*
|
|
930
|
+
* The per-row motion is CSS (the option rule in each package's stylesheet —
|
|
931
|
+
* keep the three copies identical; the parity test compares them). This module
|
|
932
|
+
* owns the *timing numbers* and the per-row delay, so vanilla, React and
|
|
933
|
+
* Angular can't drift onto three different cascades. `OPTION_ENTER_RISE_MS` and
|
|
934
|
+
* `OPTION_ENTER_FADE_MS` must match the durations declared in those rules.
|
|
935
|
+
*/
|
|
936
|
+
/** Gap between consecutive rows starting their entrance. */
|
|
937
|
+
declare const OPTION_ENTER_STAGGER_MS = 80;
|
|
938
|
+
/** Duration of a row's opacity fade — mirrors the stylesheets' entrance rule. */
|
|
939
|
+
declare const OPTION_ENTER_FADE_MS = 150;
|
|
940
|
+
/** Duration of a row's rise into place — mirrors the stylesheets' entrance rule. */
|
|
941
|
+
declare const OPTION_ENTER_RISE_MS = 280;
|
|
942
|
+
/** Distance a row rises from, in CSS px — mirrors the stylesheets' entrance rule. */
|
|
943
|
+
declare const OPTION_ENTER_RISE_PX = 16;
|
|
944
|
+
/** Inline custom property each option carries with its own start delay. */
|
|
945
|
+
declare const OPTION_ENTER_DELAY_VAR = "--aia-option-enter-delay";
|
|
946
|
+
/**
|
|
947
|
+
* Start delay (ms) for the option at `index` in a grid of `count` options laid
|
|
948
|
+
* out in `cols` columns, when the dropdown sits `optionsPosition` the input.
|
|
949
|
+
*
|
|
950
|
+
* Rows, not cells, are the unit: the cells of one row share a start so a
|
|
951
|
+
* two-column layout reads as rows appearing, not as a zig-zag. "below" starts
|
|
952
|
+
* at the top row (the one against the input); "above" starts at the bottom row
|
|
953
|
+
* for the same reason — the stack is reversed there and the last row is the
|
|
954
|
+
* one nearest the input.
|
|
955
|
+
*/
|
|
956
|
+
declare function optionEnterDelayMs(index: number, cols: number, count: number, optionsPosition?: "above" | "below"): number;
|
|
957
|
+
/**
|
|
958
|
+
* How long after the options render until the last row has settled — the
|
|
959
|
+
* moment anything that measures the grid (the scroll arrow) can trust its
|
|
960
|
+
* geometry again, since a row mid-rise still extends the scrollable area.
|
|
961
|
+
*/
|
|
962
|
+
declare function optionsEntranceDurationMs(count: number, cols: number): number;
|
|
963
|
+
|
|
653
964
|
/**
|
|
654
965
|
* Rows/columns policy for the dropdown's options grid.
|
|
655
966
|
*
|
|
@@ -671,11 +982,61 @@ interface OptionsGridLayout {
|
|
|
671
982
|
}
|
|
672
983
|
/**
|
|
673
984
|
* - Mobile: one column, five rows visible (scroll past five).
|
|
674
|
-
* - Web: one column, four rows visible
|
|
675
|
-
*
|
|
676
|
-
*
|
|
985
|
+
* - Web: one column, four rows visible (scroll past four).
|
|
986
|
+
*
|
|
987
|
+
* This is the measurement-free baseline (and the only layout mobile ever
|
|
988
|
+
* uses). On web, `planOptionsGrid` upgrades five-plus options to two columns
|
|
989
|
+
* when the rendered rows prove they fit — the upgrade needs the DOM, so it
|
|
990
|
+
* can't live here.
|
|
677
991
|
*/
|
|
678
992
|
declare function computeOptionsGridLayout(count: number, isMobile: boolean): OptionsGridLayout;
|
|
993
|
+
interface OptionsGridPlan extends OptionsGridLayout {
|
|
994
|
+
/**
|
|
995
|
+
* Value for `grid-template-columns`. Tracks carry no inner spaces (the
|
|
996
|
+
* keyboard controller counts tracks by splitting on spaces).
|
|
997
|
+
*/
|
|
998
|
+
template: string;
|
|
999
|
+
/**
|
|
1000
|
+
* Indices of the options on the last visible row of a scrollable web grid —
|
|
1001
|
+
* their text gets the fade that signals more options below. Empty on mobile
|
|
1002
|
+
* and whenever everything is already visible.
|
|
1003
|
+
*/
|
|
1004
|
+
scrollHintIndices: number[];
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Whether measuring the rendered rows could change the plan at all. Measuring
|
|
1008
|
+
* clones every row and forces a layout, and renders happen per keystroke —
|
|
1009
|
+
* so the callers skip it whenever `planOptionsGrid` would ignore the result
|
|
1010
|
+
* anyway (mobile, or fewer options than the two-column minimum).
|
|
1011
|
+
*/
|
|
1012
|
+
declare function needsOptionsGridMeasurement(count: number, isMobile: boolean): boolean;
|
|
1013
|
+
/**
|
|
1014
|
+
* The full layout decision, measurements included.
|
|
1015
|
+
*
|
|
1016
|
+
* Web, five-plus options: two columns of three visible rows — but only when
|
|
1017
|
+
* every option provably fits on one line. Rows fill row-major (even indices
|
|
1018
|
+
* left, odd right), each column is as wide as its widest option, and the
|
|
1019
|
+
* columns may be unequal: the template splits the width in proportion to the
|
|
1020
|
+
* two column maxima, so whenever `left + right <= gridWidth`, each column gets
|
|
1021
|
+
* at least what its widest row needs. When the pair doesn't fit — or there are
|
|
1022
|
+
* no usable measurements (SSR, hidden grid, loading skeletons) — the layout
|
|
1023
|
+
* stays one scrollable column.
|
|
1024
|
+
*
|
|
1025
|
+
* `rowWidths` are single-line pixel widths of the rendered rows (see
|
|
1026
|
+
* `measureOptionsGrid`); `gridWidth` is the grid's content width.
|
|
1027
|
+
*/
|
|
1028
|
+
declare function planOptionsGrid(count: number, isMobile: boolean, rowWidths: number[] | null, gridWidth: number | null): OptionsGridPlan;
|
|
1029
|
+
/**
|
|
1030
|
+
* Measure the rendered option rows for `planOptionsGrid`: each row's
|
|
1031
|
+
* single-line width (a hidden `max-content` + `nowrap` clone, so real classes,
|
|
1032
|
+
* fonts, icons, tags and padding are all in the number), and the width the
|
|
1033
|
+
* columns have to share — the grid's content box less its column gap.
|
|
1034
|
+
* Returns null when there's nothing to measure.
|
|
1035
|
+
*/
|
|
1036
|
+
declare function measureOptionsGrid(grid: HTMLElement): {
|
|
1037
|
+
rowWidths: number[];
|
|
1038
|
+
gridWidth: number;
|
|
1039
|
+
} | null;
|
|
679
1040
|
/**
|
|
680
1041
|
* `grid-template-columns` for a fixed column count. Space-separated
|
|
681
1042
|
* `minmax(0,1fr)` tracks (no `repeat()`, no inner spaces) so rows fill
|
|
@@ -711,6 +1072,10 @@ declare function getCursorOffset(root: HTMLElement): number | null;
|
|
|
711
1072
|
* with no following text node, we use `setStartAfter(strong)` so the caret
|
|
712
1073
|
* sits OUTSIDE the bold subtree — otherwise a caret at the end of a strong's
|
|
713
1074
|
* text is still "inside" the strong, which would falsely trigger re-edit mode.
|
|
1075
|
+
*
|
|
1076
|
+
* Also scrolls the caret back into view (see `scrollCaretIntoView`) — this is
|
|
1077
|
+
* the single caret-placement choke point, and a scripted caret move does not
|
|
1078
|
+
* get the browser's own scroll-to-caret behaviour.
|
|
714
1079
|
*/
|
|
715
1080
|
declare function setCursorOffset(root: HTMLElement, offset: number): void;
|
|
716
1081
|
/**
|
|
@@ -725,6 +1090,66 @@ declare function cursorIsAtEnd(root: HTMLElement): boolean;
|
|
|
725
1090
|
*/
|
|
726
1091
|
declare function previousGraphemeBoundary(text: string, offset: number): number;
|
|
727
1092
|
|
|
1093
|
+
/**
|
|
1094
|
+
* Scroll-to-caret for the editor's own scroll container.
|
|
1095
|
+
*
|
|
1096
|
+
* The editor is a scroll container (`overflow-y: auto` under a height cap in
|
|
1097
|
+
* the core stylesheet; a consumer holding it to one line adds `overflow-x`).
|
|
1098
|
+
* Browsers scroll such a box to the caret as the user types, but not when the
|
|
1099
|
+
* caret is moved by script — which the SDK does on every render that actually
|
|
1100
|
+
* rewrites the editable's content (`renderEditable` early-returns on an
|
|
1101
|
+
* unchanged segment key, and restores the caret only while focused).
|
|
1102
|
+
* Everything here exists to close that gap.
|
|
1103
|
+
*/
|
|
1104
|
+
/**
|
|
1105
|
+
* Scroll the caret back into view inside the editor's own scroll container.
|
|
1106
|
+
*
|
|
1107
|
+
* Browsers do this themselves while the user types, but not when the caret is
|
|
1108
|
+
* moved by script — so every programmatic caret placement (option selection,
|
|
1109
|
+
* caret restore after a re-render, controlled-mode `setValue`) would otherwise
|
|
1110
|
+
* leave the caret, and everything typed next, outside the visible box.
|
|
1111
|
+
*
|
|
1112
|
+
* Exactly one box is scrolled, by writing its own `scrollLeft` / `scrollTop`:
|
|
1113
|
+
* the nearest scrollable ancestor of the editable, which inside the widget's
|
|
1114
|
+
* own markup is the editor. `scrollIntoView` is deliberately not used — it
|
|
1115
|
+
* would walk the whole ancestor chain and the page with it. When the widget
|
|
1116
|
+
* does not own the editable's ancestry (a consumer-rendered editor in
|
|
1117
|
+
* `"dropdown"` / `"headless"` mode) that nearest box may be one of the
|
|
1118
|
+
* consumer's, which is what the browser would scroll for typed input anyway;
|
|
1119
|
+
* nothing above it moves either way. A no-op when nothing overflows, when the
|
|
1120
|
+
* caret is already visible, or when the caret has no measurable box.
|
|
1121
|
+
*/
|
|
1122
|
+
declare function scrollCaretIntoView(root: HTMLElement): void;
|
|
1123
|
+
|
|
1124
|
+
/**
|
|
1125
|
+
* Types the starting-state placeholder into the input one character at a
|
|
1126
|
+
* time instead of popping it in whole.
|
|
1127
|
+
*
|
|
1128
|
+
* The reference (the RB2B support box, 2026-08-19): ~33 ms per character, no
|
|
1129
|
+
* caret, a brief hold on word boundaries; when the phrase changes the old
|
|
1130
|
+
* one fades out (~120 ms), the field sits empty for a beat (~50 ms), then the
|
|
1131
|
+
* new phrase types in from nothing. Characters appear at full opacity — the
|
|
1132
|
+
* only fade is the outgoing phrase.
|
|
1133
|
+
*
|
|
1134
|
+
* Shared by every package: vanilla, React and Angular all render the
|
|
1135
|
+
* placeholder through `renderEditableContent`, which hands the target text
|
|
1136
|
+
* to `syncPlaceholder` below. The typed prefix lives in `data-placeholder`
|
|
1137
|
+
* (the stylesheets render it via `::before`), so nothing enters the
|
|
1138
|
+
* contentEditable's DOM and the core's notion of the placeholder — which
|
|
1139
|
+
* drives Tab-to-accept and prefix-typing suppression — stays the full phrase
|
|
1140
|
+
* at all times.
|
|
1141
|
+
*/
|
|
1142
|
+
/** Delay between characters. */
|
|
1143
|
+
declare const PLACEHOLDER_TYPE_MS = 33;
|
|
1144
|
+
/** Extra hold after a word boundary (space / hyphen). */
|
|
1145
|
+
declare const PLACEHOLDER_WORD_PAUSE_MS = 70;
|
|
1146
|
+
/** Fade-out of the outgoing phrase — must match `--aia-placeholder-fade` in the stylesheets. */
|
|
1147
|
+
declare const PLACEHOLDER_FADE_OUT_MS = 120;
|
|
1148
|
+
/** Empty beat between the fade-out and the next phrase typing in. */
|
|
1149
|
+
declare const PLACEHOLDER_SWAP_GAP_MS = 50;
|
|
1150
|
+
/** Set on the input while the outgoing phrase fades; the stylesheets fade `::before` on it. */
|
|
1151
|
+
declare const PLACEHOLDER_LEAVING_ATTR = "data-aia-placeholder-leaving";
|
|
1152
|
+
|
|
728
1153
|
interface RenderEditableArgs {
|
|
729
1154
|
input: HTMLElement;
|
|
730
1155
|
segments: Segment[];
|
|
@@ -749,6 +1174,56 @@ interface RenderEditableArgs {
|
|
|
749
1174
|
*/
|
|
750
1175
|
declare function renderEditableContent(args: RenderEditableArgs): void;
|
|
751
1176
|
|
|
1177
|
+
/**
|
|
1178
|
+
* The "more below" arrow for the options grid.
|
|
1179
|
+
*
|
|
1180
|
+
* When the option list is taller than its scroll box, a small round button
|
|
1181
|
+
* with a down chevron sits at the bottom-centre of the list. It slides up into
|
|
1182
|
+
* view from behind the dropdown's lower edge (the footer, when the dropdown
|
|
1183
|
+
* opens below the input) the moment there is more to scroll to, slides back
|
|
1184
|
+
* down once the list is scrolled to its end, and scrolls the list a page on
|
|
1185
|
+
* click. The reference (the RB2B support panel, 2026-08-19): a white 32 px
|
|
1186
|
+
* disc that rises from behind the composer over ~150–180 ms, no fade.
|
|
1187
|
+
*
|
|
1188
|
+
* The DOM differs per package (vanilla builds the button here; React and
|
|
1189
|
+
* Angular render their own markup with the same classes and data attributes),
|
|
1190
|
+
* so the *behaviour* lives in this one controller and each package hands it
|
|
1191
|
+
* its dropdown, grid and button elements:
|
|
1192
|
+
*
|
|
1193
|
+
* - `update()` after every render — re-measures where the grid's bottom edge
|
|
1194
|
+
* is and re-evaluates visibility. The disc seats itself in the grid's
|
|
1195
|
+
* reserved fade band when there is one (below-mode with the footer), and
|
|
1196
|
+
* floats a small gap above the grid's edge in layouts that zero the band
|
|
1197
|
+
* (`optionsPosition="above"`, product strip). A new
|
|
1198
|
+
* option group (`data-aia-group` on the grid changed) keeps the arrow hidden
|
|
1199
|
+
* until the rows' entrance animation has settled, because a row mid-rise
|
|
1200
|
+
* still extends the scrollable area and would flash the arrow on.
|
|
1201
|
+
* - The controller listens for the grid's own `scroll` and size changes itself.
|
|
1202
|
+
* - `destroy()` when the grid or dropdown goes away.
|
|
1203
|
+
*/
|
|
1204
|
+
declare const SCROLL_ARROW_CLASS = "magicx-aia-scroll-arrow";
|
|
1205
|
+
/** Stable styling hook (module class names aren't part of the API). */
|
|
1206
|
+
declare const SCROLL_ARROW_ATTR = "data-aia-scroll-arrow";
|
|
1207
|
+
/** Present on the button while it is shown. The stylesheets slide it in on this. */
|
|
1208
|
+
declare const SCROLL_ARROW_VISIBLE_ATTR = "data-aia-visible";
|
|
1209
|
+
/** Inline custom property: how far the button's bottom edge sits above the dropdown's padding edge. */
|
|
1210
|
+
declare const SCROLL_ARROW_BOTTOM_VAR = "--aia-scroll-arrow-bottom";
|
|
1211
|
+
declare const SCROLL_ARROW_LABEL = "Scroll down for more options";
|
|
1212
|
+
interface ScrollArrowController {
|
|
1213
|
+
/** Re-measure and re-evaluate. Call after every render of the dropdown. */
|
|
1214
|
+
update(): void;
|
|
1215
|
+
destroy(): void;
|
|
1216
|
+
}
|
|
1217
|
+
interface ScrollArrowArgs {
|
|
1218
|
+
/** The dropdown panel — the button's containing block; its lower edge clips the hidden button. */
|
|
1219
|
+
dropdown: HTMLElement;
|
|
1220
|
+
/** The options grid (the scroll box). */
|
|
1221
|
+
grid: HTMLElement;
|
|
1222
|
+
/** The arrow button, already in the dropdown. */
|
|
1223
|
+
button: HTMLElement;
|
|
1224
|
+
}
|
|
1225
|
+
declare function attachScrollArrow({ dropdown, grid, button, }: ScrollArrowArgs): ScrollArrowController;
|
|
1226
|
+
|
|
752
1227
|
type Listener<S> = (next: S, prev: S) => void;
|
|
753
1228
|
interface Store<S> {
|
|
754
1229
|
get: () => S;
|
|
@@ -819,6 +1294,23 @@ declare class ModeController {
|
|
|
819
1294
|
private detachListener;
|
|
820
1295
|
}
|
|
821
1296
|
|
|
1297
|
+
/**
|
|
1298
|
+
* The dropdown pill-bar label for a parameter that has no suggestion behind it.
|
|
1299
|
+
*
|
|
1300
|
+
* Every pill normally shows its suggestion's `text` — a phrase written to be
|
|
1301
|
+
* read, "Due date". A date the server identified in the user's own words
|
|
1302
|
+
* ("september 5th") has no suggestion to borrow one from, only the wire `type`,
|
|
1303
|
+
* so the type is read back into words instead: `travel_date` and `travelDate`
|
|
1304
|
+
* both become "travel date". Without this the raw type is the label, both while
|
|
1305
|
+
* the calendar is open over the span and again if that parameter is re-edited
|
|
1306
|
+
* after being answered.
|
|
1307
|
+
*
|
|
1308
|
+
* Lower-casing costs nothing here — every package's pill CSS sets
|
|
1309
|
+
* `text-transform: uppercase`, so a label's original case never reaches the
|
|
1310
|
+
* screen either way.
|
|
1311
|
+
*/
|
|
1312
|
+
declare function identifiedParamLabel(type: string): string;
|
|
1313
|
+
|
|
822
1314
|
/**
|
|
823
1315
|
* Sentinel `text` marking a `completed_params` entry the user skipped (→)
|
|
824
1316
|
* rather than filled. Sent regardless of `maskCompletedText` — it's a fixed
|
|
@@ -851,4 +1343,4 @@ declare function withSkippedParams(completed: CompletedParam[], skipped: Skipped
|
|
|
851
1343
|
*/
|
|
852
1344
|
declare function buildSubmitResult(text: string, completedParams: CompletedParamState[], skippedParams?: SkippedParamState[]): AutocompleteResult;
|
|
853
1345
|
|
|
854
|
-
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 };
|
|
1346
|
+
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, OPTION_ENTER_DELAY_VAR, OPTION_ENTER_FADE_MS, OPTION_ENTER_RISE_MS, OPTION_ENTER_RISE_PX, OPTION_ENTER_STAGGER_MS, type OptionOverrides, type OptionsGridLayout, type OptionsGridPlan, PLACEHOLDER_FADE_OUT_MS, PLACEHOLDER_LEAVING_ATTR, PLACEHOLDER_SWAP_GAP_MS, PLACEHOLDER_TYPE_MS, PLACEHOLDER_WORD_PAUSE_MS, type Product, type ProductsConfig, type RecentlySuggested, type RenderMode, SCROLL_ARROW_ATTR, SCROLL_ARROW_BOTTOM_VAR, SCROLL_ARROW_CLASS, SCROLL_ARROW_LABEL, SCROLL_ARROW_VISIBLE_ATTR, SKIPPED_PARAM_TEXT, type ScrollArrowArgs, type ScrollArrowController, type Segment, type SkippedParamState, type Store, type Suggestion, type SuggestionOption, type TaskKind, WEEKDAY_LABELS, addMonths, attachScrollArrow, buildAttributionUrl, buildDateOptions, buildQuery, buildSubmitResult, cellDay, cellIso, computeOptionsGridLayout, createStore, cursorIsAtEnd, extractPlainText, formatDate, getCursorOffset, getFooterHint, identifiedParamLabel, isOptionsGridMobileViewport, isoDate, measureOptionsGrid, monthLabel, needsOptionsGridMeasurement, optionEnterDelayMs, optionsEntranceDurationMs, optionsGridTemplateColumns, parseDate, parseLooseDate, plainTextLength, planOptionsGrid, previousGraphemeBoundary, renderEditableContent, resolveFormatType, resolveIdentifiedDate, scrollCaretIntoView, selectedIsoFromText, setCursorOffset, withSkippedParams };
|