@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/dist/index.js CHANGED
@@ -1,412 +1,61 @@
1
- // src/core/defaults.ts
2
- var DEFAULT_MESSAGES = {
3
- loading: "Searching...",
4
- empty: "No results",
5
- error: "Search failed"
6
- };
7
- var parseJsonResponse = async (response) => response.json();
8
- var mapCommonResponse = (payload) => {
9
- if (Array.isArray(payload)) return payload;
10
- if (!payload || typeof payload !== "object") throw invalidPayloadError();
11
- const record = payload;
12
- if (Array.isArray(record.suggestions)) return record.suggestions;
13
- if (Array.isArray(record.items)) return record.items;
14
- if (Array.isArray(record.data)) return record.data;
15
- throw invalidPayloadError();
16
- };
17
- function getDefaultLabel(suggestion) {
18
- if (typeof suggestion === "string") return suggestion;
19
- if (!suggestion || typeof suggestion !== "object") return String(suggestion ?? "");
20
- const record = suggestion;
21
- return String(record.label ?? record.value ?? record.title ?? "");
22
- }
23
- function getDefaultValue(suggestion) {
24
- if (typeof suggestion === "string") return suggestion;
25
- if (!suggestion || typeof suggestion !== "object") return String(suggestion ?? "");
26
- const record = suggestion;
27
- return String(record.value ?? record.label ?? record.title ?? "");
28
- }
29
- function getDefaultId(suggestion, index) {
30
- if (!suggestion || typeof suggestion !== "object") return index;
31
- return suggestion.id ?? index;
32
- }
33
- function validateOptions(options) {
34
- if (!options.endpoint) throw new TypeError("Autocomplete requires an endpoint.");
35
- assertNonNegativeInteger(options.minLength, "minLength");
36
- assertNonNegativeInteger(options.debounce, "debounce");
37
- if (options.maxResults !== void 0 && (!Number.isInteger(options.maxResults) || options.maxResults < 1)) {
38
- throw new TypeError("Autocomplete maxResults must be an integer greater than zero.");
39
- }
40
- }
41
- function assertNonNegativeInteger(value, name) {
42
- if (value !== void 0 && (!Number.isInteger(value) || value < 0)) {
43
- throw new TypeError(`Autocomplete ${name} must be a non-negative integer.`);
44
- }
45
- }
46
- function invalidPayloadError() {
47
- return new TypeError(
48
- "Autocomplete response does not contain an array. Configure mapResponse(payload, response, query)."
49
- );
50
- }
51
-
52
- // src/core/dom.ts
53
- function resolveInput(target) {
54
- const element = typeof target === "string" ? document.querySelector(target) : target;
55
- if (!(element instanceof HTMLInputElement)) {
56
- throw new TypeError("Autocomplete requires an HTMLInputElement or a valid selector.");
57
- }
58
- return element;
59
- }
60
- function sanitizeId(value) {
61
- return String(value).replace(/[^a-zA-Z0-9_-]/g, "-");
62
- }
63
- function restoreAttribute(element, name, value) {
64
- if (value === null) {
65
- element.removeAttribute(name);
66
- return;
67
- }
68
- element.setAttribute(name, value);
69
- }
70
-
71
- // src/core/controller.ts
72
- var instanceCount = 0;
73
- function createAutocompleteController(target, options) {
74
- validateOptions(options);
75
- const input = resolveInput(target);
76
- const instanceId = `autocomplete-${++instanceCount}`;
77
- const messages = { ...DEFAULT_MESSAGES, ...options.messages };
78
- const mapResponse = options.mapResponse ?? mapCommonResponse;
79
- const settings = {
80
- queryParam: options.queryParam === void 0 ? "q" : options.queryParam,
81
- requestInit: options.requestInit ?? {},
82
- parseResponse: options.parseResponse ?? parseJsonResponse,
83
- mapResponse,
84
- getId: options.getId ?? getDefaultId,
85
- getLabel: options.getLabel ?? getDefaultLabel,
86
- getValue: options.getValue ?? getDefaultValue,
87
- renderOption: options.renderOption,
88
- minLength: options.minLength ?? 2,
89
- debounce: options.debounce ?? 250,
90
- maxResults: options.maxResults ?? 8,
91
- searchOnFocus: options.searchOnFocus ?? true,
92
- onSelect: options.onSelect,
93
- onError: options.onError
94
- };
95
- const list = document.createElement("div");
96
- const originalAttributes = /* @__PURE__ */ new Map();
97
- let suggestions = [];
98
- let activeIndex = -1;
99
- let selected = null;
100
- let debounceTimer;
101
- let abortController = null;
102
- let requestSequence = 0;
103
- let destroyed = false;
104
- let suppressFocusSearch = false;
105
- list.id = `${instanceId}-listbox`;
106
- list.hidden = true;
107
- list.role = "listbox";
108
- list.dataset.autocompleteList = "";
109
- for (const name of [
110
- "role",
111
- "autocomplete",
112
- "aria-autocomplete",
113
- "aria-controls",
114
- "aria-expanded",
115
- "aria-activedescendant"
116
- ]) {
117
- originalAttributes.set(name, input.getAttribute(name));
118
- }
119
- input.role = "combobox";
120
- input.autocomplete = "off";
121
- input.setAttribute("aria-autocomplete", "list");
122
- input.setAttribute("aria-controls", list.id);
123
- input.setAttribute("aria-expanded", "false");
124
- input.insertAdjacentElement("afterend", list);
125
- function buildUrl(query) {
126
- const endpoint = typeof options.endpoint === "function" ? options.endpoint(query, input) : options.endpoint;
127
- const url = new URL(endpoint, document.baseURI);
128
- if (settings.queryParam !== null) url.searchParams.set(settings.queryParam, query);
129
- return url;
130
- }
131
- function buildRequestInit(query, signal) {
132
- const init = typeof settings.requestInit === "function" ? settings.requestInit(query, input) : settings.requestInit;
133
- return { ...init, signal };
134
- }
135
- function handleInput(event) {
136
- if (event.isComposing) return;
137
- scheduleSearch();
138
- }
139
- function scheduleSearch() {
140
- window.clearTimeout(debounceTimer);
141
- const query = input.value.trim();
142
- if (selected !== null) {
143
- selected = null;
144
- dispatch("autocomplete:clear", { query });
145
- }
146
- if (query.length < settings.minLength) {
147
- cancel();
148
- close();
149
- return;
150
- }
151
- debounceTimer = window.setTimeout(() => void search(query), settings.debounce);
152
- }
153
- async function search(query = input.value.trim()) {
154
- if (destroyed || query.length < settings.minLength) {
155
- close();
156
- return;
157
- }
158
- cancel();
159
- const sequence = ++requestSequence;
160
- const controller = new AbortController();
161
- abortController = controller;
162
- renderStatus(messages.loading, "status", "loading");
163
- dispatch("autocomplete:loadstart", { query });
164
- try {
165
- const response = await fetch(buildUrl(query), buildRequestInit(query, controller.signal));
166
- if (!response.ok) throw new Error(`Autocomplete request failed with HTTP ${response.status}.`);
167
- const payload = await settings.parseResponse(response, query);
168
- const result = await settings.mapResponse(payload, response, query);
169
- if (!Array.isArray(result))
170
- throw new TypeError("Autocomplete mapResponse must return an array.");
171
- if (sequence !== requestSequence) return;
172
- suggestions = result.slice(0, settings.maxResults).map(normalizeSuggestion);
173
- activeIndex = -1;
174
- if (suggestions.length === 0) renderStatus(messages.empty, "status", "empty");
175
- else renderSuggestions();
176
- dispatch("autocomplete:load", {
177
- query,
178
- suggestions: suggestions.map((suggestion) => suggestion.data)
179
- });
180
- } catch (error) {
181
- if (isAbortError(error) || sequence !== requestSequence) return;
182
- suggestions = [];
183
- activeIndex = -1;
184
- renderStatus(messages.error, "alert", "error");
185
- dispatch("autocomplete:error", { query, error });
186
- settings.onError?.(error, input);
187
- } finally {
188
- if (sequence === requestSequence) abortController = null;
189
- }
190
- }
191
- function normalizeSuggestion(suggestion, index) {
192
- return {
193
- id: `${instanceId}-option-${sanitizeId(settings.getId(suggestion, index))}`,
194
- label: String(settings.getLabel(suggestion)),
195
- value: String(settings.getValue(suggestion)),
196
- data: suggestion
197
- };
198
- }
199
- function renderSuggestions() {
200
- const fragment = document.createDocumentFragment();
201
- list.replaceChildren();
202
- suggestions.forEach((suggestion, index) => {
203
- const option = document.createElement("button");
204
- option.id = suggestion.id;
205
- option.type = "button";
206
- option.role = "option";
207
- option.textContent = suggestion.label;
208
- option.dataset.autocompleteOption = "";
209
- option.dataset.index = String(index);
210
- option.setAttribute("aria-selected", "false");
211
- settings.renderOption?.(option, suggestion.data);
212
- option.addEventListener("pointerdown", handleOptionPointerDown);
213
- option.addEventListener("pointerenter", handleOptionPointerEnter);
214
- fragment.append(option);
215
- });
216
- list.append(fragment);
217
- open();
218
- }
219
- function renderStatus(message, role, state) {
220
- const element = document.createElement("div");
221
- element.role = role;
222
- element.textContent = message;
223
- element.dataset.autocompleteStatus = state;
224
- list.replaceChildren(element);
225
- open(true);
226
- }
227
- function open(allowStatus = false) {
228
- if (destroyed || !allowStatus && suggestions.length === 0) return;
229
- list.hidden = false;
230
- input.setAttribute("aria-expanded", "true");
231
- }
232
- function close() {
233
- activeIndex = -1;
234
- list.hidden = true;
235
- input.setAttribute("aria-expanded", "false");
236
- input.removeAttribute("aria-activedescendant");
237
- updateActiveOption();
238
- }
239
- function dismiss() {
240
- cancel();
241
- close();
242
- }
243
- function clear() {
244
- const hadSelection = selected !== null;
245
- cancel();
246
- suggestions = [];
247
- activeIndex = -1;
248
- selected = null;
249
- input.value = "";
250
- close();
251
- if (hadSelection) dispatch("autocomplete:clear", { query: "" });
252
- }
253
- function moveActiveOption(direction) {
254
- if (list.hidden || suggestions.length === 0) return;
255
- activeIndex = activeIndex === -1 ? direction > 0 ? 0 : suggestions.length - 1 : (activeIndex + direction + suggestions.length) % suggestions.length;
256
- updateActiveOption();
257
- }
258
- function updateActiveOption() {
259
- const options2 = list.querySelectorAll("[data-autocomplete-option]");
260
- options2.forEach((option, index) => {
261
- const active2 = index === activeIndex;
262
- option.dataset.active = String(active2);
263
- option.setAttribute("aria-selected", String(active2));
264
- });
265
- const active = options2.item(activeIndex);
266
- if (!active) {
267
- input.removeAttribute("aria-activedescendant");
268
- return;
269
- }
270
- input.setAttribute("aria-activedescendant", active.id);
271
- active.scrollIntoView({ block: "nearest" });
272
- }
273
- function select(index) {
274
- const suggestion = suggestions[index];
275
- if (!suggestion) return;
276
- selected = suggestion.data;
277
- input.value = suggestion.value;
278
- dismiss();
279
- dispatch("autocomplete:select", {
280
- suggestion: suggestion.data,
281
- label: suggestion.label,
282
- value: suggestion.value
283
- });
284
- settings.onSelect?.(suggestion.data, input);
285
- }
286
- function dispatch(name, detail) {
287
- input.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
288
- }
289
- function cancel() {
290
- window.clearTimeout(debounceTimer);
291
- debounceTimer = void 0;
292
- requestSequence += 1;
293
- abortController?.abort();
294
- abortController = null;
295
- }
296
- function handleFocus() {
297
- if (suppressFocusSearch) return;
298
- if (settings.searchOnFocus && input.value.trim().length >= settings.minLength) {
299
- scheduleSearch();
300
- }
301
- }
302
- function handleKeydown(event) {
303
- if (event.key === "ArrowDown" || event.key === "ArrowUp") {
304
- if (suggestions.length === 0 || list.hidden) return;
305
- event.preventDefault();
306
- moveActiveOption(event.key === "ArrowDown" ? 1 : -1);
307
- return;
308
- }
309
- if (event.key === "Enter" && !list.hidden && activeIndex >= 0) {
310
- event.preventDefault();
311
- select(activeIndex);
312
- return;
313
- }
314
- if ((event.key === "Home" || event.key === "End") && !list.hidden && suggestions.length > 0) {
315
- event.preventDefault();
316
- activeIndex = event.key === "Home" ? 0 : suggestions.length - 1;
317
- updateActiveOption();
318
- return;
319
- }
320
- if (event.key === "Escape") {
321
- event.preventDefault();
322
- dismiss();
323
- return;
324
- }
325
- if (event.key === "Tab") dismiss();
326
- }
327
- function handleOptionPointerDown(event) {
328
- event.preventDefault();
329
- const element = event.currentTarget;
330
- select(Number(element.dataset.index));
331
- if (document.activeElement !== input) {
332
- suppressFocusSearch = true;
333
- input.focus({ preventScroll: true });
334
- suppressFocusSearch = false;
335
- }
336
- }
337
- function handleOptionPointerEnter(event) {
338
- const element = event.currentTarget;
339
- activeIndex = Number(element.dataset.index);
340
- updateActiveOption();
341
- }
342
- function handleDocumentPointerDown(event) {
343
- if (event.target !== input && !list.contains(event.target)) dismiss();
344
- }
345
- function destroy2() {
346
- if (destroyed) return;
347
- destroyed = true;
348
- cancel();
349
- input.removeEventListener("input", handleInput);
350
- input.removeEventListener("compositionend", scheduleSearch);
351
- input.removeEventListener("focus", handleFocus);
352
- input.removeEventListener("keydown", handleKeydown);
353
- document.removeEventListener("pointerdown", handleDocumentPointerDown);
354
- list.remove();
355
- originalAttributes.forEach((value, name) => restoreAttribute(input, name, value));
356
- }
357
- input.addEventListener("input", handleInput);
358
- input.addEventListener("compositionend", scheduleSearch);
359
- input.addEventListener("focus", handleFocus);
360
- input.addEventListener("keydown", handleKeydown);
361
- document.addEventListener("pointerdown", handleDocumentPointerDown);
362
- return {
363
- element: input,
364
- listElement: list,
365
- options,
366
- search,
367
- close: dismiss,
368
- clear,
369
- cancel,
370
- getSelected: () => selected,
371
- destroy: destroy2
372
- };
373
- }
374
- function isAbortError(error) {
375
- return error instanceof DOMException && error.name === "AbortError";
376
- }
377
-
378
- // src/api/create-autocomplete.ts
379
- function createAutocomplete(target, options) {
380
- return createAutocompleteController(target, options);
381
- }
382
- var autocomplete = createAutocomplete;
383
-
384
- // src/browser/registry.ts
385
- var available = /* @__PURE__ */ Object.create(null);
386
- function create(target, options) {
387
- const input = resolveInput(target);
388
- if (!input.id) throw new TypeError("Autocomplete.create requires an input with an id.");
389
- available[input.id]?.destroy();
390
- const controller = createAutocomplete(input, options);
391
- available[input.id] = controller;
392
- return controller;
393
- }
394
- function destroy(id) {
395
- available[id]?.destroy();
396
- delete available[id];
397
- }
398
- var browser = {
399
- autocomplete: createAutocomplete,
400
- create,
401
- destroy,
402
- available
403
- };
1
+ import "./chunk-AD7T42HJ.js";
2
+ import "./chunk-PPDQPLPE.js";
3
+ import {
4
+ Autocomplete,
5
+ browser
6
+ } from "./chunk-5UZVFHB4.js";
7
+ import {
8
+ autocomplete,
9
+ createAutocomplete,
10
+ renderHighlight
11
+ } from "./chunk-U5JYDGKW.js";
12
+ import {
13
+ DEFAULT_CACHE_SIZE,
14
+ DEFAULT_CACHE_TTL,
15
+ DEFAULT_DEBOUNCE,
16
+ DEFAULT_MAX_RESULTS,
17
+ DEFAULT_MESSAGES,
18
+ DEFAULT_MIN_LENGTH,
19
+ DEFAULT_QUERY_PARAM,
20
+ HIGHLIGHT_ATTRIBUTE,
21
+ LIST_ATTRIBUTE,
22
+ LIVE_ATTRIBUTE,
23
+ OPTION_ATTRIBUTE,
24
+ POPUP_ATTRIBUTE,
25
+ STATUS_ATTRIBUTE,
26
+ createCache,
27
+ filterSuggestions,
28
+ fromEndpoint,
29
+ highlight,
30
+ mapCommonResponse,
31
+ normalizeText,
32
+ parseJsonResponse
33
+ } from "./chunk-4IPBCQDZ.js";
404
34
  export {
35
+ Autocomplete,
36
+ DEFAULT_CACHE_SIZE,
37
+ DEFAULT_CACHE_TTL,
38
+ DEFAULT_DEBOUNCE,
39
+ DEFAULT_MAX_RESULTS,
40
+ DEFAULT_MESSAGES,
41
+ DEFAULT_MIN_LENGTH,
42
+ DEFAULT_QUERY_PARAM,
43
+ HIGHLIGHT_ATTRIBUTE,
44
+ LIST_ATTRIBUTE,
45
+ LIVE_ATTRIBUTE,
46
+ OPTION_ATTRIBUTE,
47
+ POPUP_ATTRIBUTE,
48
+ STATUS_ATTRIBUTE,
405
49
  autocomplete,
406
50
  browser,
407
51
  createAutocomplete,
408
- createAutocompleteController,
52
+ createCache,
53
+ filterSuggestions,
54
+ fromEndpoint,
55
+ highlight,
409
56
  mapCommonResponse,
410
- parseJsonResponse
57
+ normalizeText,
58
+ parseJsonResponse,
59
+ renderHighlight
411
60
  };
412
61
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/core/defaults.ts","../src/core/dom.ts","../src/core/controller.ts","../src/api/create-autocomplete.ts","../src/browser/registry.ts"],"sourcesContent":["import type {\n AutocompleteMessages,\n AutocompleteOptions,\n AutocompleteParser,\n AutocompleteResponseMapper,\n} from './types'\n\nexport const DEFAULT_MESSAGES: AutocompleteMessages = {\n loading: 'Searching...',\n empty: 'No results',\n error: 'Search failed',\n}\n\nexport const parseJsonResponse: AutocompleteParser = async response => response.json()\n\nexport const mapCommonResponse: AutocompleteResponseMapper<unknown> = payload => {\n if (Array.isArray(payload)) return payload\n if (!payload || typeof payload !== 'object') throw invalidPayloadError()\n\n const record = payload as Record<string, unknown>\n if (Array.isArray(record.suggestions)) return record.suggestions\n if (Array.isArray(record.items)) return record.items\n if (Array.isArray(record.data)) return record.data\n\n throw invalidPayloadError()\n}\n\nexport function getDefaultLabel(suggestion: unknown): string {\n if (typeof suggestion === 'string') return suggestion\n if (!suggestion || typeof suggestion !== 'object') return String(suggestion ?? '')\n\n const record = suggestion as Record<string, unknown>\n return String(record.label ?? record.value ?? record.title ?? '')\n}\n\nexport function getDefaultValue(suggestion: unknown): string {\n if (typeof suggestion === 'string') return suggestion\n if (!suggestion || typeof suggestion !== 'object') return String(suggestion ?? '')\n\n const record = suggestion as Record<string, unknown>\n return String(record.value ?? record.label ?? record.title ?? '')\n}\n\nexport function getDefaultId(suggestion: unknown, index: number): string | number {\n if (!suggestion || typeof suggestion !== 'object') return index\n return ((suggestion as Record<string, unknown>).id as string | number | undefined) ?? index\n}\n\nexport function validateOptions<T>(options: AutocompleteOptions<T>): void {\n if (!options.endpoint) throw new TypeError('Autocomplete requires an endpoint.')\n assertNonNegativeInteger(options.minLength, 'minLength')\n assertNonNegativeInteger(options.debounce, 'debounce')\n\n if (\n options.maxResults !== undefined &&\n (!Number.isInteger(options.maxResults) || options.maxResults < 1)\n ) {\n throw new TypeError('Autocomplete maxResults must be an integer greater than zero.')\n }\n}\n\nfunction assertNonNegativeInteger(value: number | undefined, name: string): void {\n if (value !== undefined && (!Number.isInteger(value) || value < 0)) {\n throw new TypeError(`Autocomplete ${name} must be a non-negative integer.`)\n }\n}\n\nfunction invalidPayloadError(): TypeError {\n return new TypeError(\n 'Autocomplete response does not contain an array. Configure mapResponse(payload, response, query).'\n )\n}\n","import type { AutocompleteTarget } from './types'\n\nexport function resolveInput(target: AutocompleteTarget): HTMLInputElement {\n const element = typeof target === 'string' ? document.querySelector(target) : target\n\n if (!(element instanceof HTMLInputElement)) {\n throw new TypeError('Autocomplete requires an HTMLInputElement or a valid selector.')\n }\n\n return element\n}\n\nexport function sanitizeId(value: unknown): string {\n return String(value).replace(/[^a-zA-Z0-9_-]/g, '-')\n}\n\nexport function restoreAttribute(element: HTMLElement, name: string, value: string | null): void {\n if (value === null) {\n element.removeAttribute(name)\n return\n }\n\n element.setAttribute(name, value)\n}\n","import {\n DEFAULT_MESSAGES,\n getDefaultId,\n getDefaultLabel,\n getDefaultValue,\n mapCommonResponse,\n parseJsonResponse,\n validateOptions,\n} from './defaults'\nimport { resolveInput, restoreAttribute, sanitizeId } from './dom'\nimport type {\n AutocompleteController,\n AutocompleteMessages,\n AutocompleteOptions,\n AutocompleteResponseMapper,\n AutocompleteTarget,\n NormalizedSuggestion,\n} from './types'\n\nlet instanceCount = 0\n\nexport function createAutocompleteController<T>(\n target: AutocompleteTarget,\n options: AutocompleteOptions<T>\n): AutocompleteController<T> {\n validateOptions(options)\n\n const input = resolveInput(target)\n const instanceId = `autocomplete-${++instanceCount}`\n const messages: AutocompleteMessages = { ...DEFAULT_MESSAGES, ...options.messages }\n const mapResponse: AutocompleteResponseMapper<T> =\n options.mapResponse ?? (mapCommonResponse as AutocompleteResponseMapper<T>)\n const settings = {\n queryParam: options.queryParam === undefined ? 'q' : options.queryParam,\n requestInit: options.requestInit ?? {},\n parseResponse: options.parseResponse ?? parseJsonResponse,\n mapResponse,\n getId: options.getId ?? (getDefaultId as (suggestion: T, index: number) => string | number),\n getLabel: options.getLabel ?? (getDefaultLabel as (suggestion: T) => string),\n getValue: options.getValue ?? (getDefaultValue as (suggestion: T) => string),\n renderOption: options.renderOption,\n minLength: options.minLength ?? 2,\n debounce: options.debounce ?? 250,\n maxResults: options.maxResults ?? 8,\n searchOnFocus: options.searchOnFocus ?? true,\n onSelect: options.onSelect,\n onError: options.onError,\n }\n const list = document.createElement('div')\n const originalAttributes = new Map<string, string | null>()\n\n let suggestions: NormalizedSuggestion<T>[] = []\n let activeIndex = -1\n let selected: T | null = null\n let debounceTimer: number | undefined\n let abortController: AbortController | null = null\n let requestSequence = 0\n let destroyed = false\n let suppressFocusSearch = false\n\n list.id = `${instanceId}-listbox`\n list.hidden = true\n list.role = 'listbox'\n list.dataset.autocompleteList = ''\n\n for (const name of [\n 'role',\n 'autocomplete',\n 'aria-autocomplete',\n 'aria-controls',\n 'aria-expanded',\n 'aria-activedescendant',\n ]) {\n originalAttributes.set(name, input.getAttribute(name))\n }\n\n input.role = 'combobox'\n input.autocomplete = 'off'\n input.setAttribute('aria-autocomplete', 'list')\n input.setAttribute('aria-controls', list.id)\n input.setAttribute('aria-expanded', 'false')\n input.insertAdjacentElement('afterend', list)\n\n function buildUrl(query: string): URL {\n const endpoint =\n typeof options.endpoint === 'function' ? options.endpoint(query, input) : options.endpoint\n const url = new URL(endpoint, document.baseURI)\n\n if (settings.queryParam !== null) url.searchParams.set(settings.queryParam, query)\n return url\n }\n\n function buildRequestInit(query: string, signal: AbortSignal): RequestInit {\n const init =\n typeof settings.requestInit === 'function'\n ? settings.requestInit(query, input)\n : settings.requestInit\n\n return { ...init, signal }\n }\n\n function handleInput(event: Event): void {\n // Wait for IME composition to finish (compositionend schedules the search).\n if ((event as InputEvent).isComposing) return\n scheduleSearch()\n }\n\n function scheduleSearch(): void {\n window.clearTimeout(debounceTimer)\n const query = input.value.trim()\n\n if (selected !== null) {\n selected = null\n dispatch('autocomplete:clear', { query })\n }\n\n if (query.length < settings.minLength) {\n cancel()\n close()\n return\n }\n\n debounceTimer = window.setTimeout(() => void search(query), settings.debounce)\n }\n\n async function search(query = input.value.trim()): Promise<void> {\n if (destroyed || query.length < settings.minLength) {\n close()\n return\n }\n\n cancel()\n const sequence = ++requestSequence\n const controller = new AbortController()\n abortController = controller\n\n renderStatus(messages.loading, 'status', 'loading')\n dispatch('autocomplete:loadstart', { query })\n\n try {\n const response = await fetch(buildUrl(query), buildRequestInit(query, controller.signal))\n if (!response.ok) throw new Error(`Autocomplete request failed with HTTP ${response.status}.`)\n\n const payload = await settings.parseResponse(response, query)\n const result = await settings.mapResponse(payload, response, query)\n if (!Array.isArray(result))\n throw new TypeError('Autocomplete mapResponse must return an array.')\n if (sequence !== requestSequence) return\n\n suggestions = result.slice(0, settings.maxResults).map(normalizeSuggestion)\n activeIndex = -1\n\n if (suggestions.length === 0) renderStatus(messages.empty, 'status', 'empty')\n else renderSuggestions()\n\n dispatch('autocomplete:load', {\n query,\n suggestions: suggestions.map(suggestion => suggestion.data),\n })\n } catch (error) {\n if (isAbortError(error) || sequence !== requestSequence) return\n\n suggestions = []\n activeIndex = -1\n renderStatus(messages.error, 'alert', 'error')\n dispatch('autocomplete:error', { query, error })\n settings.onError?.(error, input)\n } finally {\n if (sequence === requestSequence) abortController = null\n }\n }\n\n function normalizeSuggestion(suggestion: T, index: number): NormalizedSuggestion<T> {\n return {\n id: `${instanceId}-option-${sanitizeId(settings.getId(suggestion, index))}`,\n label: String(settings.getLabel(suggestion)),\n value: String(settings.getValue(suggestion)),\n data: suggestion,\n }\n }\n\n function renderSuggestions(): void {\n const fragment = document.createDocumentFragment()\n list.replaceChildren()\n\n suggestions.forEach((suggestion, index) => {\n const option = document.createElement('button')\n option.id = suggestion.id\n option.type = 'button'\n option.role = 'option'\n option.textContent = suggestion.label\n option.dataset.autocompleteOption = ''\n option.dataset.index = String(index)\n option.setAttribute('aria-selected', 'false')\n settings.renderOption?.(option, suggestion.data)\n option.addEventListener('pointerdown', handleOptionPointerDown)\n option.addEventListener('pointerenter', handleOptionPointerEnter)\n fragment.append(option)\n })\n\n list.append(fragment)\n open()\n }\n\n function renderStatus(message: string, role: 'status' | 'alert', state: string): void {\n const element = document.createElement('div')\n element.role = role\n element.textContent = message\n element.dataset.autocompleteStatus = state\n list.replaceChildren(element)\n open(true)\n }\n\n function open(allowStatus = false): void {\n if (destroyed || (!allowStatus && suggestions.length === 0)) return\n list.hidden = false\n input.setAttribute('aria-expanded', 'true')\n }\n\n function close(): void {\n activeIndex = -1\n list.hidden = true\n input.setAttribute('aria-expanded', 'false')\n input.removeAttribute('aria-activedescendant')\n updateActiveOption()\n }\n\n // Closing is a user decision: drop the pending search and abort the request\n // in flight, otherwise their response would reopen the list.\n function dismiss(): void {\n cancel()\n close()\n }\n\n function clear(): void {\n const hadSelection = selected !== null\n cancel()\n suggestions = []\n activeIndex = -1\n selected = null\n input.value = ''\n close()\n if (hadSelection) dispatch('autocomplete:clear', { query: '' })\n }\n\n function moveActiveOption(direction: number): void {\n if (list.hidden || suggestions.length === 0) return\n\n activeIndex =\n activeIndex === -1\n ? direction > 0\n ? 0\n : suggestions.length - 1\n : (activeIndex + direction + suggestions.length) % suggestions.length\n\n updateActiveOption()\n }\n\n function updateActiveOption(): void {\n const options = list.querySelectorAll<HTMLButtonElement>('[data-autocomplete-option]')\n\n options.forEach((option, index) => {\n const active = index === activeIndex\n option.dataset.active = String(active)\n option.setAttribute('aria-selected', String(active))\n })\n\n const active = options.item(activeIndex)\n if (!active) {\n input.removeAttribute('aria-activedescendant')\n return\n }\n\n input.setAttribute('aria-activedescendant', active.id)\n active.scrollIntoView({ block: 'nearest' })\n }\n\n function select(index: number): void {\n const suggestion = suggestions[index]\n if (!suggestion) return\n\n selected = suggestion.data\n input.value = suggestion.value\n dismiss()\n dispatch('autocomplete:select', {\n suggestion: suggestion.data,\n label: suggestion.label,\n value: suggestion.value,\n })\n settings.onSelect?.(suggestion.data, input)\n }\n\n function dispatch(name: string, detail: object): void {\n input.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }))\n }\n\n function cancel(): void {\n window.clearTimeout(debounceTimer)\n debounceTimer = undefined\n requestSequence += 1\n abortController?.abort()\n abortController = null\n }\n\n function handleFocus(): void {\n if (suppressFocusSearch) return\n\n if (settings.searchOnFocus && input.value.trim().length >= settings.minLength) {\n scheduleSearch()\n }\n }\n\n function handleKeydown(event: KeyboardEvent): void {\n if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {\n if (suggestions.length === 0 || list.hidden) return\n event.preventDefault()\n moveActiveOption(event.key === 'ArrowDown' ? 1 : -1)\n return\n }\n\n if (event.key === 'Enter' && !list.hidden && activeIndex >= 0) {\n event.preventDefault()\n select(activeIndex)\n return\n }\n\n if ((event.key === 'Home' || event.key === 'End') && !list.hidden && suggestions.length > 0) {\n event.preventDefault()\n activeIndex = event.key === 'Home' ? 0 : suggestions.length - 1\n updateActiveOption()\n return\n }\n\n if (event.key === 'Escape') {\n event.preventDefault()\n dismiss()\n return\n }\n\n if (event.key === 'Tab') dismiss()\n }\n\n function handleOptionPointerDown(event: PointerEvent): void {\n event.preventDefault()\n const element = event.currentTarget as HTMLButtonElement\n select(Number(element.dataset.index))\n\n if (document.activeElement !== input) {\n suppressFocusSearch = true\n input.focus({ preventScroll: true })\n suppressFocusSearch = false\n }\n }\n\n function handleOptionPointerEnter(event: PointerEvent): void {\n const element = event.currentTarget as HTMLButtonElement\n activeIndex = Number(element.dataset.index)\n updateActiveOption()\n }\n\n function handleDocumentPointerDown(event: PointerEvent): void {\n if (event.target !== input && !list.contains(event.target as Node)) dismiss()\n }\n\n function destroy(): void {\n if (destroyed) return\n destroyed = true\n cancel()\n input.removeEventListener('input', handleInput)\n input.removeEventListener('compositionend', scheduleSearch)\n input.removeEventListener('focus', handleFocus)\n input.removeEventListener('keydown', handleKeydown)\n document.removeEventListener('pointerdown', handleDocumentPointerDown)\n list.remove()\n\n originalAttributes.forEach((value, name) => restoreAttribute(input, name, value))\n }\n\n input.addEventListener('input', handleInput)\n input.addEventListener('compositionend', scheduleSearch)\n input.addEventListener('focus', handleFocus)\n input.addEventListener('keydown', handleKeydown)\n document.addEventListener('pointerdown', handleDocumentPointerDown)\n\n return {\n element: input,\n listElement: list,\n options,\n search,\n close: dismiss,\n clear,\n cancel,\n getSelected: () => selected,\n destroy,\n }\n}\n\nfunction isAbortError(error: unknown): boolean {\n return error instanceof DOMException && error.name === 'AbortError'\n}\n","import { createAutocompleteController } from '../core/controller'\nimport type { AutocompleteController, AutocompleteOptions, AutocompleteTarget } from '../core/types'\n\nexport function createAutocomplete<T = unknown>(\n target: AutocompleteTarget,\n options: AutocompleteOptions<T>\n): AutocompleteController<T> {\n return createAutocompleteController(target, options)\n}\n\nexport const autocomplete = createAutocomplete\n","import { createAutocomplete } from '../api/create-autocomplete'\nimport { resolveInput } from '../core/dom'\nimport type { AutocompleteController, AutocompleteOptions, AutocompleteTarget } from '../core/types'\n\nexport type AutocompleteAvailable = Record<string, AutocompleteController<unknown>>\n\nexport interface AutocompleteApi {\n autocomplete: typeof createAutocomplete\n create: <T>(\n target: AutocompleteTarget,\n options: AutocompleteOptions<T>\n ) => AutocompleteController<T>\n destroy: (id: string) => void\n available: AutocompleteAvailable\n}\n\nexport const available: AutocompleteAvailable = Object.create(null) as AutocompleteAvailable\n\nfunction create<T>(\n target: AutocompleteTarget,\n options: AutocompleteOptions<T>\n): AutocompleteController<T> {\n const input = resolveInput(target)\n if (!input.id) throw new TypeError('Autocomplete.create requires an input with an id.')\n\n available[input.id]?.destroy()\n const controller = createAutocomplete(input, options)\n available[input.id] = controller as AutocompleteController<unknown>\n return controller\n}\n\nfunction destroy(id: string): void {\n available[id]?.destroy()\n delete available[id]\n}\n\nexport const browser: AutocompleteApi = {\n autocomplete: createAutocomplete,\n create,\n destroy,\n available,\n}\n"],"mappings":"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","names":["options","active","destroy"]}
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,7 @@
1
+ import { t as AutocompleteTarget, k as AutocompleteOptions, b as AutocompleteController } from './types-CPvjhqjb.js';
2
+
3
+ /** Turns an existing text input into an accessible combobox. */
4
+ declare const autocomplete: <T = unknown>(target: AutocompleteTarget, options: AutocompleteOptions<T>) => AutocompleteController<T>;
5
+ declare const createAutocomplete: <T = unknown>(target: AutocompleteTarget, options: AutocompleteOptions<T>) => AutocompleteController<T>;
6
+
7
+ export { autocomplete as a, createAutocomplete as c };
@@ -0,0 +1,7 @@
1
+ import { t as AutocompleteTarget, k as AutocompleteOptions, b as AutocompleteController } from './types-CPvjhqjb.cjs';
2
+
3
+ /** Turns an existing text input into an accessible combobox. */
4
+ declare const autocomplete: <T = unknown>(target: AutocompleteTarget, options: AutocompleteOptions<T>) => AutocompleteController<T>;
5
+ declare const createAutocomplete: <T = unknown>(target: AutocompleteTarget, options: AutocompleteOptions<T>) => AutocompleteController<T>;
6
+
7
+ export { autocomplete as a, createAutocomplete as c };
package/dist/styles.css CHANGED
@@ -1,4 +1,4 @@
1
- [data-autocomplete-list] {
1
+ [data-autocomplete-popup] {
2
2
  position: absolute;
3
3
  z-index: var(--autocomplete-z-index, 50);
4
4
  right: 0;
@@ -13,19 +13,15 @@
13
13
  color: var(--autocomplete-color, #111827);
14
14
  }
15
15
 
16
- [data-autocomplete-list][hidden] {
16
+ [data-autocomplete-popup][hidden],
17
+ [data-autocomplete-list][hidden],
18
+ [data-autocomplete-status][hidden] {
17
19
  display: none;
18
20
  }
19
21
 
20
22
  [data-autocomplete-option] {
21
23
  display: block;
22
- width: 100%;
23
24
  padding: var(--autocomplete-option-padding, 0.75rem 1rem);
24
- border: 0;
25
- color: inherit;
26
- background: transparent;
27
- font: inherit;
28
- text-align: left;
29
25
  cursor: pointer;
30
26
  }
31
27
 
@@ -35,6 +31,13 @@
35
31
  color: var(--autocomplete-active-color, inherit);
36
32
  }
37
33
 
34
+ [data-autocomplete-highlight] {
35
+ padding: 0;
36
+ background: var(--autocomplete-highlight-background, transparent);
37
+ color: var(--autocomplete-highlight-color, inherit);
38
+ font-weight: var(--autocomplete-highlight-weight, 600);
39
+ }
40
+
38
41
  [data-autocomplete-status] {
39
42
  padding: var(--autocomplete-option-padding, 0.75rem 1rem);
40
43
  color: var(--autocomplete-muted-color, #6b7280);
@@ -44,8 +47,15 @@
44
47
  color: var(--autocomplete-error-color, #b91c1c);
45
48
  }
46
49
 
50
+ @media (forced-colors: active) {
51
+ [data-autocomplete-option][data-active='true'] {
52
+ outline: 2px solid Highlight;
53
+ outline-offset: -2px;
54
+ }
55
+ }
56
+
47
57
  @media (prefers-reduced-motion: reduce) {
48
- [data-autocomplete-list] {
58
+ [data-autocomplete-popup] {
49
59
  scroll-behavior: auto;
50
60
  }
51
61
  }