@magicx-eng/ai-autocomplete-vanilla 0.1.1 → 0.1.3
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 +244 -140
- package/index.d.mts +53 -34
- package/index.d.ts +53 -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
CHANGED
|
@@ -4,6 +4,7 @@ A framework-agnostic vanilla JS/TypeScript library that provides a guided AI-pow
|
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
|
+
- **Three tiers of integration** — full drop-in component, dropdown-only, or fully headless
|
|
7
8
|
- **Zero dependencies** — no React, no framework, no virtual DOM
|
|
8
9
|
- **Pill-based input** — inline pills for unfilled parameters, completed text for filled ones
|
|
9
10
|
- **Pill placement** — render pills inline in the input or inside the dropdown
|
|
@@ -12,8 +13,7 @@ A framework-agnostic vanilla JS/TypeScript library that provides a guided AI-pow
|
|
|
12
13
|
- **Keyboard navigation** — arrow keys, enter to submit, tab to autocomplete
|
|
13
14
|
- **Client-side filtering** — instant substring filtering on every keystroke
|
|
14
15
|
- **Option overrides** — inject or dynamically generate client-side options per suggestion type
|
|
15
|
-
- **Controlled & uncontrolled** — works out of the box or integrates with external state
|
|
16
|
-
- **Imperative API** — `focus()`, `reset()`, `setMode()`, `destroy()`, and more
|
|
16
|
+
- **Controlled & uncontrolled** — works out of the box or integrates with external state
|
|
17
17
|
- **Accessible** — ARIA combobox pattern with `role="listbox"`, `aria-activedescendant`
|
|
18
18
|
- **Animations** — option selection streak animation, text shimmer on newly added params
|
|
19
19
|
- **Lightweight** — ~10 KB gzipped, styles auto-injected at runtime
|
|
@@ -26,121 +26,219 @@ A framework-agnostic vanilla JS/TypeScript library that provides a guided AI-pow
|
|
|
26
26
|
pnpm add @magicx-eng/ai-autocomplete-vanilla
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
##
|
|
29
|
+
## Three Tiers
|
|
30
30
|
|
|
31
|
-
|
|
32
|
-
|
|
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 |
|
|
33
36
|
|
|
34
|
-
|
|
35
|
-
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
37
|
+
---
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
+
});
|
|
49
57
|
```
|
|
50
58
|
|
|
51
59
|
No CSS import needed — styles are auto-injected on first instantiation.
|
|
52
60
|
|
|
53
|
-
|
|
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
|
+
closeDropdownOnBlur: true, // false = keep dropdown open even when input loses focus
|
|
81
|
+
|
|
82
|
+
// Focus
|
|
83
|
+
autoFocus: true, // focus the input on mount (Tier 1 only)
|
|
84
|
+
|
|
85
|
+
// Events
|
|
86
|
+
onSubmit: (result) => { ... },
|
|
87
|
+
onError: (error) => { ... },
|
|
88
|
+
onChange: (text) => { ... },
|
|
89
|
+
onParamsChange: (params) => { ... },
|
|
90
|
+
onFocus: () => { ... },
|
|
91
|
+
onBlur: () => { ... },
|
|
92
|
+
|
|
93
|
+
// Controlled mode
|
|
94
|
+
value: "initial text",
|
|
95
|
+
completedParams: [],
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Methods
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
ac.focus(); // Focus the input
|
|
103
|
+
ac.blur(); // Blur the input
|
|
104
|
+
ac.reset(); // Clear everything, re-fetch
|
|
105
|
+
ac.destroy(); // Remove DOM, listeners, timers
|
|
106
|
+
ac.setMode("dark"); // Switch color mode
|
|
107
|
+
ac.update({ animations: false, optionsPosition: "above" });
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
> `autoFocus`, `focus()`, and `blur()` only operate on the textarea the library owns (Tier 1 / `renderMode: "full"`). In Tier 2/3, the consumer owns the input and calls `.focus()` / `.blur()` on their own element; the `onFocus` / `onBlur` callbacks still fire whenever `setFocused()` is called.
|
|
111
|
+
|
|
112
|
+
---
|
|
54
113
|
|
|
55
|
-
|
|
114
|
+
## Tier 2: Dropdown Only
|
|
56
115
|
|
|
57
|
-
|
|
58
|
-
<template>
|
|
59
|
-
<div ref="acRef" />
|
|
60
|
-
</template>
|
|
116
|
+
You own the input. The library renders the dropdown with pills inside a container you provide.
|
|
61
117
|
|
|
62
|
-
|
|
63
|
-
import { ref, onMounted, onUnmounted } from "vue";
|
|
118
|
+
```ts
|
|
64
119
|
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
65
120
|
|
|
66
|
-
const
|
|
67
|
-
|
|
121
|
+
const input = document.getElementById("my-input");
|
|
122
|
+
const dropdownContainer = document.getElementById("my-dropdown");
|
|
68
123
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
124
|
+
const ac = new AIAutocomplete(dropdownContainer, {
|
|
125
|
+
renderMode: "dropdown",
|
|
126
|
+
apiConfig: { apiKey: "your_api_key" },
|
|
127
|
+
onChange: (text) => {
|
|
128
|
+
// Keep your input in sync when the library updates text (e.g. option selected)
|
|
129
|
+
if (input.value !== text) input.value = text;
|
|
130
|
+
},
|
|
131
|
+
onSubmit: (result) => console.log(result),
|
|
74
132
|
});
|
|
75
133
|
|
|
76
|
-
|
|
77
|
-
|
|
134
|
+
// Wire your input → library
|
|
135
|
+
input.addEventListener("input", () => ac.handleTextChange(input.value));
|
|
136
|
+
input.addEventListener("keydown", (e) => ac.handleKeyDown(e));
|
|
137
|
+
input.addEventListener("focus", () => ac.setFocused(true));
|
|
138
|
+
input.addEventListener("blur", () => ac.setFocused(false));
|
|
78
139
|
```
|
|
79
140
|
|
|
80
|
-
|
|
141
|
+
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.
|
|
81
142
|
|
|
82
|
-
|
|
83
|
-
<script>
|
|
84
|
-
import { onMount, onDestroy } from "svelte";
|
|
85
|
-
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
143
|
+
> 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.
|
|
86
144
|
|
|
87
|
-
|
|
88
|
-
let instance;
|
|
145
|
+
---
|
|
89
146
|
|
|
90
|
-
|
|
91
|
-
instance = new AIAutocomplete(el, {
|
|
92
|
-
apiConfig: { endpoint: "/ac/suggest", apiKey: "your_key" },
|
|
93
|
-
onSubmit: (result) => console.log(result),
|
|
94
|
-
});
|
|
95
|
-
});
|
|
147
|
+
## Tier 3: Headless
|
|
96
148
|
|
|
97
|
-
|
|
98
|
-
</script>
|
|
149
|
+
No DOM at all. You get state, controllers, and derived data. You render everything yourself.
|
|
99
150
|
|
|
100
|
-
|
|
101
|
-
|
|
151
|
+
This is what framework wrappers (React, Vue, Svelte) use under the hood.
|
|
152
|
+
|
|
153
|
+
```ts
|
|
154
|
+
import { AIAutocomplete } from "@magicx-eng/ai-autocomplete-vanilla";
|
|
102
155
|
|
|
103
|
-
|
|
156
|
+
const ac = new AIAutocomplete(document.createElement("div"), {
|
|
157
|
+
renderMode: "headless",
|
|
158
|
+
apiConfig: { apiKey: "your_api_key" },
|
|
159
|
+
onSubmit: (result) => console.log(result),
|
|
160
|
+
});
|
|
104
161
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
162
|
+
// Read state
|
|
163
|
+
const state = ac.getState();
|
|
164
|
+
// state.text, state.completedParams, state.suggestions,
|
|
165
|
+
// state.segments, state.filteredOptions, state.actionableSuggestions,
|
|
166
|
+
// state.placeholderText, state.isDropdownOpen, state.activeDropdownIndex,
|
|
167
|
+
// state.newParamId, state.isLoading, state.isReady, state.error
|
|
168
|
+
|
|
169
|
+
// Subscribe to changes (fires after derived state is settled)
|
|
170
|
+
const unsub = ac.subscribe((state) => {
|
|
171
|
+
renderMyInput(state.text, state.placeholderText);
|
|
172
|
+
renderMyPills(state.actionableSuggestions);
|
|
173
|
+
renderMyDropdown(state.filteredOptions, state.activeDropdownIndex, state.isDropdownOpen);
|
|
174
|
+
renderMySegments(state.segments, state.newParamId);
|
|
175
|
+
});
|
|
109
176
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
177
|
+
// Forward user actions → library
|
|
178
|
+
myInput.addEventListener("input", () => ac.handleTextChange(myInput.value));
|
|
179
|
+
myInput.addEventListener("keydown", (e) => ac.handleKeyDown(e));
|
|
180
|
+
myInput.addEventListener("focus", () => ac.setFocused(true));
|
|
181
|
+
myInput.addEventListener("blur", () => ac.setFocused(false));
|
|
182
|
+
myOptionEl.addEventListener("click", () => ac.selectOption(option));
|
|
183
|
+
myPillEl.addEventListener("click", () => ac.setActivePill(index));
|
|
184
|
+
|
|
185
|
+
// Cleanup
|
|
186
|
+
ac.destroy();
|
|
187
|
+
unsub();
|
|
115
188
|
```
|
|
116
189
|
|
|
117
|
-
|
|
190
|
+
### State shape (`CoreState`)
|
|
118
191
|
|
|
119
|
-
|
|
192
|
+
| Field | Type | Description |
|
|
193
|
+
|---|---|---|
|
|
194
|
+
| `text` | `string` | Current input text |
|
|
195
|
+
| `completedParams` | `CompletedParamState[]` | Filled parameters |
|
|
196
|
+
| `suggestions` | `Suggestion[]` | All suggestions from server (including placeholder type) |
|
|
197
|
+
| `actionableSuggestions` | `Suggestion[]` | Non-placeholder suggestions (the pills) |
|
|
198
|
+
| `filteredOptions` | `SuggestionOption[]` | Options for the active suggestion, filtered by current query |
|
|
199
|
+
| `segments` | `Segment[]` | Input text split into typed text vs completed params — for overlay rendering |
|
|
200
|
+
| `placeholderText` | `string` | Placeholder text from server suggestions (joined `placeholder`-type suggestion texts) |
|
|
201
|
+
| `activeDropdownIndex` | `number` | Highlighted option index (-1 = none) |
|
|
202
|
+
| `isDropdownOpen` | `boolean` | Whether the dropdown should be visible |
|
|
203
|
+
| `newParamId` | `string \| null` | ID of the most recently added param (for shimmer animation) |
|
|
204
|
+
| `isLoading` | `boolean` | Fetch in progress |
|
|
205
|
+
| `isReady` | `boolean` | Server indicates query is complete |
|
|
206
|
+
| `error` | `Error \| null` | Last fetch error |
|
|
207
|
+
|
|
208
|
+
### Actions
|
|
120
209
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
210
|
+
| Method | Description |
|
|
211
|
+
|---|---|
|
|
212
|
+
| `handleTextChange(value)` | Call when user types. Handles capitalization, param reconciliation, filter updates. |
|
|
213
|
+
| `handleKeyDown(event)` | Forward keyboard events. Handles arrow nav, Enter, Tab, Escape. |
|
|
214
|
+
| `setFocused(focused)` | Notify the library that the input has focus. Required for `dropdownTrigger: "auto"` in Tier 2/3 — the dropdown only opens while focused. |
|
|
215
|
+
| `selectOption(option)` | Select a dropdown option. Updates text, creates completed param, triggers shimmer. |
|
|
216
|
+
| `setActiveDropdownIndex(index)` | Set the highlighted option (for mouse hover). |
|
|
217
|
+
| `setActivePill(index)` | Reorder pills — moves pill at `index` to front (active). |
|
|
218
|
+
| `removeLastParam()` | Remove last completed param, restore it as a pill. |
|
|
219
|
+
| `clearNewParamId()` | Clear shimmer animation state. |
|
|
220
|
+
| `reset()` | Clear all state, re-fetch initial suggestions. |
|
|
221
|
+
| `setValue(text)` | Set text (controlled mode). |
|
|
222
|
+
| `setCompletedParams(params)` | Set completed params (controlled mode). |
|
|
223
|
+
| `setMode(mode)` | Switch color mode at runtime. |
|
|
224
|
+
| `update(opts)` | Update multiple options at once (e.g. `animations`, `pillPlacement`, `dropdownTrigger`, `optionsPosition`). |
|
|
225
|
+
| `getState()` | Get a snapshot of the current state. Useful for initial read before subscribing. |
|
|
226
|
+
| `subscribe(listener)` | Subscribe to state changes (fires after derived state settles). Returns unsubscribe function. |
|
|
227
|
+
| `destroy()` | Cleanup — abort fetches, clear timers, remove listeners. |
|
|
124
228
|
|
|
125
|
-
###
|
|
229
|
+
### Building a framework wrapper
|
|
126
230
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
| `onSubmit?` | `(result: AutocompleteResult) => void` | — | Called on Enter or submit button. |
|
|
139
|
-
| `onError?` | `(error: Error) => void` | — | Called when a fetch fails. |
|
|
140
|
-
| `onChange?` | `(text: string) => void` | — | Called when text changes. |
|
|
141
|
-
| `onParamsChange?` | `(params: CompletedParamState[]) => void` | — | Called when completed params change. |
|
|
142
|
-
| `value?` | `string` | — | Controlled text value. |
|
|
143
|
-
| `completedParams?` | `CompletedParamState[]` | — | Controlled completed params. |
|
|
231
|
+
The pattern for any framework:
|
|
232
|
+
|
|
233
|
+
1. Create a headless instance on mount
|
|
234
|
+
2. Subscribe to state → map to framework reactivity (`useState`, `ref()`, `writable()`)
|
|
235
|
+
3. Render your own components using the state
|
|
236
|
+
4. Forward user events (input, keydown, click) → core actions
|
|
237
|
+
5. Destroy on unmount
|
|
238
|
+
|
|
239
|
+
---
|
|
240
|
+
|
|
241
|
+
## API Reference
|
|
144
242
|
|
|
145
243
|
### `APIConfig`
|
|
146
244
|
|
|
@@ -172,100 +270,104 @@ A discriminated union: `APIKeyConfig | AccessTokenConfig`.
|
|
|
172
270
|
}
|
|
173
271
|
```
|
|
174
272
|
|
|
175
|
-
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.
|
|
273
|
+
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`.
|
|
176
274
|
|
|
177
|
-
###
|
|
275
|
+
### `AutocompleteResult`
|
|
178
276
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
|
182
|
-
|
|
183
|
-
| `
|
|
184
|
-
| `
|
|
185
|
-
| `
|
|
186
|
-
| `setValue(text)` | Set text value (controlled mode). |
|
|
187
|
-
| `setCompletedParams(params)` | Set completed params (controlled mode). |
|
|
188
|
-
| `setActivePill(index)` | Reorder pills, making the one at `index` active. |
|
|
189
|
-
| `removeLastParam()` | Remove the last completed param and restore it as a pill. |
|
|
190
|
-
| `clearNewParamId()` | Clear the shimmer animation state. |
|
|
191
|
-
| `getState()` | Get a snapshot of the full internal state. |
|
|
192
|
-
| `isReady` | `true` when the server indicates the query is ready for execution. |
|
|
277
|
+
The object passed to `onSubmit`:
|
|
278
|
+
|
|
279
|
+
| Field | Type | Description |
|
|
280
|
+
|---|---|---|
|
|
281
|
+
| `query` | `string` | Plain text as the user sees it. |
|
|
282
|
+
| `raw_query` | `string` | Text with placeholder tokens (e.g. `"Create a {{TASK_1}}"`). |
|
|
283
|
+
| `completed_params` | `CompletedParam[]` | Array of filled parameter values. |
|
|
193
284
|
|
|
194
285
|
### Event Subscription
|
|
195
286
|
|
|
196
287
|
```ts
|
|
197
|
-
const off =
|
|
288
|
+
const off = ac.on("submit", (result) => { ... });
|
|
198
289
|
off(); // unsubscribe
|
|
199
290
|
```
|
|
200
291
|
|
|
201
|
-
Events: `submit`, `error`, `change`, `paramsChange`, `
|
|
292
|
+
Events: `submit`, `error`, `change`, `paramsChange`, `focus`, `blur`.
|
|
202
293
|
|
|
203
294
|
Constructor callbacks (`onSubmit`, `onChange`, etc.) are equivalent to calling `on()`.
|
|
204
295
|
|
|
205
|
-
|
|
296
|
+
**`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.
|
|
206
297
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
| Field | Type | Description |
|
|
210
|
-
|---|---|---|
|
|
211
|
-
| `query` | `string` | Plain text as the user sees it. |
|
|
212
|
-
| `raw_query` | `string` | Text with placeholder tokens (e.g. `"Create a {{TASK_1}}"`). |
|
|
213
|
-
| `completed_params` | `CompletedParam[]` | Array of filled parameter values. |
|
|
298
|
+
---
|
|
214
299
|
|
|
215
300
|
## CSS Customization
|
|
216
301
|
|
|
217
|
-
Styles are auto-injected at runtime
|
|
302
|
+
Styles are auto-injected at runtime (Tier 1 and Tier 2). No CSS import needed. The component ships built-in light and dark defaults.
|
|
218
303
|
|
|
219
304
|
### CSS Variables
|
|
220
305
|
|
|
221
|
-
Override these on the container element
|
|
306
|
+
Override these on the container element. All built-in defaults use `:where()` (zero specificity) — your overrides always win without `!important`.
|
|
222
307
|
|
|
223
308
|
| Variable | Default (light) | Default (dark) | Description |
|
|
224
309
|
|---|---|---|---|
|
|
225
|
-
| `--
|
|
226
|
-
| `--
|
|
227
|
-
| `--
|
|
228
|
-
| `--
|
|
229
|
-
| `--
|
|
230
|
-
| `--
|
|
231
|
-
| `--
|
|
232
|
-
| `--
|
|
233
|
-
| `--
|
|
310
|
+
| `--aia-pill-bg` | `#bdbdbd` | `#bdbdbd` | Pill background |
|
|
311
|
+
| `--aia-pill-color` | `#000000` | `#ffffff` | Pill text |
|
|
312
|
+
| `--aia-pill-font-size` | `19px` | `19px` | Pill font size |
|
|
313
|
+
| `--aia-option-bg` | `transparent` | `transparent` | Option background (highlighted) |
|
|
314
|
+
| `--aia-option-color` | `#000000` | `#ffffff` | Option text |
|
|
315
|
+
| `--aia-option-color-selected` | `#000000` | `#ffffff` | Highlighted option text |
|
|
316
|
+
| `--aia-option-font-size` | `19px` | `19px` | Option font size |
|
|
317
|
+
| `--aia-written-text-color` | `#000000` | `#ffffff` | Input text |
|
|
318
|
+
| `--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. |
|
|
320
|
+
| `--aia-submit-bg` | `#000000` | `#ffffff` | Submit button background |
|
|
321
|
+
| `--aia-submit-color` | `#ffffff` | `#000000` | Submit button icon color |
|
|
322
|
+
| `--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. |
|
|
323
|
+
| `--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). |
|
|
324
|
+
| `--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), …)`). |
|
|
325
|
+
| `--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. |
|
|
234
326
|
|
|
235
327
|
### Per-mode Overrides
|
|
236
328
|
|
|
237
329
|
```css
|
|
238
330
|
#my-autocomplete[data-mode="light"] {
|
|
239
|
-
--
|
|
240
|
-
--ac-written-text-color: #1e293b;
|
|
331
|
+
--aia-pill-bg: #e2e8f0;
|
|
241
332
|
}
|
|
242
|
-
|
|
243
333
|
#my-autocomplete[data-mode="dark"] {
|
|
244
|
-
--
|
|
245
|
-
--ac-written-text-color: #f8fafc;
|
|
334
|
+
--aia-pill-bg: #334155;
|
|
246
335
|
}
|
|
247
336
|
```
|
|
248
337
|
|
|
249
338
|
### Live Preview
|
|
250
339
|
|
|
251
|
-
CSS variables update instantly — great for theme editors:
|
|
252
|
-
|
|
253
340
|
```ts
|
|
254
341
|
document.getElementById("my-autocomplete")
|
|
255
|
-
.style.setProperty("--
|
|
342
|
+
.style.setProperty("--aia-pill-bg", newColor);
|
|
256
343
|
```
|
|
257
344
|
|
|
258
|
-
|
|
345
|
+
### Selector Hooks
|
|
259
346
|
|
|
260
|
-
|
|
347
|
+
For styling beyond the CSS variables, target these stable `data-aia-*` attributes:
|
|
348
|
+
|
|
349
|
+
| Attribute | Element |
|
|
261
350
|
|---|---|
|
|
262
|
-
|
|
|
263
|
-
|
|
|
264
|
-
|
|
|
265
|
-
|
|
|
266
|
-
|
|
|
267
|
-
|
|
|
268
|
-
|
|
|
351
|
+
| `[data-aia-editor]` | Editor area wrapping the textarea + overlay |
|
|
352
|
+
| `[data-aia-textarea]` | The `<textarea>` |
|
|
353
|
+
| `[data-aia-submit]` | Submit button |
|
|
354
|
+
| `[data-aia-pill]` | Each unfilled-suggestion pill |
|
|
355
|
+
| `[data-aia-pillbar]` | Pill bar container inside the dropdown |
|
|
356
|
+
| `[data-aia-option]` | Each suggestion option |
|
|
357
|
+
| `[data-aia-dropdown]` | The dropdown root (listbox) |
|
|
358
|
+
|
|
359
|
+
The vanilla core also exposes stable BEM class names (`magicx-aia-*`); both can be used.
|
|
360
|
+
|
|
361
|
+
```css
|
|
362
|
+
/* Solid (non-glass) dropdown */
|
|
363
|
+
#my-autocomplete [data-aia-dropdown] {
|
|
364
|
+
background: #fff;
|
|
365
|
+
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
|
366
|
+
backdrop-filter: none;
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
---
|
|
269
371
|
|
|
270
372
|
## Option Overrides
|
|
271
373
|
|
|
@@ -286,6 +388,8 @@ new AIAutocomplete(el, {
|
|
|
286
388
|
});
|
|
287
389
|
```
|
|
288
390
|
|
|
391
|
+
---
|
|
392
|
+
|
|
289
393
|
## License
|
|
290
394
|
|
|
291
395
|
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,60 +84,67 @@ 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
|
+
/** When true (default), the dropdown closes when the input loses focus. Set to false to keep it open whenever options are available, regardless of focus. */
|
|
110
|
+
closeDropdownOnBlur?: boolean;
|
|
111
|
+
/** Render mode. Default: "full". */
|
|
112
|
+
renderMode?: RenderMode;
|
|
113
|
+
/** Focus the input on mount (Tier 1 / "full" render mode only). Default: true. */
|
|
114
|
+
autoFocus?: boolean;
|
|
108
115
|
onSubmit?: (result: AutocompleteResult) => void;
|
|
109
116
|
onError?: (error: Error) => void;
|
|
110
117
|
onChange?: (text: string) => void;
|
|
111
118
|
onParamsChange?: (params: CompletedParamState[]) => void;
|
|
112
119
|
onStateChange?: (state: CoreState) => void;
|
|
120
|
+
/** Called when the input gains focus (or `setFocused(true)` is called). */
|
|
121
|
+
onFocus?: () => void;
|
|
122
|
+
/** Called when the input loses focus (or `setFocused(false)` is called). */
|
|
123
|
+
onBlur?: () => void;
|
|
113
124
|
value?: string;
|
|
114
125
|
completedParams?: CompletedParamState[];
|
|
115
126
|
}
|
|
116
127
|
|
|
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
128
|
declare class AIAutocomplete {
|
|
135
129
|
private store;
|
|
136
|
-
private
|
|
130
|
+
private _listboxId;
|
|
137
131
|
private opts;
|
|
138
132
|
private fetchController;
|
|
139
|
-
keyboardController
|
|
133
|
+
private keyboardController;
|
|
140
134
|
private pillsController;
|
|
141
135
|
private modeController;
|
|
142
136
|
private container;
|
|
143
137
|
private unsubscribers;
|
|
144
138
|
private derivedInProgress;
|
|
139
|
+
private renderMode;
|
|
145
140
|
private domRefs;
|
|
141
|
+
private dropdownRefs;
|
|
146
142
|
private newParamTimer;
|
|
143
|
+
private suggestionRemovalTimer;
|
|
144
|
+
private externalListeners;
|
|
147
145
|
constructor(container: HTMLElement, opts?: CoreOptions);
|
|
148
146
|
focus(): void;
|
|
147
|
+
blur(): void;
|
|
149
148
|
reset(): void;
|
|
150
149
|
destroy(): void;
|
|
151
150
|
setMode(mode: AppearanceMode): void;
|
|
@@ -154,15 +153,35 @@ declare class AIAutocomplete {
|
|
|
154
153
|
setActivePill(index: number): void;
|
|
155
154
|
removeLastParam(): void;
|
|
156
155
|
clearNewParamId(): void;
|
|
156
|
+
setActiveDropdownIndex(index: number): void;
|
|
157
|
+
handleTextChange(value: string): void;
|
|
158
|
+
handleKeyDown(e: KeyboardEvent): void;
|
|
159
|
+
setFocused(focused: boolean): void;
|
|
160
|
+
/** Subscribe to state changes (fires after derived state is settled). Returns unsubscribe function. */
|
|
161
|
+
subscribe(listener: (state: CoreState) => void): () => void;
|
|
157
162
|
getState(): CoreState;
|
|
163
|
+
get listboxId(): string;
|
|
158
164
|
get isReady(): boolean;
|
|
159
165
|
on(event: string, callback: (...args: unknown[]) => void): () => void;
|
|
160
166
|
update(opts: Partial<CoreOptions>): void;
|
|
161
|
-
|
|
167
|
+
selectOption(option: SuggestionOption): void;
|
|
162
168
|
private recomputeDerived;
|
|
163
169
|
private setupContainer;
|
|
164
|
-
private
|
|
170
|
+
private buildAndRenderFull;
|
|
171
|
+
private buildAndRenderDropdown;
|
|
172
|
+
/** Batched render subscriber — coalesces multiple store.set calls into one DOM update. */
|
|
173
|
+
private subscribeBatchedRender;
|
|
174
|
+
/** Auto-clear newParamId after shimmer animation. */
|
|
175
|
+
private subscribeNewParamTimer;
|
|
165
176
|
private handleChange;
|
|
166
177
|
}
|
|
167
178
|
|
|
168
|
-
|
|
179
|
+
type Listener<S> = (next: S, prev: S) => void;
|
|
180
|
+
interface Store<S> {
|
|
181
|
+
get: () => S;
|
|
182
|
+
set: (patch: Partial<S> | ((s: S) => Partial<S>)) => void;
|
|
183
|
+
subscribe: (listener: Listener<S>) => () => void;
|
|
184
|
+
}
|
|
185
|
+
declare function createStore<S>(initial: S): Store<S>;
|
|
186
|
+
|
|
187
|
+
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 };
|