@oscarpalmer/oui 0.31.0 → 0.32.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/badge.css +1 -5
- package/dist/css/combobox.css +95 -10
- package/dist/js/combobox.mjs +250 -192
- package/dist/js/menu.d.mts +1 -1
- package/dist/js/toolbar.d.mts +1 -1
- package/package.json +2 -2
- package/src/css/combobox.scss +92 -13
- package/src/css/mixins.scss +8 -16
- package/src/js/combobox.ts +487 -332
- package/src/js/menu.ts +1 -1
package/src/js/combobox.ts
CHANGED
|
@@ -205,6 +205,11 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
type OuiComboboxFilter = {
|
|
209
|
+
element?: HTMLInputElement;
|
|
210
|
+
value: string;
|
|
211
|
+
};
|
|
212
|
+
|
|
208
213
|
type OuiComboboxAutoComplete = Record<OuiComboboxAutoCompleteType, boolean> & {
|
|
209
214
|
value: OuiComboboxAutoCompleteType;
|
|
210
215
|
};
|
|
@@ -218,8 +223,9 @@ type OuiComboboxState = {
|
|
|
218
223
|
content: HTMLElement;
|
|
219
224
|
disabled: boolean;
|
|
220
225
|
empty: HTMLElement;
|
|
221
|
-
filter:
|
|
226
|
+
filter: OuiComboboxFilter;
|
|
222
227
|
floatable: OuiFloatable;
|
|
228
|
+
headers?: HTMLElement[];
|
|
223
229
|
id: string;
|
|
224
230
|
internals: ElementInternals;
|
|
225
231
|
label: HTMLLabelElement;
|
|
@@ -232,6 +238,7 @@ type OuiComboboxState = {
|
|
|
232
238
|
required: boolean;
|
|
233
239
|
select: HTMLSelectElement;
|
|
234
240
|
selected: HTMLElement[];
|
|
241
|
+
selection?: HTMLElement;
|
|
235
242
|
touched: boolean;
|
|
236
243
|
type: OuiComboboxType;
|
|
237
244
|
values: string[];
|
|
@@ -245,61 +252,16 @@ type OuiComboboxUpdate = 'navigation' | 'none' | 'remove' | 'set' | 'typed';
|
|
|
245
252
|
|
|
246
253
|
// #region Functions
|
|
247
254
|
|
|
248
|
-
function
|
|
249
|
-
state: OuiComboboxState,
|
|
250
|
-
options?: HTMLElement[],
|
|
251
|
-
clear?: boolean,
|
|
252
|
-
): HTMLElement | undefined {
|
|
253
|
-
if (clear || state.autocomplete.none) {
|
|
254
|
-
state.filter = '';
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const filter = state.filter.toLocaleLowerCase();
|
|
258
|
-
|
|
259
|
-
const all = options ?? getOptions(state)[0];
|
|
260
|
-
const {length} = all;
|
|
261
|
-
|
|
262
|
-
const visible: HTMLElement[] = [];
|
|
263
|
-
|
|
264
|
-
for (let index = 0; index < length; index += 1) {
|
|
265
|
-
const option = all[index];
|
|
266
|
-
|
|
267
|
-
option.hidden = option.textContent?.toLocaleLowerCase().includes(filter) === false;
|
|
268
|
-
|
|
269
|
-
if (!option.hidden) {
|
|
270
|
-
visible.push(option);
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
state.empty.hidden = visible.length > 0;
|
|
275
|
-
|
|
276
|
-
if (state.empty.hidden) {
|
|
277
|
-
return state.active != null && visible.includes(state.active) ? state.active : visible[0];
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
function generateIds(select: HTMLSelectElement, prefix: string): void {
|
|
282
|
-
const options = [...select.options];
|
|
283
|
-
const {length} = options;
|
|
284
|
-
|
|
285
|
-
for (let index = 0; index < length; index += 1) {
|
|
286
|
-
const option = options[index];
|
|
287
|
-
|
|
288
|
-
if (option.id === '') {
|
|
289
|
-
option.id = `${prefix}_option_${index}`;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
function getAnchor(
|
|
255
|
+
function createAnchor(
|
|
295
256
|
type: OuiComboboxType,
|
|
296
257
|
id: string,
|
|
297
258
|
label: HTMLLabelElement,
|
|
298
259
|
select: HTMLSelectElement,
|
|
299
260
|
autocomplete: OuiComboboxAutoComplete,
|
|
300
261
|
multiple: boolean,
|
|
301
|
-
): HTMLElement {
|
|
302
|
-
const anchor
|
|
262
|
+
): [HTMLElement, HTMLElement | undefined] {
|
|
263
|
+
const [anchor, selection] =
|
|
264
|
+
type === 'autocomplete' ? [createInput(id, select)] : createBox(id, select, multiple);
|
|
303
265
|
|
|
304
266
|
setAria(anchor, {
|
|
305
267
|
autocomplete: type === 'autocomplete' ? autocomplete.value : undefined,
|
|
@@ -310,76 +272,47 @@ function getAnchor(
|
|
|
310
272
|
});
|
|
311
273
|
|
|
312
274
|
setAttribute(anchor, ATTRIBUTE_ANCHOR, '');
|
|
313
|
-
|
|
314
275
|
setRole(anchor, 'combobox');
|
|
315
276
|
|
|
316
|
-
return anchor;
|
|
277
|
+
return [anchor, selection];
|
|
317
278
|
}
|
|
318
279
|
|
|
319
|
-
function
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
): Value {
|
|
325
|
-
const value = getAttribute(element, attribute);
|
|
326
|
-
|
|
327
|
-
return possible.has(value as Value) ? (value as Value) : defaultValue;
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
function getAutoComplete(element: OuiComboboxElement, select: boolean): OuiComboboxAutoComplete {
|
|
331
|
-
let value: OuiComboboxAutoCompleteType;
|
|
332
|
-
|
|
333
|
-
if (select) {
|
|
334
|
-
value = 'list';
|
|
335
|
-
} else {
|
|
336
|
-
value = getAttributeValue(element, 'autocomplete', AUTOCOMPLETES, 'none');
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
return {
|
|
340
|
-
value,
|
|
341
|
-
both: value === 'both',
|
|
342
|
-
inline: value === 'inline',
|
|
343
|
-
list: value === 'list',
|
|
344
|
-
none: value === 'none',
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function getBox(id: string, select: HTMLSelectElement, multiple: boolean): HTMLElement {
|
|
280
|
+
function createBox(
|
|
281
|
+
id: string,
|
|
282
|
+
select: HTMLSelectElement,
|
|
283
|
+
multiple: boolean,
|
|
284
|
+
): [HTMLElement, HTMLElement | undefined] {
|
|
349
285
|
const box = createElement('div', {
|
|
350
286
|
id,
|
|
351
287
|
tabIndex: 0,
|
|
352
288
|
textContent: multiple ? '' : (select.selectedOptions[0]?.textContent ?? ''),
|
|
353
289
|
});
|
|
354
290
|
|
|
355
|
-
|
|
356
|
-
const selection = createElement(
|
|
357
|
-
'ul',
|
|
358
|
-
{
|
|
359
|
-
// tabIndex: 0, TODO: handle navigation
|
|
360
|
-
},
|
|
361
|
-
{
|
|
362
|
-
[ATTRIBUTE_SELECTION]: '',
|
|
363
|
-
},
|
|
364
|
-
);
|
|
291
|
+
let selection: HTMLElement | undefined;
|
|
365
292
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
293
|
+
if (multiple) {
|
|
294
|
+
selection = createSelection(select);
|
|
369
295
|
|
|
370
296
|
box.append(selection);
|
|
371
297
|
}
|
|
372
298
|
|
|
373
|
-
return box;
|
|
299
|
+
return [box, selection];
|
|
374
300
|
}
|
|
375
301
|
|
|
376
|
-
function
|
|
302
|
+
function createContent(
|
|
303
|
+
type: OuiComboboxType,
|
|
377
304
|
id: string,
|
|
378
305
|
label: HTMLLabelElement,
|
|
379
306
|
select: HTMLSelectElement,
|
|
380
307
|
autocomplete: OuiComboboxAutoComplete,
|
|
381
308
|
multiple: boolean,
|
|
382
|
-
): [
|
|
309
|
+
): [
|
|
310
|
+
HTMLElement,
|
|
311
|
+
HTMLInputElement | undefined,
|
|
312
|
+
HTMLElement[] | undefined,
|
|
313
|
+
HTMLElement[],
|
|
314
|
+
HTMLElement,
|
|
315
|
+
] {
|
|
383
316
|
const content = createElement(
|
|
384
317
|
'div',
|
|
385
318
|
{
|
|
@@ -393,6 +326,111 @@ function getContent(
|
|
|
393
326
|
},
|
|
394
327
|
);
|
|
395
328
|
|
|
329
|
+
const [filter, input] = createFilter(type, id) ?? [];
|
|
330
|
+
const [list, headers, selected] = createList(label, select, autocomplete, multiple);
|
|
331
|
+
const empty = createEmpty();
|
|
332
|
+
|
|
333
|
+
if (filter != null) {
|
|
334
|
+
content.append(filter);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
content.append(list, empty);
|
|
338
|
+
|
|
339
|
+
return [content, input, headers, selected, empty];
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
function createEmpty(): HTMLElement {
|
|
343
|
+
return createElement(
|
|
344
|
+
'div',
|
|
345
|
+
{
|
|
346
|
+
hidden: true,
|
|
347
|
+
role: 'status',
|
|
348
|
+
textContent: 'No results found',
|
|
349
|
+
},
|
|
350
|
+
{
|
|
351
|
+
[ATTRIBUTE_EMPTY]: '',
|
|
352
|
+
'aria-live': 'polite',
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function createFilter(
|
|
358
|
+
type: OuiComboboxType,
|
|
359
|
+
id: string,
|
|
360
|
+
): [HTMLElement, HTMLInputElement] | undefined {
|
|
361
|
+
if (type === 'autocomplete') {
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
const container = createElement(
|
|
366
|
+
'div',
|
|
367
|
+
{},
|
|
368
|
+
{
|
|
369
|
+
[ATTRIBUTE_FILTER]: '',
|
|
370
|
+
},
|
|
371
|
+
);
|
|
372
|
+
|
|
373
|
+
const label = createElement(
|
|
374
|
+
'label',
|
|
375
|
+
{
|
|
376
|
+
htmlFor: `${id}_filter`,
|
|
377
|
+
textContent: 'Filter options',
|
|
378
|
+
},
|
|
379
|
+
{
|
|
380
|
+
[ATTRIBUTE_FILTER_LABEL]: '',
|
|
381
|
+
},
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
const filter = createElement(
|
|
385
|
+
'input',
|
|
386
|
+
{
|
|
387
|
+
id: `${id}_filter`,
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
[ATTRIBUTE_FILTER_INPUT]: '',
|
|
391
|
+
},
|
|
392
|
+
);
|
|
393
|
+
|
|
394
|
+
container.append(label, filter);
|
|
395
|
+
|
|
396
|
+
return [container, filter];
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function createHeader(group: HTMLOptGroupElement): HTMLElement {
|
|
400
|
+
return createElement(
|
|
401
|
+
'li',
|
|
402
|
+
{
|
|
403
|
+
id: group.id,
|
|
404
|
+
role: 'presentation',
|
|
405
|
+
textContent: group.label,
|
|
406
|
+
},
|
|
407
|
+
{
|
|
408
|
+
[ATTRIBUTE_HEADER]: '',
|
|
409
|
+
},
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function createInput(id: string, select: HTMLSelectElement): HTMLInputElement {
|
|
414
|
+
return createElement(
|
|
415
|
+
'input',
|
|
416
|
+
{
|
|
417
|
+
id,
|
|
418
|
+
value: select.selectedOptions[0]?.textContent ?? '',
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
[ATTRIBUTE_ANCHOR]: '',
|
|
422
|
+
autocomplete: 'off',
|
|
423
|
+
type: 'text',
|
|
424
|
+
},
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function createList(
|
|
429
|
+
label: HTMLLabelElement,
|
|
430
|
+
select: HTMLSelectElement,
|
|
431
|
+
autocomplete: OuiComboboxAutoComplete,
|
|
432
|
+
multiple: boolean,
|
|
433
|
+
): [HTMLElement, HTMLElement[] | undefined, HTMLElement[]] {
|
|
396
434
|
const list = createElement(
|
|
397
435
|
'ul',
|
|
398
436
|
{
|
|
@@ -405,27 +443,79 @@ function getContent(
|
|
|
405
443
|
},
|
|
406
444
|
);
|
|
407
445
|
|
|
408
|
-
const
|
|
446
|
+
const groups = [...select.querySelectorAll('optgroup')];
|
|
447
|
+
const groupsLength = groups.length;
|
|
409
448
|
|
|
410
449
|
const selected: HTMLElement[] = [];
|
|
411
450
|
|
|
451
|
+
let headers: HTMLElement[] | undefined;
|
|
452
|
+
|
|
453
|
+
if (groupsLength > 0) {
|
|
454
|
+
headers = [];
|
|
455
|
+
|
|
456
|
+
let firstGroup: number | undefined;
|
|
457
|
+
|
|
458
|
+
for (let groupIndex = 0; groupIndex < groupsLength; groupIndex += 1) {
|
|
459
|
+
const group = groups[groupIndex];
|
|
460
|
+
const options = [...group.querySelectorAll('option')];
|
|
461
|
+
const optionsLength = options.length;
|
|
462
|
+
|
|
463
|
+
if (optionsLength === 0) {
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
firstGroup ??= groupIndex;
|
|
468
|
+
|
|
469
|
+
const header = createHeader(group);
|
|
470
|
+
|
|
471
|
+
headers.push(header);
|
|
472
|
+
list.append(header);
|
|
473
|
+
|
|
474
|
+
createOptions(options, autocomplete, list, selected, groupIndex === firstGroup, header.id);
|
|
475
|
+
}
|
|
476
|
+
} else {
|
|
477
|
+
createOptions([...select.options], autocomplete, list, selected, true);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return [list, headers, selected];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
function createOption(
|
|
484
|
+
index: number,
|
|
485
|
+
option: HTMLOptionElement,
|
|
486
|
+
autocomplete: OuiComboboxAutoComplete,
|
|
487
|
+
active: boolean,
|
|
488
|
+
group?: string,
|
|
489
|
+
): HTMLElement {
|
|
490
|
+
return createElement(
|
|
491
|
+
'li',
|
|
492
|
+
{
|
|
493
|
+
id: option.id,
|
|
494
|
+
role: 'option',
|
|
495
|
+
textContent: option.textContent,
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
group,
|
|
499
|
+
[ATTRIBUTE_OPTION]: '',
|
|
500
|
+
[ATTRIBUTE_OPTION_ACTIVE]: active && index === 0 ? '' : undefined,
|
|
501
|
+
value: option.value,
|
|
502
|
+
'aria-selected': !autocomplete.none && option.selected,
|
|
503
|
+
},
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function createOptions(
|
|
508
|
+
options: HTMLOptionElement[],
|
|
509
|
+
autocomplete: OuiComboboxAutoComplete,
|
|
510
|
+
list: HTMLElement,
|
|
511
|
+
selected: HTMLElement[],
|
|
512
|
+
active: boolean,
|
|
513
|
+
group?: string,
|
|
514
|
+
): void {
|
|
412
515
|
for (let index = 0; index < options.length; index += 1) {
|
|
413
516
|
const option = options[index];
|
|
414
517
|
|
|
415
|
-
const item =
|
|
416
|
-
'li',
|
|
417
|
-
{
|
|
418
|
-
id: option.id,
|
|
419
|
-
textContent: option.textContent,
|
|
420
|
-
},
|
|
421
|
-
{
|
|
422
|
-
[ATTRIBUTE_OPTION]: '',
|
|
423
|
-
[ATTRIBUTE_OPTION_ACTIVE]: index === 0 ? '' : undefined,
|
|
424
|
-
role: 'option',
|
|
425
|
-
value: option.value,
|
|
426
|
-
'aria-selected': !autocomplete.none && option.selected,
|
|
427
|
-
},
|
|
428
|
-
);
|
|
518
|
+
const item = createOption(index, option, autocomplete, active, group);
|
|
429
519
|
|
|
430
520
|
if (option.selected) {
|
|
431
521
|
selected.push(item);
|
|
@@ -433,23 +523,118 @@ function getContent(
|
|
|
433
523
|
|
|
434
524
|
list.append(item);
|
|
435
525
|
}
|
|
526
|
+
}
|
|
436
527
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
role: 'status',
|
|
442
|
-
textContent: 'No results found',
|
|
443
|
-
},
|
|
528
|
+
function createSelection(select: HTMLSelectElement): HTMLElement {
|
|
529
|
+
const selection = createElement(
|
|
530
|
+
'ul',
|
|
531
|
+
{},
|
|
444
532
|
{
|
|
445
|
-
[
|
|
446
|
-
'aria-live': 'polite',
|
|
533
|
+
[ATTRIBUTE_SELECTION]: '',
|
|
447
534
|
},
|
|
448
535
|
);
|
|
449
536
|
|
|
450
|
-
|
|
537
|
+
for (const option of select.selectedOptions) {
|
|
538
|
+
selection.append(getSelectionItem(option));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
return selection;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
function filterOptions(
|
|
545
|
+
state: OuiComboboxState,
|
|
546
|
+
options?: HTMLElement[],
|
|
547
|
+
clear?: boolean,
|
|
548
|
+
): HTMLElement | undefined {
|
|
549
|
+
if (clear || state.autocomplete.none) {
|
|
550
|
+
state.filter.value = '';
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const filter = state.filter.value.toLocaleLowerCase();
|
|
554
|
+
|
|
555
|
+
const groups = (state.headers?.reduce(
|
|
556
|
+
(record, header) => {
|
|
557
|
+
record[header.id] = 0;
|
|
558
|
+
|
|
559
|
+
return record;
|
|
560
|
+
},
|
|
561
|
+
{} as Record<string, number>,
|
|
562
|
+
) ?? {}) as Record<string, number>;
|
|
563
|
+
|
|
564
|
+
const all = options ?? getOptions(state)[0];
|
|
565
|
+
let {length} = all;
|
|
566
|
+
|
|
567
|
+
const visible: HTMLElement[] = [];
|
|
568
|
+
|
|
569
|
+
for (let index = 0; index < length; index += 1) {
|
|
570
|
+
const option = all[index];
|
|
451
571
|
|
|
452
|
-
|
|
572
|
+
option.hidden = option.textContent?.toLocaleLowerCase().includes(filter) === false;
|
|
573
|
+
|
|
574
|
+
if (!option.hidden) {
|
|
575
|
+
groups[getAttribute(option, 'group') ?? ''] += 1;
|
|
576
|
+
|
|
577
|
+
visible.push(option);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (state.headers != null) {
|
|
582
|
+
length = state.headers.length;
|
|
583
|
+
|
|
584
|
+
for (let index = 0; index < length; index += 1) {
|
|
585
|
+
const header = state.headers[index];
|
|
586
|
+
|
|
587
|
+
header.hidden = groups[header.id] === 0;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
state.empty.hidden = visible.length > 0;
|
|
592
|
+
|
|
593
|
+
if (state.empty.hidden) {
|
|
594
|
+
return state.active != null && visible.includes(state.active) ? state.active : visible[0];
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function generateIds(select: HTMLSelectElement, prefix: string): void {
|
|
599
|
+
const elements = [...select.querySelectorAll('optgroup'), ...select.options];
|
|
600
|
+
const {length} = elements;
|
|
601
|
+
|
|
602
|
+
for (let index = 0; index < length; index += 1) {
|
|
603
|
+
const element = elements[index];
|
|
604
|
+
|
|
605
|
+
if (element.id === '') {
|
|
606
|
+
element.id = `${prefix}${element instanceof HTMLOptGroupElement ? '_group_' : '_option_'}${index}`;
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function getAttributeValue<Value>(
|
|
612
|
+
element: HTMLElement,
|
|
613
|
+
attribute: string,
|
|
614
|
+
possible: Set<Value>,
|
|
615
|
+
defaultValue: Value,
|
|
616
|
+
): Value {
|
|
617
|
+
const value = getAttribute(element, attribute);
|
|
618
|
+
|
|
619
|
+
return possible.has(value as Value) ? (value as Value) : defaultValue;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
function getAutoComplete(element: OuiComboboxElement, select: boolean): OuiComboboxAutoComplete {
|
|
623
|
+
let value: OuiComboboxAutoCompleteType;
|
|
624
|
+
|
|
625
|
+
if (select) {
|
|
626
|
+
value = 'list';
|
|
627
|
+
} else {
|
|
628
|
+
value = getAttributeValue(element, 'autocomplete', AUTOCOMPLETES, 'none');
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
value,
|
|
633
|
+
both: value === 'both',
|
|
634
|
+
inline: value === 'inline',
|
|
635
|
+
list: value === 'list',
|
|
636
|
+
none: value === 'none',
|
|
637
|
+
};
|
|
453
638
|
}
|
|
454
639
|
|
|
455
640
|
function getFormValue(state: OuiComboboxState, values: string[]): string {
|
|
@@ -490,21 +675,6 @@ function getId(): string {
|
|
|
490
675
|
return `oui_combobox_${index}`;
|
|
491
676
|
}
|
|
492
677
|
|
|
493
|
-
function getInput(id: string, select: HTMLSelectElement): HTMLInputElement {
|
|
494
|
-
return createElement(
|
|
495
|
-
'input',
|
|
496
|
-
{
|
|
497
|
-
id,
|
|
498
|
-
value: select.selectedOptions[0]?.textContent ?? '',
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
[ATTRIBUTE_ANCHOR]: '',
|
|
502
|
-
autocomplete: 'off',
|
|
503
|
-
type: 'text',
|
|
504
|
-
},
|
|
505
|
-
);
|
|
506
|
-
}
|
|
507
|
-
|
|
508
678
|
function getOption(
|
|
509
679
|
state: OuiComboboxState,
|
|
510
680
|
value: unknown,
|
|
@@ -639,9 +809,16 @@ function getState(element: OuiComboboxElement, internals: ElementInternals): Oui
|
|
|
639
809
|
|
|
640
810
|
generateIds(select, id);
|
|
641
811
|
|
|
642
|
-
const anchor =
|
|
812
|
+
const [anchor, selection] = createAnchor(type, id, label, select, autocomplete, multiple);
|
|
643
813
|
|
|
644
|
-
const [content, selected, empty] =
|
|
814
|
+
const [content, filter, headers, selected, empty] = createContent(
|
|
815
|
+
type,
|
|
816
|
+
id,
|
|
817
|
+
label,
|
|
818
|
+
select,
|
|
819
|
+
autocomplete,
|
|
820
|
+
multiple,
|
|
821
|
+
);
|
|
645
822
|
|
|
646
823
|
const values =
|
|
647
824
|
selected.length === 0 ? [''] : selected.map(option => getAttribute(option, 'value') ?? '');
|
|
@@ -665,6 +842,7 @@ function getState(element: OuiComboboxElement, internals: ElementInternals): Oui
|
|
|
665
842
|
disabled,
|
|
666
843
|
empty,
|
|
667
844
|
floatable,
|
|
845
|
+
headers,
|
|
668
846
|
id,
|
|
669
847
|
internals,
|
|
670
848
|
label,
|
|
@@ -673,16 +851,21 @@ function getState(element: OuiComboboxElement, internals: ElementInternals): Oui
|
|
|
673
851
|
required,
|
|
674
852
|
select,
|
|
675
853
|
selected,
|
|
854
|
+
selection,
|
|
676
855
|
type,
|
|
677
856
|
values,
|
|
678
|
-
|
|
857
|
+
active: selected[0],
|
|
858
|
+
filter: {
|
|
859
|
+
element: filter,
|
|
860
|
+
value: '',
|
|
861
|
+
},
|
|
679
862
|
listeners: [],
|
|
680
863
|
name: element.name,
|
|
681
864
|
touched: false,
|
|
682
865
|
};
|
|
683
866
|
|
|
684
867
|
state.listeners.push(
|
|
685
|
-
on(
|
|
868
|
+
on(element, 'focusout', () => {
|
|
686
869
|
state.touched = true;
|
|
687
870
|
|
|
688
871
|
updateValidity(state);
|
|
@@ -697,12 +880,18 @@ function getState(element: OuiComboboxElement, internals: ElementInternals): Oui
|
|
|
697
880
|
});
|
|
698
881
|
|
|
699
882
|
if (floatable.open) {
|
|
883
|
+
state.filter.element?.focus();
|
|
884
|
+
|
|
700
885
|
updateWidth(state);
|
|
701
886
|
}
|
|
702
887
|
}),
|
|
703
888
|
);
|
|
704
889
|
|
|
705
890
|
setDisabled(element, state, disabled);
|
|
891
|
+
setLimit('max', element, state, getAttribute(element, 'max'));
|
|
892
|
+
setLimit('min', element, state, getAttribute(element, 'min'));
|
|
893
|
+
setReadonly(element, state, readonly);
|
|
894
|
+
setRequired(element, state, required);
|
|
706
895
|
|
|
707
896
|
return state;
|
|
708
897
|
}
|
|
@@ -711,11 +900,87 @@ function hasTextSelection(element: HTMLElement): element is HTMLInputElement {
|
|
|
711
900
|
return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
712
901
|
}
|
|
713
902
|
|
|
714
|
-
function
|
|
903
|
+
function onClick(event: PointerEvent): void {
|
|
904
|
+
const combobox = findAncestor(event, TAGNAME);
|
|
905
|
+
const state = states.get(combobox!);
|
|
906
|
+
|
|
907
|
+
if (combobox == null || state == null) {
|
|
908
|
+
return;
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const label = findAncestor(event, 'label');
|
|
912
|
+
|
|
913
|
+
if (label != null) {
|
|
914
|
+
if (label === state.label && combobox.type === 'select') {
|
|
915
|
+
state.anchor.focus();
|
|
916
|
+
|
|
917
|
+
state.floatable.show();
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
const selection = findAncestor(event, `[${ATTRIBUTE_SELECTION_ITEM}]`);
|
|
924
|
+
|
|
925
|
+
if (selection != null) {
|
|
926
|
+
const option = state.selected.find(option => option.id === getAttribute(selection, 'option'));
|
|
927
|
+
|
|
928
|
+
setValue('set', combobox, state, option);
|
|
929
|
+
|
|
930
|
+
return;
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
const anchor = findAncestor(event, SELECTOR_ANCHOR);
|
|
934
|
+
|
|
935
|
+
if (anchor != null) {
|
|
936
|
+
state.floatable.show();
|
|
937
|
+
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
function onContent(event: PointerEvent, content: HTMLElement): void {
|
|
943
|
+
const combobox = comboboxes.get(content);
|
|
944
|
+
const state = states.get(content);
|
|
945
|
+
|
|
946
|
+
if (combobox == null || state == null) {
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
const option = findAncestor(event, SELECTOR_OPTION);
|
|
951
|
+
|
|
952
|
+
if (option instanceof HTMLElement) {
|
|
953
|
+
setNext('set', combobox, state, getOptions(state)[0], option);
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
(state.filter.element ?? state.anchor).focus();
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function onEnter(combobox: OuiComboboxElement, state: OuiComboboxState): void {
|
|
960
|
+
if (hasTextSelection(state.anchor)) {
|
|
961
|
+
onSelection(combobox, state);
|
|
962
|
+
|
|
963
|
+
return;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (!state.floatable.open) {
|
|
967
|
+
state.floatable.show();
|
|
968
|
+
|
|
969
|
+
return;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
setNext('set', combobox, state, getOptions(state)[0], state.active);
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
function onEscape(state: OuiComboboxState): void {
|
|
976
|
+
state.floatable.hide();
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
function onInput(
|
|
715
980
|
event: KeyboardEvent,
|
|
716
981
|
handler: (event: KeyboardEvent, combobox: OuiComboboxElement, state: OuiComboboxState) => void,
|
|
717
982
|
): void {
|
|
718
|
-
if (event.ctrlKey || event.metaKey
|
|
983
|
+
if (event.ctrlKey || event.metaKey) {
|
|
719
984
|
return;
|
|
720
985
|
}
|
|
721
986
|
|
|
@@ -726,14 +991,14 @@ function onAnchor(
|
|
|
726
991
|
return;
|
|
727
992
|
}
|
|
728
993
|
|
|
729
|
-
const
|
|
994
|
+
const input = findAncestor(event, SELECTOR_ANCHOR) ?? findAncestor(event, SELECTOR_FILTER);
|
|
730
995
|
|
|
731
|
-
if (
|
|
996
|
+
if (input != null) {
|
|
732
997
|
handler(event, combobox, state);
|
|
733
998
|
}
|
|
734
999
|
}
|
|
735
1000
|
|
|
736
|
-
function
|
|
1001
|
+
function onInputKeydown(
|
|
737
1002
|
event: KeyboardEvent,
|
|
738
1003
|
combobox: OuiComboboxElement,
|
|
739
1004
|
state: OuiComboboxState,
|
|
@@ -743,7 +1008,6 @@ function onAnchorKeydown(
|
|
|
743
1008
|
let preventDefault = true;
|
|
744
1009
|
|
|
745
1010
|
switch (true) {
|
|
746
|
-
case event.key === ' ' && state.type === 'select':
|
|
747
1011
|
case event.key === 'Enter':
|
|
748
1012
|
onEnter(combobox, state);
|
|
749
1013
|
break;
|
|
@@ -752,10 +1016,6 @@ function onAnchorKeydown(
|
|
|
752
1016
|
onEscape(state);
|
|
753
1017
|
break;
|
|
754
1018
|
|
|
755
|
-
case isSelect && KEYS_ABSOLUTE.has(event.key):
|
|
756
|
-
onNavigation(event, combobox, state);
|
|
757
|
-
break;
|
|
758
|
-
|
|
759
1019
|
case !isSelect && KEYS_SELECTION.has(event.key): {
|
|
760
1020
|
preventDefault = false;
|
|
761
1021
|
|
|
@@ -787,143 +1047,46 @@ function onAnchorKeydown(
|
|
|
787
1047
|
}
|
|
788
1048
|
}
|
|
789
1049
|
|
|
790
|
-
function
|
|
1050
|
+
function onInputKeyup(
|
|
791
1051
|
event: KeyboardEvent,
|
|
792
1052
|
combobox: OuiComboboxElement,
|
|
793
1053
|
state: OuiComboboxState,
|
|
794
1054
|
): void {
|
|
1055
|
+
const filter = state.filter.element ?? state.anchor;
|
|
795
1056
|
const typed = event.key.length === 1 && EXPRESSION_TYPED.test(event.key);
|
|
796
1057
|
|
|
797
|
-
if (state.type === 'select') {
|
|
798
|
-
if (typed) {
|
|
799
|
-
event.preventDefault();
|
|
800
|
-
event.stopPropagation();
|
|
801
|
-
|
|
802
|
-
onScrollTo(event, combobox, state);
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
return;
|
|
806
|
-
}
|
|
807
|
-
|
|
808
|
-
let preventDefault = true;
|
|
809
|
-
|
|
810
|
-
if (typed) {
|
|
811
|
-
state.filter += event.key;
|
|
812
|
-
}
|
|
813
|
-
|
|
814
1058
|
if (
|
|
815
|
-
|
|
816
|
-
(KEYS_REMOVE.has(event.key) ||
|
|
1059
|
+
!(filter instanceof HTMLInputElement) ||
|
|
1060
|
+
!(typed || KEYS_REMOVE.has(event.key) || filter.value.length < state.filter.value.length)
|
|
817
1061
|
) {
|
|
818
|
-
state.filter = state.anchor.value;
|
|
819
|
-
|
|
820
|
-
filterOptions(state);
|
|
821
|
-
}
|
|
822
|
-
|
|
823
|
-
switch (true) {
|
|
824
|
-
case typed:
|
|
825
|
-
onTyped(combobox, state);
|
|
826
|
-
break;
|
|
827
|
-
|
|
828
|
-
case KEYS_REMOVE.has(event.key):
|
|
829
|
-
onRemove(combobox, state);
|
|
830
|
-
break;
|
|
831
|
-
|
|
832
|
-
default: {
|
|
833
|
-
preventDefault = false;
|
|
834
|
-
|
|
835
|
-
break;
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
if (preventDefault) {
|
|
840
|
-
event.preventDefault();
|
|
841
|
-
event.stopPropagation();
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
function onClick(event: PointerEvent): void {
|
|
846
|
-
const combobox = findAncestor(event, TAGNAME);
|
|
847
|
-
const state = states.get(combobox!);
|
|
848
|
-
|
|
849
|
-
if (combobox == null || state == null) {
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
const label = findAncestor(event, 'label');
|
|
854
|
-
|
|
855
|
-
if (label != null) {
|
|
856
|
-
if (label === state.label && combobox.type === 'select') {
|
|
857
|
-
state.anchor.focus();
|
|
858
|
-
|
|
859
|
-
state.floatable.show();
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
return;
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
const selection = findAncestor(event, `[${ATTRIBUTE_SELECTION_ITEM}]`);
|
|
866
|
-
|
|
867
|
-
if (selection != null) {
|
|
868
|
-
const option = state.selected.find(option => option.id === getAttribute(selection, 'option'));
|
|
869
|
-
|
|
870
|
-
setValue('set', combobox, state, option);
|
|
871
|
-
|
|
872
|
-
return;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
const anchor = findAncestor(event, SELECTOR_ANCHOR);
|
|
876
|
-
|
|
877
|
-
if (anchor != null) {
|
|
878
|
-
state.floatable.show();
|
|
879
|
-
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
function onContent(event: PointerEvent, content: HTMLElement): void {
|
|
885
|
-
const combobox = comboboxes.get(content);
|
|
886
|
-
const state = states.get(content);
|
|
887
|
-
|
|
888
|
-
if (combobox == null || state == null) {
|
|
889
1062
|
return;
|
|
890
1063
|
}
|
|
891
1064
|
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
if (option instanceof HTMLElement) {
|
|
895
|
-
setNext('set', combobox, state, getOptions(state)[0], option);
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
state.anchor.focus();
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
function onEnter(combobox: OuiComboboxElement, state: OuiComboboxState): void {
|
|
902
|
-
if (hasTextSelection(state.anchor)) {
|
|
903
|
-
onSelection(combobox, state);
|
|
1065
|
+
event.preventDefault();
|
|
1066
|
+
event.stopPropagation();
|
|
904
1067
|
|
|
905
|
-
|
|
1068
|
+
if (typed) {
|
|
1069
|
+
state.filter.value += event.key;
|
|
1070
|
+
} else {
|
|
1071
|
+
state.filter.value = filter.value;
|
|
906
1072
|
}
|
|
907
1073
|
|
|
908
|
-
|
|
909
|
-
|
|
1074
|
+
const [options] = getOptions(state);
|
|
1075
|
+
const next = filterOptions(state, options);
|
|
910
1076
|
|
|
911
|
-
|
|
1077
|
+
if (typed) {
|
|
1078
|
+
onTyped(combobox, state, options, next);
|
|
1079
|
+
} else {
|
|
1080
|
+
setNext('remove', combobox, state, options, next);
|
|
912
1081
|
}
|
|
913
|
-
|
|
914
|
-
setNext('set', combobox, state, getOptions(state)[0], state.active);
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
function onEscape(state: OuiComboboxState): void {
|
|
918
|
-
state.floatable.hide();
|
|
919
1082
|
}
|
|
920
1083
|
|
|
921
1084
|
function onKeydown(event: KeyboardEvent): void {
|
|
922
|
-
|
|
1085
|
+
onInput(event, onInputKeydown);
|
|
923
1086
|
}
|
|
924
1087
|
|
|
925
1088
|
function onKeyup(event: KeyboardEvent): void {
|
|
926
|
-
|
|
1089
|
+
onInput(event, onInputKeyup);
|
|
927
1090
|
}
|
|
928
1091
|
|
|
929
1092
|
function onNavigation(
|
|
@@ -985,34 +1148,8 @@ function onNavigation(
|
|
|
985
1148
|
setNext(state.autocomplete.none ? 'none' : 'navigation', combobox, state, all, next);
|
|
986
1149
|
}
|
|
987
1150
|
|
|
988
|
-
function onRemove(combobox: OuiComboboxElement, state: OuiComboboxState): void {
|
|
989
|
-
const [all] = getOptions(state);
|
|
990
|
-
|
|
991
|
-
const next = filterOptions(state, all);
|
|
992
|
-
|
|
993
|
-
setNext('remove', combobox, state, all, next);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
function onScrollTo(
|
|
997
|
-
event: KeyboardEvent,
|
|
998
|
-
combobox: OuiComboboxElement,
|
|
999
|
-
state: OuiComboboxState,
|
|
1000
|
-
): void {
|
|
1001
|
-
const [all] = getOptions(state);
|
|
1002
|
-
|
|
1003
|
-
const key = event.key.toLocaleLowerCase();
|
|
1004
|
-
|
|
1005
|
-
const next =
|
|
1006
|
-
all.find(option => option.textContent?.toLocaleLowerCase().startsWith(key) === true) ??
|
|
1007
|
-
all.find(option => option.textContent?.toLocaleLowerCase().includes(key) === true);
|
|
1008
|
-
|
|
1009
|
-
if (next != null) {
|
|
1010
|
-
setNext('navigation', combobox, state, all, next);
|
|
1011
|
-
}
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
1151
|
function onSelection(combobox: OuiComboboxElement, state: OuiComboboxState): void {
|
|
1015
|
-
state.filter = state.anchor instanceof HTMLInputElement ? state.anchor.value : '';
|
|
1152
|
+
state.filter.value = state.anchor instanceof HTMLInputElement ? state.anchor.value : '';
|
|
1016
1153
|
|
|
1017
1154
|
filterOptions(state);
|
|
1018
1155
|
|
|
@@ -1025,44 +1162,42 @@ function onTab(combobox: OuiComboboxElement, state: OuiComboboxState): void {
|
|
|
1025
1162
|
}
|
|
1026
1163
|
}
|
|
1027
1164
|
|
|
1028
|
-
function onTyped(
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1165
|
+
function onTyped(
|
|
1166
|
+
combobox: OuiComboboxElement,
|
|
1167
|
+
state: OuiComboboxState,
|
|
1168
|
+
options?: HTMLElement[],
|
|
1169
|
+
next?: HTMLElement,
|
|
1170
|
+
): void {
|
|
1171
|
+
const filter = state.filter.element ?? state.anchor;
|
|
1172
|
+
|
|
1173
|
+
if (!state.autocomplete.inline && filter instanceof HTMLInputElement && filter.value.length > 0) {
|
|
1034
1174
|
state.floatable.show();
|
|
1035
1175
|
}
|
|
1036
1176
|
|
|
1037
|
-
const
|
|
1177
|
+
const all = options ?? getOptions(state)[0];
|
|
1038
1178
|
|
|
1039
|
-
const
|
|
1179
|
+
const option = state.autocomplete.none
|
|
1180
|
+
? (state.active ?? all[0])
|
|
1181
|
+
: (next ?? filterOptions(state, options));
|
|
1040
1182
|
|
|
1041
|
-
setNext('typed', combobox, state,
|
|
1183
|
+
setNext('typed', combobox, state, all, option);
|
|
1042
1184
|
}
|
|
1043
1185
|
|
|
1044
1186
|
function setDisabled(combobox: OuiComboboxElement, state: OuiComboboxState, value: boolean): void {
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
setAria(state.anchor, 'disabled', value);
|
|
1048
|
-
setProperty(combobox, 'disabled', value);
|
|
1049
|
-
|
|
1050
|
-
if (state.anchor instanceof HTMLInputElement) {
|
|
1051
|
-
state.anchor.readOnly = value;
|
|
1052
|
-
}
|
|
1187
|
+
setProperties('disabled', 'disabled', combobox, state, value);
|
|
1053
1188
|
}
|
|
1054
1189
|
|
|
1055
1190
|
function setLimit(
|
|
1056
1191
|
type: 'max' | 'min',
|
|
1057
1192
|
combobox: OuiComboboxElement,
|
|
1058
1193
|
state: OuiComboboxState,
|
|
1059
|
-
value?:
|
|
1194
|
+
value?: unknown,
|
|
1060
1195
|
): void {
|
|
1061
1196
|
const updateLimit =
|
|
1062
1197
|
typeof value === 'number' && !Number.isNaN(value) ? value !== state[type] : value == null;
|
|
1063
1198
|
|
|
1064
1199
|
if (updateLimit) {
|
|
1065
|
-
state[type] = value;
|
|
1200
|
+
state[type] = value as number | undefined;
|
|
1066
1201
|
|
|
1067
1202
|
setAttribute(combobox, type, value);
|
|
1068
1203
|
|
|
@@ -1087,26 +1222,38 @@ function setNext(
|
|
|
1087
1222
|
|
|
1088
1223
|
next?.scrollIntoView(scrollOptions);
|
|
1089
1224
|
|
|
1225
|
+
// TODO: offset scroll to prevent options under headers
|
|
1226
|
+
|
|
1090
1227
|
setValue(update, combobox, state, next);
|
|
1091
1228
|
}
|
|
1092
1229
|
|
|
1093
|
-
function
|
|
1094
|
-
|
|
1230
|
+
function setProperties(
|
|
1231
|
+
key: 'disabled' | 'readonly' | 'required',
|
|
1232
|
+
property: 'disabled' | 'readOnly' | 'required',
|
|
1233
|
+
combobox: OuiComboboxElement,
|
|
1234
|
+
state: OuiComboboxState,
|
|
1235
|
+
value: boolean,
|
|
1236
|
+
): void {
|
|
1237
|
+
state[key] = value;
|
|
1238
|
+
|
|
1239
|
+
setAria(state.anchor, key, value);
|
|
1240
|
+
setProperty(combobox, key, value);
|
|
1095
1241
|
|
|
1096
|
-
|
|
1097
|
-
|
|
1242
|
+
if (state.filter.element != null) {
|
|
1243
|
+
state.filter.element[property] = value;
|
|
1244
|
+
}
|
|
1098
1245
|
|
|
1099
1246
|
if (state.anchor instanceof HTMLInputElement) {
|
|
1100
|
-
state.anchor
|
|
1247
|
+
state.anchor[property] = value;
|
|
1101
1248
|
}
|
|
1102
1249
|
}
|
|
1103
1250
|
|
|
1104
|
-
function
|
|
1105
|
-
state
|
|
1106
|
-
|
|
1107
|
-
setAria(state.anchor, 'required', value);
|
|
1108
|
-
setProperty(combobox, 'required', value);
|
|
1251
|
+
function setReadonly(combobox: OuiComboboxElement, state: OuiComboboxState, value: boolean): void {
|
|
1252
|
+
setProperties('readonly', 'readOnly', combobox, state, value);
|
|
1253
|
+
}
|
|
1109
1254
|
|
|
1255
|
+
function setRequired(combobox: OuiComboboxElement, state: OuiComboboxState, value: boolean): void {
|
|
1256
|
+
setProperties('required', 'required', combobox, state, value);
|
|
1110
1257
|
updateValidity(state);
|
|
1111
1258
|
}
|
|
1112
1259
|
|
|
@@ -1214,13 +1361,13 @@ function setValues(combobox: OuiComboboxElement, state: OuiComboboxState, value:
|
|
|
1214
1361
|
for (const option of state.selected) {
|
|
1215
1362
|
setAria(option, 'selected', false);
|
|
1216
1363
|
|
|
1217
|
-
state.
|
|
1364
|
+
state.selection?.querySelector(`[option="${option.id}"]`)?.remove();
|
|
1218
1365
|
}
|
|
1219
1366
|
|
|
1220
1367
|
for (const option of selected) {
|
|
1221
1368
|
setAria(option, 'selected', true);
|
|
1222
1369
|
|
|
1223
|
-
state.
|
|
1370
|
+
state.selection?.append(getSelectionItem(option));
|
|
1224
1371
|
}
|
|
1225
1372
|
|
|
1226
1373
|
state.selected = selected.slice();
|
|
@@ -1243,7 +1390,7 @@ function updateAutoComplete(
|
|
|
1243
1390
|
|
|
1244
1391
|
if (
|
|
1245
1392
|
update === 'typed' &&
|
|
1246
|
-
!content.toLocaleLowerCase().startsWith(state.filter.toLocaleLowerCase())
|
|
1393
|
+
!content.toLocaleLowerCase().startsWith(state.filter.value.toLocaleLowerCase())
|
|
1247
1394
|
) {
|
|
1248
1395
|
return;
|
|
1249
1396
|
}
|
|
@@ -1253,7 +1400,7 @@ function updateAutoComplete(
|
|
|
1253
1400
|
if (state.anchor instanceof HTMLInputElement) {
|
|
1254
1401
|
state.anchor.value = content;
|
|
1255
1402
|
|
|
1256
|
-
state.anchor.setSelectionRange(update === 'typed' ? state.filter.length : length, length);
|
|
1403
|
+
state.anchor.setSelectionRange(update === 'typed' ? state.filter.value.length : length, length);
|
|
1257
1404
|
} else {
|
|
1258
1405
|
state.anchor.textContent = content;
|
|
1259
1406
|
}
|
|
@@ -1294,9 +1441,9 @@ function updateSelect(
|
|
|
1294
1441
|
}
|
|
1295
1442
|
|
|
1296
1443
|
if (added === true) {
|
|
1297
|
-
state.
|
|
1444
|
+
state.selection?.append(getSelectionItem(next));
|
|
1298
1445
|
} else if (added === false) {
|
|
1299
|
-
state.
|
|
1446
|
+
state.selection?.querySelector(`[option="${next.id}"]`)?.remove();
|
|
1300
1447
|
}
|
|
1301
1448
|
}
|
|
1302
1449
|
|
|
@@ -1406,6 +1553,14 @@ const ATTRIBUTE_CONTENT = `${TAGNAME}-content`;
|
|
|
1406
1553
|
|
|
1407
1554
|
const ATTRIBUTE_EMPTY = `${TAGNAME}-empty`;
|
|
1408
1555
|
|
|
1556
|
+
const ATTRIBUTE_FILTER = `${TAGNAME}-filter`;
|
|
1557
|
+
|
|
1558
|
+
const ATTRIBUTE_FILTER_INPUT = `${ATTRIBUTE_FILTER}-input`;
|
|
1559
|
+
|
|
1560
|
+
const ATTRIBUTE_FILTER_LABEL = `${ATTRIBUTE_FILTER}-label`;
|
|
1561
|
+
|
|
1562
|
+
const ATTRIBUTE_HEADER = `${TAGNAME}-header`;
|
|
1563
|
+
|
|
1409
1564
|
const ATTRIBUTE_LIST = `${TAGNAME}-list`;
|
|
1410
1565
|
|
|
1411
1566
|
const ATTRIBUTE_OPTION = `${TAGNAME}-option`;
|
|
@@ -1428,8 +1583,6 @@ const INVALID_OVERFLOW = 'Please select fewer options (maximum is {max})';
|
|
|
1428
1583
|
|
|
1429
1584
|
const INVALID_UNDERFLOW = 'Please select more options (minimum is {min})';
|
|
1430
1585
|
|
|
1431
|
-
const KEYS_ABSOLUTE = new Set(['End', 'Home']);
|
|
1432
|
-
|
|
1433
1586
|
const KEYS_NAVIGATION = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
|
|
1434
1587
|
|
|
1435
1588
|
const KEYS_REMOVE = new Set(['Backspace', 'Delete']);
|
|
@@ -1444,6 +1597,8 @@ const PROPERTY_STYLE = '--oui-combobox-width';
|
|
|
1444
1597
|
|
|
1445
1598
|
const SELECTOR_ANCHOR = `[${ATTRIBUTE_ANCHOR}]`;
|
|
1446
1599
|
|
|
1600
|
+
const SELECTOR_FILTER = `[${ATTRIBUTE_FILTER_INPUT}]`;
|
|
1601
|
+
|
|
1447
1602
|
const SELECTOR_OPTION = `[${ATTRIBUTE_OPTION}]`;
|
|
1448
1603
|
|
|
1449
1604
|
const TYPES = new Set<OuiComboboxType>(['autocomplete', 'select']);
|