@magicx-eng/ai-autocomplete-react 0.1.38 → 0.1.39

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
@@ -5,16 +5,21 @@ A React/TypeScript SDK that provides a guided AI-powered autocomplete experience
5
5
  ## Features
6
6
 
7
7
  - **Two tiers of integration** — use the full `<AIAutocomplete />` component or go headless with `useAIAutocomplete()` + `<AIAutocompleteDropdown />`
8
- - **Pill-based input** — inline pills for unfilled parameters, bold text for completed ones
8
+ - **Rich inline input (Tier 1)** — a single `contentEditable` surface: typed text, bold completed params, and inline pills share one editing context
9
+ - **Pill-based input** — non-editable inline pills for unfilled parameters, bold inline text for completed ones
10
+ - **Instant exact-match bolding** — typing the full text of an option immediately promotes it to a completed param (no debounced fetch wait). Works in the normal typing flow *and* while re-editing an existing completed param.
11
+ - **Re-edit completed params** — tap a bold completed param to replace it; the dropdown re-opens with the cached options the server originally returned for that param
9
12
  - **Pill placement** — render pills inline in the input or inside the dropdown
10
13
  - **Light/dark mode** — built-in themes with `prefers-color-scheme` support, fully overridable via CSS variables
14
+ - **Inherits your font** — defaults to the host page's font, with `--aia-font-family` to pin a specific font on the library
11
15
  - **Access token auth** — short-lived tokens with automatic refresh, single-flight deduplication, and 401 retry
12
- - **Keyboard navigation** — arrow keys, enter to submit, tab to autocomplete
16
+ - **Keyboard navigation** — arrow keys, enter to submit, tab to autocomplete, backspace to un-bold the last completed param
17
+ - **IME-safe** — composition events are buffered so input text is committed once, after composition ends
13
18
  - **Client-side filtering** — instant substring filtering on every keystroke
14
19
  - **Option overrides** — inject or dynamically generate client-side options per suggestion type
15
20
  - **Controlled & uncontrolled** — works out of the box or integrates with external state
16
21
  - **Ref forwarding** — imperative `focus()`, `blur()`, `reset()`, and `setMode()` via ref
17
- - **Accessible** — ARIA combobox pattern with `role="listbox"`, `aria-activedescendant`
22
+ - **Accessible** — ARIA combobox 1.2 pattern with `role="listbox"`, `aria-activedescendant`
18
23
  - **Animations** — option selection streak animation, text shimmer on newly added params
19
24
  - **Lightweight** — styles auto-injected at runtime
20
25
  - **TypeScript first** — full type definitions shipped with the package
@@ -96,7 +101,7 @@ const ref = useRef<AIAutocompleteHandle>(null);
96
101
 
97
102
  ### Focus Control
98
103
 
99
- The component auto-focuses the input on mount. To opt out, pass `autoFocus={false}`. Listen to focus changes with `onFocus` / `onBlur`:
104
+ The component auto-focuses the contentEditable editor on mount. To opt out, pass `autoFocus={false}`. Listen to focus changes with `onFocus` / `onBlur`:
100
105
 
101
106
  ```tsx
102
107
  <AIAutocomplete
@@ -107,6 +112,20 @@ The component auto-focuses the input on mount. To opt out, pass `autoFocus={fals
107
112
  />
108
113
  ```
109
114
 
115
+ ### Backspace into a completed param
116
+
117
+ Pressing Backspace while the caret is inside or immediately after a bold completed param drops the param's "completed" status and removes one grapheme before the caret. The remaining text stays in the editor as plain (un-bold) text so the user can keep editing instead of losing the whole phrase.
118
+
119
+ ### Re-edit a completed param
120
+
121
+ Tapping a bold completed param enters *re-edit mode* — the dropdown re-opens with the cached options the server originally returned for that param. From there:
122
+
123
+ - **Typing** atomically replaces the bold with what you type. If what you type exactly matches one of the cached options, it's re-promoted to a bold completed param immediately.
124
+ - **Clicking an option** replaces the bold with the new selection.
125
+ - **Arrow keys, Escape, or clicking outside the param** exit re-edit mode without changing anything.
126
+
127
+ After a completed param is added (by any means — option click, exact-match typing, or re-edit), the caret always lands right after the trailing space following the bold so typing can continue immediately. A space is inserted if one wasn't already there.
128
+
110
129
  ---
111
130
 
112
131
  ## Tier 2: Headless
@@ -246,7 +265,7 @@ The headless hook for Tier 2. Accepts the same props as `<AIAutocomplete />` exc
246
265
  |---|---|---|
247
266
  | `completedParams` | `CompletedParamState[]` | Filled parameters. |
248
267
  | `suggestionPills` | `Suggestion[]` | Unfilled suggestions (pills). First item is the active pill. |
249
- | `segments` | `Segment[]` | Input text split into text vs completed segments for overlay rendering. |
268
+ | `segments` | `Segment[]` | Input text split into typed text vs completed params — completed segments render as bold `<strong>` runs inside the editor. |
250
269
  | `newParamId` | `string \| null` | ID of the most recently added param (for shimmer animation). |
251
270
  | `suggestions` | `Suggestion[]` | All suggestions from server (including placeholder type). |
252
271
  | `activeIndex` | `number` | Highlighted option index. `-1` = none. |
@@ -259,7 +278,7 @@ The headless hook for Tier 2. Accepts the same props as `<AIAutocomplete />` exc
259
278
  | Field | Type | Description |
260
279
  |---|---|---|
261
280
  | `setActivePill` | `(index: number) => void` | Move pill at `index` to front (active). |
262
- | `removeLastParam` | `() => void` | Remove last completed param, restore as pill. |
281
+ | `removeLastParam` | `() => void` | Remove the last completed param from state. The text stays in the input as plain text. |
263
282
  | `clearNewParamId` | `() => void` | Clear shimmer animation state. |
264
283
  | `reset` | `() => void` | Clear all state, re-fetch, and start a new session (rotates `session_id`). Call this after handling submit. |
265
284
 
@@ -307,6 +326,7 @@ Override on the container (via `className`). All defaults use `:where()` (zero s
307
326
 
308
327
  | Variable | Light | Dark | Description |
309
328
  |---|---|---|---|
329
+ | `--aia-font-family` | `inherit` | `inherit` | Font used by the library. Defaults to `inherit` so the library picks up your page's font automatically. Set this to pin a specific font on the library without changing the surrounding page. |
310
330
  | `--aia-pill-bg` | `#bdbdbd` | `#bdbdbd` | Pill background |
311
331
  | `--aia-pill-color` | `#000000` | `#ffffff` | Pill text |
312
332
  | `--aia-pill-font-size` | `19px` | `19px` | Pill font size |
@@ -316,7 +336,7 @@ Override on the container (via `className`). All defaults use `:where()` (zero s
316
336
  | `--aia-option-font-size` | `19px` | `19px` | Option font size |
317
337
  | `--aia-written-text-color` | `#000000` | `#ffffff` | Input text |
318
338
  | `--aia-written-text-font-size` | `19px` | `19px` | Input text font size |
319
- | `--aia-caret-color` | `--aia-written-text-color` | `--aia-written-text-color` | Textarea caret color. Override independently of input text color. |
339
+ | `--aia-caret-color` | `--aia-written-text-color` | `--aia-written-text-color` | Editor caret color. Override independently of input text color. |
320
340
  | `--aia-submit-bg` | `#000000` | `#ffffff` | Submit button background |
321
341
  | `--aia-submit-color` | `#ffffff` | `#000000` | Submit button icon color |
322
342
  | `--aia-dropdown-bg` | — | — | Optional bg color the dropdown's "glass" rim shadow tints toward. Set this to the page background behind the dropdown so the bottom-corner glow blends seamlessly. |
@@ -343,14 +363,17 @@ For styling beyond the CSS variables, target these stable `data-aia-*` attribute
343
363
 
344
364
  | Attribute | Element |
345
365
  |---|---|
346
- | `[data-aia-editor]` | Editor area wrapping the textarea + overlay |
347
- | `[data-aia-textarea]` | The `<textarea>` |
366
+ | `[data-aia-editor]` | Editor area wrapping the contentEditable + inline pill list |
367
+ | `[data-aia-input]` | The Tier 1 contentEditable `<div>` that owns typed text and bold completed params. (Replaces the previous `[data-aia-textarea]` selector.) |
368
+ | `[data-aia-pill-list-container]` | Inline sibling of the editor that holds unfilled-suggestion pills |
348
369
  | `[data-aia-submit]` | Submit button |
349
370
  | `[data-aia-pill]` | Each unfilled-suggestion pill |
350
371
  | `[data-aia-pillbar]` | Pill bar container inside the dropdown |
351
372
  | `[data-aia-option]` | Each suggestion option |
352
373
  | `[data-aia-dropdown]` | The dropdown root (listbox) |
353
374
 
375
+ Completed params render as inline `<strong>` elements inside the editor. Override their weight with `[data-aia-input] strong { font-weight: 700; }` (the built-in style uses `:where()` so any consumer selector wins without `!important`).
376
+
354
377
  ```css
355
378
  /* Solid (non-glass) dropdown */
356
379
  .my-autocomplete [data-aia-dropdown] {
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ChangeEvent, KeyboardEvent } from 'react';
2
+ import { ReactNode, KeyboardEvent, ChangeEvent } from 'react';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
 
5
5
  type TaskKind = "automation" | "email" | "insight";
@@ -26,8 +26,11 @@ interface Suggestion {
26
26
  interface CompletedParamState extends CompletedParam {
27
27
  id: string;
28
28
  text: string;
29
+ /** Source suggestion's `type` — used by re-edit mode to render the dropdown pill. */
29
30
  suggestionType: string;
31
+ /** Source suggestion's `text` — used by re-edit mode to render the dropdown pill. */
30
32
  suggestionPlaceholder: string;
33
+ /** Cached options from the suggestion that produced this param — re-edit shows these. */
31
34
  options: SuggestionOption[];
32
35
  metadata?: Record<string, unknown>;
33
36
  }
@@ -136,6 +139,14 @@ interface UseAIAutocompleteOptions {
136
139
  completedParams?: CompletedParamState[];
137
140
  onChange?: (value: string) => void;
138
141
  onParamsChange?: (params: CompletedParamState[]) => void;
142
+ /**
143
+ * Tier 1 helper. Invoked by the core when re-edit / Backspace flows need
144
+ * the editor caret parked at a specific plain-text offset. The Tier 1
145
+ * `<AIAutocomplete />` component supplies this from its inputRef; headless
146
+ * consumers can ignore it.
147
+ * @internal
148
+ */
149
+ setCursor?: (offset: number) => void;
139
150
  }
140
151
  interface UseAIAutocompleteReturn {
141
152
  completedParams: CompletedParamState[];
@@ -150,7 +161,33 @@ interface UseAIAutocompleteReturn {
150
161
  activeIndex: number;
151
162
  isReady: boolean;
152
163
  isLoading: boolean;
164
+ isFocused: boolean;
165
+ isDropdownOpen: boolean;
166
+ placeholderText: string;
167
+ listboxId: string;
153
168
  error: Error | null;
169
+ /** Tier 1 helper: forward plain-text input to the core (autocapitalize handled). */
170
+ handleTextChange: (value: string) => void;
171
+ /** Tier 1 helper: forward keyboard events to the core. */
172
+ handleKeyDown: (e: KeyboardEvent<HTMLElement> | globalThis.KeyboardEvent) => void;
173
+ /** Tier 1 helper: notify the core of focus state. */
174
+ setFocused: (focused: boolean) => void;
175
+ /** Tier 1 re-edit: snapshot of the bold param currently being re-edited (null when not editing). */
176
+ editingParam: CompletedParamState | null;
177
+ /** Tier 1 re-edit: plain-text offset where the editing region starts (null when not editing). */
178
+ editingAnchor: number | null;
179
+ /** Tier 1 helper: latest known caret offset within the editor. Promote paths stamp this with the position the caret should land after a completion. */
180
+ caretOffset: number | null;
181
+ /** Tier 1 re-edit: enter re-edit mode for the bold param with the given id. */
182
+ startEditingParam: (paramId: string) => void;
183
+ /** Tier 1 re-edit: exit re-edit mode. */
184
+ exitEditMode: () => void;
185
+ /** Tier 1 re-edit: update caret tracking after a text-input event (extends edit tail). */
186
+ handleCaretAfterInput: (offset: number | null) => void;
187
+ /** Tier 1 re-edit: update caret on a selection-only move (may exit edit mode). */
188
+ handleCaretMove: (offset: number | null) => void;
189
+ /** Tier 1 re-edit: replace the bold param being edited with the given text. Returns true when applied. */
190
+ replaceEditingRange: (replacement: string) => boolean;
154
191
  inputProps: {
155
192
  value: string;
156
193
  placeholder: string | undefined;
@@ -180,12 +217,14 @@ interface AIAutocompleteDropdownProps {
180
217
  onPillClick?: (index: number) => void;
181
218
  /** Whether to render pills inside the dropdown. Default: true. Tier 2 consumers who render their own pills should set this to false. */
182
219
  showPills?: boolean;
220
+ /** True while a fetch for the latest query is in flight. Replaces options/pills with a skeleton. */
221
+ isLoading?: boolean;
183
222
  }
184
223
 
185
224
  declare const AIAutocomplete: react.ForwardRefExoticComponent<AIAutocompleteProps & react.RefAttributes<AIAutocompleteHandle>>;
186
225
 
187
- declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
226
+ declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, isLoading, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
188
227
 
189
- declare function useAIAutocomplete({ onSubmit, onError, optionOverrides, maskCompletedText, apiConfig, columns, dropdownTrigger, closeDropdownOnBlur, onFocus, onBlur, value: controlledValue, completedParams: controlledParams, onChange: onChangeProp, onParamsChange, source, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
228
+ declare function useAIAutocomplete({ onSubmit, onError, optionOverrides, maskCompletedText, apiConfig, columns, dropdownTrigger, closeDropdownOnBlur, onFocus, onBlur, value: controlledValue, completedParams: controlledParams, onChange: onChangeProp, onParamsChange, source, setCursor, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
190
229
 
191
230
  export { AIAutocomplete, AIAutocompleteDropdown, type AIAutocompleteDropdownProps, type AIAutocompleteHandle, type AIAutocompleteProps, type APIConfig, type APIKeyConfig, type AccessTokenConfig, type AccessTokenResult, type AppearanceMode, type AutocompleteResult, type CompletedParam, type CompletedParamState, type OptionOverrides, type Segment, type Suggestion, type SuggestionOption, type TaskKind, type UseAIAutocompleteOptions, type UseAIAutocompleteReturn, useAIAutocomplete };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as react from 'react';
2
- import { ReactNode, ChangeEvent, KeyboardEvent } from 'react';
2
+ import { ReactNode, KeyboardEvent, ChangeEvent } from 'react';
3
3
  import * as react_jsx_runtime from 'react/jsx-runtime';
4
4
 
5
5
  type TaskKind = "automation" | "email" | "insight";
@@ -26,8 +26,11 @@ interface Suggestion {
26
26
  interface CompletedParamState extends CompletedParam {
27
27
  id: string;
28
28
  text: string;
29
+ /** Source suggestion's `type` — used by re-edit mode to render the dropdown pill. */
29
30
  suggestionType: string;
31
+ /** Source suggestion's `text` — used by re-edit mode to render the dropdown pill. */
30
32
  suggestionPlaceholder: string;
33
+ /** Cached options from the suggestion that produced this param — re-edit shows these. */
31
34
  options: SuggestionOption[];
32
35
  metadata?: Record<string, unknown>;
33
36
  }
@@ -136,6 +139,14 @@ interface UseAIAutocompleteOptions {
136
139
  completedParams?: CompletedParamState[];
137
140
  onChange?: (value: string) => void;
138
141
  onParamsChange?: (params: CompletedParamState[]) => void;
142
+ /**
143
+ * Tier 1 helper. Invoked by the core when re-edit / Backspace flows need
144
+ * the editor caret parked at a specific plain-text offset. The Tier 1
145
+ * `<AIAutocomplete />` component supplies this from its inputRef; headless
146
+ * consumers can ignore it.
147
+ * @internal
148
+ */
149
+ setCursor?: (offset: number) => void;
139
150
  }
140
151
  interface UseAIAutocompleteReturn {
141
152
  completedParams: CompletedParamState[];
@@ -150,7 +161,33 @@ interface UseAIAutocompleteReturn {
150
161
  activeIndex: number;
151
162
  isReady: boolean;
152
163
  isLoading: boolean;
164
+ isFocused: boolean;
165
+ isDropdownOpen: boolean;
166
+ placeholderText: string;
167
+ listboxId: string;
153
168
  error: Error | null;
169
+ /** Tier 1 helper: forward plain-text input to the core (autocapitalize handled). */
170
+ handleTextChange: (value: string) => void;
171
+ /** Tier 1 helper: forward keyboard events to the core. */
172
+ handleKeyDown: (e: KeyboardEvent<HTMLElement> | globalThis.KeyboardEvent) => void;
173
+ /** Tier 1 helper: notify the core of focus state. */
174
+ setFocused: (focused: boolean) => void;
175
+ /** Tier 1 re-edit: snapshot of the bold param currently being re-edited (null when not editing). */
176
+ editingParam: CompletedParamState | null;
177
+ /** Tier 1 re-edit: plain-text offset where the editing region starts (null when not editing). */
178
+ editingAnchor: number | null;
179
+ /** Tier 1 helper: latest known caret offset within the editor. Promote paths stamp this with the position the caret should land after a completion. */
180
+ caretOffset: number | null;
181
+ /** Tier 1 re-edit: enter re-edit mode for the bold param with the given id. */
182
+ startEditingParam: (paramId: string) => void;
183
+ /** Tier 1 re-edit: exit re-edit mode. */
184
+ exitEditMode: () => void;
185
+ /** Tier 1 re-edit: update caret tracking after a text-input event (extends edit tail). */
186
+ handleCaretAfterInput: (offset: number | null) => void;
187
+ /** Tier 1 re-edit: update caret on a selection-only move (may exit edit mode). */
188
+ handleCaretMove: (offset: number | null) => void;
189
+ /** Tier 1 re-edit: replace the bold param being edited with the given text. Returns true when applied. */
190
+ replaceEditingRange: (replacement: string) => boolean;
154
191
  inputProps: {
155
192
  value: string;
156
193
  placeholder: string | undefined;
@@ -180,12 +217,14 @@ interface AIAutocompleteDropdownProps {
180
217
  onPillClick?: (index: number) => void;
181
218
  /** Whether to render pills inside the dropdown. Default: true. Tier 2 consumers who render their own pills should set this to false. */
182
219
  showPills?: boolean;
220
+ /** True while a fetch for the latest query is in flight. Replaces options/pills with a skeleton. */
221
+ isLoading?: boolean;
183
222
  }
184
223
 
185
224
  declare const AIAutocomplete: react.ForwardRefExoticComponent<AIAutocompleteProps & react.RefAttributes<AIAutocompleteHandle>>;
186
225
 
187
- declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
226
+ declare function AIAutocompleteDropdown({ suggestions, activeIndex, onSelect, onHighlight, isOpen, id, className, pills, onPillClick, showPills, isLoading, }: AIAutocompleteDropdownProps): react_jsx_runtime.JSX.Element;
188
227
 
189
- declare function useAIAutocomplete({ onSubmit, onError, optionOverrides, maskCompletedText, apiConfig, columns, dropdownTrigger, closeDropdownOnBlur, onFocus, onBlur, value: controlledValue, completedParams: controlledParams, onChange: onChangeProp, onParamsChange, source, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
228
+ declare function useAIAutocomplete({ onSubmit, onError, optionOverrides, maskCompletedText, apiConfig, columns, dropdownTrigger, closeDropdownOnBlur, onFocus, onBlur, value: controlledValue, completedParams: controlledParams, onChange: onChangeProp, onParamsChange, source, setCursor, }: UseAIAutocompleteOptions): UseAIAutocompleteReturn;
190
229
 
191
230
  export { AIAutocomplete, AIAutocompleteDropdown, type AIAutocompleteDropdownProps, type AIAutocompleteHandle, type AIAutocompleteProps, type APIConfig, type APIKeyConfig, type AccessTokenConfig, type AccessTokenResult, type AppearanceMode, type AutocompleteResult, type CompletedParam, type CompletedParamState, type OptionOverrides, type Segment, type Suggestion, type SuggestionOption, type TaskKind, type UseAIAutocompleteOptions, type UseAIAutocompleteReturn, useAIAutocomplete };