@oscarpalmer/oui 0.32.0 → 0.33.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/dist/css/combobox.css +93 -42
- package/dist/js/combobox.mjs +192 -250
- package/dist/js/index.d.mts +643 -14
- package/dist/js/index.mjs +4875 -18
- package/dist/js/menu.d.mts +1 -1
- package/dist/js/toolbar.d.mts +1 -1
- package/package.json +4 -4
- package/src/css/combobox.scss +98 -31
- package/src/js/combobox.ts +1623 -847
- package/src/js/internal/navigable.ts +10 -3
package/dist/js/combobox.mjs
CHANGED
|
@@ -161,8 +161,30 @@ var OuiComboboxElement = class extends HTMLElement {
|
|
|
161
161
|
this.clear();
|
|
162
162
|
}
|
|
163
163
|
};
|
|
164
|
-
function
|
|
165
|
-
|
|
164
|
+
function filterOptions(state, options, clear) {
|
|
165
|
+
if (clear || state.autocomplete.none) state.filter = "";
|
|
166
|
+
const filter = state.filter.toLocaleLowerCase();
|
|
167
|
+
const all = options ?? getOptions(state)[0];
|
|
168
|
+
const { length } = all;
|
|
169
|
+
const visible = [];
|
|
170
|
+
for (let index = 0; index < length; index += 1) {
|
|
171
|
+
const option = all[index];
|
|
172
|
+
option.hidden = option.textContent?.toLocaleLowerCase().includes(filter) === false;
|
|
173
|
+
if (!option.hidden) visible.push(option);
|
|
174
|
+
}
|
|
175
|
+
state.empty.hidden = visible.length > 0;
|
|
176
|
+
if (state.empty.hidden) return state.active != null && visible.includes(state.active) ? state.active : visible[0];
|
|
177
|
+
}
|
|
178
|
+
function generateIds(select, prefix) {
|
|
179
|
+
const options = [...select.options];
|
|
180
|
+
const { length } = options;
|
|
181
|
+
for (let index = 0; index < length; index += 1) {
|
|
182
|
+
const option = options[index];
|
|
183
|
+
if (option.id === "") option.id = `${prefix}_option_${index}`;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function getAnchor(type, id, label, select, autocomplete, multiple) {
|
|
187
|
+
const anchor = type === "autocomplete" ? getInput(id, select) : getBox(id, select, multiple);
|
|
166
188
|
setAria(anchor, {
|
|
167
189
|
autocomplete: type === "autocomplete" ? autocomplete.value : void 0,
|
|
168
190
|
controls: `${id}_content`,
|
|
@@ -172,186 +194,80 @@ function createAnchor(type, id, label, select, autocomplete, multiple) {
|
|
|
172
194
|
});
|
|
173
195
|
setAttribute(anchor, ATTRIBUTE_ANCHOR, "");
|
|
174
196
|
setRole(anchor, "combobox");
|
|
175
|
-
return
|
|
197
|
+
return anchor;
|
|
198
|
+
}
|
|
199
|
+
function getAttributeValue(element, attribute, possible, defaultValue) {
|
|
200
|
+
const value = getAttribute(element, attribute);
|
|
201
|
+
return possible.has(value) ? value : defaultValue;
|
|
202
|
+
}
|
|
203
|
+
function getAutoComplete(element, select) {
|
|
204
|
+
let value;
|
|
205
|
+
if (select) value = "list";
|
|
206
|
+
else value = getAttributeValue(element, "autocomplete", AUTOCOMPLETES, "none");
|
|
207
|
+
return {
|
|
208
|
+
value,
|
|
209
|
+
both: value === "both",
|
|
210
|
+
inline: value === "inline",
|
|
211
|
+
list: value === "list",
|
|
212
|
+
none: value === "none"
|
|
213
|
+
};
|
|
176
214
|
}
|
|
177
|
-
function
|
|
215
|
+
function getBox(id, select, multiple) {
|
|
178
216
|
const box = createElement("div", {
|
|
179
217
|
id,
|
|
180
218
|
tabIndex: 0,
|
|
181
219
|
textContent: multiple ? "" : select.selectedOptions[0]?.textContent ?? ""
|
|
182
220
|
});
|
|
183
|
-
let selection;
|
|
184
221
|
if (multiple) {
|
|
185
|
-
selection =
|
|
222
|
+
const selection = createElement("ul", {}, { [ATTRIBUTE_SELECTION]: "" });
|
|
223
|
+
for (const option of select.selectedOptions) selection.append(getSelectionItem(option));
|
|
186
224
|
box.append(selection);
|
|
187
225
|
}
|
|
188
|
-
return
|
|
226
|
+
return box;
|
|
189
227
|
}
|
|
190
|
-
function
|
|
228
|
+
function getContent(id, label, select, autocomplete, multiple) {
|
|
191
229
|
const content = createElement("div", { id: `${id}_content` }, {
|
|
192
230
|
[ATTRIBUTE_CONTENT]: "",
|
|
193
231
|
popover: "",
|
|
194
232
|
tabindex: -1,
|
|
195
233
|
"aria-labelledby": label.id
|
|
196
234
|
});
|
|
197
|
-
const [filter, input] = createFilter(type, id) ?? [];
|
|
198
|
-
const [list, headers, selected] = createList(label, select, autocomplete, multiple);
|
|
199
|
-
const empty = createEmpty();
|
|
200
|
-
if (filter != null) content.append(filter);
|
|
201
|
-
content.append(list, empty);
|
|
202
|
-
return [
|
|
203
|
-
content,
|
|
204
|
-
input,
|
|
205
|
-
headers,
|
|
206
|
-
selected,
|
|
207
|
-
empty
|
|
208
|
-
];
|
|
209
|
-
}
|
|
210
|
-
function createEmpty() {
|
|
211
|
-
return createElement("div", {
|
|
212
|
-
hidden: true,
|
|
213
|
-
role: "status",
|
|
214
|
-
textContent: "No results found"
|
|
215
|
-
}, {
|
|
216
|
-
[ATTRIBUTE_EMPTY]: "",
|
|
217
|
-
"aria-live": "polite"
|
|
218
|
-
});
|
|
219
|
-
}
|
|
220
|
-
function createFilter(type, id) {
|
|
221
|
-
if (type === "autocomplete") return;
|
|
222
|
-
const container = createElement("div", {}, { [ATTRIBUTE_FILTER]: "" });
|
|
223
|
-
const label = createElement("label", {
|
|
224
|
-
htmlFor: `${id}_filter`,
|
|
225
|
-
textContent: "Filter options"
|
|
226
|
-
}, { [ATTRIBUTE_FILTER_LABEL]: "" });
|
|
227
|
-
const filter = createElement("input", { id: `${id}_filter` }, { [ATTRIBUTE_FILTER_INPUT]: "" });
|
|
228
|
-
container.append(label, filter);
|
|
229
|
-
return [container, filter];
|
|
230
|
-
}
|
|
231
|
-
function createHeader(group) {
|
|
232
|
-
return createElement("li", {
|
|
233
|
-
id: group.id,
|
|
234
|
-
role: "presentation",
|
|
235
|
-
textContent: group.label
|
|
236
|
-
}, { [ATTRIBUTE_HEADER]: "" });
|
|
237
|
-
}
|
|
238
|
-
function createInput(id, select) {
|
|
239
|
-
return createElement("input", {
|
|
240
|
-
id,
|
|
241
|
-
value: select.selectedOptions[0]?.textContent ?? ""
|
|
242
|
-
}, {
|
|
243
|
-
[ATTRIBUTE_ANCHOR]: "",
|
|
244
|
-
autocomplete: "off",
|
|
245
|
-
type: "text"
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
function createList(label, select, autocomplete, multiple) {
|
|
249
235
|
const list = createElement("ul", { role: "listbox" }, {
|
|
250
236
|
[ATTRIBUTE_LIST]: "",
|
|
251
237
|
"aria-labelledby": label.id,
|
|
252
238
|
"aria-multiselectable": multiple
|
|
253
239
|
});
|
|
254
|
-
const
|
|
255
|
-
const groupsLength = groups.length;
|
|
240
|
+
const options = [...select.options];
|
|
256
241
|
const selected = [];
|
|
257
|
-
let headers;
|
|
258
|
-
if (groupsLength > 0) {
|
|
259
|
-
headers = [];
|
|
260
|
-
let firstGroup;
|
|
261
|
-
for (let groupIndex = 0; groupIndex < groupsLength; groupIndex += 1) {
|
|
262
|
-
const group = groups[groupIndex];
|
|
263
|
-
const options = [...group.querySelectorAll("option")];
|
|
264
|
-
if (options.length === 0) continue;
|
|
265
|
-
firstGroup ??= groupIndex;
|
|
266
|
-
const header = createHeader(group);
|
|
267
|
-
headers.push(header);
|
|
268
|
-
list.append(header);
|
|
269
|
-
createOptions(options, autocomplete, list, selected, groupIndex === firstGroup, header.id);
|
|
270
|
-
}
|
|
271
|
-
} else createOptions([...select.options], autocomplete, list, selected, true);
|
|
272
|
-
return [
|
|
273
|
-
list,
|
|
274
|
-
headers,
|
|
275
|
-
selected
|
|
276
|
-
];
|
|
277
|
-
}
|
|
278
|
-
function createOption(index, option, autocomplete, active, group) {
|
|
279
|
-
return createElement("li", {
|
|
280
|
-
id: option.id,
|
|
281
|
-
role: "option",
|
|
282
|
-
textContent: option.textContent
|
|
283
|
-
}, {
|
|
284
|
-
group,
|
|
285
|
-
[ATTRIBUTE_OPTION]: "",
|
|
286
|
-
[ATTRIBUTE_OPTION_ACTIVE]: active && index === 0 ? "" : void 0,
|
|
287
|
-
value: option.value,
|
|
288
|
-
"aria-selected": !autocomplete.none && option.selected
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
function createOptions(options, autocomplete, list, selected, active, group) {
|
|
292
242
|
for (let index = 0; index < options.length; index += 1) {
|
|
293
243
|
const option = options[index];
|
|
294
|
-
const item =
|
|
244
|
+
const item = createElement("li", {
|
|
245
|
+
id: option.id,
|
|
246
|
+
textContent: option.textContent
|
|
247
|
+
}, {
|
|
248
|
+
[ATTRIBUTE_OPTION]: "",
|
|
249
|
+
[ATTRIBUTE_OPTION_ACTIVE]: index === 0 ? "" : void 0,
|
|
250
|
+
role: "option",
|
|
251
|
+
value: option.value,
|
|
252
|
+
"aria-selected": !autocomplete.none && option.selected
|
|
253
|
+
});
|
|
295
254
|
if (option.selected) selected.push(item);
|
|
296
255
|
list.append(item);
|
|
297
256
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
let { length } = all;
|
|
313
|
-
const visible = [];
|
|
314
|
-
for (let index = 0; index < length; index += 1) {
|
|
315
|
-
const option = all[index];
|
|
316
|
-
option.hidden = option.textContent?.toLocaleLowerCase().includes(filter) === false;
|
|
317
|
-
if (!option.hidden) {
|
|
318
|
-
groups[getAttribute(option, "group") ?? ""] += 1;
|
|
319
|
-
visible.push(option);
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
if (state.headers != null) {
|
|
323
|
-
length = state.headers.length;
|
|
324
|
-
for (let index = 0; index < length; index += 1) {
|
|
325
|
-
const header = state.headers[index];
|
|
326
|
-
header.hidden = groups[header.id] === 0;
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
state.empty.hidden = visible.length > 0;
|
|
330
|
-
if (state.empty.hidden) return state.active != null && visible.includes(state.active) ? state.active : visible[0];
|
|
331
|
-
}
|
|
332
|
-
function generateIds(select, prefix) {
|
|
333
|
-
const elements = [...select.querySelectorAll("optgroup"), ...select.options];
|
|
334
|
-
const { length } = elements;
|
|
335
|
-
for (let index = 0; index < length; index += 1) {
|
|
336
|
-
const element = elements[index];
|
|
337
|
-
if (element.id === "") element.id = `${prefix}${element instanceof HTMLOptGroupElement ? "_group_" : "_option_"}${index}`;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function getAttributeValue(element, attribute, possible, defaultValue) {
|
|
341
|
-
const value = getAttribute(element, attribute);
|
|
342
|
-
return possible.has(value) ? value : defaultValue;
|
|
343
|
-
}
|
|
344
|
-
function getAutoComplete(element, select) {
|
|
345
|
-
let value;
|
|
346
|
-
if (select) value = "list";
|
|
347
|
-
else value = getAttributeValue(element, "autocomplete", AUTOCOMPLETES, "none");
|
|
348
|
-
return {
|
|
349
|
-
value,
|
|
350
|
-
both: value === "both",
|
|
351
|
-
inline: value === "inline",
|
|
352
|
-
list: value === "list",
|
|
353
|
-
none: value === "none"
|
|
354
|
-
};
|
|
257
|
+
const empty = createElement("div", {
|
|
258
|
+
hidden: true,
|
|
259
|
+
role: "status",
|
|
260
|
+
textContent: "No results found"
|
|
261
|
+
}, {
|
|
262
|
+
[ATTRIBUTE_EMPTY]: "",
|
|
263
|
+
"aria-live": "polite"
|
|
264
|
+
});
|
|
265
|
+
content.append(list, empty);
|
|
266
|
+
return [
|
|
267
|
+
content,
|
|
268
|
+
selected,
|
|
269
|
+
empty
|
|
270
|
+
];
|
|
355
271
|
}
|
|
356
272
|
function getFormValue(state, values) {
|
|
357
273
|
if (values.length === 0 || !state.multiple) return values[0];
|
|
@@ -371,6 +287,16 @@ function getId() {
|
|
|
371
287
|
index += 1;
|
|
372
288
|
return `oui_combobox_${index}`;
|
|
373
289
|
}
|
|
290
|
+
function getInput(id, select) {
|
|
291
|
+
return createElement("input", {
|
|
292
|
+
id,
|
|
293
|
+
value: select.selectedOptions[0]?.textContent ?? ""
|
|
294
|
+
}, {
|
|
295
|
+
[ATTRIBUTE_ANCHOR]: "",
|
|
296
|
+
autocomplete: "off",
|
|
297
|
+
type: "text"
|
|
298
|
+
});
|
|
299
|
+
}
|
|
374
300
|
function getOption(state, value) {
|
|
375
301
|
const [all] = getOptions(state);
|
|
376
302
|
const valueAsString = getString(value).toLocaleLowerCase();
|
|
@@ -429,8 +355,8 @@ function getState(element, internals) {
|
|
|
429
355
|
label.htmlFor = id;
|
|
430
356
|
label.id = `${id}_label`;
|
|
431
357
|
generateIds(select, id);
|
|
432
|
-
const
|
|
433
|
-
const [content,
|
|
358
|
+
const anchor = getAnchor(type, id, label, select, autocomplete, multiple);
|
|
359
|
+
const [content, selected, empty] = getContent(id, label, select, autocomplete, multiple);
|
|
434
360
|
const values = selected.length === 0 ? [""] : selected.map((option) => getAttribute(option, "value") ?? "");
|
|
435
361
|
const floatable = createEmbeddedFloatable(anchor, content, {
|
|
436
362
|
position: "below-start",
|
|
@@ -447,7 +373,6 @@ function getState(element, internals) {
|
|
|
447
373
|
disabled,
|
|
448
374
|
empty,
|
|
449
375
|
floatable,
|
|
450
|
-
headers,
|
|
451
376
|
id,
|
|
452
377
|
internals,
|
|
453
378
|
label,
|
|
@@ -456,19 +381,14 @@ function getState(element, internals) {
|
|
|
456
381
|
required,
|
|
457
382
|
select,
|
|
458
383
|
selected,
|
|
459
|
-
selection,
|
|
460
384
|
type,
|
|
461
385
|
values,
|
|
462
|
-
|
|
463
|
-
filter: {
|
|
464
|
-
element: filter,
|
|
465
|
-
value: ""
|
|
466
|
-
},
|
|
386
|
+
filter: "",
|
|
467
387
|
listeners: [],
|
|
468
388
|
name: element.name,
|
|
469
389
|
touched: false
|
|
470
390
|
};
|
|
471
|
-
state.listeners.push(on(
|
|
391
|
+
state.listeners.push(on(anchor, "blur", () => {
|
|
472
392
|
state.touched = true;
|
|
473
393
|
updateValidity(state);
|
|
474
394
|
}), on(content, "click", (event) => {
|
|
@@ -478,21 +398,84 @@ function getState(element, internals) {
|
|
|
478
398
|
activedescendant: floatable.open ? state.active?.id : void 0,
|
|
479
399
|
expanded: floatable.open
|
|
480
400
|
});
|
|
481
|
-
if (floatable.open)
|
|
482
|
-
state.filter.element?.focus();
|
|
483
|
-
updateWidth(state);
|
|
484
|
-
}
|
|
401
|
+
if (floatable.open) updateWidth(state);
|
|
485
402
|
}));
|
|
486
403
|
setDisabled(element, state, disabled);
|
|
487
|
-
setLimit("max", element, state, getAttribute(element, "max"));
|
|
488
|
-
setLimit("min", element, state, getAttribute(element, "min"));
|
|
489
|
-
setReadonly(element, state, readonly);
|
|
490
|
-
setRequired(element, state, required);
|
|
491
404
|
return state;
|
|
492
405
|
}
|
|
493
406
|
function hasTextSelection(element) {
|
|
494
407
|
return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
495
408
|
}
|
|
409
|
+
function onAnchor(event, handler) {
|
|
410
|
+
if (event.ctrlKey || event.metaKey || event.shiftKey) return;
|
|
411
|
+
const combobox = findAncestor(event, TAGNAME);
|
|
412
|
+
const state = states.get(combobox);
|
|
413
|
+
if (combobox == null || state == null) return;
|
|
414
|
+
if (findAncestor(event, SELECTOR_ANCHOR) != null) handler(event, combobox, state);
|
|
415
|
+
}
|
|
416
|
+
function onAnchorKeydown(event, combobox, state) {
|
|
417
|
+
const isSelect = state.type === "select";
|
|
418
|
+
let preventDefault = true;
|
|
419
|
+
switch (true) {
|
|
420
|
+
case event.key === " " && state.type === "select":
|
|
421
|
+
case event.key === "Enter":
|
|
422
|
+
onEnter(combobox, state);
|
|
423
|
+
break;
|
|
424
|
+
case event.key === "Escape":
|
|
425
|
+
onEscape(state);
|
|
426
|
+
break;
|
|
427
|
+
case isSelect && KEYS_ABSOLUTE.has(event.key):
|
|
428
|
+
onNavigation(event, combobox, state);
|
|
429
|
+
break;
|
|
430
|
+
case !isSelect && KEYS_SELECTION.has(event.key):
|
|
431
|
+
preventDefault = false;
|
|
432
|
+
if (hasTextSelection(state.anchor)) onSelection(combobox, state);
|
|
433
|
+
break;
|
|
434
|
+
case KEYS_NAVIGATION.has(event.key):
|
|
435
|
+
onNavigation(event, combobox, state);
|
|
436
|
+
break;
|
|
437
|
+
default:
|
|
438
|
+
preventDefault = false;
|
|
439
|
+
if (event.key === "Tab") onTab(combobox, state);
|
|
440
|
+
break;
|
|
441
|
+
}
|
|
442
|
+
if (preventDefault) {
|
|
443
|
+
event.preventDefault();
|
|
444
|
+
event.stopPropagation();
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function onAnchorKeyup(event, combobox, state) {
|
|
448
|
+
const typed = event.key.length === 1 && EXPRESSION_TYPED.test(event.key);
|
|
449
|
+
if (state.type === "select") {
|
|
450
|
+
if (typed) {
|
|
451
|
+
event.preventDefault();
|
|
452
|
+
event.stopPropagation();
|
|
453
|
+
onScrollTo(event, combobox, state);
|
|
454
|
+
}
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
let preventDefault = true;
|
|
458
|
+
if (typed) state.filter += event.key;
|
|
459
|
+
if (state.anchor instanceof HTMLInputElement && (KEYS_REMOVE.has(event.key) || state.anchor.value.length < state.filter.length)) {
|
|
460
|
+
state.filter = state.anchor.value;
|
|
461
|
+
filterOptions(state);
|
|
462
|
+
}
|
|
463
|
+
switch (true) {
|
|
464
|
+
case typed:
|
|
465
|
+
onTyped(combobox, state);
|
|
466
|
+
break;
|
|
467
|
+
case KEYS_REMOVE.has(event.key):
|
|
468
|
+
onRemove(combobox, state);
|
|
469
|
+
break;
|
|
470
|
+
default:
|
|
471
|
+
preventDefault = false;
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
if (preventDefault) {
|
|
475
|
+
event.preventDefault();
|
|
476
|
+
event.stopPropagation();
|
|
477
|
+
}
|
|
478
|
+
}
|
|
496
479
|
function onClick(event) {
|
|
497
480
|
const combobox = findAncestor(event, TAGNAME);
|
|
498
481
|
const state = states.get(combobox);
|
|
@@ -521,7 +504,7 @@ function onContent(event, content) {
|
|
|
521
504
|
if (combobox == null || state == null) return;
|
|
522
505
|
const option = findAncestor(event, SELECTOR_OPTION);
|
|
523
506
|
if (option instanceof HTMLElement) setNext("set", combobox, state, getOptions(state)[0], option);
|
|
524
|
-
|
|
507
|
+
state.anchor.focus();
|
|
525
508
|
}
|
|
526
509
|
function onEnter(combobox, state) {
|
|
527
510
|
if (hasTextSelection(state.anchor)) {
|
|
@@ -537,58 +520,11 @@ function onEnter(combobox, state) {
|
|
|
537
520
|
function onEscape(state) {
|
|
538
521
|
state.floatable.hide();
|
|
539
522
|
}
|
|
540
|
-
function onInput(event, handler) {
|
|
541
|
-
if (event.ctrlKey || event.metaKey) return;
|
|
542
|
-
const combobox = findAncestor(event, TAGNAME);
|
|
543
|
-
const state = states.get(combobox);
|
|
544
|
-
if (combobox == null || state == null) return;
|
|
545
|
-
if ((findAncestor(event, SELECTOR_ANCHOR) ?? findAncestor(event, SELECTOR_FILTER)) != null) handler(event, combobox, state);
|
|
546
|
-
}
|
|
547
|
-
function onInputKeydown(event, combobox, state) {
|
|
548
|
-
const isSelect = state.type === "select";
|
|
549
|
-
let preventDefault = true;
|
|
550
|
-
switch (true) {
|
|
551
|
-
case event.key === "Enter":
|
|
552
|
-
onEnter(combobox, state);
|
|
553
|
-
break;
|
|
554
|
-
case event.key === "Escape":
|
|
555
|
-
onEscape(state);
|
|
556
|
-
break;
|
|
557
|
-
case !isSelect && KEYS_SELECTION.has(event.key):
|
|
558
|
-
preventDefault = false;
|
|
559
|
-
if (hasTextSelection(state.anchor)) onSelection(combobox, state);
|
|
560
|
-
break;
|
|
561
|
-
case KEYS_NAVIGATION.has(event.key):
|
|
562
|
-
onNavigation(event, combobox, state);
|
|
563
|
-
break;
|
|
564
|
-
default:
|
|
565
|
-
preventDefault = false;
|
|
566
|
-
if (event.key === "Tab") onTab(combobox, state);
|
|
567
|
-
break;
|
|
568
|
-
}
|
|
569
|
-
if (preventDefault) {
|
|
570
|
-
event.preventDefault();
|
|
571
|
-
event.stopPropagation();
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
function onInputKeyup(event, combobox, state) {
|
|
575
|
-
const filter = state.filter.element ?? state.anchor;
|
|
576
|
-
const typed = event.key.length === 1 && EXPRESSION_TYPED.test(event.key);
|
|
577
|
-
if (!(filter instanceof HTMLInputElement) || !(typed || KEYS_REMOVE.has(event.key) || filter.value.length < state.filter.value.length)) return;
|
|
578
|
-
event.preventDefault();
|
|
579
|
-
event.stopPropagation();
|
|
580
|
-
if (typed) state.filter.value += event.key;
|
|
581
|
-
else state.filter.value = filter.value;
|
|
582
|
-
const [options] = getOptions(state);
|
|
583
|
-
const next = filterOptions(state, options);
|
|
584
|
-
if (typed) onTyped(combobox, state, options, next);
|
|
585
|
-
else setNext("remove", combobox, state, options, next);
|
|
586
|
-
}
|
|
587
523
|
function onKeydown(event) {
|
|
588
|
-
|
|
524
|
+
onAnchor(event, onAnchorKeydown);
|
|
589
525
|
}
|
|
590
526
|
function onKeyup(event) {
|
|
591
|
-
|
|
527
|
+
onAnchor(event, onAnchorKeyup);
|
|
592
528
|
}
|
|
593
529
|
function onNavigation(event, combobox, state) {
|
|
594
530
|
let skip = false;
|
|
@@ -624,22 +560,34 @@ function onNavigation(event, combobox, state) {
|
|
|
624
560
|
}
|
|
625
561
|
setNext(state.autocomplete.none ? "none" : "navigation", combobox, state, all, next);
|
|
626
562
|
}
|
|
563
|
+
function onRemove(combobox, state) {
|
|
564
|
+
const [all] = getOptions(state);
|
|
565
|
+
setNext("remove", combobox, state, all, filterOptions(state, all));
|
|
566
|
+
}
|
|
567
|
+
function onScrollTo(event, combobox, state) {
|
|
568
|
+
const [all] = getOptions(state);
|
|
569
|
+
const key = event.key.toLocaleLowerCase();
|
|
570
|
+
const next = all.find((option) => option.textContent?.toLocaleLowerCase().startsWith(key) === true) ?? all.find((option) => option.textContent?.toLocaleLowerCase().includes(key) === true);
|
|
571
|
+
if (next != null) setNext("navigation", combobox, state, all, next);
|
|
572
|
+
}
|
|
627
573
|
function onSelection(combobox, state) {
|
|
628
|
-
state.filter
|
|
574
|
+
state.filter = state.anchor instanceof HTMLInputElement ? state.anchor.value : "";
|
|
629
575
|
filterOptions(state);
|
|
630
576
|
setNext("set", combobox, state, getOptions(state)[0], state.active);
|
|
631
577
|
}
|
|
632
578
|
function onTab(combobox, state) {
|
|
633
579
|
if (combobox !== document.activeElement || !combobox.contains(document.activeElement)) state.floatable.hide();
|
|
634
580
|
}
|
|
635
|
-
function onTyped(combobox, state
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
setNext("typed", combobox, state, all, state.autocomplete.none ? state.active ?? all[0] : next ?? filterOptions(state, options));
|
|
581
|
+
function onTyped(combobox, state) {
|
|
582
|
+
if (!state.autocomplete.inline && state.anchor instanceof HTMLInputElement && state.anchor.value.length > 0) state.floatable.show();
|
|
583
|
+
const options = getOptions(state)[0];
|
|
584
|
+
setNext("typed", combobox, state, options, state.autocomplete.none ? state.active ?? options[0] : filterOptions(state));
|
|
640
585
|
}
|
|
641
586
|
function setDisabled(combobox, state, value) {
|
|
642
|
-
|
|
587
|
+
state.disabled = value;
|
|
588
|
+
setAria(state.anchor, "disabled", value);
|
|
589
|
+
setProperty(combobox, "disabled", value);
|
|
590
|
+
if (state.anchor instanceof HTMLInputElement) state.anchor.readOnly = value;
|
|
643
591
|
}
|
|
644
592
|
function setLimit(type, combobox, state, value) {
|
|
645
593
|
if (typeof value === "number" && !Number.isNaN(value) ? value !== state[type] : value == null) {
|
|
@@ -655,18 +603,16 @@ function setNext(update, combobox, state, all, next) {
|
|
|
655
603
|
next?.scrollIntoView(scrollOptions);
|
|
656
604
|
setValue(update, combobox, state, next);
|
|
657
605
|
}
|
|
658
|
-
function setProperties(key, property, combobox, state, value) {
|
|
659
|
-
state[key] = value;
|
|
660
|
-
setAria(state.anchor, key, value);
|
|
661
|
-
setProperty(combobox, key, value);
|
|
662
|
-
if (state.filter.element != null) state.filter.element[property] = value;
|
|
663
|
-
if (state.anchor instanceof HTMLInputElement) state.anchor[property] = value;
|
|
664
|
-
}
|
|
665
606
|
function setReadonly(combobox, state, value) {
|
|
666
|
-
|
|
607
|
+
state.readonly = value;
|
|
608
|
+
setAria(state.anchor, "readonly", value);
|
|
609
|
+
setProperty(combobox, "readonly", value);
|
|
610
|
+
if (state.anchor instanceof HTMLInputElement) state.anchor.readOnly = value;
|
|
667
611
|
}
|
|
668
612
|
function setRequired(combobox, state, value) {
|
|
669
|
-
|
|
613
|
+
state.required = value;
|
|
614
|
+
setAria(state.anchor, "required", value);
|
|
615
|
+
setProperty(combobox, "required", value);
|
|
670
616
|
updateValidity(state);
|
|
671
617
|
}
|
|
672
618
|
function setValue(update, combobox, state, next) {
|
|
@@ -719,11 +665,11 @@ function setValues(combobox, state, value) {
|
|
|
719
665
|
}
|
|
720
666
|
for (const option of state.selected) {
|
|
721
667
|
setAria(option, "selected", false);
|
|
722
|
-
state.
|
|
668
|
+
state.anchor.querySelector(`[${ATTRIBUTE_SELECTION_ITEM}][option="${option.id}"]`)?.remove();
|
|
723
669
|
}
|
|
724
670
|
for (const option of selected) {
|
|
725
671
|
setAria(option, "selected", true);
|
|
726
|
-
state.
|
|
672
|
+
state.anchor.querySelector(`[${ATTRIBUTE_SELECTION}]`)?.append(getSelectionItem(option));
|
|
727
673
|
}
|
|
728
674
|
state.selected = selected.slice();
|
|
729
675
|
setNext("navigation", combobox, state, all, next);
|
|
@@ -732,11 +678,11 @@ function setValues(combobox, state, value) {
|
|
|
732
678
|
function updateAutoComplete(update, state, next) {
|
|
733
679
|
if (!(update === "set" || state.autocomplete.both || state.autocomplete.inline)) return;
|
|
734
680
|
const content = next.textContent ?? "";
|
|
735
|
-
if (update === "typed" && !content.toLocaleLowerCase().startsWith(state.filter.
|
|
681
|
+
if (update === "typed" && !content.toLocaleLowerCase().startsWith(state.filter.toLocaleLowerCase())) return;
|
|
736
682
|
const { length } = content;
|
|
737
683
|
if (state.anchor instanceof HTMLInputElement) {
|
|
738
684
|
state.anchor.value = content;
|
|
739
|
-
state.anchor.setSelectionRange(update === "typed" ? state.filter.
|
|
685
|
+
state.anchor.setSelectionRange(update === "typed" ? state.filter.length : length, length);
|
|
740
686
|
} else state.anchor.textContent = content;
|
|
741
687
|
}
|
|
742
688
|
function updateForm(combobox, state, values) {
|
|
@@ -754,8 +700,8 @@ function updateSelect(update, state, next, added) {
|
|
|
754
700
|
state.anchor.textContent = next.textContent ?? "";
|
|
755
701
|
return;
|
|
756
702
|
}
|
|
757
|
-
if (added === true) state.
|
|
758
|
-
else if (added === false) state.
|
|
703
|
+
if (added === true) state.anchor.querySelector(`[${ATTRIBUTE_SELECTION}]`)?.append(getSelectionItem(next));
|
|
704
|
+
else if (added === false) state.anchor.querySelector(`[${ATTRIBUTE_SELECTION_ITEM}][option="${next.id}"]`)?.remove();
|
|
759
705
|
}
|
|
760
706
|
function updateSelected(update, state, next) {
|
|
761
707
|
const [all] = getOptions(state);
|
|
@@ -804,10 +750,6 @@ const TAGNAME = "oui-combobox";
|
|
|
804
750
|
const ATTRIBUTE_ANCHOR = `${TAGNAME}-anchor`;
|
|
805
751
|
const ATTRIBUTE_CONTENT = `${TAGNAME}-content`;
|
|
806
752
|
const ATTRIBUTE_EMPTY = `${TAGNAME}-empty`;
|
|
807
|
-
const ATTRIBUTE_FILTER = `${TAGNAME}-filter`;
|
|
808
|
-
const ATTRIBUTE_FILTER_INPUT = `${ATTRIBUTE_FILTER}-input`;
|
|
809
|
-
const ATTRIBUTE_FILTER_LABEL = `${ATTRIBUTE_FILTER}-label`;
|
|
810
|
-
const ATTRIBUTE_HEADER = `${TAGNAME}-header`;
|
|
811
753
|
const ATTRIBUTE_LIST = `${TAGNAME}-list`;
|
|
812
754
|
const ATTRIBUTE_OPTION = `${TAGNAME}-option`;
|
|
813
755
|
const ATTRIBUTE_OPTION_ACTIVE = `${TAGNAME}-option-active`;
|
|
@@ -824,6 +766,7 @@ const INVALID_BAD = "Please select an option from the list";
|
|
|
824
766
|
const INVALID_INPUT = "Please enter a value";
|
|
825
767
|
const INVALID_OVERFLOW = "Please select fewer options (maximum is {max})";
|
|
826
768
|
const INVALID_UNDERFLOW = "Please select more options (minimum is {min})";
|
|
769
|
+
const KEYS_ABSOLUTE = /* @__PURE__ */ new Set(["End", "Home"]);
|
|
827
770
|
const KEYS_NAVIGATION = /* @__PURE__ */ new Set([
|
|
828
771
|
"ArrowDown",
|
|
829
772
|
"ArrowUp",
|
|
@@ -836,7 +779,6 @@ const MESSAGE_LABEL = `<${TAGNAME}> must contain a <label> element for the <sele
|
|
|
836
779
|
const MESSAGE_SELECT = `<${TAGNAME}> must contain a valid <select> element`;
|
|
837
780
|
const PROPERTY_STYLE = "--oui-combobox-width";
|
|
838
781
|
const SELECTOR_ANCHOR = `[${ATTRIBUTE_ANCHOR}]`;
|
|
839
|
-
const SELECTOR_FILTER = `[${ATTRIBUTE_FILTER_INPUT}]`;
|
|
840
782
|
const SELECTOR_OPTION = `[${ATTRIBUTE_OPTION}]`;
|
|
841
783
|
const TYPES = /* @__PURE__ */ new Set(["autocomplete", "select"]);
|
|
842
784
|
const comboboxes = /* @__PURE__ */ new WeakMap();
|