@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.
- package/CHANGELOG.md +29 -0
- package/LICENSE.md +1 -1
- package/README.md +208 -32
- package/dist/browser/global.global.js +505 -274
- package/dist/browser/global.global.js.map +1 -1
- package/dist/browser/index.cjs +663 -0
- package/dist/browser/index.cjs.map +1 -0
- package/dist/browser/index.d.cts +31 -0
- package/dist/browser/index.d.ts +31 -0
- package/dist/browser/index.js +24 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/chunk-4IPBCQDZ.js +218 -0
- package/dist/chunk-4IPBCQDZ.js.map +1 -0
- package/dist/chunk-5UZVFHB4.js +70 -0
- package/dist/chunk-5UZVFHB4.js.map +1 -0
- package/dist/chunk-AD7T42HJ.js +1 -0
- package/dist/chunk-AD7T42HJ.js.map +1 -0
- package/dist/chunk-PPDQPLPE.js +1 -0
- package/dist/chunk-PPDQPLPE.js.map +1 -0
- package/dist/chunk-U5JYDGKW.js +403 -0
- package/dist/chunk-U5JYDGKW.js.map +1 -0
- package/dist/core/index.cjs +181 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +31 -0
- package/dist/core/index.d.ts +31 -0
- package/dist/core/index.js +22 -0
- package/dist/core/index.js.map +1 -0
- package/dist/index.cjs +527 -274
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -69
- package/dist/index.d.ts +20 -69
- package/dist/index.js +54 -405
- package/dist/index.js.map +1 -1
- package/dist/render-B38UXYCT.d.ts +7 -0
- package/dist/render-WD69VMUs.d.cts +7 -0
- package/dist/styles.css +19 -9
- package/dist/types-CPvjhqjb.d.cts +118 -0
- package/dist/types-CPvjhqjb.d.ts +118 -0
- package/dist/vanilla/index.cjs +598 -0
- package/dist/vanilla/index.cjs.map +1 -0
- package/dist/vanilla/index.d.cts +11 -0
- package/dist/vanilla/index.d.ts +11 -0
- package/dist/vanilla/index.js +13 -0
- package/dist/vanilla/index.js.map +1 -0
- package/package.json +19 -4
- package/dist/browser/global.d.ts +0 -61
package/dist/index.cjs
CHANGED
|
@@ -20,22 +20,83 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var src_exports = {};
|
|
22
22
|
__export(src_exports, {
|
|
23
|
+
Autocomplete: () => Autocomplete,
|
|
24
|
+
DEFAULT_CACHE_SIZE: () => DEFAULT_CACHE_SIZE,
|
|
25
|
+
DEFAULT_CACHE_TTL: () => DEFAULT_CACHE_TTL,
|
|
26
|
+
DEFAULT_DEBOUNCE: () => DEFAULT_DEBOUNCE,
|
|
27
|
+
DEFAULT_MAX_RESULTS: () => DEFAULT_MAX_RESULTS,
|
|
28
|
+
DEFAULT_MESSAGES: () => DEFAULT_MESSAGES,
|
|
29
|
+
DEFAULT_MIN_LENGTH: () => DEFAULT_MIN_LENGTH,
|
|
30
|
+
DEFAULT_QUERY_PARAM: () => DEFAULT_QUERY_PARAM,
|
|
31
|
+
HIGHLIGHT_ATTRIBUTE: () => HIGHLIGHT_ATTRIBUTE,
|
|
32
|
+
LIST_ATTRIBUTE: () => LIST_ATTRIBUTE,
|
|
33
|
+
LIVE_ATTRIBUTE: () => LIVE_ATTRIBUTE,
|
|
34
|
+
OPTION_ATTRIBUTE: () => OPTION_ATTRIBUTE,
|
|
35
|
+
POPUP_ATTRIBUTE: () => POPUP_ATTRIBUTE,
|
|
36
|
+
STATUS_ATTRIBUTE: () => STATUS_ATTRIBUTE,
|
|
23
37
|
autocomplete: () => autocomplete,
|
|
24
38
|
browser: () => browser,
|
|
25
39
|
createAutocomplete: () => createAutocomplete,
|
|
26
|
-
|
|
40
|
+
createCache: () => createCache,
|
|
41
|
+
filterSuggestions: () => filterSuggestions,
|
|
42
|
+
fromEndpoint: () => fromEndpoint,
|
|
43
|
+
highlight: () => highlight,
|
|
27
44
|
mapCommonResponse: () => mapCommonResponse,
|
|
28
|
-
|
|
45
|
+
normalizeText: () => normalizeText,
|
|
46
|
+
parseJsonResponse: () => parseJsonResponse,
|
|
47
|
+
renderHighlight: () => renderHighlight
|
|
29
48
|
});
|
|
30
49
|
module.exports = __toCommonJS(src_exports);
|
|
31
50
|
|
|
32
|
-
// src/
|
|
33
|
-
var
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
51
|
+
// src/constants.ts
|
|
52
|
+
var DEFAULT_QUERY_PARAM = "q";
|
|
53
|
+
var DEFAULT_MIN_LENGTH = 2;
|
|
54
|
+
var DEFAULT_DEBOUNCE = 250;
|
|
55
|
+
var DEFAULT_MAX_RESULTS = 8;
|
|
56
|
+
var DEFAULT_CACHE_SIZE = 50;
|
|
57
|
+
var DEFAULT_CACHE_TTL = 3e5;
|
|
58
|
+
var POPUP_ATTRIBUTE = "data-autocomplete-popup";
|
|
59
|
+
var LIST_ATTRIBUTE = "data-autocomplete-list";
|
|
60
|
+
var OPTION_ATTRIBUTE = "data-autocomplete-option";
|
|
61
|
+
var STATUS_ATTRIBUTE = "data-autocomplete-status";
|
|
62
|
+
var LIVE_ATTRIBUTE = "data-autocomplete-live";
|
|
63
|
+
var HIGHLIGHT_ATTRIBUTE = "data-autocomplete-highlight";
|
|
64
|
+
|
|
65
|
+
// src/core/cache.ts
|
|
66
|
+
var createCache = ({ max = DEFAULT_CACHE_SIZE, ttl = DEFAULT_CACHE_TTL } = {}, now = Date.now) => {
|
|
67
|
+
const entries = /* @__PURE__ */ new Map();
|
|
68
|
+
const get = (key) => {
|
|
69
|
+
const entry = entries.get(key);
|
|
70
|
+
if (!entry) return void 0;
|
|
71
|
+
entries.delete(key);
|
|
72
|
+
if (entry.expires <= now()) return void 0;
|
|
73
|
+
entries.set(key, entry);
|
|
74
|
+
return entry.value;
|
|
75
|
+
};
|
|
76
|
+
const set = (key, value) => {
|
|
77
|
+
entries.delete(key);
|
|
78
|
+
entries.set(key, { value, expires: now() + ttl });
|
|
79
|
+
while (entries.size > max) {
|
|
80
|
+
const oldest = entries.keys().next();
|
|
81
|
+
if (oldest.done) break;
|
|
82
|
+
entries.delete(oldest.value);
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
get,
|
|
87
|
+
set,
|
|
88
|
+
clear: () => entries.clear(),
|
|
89
|
+
get size() {
|
|
90
|
+
return entries.size;
|
|
91
|
+
}
|
|
92
|
+
};
|
|
37
93
|
};
|
|
94
|
+
|
|
95
|
+
// src/core/source.ts
|
|
38
96
|
var parseJsonResponse = async (response) => response.json();
|
|
97
|
+
var invalidPayloadError = () => new TypeError(
|
|
98
|
+
"Autocomplete response does not contain an array. Configure mapResponse(payload, response, query)."
|
|
99
|
+
);
|
|
39
100
|
var mapCommonResponse = (payload) => {
|
|
40
101
|
if (Array.isArray(payload)) return payload;
|
|
41
102
|
if (!payload || typeof payload !== "object") throw invalidPayloadError();
|
|
@@ -45,86 +106,200 @@ var mapCommonResponse = (payload) => {
|
|
|
45
106
|
if (Array.isArray(record.data)) return record.data;
|
|
46
107
|
throw invalidPayloadError();
|
|
47
108
|
};
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
109
|
+
var fromEndpoint = ({
|
|
110
|
+
endpoint,
|
|
111
|
+
queryParam = DEFAULT_QUERY_PARAM,
|
|
112
|
+
requestInit = {},
|
|
113
|
+
parseResponse = parseJsonResponse,
|
|
114
|
+
mapResponse = mapCommonResponse
|
|
115
|
+
}) => {
|
|
116
|
+
if (!endpoint) throw new TypeError("Autocomplete requires an endpoint.");
|
|
117
|
+
return async (query, { signal, input }) => {
|
|
118
|
+
const resolved = typeof endpoint === "function" ? endpoint(query, input) : endpoint;
|
|
119
|
+
const url = new URL(resolved, document.baseURI);
|
|
120
|
+
if (queryParam !== null) url.searchParams.set(queryParam, query);
|
|
121
|
+
const init = typeof requestInit === "function" ? requestInit(query, input) : requestInit;
|
|
122
|
+
const response = await fetch(url, { ...init, signal });
|
|
123
|
+
if (!response.ok) throw new Error(`Autocomplete request failed with HTTP ${response.status}.`);
|
|
124
|
+
const payload = await parseResponse(response, query);
|
|
125
|
+
const result = await mapResponse(payload, response, query);
|
|
126
|
+
if (!Array.isArray(result))
|
|
127
|
+
throw new TypeError("Autocomplete mapResponse must return an array.");
|
|
128
|
+
return result;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
// src/core/state.ts
|
|
133
|
+
var DEFAULT_MESSAGES = {
|
|
134
|
+
loading: "Searching...",
|
|
135
|
+
empty: "No results",
|
|
136
|
+
error: "Search failed",
|
|
137
|
+
results: ({ count }) => count === 1 ? "1 result available" : `${count} results available`
|
|
138
|
+
};
|
|
139
|
+
var resolveMessage = (message, context) => typeof message === "function" ? message(context) : message;
|
|
140
|
+
var readField = (suggestion, keys) => {
|
|
55
141
|
if (typeof suggestion === "string") return suggestion;
|
|
56
142
|
if (!suggestion || typeof suggestion !== "object") return String(suggestion ?? "");
|
|
57
143
|
const record = suggestion;
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
144
|
+
const key = keys.find((name) => record[name] !== void 0 && record[name] !== null);
|
|
145
|
+
return key === void 0 ? "" : String(record[key]);
|
|
146
|
+
};
|
|
147
|
+
var getDefaultLabel = (suggestion) => readField(suggestion, ["label", "value", "title", "name"]);
|
|
148
|
+
var getDefaultValue = (suggestion) => readField(suggestion, ["value", "label", "title", "name"]);
|
|
149
|
+
var getDefaultId = (suggestion, index) => {
|
|
61
150
|
if (!suggestion || typeof suggestion !== "object") return index;
|
|
62
151
|
return suggestion.id ?? index;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (!
|
|
152
|
+
};
|
|
153
|
+
var assertNonNegativeInteger = (value, name) => {
|
|
154
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 0)) {
|
|
155
|
+
throw new TypeError(`Autocomplete ${name} must be a non-negative integer.`);
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
var assertPositiveInteger = (value, name) => {
|
|
159
|
+
if (value !== void 0 && (!Number.isInteger(value) || value < 1)) {
|
|
160
|
+
throw new TypeError(`Autocomplete ${name} must be an integer greater than zero.`);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
var validateOptions = (options) => {
|
|
164
|
+
const hasSource = options.source !== void 0;
|
|
165
|
+
const hasEndpoint = options.endpoint !== void 0;
|
|
166
|
+
if (hasSource && hasEndpoint) {
|
|
167
|
+
throw new TypeError("Autocomplete accepts either a source or an endpoint, not both.");
|
|
168
|
+
}
|
|
169
|
+
if (!hasSource && !options.endpoint) {
|
|
170
|
+
throw new TypeError("Autocomplete requires a source or an endpoint.");
|
|
171
|
+
}
|
|
172
|
+
if (hasSource && !Array.isArray(options.source) && typeof options.source !== "function") {
|
|
173
|
+
throw new TypeError("Autocomplete source must be an array or a function.");
|
|
174
|
+
}
|
|
66
175
|
assertNonNegativeInteger(options.minLength, "minLength");
|
|
67
176
|
assertNonNegativeInteger(options.debounce, "debounce");
|
|
68
|
-
|
|
69
|
-
|
|
177
|
+
assertPositiveInteger(options.maxResults, "maxResults");
|
|
178
|
+
if (typeof options.cache === "object") {
|
|
179
|
+
assertPositiveInteger(options.cache.max, "cache.max");
|
|
180
|
+
assertNonNegativeInteger(options.cache.ttl, "cache.ttl");
|
|
70
181
|
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// src/core/text.ts
|
|
185
|
+
var COMBINING_MARKS = /\p{M}/gu;
|
|
186
|
+
var WHITESPACE = /\s+/;
|
|
187
|
+
var normalizeText = (value) => value.normalize("NFD").replace(COMBINING_MARKS, "").toLowerCase();
|
|
188
|
+
var getTerms = (query, normalize) => Array.from(new Set(normalize(query).split(WHITESPACE).filter(Boolean)));
|
|
189
|
+
var filterSuggestions = (suggestions, query, { getText, normalize = normalizeText }) => {
|
|
190
|
+
const terms = getTerms(query, normalize);
|
|
191
|
+
if (terms.length === 0) return [...suggestions];
|
|
192
|
+
return suggestions.filter((suggestion) => {
|
|
193
|
+
const text = normalize(getText(suggestion));
|
|
194
|
+
return terms.every((term) => text.includes(term));
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
var highlight = (text, query, normalize = normalizeText) => {
|
|
198
|
+
const terms = getTerms(query, normalize);
|
|
199
|
+
if (terms.length === 0 || text === "") return text === "" ? [] : [{ text, match: false }];
|
|
200
|
+
let normalized = "";
|
|
201
|
+
const starts = [];
|
|
202
|
+
const ends = [];
|
|
203
|
+
let offset = 0;
|
|
204
|
+
for (const char of text) {
|
|
205
|
+
const piece = normalize(char);
|
|
206
|
+
if (piece === "" && ends.length > 0) ends[ends.length - 1] = offset + char.length;
|
|
207
|
+
normalized += piece;
|
|
208
|
+
for (let index = 0; index < piece.length; index += 1) {
|
|
209
|
+
starts.push(offset);
|
|
210
|
+
ends.push(offset + char.length);
|
|
211
|
+
}
|
|
212
|
+
offset += char.length;
|
|
75
213
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
214
|
+
const ranges = [];
|
|
215
|
+
for (const term of terms) {
|
|
216
|
+
let found = normalized.indexOf(term);
|
|
217
|
+
while (found !== -1) {
|
|
218
|
+
ranges.push([starts[found], ends[found + term.length - 1]]);
|
|
219
|
+
found = normalized.indexOf(term, found + 1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
if (ranges.length === 0) return [{ text, match: false }];
|
|
223
|
+
ranges.sort((a, b) => a[0] - b[0]);
|
|
224
|
+
const merged = [];
|
|
225
|
+
for (const range of ranges) {
|
|
226
|
+
const last = merged[merged.length - 1];
|
|
227
|
+
if (last && range[0] <= last[1]) last[1] = Math.max(last[1], range[1]);
|
|
228
|
+
else merged.push([range[0], range[1]]);
|
|
229
|
+
}
|
|
230
|
+
const parts = [];
|
|
231
|
+
let cursor = 0;
|
|
232
|
+
for (const [start, end] of merged) {
|
|
233
|
+
if (start > cursor) parts.push({ text: text.slice(cursor, start), match: false });
|
|
234
|
+
parts.push({ text: text.slice(start, end), match: true });
|
|
235
|
+
cursor = end;
|
|
236
|
+
}
|
|
237
|
+
if (cursor < text.length) parts.push({ text: text.slice(cursor), match: false });
|
|
238
|
+
return parts;
|
|
239
|
+
};
|
|
82
240
|
|
|
83
|
-
// src/
|
|
84
|
-
|
|
241
|
+
// src/vanilla/dom.ts
|
|
242
|
+
var resolveInput = (target) => {
|
|
85
243
|
const element = typeof target === "string" ? document.querySelector(target) : target;
|
|
86
244
|
if (!(element instanceof HTMLInputElement)) {
|
|
87
245
|
throw new TypeError("Autocomplete requires an HTMLInputElement or a valid selector.");
|
|
88
246
|
}
|
|
89
247
|
return element;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
248
|
+
};
|
|
249
|
+
var sanitizeId = (value) => String(value).replace(/[^a-zA-Z0-9_-]/g, "-");
|
|
250
|
+
var restoreAttribute = (element, name, value) => {
|
|
251
|
+
if (value === null) element.removeAttribute(name);
|
|
252
|
+
else element.setAttribute(name, value);
|
|
253
|
+
};
|
|
254
|
+
var renderHighlight = (element, parts) => {
|
|
255
|
+
for (const part of parts) {
|
|
256
|
+
if (!part.match) {
|
|
257
|
+
element.append(document.createTextNode(part.text));
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
const mark = document.createElement("mark");
|
|
261
|
+
mark.setAttribute(HIGHLIGHT_ATTRIBUTE, "");
|
|
262
|
+
mark.textContent = part.text;
|
|
263
|
+
element.append(mark);
|
|
98
264
|
}
|
|
99
|
-
|
|
100
|
-
|
|
265
|
+
};
|
|
266
|
+
var VISUALLY_HIDDEN = "position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;border:0";
|
|
101
267
|
|
|
102
|
-
// src/
|
|
268
|
+
// src/vanilla/render.ts
|
|
269
|
+
var RESTORED_ATTRIBUTES = [
|
|
270
|
+
"role",
|
|
271
|
+
"autocomplete",
|
|
272
|
+
"aria-autocomplete",
|
|
273
|
+
"aria-controls",
|
|
274
|
+
"aria-expanded",
|
|
275
|
+
"aria-activedescendant"
|
|
276
|
+
];
|
|
103
277
|
var instanceCount = 0;
|
|
104
|
-
|
|
278
|
+
var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError";
|
|
279
|
+
var autocomplete = (target, options) => {
|
|
105
280
|
validateOptions(options);
|
|
106
281
|
const input = resolveInput(target);
|
|
107
282
|
const instanceId = `autocomplete-${++instanceCount}`;
|
|
108
283
|
const messages = { ...DEFAULT_MESSAGES, ...options.messages };
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
searchOnFocus: options.searchOnFocus ?? true,
|
|
123
|
-
onSelect: options.onSelect,
|
|
124
|
-
onError: options.onError
|
|
125
|
-
};
|
|
284
|
+
const localSuggestions = Array.isArray(options.source) ? options.source : null;
|
|
285
|
+
const getLabel = options.getLabel ?? getDefaultLabel;
|
|
286
|
+
const getValue = options.getValue ?? getDefaultValue;
|
|
287
|
+
const getId = options.getId ?? getDefaultId;
|
|
288
|
+
const normalize = options.normalize ?? normalizeText;
|
|
289
|
+
const minLength = options.minLength ?? (localSuggestions ? 1 : DEFAULT_MIN_LENGTH);
|
|
290
|
+
const debounce = options.debounce ?? (localSuggestions ? 0 : DEFAULT_DEBOUNCE);
|
|
291
|
+
const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
|
|
292
|
+
const searchOnFocus = options.searchOnFocus ?? true;
|
|
293
|
+
const highlightEnabled = options.highlight ?? true;
|
|
294
|
+
const source = localSuggestions ? null : typeof options.source === "function" ? options.source : fromEndpoint(options);
|
|
295
|
+
const cache = source && options.cache !== false ? createCache(typeof options.cache === "object" ? options.cache : {}) : null;
|
|
296
|
+
const popup = document.createElement("div");
|
|
126
297
|
const list = document.createElement("div");
|
|
298
|
+
const status = document.createElement("div");
|
|
299
|
+
const live = document.createElement("div");
|
|
127
300
|
const originalAttributes = /* @__PURE__ */ new Map();
|
|
301
|
+
const label = input.labels?.[0] ?? null;
|
|
302
|
+
const originalLabelId = label?.getAttribute("id") ?? null;
|
|
128
303
|
let suggestions = [];
|
|
129
304
|
let activeIndex = -1;
|
|
130
305
|
let selected = null;
|
|
@@ -133,145 +308,220 @@ function createAutocompleteController(target, options) {
|
|
|
133
308
|
let requestSequence = 0;
|
|
134
309
|
let destroyed = false;
|
|
135
310
|
let suppressFocusSearch = false;
|
|
311
|
+
popup.hidden = true;
|
|
312
|
+
popup.setAttribute(POPUP_ATTRIBUTE, "");
|
|
136
313
|
list.id = `${instanceId}-listbox`;
|
|
137
314
|
list.hidden = true;
|
|
138
315
|
list.role = "listbox";
|
|
139
|
-
list.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
316
|
+
list.setAttribute(LIST_ATTRIBUTE, "");
|
|
317
|
+
status.hidden = true;
|
|
318
|
+
status.setAttribute(STATUS_ATTRIBUTE, "");
|
|
319
|
+
status.setAttribute("aria-hidden", "true");
|
|
320
|
+
live.role = "status";
|
|
321
|
+
live.setAttribute("aria-live", "polite");
|
|
322
|
+
live.setAttribute("aria-atomic", "true");
|
|
323
|
+
live.setAttribute(LIVE_ATTRIBUTE, "");
|
|
324
|
+
live.style.cssText = VISUALLY_HIDDEN;
|
|
325
|
+
if (label) {
|
|
326
|
+
if (!label.id) label.id = `${instanceId}-label`;
|
|
327
|
+
list.setAttribute("aria-labelledby", label.id);
|
|
149
328
|
}
|
|
329
|
+
for (const name of RESTORED_ATTRIBUTES) originalAttributes.set(name, input.getAttribute(name));
|
|
150
330
|
input.role = "combobox";
|
|
151
331
|
input.autocomplete = "off";
|
|
152
332
|
input.setAttribute("aria-autocomplete", "list");
|
|
153
333
|
input.setAttribute("aria-controls", list.id);
|
|
154
334
|
input.setAttribute("aria-expanded", "false");
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
335
|
+
popup.append(list, status);
|
|
336
|
+
input.insertAdjacentElement("afterend", popup);
|
|
337
|
+
popup.insertAdjacentElement("afterend", live);
|
|
338
|
+
const dispatch = (name, detail) => {
|
|
339
|
+
input.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
|
|
340
|
+
};
|
|
341
|
+
const announce = (message) => {
|
|
342
|
+
live.textContent = message;
|
|
343
|
+
};
|
|
344
|
+
const cancel = () => {
|
|
345
|
+
clearTimeout(debounceTimer);
|
|
346
|
+
debounceTimer = void 0;
|
|
347
|
+
requestSequence += 1;
|
|
348
|
+
abortController?.abort();
|
|
349
|
+
abortController = null;
|
|
350
|
+
};
|
|
351
|
+
const updateActiveOption = () => {
|
|
352
|
+
const elements = list.querySelectorAll(`[${OPTION_ATTRIBUTE}]`);
|
|
353
|
+
elements.forEach((element, index) => {
|
|
354
|
+
const active2 = index === activeIndex;
|
|
355
|
+
element.dataset.active = String(active2);
|
|
356
|
+
element.setAttribute("aria-selected", String(active2));
|
|
357
|
+
});
|
|
358
|
+
const active = elements.item(activeIndex);
|
|
359
|
+
if (!active) {
|
|
360
|
+
input.removeAttribute("aria-activedescendant");
|
|
180
361
|
return;
|
|
181
362
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
363
|
+
input.setAttribute("aria-activedescendant", active.id);
|
|
364
|
+
active.scrollIntoView({ block: "nearest" });
|
|
365
|
+
};
|
|
366
|
+
const close = () => {
|
|
367
|
+
activeIndex = -1;
|
|
368
|
+
popup.hidden = true;
|
|
369
|
+
list.hidden = true;
|
|
370
|
+
status.hidden = true;
|
|
371
|
+
input.setAttribute("aria-expanded", "false");
|
|
372
|
+
input.removeAttribute("aria-activedescendant");
|
|
373
|
+
updateActiveOption();
|
|
374
|
+
};
|
|
375
|
+
const dismiss = () => {
|
|
376
|
+
cancel();
|
|
377
|
+
close();
|
|
378
|
+
};
|
|
379
|
+
const renderStatus = (state, query) => {
|
|
380
|
+
if (destroyed) return;
|
|
381
|
+
const message = resolveMessage(messages[state], { query, count: 0 });
|
|
382
|
+
list.replaceChildren();
|
|
383
|
+
list.hidden = true;
|
|
384
|
+
status.textContent = message;
|
|
385
|
+
status.setAttribute(STATUS_ATTRIBUTE, state);
|
|
386
|
+
status.hidden = false;
|
|
387
|
+
popup.hidden = false;
|
|
388
|
+
input.setAttribute("aria-expanded", "false");
|
|
389
|
+
input.removeAttribute("aria-activedescendant");
|
|
390
|
+
if (state !== "loading") announce(message);
|
|
391
|
+
};
|
|
392
|
+
const select = (index) => {
|
|
393
|
+
const suggestion = suggestions[index];
|
|
394
|
+
if (!suggestion) return;
|
|
395
|
+
selected = suggestion.data;
|
|
396
|
+
input.value = suggestion.value;
|
|
397
|
+
dismiss();
|
|
398
|
+
dispatch("autocomplete:select", {
|
|
399
|
+
suggestion: suggestion.data,
|
|
400
|
+
label: suggestion.label,
|
|
401
|
+
value: suggestion.value
|
|
402
|
+
});
|
|
403
|
+
options.onSelect?.(suggestion.data, input);
|
|
404
|
+
};
|
|
405
|
+
const handleOptionPointerDown = (event) => {
|
|
406
|
+
event.preventDefault();
|
|
407
|
+
select(Number(event.currentTarget.dataset.index));
|
|
408
|
+
if (document.activeElement !== input) {
|
|
409
|
+
suppressFocusSearch = true;
|
|
410
|
+
input.focus({ preventScroll: true });
|
|
411
|
+
suppressFocusSearch = false;
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
const handleOptionPointerEnter = (event) => {
|
|
415
|
+
activeIndex = Number(event.currentTarget.dataset.index);
|
|
416
|
+
updateActiveOption();
|
|
417
|
+
};
|
|
418
|
+
const renderSuggestions = (query) => {
|
|
419
|
+
const fragment = document.createDocumentFragment();
|
|
420
|
+
suggestions.forEach((suggestion, index) => {
|
|
421
|
+
const option = document.createElement("div");
|
|
422
|
+
const parts = highlightEnabled ? highlight(suggestion.label, query, normalize) : [{ text: suggestion.label, match: false }];
|
|
423
|
+
option.id = suggestion.id;
|
|
424
|
+
option.role = "option";
|
|
425
|
+
option.setAttribute(OPTION_ATTRIBUTE, "");
|
|
426
|
+
option.dataset.index = String(index);
|
|
427
|
+
option.setAttribute("aria-selected", "false");
|
|
428
|
+
renderHighlight(option, parts);
|
|
429
|
+
options.renderOption?.(option, suggestion.data, {
|
|
430
|
+
query,
|
|
431
|
+
index,
|
|
432
|
+
label: suggestion.label,
|
|
433
|
+
highlight: parts
|
|
434
|
+
});
|
|
435
|
+
option.addEventListener("pointerdown", handleOptionPointerDown);
|
|
436
|
+
option.addEventListener("pointerenter", handleOptionPointerEnter);
|
|
437
|
+
fragment.append(option);
|
|
438
|
+
});
|
|
439
|
+
list.replaceChildren(fragment);
|
|
440
|
+
list.hidden = false;
|
|
441
|
+
status.hidden = true;
|
|
442
|
+
popup.hidden = false;
|
|
443
|
+
input.setAttribute("aria-expanded", "true");
|
|
444
|
+
};
|
|
445
|
+
const normalizeSuggestion = (suggestion, index) => ({
|
|
446
|
+
id: `${instanceId}-option-${sanitizeId(getId(suggestion, index))}`,
|
|
447
|
+
label: String(getLabel(suggestion)),
|
|
448
|
+
value: String(getValue(suggestion)),
|
|
449
|
+
data: suggestion
|
|
450
|
+
});
|
|
451
|
+
const showResults = (query, result, cached) => {
|
|
452
|
+
if (destroyed) return;
|
|
453
|
+
suggestions = result.slice(0, maxResults).map(normalizeSuggestion);
|
|
454
|
+
activeIndex = -1;
|
|
455
|
+
if (suggestions.length === 0) renderStatus("empty", query);
|
|
456
|
+
else {
|
|
457
|
+
renderSuggestions(query);
|
|
458
|
+
announce(resolveMessage(messages.results, { query, count: suggestions.length }));
|
|
459
|
+
}
|
|
460
|
+
dispatch("autocomplete:load", {
|
|
461
|
+
query,
|
|
462
|
+
suggestions: suggestions.map((suggestion) => suggestion.data),
|
|
463
|
+
cached
|
|
464
|
+
});
|
|
465
|
+
};
|
|
466
|
+
const filterLocal = (items, query) => {
|
|
467
|
+
const filter = options.filter;
|
|
468
|
+
return filter ? items.filter((item) => filter(item, query)) : filterSuggestions(items, query, { getText: getLabel, normalize });
|
|
469
|
+
};
|
|
470
|
+
const search = async (query = input.value.trim()) => {
|
|
471
|
+
if (destroyed || query.length < minLength) {
|
|
186
472
|
close();
|
|
187
473
|
return;
|
|
188
474
|
}
|
|
189
475
|
cancel();
|
|
190
476
|
const sequence = ++requestSequence;
|
|
477
|
+
if (localSuggestions) {
|
|
478
|
+
showResults(query, filterLocal(localSuggestions, query), false);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const hit = cache?.get(query);
|
|
482
|
+
if (hit) {
|
|
483
|
+
showResults(query, hit, true);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
191
486
|
const controller = new AbortController();
|
|
192
487
|
abortController = controller;
|
|
193
|
-
renderStatus(
|
|
488
|
+
renderStatus("loading", query);
|
|
194
489
|
dispatch("autocomplete:loadstart", { query });
|
|
195
490
|
try {
|
|
196
|
-
const
|
|
197
|
-
if (!
|
|
198
|
-
const payload = await settings.parseResponse(response, query);
|
|
199
|
-
const result = await settings.mapResponse(payload, response, query);
|
|
200
|
-
if (!Array.isArray(result))
|
|
201
|
-
throw new TypeError("Autocomplete mapResponse must return an array.");
|
|
491
|
+
const result = await source(query, { signal: controller.signal, input });
|
|
492
|
+
if (!Array.isArray(result)) throw new TypeError("Autocomplete source must return an array.");
|
|
202
493
|
if (sequence !== requestSequence) return;
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (suggestions.length === 0) renderStatus(messages.empty, "status", "empty");
|
|
206
|
-
else renderSuggestions();
|
|
207
|
-
dispatch("autocomplete:load", {
|
|
208
|
-
query,
|
|
209
|
-
suggestions: suggestions.map((suggestion) => suggestion.data)
|
|
210
|
-
});
|
|
494
|
+
cache?.set(query, result);
|
|
495
|
+
showResults(query, result, false);
|
|
211
496
|
} catch (error) {
|
|
212
|
-
if (isAbortError(error) || sequence !== requestSequence) return;
|
|
497
|
+
if (isAbortError(error) || sequence !== requestSequence || destroyed) return;
|
|
213
498
|
suggestions = [];
|
|
214
499
|
activeIndex = -1;
|
|
215
|
-
renderStatus(
|
|
500
|
+
renderStatus("error", query);
|
|
216
501
|
dispatch("autocomplete:error", { query, error });
|
|
217
|
-
|
|
502
|
+
options.onError?.(error, input);
|
|
218
503
|
} finally {
|
|
219
504
|
if (sequence === requestSequence) abortController = null;
|
|
220
505
|
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
option.dataset.index = String(index);
|
|
241
|
-
option.setAttribute("aria-selected", "false");
|
|
242
|
-
settings.renderOption?.(option, suggestion.data);
|
|
243
|
-
option.addEventListener("pointerdown", handleOptionPointerDown);
|
|
244
|
-
option.addEventListener("pointerenter", handleOptionPointerEnter);
|
|
245
|
-
fragment.append(option);
|
|
246
|
-
});
|
|
247
|
-
list.append(fragment);
|
|
248
|
-
open();
|
|
249
|
-
}
|
|
250
|
-
function renderStatus(message, role, state) {
|
|
251
|
-
const element = document.createElement("div");
|
|
252
|
-
element.role = role;
|
|
253
|
-
element.textContent = message;
|
|
254
|
-
element.dataset.autocompleteStatus = state;
|
|
255
|
-
list.replaceChildren(element);
|
|
256
|
-
open(true);
|
|
257
|
-
}
|
|
258
|
-
function open(allowStatus = false) {
|
|
259
|
-
if (destroyed || !allowStatus && suggestions.length === 0) return;
|
|
260
|
-
list.hidden = false;
|
|
261
|
-
input.setAttribute("aria-expanded", "true");
|
|
262
|
-
}
|
|
263
|
-
function close() {
|
|
264
|
-
activeIndex = -1;
|
|
265
|
-
list.hidden = true;
|
|
266
|
-
input.setAttribute("aria-expanded", "false");
|
|
267
|
-
input.removeAttribute("aria-activedescendant");
|
|
268
|
-
updateActiveOption();
|
|
269
|
-
}
|
|
270
|
-
function dismiss() {
|
|
271
|
-
cancel();
|
|
272
|
-
close();
|
|
273
|
-
}
|
|
274
|
-
function clear() {
|
|
506
|
+
};
|
|
507
|
+
const scheduleSearch = () => {
|
|
508
|
+
clearTimeout(debounceTimer);
|
|
509
|
+
const query = input.value.trim();
|
|
510
|
+
if (selected !== null) {
|
|
511
|
+
selected = null;
|
|
512
|
+
dispatch("autocomplete:clear", { query });
|
|
513
|
+
}
|
|
514
|
+
if (query.length < minLength) {
|
|
515
|
+
dismiss();
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (debounce === 0) {
|
|
519
|
+
void search(query);
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
debounceTimer = setTimeout(() => void search(query), debounce);
|
|
523
|
+
};
|
|
524
|
+
const clear = () => {
|
|
275
525
|
const hadSelection = selected !== null;
|
|
276
526
|
cancel();
|
|
277
527
|
suggestions = [];
|
|
@@ -279,70 +529,36 @@ function createAutocompleteController(target, options) {
|
|
|
279
529
|
selected = null;
|
|
280
530
|
input.value = "";
|
|
281
531
|
close();
|
|
532
|
+
announce("");
|
|
282
533
|
if (hadSelection) dispatch("autocomplete:clear", { query: "" });
|
|
283
|
-
}
|
|
284
|
-
|
|
534
|
+
};
|
|
535
|
+
const moveActiveOption = (direction) => {
|
|
285
536
|
if (list.hidden || suggestions.length === 0) return;
|
|
286
537
|
activeIndex = activeIndex === -1 ? direction > 0 ? 0 : suggestions.length - 1 : (activeIndex + direction + suggestions.length) % suggestions.length;
|
|
287
538
|
updateActiveOption();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
option.setAttribute("aria-selected", String(active2));
|
|
295
|
-
});
|
|
296
|
-
const active = options2.item(activeIndex);
|
|
297
|
-
if (!active) {
|
|
298
|
-
input.removeAttribute("aria-activedescendant");
|
|
299
|
-
return;
|
|
300
|
-
}
|
|
301
|
-
input.setAttribute("aria-activedescendant", active.id);
|
|
302
|
-
active.scrollIntoView({ block: "nearest" });
|
|
303
|
-
}
|
|
304
|
-
function select(index) {
|
|
305
|
-
const suggestion = suggestions[index];
|
|
306
|
-
if (!suggestion) return;
|
|
307
|
-
selected = suggestion.data;
|
|
308
|
-
input.value = suggestion.value;
|
|
309
|
-
dismiss();
|
|
310
|
-
dispatch("autocomplete:select", {
|
|
311
|
-
suggestion: suggestion.data,
|
|
312
|
-
label: suggestion.label,
|
|
313
|
-
value: suggestion.value
|
|
314
|
-
});
|
|
315
|
-
settings.onSelect?.(suggestion.data, input);
|
|
316
|
-
}
|
|
317
|
-
function dispatch(name, detail) {
|
|
318
|
-
input.dispatchEvent(new CustomEvent(name, { bubbles: true, detail }));
|
|
319
|
-
}
|
|
320
|
-
function cancel() {
|
|
321
|
-
window.clearTimeout(debounceTimer);
|
|
322
|
-
debounceTimer = void 0;
|
|
323
|
-
requestSequence += 1;
|
|
324
|
-
abortController?.abort();
|
|
325
|
-
abortController = null;
|
|
326
|
-
}
|
|
327
|
-
function handleFocus() {
|
|
539
|
+
};
|
|
540
|
+
const handleInput = (event) => {
|
|
541
|
+
if (event.isComposing) return;
|
|
542
|
+
scheduleSearch();
|
|
543
|
+
};
|
|
544
|
+
const handleFocus = () => {
|
|
328
545
|
if (suppressFocusSearch) return;
|
|
329
|
-
if (
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
function handleKeydown(event) {
|
|
546
|
+
if (searchOnFocus && input.value.trim().length >= minLength) scheduleSearch();
|
|
547
|
+
};
|
|
548
|
+
const handleKeydown = (event) => {
|
|
549
|
+
const listOpen = !list.hidden && suggestions.length > 0;
|
|
334
550
|
if (event.key === "ArrowDown" || event.key === "ArrowUp") {
|
|
335
|
-
if (
|
|
551
|
+
if (!listOpen) return;
|
|
336
552
|
event.preventDefault();
|
|
337
553
|
moveActiveOption(event.key === "ArrowDown" ? 1 : -1);
|
|
338
554
|
return;
|
|
339
555
|
}
|
|
340
|
-
if (event.key === "Enter" &&
|
|
556
|
+
if (event.key === "Enter" && listOpen && activeIndex >= 0) {
|
|
341
557
|
event.preventDefault();
|
|
342
558
|
select(activeIndex);
|
|
343
559
|
return;
|
|
344
560
|
}
|
|
345
|
-
if ((event.key === "Home" || event.key === "End") &&
|
|
561
|
+
if ((event.key === "Home" || event.key === "End") && listOpen) {
|
|
346
562
|
event.preventDefault();
|
|
347
563
|
activeIndex = event.key === "Home" ? 0 : suggestions.length - 1;
|
|
348
564
|
updateActiveOption();
|
|
@@ -354,26 +570,11 @@ function createAutocompleteController(target, options) {
|
|
|
354
570
|
return;
|
|
355
571
|
}
|
|
356
572
|
if (event.key === "Tab") dismiss();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
event.
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
if (document.activeElement !== input) {
|
|
363
|
-
suppressFocusSearch = true;
|
|
364
|
-
input.focus({ preventScroll: true });
|
|
365
|
-
suppressFocusSearch = false;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
function handleOptionPointerEnter(event) {
|
|
369
|
-
const element = event.currentTarget;
|
|
370
|
-
activeIndex = Number(element.dataset.index);
|
|
371
|
-
updateActiveOption();
|
|
372
|
-
}
|
|
373
|
-
function handleDocumentPointerDown(event) {
|
|
374
|
-
if (event.target !== input && !list.contains(event.target)) dismiss();
|
|
375
|
-
}
|
|
376
|
-
function destroy2() {
|
|
573
|
+
};
|
|
574
|
+
const handleDocumentPointerDown = (event) => {
|
|
575
|
+
if (event.target !== input && !popup.contains(event.target)) dismiss();
|
|
576
|
+
};
|
|
577
|
+
const destroy = () => {
|
|
377
578
|
if (destroyed) return;
|
|
378
579
|
destroyed = true;
|
|
379
580
|
cancel();
|
|
@@ -382,9 +583,12 @@ function createAutocompleteController(target, options) {
|
|
|
382
583
|
input.removeEventListener("focus", handleFocus);
|
|
383
584
|
input.removeEventListener("keydown", handleKeydown);
|
|
384
585
|
document.removeEventListener("pointerdown", handleDocumentPointerDown);
|
|
385
|
-
|
|
586
|
+
popup.remove();
|
|
587
|
+
live.remove();
|
|
588
|
+
cache?.clear();
|
|
386
589
|
originalAttributes.forEach((value, name) => restoreAttribute(input, name, value));
|
|
387
|
-
|
|
590
|
+
if (label) restoreAttribute(label, "id", originalLabelId);
|
|
591
|
+
};
|
|
388
592
|
input.addEventListener("input", handleInput);
|
|
389
593
|
input.addEventListener("compositionend", scheduleSearch);
|
|
390
594
|
input.addEventListener("focus", handleFocus);
|
|
@@ -392,53 +596,102 @@ function createAutocompleteController(target, options) {
|
|
|
392
596
|
document.addEventListener("pointerdown", handleDocumentPointerDown);
|
|
393
597
|
return {
|
|
394
598
|
element: input,
|
|
599
|
+
popupElement: popup,
|
|
395
600
|
listElement: list,
|
|
601
|
+
statusElement: status,
|
|
602
|
+
liveElement: live,
|
|
396
603
|
options,
|
|
397
604
|
search,
|
|
398
605
|
close: dismiss,
|
|
399
606
|
clear,
|
|
400
607
|
cancel,
|
|
608
|
+
clearCache: () => cache?.clear(),
|
|
401
609
|
getSelected: () => selected,
|
|
402
|
-
destroy
|
|
610
|
+
destroy
|
|
403
611
|
};
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
return error instanceof DOMException && error.name === "AbortError";
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// src/api/create-autocomplete.ts
|
|
410
|
-
function createAutocomplete(target, options) {
|
|
411
|
-
return createAutocompleteController(target, options);
|
|
412
|
-
}
|
|
413
|
-
var autocomplete = createAutocomplete;
|
|
612
|
+
};
|
|
613
|
+
var createAutocomplete = autocomplete;
|
|
414
614
|
|
|
415
615
|
// src/browser/registry.ts
|
|
416
616
|
var available = /* @__PURE__ */ Object.create(null);
|
|
417
|
-
|
|
418
|
-
const
|
|
419
|
-
if (!
|
|
420
|
-
available[input.id]?.destroy();
|
|
421
|
-
const controller = createAutocomplete(input, options);
|
|
422
|
-
available[input.id] = controller;
|
|
423
|
-
return controller;
|
|
424
|
-
}
|
|
425
|
-
function destroy(id) {
|
|
426
|
-
available[id]?.destroy();
|
|
617
|
+
var removeRegistration = (id) => {
|
|
618
|
+
const entry = available[id];
|
|
619
|
+
if (!entry) return;
|
|
427
620
|
delete available[id];
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
621
|
+
entry.destroy();
|
|
622
|
+
};
|
|
623
|
+
var newAutocomplete = ({
|
|
624
|
+
id,
|
|
625
|
+
options
|
|
626
|
+
}) => {
|
|
627
|
+
if (!id) {
|
|
628
|
+
console.error("Autocomplete ID is required");
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
const input = document.getElementById(id);
|
|
632
|
+
if (!(input instanceof HTMLInputElement)) {
|
|
633
|
+
console.warn(`Autocomplete input with ID ${id} not found`);
|
|
634
|
+
return null;
|
|
635
|
+
}
|
|
636
|
+
removeRegistration(id);
|
|
637
|
+
const controller = autocomplete(input, options);
|
|
638
|
+
const destroy = controller.destroy;
|
|
639
|
+
const registered = {
|
|
640
|
+
...controller,
|
|
641
|
+
destroy: () => {
|
|
642
|
+
if (available[id] === registered) delete available[id];
|
|
643
|
+
destroy();
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
available[id] = registered;
|
|
647
|
+
return registered;
|
|
648
|
+
};
|
|
649
|
+
var getAutocomplete = (id) => available[id] ?? null;
|
|
650
|
+
var getAutocompletes = () => ({
|
|
651
|
+
...available
|
|
652
|
+
});
|
|
653
|
+
var destroyAutocomplete = (target) => {
|
|
654
|
+
if (typeof target === "string") return removeRegistration(target);
|
|
655
|
+
const entry = Object.entries(available).find(([, controller]) => controller.element === target);
|
|
656
|
+
if (entry) removeRegistration(entry[0]);
|
|
657
|
+
};
|
|
658
|
+
var destroyAutocompletes = () => Object.keys(available).forEach(removeRegistration);
|
|
659
|
+
var Autocomplete = {
|
|
660
|
+
autocomplete,
|
|
661
|
+
newAutocomplete,
|
|
662
|
+
getAutocomplete,
|
|
663
|
+
getAutocompletes,
|
|
664
|
+
destroyAutocomplete,
|
|
665
|
+
destroyAutocompletes,
|
|
433
666
|
available
|
|
434
667
|
};
|
|
668
|
+
var browser = Autocomplete;
|
|
435
669
|
// Annotate the CommonJS export names for ESM import in node:
|
|
436
670
|
0 && (module.exports = {
|
|
671
|
+
Autocomplete,
|
|
672
|
+
DEFAULT_CACHE_SIZE,
|
|
673
|
+
DEFAULT_CACHE_TTL,
|
|
674
|
+
DEFAULT_DEBOUNCE,
|
|
675
|
+
DEFAULT_MAX_RESULTS,
|
|
676
|
+
DEFAULT_MESSAGES,
|
|
677
|
+
DEFAULT_MIN_LENGTH,
|
|
678
|
+
DEFAULT_QUERY_PARAM,
|
|
679
|
+
HIGHLIGHT_ATTRIBUTE,
|
|
680
|
+
LIST_ATTRIBUTE,
|
|
681
|
+
LIVE_ATTRIBUTE,
|
|
682
|
+
OPTION_ATTRIBUTE,
|
|
683
|
+
POPUP_ATTRIBUTE,
|
|
684
|
+
STATUS_ATTRIBUTE,
|
|
437
685
|
autocomplete,
|
|
438
686
|
browser,
|
|
439
687
|
createAutocomplete,
|
|
440
|
-
|
|
688
|
+
createCache,
|
|
689
|
+
filterSuggestions,
|
|
690
|
+
fromEndpoint,
|
|
691
|
+
highlight,
|
|
441
692
|
mapCommonResponse,
|
|
442
|
-
|
|
693
|
+
normalizeText,
|
|
694
|
+
parseJsonResponse,
|
|
695
|
+
renderHighlight
|
|
443
696
|
});
|
|
444
697
|
//# sourceMappingURL=index.cjs.map
|