@magicx-eng/ai-autocomplete-vanilla 0.16.1 → 0.18.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 +61 -14
- package/dist/index.d.mts +176 -41
- package/dist/index.d.ts +176 -41
- package/dist/index.js +32 -10
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +32 -10
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ A framework-agnostic vanilla JS/TypeScript library that provides a guided AI-pow
|
|
|
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
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 the way it would be written — `Tuesday` for a date inside the next week, `March 23` for one later this year, `March 23 2027` for another year. Tapping a committed date re-opens the calendar on the month that text names now — for a weekday name, that is the next such day, not the one originally picked.
|
|
18
|
-
- **Option overrides** —
|
|
18
|
+
- **Option overrides** — supply the options for a parameter yourself: a fixed list, a computed one, or one fetched from your own search endpoint as the user types
|
|
19
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`
|
|
20
20
|
- **Controlled & uncontrolled** — works out of the box or integrates with external state
|
|
21
21
|
- **Accessible** — ARIA combobox 1.2 pattern with `role="listbox"`, `aria-activedescendant`
|
|
@@ -74,7 +74,7 @@ const ac = new AIAutocomplete(container, {
|
|
|
74
74
|
|
|
75
75
|
// API
|
|
76
76
|
apiConfig: { apiKey: "...", authScheme: "Bearer", endpoint: "https://api.ai-autocomplete.com/api/suggest" },
|
|
77
|
-
optionOverrides: {
|
|
77
|
+
optionOverrides: { location: async (query, signal) => [...] }, // see "Option Overrides"
|
|
78
78
|
columns: 2,
|
|
79
79
|
maskCompletedText: false, // when true, omits completed params' text from API requests (PII masking)
|
|
80
80
|
additionalContext: { tier: "gold" }, // optional user context, to personalize suggestions and options
|
|
@@ -112,6 +112,7 @@ const ac = new AIAutocomplete(container, {
|
|
|
112
112
|
|
|
113
113
|
// Events
|
|
114
114
|
onSubmit: (result) => { ... },
|
|
115
|
+
onResult: (result) => { ... }, // after every successful round-trip — see "Reading the query as it's built"
|
|
115
116
|
onError: (error) => { ... },
|
|
116
117
|
onChange: (text) => { ... },
|
|
117
118
|
onParamsChange: (params) => { ... },
|
|
@@ -135,6 +136,7 @@ ac.destroy(); // Remove DOM, listeners, timers
|
|
|
135
136
|
ac.setMode("dark"); // Switch color mode
|
|
136
137
|
ac.update({ animations: false, optionsPosition: "above" });
|
|
137
138
|
ac.selectProduct(product); // Emit onProductSelect (Tier 3 / custom strips)
|
|
139
|
+
ac.getResult(); // The AutocompleteResult as it stands right now
|
|
138
140
|
```
|
|
139
141
|
|
|
140
142
|
> **Tier 1 auto-resets** after both Enter key and built-in submit-button clicks — your `onSubmit` runs, then the SDK clears the input and starts a new session. You don't need to call `ac.reset()` yourself in Tier 1.
|
|
@@ -451,6 +453,8 @@ unsub();
|
|
|
451
453
|
| `isDropdownOpen` | `boolean` | Whether the dropdown should be visible |
|
|
452
454
|
| `newParamId` | `string \| null` | ID of the most recently added param (for shimmer animation) |
|
|
453
455
|
| `isLoading` | `boolean` | Fetch in progress. The Tier 1 / Tier 2 renderers gate the loading skeleton additionally on `!inSelectionAnimation` and `!editingParam`. |
|
|
456
|
+
| `optionQuery` | `string` | The phrase the active parameter's options are filtered by — what the user has typed for it, trimmed — or the replacement typed so far during a re-edit. This is what an [option override](#option-overrides) is asked with. |
|
|
457
|
+
| `isSearchingOptions` | `boolean` | True while an [option override](#option-overrides) for the parameter on screen has been asked and hasn't answered. Render the same skeleton you render for `isLoading`; the built-in dropdowns do. |
|
|
454
458
|
| `inSelectionAnimation` | `boolean` | True for the 500 ms after a user-initiated option tap so the press animation can finish before the dropdown switches to the loading skeleton. |
|
|
455
459
|
| `editingParam` | `CompletedParamState \| null` | When non-null, the user is re-editing a bold completed param; cached options remain visible and the loading skeleton is suppressed. |
|
|
456
460
|
| `products` | `Product[]` | Results of the latest product search (empty unless `products` is configured). See [Product strip](#product-strip) — Tier 3 consumers render their own cards and call `selectProduct(product)` to emit `onProductSelect`. |
|
|
@@ -553,13 +557,42 @@ The SDK handles token refresh transparently: if a request returns 401, it calls
|
|
|
553
557
|
|
|
554
558
|
### `AutocompleteResult`
|
|
555
559
|
|
|
556
|
-
The object passed to `onSubmit
|
|
560
|
+
The structured query as the SDK currently understands it. The same object is passed to `onSubmit` when the user submits, to `onResult` after every successful round-trip, and returned by `ac.getResult()` on demand:
|
|
557
561
|
|
|
558
562
|
| Field | Type | Description |
|
|
559
563
|
|---|---|---|
|
|
560
|
-
| `query` | `string` | Plain text as the user sees it. |
|
|
561
|
-
| `raw_query` | `string` |
|
|
562
|
-
| `completed_params` | `CompletedParam[]` |
|
|
564
|
+
| `query` | `string` | Plain text as the user sees it, trimmed. |
|
|
565
|
+
| `raw_query` | `string` | `query` with each completed param replaced by its placeholder token (e.g. `"Create a {{TASK_1}}"`). |
|
|
566
|
+
| `completed_params` | `CompletedParam[]` | Filled parameter values in query order, followed by any the user skipped (see below). |
|
|
567
|
+
| `identified_params` | `IdentifiedParam[]` | Parameters the server recognised in the user's own words, as `{ type, value }` — e.g. `{ type: "due_date", value: "Friday" }` when the user typed "by Friday" instead of picking a date. They are not tokenized in `raw_query`, and they are tentative: each response replaces the set, and an entry drops out as soon as its text is edited away. |
|
|
568
|
+
| `is_ready` | `boolean` | Whether the server considers the query complete enough to act on. |
|
|
569
|
+
|
|
570
|
+
#### Reading the query as it's built
|
|
571
|
+
|
|
572
|
+
You don't have to wait for submit. `onResult` fires after **every** successful round-trip with the result as it now stands, so you can mirror the structured query live — a preview panel, a draft saved as the user goes, analytics on how far they got:
|
|
573
|
+
|
|
574
|
+
```ts
|
|
575
|
+
const ac = new AIAutocomplete(el, {
|
|
576
|
+
onResult: (result) => {
|
|
577
|
+
preview.textContent = result.raw_query;
|
|
578
|
+
if (result.is_ready) enableRunButton();
|
|
579
|
+
},
|
|
580
|
+
onSubmit: (result) => run(result),
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
// Or subscribe later / more than once:
|
|
584
|
+
const off = ac.on("result", (result) => save(result));
|
|
585
|
+
|
|
586
|
+
// Or read it on demand, from a place that has no event handy:
|
|
587
|
+
const draft = ac.getResult();
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
What "every successful round-trip" means in practice:
|
|
591
|
+
|
|
592
|
+
- It fires once per response the SDK applied — including the very first request on mount (an empty result) and after a response that promoted text the user had already typed into a completed param.
|
|
593
|
+
- It does **not** fire for a request that failed (`onError` does), was cancelled, or was superseded by a newer one before it returned.
|
|
594
|
+
- Keystrokes that haven't been sent yet don't fire it. Typing is debounced and only reaches the server once at least two new characters have been added, so `onResult` lags the input by a beat; `ac.getResult()` always reflects the input as it is right now.
|
|
595
|
+
- A submit is the last result you saw plus whatever the user typed since. Both are built by the same exported `buildSubmitResult`, so the shapes never drift.
|
|
563
596
|
|
|
564
597
|
#### Skipped parameters
|
|
565
598
|
|
|
@@ -585,9 +618,11 @@ const offA = ac.on("change", logToAnalytics);
|
|
|
585
618
|
const offB = ac.on("change", syncToStore);
|
|
586
619
|
```
|
|
587
620
|
|
|
588
|
-
Events: `submit`, `error`, `change`, `paramsChange`, `stateChange`, `focus`, `blur`, `productSelect`.
|
|
621
|
+
Events: `submit`, `result`, `error`, `change`, `paramsChange`, `stateChange`, `focus`, `blur`, `productSelect`.
|
|
622
|
+
|
|
623
|
+
`result` carries an `AutocompleteResult` after every successful round-trip — see [Reading the query as it's built](#reading-the-query-as-its-built). Prefer it over `stateChange` when what you want is the structured query: `stateChange` fires on every internal update (focus, highlight, keystroke) and hands you raw state you would have to assemble yourself.
|
|
589
624
|
|
|
590
|
-
**Errors in your callbacks are contained.** A listener that throws is logged to the console (once per event per instance) and the remaining listeners still run — the SDK's own state is unaffected, so a bug in one handler can't stall the widget. The same holds for `subscribe()` listeners and `optionOverrides` functions; an override that throws
|
|
625
|
+
**Errors in your callbacks are contained.** A listener that throws is logged to the console (once per event per instance) and the remaining listeners still run — the SDK's own state is unaffected, so a bug in one handler can't stall the widget. The same holds for `subscribe()` listeners and `optionOverrides` functions; an override that throws or rejects is logged once per instance and treated as an empty answer, never as a fetch error. One exception is deliberate: if an `onSubmit` handler throws, Tier 1 skips its auto-reset so the user's typed query isn't cleared out from under a failed submit.
|
|
591
626
|
|
|
592
627
|
Constructor callbacks (`onSubmit`, `onChange`, etc.) are registered once at construction as the initial listener for that event. Use `on()` for any additional or replacement listeners. **`update()` does not swap event listeners** — use `on()` for dynamic listener management.
|
|
593
628
|
|
|
@@ -773,24 +808,36 @@ const ac = new AIAutocomplete(container, {
|
|
|
773
808
|
|
|
774
809
|
## Option Overrides
|
|
775
810
|
|
|
811
|
+
Supply the options for a parameter yourself instead of taking the server's. Each entry is keyed by the suggestion `type` and is a function of the phrase the user has typed for that parameter:
|
|
812
|
+
|
|
776
813
|
```ts
|
|
777
814
|
new AIAutocomplete(el, {
|
|
778
815
|
optionOverrides: {
|
|
816
|
+
// A fixed list — a plain array is applied at once, no loading state.
|
|
779
817
|
account: () => [
|
|
780
818
|
{ text: "Savings", is_tappable: true, kind: null },
|
|
781
819
|
{ text: "Checking", is_tappable: true, kind: null },
|
|
782
820
|
],
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
821
|
+
// A list that lives behind a request — return a promise. The dropdown
|
|
822
|
+
// shows its loading skeleton until it settles.
|
|
823
|
+
location: async (query, signal) => {
|
|
824
|
+
const res = await fetch(`/api/locations?q=${encodeURIComponent(query)}`, { signal });
|
|
825
|
+
const places = await res.json();
|
|
826
|
+
return places.map((p) => ({ text: p.name, is_tappable: true, kind: null }));
|
|
787
827
|
},
|
|
788
828
|
},
|
|
789
|
-
onSubmit: handleSubmit,
|
|
790
829
|
});
|
|
791
830
|
```
|
|
792
831
|
|
|
793
|
-
|
|
832
|
+
**When it's called.** Once the moment the parameter becomes active — a response suggests it, a skip or a selection moves it to the front, or the user taps a completed value of that type to change it — with whatever they have already typed for it (usually `""`, the request for the default list). Then again on the SDK's typing debounce with each new phrase, so you can run a search or page through a larger list. If a phrase is already covered by what you last returned, return that list again. The third argument is the `Suggestion` being answered, for the rare override that serves more than one type.
|
|
833
|
+
|
|
834
|
+
**What's shown.** Your answer, as-is: it is not filtered again by the phrase it was produced for, so a fuzzy or synonym match survives. Between two calls the previous answer is filtered locally by what the user types, so the list reacts to every keystroke. Typing the full text of an option in the answer completes the parameter, exactly as it does for a server option.
|
|
835
|
+
|
|
836
|
+
**The server's role.** The server's own options for an overridden type are never shown. While an override owns the active parameter the server is not asked for suggestions; it is asked again when the parameter is answered or skipped. Return an empty list for a typed phrase and the SDK falls back to the server for that phrase — the typed text goes out the way it does for a parameter with no matching options — and won't ask you the same phrase twice. An empty list for `""` leaves the parameter on screen with no options.
|
|
837
|
+
|
|
838
|
+
**Cancellation and errors.** Honour `signal`: it is aborted when a newer phrase supersedes the call, when the parameter stops being active, and on `destroy()`. A throw or a rejection is logged once per instance and treated as an empty answer, never as a fetch error.
|
|
839
|
+
|
|
840
|
+
Tier 3 exposes the pieces: `state.optionQuery` is the phrase, and `state.isSearchingOptions` is true while an answer is pending.
|
|
794
841
|
|
|
795
842
|
## License
|
|
796
843
|
|
package/dist/index.d.mts
CHANGED
|
@@ -147,7 +147,42 @@ interface AccessTokenResult {
|
|
|
147
147
|
expiresAt?: number;
|
|
148
148
|
}
|
|
149
149
|
type APIConfig = APIKeyConfig | AccessTokenConfig;
|
|
150
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Supplies the options for one suggestion type in place of the server's,
|
|
152
|
+
* which are never shown for an overridden type.
|
|
153
|
+
*
|
|
154
|
+
* Called
|
|
155
|
+
* - the moment a pill of this type becomes active (a response suggests it, a
|
|
156
|
+
* skip or a selection moves it to the front, a completed param of this type
|
|
157
|
+
* is tapped to re-edit), with whatever the user has already typed for it —
|
|
158
|
+
* usually `""`, the request for the default list;
|
|
159
|
+
* - again on the SDK's typing debounce with each new phrase, so a consumer
|
|
160
|
+
* whose options live behind a request can run a second search or page
|
|
161
|
+
* through a larger list. Return the same list early if the phrase is
|
|
162
|
+
* already covered by what was last returned.
|
|
163
|
+
*
|
|
164
|
+
* A plain array is applied synchronously — the right shape for a fixed or
|
|
165
|
+
* locally computed list. A promise shows the dropdown's loading state until it
|
|
166
|
+
* settles. Either way the answer is listed as-is, never filtered again by the
|
|
167
|
+
* phrase it was produced for, so a fuzzy or synonym match survives. Between
|
|
168
|
+
* two calls the previous answer is filtered locally by what the user types,
|
|
169
|
+
* for instant feedback.
|
|
170
|
+
*
|
|
171
|
+
* The server is not asked for suggestions while an override owns the active
|
|
172
|
+
* pill. It is asked again when the user answers or skips the pill, and as a
|
|
173
|
+
* fallback when the override returns an empty list for a non-empty phrase —
|
|
174
|
+
* the typed text then goes to the server the way it does for a pill with no
|
|
175
|
+
* matching options. An empty answer for `""` leaves the pill with no options,
|
|
176
|
+
* and the override is not re-asked for a phrase it has already answered empty.
|
|
177
|
+
*
|
|
178
|
+
* Honour `signal` in anything asynchronous: it is aborted when a newer phrase
|
|
179
|
+
* supersedes this call, when the pill stops being active, and on `destroy()`.
|
|
180
|
+
* A rejected promise or a throw is contained — logged once per instance and
|
|
181
|
+
* treated as an empty answer.
|
|
182
|
+
*/
|
|
183
|
+
type OptionOverride = (query: string, signal: AbortSignal, suggestion: Suggestion) => Promise<SuggestionOption[]> | SuggestionOption[];
|
|
184
|
+
/** Per-suggestion-type option overrides, keyed by the suggestion's `type`. */
|
|
185
|
+
type OptionOverrides = Record<string, OptionOverride>;
|
|
151
186
|
/**
|
|
152
187
|
* A single product card in the dropdown's product strip.
|
|
153
188
|
*
|
|
@@ -208,10 +243,33 @@ interface ProductsConfig {
|
|
|
208
243
|
/** Optional cap the SDK applies after `transform`. */
|
|
209
244
|
limit?: number;
|
|
210
245
|
}
|
|
246
|
+
/**
|
|
247
|
+
* The structured query as the SDK currently understands it.
|
|
248
|
+
*
|
|
249
|
+
* Delivered to `onResult` after every successful suggestion round-trip and to
|
|
250
|
+
* `onSubmit` when the user submits. Both are built by `buildSubmitResult`, so
|
|
251
|
+
* a submit is just the last result the consumer already saw — plus whatever
|
|
252
|
+
* the user typed in between.
|
|
253
|
+
*/
|
|
211
254
|
interface AutocompleteResult {
|
|
255
|
+
/** Plain text as the user sees it, trimmed. */
|
|
212
256
|
query: string;
|
|
257
|
+
/** `query` with each completed param replaced by its `{{PLACEHOLDER}}` token. */
|
|
213
258
|
raw_query: string;
|
|
259
|
+
/**
|
|
260
|
+
* Params the user filled (in query order), followed by any they skipped
|
|
261
|
+
* (`text: "skipped"`, no placeholder). See `withSkippedParams`.
|
|
262
|
+
*/
|
|
214
263
|
completed_params: CompletedParam[];
|
|
264
|
+
/**
|
|
265
|
+
* Params the server identified in the user's own words — spans it matched to
|
|
266
|
+
* a parameter type without the user picking an option. Tentative: replaced
|
|
267
|
+
* wholesale by every response and dropped as soon as the text no longer
|
|
268
|
+
* matches. Never tokenized in `raw_query`.
|
|
269
|
+
*/
|
|
270
|
+
identified_params: IdentifiedParam[];
|
|
271
|
+
/** Whether the server considers the query complete enough to act on. */
|
|
272
|
+
is_ready: boolean;
|
|
215
273
|
}
|
|
216
274
|
|
|
217
275
|
/** A calendar month on screen. `month` is 0-based, matching `Date#getMonth`. */
|
|
@@ -368,6 +426,20 @@ interface CoreInputState {
|
|
|
368
426
|
* never shows results belonging to an older query.
|
|
369
427
|
*/
|
|
370
428
|
products: Product[];
|
|
429
|
+
/**
|
|
430
|
+
* The consumer's {@link OptionSource} request for the pill on screen — the
|
|
431
|
+
* active suggestion, or the completed param being re-edited. `type` is the
|
|
432
|
+
* suggestion type it was asked for and `query` the phrase it was asked with;
|
|
433
|
+
* the answer itself is written into that suggestion's (or param's) `options`
|
|
434
|
+
* so every reader of those — filtering, exact-match promotion, the re-edit
|
|
435
|
+
* cache — sees it without knowing where it came from. Null when no source
|
|
436
|
+
* owns the pill on screen. Owned by `OptionSourceController`.
|
|
437
|
+
*/
|
|
438
|
+
optionSearch: {
|
|
439
|
+
type: string;
|
|
440
|
+
query: string;
|
|
441
|
+
status: "loading" | "done";
|
|
442
|
+
} | null;
|
|
371
443
|
activeDropdownIndex: number;
|
|
372
444
|
newParamId: string | null;
|
|
373
445
|
isLoading: boolean;
|
|
@@ -393,7 +465,7 @@ interface CoreInputState {
|
|
|
393
465
|
/** Current caret offset within the editor. Tracked via DOM `selectionchange`. */
|
|
394
466
|
caretOffset: number | null;
|
|
395
467
|
/**
|
|
396
|
-
* True for ~
|
|
468
|
+
* True for ~170ms after a user-initiated option selection so the press
|
|
397
469
|
* animation can finish before the dropdown switches to its loading skeleton.
|
|
398
470
|
* Set by selectOption / ReEditManager.selectOption, cleared by a timer.
|
|
399
471
|
*/
|
|
@@ -467,6 +539,20 @@ interface CoreDerivedState {
|
|
|
467
539
|
/** The month the datepicker is showing. Null unless `activeFormatType` is `"date"`. */
|
|
468
540
|
dateView: DateMonthView | null;
|
|
469
541
|
placeholderText: string;
|
|
542
|
+
/**
|
|
543
|
+
* The phrase the active pill's options are filtered by — what the user has
|
|
544
|
+
* typed for it, trimmed — or, during a re-edit, the replacement typed so
|
|
545
|
+
* far. `""` when nothing has been typed for the pill yet. This is the query
|
|
546
|
+
* an {@link OptionSource} is asked with.
|
|
547
|
+
*/
|
|
548
|
+
optionQuery: string;
|
|
549
|
+
/**
|
|
550
|
+
* True while the consumer's {@link OptionSource} for the pill on screen has
|
|
551
|
+
* been asked and hasn't answered yet. The built-in dropdowns show their
|
|
552
|
+
* loading skeleton for it exactly as they do for `isLoading`; a custom UI
|
|
553
|
+
* should treat it the same way. Never true for a pill without a source.
|
|
554
|
+
*/
|
|
555
|
+
isSearchingOptions: boolean;
|
|
470
556
|
isDropdownOpen: boolean;
|
|
471
557
|
/**
|
|
472
558
|
* Whether the active (leading) pill should render in its `selected` state
|
|
@@ -561,7 +647,22 @@ interface CoreOptions {
|
|
|
561
647
|
* the dropdown changes — no request, no markup, no layout shift.
|
|
562
648
|
*/
|
|
563
649
|
products?: ProductsConfig;
|
|
650
|
+
/** Called when the user submits (Enter, or the submit button in Tier 1). */
|
|
564
651
|
onSubmit?: (result: AutocompleteResult) => void;
|
|
652
|
+
/**
|
|
653
|
+
* Called after every successful suggestion round-trip with the structured
|
|
654
|
+
* query as the SDK now understands it — the same `AutocompleteResult` shape
|
|
655
|
+
* `onSubmit` delivers, so a consumer can mirror the query as it is built
|
|
656
|
+
* (a live preview, a draft, analytics) without waiting for submit.
|
|
657
|
+
*
|
|
658
|
+
* Fires once per response that was applied, including the initial request
|
|
659
|
+
* on mount (an empty result) and after a response that promoted typed text
|
|
660
|
+
* into a completed param. Does not fire for a request that failed, was
|
|
661
|
+
* aborted, or was superseded by a newer one before it returned. Nothing
|
|
662
|
+
* fires for a keystroke that hasn't been sent yet — read `getResult()` for
|
|
663
|
+
* the current value on demand.
|
|
664
|
+
*/
|
|
665
|
+
onResult?: (result: AutocompleteResult) => void;
|
|
565
666
|
onError?: (error: Error) => void;
|
|
566
667
|
onChange?: (text: string) => void;
|
|
567
668
|
onParamsChange?: (params: CompletedParamState[]) => void;
|
|
@@ -591,6 +692,7 @@ interface CoreOptions {
|
|
|
591
692
|
|
|
592
693
|
type AIAutocompleteEvents = {
|
|
593
694
|
submit: [result: AutocompleteResult];
|
|
695
|
+
result: [result: AutocompleteResult];
|
|
594
696
|
error: [error: Error];
|
|
595
697
|
change: [text: string];
|
|
596
698
|
paramsChange: [params: CompletedParamState[]];
|
|
@@ -599,7 +701,7 @@ type AIAutocompleteEvents = {
|
|
|
599
701
|
blur: [];
|
|
600
702
|
productSelect: [product: Product];
|
|
601
703
|
};
|
|
602
|
-
type CoreUpdateOptions = Partial<Omit<CoreOptions, "onSubmit" | "onError" | "onChange" | "onParamsChange" | "onStateChange" | "onFocus" | "onBlur" | "onProductSelect" | "styleRoot">>;
|
|
704
|
+
type CoreUpdateOptions = Partial<Omit<CoreOptions, "onSubmit" | "onResult" | "onError" | "onChange" | "onParamsChange" | "onStateChange" | "onFocus" | "onBlur" | "onProductSelect" | "styleRoot">>;
|
|
603
705
|
declare class AIAutocomplete {
|
|
604
706
|
private inputStore;
|
|
605
707
|
private store;
|
|
@@ -609,6 +711,7 @@ declare class AIAutocomplete {
|
|
|
609
711
|
private keyboardController;
|
|
610
712
|
private pillsController;
|
|
611
713
|
private productsController;
|
|
714
|
+
private optionSource;
|
|
612
715
|
private reEdit;
|
|
613
716
|
private modeController;
|
|
614
717
|
private container;
|
|
@@ -617,11 +720,9 @@ declare class AIAutocomplete {
|
|
|
617
720
|
private domRefs;
|
|
618
721
|
private dropdownRefs;
|
|
619
722
|
private timers;
|
|
620
|
-
/** One per instance — see {@link ConsumerBoundary}. Shared by the emitter
|
|
723
|
+
/** One per instance — see {@link ConsumerBoundary}. Shared by the emitter and `subscribe()`. */
|
|
621
724
|
private boundary;
|
|
622
725
|
/** Identity of the raw override record the wrapped copy below was built from. */
|
|
623
|
-
private rawOverrides;
|
|
624
|
-
private wrappedOverrides;
|
|
625
726
|
private subscriberCount;
|
|
626
727
|
private emitter;
|
|
627
728
|
private sessionId;
|
|
@@ -782,6 +883,14 @@ declare class AIAutocomplete {
|
|
|
782
883
|
*/
|
|
783
884
|
subscribe(listener: (state: CoreState) => void): () => void;
|
|
784
885
|
getState(): CoreState;
|
|
886
|
+
/**
|
|
887
|
+
* The structured query as the SDK understands it right now — the same
|
|
888
|
+
* object `onResult` delivers after each response and `onSubmit` delivers
|
|
889
|
+
* on submit. Read it on demand from a place that has no event handy (a
|
|
890
|
+
* custom submit button, a "save draft" action).
|
|
891
|
+
*/
|
|
892
|
+
getResult(): AutocompleteResult;
|
|
893
|
+
private buildResult;
|
|
785
894
|
get listboxId(): string;
|
|
786
895
|
get isReady(): boolean;
|
|
787
896
|
/**
|
|
@@ -797,24 +906,6 @@ declare class AIAutocomplete {
|
|
|
797
906
|
selectOption(option: SuggestionOption): void;
|
|
798
907
|
private startSelectionAnimationTimer;
|
|
799
908
|
private fireTelemetry;
|
|
800
|
-
/**
|
|
801
|
-
* `this.opts` with every `optionOverrides` entry wrapped in the instance's
|
|
802
|
-
* {@link ConsumerBoundary}.
|
|
803
|
-
*
|
|
804
|
-
* The derive layer calls these functions on the SDK's stack — from
|
|
805
|
-
* `getState()`, and from inside the store's notification drain — so an
|
|
806
|
-
* un-wrapped throw would unwind whatever internal operation triggered the
|
|
807
|
-
* derive and abort delivery of every queued notification with it, taking the
|
|
808
|
-
* instance down rather than just the override. Wrapped, a failed override
|
|
809
|
-
* answers `undefined` and each call site falls back to the server's options.
|
|
810
|
-
*
|
|
811
|
-
* Memoized on the raw record's identity so a swapped integration is
|
|
812
|
-
* re-wrapped while a stable one isn't re-wrapped on every derive. Note
|
|
813
|
-
* `update({ optionOverrides })` only becomes visible on the next store write
|
|
814
|
-
* — the derived layer memoizes on inputs identity, and `update` doesn't
|
|
815
|
-
* invalidate it for this key. Pre-existing, and unchanged by the wrapping.
|
|
816
|
-
*/
|
|
817
|
-
private deriveOpts;
|
|
818
909
|
private setupContainer;
|
|
819
910
|
private buildAndRenderFull;
|
|
820
911
|
private buildAndRenderDropdown;
|
|
@@ -1024,12 +1115,18 @@ declare function needsOptionsGridMeasurement(count: number, isMobile: boolean):
|
|
|
1024
1115
|
*
|
|
1025
1116
|
* Web, five-plus options: two columns of three visible rows — but only when
|
|
1026
1117
|
* every option provably fits on one line. Rows fill row-major (even indices
|
|
1027
|
-
* left, odd right)
|
|
1028
|
-
*
|
|
1029
|
-
*
|
|
1030
|
-
*
|
|
1031
|
-
*
|
|
1032
|
-
*
|
|
1118
|
+
* left, odd right). The left track is exactly as wide as its widest option
|
|
1119
|
+
* (a fixed pixel width from the measurement) and the right track takes the
|
|
1120
|
+
* rest of the grid — which is at least its own widest option, because
|
|
1121
|
+
* `left + right <= gridWidth` is the fit check. So the second column starts
|
|
1122
|
+
* right where the first column's longest text ends, and short options such as
|
|
1123
|
+
* sizes sit beside each other. (Splitting the width in proportion to the two
|
|
1124
|
+
* maxima, as this used to, handed ALL the slack out in that ratio too: one
|
|
1125
|
+
* long option in the left column and "34"-length options on the right gave
|
|
1126
|
+
* the left column ~85% of the box and pushed the right column to the far
|
|
1127
|
+
* edge, 2026-09-03.) When the pair doesn't fit — or there are no usable
|
|
1128
|
+
* measurements (SSR, hidden grid, loading skeletons) — the layout stays one
|
|
1129
|
+
* scrollable column.
|
|
1033
1130
|
*
|
|
1034
1131
|
* `rowWidths` are single-line pixel widths of the rendered rows (see
|
|
1035
1132
|
* `measureOptionsGrid`); `gridWidth` is the grid's content width.
|
|
@@ -1189,10 +1286,17 @@ declare function renderEditableContent(args: RenderEditableArgs): void;
|
|
|
1189
1286
|
* When the option list is taller than its scroll box, a small round button
|
|
1190
1287
|
* with a down chevron sits at the bottom-centre of the list. It slides up into
|
|
1191
1288
|
* view from behind the dropdown's lower edge (the footer, when the dropdown
|
|
1192
|
-
* opens below the input) the moment there is more to scroll to,
|
|
1193
|
-
*
|
|
1194
|
-
* click. The reference (the RB2B support panel,
|
|
1195
|
-
* disc that rises from behind the composer over
|
|
1289
|
+
* opens below the input) the moment there is more to scroll to, dissolves in
|
|
1290
|
+
* place once the list is scrolled to its end, and scrolls the list a page on
|
|
1291
|
+
* click. The entrance follows the reference (the RB2B support panel,
|
|
1292
|
+
* 2026-08-19): a white 32 px disc that rises from behind the composer over
|
|
1293
|
+
* ~150–180 ms, no fade. The exit deliberately does not mirror it — sliding
|
|
1294
|
+
* back down read as the disc "falling" into the footer (2026-09-03), so
|
|
1295
|
+
* instead it fades out where it stands, with a slight shrink, over
|
|
1296
|
+
* SCROLL_ARROW_LEAVE_MS. The stylesheets key the two moves off two attributes:
|
|
1297
|
+
* `data-aia-visible` (rise, stay) and `data-aia-leaving` (dissolve), which
|
|
1298
|
+
* this controller holds for the fade's length and then clears, so the disc
|
|
1299
|
+
* re-parks below the edge — invisibly — ready to rise again.
|
|
1196
1300
|
*
|
|
1197
1301
|
* The DOM differs per package (vanilla builds the button here; React and
|
|
1198
1302
|
* Angular render their own markup with the same classes and data attributes),
|
|
@@ -1215,6 +1319,16 @@ declare const SCROLL_ARROW_CLASS = "magicx-aia-scroll-arrow";
|
|
|
1215
1319
|
declare const SCROLL_ARROW_ATTR = "data-aia-scroll-arrow";
|
|
1216
1320
|
/** Present on the button while it is shown. The stylesheets slide it in on this. */
|
|
1217
1321
|
declare const SCROLL_ARROW_VISIBLE_ATTR = "data-aia-visible";
|
|
1322
|
+
/**
|
|
1323
|
+
* Present on the button while it fades out. The stylesheets dissolve it in
|
|
1324
|
+
* place on this; the controller clears it after SCROLL_ARROW_LEAVE_MS.
|
|
1325
|
+
*/
|
|
1326
|
+
declare const SCROLL_ARROW_LEAVING_ATTR = "data-aia-leaving";
|
|
1327
|
+
/**
|
|
1328
|
+
* Length of the fade-out. Mirrors the `[data-aia-leaving]` transition in each
|
|
1329
|
+
* package's stylesheet — change both together.
|
|
1330
|
+
*/
|
|
1331
|
+
declare const SCROLL_ARROW_LEAVE_MS = 240;
|
|
1218
1332
|
/** Inline custom property: how far the button's bottom edge sits above the dropdown's padding edge. */
|
|
1219
1333
|
declare const SCROLL_ARROW_BOTTOM_VAR = "--aia-scroll-arrow-bottom";
|
|
1220
1334
|
declare const SCROLL_ARROW_LABEL = "Scroll down for more options";
|
|
@@ -1289,6 +1403,16 @@ declare function getFooterHint(optionHighlighted: boolean, isInputEmpty: boolean
|
|
|
1289
1403
|
hint: string;
|
|
1290
1404
|
};
|
|
1291
1405
|
|
|
1406
|
+
/**
|
|
1407
|
+
* Projects client-side identified-param state onto its wire shape.
|
|
1408
|
+
*
|
|
1409
|
+
* The one place the `{ type, value }` form is spelled out. Both the request
|
|
1410
|
+
* body (`api.ts`) and the `AutocompleteResult` handed to `onResult` /
|
|
1411
|
+
* `onSubmit` go through it, so the params the consumer receives are exactly
|
|
1412
|
+
* the ones the server was told about.
|
|
1413
|
+
*/
|
|
1414
|
+
declare function toWireIdentifiedParams(params: IdentifiedParamState[]): IdentifiedParam[];
|
|
1415
|
+
|
|
1292
1416
|
declare class ModeController {
|
|
1293
1417
|
private container;
|
|
1294
1418
|
private mode;
|
|
@@ -1342,14 +1466,25 @@ declare const SKIPPED_PARAM_TEXT = "skipped";
|
|
|
1342
1466
|
declare function withSkippedParams(completed: CompletedParam[], skipped: SkippedParamState[]): CompletedParam[];
|
|
1343
1467
|
|
|
1344
1468
|
/**
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
|
|
1469
|
+
* Fields of an {@link AutocompleteResult} that come from the latest server
|
|
1470
|
+
* response rather than from the input. Both default to "nothing yet".
|
|
1471
|
+
*/
|
|
1472
|
+
interface SubmitResultExtras {
|
|
1473
|
+
/** Server-identified params (`state.identifiedParams`). Default: none. */
|
|
1474
|
+
identifiedParams?: IdentifiedParamState[];
|
|
1475
|
+
/** Server's "query is complete" verdict (`state.isReady`). Default: false. */
|
|
1476
|
+
isReady?: boolean;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Builds the `AutocompleteResult` handed to `onSubmit` and `onResult`: the
|
|
1480
|
+
* placeholder-tokenized raw query plus the completed params, with skipped
|
|
1481
|
+
* suggestions folded in (see {@link withSkippedParams}), the server-identified
|
|
1482
|
+
* params in their wire shape, and the server's readiness verdict.
|
|
1348
1483
|
*
|
|
1349
1484
|
* Shared by every submit path — vanilla Enter / submit button, the React Tier 1
|
|
1350
|
-
* component, the Angular Tier 1 component —
|
|
1351
|
-
* result contains.
|
|
1485
|
+
* component, the Angular Tier 1 component — and by the core's per-response
|
|
1486
|
+
* `result` event, so none of them can drift on what a result contains.
|
|
1352
1487
|
*/
|
|
1353
|
-
declare function buildSubmitResult(text: string, completedParams: CompletedParamState[], skippedParams?: SkippedParamState[]): AutocompleteResult;
|
|
1488
|
+
declare function buildSubmitResult(text: string, completedParams: CompletedParamState[], skippedParams?: SkippedParamState[], extras?: SubmitResultExtras): AutocompleteResult;
|
|
1354
1489
|
|
|
1355
|
-
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 };
|
|
1490
|
+
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 OptionOverride, 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_LEAVE_MS, SCROLL_ARROW_LEAVING_ATTR, SCROLL_ARROW_VISIBLE_ATTR, SKIPPED_PARAM_TEXT, type ScrollArrowArgs, type ScrollArrowController, type Segment, type SkippedParamState, type Store, type SubmitResultExtras, 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, toWireIdentifiedParams, withSkippedParams };
|