@magicx-eng/ai-autocomplete-vanilla 0.1.0 → 0.1.2
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 +386 -0
- package/index.d.mts +44 -34
- package/index.d.ts +44 -34
- package/index.js +215 -174
- package/index.js.map +1 -1
- package/index.mjs +215 -174
- package/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# @magicx-eng/ai-autocomplete-vanilla
|
|
2
|
+
|
|
3
|
+
A framework-agnostic vanilla JS/TypeScript library that provides a guided AI-powered autocomplete experience with pill-based input and dropdown suggestions. Works with any tech stack — React, Vue, Svelte, Angular, or plain HTML.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Three tiers of integration** — full drop-in component, dropdown-only, or fully headless
|
|
8
|
+
- **Zero dependencies** — no React, no framework, no virtual DOM
|
|
9
|
+
- **Pill-based input** — inline pills for unfilled parameters, completed text for filled ones
|
|
10
|
+
- **Pill placement** — render pills inline in the input or inside the dropdown
|
|
11
|
+
- **Light/dark mode** — built-in themes with `prefers-color-scheme` support, fully overridable via CSS variables
|
|
12
|
+
- **Access token auth** — short-lived tokens with automatic refresh, single-flight deduplication, and 401 retry
|
|
13
|
+
- **Keyboard navigation** — arrow keys, enter to submit, tab to autocomplete
|
|
14
|
+
- **Client-side filtering** — instant substring filtering on every keystroke
|
|
15
|
+
- **Option overrides** — inject or dynamically generate client-side options per suggestion type
|
|
16
|
+
- **Controlled & uncontrolled** — works out of the box or integrates with external state
|
|
17
|
+
- **Accessible** — ARIA combobox pattern with `role="listbox"`, `aria-activedescendant`
|
|
18
|
+
- **Animations** — option selection streak animation, text shimmer on newly added params
|
|
19
|
+
- **Lightweight** — ~10 KB gzipped, styles auto-injected at runtime
|
|
20
|
+
- **TypeScript first** — full type definitions shipped with the package
|
|
21
|
+
- **SSR-safe** — no top-level `document`/`window` access
|
|
22
|
+
|
|
23
|
+
## Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
pnpm add @magicx-eng/ai-autocomplete-vanilla
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Three Tiers
|
|
30
|
+
|
|
31
|
+
| Tier | What you get | What you own | Use when |
|
|
32
|
+
|---|---|---|---|
|
|
33
|
+
| **Tier 1: Full** | Input + dropdown + pills + keyboard + state | Nothing — drop in and go | You want a complete widget with zero setup |
|
|
34
|
+
| **Tier 2: Dropdown** | Dropdown + pills + keyboard + state | The input element | You need a custom input (e.g. rich text, search bar) |
|
|
35
|
+
| **Tier 3: Headless** | State + controllers + derived data | All DOM | You're building a framework wrapper (React, Vue, Svelte) or need full rendering control |
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Tier 1: Full Component
|
|
40
|
+
|
|
41
|
+
Drop-in. The library creates and owns the input, dropdown, pills, and all DOM.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
45
|
+
|
|
46
|
+
const ac = new AIAutocomplete(document.getElementById("root"), {
|
|
47
|
+
apiConfig: {
|
|
48
|
+
endpoint: "https://api.example.com/ac/suggest",
|
|
49
|
+
apiKey: "your_api_key",
|
|
50
|
+
},
|
|
51
|
+
onSubmit: (result) => {
|
|
52
|
+
console.log(result.query); // "Create a email"
|
|
53
|
+
console.log(result.raw_query); // "Create a {{TASK_1}}"
|
|
54
|
+
console.log(result.completed_params); // [{ placeholder: "{{TASK_1}}", type: "task", ... }]
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
No CSS import needed — styles are auto-injected on first instantiation.
|
|
60
|
+
|
|
61
|
+
### Options
|
|
62
|
+
|
|
63
|
+
```ts
|
|
64
|
+
const ac = new AIAutocomplete(container, {
|
|
65
|
+
// Tier: "full" (default) | "dropdown" | "headless"
|
|
66
|
+
renderMode: "full",
|
|
67
|
+
|
|
68
|
+
// API
|
|
69
|
+
apiConfig: { apiKey: "...", authScheme: "Bearer", endpoint: "/ac/suggest" },
|
|
70
|
+
optionOverrides: { account: (query) => [...] },
|
|
71
|
+
columns: 2,
|
|
72
|
+
maskCompletedText: false, // when true, omits completed params' text from API requests (PII masking)
|
|
73
|
+
|
|
74
|
+
// Appearance
|
|
75
|
+
mode: "auto", // "light" | "dark" | "auto"
|
|
76
|
+
optionsPosition: "below", // "above" | "below"
|
|
77
|
+
animations: true, // enable streak + shimmer
|
|
78
|
+
pillPlacement: "inline", // "inline" | "dropdown" | "hidden"
|
|
79
|
+
dropdownTrigger: "auto", // "auto" | "manual" | "hidden"
|
|
80
|
+
|
|
81
|
+
// Events
|
|
82
|
+
onSubmit: (result) => { ... },
|
|
83
|
+
onError: (error) => { ... },
|
|
84
|
+
onChange: (text) => { ... },
|
|
85
|
+
onParamsChange: (params) => { ... },
|
|
86
|
+
|
|
87
|
+
// Controlled mode
|
|
88
|
+
value: "initial text",
|
|
89
|
+
completedParams: [],
|
|
90
|
+
});
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Methods
|
|
94
|
+
|
|
95
|
+
```ts
|
|
96
|
+
ac.focus(); // Focus the input
|
|
97
|
+
ac.reset(); // Clear everything, re-fetch
|
|
98
|
+
ac.destroy(); // Remove DOM, listeners, timers
|
|
99
|
+
ac.setMode("dark"); // Switch color mode
|
|
100
|
+
ac.update({ animations: false, optionsPosition: "above" });
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Tier 2: Dropdown Only
|
|
106
|
+
|
|
107
|
+
You own the input. The library renders the dropdown with pills inside a container you provide.
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
111
|
+
|
|
112
|
+
const input = document.getElementById("my-input");
|
|
113
|
+
const dropdownContainer = document.getElementById("my-dropdown");
|
|
114
|
+
|
|
115
|
+
const ac = new AIAutocomplete(dropdownContainer, {
|
|
116
|
+
renderMode: "dropdown",
|
|
117
|
+
apiConfig: { apiKey: "your_api_key" },
|
|
118
|
+
onChange: (text) => {
|
|
119
|
+
// Keep your input in sync when the library updates text (e.g. option selected)
|
|
120
|
+
if (input.value !== text) input.value = text;
|
|
121
|
+
},
|
|
122
|
+
onSubmit: (result) => console.log(result),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Wire your input → library
|
|
126
|
+
input.addEventListener("input", () => ac.handleTextChange(input.value));
|
|
127
|
+
input.addEventListener("keydown", (e) => ac.handleKeyDown(e));
|
|
128
|
+
input.addEventListener("focus", () => ac.setFocused(true));
|
|
129
|
+
input.addEventListener("blur", () => ac.setFocused(false));
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Pills always render inside the dropdown in this mode. The library creates the dropdown DOM inside your container — you just provide the element and wire up your input's events.
|
|
133
|
+
|
|
134
|
+
> Focus/blur wiring is required when `dropdownTrigger` is `"auto"` (the default) — the dropdown only opens while the input is focused. Without `setFocused()`, the dropdown will never open.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Tier 3: Headless
|
|
139
|
+
|
|
140
|
+
No DOM at all. You get state, controllers, and derived data. You render everything yourself.
|
|
141
|
+
|
|
142
|
+
This is what framework wrappers (React, Vue, Svelte) use under the hood.
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
146
|
+
|
|
147
|
+
const ac = new AIAutocomplete(document.createElement("div"), {
|
|
148
|
+
renderMode: "headless",
|
|
149
|
+
apiConfig: { apiKey: "your_api_key" },
|
|
150
|
+
onSubmit: (result) => console.log(result),
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Read state
|
|
154
|
+
const state = ac.getState();
|
|
155
|
+
// state.text, state.completedParams, state.suggestions,
|
|
156
|
+
// state.segments, state.filteredOptions, state.actionableSuggestions,
|
|
157
|
+
// state.placeholderText, state.isDropdownOpen, state.activeDropdownIndex,
|
|
158
|
+
// state.newParamId, state.isLoading, state.isReady, state.error
|
|
159
|
+
|
|
160
|
+
// Subscribe to changes (fires after derived state is settled)
|
|
161
|
+
const unsub = ac.subscribe((state) => {
|
|
162
|
+
renderMyInput(state.text, state.placeholderText);
|
|
163
|
+
renderMyPills(state.actionableSuggestions);
|
|
164
|
+
renderMyDropdown(state.filteredOptions, state.activeDropdownIndex, state.isDropdownOpen);
|
|
165
|
+
renderMySegments(state.segments, state.newParamId);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Forward user actions → library
|
|
169
|
+
myInput.addEventListener("input", () => ac.handleTextChange(myInput.value));
|
|
170
|
+
myInput.addEventListener("keydown", (e) => ac.handleKeyDown(e));
|
|
171
|
+
myInput.addEventListener("focus", () => ac.setFocused(true));
|
|
172
|
+
myInput.addEventListener("blur", () => ac.setFocused(false));
|
|
173
|
+
myOptionEl.addEventListener("click", () => ac.selectOption(option));
|
|
174
|
+
myPillEl.addEventListener("click", () => ac.setActivePill(index));
|
|
175
|
+
|
|
176
|
+
// Cleanup
|
|
177
|
+
ac.destroy();
|
|
178
|
+
unsub();
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### State shape (`CoreState`)
|
|
182
|
+
|
|
183
|
+
| Field | Type | Description |
|
|
184
|
+
|---|---|---|
|
|
185
|
+
| `text` | `string` | Current input text |
|
|
186
|
+
| `completedParams` | `CompletedParamState[]` | Filled parameters |
|
|
187
|
+
| `suggestions` | `Suggestion[]` | All suggestions from server (including placeholder type) |
|
|
188
|
+
| `actionableSuggestions` | `Suggestion[]` | Non-placeholder suggestions (the pills) |
|
|
189
|
+
| `filteredOptions` | `SuggestionOption[]` | Options for the active suggestion, filtered by current query |
|
|
190
|
+
| `segments` | `Segment[]` | Input text split into typed text vs completed params — for overlay rendering |
|
|
191
|
+
| `placeholderText` | `string` | Placeholder text from server suggestions (joined `placeholder`-type suggestion texts) |
|
|
192
|
+
| `activeDropdownIndex` | `number` | Highlighted option index (-1 = none) |
|
|
193
|
+
| `isDropdownOpen` | `boolean` | Whether the dropdown should be visible |
|
|
194
|
+
| `newParamId` | `string \| null` | ID of the most recently added param (for shimmer animation) |
|
|
195
|
+
| `isLoading` | `boolean` | Fetch in progress |
|
|
196
|
+
| `isReady` | `boolean` | Server indicates query is complete |
|
|
197
|
+
| `error` | `Error \| null` | Last fetch error |
|
|
198
|
+
|
|
199
|
+
### Actions
|
|
200
|
+
|
|
201
|
+
| Method | Description |
|
|
202
|
+
|---|---|
|
|
203
|
+
| `handleTextChange(value)` | Call when user types. Handles capitalization, param reconciliation, filter updates. |
|
|
204
|
+
| `handleKeyDown(event)` | Forward keyboard events. Handles arrow nav, Enter, Tab, Escape. |
|
|
205
|
+
| `setFocused(focused)` | Notify the library that the input has focus. Required for `dropdownTrigger: "auto"` in Tier 2/3 — the dropdown only opens while focused. |
|
|
206
|
+
| `selectOption(option)` | Select a dropdown option. Updates text, creates completed param, triggers shimmer. |
|
|
207
|
+
| `setActiveDropdownIndex(index)` | Set the highlighted option (for mouse hover). |
|
|
208
|
+
| `setActivePill(index)` | Reorder pills — moves pill at `index` to front (active). |
|
|
209
|
+
| `removeLastParam()` | Remove last completed param, restore it as a pill. |
|
|
210
|
+
| `clearNewParamId()` | Clear shimmer animation state. |
|
|
211
|
+
| `reset()` | Clear all state, re-fetch initial suggestions. |
|
|
212
|
+
| `setValue(text)` | Set text (controlled mode). |
|
|
213
|
+
| `setCompletedParams(params)` | Set completed params (controlled mode). |
|
|
214
|
+
| `setMode(mode)` | Switch color mode at runtime. |
|
|
215
|
+
| `update(opts)` | Update multiple options at once (e.g. `animations`, `pillPlacement`, `dropdownTrigger`, `optionsPosition`). |
|
|
216
|
+
| `getState()` | Get a snapshot of the current state. Useful for initial read before subscribing. |
|
|
217
|
+
| `subscribe(listener)` | Subscribe to state changes (fires after derived state settles). Returns unsubscribe function. |
|
|
218
|
+
| `destroy()` | Cleanup — abort fetches, clear timers, remove listeners. |
|
|
219
|
+
|
|
220
|
+
### Building a framework wrapper
|
|
221
|
+
|
|
222
|
+
The pattern for any framework:
|
|
223
|
+
|
|
224
|
+
1. Create a headless instance on mount
|
|
225
|
+
2. Subscribe to state → map to framework reactivity (`useState`, `ref()`, `writable()`)
|
|
226
|
+
3. Render your own components using the state
|
|
227
|
+
4. Forward user events (input, keydown, click) → core actions
|
|
228
|
+
5. Destroy on unmount
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## API Reference
|
|
233
|
+
|
|
234
|
+
### `APIConfig`
|
|
235
|
+
|
|
236
|
+
A discriminated union: `APIKeyConfig | AccessTokenConfig`.
|
|
237
|
+
|
|
238
|
+
#### API Key Mode (default)
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
{
|
|
242
|
+
apiKey: "your_api_key",
|
|
243
|
+
authScheme: "Bearer", // or "Basic"
|
|
244
|
+
endpoint: "/ac/suggest", // optional
|
|
245
|
+
appIdentifier: "my-app", // optional
|
|
246
|
+
headers: { "X-Custom": "v" }, // optional
|
|
247
|
+
}
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
#### Access Token Mode
|
|
251
|
+
|
|
252
|
+
```ts
|
|
253
|
+
{
|
|
254
|
+
type: "accessToken",
|
|
255
|
+
getAccessToken: async () => {
|
|
256
|
+
const res = await fetch("/api/ac-token");
|
|
257
|
+
const { access_token, expires_at } = await res.json();
|
|
258
|
+
return { accessToken: access_token, expiresAt: expires_at };
|
|
259
|
+
},
|
|
260
|
+
endpoint: "/ac/suggest", // optional
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
The SDK handles token refresh transparently: if a request returns 401, it calls `getAccessToken` once, retries, and only surfaces an error if the retry also fails. Concurrent 401s share a single refresh call. Tokens are refreshed proactively 30 seconds before `expiresAt`.
|
|
265
|
+
|
|
266
|
+
### `AutocompleteResult`
|
|
267
|
+
|
|
268
|
+
The object passed to `onSubmit`:
|
|
269
|
+
|
|
270
|
+
| Field | Type | Description |
|
|
271
|
+
|---|---|---|
|
|
272
|
+
| `query` | `string` | Plain text as the user sees it. |
|
|
273
|
+
| `raw_query` | `string` | Text with placeholder tokens (e.g. `"Create a {{TASK_1}}"`). |
|
|
274
|
+
| `completed_params` | `CompletedParam[]` | Array of filled parameter values. |
|
|
275
|
+
|
|
276
|
+
### Event Subscription
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
const off = ac.on("submit", (result) => { ... });
|
|
280
|
+
off(); // unsubscribe
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Events: `submit`, `error`, `change`, `paramsChange`.
|
|
284
|
+
|
|
285
|
+
Constructor callbacks (`onSubmit`, `onChange`, etc.) are equivalent to calling `on()`.
|
|
286
|
+
|
|
287
|
+
**`subscribe()` vs `on()`:** Use `on()` for specific events (submit, error, text change). Use `subscribe()` (Tier 3) for full state updates — it fires after derived state is settled, making it safe for rendering.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## CSS Customization
|
|
292
|
+
|
|
293
|
+
Styles are auto-injected at runtime (Tier 1 and Tier 2). No CSS import needed. The component ships built-in light and dark defaults.
|
|
294
|
+
|
|
295
|
+
### CSS Variables
|
|
296
|
+
|
|
297
|
+
Override these on the container element. All built-in defaults use `:where()` (zero specificity) — your overrides always win without `!important`.
|
|
298
|
+
|
|
299
|
+
| Variable | Default (light) | Default (dark) | Description |
|
|
300
|
+
|---|---|---|---|
|
|
301
|
+
| `--aia-pill-bg` | `#bdbdbd` | `#bdbdbd` | Pill background |
|
|
302
|
+
| `--aia-pill-color` | `#000000` | `#ffffff` | Pill text |
|
|
303
|
+
| `--aia-pill-font-size` | `19px` | `19px` | Pill font size |
|
|
304
|
+
| `--aia-option-bg` | `transparent` | `transparent` | Option background (highlighted) |
|
|
305
|
+
| `--aia-option-color` | `#000000` | `#ffffff` | Option text |
|
|
306
|
+
| `--aia-option-color-selected` | `#000000` | `#ffffff` | Highlighted option text |
|
|
307
|
+
| `--aia-option-font-size` | `19px` | `19px` | Option font size |
|
|
308
|
+
| `--aia-written-text-color` | `#000000` | `#ffffff` | Input text |
|
|
309
|
+
| `--aia-written-text-font-size` | `19px` | `19px` | Input text font size |
|
|
310
|
+
| `--aia-caret-color` | `--aia-written-text-color` | `--aia-written-text-color` | Textarea caret color. Override independently of input text color. |
|
|
311
|
+
| `--aia-submit-bg` | `#000000` | `#ffffff` | Submit button background |
|
|
312
|
+
| `--aia-submit-color` | `#ffffff` | `#000000` | Submit button icon color |
|
|
313
|
+
| `--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. |
|
|
314
|
+
| `--aia-scrollbar-thumb` | `rgba(0, 0, 0, 0.3)` | `rgba(0, 0, 0, 0.3)` | Color of the option list's scrollbar thumb (Firefox + WebKit). |
|
|
315
|
+
| `--aia-streak-rgb` | `99, 102, 241` | `255, 255, 255` | Comma-separated RGB triplet used to tint the option-selection streak animation (consumed via `rgba(var(--aia-streak-rgb), …)`). |
|
|
316
|
+
| `--aia-streak-glass-bg` | `rgba(99, 102, 241, 0.1)` | `rgba(255, 255, 255, 0.1)` | Background fill for the streak's glass-pill effect. |
|
|
317
|
+
|
|
318
|
+
### Per-mode Overrides
|
|
319
|
+
|
|
320
|
+
```css
|
|
321
|
+
#my-autocomplete[data-mode="light"] {
|
|
322
|
+
--aia-pill-bg: #e2e8f0;
|
|
323
|
+
}
|
|
324
|
+
#my-autocomplete[data-mode="dark"] {
|
|
325
|
+
--aia-pill-bg: #334155;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Live Preview
|
|
330
|
+
|
|
331
|
+
```ts
|
|
332
|
+
document.getElementById("my-autocomplete")
|
|
333
|
+
.style.setProperty("--aia-pill-bg", newColor);
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Selector Hooks
|
|
337
|
+
|
|
338
|
+
For styling beyond the CSS variables, target these stable `data-aia-*` attributes:
|
|
339
|
+
|
|
340
|
+
| Attribute | Element |
|
|
341
|
+
|---|---|
|
|
342
|
+
| `[data-aia-editor]` | Editor area wrapping the textarea + overlay |
|
|
343
|
+
| `[data-aia-textarea]` | The `<textarea>` |
|
|
344
|
+
| `[data-aia-submit]` | Submit button |
|
|
345
|
+
| `[data-aia-pill]` | Each unfilled-suggestion pill |
|
|
346
|
+
| `[data-aia-pillbar]` | Pill bar container inside the dropdown |
|
|
347
|
+
| `[data-aia-option]` | Each suggestion option |
|
|
348
|
+
| `[data-aia-dropdown]` | The dropdown root (listbox) |
|
|
349
|
+
|
|
350
|
+
The vanilla core also exposes stable BEM class names (`magicx-aia-*`); both can be used.
|
|
351
|
+
|
|
352
|
+
```css
|
|
353
|
+
/* Solid (non-glass) dropdown */
|
|
354
|
+
#my-autocomplete [data-aia-dropdown] {
|
|
355
|
+
background: #fff;
|
|
356
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
357
|
+
backdrop-filter: none;
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Option Overrides
|
|
364
|
+
|
|
365
|
+
```ts
|
|
366
|
+
new AIAutocomplete(el, {
|
|
367
|
+
optionOverrides: {
|
|
368
|
+
account: () => [
|
|
369
|
+
{ text: "Savings", is_tappable: true, kind: null },
|
|
370
|
+
{ text: "Checking", is_tappable: true, kind: null },
|
|
371
|
+
],
|
|
372
|
+
value: (query) => {
|
|
373
|
+
const digits = query.replace(/\D/g, "");
|
|
374
|
+
if (!digits) return [{ text: "$100", is_tappable: true, kind: null }];
|
|
375
|
+
return [{ text: `$${digits}`, is_tappable: true, kind: null }];
|
|
376
|
+
},
|
|
377
|
+
},
|
|
378
|
+
onSubmit: handleSubmit,
|
|
379
|
+
});
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## License
|
|
385
|
+
|
|
386
|
+
Private package. All rights reserved.
|
package/index.d.mts
CHANGED
|
@@ -43,7 +43,7 @@ interface APIConfigBase {
|
|
|
43
43
|
}
|
|
44
44
|
interface APIKeyConfig extends APIConfigBase {
|
|
45
45
|
type?: "apiKey";
|
|
46
|
-
apiKey
|
|
46
|
+
apiKey?: string;
|
|
47
47
|
authScheme?: "Bearer" | "Basic";
|
|
48
48
|
}
|
|
49
49
|
interface AccessTokenConfig extends APIConfigBase {
|
|
@@ -64,14 +64,6 @@ interface AutocompleteResult {
|
|
|
64
64
|
completed_params: CompletedParam[];
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
type Listener<S> = (next: S, prev: S) => void;
|
|
68
|
-
interface Store<S> {
|
|
69
|
-
get: () => S;
|
|
70
|
-
set: (patch: Partial<S> | ((s: S) => Partial<S>)) => void;
|
|
71
|
-
subscribe: (listener: Listener<S>) => () => void;
|
|
72
|
-
}
|
|
73
|
-
declare function createStore<S>(initial: S): Store<S>;
|
|
74
|
-
|
|
75
67
|
/** Internal state managed by the store. */
|
|
76
68
|
interface CoreState {
|
|
77
69
|
text: string;
|
|
@@ -92,19 +84,30 @@ interface CoreState {
|
|
|
92
84
|
pillTapped: boolean;
|
|
93
85
|
skipNextFetch: boolean;
|
|
94
86
|
lastRawQuery: string;
|
|
87
|
+
isFocused: boolean;
|
|
95
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Render mode:
|
|
91
|
+
* - "full" (default) — renders input + dropdown + pills. Tier 1.
|
|
92
|
+
* - "dropdown" — renders only the dropdown with pills. Consumer owns the input. Tier 2.
|
|
93
|
+
* - "headless" — no DOM. State + controllers only. For framework wrappers.
|
|
94
|
+
*/
|
|
95
|
+
type RenderMode = "full" | "dropdown" | "headless";
|
|
96
96
|
/** Options passed to the AIAutocomplete constructor. */
|
|
97
97
|
interface CoreOptions {
|
|
98
98
|
apiConfig?: APIConfig;
|
|
99
99
|
optionOverrides?: OptionOverrides;
|
|
100
100
|
maskCompletedText?: boolean;
|
|
101
|
-
placeholder?: string;
|
|
102
101
|
columns?: number;
|
|
103
|
-
|
|
104
|
-
pillPlacement?: "inline" | "dropdown";
|
|
102
|
+
/** Where pills render in "full" mode. Ignored in "dropdown" mode (always in dropdown). */
|
|
103
|
+
pillPlacement?: "inline" | "dropdown" | "hidden";
|
|
105
104
|
mode?: AppearanceMode;
|
|
106
105
|
optionsPosition?: "above" | "below";
|
|
107
106
|
animations?: boolean;
|
|
107
|
+
/** When the dropdown appears. "auto" = shows when options available. "manual" = only on pill tap. "hidden" = never. Default: "auto". */
|
|
108
|
+
dropdownTrigger?: "auto" | "manual" | "hidden";
|
|
109
|
+
/** Render mode. Default: "full". */
|
|
110
|
+
renderMode?: RenderMode;
|
|
108
111
|
onSubmit?: (result: AutocompleteResult) => void;
|
|
109
112
|
onError?: (error: Error) => void;
|
|
110
113
|
onChange?: (text: string) => void;
|
|
@@ -114,36 +117,23 @@ interface CoreOptions {
|
|
|
114
117
|
completedParams?: CompletedParamState[];
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
interface KeyboardContext {
|
|
118
|
-
columns: number;
|
|
119
|
-
listboxId: string;
|
|
120
|
-
onSubmit?: (result: AutocompleteResult) => void;
|
|
121
|
-
selectOption: (option: SuggestionOption) => void;
|
|
122
|
-
}
|
|
123
|
-
declare class KeyboardController {
|
|
124
|
-
private store;
|
|
125
|
-
private ctx;
|
|
126
|
-
constructor(store: Store<CoreState>, ctx: KeyboardContext);
|
|
127
|
-
handleKeyDown(e: KeyboardEvent): void;
|
|
128
|
-
private getTappableIndices;
|
|
129
|
-
private clickOrSelect;
|
|
130
|
-
/** Delegate to pills controller logic inline (avoids circular dep) */
|
|
131
|
-
private pillsSetActivePill;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
120
|
declare class AIAutocomplete {
|
|
135
121
|
private store;
|
|
136
|
-
private
|
|
122
|
+
private _listboxId;
|
|
137
123
|
private opts;
|
|
138
124
|
private fetchController;
|
|
139
|
-
keyboardController
|
|
125
|
+
private keyboardController;
|
|
140
126
|
private pillsController;
|
|
141
127
|
private modeController;
|
|
142
128
|
private container;
|
|
143
129
|
private unsubscribers;
|
|
144
130
|
private derivedInProgress;
|
|
131
|
+
private renderMode;
|
|
145
132
|
private domRefs;
|
|
133
|
+
private dropdownRefs;
|
|
146
134
|
private newParamTimer;
|
|
135
|
+
private suggestionRemovalTimer;
|
|
136
|
+
private externalListeners;
|
|
147
137
|
constructor(container: HTMLElement, opts?: CoreOptions);
|
|
148
138
|
focus(): void;
|
|
149
139
|
reset(): void;
|
|
@@ -154,15 +144,35 @@ declare class AIAutocomplete {
|
|
|
154
144
|
setActivePill(index: number): void;
|
|
155
145
|
removeLastParam(): void;
|
|
156
146
|
clearNewParamId(): void;
|
|
147
|
+
setActiveDropdownIndex(index: number): void;
|
|
148
|
+
handleTextChange(value: string): void;
|
|
149
|
+
handleKeyDown(e: KeyboardEvent): void;
|
|
150
|
+
setFocused(focused: boolean): void;
|
|
151
|
+
/** Subscribe to state changes (fires after derived state is settled). Returns unsubscribe function. */
|
|
152
|
+
subscribe(listener: (state: CoreState) => void): () => void;
|
|
157
153
|
getState(): CoreState;
|
|
154
|
+
get listboxId(): string;
|
|
158
155
|
get isReady(): boolean;
|
|
159
156
|
on(event: string, callback: (...args: unknown[]) => void): () => void;
|
|
160
157
|
update(opts: Partial<CoreOptions>): void;
|
|
161
|
-
|
|
158
|
+
selectOption(option: SuggestionOption): void;
|
|
162
159
|
private recomputeDerived;
|
|
163
160
|
private setupContainer;
|
|
164
|
-
private
|
|
161
|
+
private buildAndRenderFull;
|
|
162
|
+
private buildAndRenderDropdown;
|
|
163
|
+
/** Batched render subscriber — coalesces multiple store.set calls into one DOM update. */
|
|
164
|
+
private subscribeBatchedRender;
|
|
165
|
+
/** Auto-clear newParamId after shimmer animation. */
|
|
166
|
+
private subscribeNewParamTimer;
|
|
165
167
|
private handleChange;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
|
|
170
|
+
type Listener<S> = (next: S, prev: S) => void;
|
|
171
|
+
interface Store<S> {
|
|
172
|
+
get: () => S;
|
|
173
|
+
set: (patch: Partial<S> | ((s: S) => Partial<S>)) => void;
|
|
174
|
+
subscribe: (listener: Listener<S>) => () => void;
|
|
175
|
+
}
|
|
176
|
+
declare function createStore<S>(initial: S): Store<S>;
|
|
177
|
+
|
|
178
|
+
export { AIAutocomplete, type APIConfig, type APIKeyConfig, type AccessTokenConfig, type AccessTokenResult, type AppearanceMode, type AutocompleteResult, type CompletedParam, type CompletedParamState, type CoreOptions, type CoreState, type RenderMode, type Segment, type Store, type Suggestion, type SuggestionOption, createStore };
|
package/index.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ interface APIConfigBase {
|
|
|
43
43
|
}
|
|
44
44
|
interface APIKeyConfig extends APIConfigBase {
|
|
45
45
|
type?: "apiKey";
|
|
46
|
-
apiKey
|
|
46
|
+
apiKey?: string;
|
|
47
47
|
authScheme?: "Bearer" | "Basic";
|
|
48
48
|
}
|
|
49
49
|
interface AccessTokenConfig extends APIConfigBase {
|
|
@@ -64,14 +64,6 @@ interface AutocompleteResult {
|
|
|
64
64
|
completed_params: CompletedParam[];
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
type Listener<S> = (next: S, prev: S) => void;
|
|
68
|
-
interface Store<S> {
|
|
69
|
-
get: () => S;
|
|
70
|
-
set: (patch: Partial<S> | ((s: S) => Partial<S>)) => void;
|
|
71
|
-
subscribe: (listener: Listener<S>) => () => void;
|
|
72
|
-
}
|
|
73
|
-
declare function createStore<S>(initial: S): Store<S>;
|
|
74
|
-
|
|
75
67
|
/** Internal state managed by the store. */
|
|
76
68
|
interface CoreState {
|
|
77
69
|
text: string;
|
|
@@ -92,19 +84,30 @@ interface CoreState {
|
|
|
92
84
|
pillTapped: boolean;
|
|
93
85
|
skipNextFetch: boolean;
|
|
94
86
|
lastRawQuery: string;
|
|
87
|
+
isFocused: boolean;
|
|
95
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Render mode:
|
|
91
|
+
* - "full" (default) — renders input + dropdown + pills. Tier 1.
|
|
92
|
+
* - "dropdown" — renders only the dropdown with pills. Consumer owns the input. Tier 2.
|
|
93
|
+
* - "headless" — no DOM. State + controllers only. For framework wrappers.
|
|
94
|
+
*/
|
|
95
|
+
type RenderMode = "full" | "dropdown" | "headless";
|
|
96
96
|
/** Options passed to the AIAutocomplete constructor. */
|
|
97
97
|
interface CoreOptions {
|
|
98
98
|
apiConfig?: APIConfig;
|
|
99
99
|
optionOverrides?: OptionOverrides;
|
|
100
100
|
maskCompletedText?: boolean;
|
|
101
|
-
placeholder?: string;
|
|
102
101
|
columns?: number;
|
|
103
|
-
|
|
104
|
-
pillPlacement?: "inline" | "dropdown";
|
|
102
|
+
/** Where pills render in "full" mode. Ignored in "dropdown" mode (always in dropdown). */
|
|
103
|
+
pillPlacement?: "inline" | "dropdown" | "hidden";
|
|
105
104
|
mode?: AppearanceMode;
|
|
106
105
|
optionsPosition?: "above" | "below";
|
|
107
106
|
animations?: boolean;
|
|
107
|
+
/** When the dropdown appears. "auto" = shows when options available. "manual" = only on pill tap. "hidden" = never. Default: "auto". */
|
|
108
|
+
dropdownTrigger?: "auto" | "manual" | "hidden";
|
|
109
|
+
/** Render mode. Default: "full". */
|
|
110
|
+
renderMode?: RenderMode;
|
|
108
111
|
onSubmit?: (result: AutocompleteResult) => void;
|
|
109
112
|
onError?: (error: Error) => void;
|
|
110
113
|
onChange?: (text: string) => void;
|
|
@@ -114,36 +117,23 @@ interface CoreOptions {
|
|
|
114
117
|
completedParams?: CompletedParamState[];
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
interface KeyboardContext {
|
|
118
|
-
columns: number;
|
|
119
|
-
listboxId: string;
|
|
120
|
-
onSubmit?: (result: AutocompleteResult) => void;
|
|
121
|
-
selectOption: (option: SuggestionOption) => void;
|
|
122
|
-
}
|
|
123
|
-
declare class KeyboardController {
|
|
124
|
-
private store;
|
|
125
|
-
private ctx;
|
|
126
|
-
constructor(store: Store<CoreState>, ctx: KeyboardContext);
|
|
127
|
-
handleKeyDown(e: KeyboardEvent): void;
|
|
128
|
-
private getTappableIndices;
|
|
129
|
-
private clickOrSelect;
|
|
130
|
-
/** Delegate to pills controller logic inline (avoids circular dep) */
|
|
131
|
-
private pillsSetActivePill;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
120
|
declare class AIAutocomplete {
|
|
135
121
|
private store;
|
|
136
|
-
private
|
|
122
|
+
private _listboxId;
|
|
137
123
|
private opts;
|
|
138
124
|
private fetchController;
|
|
139
|
-
keyboardController
|
|
125
|
+
private keyboardController;
|
|
140
126
|
private pillsController;
|
|
141
127
|
private modeController;
|
|
142
128
|
private container;
|
|
143
129
|
private unsubscribers;
|
|
144
130
|
private derivedInProgress;
|
|
131
|
+
private renderMode;
|
|
145
132
|
private domRefs;
|
|
133
|
+
private dropdownRefs;
|
|
146
134
|
private newParamTimer;
|
|
135
|
+
private suggestionRemovalTimer;
|
|
136
|
+
private externalListeners;
|
|
147
137
|
constructor(container: HTMLElement, opts?: CoreOptions);
|
|
148
138
|
focus(): void;
|
|
149
139
|
reset(): void;
|
|
@@ -154,15 +144,35 @@ declare class AIAutocomplete {
|
|
|
154
144
|
setActivePill(index: number): void;
|
|
155
145
|
removeLastParam(): void;
|
|
156
146
|
clearNewParamId(): void;
|
|
147
|
+
setActiveDropdownIndex(index: number): void;
|
|
148
|
+
handleTextChange(value: string): void;
|
|
149
|
+
handleKeyDown(e: KeyboardEvent): void;
|
|
150
|
+
setFocused(focused: boolean): void;
|
|
151
|
+
/** Subscribe to state changes (fires after derived state is settled). Returns unsubscribe function. */
|
|
152
|
+
subscribe(listener: (state: CoreState) => void): () => void;
|
|
157
153
|
getState(): CoreState;
|
|
154
|
+
get listboxId(): string;
|
|
158
155
|
get isReady(): boolean;
|
|
159
156
|
on(event: string, callback: (...args: unknown[]) => void): () => void;
|
|
160
157
|
update(opts: Partial<CoreOptions>): void;
|
|
161
|
-
|
|
158
|
+
selectOption(option: SuggestionOption): void;
|
|
162
159
|
private recomputeDerived;
|
|
163
160
|
private setupContainer;
|
|
164
|
-
private
|
|
161
|
+
private buildAndRenderFull;
|
|
162
|
+
private buildAndRenderDropdown;
|
|
163
|
+
/** Batched render subscriber — coalesces multiple store.set calls into one DOM update. */
|
|
164
|
+
private subscribeBatchedRender;
|
|
165
|
+
/** Auto-clear newParamId after shimmer animation. */
|
|
166
|
+
private subscribeNewParamTimer;
|
|
165
167
|
private handleChange;
|
|
166
168
|
}
|
|
167
169
|
|
|
168
|
-
|
|
170
|
+
type Listener<S> = (next: S, prev: S) => void;
|
|
171
|
+
interface Store<S> {
|
|
172
|
+
get: () => S;
|
|
173
|
+
set: (patch: Partial<S> | ((s: S) => Partial<S>)) => void;
|
|
174
|
+
subscribe: (listener: Listener<S>) => () => void;
|
|
175
|
+
}
|
|
176
|
+
declare function createStore<S>(initial: S): Store<S>;
|
|
177
|
+
|
|
178
|
+
export { AIAutocomplete, type APIConfig, type APIKeyConfig, type AccessTokenConfig, type AccessTokenResult, type AppearanceMode, type AutocompleteResult, type CompletedParam, type CompletedParamState, type CoreOptions, type CoreState, type RenderMode, type Segment, type Store, type Suggestion, type SuggestionOption, createStore };
|