@samline/autocomplete 1.0.0 → 2.0.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.
Files changed (46) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/LICENSE.md +1 -1
  3. package/README.md +208 -32
  4. package/dist/browser/global.global.js +505 -274
  5. package/dist/browser/global.global.js.map +1 -1
  6. package/dist/browser/index.cjs +663 -0
  7. package/dist/browser/index.cjs.map +1 -0
  8. package/dist/browser/index.d.cts +31 -0
  9. package/dist/browser/index.d.ts +31 -0
  10. package/dist/browser/index.js +24 -0
  11. package/dist/browser/index.js.map +1 -0
  12. package/dist/chunk-4IPBCQDZ.js +218 -0
  13. package/dist/chunk-4IPBCQDZ.js.map +1 -0
  14. package/dist/chunk-5UZVFHB4.js +70 -0
  15. package/dist/chunk-5UZVFHB4.js.map +1 -0
  16. package/dist/chunk-AD7T42HJ.js +1 -0
  17. package/dist/chunk-AD7T42HJ.js.map +1 -0
  18. package/dist/chunk-PPDQPLPE.js +1 -0
  19. package/dist/chunk-PPDQPLPE.js.map +1 -0
  20. package/dist/chunk-U5JYDGKW.js +403 -0
  21. package/dist/chunk-U5JYDGKW.js.map +1 -0
  22. package/dist/core/index.cjs +181 -0
  23. package/dist/core/index.cjs.map +1 -0
  24. package/dist/core/index.d.cts +31 -0
  25. package/dist/core/index.d.ts +31 -0
  26. package/dist/core/index.js +22 -0
  27. package/dist/core/index.js.map +1 -0
  28. package/dist/index.cjs +527 -274
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +20 -69
  31. package/dist/index.d.ts +20 -69
  32. package/dist/index.js +54 -405
  33. package/dist/index.js.map +1 -1
  34. package/dist/render-B38UXYCT.d.ts +7 -0
  35. package/dist/render-WD69VMUs.d.cts +7 -0
  36. package/dist/styles.css +19 -9
  37. package/dist/types-CPvjhqjb.d.cts +118 -0
  38. package/dist/types-CPvjhqjb.d.ts +118 -0
  39. package/dist/vanilla/index.cjs +598 -0
  40. package/dist/vanilla/index.cjs.map +1 -0
  41. package/dist/vanilla/index.d.cts +11 -0
  42. package/dist/vanilla/index.d.ts +11 -0
  43. package/dist/vanilla/index.js +13 -0
  44. package/dist/vanilla/index.js.map +1 -0
  45. package/package.json +19 -4
  46. package/dist/browser/global.d.ts +0 -61
package/CHANGELOG.md CHANGED
@@ -2,6 +2,34 @@
2
2
 
3
3
  All notable changes to `@samline/autocomplete` are documented in this file. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
4
 
5
+ ## [2.0.0] - Unreleased
6
+
7
+ Sources beyond HTTP, caching, highlighting, a stricter listbox and the registry shared by the other `@samline` packages.
8
+
9
+ ### Breaking
10
+
11
+ - The browser registry follows the shared `@samline` shape: `newAutocomplete({ id, options })`, `getAutocomplete(id)`, `getAutocompletes()`, `destroyAutocomplete(idOrInput)` and `destroyAutocompletes()` replace `create(target, options)` and `destroy(id)`. `newAutocomplete` logs and returns `null` instead of throwing when the id or input is missing.
12
+ - `@samline/autocomplete/browser` is now the registry as an ESM/CommonJS module. The standalone script moved to `@samline/autocomplete/browser/global` (the file is still `dist/browser/global.global.js`).
13
+ - Options are `<div role="option">` elements instead of `<button>`, so they are no longer Tab stops. `renderOption` receives an `HTMLElement` and a third `context` argument.
14
+ - The listbox now sits inside `[data-autocomplete-popup]`, which carries the positioning styles. Loading, empty and error messages render in `[data-autocomplete-status]` outside the listbox, instead of inside it with `role="status"` / `role="alert"`; a separate polite live region announces them. Update selectors that targeted `[data-autocomplete-list]` for positioning.
15
+ - Labels are highlighted by default. Set `highlight: false` for plain text.
16
+ - Function and endpoint results are cached per query for five minutes by default. Set `cache: false` for the previous behavior.
17
+ - `createAutocompleteController` is removed; use `createAutocomplete` or `autocomplete`.
18
+ - The default label and value also read a `name` field.
19
+
20
+ ### Added
21
+
22
+ - `source`: a local array filtered in the browser, or `(query, { signal, input }) => items`, as an alternative to `endpoint`. Array sources default to `minLength: 1` and `debounce: 0`.
23
+ - `filter` and `normalize` options; matching ignores case and accents by default.
24
+ - `cache` (`true`, `false` or `{ max, ttl }`) and `controller.clearCache()`. `autocomplete:load` reports `cached`.
25
+ - `highlight` option and `<mark data-autocomplete-highlight>` rendering. `renderOption` context `{ query, index, label, highlight }`.
26
+ - `messages.results` and message functions of `{ query, count }` for translations and plurals.
27
+ - Controller `popupElement`, `statusElement` and `liveElement`. The listbox is labelled by the input's `<label>`.
28
+ - Helpers `fromEndpoint`, `filterSuggestions`, `normalizeText`, `highlight`, `renderHighlight`, `createCache` and `DEFAULT_MESSAGES`.
29
+ - Entrypoints `@samline/autocomplete/core`, `@samline/autocomplete/vanilla` and the `style.css` alias.
30
+ - Stylesheet: highlight custom properties and a forced-colors outline for the active option.
31
+ - A "Local data" example.
32
+
5
33
  ## [1.0.0] - 2026-11-13
6
34
 
7
35
  First public release.
@@ -20,4 +48,5 @@ First public release.
20
48
  - Escape, Tab, clicking outside, selecting a suggestion and `close()` cancel the pending debounced search and abort the request in flight. Before, a late response reopened the list after it was dismissed.
21
49
  - The standalone script defines `window.Autocomplete` with its documented members. Before, the global was wrapped as `{ default }`.
22
50
 
51
+ [2.0.0]: https://github.com/samline/autocomplete/compare/v1.0.0...HEAD
23
52
  [1.0.0]: https://github.com/samline/autocomplete/releases/tag/v1.0.0
package/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 samline
3
+ Copyright (c) 2026 Samuel Olvera (samline)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @samline/autocomplete
2
2
 
3
- An accessible, framework-agnostic remote autocomplete (combobox) for vanilla JavaScript, TypeScript and plain HTML. Point an input at your endpoint; debounce, cancellation, keyboard navigation, events and cleanup are handled for you.
3
+ An accessible, framework-agnostic autocomplete (combobox) for vanilla JavaScript, TypeScript and plain HTML. Point an input at an endpoint, an async function or a local array; debounce, cancellation, caching, accent-insensitive matching, highlighting, keyboard navigation, screen reader announcements, events and cleanup are handled for you.
4
4
 
5
5
  [Demo](https://samline.github.io/autocomplete/) · [Examples](https://samline.github.io/autocomplete/examples/) · [Documentation](https://samline.github.io/autocomplete/docs/) · [Changelog](CHANGELOG.md)
6
6
 
@@ -30,7 +30,13 @@ pnpm add @samline/autocomplete
30
30
  bun add @samline/autocomplete
31
31
  ```
32
32
 
33
- Works in modern browsers with `fetch` and `AbortController`, and imports in Node 20 or newer. No runtime dependencies; the stylesheet is optional.
33
+ Requires Node 20+ when bundling. Runs in modern browsers with `fetch` and `AbortController`. No runtime dependencies.
34
+
35
+ Import the optional stylesheet once in your application entrypoint:
36
+
37
+ ```js
38
+ import '@samline/autocomplete/styles.css'
39
+ ```
34
40
 
35
41
  ## Quick start
36
42
 
@@ -52,69 +58,239 @@ const controller = createAutocomplete('#city', {
52
58
  onSelect: city => console.log('Selected', city),
53
59
  })
54
60
 
55
- controller.destroy() // removes the list and restores the input
61
+ // When the owning component or page leaves:
62
+ controller.destroy()
56
63
  ```
57
64
 
65
+ What this does:
66
+
67
+ - Turns the existing input into an ARIA combobox and renders a popup with a listbox right after it.
68
+ - Searches once the query reaches two characters, 250 ms after typing stops, and caches each query's results for five minutes.
69
+ - Reads a root array, or a `suggestions`, `items` or `data` array, from the JSON response.
70
+ - Highlights the typed words in each label and announces the result count to screen readers.
71
+ - Writes the chosen suggestion into the input, dispatches `autocomplete:select` and calls `onSelect`.
72
+ - Restores the input attributes it changed and removes the list when `destroy()` runs.
73
+
74
+ The wrapper needs `position: relative` because the default popup is absolutely positioned under the input.
75
+
58
76
  ## HTML / CDN
59
77
 
60
- For Shopify, WordPress or plain HTML pages without a build step, load the stylesheet and the browser bundle. Pin the version in production.
78
+ Choose **Package / ESM** when installing with npm, pnpm or bun and importing Autocomplete in your application. Choose **HTML / CDN** for Shopify, WordPress or plain HTML pages without a build step. Both use the same options and controller.
61
79
 
62
80
  ```html
63
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@samline/autocomplete@1.0.0/dist/styles.css" />
64
- <script src="https://cdn.jsdelivr.net/npm/@samline/autocomplete@1.0.0/dist/browser/global.global.js"></script>
81
+ <link
82
+ rel="stylesheet"
83
+ href="https://cdn.jsdelivr.net/npm/@samline/autocomplete@2.0.0/dist/styles.css"
84
+ />
85
+ <script src="https://cdn.jsdelivr.net/npm/@samline/autocomplete@2.0.0/dist/browser/global.global.js"></script>
86
+ ```
87
+
88
+ > Pin the version in production, or self-host the same files.
89
+
90
+ The standalone script (an IIFE) defines `window.Autocomplete`:
91
+
92
+ ```html
93
+ <div class="field" style="position: relative">
94
+ <label for="city">City</label>
95
+ <input id="city" type="text" />
96
+ </div>
97
+
65
98
  <script>
66
- window.Autocomplete.create('#city', { endpoint: '/api/cities' })
99
+ const city = window.Autocomplete.newAutocomplete({
100
+ id: 'city',
101
+ options: { endpoint: '/api/cities' },
102
+ })
67
103
  </script>
68
104
  ```
69
105
 
70
- `window.Autocomplete` has `create(target, options)` (requires an input id and registers the controller), `destroy(id)`, `available` and `autocomplete(target, options)`.
106
+ The global follows the same registry shape as the other `@samline` packages:
107
+
108
+ | Member | Purpose |
109
+ | ---------------------------------------- | ------------------------------------------------------------------ |
110
+ | `newAutocomplete({ id, options })` | Enhances the input with that id; replaces a previous registration. |
111
+ | `getAutocomplete(id)` | The registered controller, or `null`. |
112
+ | `getAutocompletes()` | A copy of every registered controller by id. |
113
+ | `destroyAutocomplete(idOrInput)` | Destroys one registered controller. |
114
+ | `destroyAutocompletes()` | Destroys them all. |
115
+ | `available`, `autocomplete(input, opts)` | The live registry, and a factory that bypasses it. |
116
+
117
+ `newAutocomplete` logs and returns `null` when the id is empty or no input has it.
71
118
 
72
119
  ## Capabilities
73
120
 
121
+ - Three kinds of source: an HTTP endpoint, an async function (SDKs, GraphQL, workers) or a local array.
122
+ - Any response shape through `parseResponse`, `mapResponse`, `getId`, `getLabel` and `getValue`.
123
+ - Accent- and case-insensitive matching: `merida` finds `Mérida`.
124
+ - Highlighted matches rendered as `<mark>` elements, never HTML strings.
125
+ - A per-query LRU cache with expiry for function and endpoint sources.
126
+ - WAI-ARIA combobox and listbox with full keyboard operation and a polite live region.
127
+ - Debounced searches, aborted superseded requests and ignored late responses.
128
+ - Bubbling DOM events that keep hidden fields and other UI in sync.
129
+ - An optional stylesheet themed with `--autocomplete-*` custom properties.
130
+ - React, Vue, Svelte, Shopify and WordPress through the same API, with no adapter.
131
+
132
+ [Working examples](https://samline.github.io/autocomplete/examples/) cover remote search, custom options, events and local data, each with copyable files.
133
+
134
+ ### Any source
135
+
136
+ Pass exactly one of `source` or `endpoint`.
137
+
138
+ ```js
139
+ // A local array, filtered in the browser (minLength 1, no debounce by default).
140
+ createAutocomplete('#state', { source: ['Michoacán', 'Nuevo León', 'Querétaro'] })
141
+
142
+ // Any async function. Pass the signal on so dismissed searches abort.
143
+ createAutocomplete('#user', {
144
+ source: (query, { signal }) => api.users.search(query, { signal }),
145
+ getLabel: user => user.name,
146
+ })
147
+ ```
148
+
149
+ `filter(item, query)` replaces the local match, and `normalize(text)` replaces the accent- and case-insensitive comparison used by the filter and the highlighting.
150
+
74
151
  ### Any endpoint, any response
75
152
 
76
- `endpoint` is a URL or a function of the query. `requestInit` adds headers or credentials, `parseResponse` replaces JSON parsing, and `mapResponse`, `getId`, `getLabel` and `getValue` adapt whatever your API returns.
153
+ `endpoint` is a URL or a function of the query. `queryParam` names the query-string key (`q` by default; `null` when the function builds the complete URL). `requestInit` adds headers or credentials, `parseResponse` replaces JSON parsing, and `mapResponse` returns the suggestion array.
154
+
155
+ ```js
156
+ createAutocomplete('#city', {
157
+ endpoint: query => `/api/search?term=${encodeURIComponent(query)}`,
158
+ queryParam: null,
159
+ requestInit: { headers: { Authorization: `Bearer ${token}` } },
160
+ mapResponse: payload => payload.data.items,
161
+ getLabel: item => item.title,
162
+ })
163
+ ```
164
+
165
+ ### Caching
166
+
167
+ Function and endpoint results are cached per query: up to 50 queries for five minutes, least recently used first out. Failures are never cached.
168
+
169
+ ```js
170
+ createAutocomplete('#city', { endpoint: '/api/cities', cache: { max: 100, ttl: 60_000 } })
171
+ createAutocomplete('#stock', { endpoint: '/api/stock', cache: false })
172
+ ```
173
+
174
+ Call `controller.clearCache()` after the data changes. `autocomplete:load` reports `cached: true` for cache hits.
175
+
176
+ ### Highlighting and custom options
177
+
178
+ Each label is split into matching and non-matching parts and rendered with `<mark data-autocomplete-highlight>`; set `highlight: false` to render plain text. `renderOption(element, item, context)` receives `{ query, index, label, highlight }` to append your own DOM nodes, never HTML strings.
179
+
180
+ ```js
181
+ import { createAutocomplete, renderHighlight } from '@samline/autocomplete'
182
+
183
+ createAutocomplete('#city', {
184
+ endpoint: '/api/cities',
185
+ renderOption: (option, city, { highlight }) => {
186
+ option.replaceChildren()
187
+ const name = document.createElement('strong')
188
+ renderHighlight(name, highlight)
189
+ const country = document.createElement('small')
190
+ country.textContent = city.country
191
+ option.append(name, country)
192
+ },
193
+ })
194
+ ```
195
+
196
+ `highlight(text, query)`, `filterSuggestions(items, query, { getText })` and `normalizeText(text)` are exported for your own rendering and filtering.
77
197
 
78
198
  ### An accessible combobox
79
199
 
80
- ARIA `combobox` and `listbox` roles, `aria-expanded` and `aria-activedescendant`, status and error messages, and ArrowUp, ArrowDown, Home, End, Enter, Escape and Tab. Searches wait for IME composition to finish.
200
+ ARIA `combobox` and `listbox` roles, `aria-expanded`, `aria-activedescendant` and a labelled listbox, and ArrowUp, ArrowDown, Home, End, Enter, Escape and Tab. Options are non-focusable `role="option"` elements. Loading, empty and error states are shown outside the listbox, and results, empty and error messages are announced through a polite live region. Searches wait for IME composition to finish.
81
201
 
82
- ### No stale results
202
+ Every message is a string or a function of `{ query, count }`:
83
203
 
84
- Searches are debounced, superseded requests are aborted and late responses ignored. Closing the list (Escape, Tab, clicking outside, selecting or `close()`) cancels the pending search.
204
+ ```js
205
+ createAutocomplete('#ciudad', {
206
+ endpoint: '/api/ciudades',
207
+ messages: {
208
+ loading: 'Buscando…',
209
+ empty: ({ query }) => `Sin resultados para «${query}»`,
210
+ error: 'La búsqueda falló',
211
+ results: ({ count }) => (count === 1 ? '1 resultado' : `${count} resultados`),
212
+ },
213
+ })
214
+ ```
85
215
 
86
- ### Events and styling
216
+ ### No stale results
87
217
 
88
- `autocomplete:select`, `autocomplete:clear`, `autocomplete:load`, `autocomplete:loadstart` and `autocomplete:error` bubble from the input. The optional stylesheet is themed with `--autocomplete-*` custom properties, and `renderOption` adds your own content to each option.
218
+ Searches are debounced, superseded requests are aborted and late responses ignored. Closing the list (Escape, Tab, clicking outside, selecting or `close()`) cancels the pending search.
89
219
 
90
220
  ## API at a glance
91
221
 
92
- | API | Purpose |
93
- | --- | --- |
94
- | `createAutocomplete(input, options)` | Attach the combobox and return a controller (`autocomplete` is an alias). |
95
- | `controller.search(query?)` / `close()` / `clear()` / `cancel()` | Run a search now, close the list, clear everything or cancel pending work. |
96
- | `controller.getSelected()` / `destroy()` | Read the selected suggestion or remove the combobox. |
97
- | `mapCommonResponse`, `parseJsonResponse` | The default response mapper and parser. |
222
+ | API | Purpose |
223
+ | ---------------------------------------- | ------------------------------------------------------ |
224
+ | `createAutocomplete(input, options)` | Controller factory (`autocomplete` is an alias). |
225
+ | `search(query?)`, `close()`, `cancel()` | Run a search now, close the list, cancel pending work. |
226
+ | `clear()`, `getSelected()` | Clear input and selection, or read the selected item. |
227
+ | `clearCache()` | Forget cached results. |
228
+ | `destroy()` | Cleanup. |
229
+ | `fromEndpoint(options)` | Build a reusable endpoint source function. |
230
+ | `highlight`, `renderHighlight` | Split a label into matches, and render the parts. |
231
+ | `filterSuggestions`, `normalizeText` | The default local filter and text normalizer. |
232
+ | `createCache(options?)` | The LRU cache used per instance. |
233
+ | `mapCommonResponse`, `parseJsonResponse` | The default response mapper and parser. |
234
+ | `Autocomplete`, `browser` | Importable equivalent of `window.Autocomplete`. |
235
+
236
+ The controller also exposes `element` (the input), `popupElement`, `listElement` (the listbox), `statusElement`, `liveElement` and `options`.
98
237
 
99
238
  ### Entrypoints
100
239
 
101
- | Entrypoint | When to use |
102
- | --- | --- |
103
- | `@samline/autocomplete` | ESM or CommonJS: `createAutocomplete`, helpers, `browser` registry and types. |
104
- | `@samline/autocomplete/browser` | Classic script that defines `window.Autocomplete`. |
105
- | `@samline/autocomplete/styles.css` | Optional base stylesheet. |
240
+ | Entrypoint | When to use |
241
+ | -------------------------------------- | ------------------------------------------------------------- |
242
+ | `@samline/autocomplete` | ESM or CommonJS: everything below, side-effect free. |
243
+ | `@samline/autocomplete/core` | DOM-free helpers: sources, cache, filtering, highlighting. |
244
+ | `@samline/autocomplete/vanilla` | The DOM factory and `renderHighlight`. |
245
+ | `@samline/autocomplete/browser` | The registry as a module (`Autocomplete`, `newAutocomplete`). |
246
+ | `@samline/autocomplete/browser/global` | Standalone script (IIFE) that defines `window.Autocomplete`. |
247
+ | `@samline/autocomplete/styles.css` | Optional base stylesheet (also `style.css`). |
248
+
249
+ ### Events
250
+
251
+ All events bubble from the input.
252
+
253
+ | Event | `detail` |
254
+ | ------------------------ | -------------------------------- |
255
+ | `autocomplete:loadstart` | `{ query }` |
256
+ | `autocomplete:load` | `{ query, suggestions, cached }` |
257
+ | `autocomplete:error` | `{ query, error }` |
258
+ | `autocomplete:select` | `{ suggestion, label, value }` |
259
+ | `autocomplete:clear` | `{ query }` |
260
+
261
+ ```js
262
+ const input = document.querySelector('#city')
263
+ const hidden = document.querySelector('#city-id')
264
+
265
+ input.addEventListener('autocomplete:select', event => {
266
+ hidden.value = event.detail.suggestion.id
267
+ })
268
+ input.addEventListener('autocomplete:clear', () => (hidden.value = ''))
269
+ ```
270
+
271
+ ### One lifecycle
272
+
273
+ - Each `createAutocomplete()` call owns one input. Keep the returned controller.
274
+ - `close()` and `cancel()` keep the controller usable; `clear()` also empties the input and dispatches `autocomplete:clear` when something was selected.
275
+ - `destroy()` aborts pending work, removes the list and listeners, and restores the input attributes. Repeated calls are harmless.
276
+ - With the registry, `newAutocomplete()` on an id that is already registered destroys the previous controller first, and a direct `destroy()` also unregisters it.
277
+
278
+ ### TypeScript
279
+
280
+ The package includes declarations. Pass your suggestion type to `createAutocomplete<T>()` to type `getLabel`, `onSelect`, `renderOption` and `getSelected()`. `AutocompleteOptions<T>`, `AutocompleteController<T>` and the event detail types are exported; see the [API reference](https://samline.github.io/autocomplete/docs/reference/api/#types).
106
281
 
107
282
  ## Documentation
108
283
 
109
284
  Read the [documentation](https://samline.github.io/autocomplete/docs/) for the full guides and API contracts, and explore [working examples](https://samline.github.io/autocomplete/examples/) with complete and separate source files.
110
285
 
111
- | Guide | What it covers |
112
- | --- | --- |
113
- | [Installation](https://samline.github.io/autocomplete/docs/getting-started/) | Package / ESM, HTML / CDN and working starter files. |
114
- | [Options](https://samline.github.io/autocomplete/docs/reference/configuration/) | Every option and its default. |
115
- | [API reference](https://samline.github.io/autocomplete/docs/reference/api/) | The controller, helpers, browser global and events. |
116
- | [Accessibility](https://samline.github.io/autocomplete/docs/guides/accessibility/) | What the combobox provides and what your page provides. |
117
- | [Styling](https://samline.github.io/autocomplete/docs/guides/styling/) | The DOM contract and the custom properties. |
286
+ | Guide | What it covers |
287
+ | ---------------------------------------------------------------------------------- | -------------------------------------------------------- |
288
+ | [Installation](https://samline.github.io/autocomplete/docs/getting-started/) | Package / ESM, HTML / CDN, frameworks and starter files. |
289
+ | [Examples](https://samline.github.io/autocomplete/docs/guides/examples/) | Live previews with their complete, copyable source. |
290
+ | [Options](https://samline.github.io/autocomplete/docs/reference/configuration/) | Every option and its default. |
291
+ | [API reference](https://samline.github.io/autocomplete/docs/reference/api/) | The controller, helpers, browser global, events, types. |
292
+ | [Accessibility](https://samline.github.io/autocomplete/docs/guides/accessibility/) | What the combobox provides and what your page provides. |
293
+ | [Styling](https://samline.github.io/autocomplete/docs/guides/styling/) | The DOM contract and the custom properties. |
118
294
 
119
295
  ## Changelog
120
296