@magicx-eng/ai-autocomplete-react 0.15.0 → 0.17.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 CHANGED
@@ -16,8 +16,8 @@ A React/TypeScript SDK that provides a guided AI-powered autocomplete experience
16
16
  - **Keyboard navigation** — arrow keys, enter to submit, tab to autocomplete, backspace to un-bold the last completed param
17
17
  - **IME-safe** — composition events are buffered so input text is committed once, after composition ends
18
18
  - **Client-side filtering** — instant substring filtering on every keystroke
19
- - **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.
20
- - **Option overrides** — inject or dynamically generate client-side options per suggestion type
19
+ - **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. A parameter whose options are written as spans (`September 11 - October 13`) gets the same calendar answered in two clicks, committed as one range.
20
+ - **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
21
21
  - **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`
22
22
  - **Controlled & uncontrolled** — works out of the box or integrates with external state
23
23
  - **Ref forwarding** — imperative `focus()`, `blur()`, `reset()`, and `setMode()` via ref
@@ -207,6 +207,25 @@ say which, and phrases like `next friday`. Those still open the calendar, just
207
207
  on the current month with nothing marked, so the user picks rather than being
208
208
  shown a guess that might be wrong.
209
209
 
210
+ **Date ranges.** A parameter whose options are written as spans — `September 11
211
+ - October 13`, `September 11 to November 16` — is answered by the same calendar
212
+ in two clicks: the first sets the start, the second the end, and the pair
213
+ commits as one answer. The days between them band as the user moves across the
214
+ calendar, so the span being chosen is visible before it is committed. Paging the
215
+ month between the two clicks is expected — that is how a span crosses months —
216
+ and <kbd>Esc</kbd> drops the start and lets them begin again. Clicking the end
217
+ before the start is fine; the two are ordered on the way in.
218
+
219
+ The span is read off the options the same way a single date is read off its own:
220
+ either separator (`-`, `–`, `—`, `to`, `through`, `until`), either half in any
221
+ date format the SDK reads, and a name that says so (`date_range`, `dateRange`)
222
+ counts too. It commits as one parameter, written the way it was offered —
223
+ `September 11 - October 13`, with the year on whichever end needs it — and both
224
+ ends are also recorded on the completed parameter's `metadata` as `YYYY-MM-DD`,
225
+ under the `DATE_RANGE_META_START` / `DATE_RANGE_META_END` keys this package
226
+ exports (`aiaDateRangeStart` / `aiaDateRangeEnd`), so you never have to parse
227
+ the prose. Re-editing one re-opens the calendar with both ends marked.
228
+
210
229
  **Typing** while the calendar is open does not filter it. The text is treated as
211
230
  a new query, so suggestions refresh as the user types — useful when someone
212
231
  would rather describe what they want than pick a day.
@@ -264,7 +283,7 @@ function App() {
264
283
 
265
284
  Skip `<AIAutocompleteDropdown />` and render the suggestions UI yourself. `dropdownProps` carries the data + actions — the active suggestion's options, the highlighted index, `isOpen`, and `onSelect` / `onHighlight`:
266
285
 
267
- > **Datepicker in a custom UI.** For a date parameter, `dropdownProps.formatType` is `"date"` and `suggestions[0].options` holds that month's day cells. Each cell's `text` is the date it commits (`"March 23"`, or `"Tuesday"` inside the next week), so rendering them as a plain list already works — `onSelect` behaves exactly as it does for an option. To draw an actual calendar, read `dateView` for the month on show, `cellDay(option)` for the number to paint, and call `onPreviousMonth` / `onNextMonth` to page. Cells that pad the start and end of the month have `is_tappable: false`.
286
+ > **Datepicker in a custom UI.** For a date parameter, `dropdownProps.formatType` is `"date"` and `suggestions[0].options` holds that month's day cells. Each cell's `text` is the date it commits (`"March 23"`, or `"Tuesday"` inside the next week), so rendering them as a plain list already works — `onSelect` behaves exactly as it does for an option. To draw an actual calendar, read `dateView` for the month on show, `cellDay(option)` for the number to paint, and call `onPreviousMonth` / `onNextMonth` to page. Cells that pad the start and end of the month have `is_tappable: false`. For a **range** parameter `formatType` is `"date-range"` and the same `onSelect` fires twice: the first records the start (`dropdownProps.dateRangeStart` holds it, and nothing is committed yet), the second commits the span. `dateCellMarks` and `visibleDateRange` are exported to paint the two ends and the band between them the way the built-in calendar does.
268
287
 
269
288
  ```tsx
270
289
  import { useAIAutocomplete } from "@magicx-eng/ai-autocomplete-react";
@@ -315,7 +334,7 @@ function App() {
315
334
  | `onError?` | `(error: Error) => void` | — | Called when a fetch fails. |
316
335
  | `apiConfig?` | `APIConfig` | — | Runtime API configuration (see below). |
317
336
  | `additionalContext?` | `Record<string, unknown>` | — | Optional user context. Include whatever you know about the user (a profile, preferences, workspace, anything) to personalize suggested parameters and options to them. |
318
- | `optionOverrides?` | `Record<string, (query: string) => SuggestionOption[]>` | — | Override options per suggestion type. |
337
+ | `optionOverrides?` | `OptionOverrides` | — | Supply the options for a parameter yourself, per suggestion type — fixed, computed, or fetched as the user types. See [Option Overrides](#option-overrides). |
319
338
  | `maskCompletedText?` | `boolean` | `false` | When `true`, omits completed params' literal text from API requests (for masking PII/sensitive values from the server). |
320
339
  | `className?` | `string` | — | CSS class applied to the container. |
321
340
  | `columns?` | `number` | `2` | Number of columns in the dropdown grid. |
@@ -683,6 +702,7 @@ Override on the container (via `className`). All defaults use `:where()` (zero s
683
702
  | `--aia-date-weekday-font-size` | `11px` | `11px` | Font size of the S/M/T/W/T/F/S column letters. |
684
703
  | `--aia-date-today-ring` | `--aia-option-color` | `--aia-option-color` | Outline drawn around today's date. |
685
704
  | `--aia-date-selected-bg` | white at 12% | white at 12% | Fill behind the date a re-edited parameter already holds. |
705
+ | `--aia-date-range-bg` | white at 6% | white at 6% | Band across the days between the two ends of a picked date range. The ends themselves use `--aia-date-selected-bg`. |
686
706
  | `--aia-skeleton-bg` | `rgba(189, 189, 189, 0.25)` | `#1a1b1d` | Fill color for the loading skeleton bars and the masked text in cached pills/options. |
687
707
 
688
708
  Legacy `--aia-color-*` variables are still supported as fallbacks.
@@ -764,23 +784,38 @@ correlate a user's report with the query that produced it.
764
784
 
765
785
  ## Option Overrides
766
786
 
787
+ 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 — return an array for a fixed or computed list, or a promise for one that lives behind a request (the dropdown shows its loading skeleton until it settles):
788
+
767
789
  ```tsx
768
- <AIAutocomplete
769
- optionOverrides={{
770
- account: () => [
771
- { text: "Savings", is_tappable: true, kind: null },
772
- { text: "Checking", is_tappable: true, kind: null },
773
- ],
774
- value: (query) => {
775
- const digits = query.replace(/\D/g, "");
776
- if (!digits) return [{ text: "$100", is_tappable: true, kind: null }];
777
- return [{ text: `$${digits}`, is_tappable: true, kind: null }];
778
- },
779
- }}
780
- onSubmit={handleSubmit}
781
- />
790
+ import type { OptionOverrides } from "@magicx-eng/ai-autocomplete-react";
791
+
792
+ // Inline is fine: the functions are read live on each call, so a fresh object
793
+ // per render is not a swap. Only the set of overridden types has to change
794
+ // for the core to be told.
795
+ const overrides: OptionOverrides = {
796
+ account: () => [
797
+ { text: "Savings", is_tappable: true, kind: null },
798
+ { text: "Checking", is_tappable: true, kind: null },
799
+ ],
800
+ location: async (query, signal) => {
801
+ const res = await fetch(`/api/locations?q=${encodeURIComponent(query)}`, { signal });
802
+ const places = await res.json();
803
+ return places.map((p) => ({ text: p.name, is_tappable: true, kind: null }));
804
+ },
805
+ };
806
+
807
+ <AIAutocomplete optionOverrides={overrides} onSubmit={handleSubmit} />;
782
808
  ```
783
809
 
810
+ How it behaves:
811
+
812
+ - **Called** once the moment the parameter becomes active, with whatever the user has already typed for it (usually `""`, the request for the default list), and again on the SDK's typing debounce with each new phrase — so you can search or page a larger list. Return the same list again if the phrase is already covered.
813
+ - **Shown as-is.** The answer is not filtered again by the phrase it was produced for, so a fuzzy match survives. Between two calls the previous answer is filtered locally by what the user types. Typing an option's full text completes the parameter, as it does for a server option.
814
+ - **The server steps back.** Its own options for an overridden type are never shown, and it is not asked for suggestions while an override owns the active parameter — 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 (once), the way it does for a parameter with no matching options. An empty list for `""` leaves the parameter on screen with no options.
815
+ - **Honour `signal`** — it is aborted when a newer phrase supersedes the call, when the parameter stops being active, and on unmount. A throw or a rejection is logged once and treated as an empty answer, never as a fetch error.
816
+
817
+ The hook's `isLoading` is true while an answer is pending, the same flag it raises for a suggest request.
818
+
784
819
  ## License
785
820
 
786
821
  Private package. All rights reserved.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, FormatType, DateMonthView, SkippedParamState, IdentifiedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
- export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, DateMonthView, FormatType, IdentifiedParam, IdentifiedParamState, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, SubmitResultExtras, Suggestion, SuggestionOption, TaskKind, WEEKDAY_LABELS, buildSubmitResult, cellDay, cellIso, formatDate, isoDate, monthLabel, parseDate, parseLooseDate, withSkippedParams } from '@magicx-eng/ai-autocomplete-vanilla';
1
+ import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, FormatType, DateMonthView, DateRange, SkippedParamState, IdentifiedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
+ export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, DATE_RANGE_META_END, DATE_RANGE_META_START, DateMonthView, DateRange, FormatType, IdentifiedParam, IdentifiedParamState, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, SubmitResultExtras, Suggestion, SuggestionOption, TaskKind, WEEKDAY_LABELS, buildSubmitResult, cellDay, cellIso, dateCellMarks, formatDate, formatDateRange, isoDate, monthLabel, parseDate, parseLooseDate, parseLooseDateRange, selectedRangeFor, visibleDateRange, withSkippedParams } from '@magicx-eng/ai-autocomplete-vanilla';
3
3
  import * as react from 'react';
4
4
  import { ReactNode, KeyboardEvent, ChangeEvent } from 'react';
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
@@ -339,7 +339,7 @@ interface AIAutocompleteDropdownProps {
339
339
  /**
340
340
  * Extra disabled gate for the skip button beyond `isLoading`. Provided by
341
341
  * `dropdownProps` from the hook (wired to `inSelectionAnimation`): the
342
- * UI-facing loading flag is deliberately suppressed during the ~500ms
342
+ * UI-facing loading flag is deliberately suppressed during the ~170ms
343
343
  * post-selection window, but `skipActivePill` no-ops in it — the button
344
344
  * renders disabled instead of swallowing clicks silently. Default: false.
345
345
  */
@@ -370,17 +370,23 @@ interface AIAutocompleteDropdownProps {
370
370
  */
371
371
  optionsPosition?: "above" | "below";
372
372
  /**
373
- * How the active pill is answered. `"date"` renders a calendar in place of
374
- * the options grid, and `suggestions[0].options` then carries that month's
375
- * cells rather than the parameter's own options — the core swaps them before
376
- * they reach here. Provided by `dropdownProps` from the hook.
373
+ * How the active pill is answered. `"date"` and `"date-range"` render a
374
+ * calendar in place of the options grid, and `suggestions[0].options` then
375
+ * carries that month's cells rather than the parameter's own options — the
376
+ * core swaps them before they reach here. A `"date-range"` parameter is
377
+ * answered by two taps of that calendar, a start and an end, committed as one
378
+ * span. Provided by `dropdownProps` from the hook.
377
379
  * Default: `"options"`.
378
380
  */
379
381
  formatType?: FormatType;
380
- /** The month the calendar shows. Null unless `formatType` is `"date"`. Provided by `dropdownProps` from the hook. */
382
+ /** The month the calendar shows. Null unless `formatType` is a calendar one. Provided by `dropdownProps` from the hook. */
381
383
  dateView?: DateMonthView | null;
382
384
  /** `YYYY-MM-DD` already held by the param being re-edited, so its cell reads as the current answer. Provided by `dropdownProps` from the hook. */
383
385
  selectedDateIso?: string | null;
386
+ /** Both ends of a range param being re-edited, so both cells read as the current answer. Provided by `dropdownProps` from the hook. */
387
+ selectedDateRange?: DateRange | null;
388
+ /** The first end of a range picked so far, while the user chooses the second. Provided by `dropdownProps` from the hook. */
389
+ dateRangeStart?: string | null;
384
390
  /** Page the calendar back a month. Provided by `dropdownProps` from the hook. */
385
391
  onPreviousMonth?: () => void;
386
392
  /** Page the calendar forward a month. Provided by `dropdownProps` from the hook. */
@@ -400,7 +406,7 @@ interface AIAutocompleteDropdownProps {
400
406
 
401
407
  declare const AIAutocomplete: react.ForwardRefExoticComponent<AIAutocompleteProps & react.RefAttributes<AIAutocompleteHandle>>;
402
408
 
403
- declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, onSkip, showSkipButton, skipDisabled, activeSelected, isLoading, isInputEmpty, products, onProductSelect, onProductFocusChange, formatType, dateView, selectedDateIso, onPreviousMonth, onNextMonth, optionsPosition, mode, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
409
+ declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, onSkip, showSkipButton, skipDisabled, activeSelected, isLoading, isInputEmpty, products, onProductSelect, onProductFocusChange, formatType, dateView, selectedDateIso, selectedDateRange, dateRangeStart, onPreviousMonth, onNextMonth, optionsPosition, mode, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
404
410
 
405
411
  declare function useAIAutocomplete({ onSubmit, onResult, onError, optionOverrides, maskCompletedText, apiConfig, additionalContext, columns, dropdownTrigger, optionsPosition, closeDropdownOnBlur, showNonTappableOptions, showSkipButton, onFocus, onBlur, value: controlledValue, completedParams: controlledParams, onChange: onChangeProp, onParamsChange, products, onProductSelect, source, setCursor, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
406
412
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, FormatType, DateMonthView, SkippedParamState, IdentifiedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
- export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, DateMonthView, FormatType, IdentifiedParam, IdentifiedParamState, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, SubmitResultExtras, Suggestion, SuggestionOption, TaskKind, WEEKDAY_LABELS, buildSubmitResult, cellDay, cellIso, formatDate, isoDate, monthLabel, parseDate, parseLooseDate, withSkippedParams } from '@magicx-eng/ai-autocomplete-vanilla';
1
+ import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, FormatType, DateMonthView, DateRange, SkippedParamState, IdentifiedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
+ export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, DATE_RANGE_META_END, DATE_RANGE_META_START, DateMonthView, DateRange, FormatType, IdentifiedParam, IdentifiedParamState, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, SubmitResultExtras, Suggestion, SuggestionOption, TaskKind, WEEKDAY_LABELS, buildSubmitResult, cellDay, cellIso, dateCellMarks, formatDate, formatDateRange, isoDate, monthLabel, parseDate, parseLooseDate, parseLooseDateRange, selectedRangeFor, visibleDateRange, withSkippedParams } from '@magicx-eng/ai-autocomplete-vanilla';
3
3
  import * as react from 'react';
4
4
  import { ReactNode, KeyboardEvent, ChangeEvent } from 'react';
5
5
  import * as react_jsx_runtime from 'react/jsx-runtime';
@@ -339,7 +339,7 @@ interface AIAutocompleteDropdownProps {
339
339
  /**
340
340
  * Extra disabled gate for the skip button beyond `isLoading`. Provided by
341
341
  * `dropdownProps` from the hook (wired to `inSelectionAnimation`): the
342
- * UI-facing loading flag is deliberately suppressed during the ~500ms
342
+ * UI-facing loading flag is deliberately suppressed during the ~170ms
343
343
  * post-selection window, but `skipActivePill` no-ops in it — the button
344
344
  * renders disabled instead of swallowing clicks silently. Default: false.
345
345
  */
@@ -370,17 +370,23 @@ interface AIAutocompleteDropdownProps {
370
370
  */
371
371
  optionsPosition?: "above" | "below";
372
372
  /**
373
- * How the active pill is answered. `"date"` renders a calendar in place of
374
- * the options grid, and `suggestions[0].options` then carries that month's
375
- * cells rather than the parameter's own options — the core swaps them before
376
- * they reach here. Provided by `dropdownProps` from the hook.
373
+ * How the active pill is answered. `"date"` and `"date-range"` render a
374
+ * calendar in place of the options grid, and `suggestions[0].options` then
375
+ * carries that month's cells rather than the parameter's own options — the
376
+ * core swaps them before they reach here. A `"date-range"` parameter is
377
+ * answered by two taps of that calendar, a start and an end, committed as one
378
+ * span. Provided by `dropdownProps` from the hook.
377
379
  * Default: `"options"`.
378
380
  */
379
381
  formatType?: FormatType;
380
- /** The month the calendar shows. Null unless `formatType` is `"date"`. Provided by `dropdownProps` from the hook. */
382
+ /** The month the calendar shows. Null unless `formatType` is a calendar one. Provided by `dropdownProps` from the hook. */
381
383
  dateView?: DateMonthView | null;
382
384
  /** `YYYY-MM-DD` already held by the param being re-edited, so its cell reads as the current answer. Provided by `dropdownProps` from the hook. */
383
385
  selectedDateIso?: string | null;
386
+ /** Both ends of a range param being re-edited, so both cells read as the current answer. Provided by `dropdownProps` from the hook. */
387
+ selectedDateRange?: DateRange | null;
388
+ /** The first end of a range picked so far, while the user chooses the second. Provided by `dropdownProps` from the hook. */
389
+ dateRangeStart?: string | null;
384
390
  /** Page the calendar back a month. Provided by `dropdownProps` from the hook. */
385
391
  onPreviousMonth?: () => void;
386
392
  /** Page the calendar forward a month. Provided by `dropdownProps` from the hook. */
@@ -400,7 +406,7 @@ interface AIAutocompleteDropdownProps {
400
406
 
401
407
  declare const AIAutocomplete: react.ForwardRefExoticComponent<AIAutocompleteProps & react.RefAttributes<AIAutocompleteHandle>>;
402
408
 
403
- declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, onSkip, showSkipButton, skipDisabled, activeSelected, isLoading, isInputEmpty, products, onProductSelect, onProductFocusChange, formatType, dateView, selectedDateIso, onPreviousMonth, onNextMonth, optionsPosition, mode, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
409
+ declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, onSkip, showSkipButton, skipDisabled, activeSelected, isLoading, isInputEmpty, products, onProductSelect, onProductFocusChange, formatType, dateView, selectedDateIso, selectedDateRange, dateRangeStart, onPreviousMonth, onNextMonth, optionsPosition, mode, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
404
410
 
405
411
  declare function useAIAutocomplete({ onSubmit, onResult, onError, optionOverrides, maskCompletedText, apiConfig, additionalContext, columns, dropdownTrigger, optionsPosition, closeDropdownOnBlur, showNonTappableOptions, showSkipButton, onFocus, onBlur, value: controlledValue, completedParams: controlledParams, onChange: onChangeProp, onParamsChange, products, onProductSelect, source, setCursor, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
406
412