@magicx-eng/ai-autocomplete-react 0.12.1 → 0.13.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,6 +16,7 @@ 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 as `MONTH DAY YEAR` (e.g. `March 23 2026`). Tapping a committed date re-opens the calendar on the month it holds.
19
20
  - **Option overrides** — inject or dynamically generate client-side options per suggestion type
20
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`
21
22
  - **Controlled & uncontrolled** — works out of the box or integrates with external state
@@ -131,6 +132,74 @@ After a completed param is added (by any means — option click, exact-match typ
131
132
 
132
133
  ---
133
134
 
135
+ ### Datepicker
136
+
137
+ When the next parameter is a date, the dropdown shows a calendar instead of a
138
+ list of options. A parameter counts as a date when its name says so, or when at
139
+ least three of the options it offers are themselves written as dates — so a
140
+ parameter called `when` or `arrival` still gets a calendar. Options written as
141
+ relative phrases (`today`, `next week`) are not read as dates; a parameter
142
+ offering those needs a name that says date.
143
+
144
+ Nothing is required to switch it on — a date parameter renders this way
145
+ automatically, and every other parameter is unaffected. Tier 1 and Tier 2
146
+ render the calendar for you; in Tier 3 the same data reaches `dropdownProps` so
147
+ you can render your own (see [Tier 3](#tier-3-headless)).
148
+
149
+ **What the user can do**
150
+
151
+ | Input | Result |
152
+ |---|---|
153
+ | Click a day | Commits that date |
154
+ | <kbd>↓</kbd> | Moves into the calendar, starting on today |
155
+ | <kbd>←</kbd> / <kbd>→</kbd> | Previous / next day, crossing week boundaries |
156
+ | <kbd>↑</kbd> / <kbd>↓</kbd> | Previous / next week. Past either end of the month, focus returns to the input |
157
+ | <kbd>Enter</kbd> | Commits the highlighted day |
158
+ | Month arrows | Page the calendar. Paging never commits anything |
159
+ | <kbd>→</kbd> at the end of the input | Skips the parameter, same as any other pill |
160
+
161
+ **The committed value** is always `MONTH DAY YEAR` in English — `March 23 2026`
162
+ — regardless of the visitor's locale, so the value you receive has one shape
163
+ everywhere. It arrives as an ordinary completed parameter: bold in the input,
164
+ and present in `completed_params` on submit like any other answer.
165
+
166
+ **Re-editing** a committed date re-opens the calendar on that date's month with
167
+ the day marked, so changing an answer takes one click.
168
+
169
+ **Reading the value back.** The date arrives as text, so if you need a `Date`
170
+ object, `parseDate` is exported for it:
171
+
172
+ ```ts
173
+ import { parseDate } from "@magicx-eng/ai-autocomplete-react";
174
+
175
+ const due = parseDate("March 23 2026"); // Date, or null if the text isn't one of ours
176
+ ```
177
+
178
+ `parseLooseDate` is exported too, for the looser shapes a person types into a
179
+ query — it reads `september 5th`, `5th September`, `Sept 5` and `2026-09-05`,
180
+ and answers `null` for anything ambiguous rather than guessing.
181
+
182
+ **Dates the user writes themselves.** When someone types a date into their query
183
+ — "fly to vegas on september 5th" — the parameter it answers is recognised in
184
+ place and the date becomes tappable. Tapping it opens the calendar on that
185
+ month with the day already marked, so confirming or changing it takes one tap,
186
+ and the picked date replaces their words with the canonical form.
187
+
188
+ The written date is read where it can be: `september 5th`, `5th September`,
189
+ `Sept 5`, `September 5, 2026` and `2026-09-05` all resolve, in any of those
190
+ orders and with or without the year (a date with no year means its next
191
+ occurrence). Two cases deliberately don't pre-select — a numeric date like
192
+ `09/05`, which is September 5th in the US and May 9th elsewhere with nothing to
193
+ say which, and phrases like `next friday`. Those still open the calendar, just
194
+ on the current month with nothing marked, so the user picks rather than being
195
+ shown a guess that might be wrong.
196
+
197
+ **Typing** while the calendar is open does not filter it. The text is treated as
198
+ a new query, so suggestions refresh as the user types — useful when someone
199
+ would rather describe what they want than pick a day.
200
+
201
+ ---
202
+
134
203
  ## Tier 2: Hook + Dropdown
135
204
 
136
205
  Use the hook to drive state and render our dropdown; you own the input element and layout:
@@ -182,6 +251,8 @@ function App() {
182
251
 
183
252
  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`:
184
253
 
254
+ > **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 2026"`), 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`.
255
+
185
256
  ```tsx
186
257
  import { useAIAutocomplete } from "@magicx-eng/ai-autocomplete-react";
187
258
 
@@ -556,6 +627,13 @@ Override on the container (via `className`). All defaults use `:where()` (zero s
556
627
  | `--aia-product-price-font-size` | `11px` | `11px` | Product price font size. |
557
628
  | `--aia-product-vendor-font-size` | `10px` | `10px` | Product vendor font size. |
558
629
  | `--aia-products-label-font-size` | `11px` | `11px` | Section label font size. |
630
+ | `--aia-date-cell-size` | `36px` | `36px` | Size of a day's square — the box that carries the highlight, the today ring and the selected fill. |
631
+ | `--aia-date-row-height` | `40px` | `40px` | Height of one week row. Lower it to fit a 6-week month in a shorter dropdown. |
632
+ | `--aia-date-cell-font-size` | `14px` | `14px` | Day-number font size. |
633
+ | `--aia-date-month-font-size` | `14px` | `14px` | Font size of the "March 2026" header. |
634
+ | `--aia-date-weekday-font-size` | `11px` | `11px` | Font size of the S/M/T/W/T/F/S column letters. |
635
+ | `--aia-date-today-ring` | `--aia-option-color` | `--aia-option-color` | Outline drawn around today's date. |
636
+ | `--aia-date-selected-bg` | white at 12% | white at 12% | Fill behind the date a re-edited parameter already holds. |
559
637
  | `--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. |
560
638
 
561
639
  Legacy `--aia-color-*` variables are still supported as fallbacks.
@@ -587,6 +665,11 @@ For styling beyond the CSS variables, target these stable `data-aia-*` attribute
587
665
  | `[data-aia-skip]` | The pill bar's trailing "skip" button. Tune via `--aia-skip-font-size` / `--aia-skip-color` / `--aia-skip-color-hover` / `--aia-skip-hover-bg` |
588
666
  | `[data-aia-option]` | Each suggestion option |
589
667
  | `[data-aia-dropdown]` | The dropdown root (listbox). Carries `data-aia-has-products` while the product strip has cards. |
668
+ | `[data-aia-datepicker]` | The calendar, rendered in place of the option list for a date parameter |
669
+ | `[data-aia-date-month]` | The "March 2026" header label |
670
+ | `[data-aia-date-prev]` / `[data-aia-date-next]` | The month arrows |
671
+ | `[data-aia-date-grid]` | The 7-column grid of day cells |
672
+ | `[data-aia-date-cell]` | Each day cell. Also carries `[data-aia-option]`, so option-level styling applies to both bodies |
590
673
  | `[data-aia-products]` | Product strip section (label + row) |
591
674
  | `[data-aia-products-row]` | The horizontally scrolling row of cards |
592
675
  | `[data-aia-product]` | Each product card |
@@ -611,11 +694,24 @@ Every `/api/suggest` request carries a `meta.session_id` UUID. A session runs fr
611
694
 
612
695
  The contract is simple: **after the user submits the query, call `reset()`**. That clears the input and rotates `session_id` so the next session begins clean.
613
696
 
614
- > **Why it matters:** the server uses `session_id` to track each session's history — what the user has been typing sequentially, which options they've selected, and how the query evolved. That context lets the model produce better, more relevant suggestions on subsequent requests within the same session. Calling `reset()` at the right moment (when the user actually submits) keeps that history accurate, so your users get higher-quality results.
697
+ > **Why it matters:** suggestions get sharper as a query develops. Each one
698
+ > takes account of what the user has already answered, so the parameters offered
699
+ > late in a query are shaped by the choices made early in it — that is what makes
700
+ > the experience feel guided rather than like a static list. `session_id` is what
701
+ > ties those requests together into one query.
702
+ >
703
+ > So `reset()` is not bookkeeping: it is how you say "that query is finished".
704
+ > Skip it and the next query is treated as a continuation of the last one, and
705
+ > its suggestions keep being shaped by answers the user has already moved on
706
+ > from — the failure is quiet, and shows up as steadily less relevant options
707
+ > rather than as an error.
615
708
 
616
709
  - **Tier 1 `<AIAutocomplete />`** does this automatically — it calls `reset()` for you after `onSubmit` returns, for both Enter-key and built-in-button submits.
617
710
  - **Tier 2 & 3 `useAIAutocomplete()`** — you own the submit flow, so call `reset()` from your `onSubmit` handler (see the example above) or from your custom button after firing `onSubmit`.
618
711
 
712
+ The id is a plain UUID. Log it next to your own request logs if you want to
713
+ correlate a user's report with the query that produced it.
714
+
619
715
  ## Option Overrides
620
716
 
621
717
  ```tsx
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, SkippedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
- export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, Suggestion, SuggestionOption, TaskKind, buildSubmitResult, withSkippedParams } from '@magicx-eng/ai-autocomplete-vanilla';
1
+ import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, FormatType, DateMonthView, SkippedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
+ export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, DateMonthView, FormatType, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, Suggestion, SuggestionOption, TaskKind, WEEKDAY_LABELS, buildSubmitResult, cellDay, cellIso, formatDate, isoDate, monthLabel, parseDate, parseLooseDate, 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';
@@ -239,6 +239,17 @@ interface UseAIAutocompleteReturn {
239
239
  setFocused: (focused: boolean) => void;
240
240
  /** Tier 1 re-edit: snapshot of the bold param currently being re-edited (null when not editing). */
241
241
  editingParam: CompletedParamState | null;
242
+ /**
243
+ * Set while the user is picking a date for a span the server identified as
244
+ * one — "september 5th" in "fly to vegas on september 5th". Null otherwise.
245
+ */
246
+ editingIdentified: {
247
+ id: string;
248
+ type: string;
249
+ anchor: number;
250
+ tail: number;
251
+ iso: string | null;
252
+ } | null;
242
253
  /** Tier 1 re-edit: plain-text offset where the editing region starts (null when not editing). */
243
254
  editingAnchor: number | null;
244
255
  /** Tier 1 helper: latest known caret offset within the editor. Promote paths stamp this with the position the caret should land after a completion. */
@@ -329,6 +340,22 @@ interface AIAutocompleteDropdownProps {
329
340
  * Default: `"below"`.
330
341
  */
331
342
  optionsPosition?: "above" | "below";
343
+ /**
344
+ * How the active pill is answered. `"date"` renders a calendar in place of
345
+ * the options grid, and `suggestions[0].options` then carries that month's
346
+ * cells rather than the parameter's own options — the core swaps them before
347
+ * they reach here. Provided by `dropdownProps` from the hook.
348
+ * Default: `"options"`.
349
+ */
350
+ formatType?: FormatType;
351
+ /** The month the calendar shows. Null unless `formatType` is `"date"`. Provided by `dropdownProps` from the hook. */
352
+ dateView?: DateMonthView | null;
353
+ /** `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. */
354
+ selectedDateIso?: string | null;
355
+ /** Page the calendar back a month. Provided by `dropdownProps` from the hook. */
356
+ onPreviousMonth?: () => void;
357
+ /** Page the calendar forward a month. Provided by `dropdownProps` from the hook. */
358
+ onNextMonth?: () => void;
332
359
  /**
333
360
  * Color mode for a **standalone** dropdown. When set, the dropdown scopes the
334
361
  * SDK's design tokens to its own root (adds the `magicx-aia` class +
@@ -344,7 +371,7 @@ interface AIAutocompleteDropdownProps {
344
371
 
345
372
  declare const AIAutocomplete: react.ForwardRefExoticComponent<AIAutocompleteProps & react.RefAttributes<AIAutocompleteHandle>>;
346
373
 
347
- declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, onSkip, showSkipButton, skipDisabled, activeSelected, isLoading, isInputEmpty, products, onProductSelect, onProductFocusChange, optionsPosition, mode, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
374
+ 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;
348
375
 
349
376
  declare function useAIAutocomplete({ onSubmit, 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;
350
377
 
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, SkippedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
- export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, Suggestion, SuggestionOption, TaskKind, buildSubmitResult, withSkippedParams } from '@magicx-eng/ai-autocomplete-vanilla';
1
+ import { AutocompleteResult, OptionOverrides, APIConfig, AppearanceMode, CompletedParamState, ProductsConfig, Product, Suggestion, SuggestionOption, FormatType, DateMonthView, SkippedParamState, Segment } from '@magicx-eng/ai-autocomplete-vanilla';
2
+ export { APIConfig, APIKeyConfig, AccessTokenConfig, AccessTokenResult, AppearanceMode, AutocompleteResult, CompletedParam, CompletedParamState, DateMonthView, FormatType, OptionOverrides, Product, ProductsConfig, Segment, SkippedParamState, Suggestion, SuggestionOption, TaskKind, WEEKDAY_LABELS, buildSubmitResult, cellDay, cellIso, formatDate, isoDate, monthLabel, parseDate, parseLooseDate, 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';
@@ -239,6 +239,17 @@ interface UseAIAutocompleteReturn {
239
239
  setFocused: (focused: boolean) => void;
240
240
  /** Tier 1 re-edit: snapshot of the bold param currently being re-edited (null when not editing). */
241
241
  editingParam: CompletedParamState | null;
242
+ /**
243
+ * Set while the user is picking a date for a span the server identified as
244
+ * one — "september 5th" in "fly to vegas on september 5th". Null otherwise.
245
+ */
246
+ editingIdentified: {
247
+ id: string;
248
+ type: string;
249
+ anchor: number;
250
+ tail: number;
251
+ iso: string | null;
252
+ } | null;
242
253
  /** Tier 1 re-edit: plain-text offset where the editing region starts (null when not editing). */
243
254
  editingAnchor: number | null;
244
255
  /** Tier 1 helper: latest known caret offset within the editor. Promote paths stamp this with the position the caret should land after a completion. */
@@ -329,6 +340,22 @@ interface AIAutocompleteDropdownProps {
329
340
  * Default: `"below"`.
330
341
  */
331
342
  optionsPosition?: "above" | "below";
343
+ /**
344
+ * How the active pill is answered. `"date"` renders a calendar in place of
345
+ * the options grid, and `suggestions[0].options` then carries that month's
346
+ * cells rather than the parameter's own options — the core swaps them before
347
+ * they reach here. Provided by `dropdownProps` from the hook.
348
+ * Default: `"options"`.
349
+ */
350
+ formatType?: FormatType;
351
+ /** The month the calendar shows. Null unless `formatType` is `"date"`. Provided by `dropdownProps` from the hook. */
352
+ dateView?: DateMonthView | null;
353
+ /** `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. */
354
+ selectedDateIso?: string | null;
355
+ /** Page the calendar back a month. Provided by `dropdownProps` from the hook. */
356
+ onPreviousMonth?: () => void;
357
+ /** Page the calendar forward a month. Provided by `dropdownProps` from the hook. */
358
+ onNextMonth?: () => void;
332
359
  /**
333
360
  * Color mode for a **standalone** dropdown. When set, the dropdown scopes the
334
361
  * SDK's design tokens to its own root (adds the `magicx-aia` class +
@@ -344,7 +371,7 @@ interface AIAutocompleteDropdownProps {
344
371
 
345
372
  declare const AIAutocomplete: react.ForwardRefExoticComponent<AIAutocompleteProps & react.RefAttributes<AIAutocompleteHandle>>;
346
373
 
347
- declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, onSkip, showSkipButton, skipDisabled, activeSelected, isLoading, isInputEmpty, products, onProductSelect, onProductFocusChange, optionsPosition, mode, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
374
+ 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;
348
375
 
349
376
  declare function useAIAutocomplete({ onSubmit, 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;
350
377