@oscarpalmer/oui 0.33.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/js/combobox/constants.combobox.d.mts +2 -0
  2. package/dist/js/combobox/constants.combobox.mjs +46 -0
  3. package/dist/js/combobox/data.combobox.d.mts +18 -0
  4. package/dist/js/combobox/data.combobox.mjs +2 -0
  5. package/dist/js/combobox/dom.combobox.d.mts +110 -0
  6. package/dist/js/combobox/dom.combobox.mjs +2 -0
  7. package/dist/js/combobox/event.combobox.d.mts +17 -0
  8. package/dist/js/combobox/event.combobox.mjs +2 -0
  9. package/dist/js/combobox/form.combobox.d.mts +6 -0
  10. package/dist/js/combobox/form.combobox.mjs +2 -0
  11. package/dist/js/combobox/i18n.combobox.d.mts +2 -0
  12. package/dist/js/combobox/i18n.combobox.mjs +2 -0
  13. package/dist/js/combobox/index.d.mts +2 -0
  14. package/dist/js/combobox/index.mjs +194 -0
  15. package/dist/js/combobox/misc.combobox.d.mts +14 -0
  16. package/dist/js/combobox/misc.combobox.mjs +2 -0
  17. package/dist/js/combobox/models.combobox.d.mts +2 -0
  18. package/dist/js/combobox/models.combobox.mjs +1 -0
  19. package/dist/js/combobox/state.combobox.d.mts +11 -0
  20. package/dist/js/combobox/state.combobox.mjs +2 -0
  21. package/dist/js/constants.combobox-DqNUGBLS.d.mts +243 -0
  22. package/dist/js/data.combobox-8RwBv5ey.mjs +1077 -0
  23. package/dist/js/dialog.mjs +3 -2
  24. package/dist/js/floatable/floatable.embedded.mjs +4 -4
  25. package/dist/js/focus-trap/focus-trap.embedded.mjs +3 -3
  26. package/dist/js/index.d.mts +14 -643
  27. package/dist/js/index.mjs +19 -4875
  28. package/dist/js/internal/draggable.mjs +6 -6
  29. package/dist/js/internal/navigable.mjs +3 -3
  30. package/dist/js/menu.d.mts +1 -1
  31. package/dist/js/menu.mjs +3 -2
  32. package/dist/js/movable/movable.embedded.mjs +3 -3
  33. package/dist/js/popover.mjs +1 -1
  34. package/dist/js/resizable/resizable.embedded.mjs +7 -7
  35. package/dist/js/sortable/sortable.embedded.mjs +7 -6
  36. package/dist/js/splitter.mjs +7 -13
  37. package/dist/js/toolbar.d.mts +1 -1
  38. package/dist/js/tooltip.mjs +3 -3
  39. package/package.json +4 -4
  40. package/src/js/combobox/constants.combobox.ts +62 -0
  41. package/src/js/combobox/data.combobox.ts +236 -0
  42. package/src/js/combobox/dom.combobox.ts +794 -0
  43. package/src/js/combobox/event.combobox.ts +454 -0
  44. package/src/js/combobox/form.combobox.ts +115 -0
  45. package/src/js/combobox/i18n.combobox.ts +179 -0
  46. package/src/js/combobox/index.ts +307 -0
  47. package/src/js/combobox/misc.combobox.ts +138 -0
  48. package/src/js/combobox/models.combobox.ts +94 -0
  49. package/src/js/combobox/state.combobox.ts +235 -0
  50. package/src/js/index.ts +1 -1
  51. package/src/js/internal/draggable.ts +3 -3
  52. package/src/js/movable/movable.embedded.ts +2 -2
  53. package/src/js/resizable/resizable.embedded.ts +3 -3
  54. package/src/js/sortable/sortable.embedded.ts +2 -2
  55. package/src/js/splitter.ts +2 -2
  56. package/dist/js/combobox.d.mts +0 -91
  57. package/dist/js/combobox.mjs +0 -797
  58. package/src/js/combobox.ts +0 -2410
@@ -0,0 +1,794 @@
1
+ import {setAria} from '@oscarpalmer/toretto/aria';
2
+ import {setAttribute} from '@oscarpalmer/toretto/attribute';
3
+ import {createElement} from '@oscarpalmer/toretto/create';
4
+ import {getData} from '@oscarpalmer/toretto/data';
5
+ import {
6
+ COMBOBOX_ATTRIBUTE_ANCHOR,
7
+ COMBOBOX_ATTRIBUTE_CONTENT,
8
+ COMBOBOX_ATTRIBUTE_FILTER,
9
+ COMBOBOX_ATTRIBUTE_FILTER_INPUT,
10
+ COMBOBOX_ATTRIBUTE_HEADER,
11
+ COMBOBOX_ATTRIBUTE_HIDDEN,
12
+ COMBOBOX_ATTRIBUTE_LIST,
13
+ COMBOBOX_ATTRIBUTE_MESSAGE,
14
+ COMBOBOX_ATTRIBUTE_OPTION,
15
+ COMBOBOX_ATTRIBUTE_OPTION_ACTIVE,
16
+ COMBOBOX_ATTRIBUTE_SELECTION,
17
+ COMBOBOX_ATTRIBUTE_SELECTION_ITEM,
18
+ comboboxScrollOptions,
19
+ } from './constants.combobox';
20
+ import {updateComboboxValidity} from './form.combobox';
21
+ import {getComboboxLocalization} from './i18n.combobox';
22
+ import type {
23
+ OuiComboboxAutoComplete,
24
+ OuiComboboxAutoCompleteType,
25
+ OuiComboboxGroup,
26
+ OuiComboboxPreview,
27
+ OuiComboboxSelection,
28
+ OuiComboboxSelectionItem,
29
+ OuiComboboxState,
30
+ OuiComboboxType,
31
+ } from './models.combobox';
32
+
33
+ // #region Types
34
+
35
+ type CreateAnchor = {
36
+ container?: HTMLElement;
37
+ element: HTMLElement;
38
+ selection?: OuiComboboxSelection;
39
+ };
40
+
41
+ type CreatAnchorParameters = {
42
+ autocomplete: OuiComboboxAutoComplete;
43
+ id: string;
44
+ label: HTMLLabelElement;
45
+ multiple: boolean;
46
+ select: HTMLSelectElement;
47
+ type: OuiComboboxType;
48
+ };
49
+
50
+ type CreateContent = {
51
+ element: HTMLElement;
52
+ filter?: CreateFilter;
53
+ list: CreateList;
54
+ message: HTMLElement;
55
+ };
56
+
57
+ type CreateContentParameters = {
58
+ autocomplete: OuiComboboxAutoComplete;
59
+ id: string;
60
+ label: HTMLLabelElement;
61
+ multiple: boolean;
62
+ select: HTMLSelectElement;
63
+ type: OuiComboboxType;
64
+ };
65
+
66
+ type CreateFilter = {
67
+ container: HTMLElement;
68
+ element: HTMLInputElement;
69
+ };
70
+
71
+ type CreateFilterParameters = {
72
+ autocomplete: OuiComboboxAutoCompleteType;
73
+ id: string;
74
+ label: HTMLLabelElement;
75
+ type: OuiComboboxType;
76
+ };
77
+
78
+ type CreateGroupParameters = {
79
+ autocomplete: OuiComboboxAutoComplete;
80
+ group: HTMLOptGroupElement;
81
+ groupOptions: HTMLOptionElement[];
82
+ selected: HTMLElement[];
83
+ };
84
+
85
+ type CreateInputParameters = {
86
+ autocomplete: OuiComboboxAutoCompleteType;
87
+ id: string;
88
+ label: HTMLLabelElement;
89
+ type: OuiComboboxType;
90
+ value: string;
91
+ };
92
+
93
+ type CreateList = {
94
+ element: HTMLElement;
95
+ groups?: OuiComboboxGroup[];
96
+ options: HTMLElement[];
97
+ selected: HTMLElement[];
98
+ };
99
+
100
+ type CreateListParameters = {
101
+ autocomplete: OuiComboboxAutoComplete;
102
+ id: string;
103
+ label: HTMLLabelElement;
104
+ multiple: boolean;
105
+ select: HTMLSelectElement;
106
+ };
107
+
108
+ type CreateOptionParameters = {
109
+ autocomplete: OuiComboboxAutoComplete;
110
+ group?: string;
111
+ index: number;
112
+ option: HTMLOptionElement;
113
+ };
114
+
115
+ type CreateOptionsParameters = {
116
+ autocomplete: OuiComboboxAutoComplete;
117
+ group?: string;
118
+ list: HTMLElement;
119
+ options: HTMLOptionElement[];
120
+ selected: HTMLElement[];
121
+ };
122
+
123
+ type CreateSelectionItemsParameters = {
124
+ container: HTMLElement;
125
+ items: OuiComboboxSelectionItem[];
126
+ options: HTMLElement[];
127
+ state?: OuiComboboxState;
128
+ };
129
+
130
+ // #endregion
131
+
132
+ // #region Functions
133
+
134
+ export function createComboboxAnchor(parameters: CreatAnchorParameters): CreateAnchor {
135
+ const {autocomplete, id, label, select, type} = parameters;
136
+
137
+ let container: HTMLElement | undefined;
138
+ let element: HTMLElement;
139
+ let selection: OuiComboboxSelection | undefined;
140
+
141
+ if (parameters.type === 'autocomplete') {
142
+ element = createComboboxInput({
143
+ id,
144
+ label,
145
+ type,
146
+ autocomplete: autocomplete.value,
147
+ value: select.selectedOptions[0]?.textContent ?? '',
148
+ });
149
+ } else {
150
+ ({container, element, selection} = createComboboxButton(parameters));
151
+ }
152
+
153
+ setAttribute(container ?? element, COMBOBOX_ATTRIBUTE_ANCHOR, '');
154
+
155
+ return {container, element, selection};
156
+ }
157
+
158
+ export function createComboboxButton(parameters: CreatAnchorParameters): CreateAnchor {
159
+ const {id, label, multiple, select} = parameters;
160
+
161
+ const element = createElement('button', {
162
+ aria: {
163
+ controls: `${id}_content`,
164
+ expanded: false,
165
+ haspopup: 'listbox',
166
+ labelledby: `${label.id} ${id}`,
167
+ },
168
+ property: {
169
+ textContent: multiple ? '' : (select.selectedOptions[0]?.textContent ?? ''),
170
+ },
171
+ });
172
+
173
+ if (!multiple) {
174
+ return {element};
175
+ }
176
+
177
+ const container = createElement('div');
178
+ const selection = createComboboxSelection(id, select);
179
+
180
+ container.append(selection.container, element);
181
+
182
+ return {container, element, selection};
183
+ }
184
+
185
+ export function createComboboxContent(parameters: CreateContentParameters): CreateContent {
186
+ const {autocomplete, id, label, multiple, select, type} = parameters;
187
+
188
+ const element = createElement('div', {
189
+ aria: {
190
+ labelledby: label.id,
191
+ },
192
+ attribute: {
193
+ [COMBOBOX_ATTRIBUTE_CONTENT]: '',
194
+ popover: '',
195
+ },
196
+ property: {
197
+ id: `${id}_content`,
198
+ tabIndex: -1,
199
+ },
200
+ });
201
+
202
+ const filter = createComboboxFilter({id, label, type, autocomplete: autocomplete.value});
203
+ const list = createComboboxList({autocomplete, id, label, multiple, select});
204
+ const message = createComboboxMessage();
205
+
206
+ if (filter != null) {
207
+ element.append(filter.container);
208
+ }
209
+
210
+ element.append(list.element, message);
211
+
212
+ return {element, filter, list, message};
213
+ }
214
+
215
+ export function createComboboxFilter(parameters: CreateFilterParameters): CreateFilter | undefined {
216
+ const {autocomplete, id, label, type} = parameters;
217
+
218
+ if (type === 'autocomplete') {
219
+ return;
220
+ }
221
+
222
+ const container = createElement('div', {
223
+ attribute: {
224
+ [COMBOBOX_ATTRIBUTE_FILTER]: '',
225
+ },
226
+ });
227
+
228
+ const element = createComboboxInput({autocomplete, id, label, type, value: ''});
229
+
230
+ setAttribute(element, COMBOBOX_ATTRIBUTE_FILTER_INPUT, '');
231
+
232
+ container.append(element);
233
+
234
+ return {container, element};
235
+ }
236
+
237
+ export function createComboboxGroup(parameters: CreateGroupParameters): OuiComboboxGroup {
238
+ const {autocomplete, group, groupOptions, selected} = parameters;
239
+
240
+ const element = createElement('li', {
241
+ aria: {
242
+ label: group.label,
243
+ },
244
+ property: {
245
+ role: 'group',
246
+ },
247
+ });
248
+
249
+ const list = createElement('ul', {
250
+ property: {
251
+ role: 'none',
252
+ },
253
+ });
254
+
255
+ const header = createComboboxHeader(group);
256
+
257
+ element.append(header, list);
258
+
259
+ return {
260
+ element,
261
+ header,
262
+ options: createComboboxOptions({
263
+ autocomplete,
264
+ list,
265
+ selected,
266
+ group: header.id,
267
+ options: groupOptions,
268
+ }),
269
+ };
270
+ }
271
+
272
+ export function createComboboxHeader(group: HTMLOptGroupElement): HTMLElement {
273
+ return createElement('div', {
274
+ aria: {
275
+ hidden: true,
276
+ },
277
+ attribute: {
278
+ [COMBOBOX_ATTRIBUTE_HEADER]: '',
279
+ },
280
+ property: {
281
+ id: group.id,
282
+ textContent: group.label,
283
+ },
284
+ });
285
+ }
286
+
287
+ export function createComboboxInput(parameters: CreateInputParameters): HTMLInputElement {
288
+ const {autocomplete, id, label, type, value} = parameters;
289
+
290
+ return createElement('input', {
291
+ aria: {
292
+ autocomplete: autocomplete,
293
+ controls: `${id}_list`,
294
+ expanded: type === 'select',
295
+ haspopup: 'listbox',
296
+ labelledby: label.id,
297
+ },
298
+ attribute: {
299
+ autocorrect: 'off',
300
+ autocomplete: 'off',
301
+ form: '',
302
+ spellcheck: 'false',
303
+ type: 'text',
304
+ },
305
+ property: {
306
+ id,
307
+ value,
308
+ role: 'combobox',
309
+ },
310
+ });
311
+ }
312
+
313
+ export function createComboboxList(parameters: CreateListParameters): CreateList {
314
+ const {autocomplete, id, label, multiple, select} = parameters;
315
+
316
+ const element = createElement('ul', {
317
+ aria: {
318
+ labelledby: label.id,
319
+ multiselectable: multiple,
320
+ },
321
+ attribute: {
322
+ [COMBOBOX_ATTRIBUTE_LIST]: '',
323
+ tabindex: -1,
324
+ },
325
+ property: {
326
+ id: `${id}_list`,
327
+ role: 'listbox',
328
+ },
329
+ });
330
+
331
+ const children = [...select.children];
332
+ const childrenLength = children.length;
333
+
334
+ const options: HTMLElement[] = [];
335
+ const selected: HTMLElement[] = [];
336
+
337
+ let groups: OuiComboboxGroup[] | undefined;
338
+
339
+ for (let childIndex = 0; childIndex < childrenLength; childIndex += 1) {
340
+ const child = children[childIndex];
341
+
342
+ if (child instanceof HTMLOptionElement) {
343
+ const option = createComboboxOption({
344
+ autocomplete,
345
+ index: childIndex,
346
+ option: child,
347
+ });
348
+
349
+ element.append(option);
350
+ options.push(option);
351
+
352
+ if (child.selected) {
353
+ selected.push(option);
354
+ }
355
+
356
+ continue;
357
+ }
358
+
359
+ if (!(child instanceof HTMLOptGroupElement)) {
360
+ continue;
361
+ }
362
+
363
+ const group = createComboboxGroup({
364
+ autocomplete,
365
+ selected,
366
+ group: child,
367
+ groupOptions: [...child.children] as HTMLOptionElement[],
368
+ });
369
+
370
+ groups ??= [];
371
+
372
+ element.append(group.element, ...group.options);
373
+ groups.push(group);
374
+ options.push(...group.options);
375
+ }
376
+
377
+ return {element, groups, options, selected};
378
+ }
379
+
380
+ export function createComboboxMessage(): HTMLElement {
381
+ return createElement('div', {
382
+ aria: {
383
+ live: 'polite',
384
+ },
385
+ attribute: {
386
+ [COMBOBOX_ATTRIBUTE_MESSAGE]: '',
387
+ },
388
+ property: {
389
+ hidden: true,
390
+ role: 'status',
391
+ },
392
+ });
393
+ }
394
+
395
+ export function createComboboxOption(parameters: CreateOptionParameters): HTMLElement {
396
+ const {autocomplete, group, option} = parameters;
397
+
398
+ return createElement('li', {
399
+ aria: {
400
+ selected: !autocomplete.none && option.selected,
401
+ },
402
+ attribute: {
403
+ [COMBOBOX_ATTRIBUTE_OPTION]: '',
404
+ },
405
+ data: {
406
+ group,
407
+ value: option.value,
408
+ },
409
+ property: {
410
+ id: option.id,
411
+ role: 'option',
412
+ textContent: option.textContent,
413
+ },
414
+ });
415
+ }
416
+
417
+ export function createComboboxOptions(parameters: CreateOptionsParameters): HTMLElement[] {
418
+ const {autocomplete, group, list, options, selected} = parameters;
419
+
420
+ const items: HTMLElement[] = [];
421
+
422
+ for (let index = 0; index < options.length; index += 1) {
423
+ const option = options[index];
424
+
425
+ const item = createComboboxOption({autocomplete, group, index, option});
426
+
427
+ items.push(item);
428
+
429
+ if (option.selected) {
430
+ selected.push(item);
431
+ }
432
+
433
+ list.append(item);
434
+ }
435
+
436
+ return items;
437
+ }
438
+
439
+ export function createComboboxSelection(
440
+ id: string,
441
+ select: HTMLSelectElement,
442
+ ): OuiComboboxSelection {
443
+ const container = createElement('ul', {
444
+ attribute: {
445
+ [COMBOBOX_ATTRIBUTE_SELECTION]: '',
446
+ },
447
+ property: {
448
+ id: `${id}_selection`,
449
+ },
450
+ });
451
+
452
+ const options = [...select.selectedOptions];
453
+
454
+ const items: OuiComboboxSelectionItem[] = [];
455
+
456
+ createComboboxSelectionItems({container, items, options});
457
+
458
+ const {button} = items[0] ?? {};
459
+
460
+ if (button != null) {
461
+ button.tabIndex = 0;
462
+ }
463
+
464
+ return {
465
+ items,
466
+ container: container,
467
+ };
468
+ }
469
+
470
+ export function createComboboxSelectionItem(
471
+ option: HTMLElement,
472
+ state?: OuiComboboxState,
473
+ ): OuiComboboxSelectionItem {
474
+ const element = createElement('li');
475
+
476
+ const localization =
477
+ state == null ? option.textContent : getComboboxLocalization('remove', state);
478
+
479
+ const button = createElement('button', {
480
+ aria: {
481
+ label:
482
+ typeof localization === 'function'
483
+ ? localization(option.textContent ?? '', getData(option, 'value', false) ?? '')
484
+ : localization,
485
+ },
486
+ attribute: {
487
+ [COMBOBOX_ATTRIBUTE_SELECTION_ITEM]: '',
488
+ tabindex: -1,
489
+ },
490
+ data: {
491
+ option: option.id,
492
+ },
493
+ });
494
+
495
+ const icon = createElement('span', {
496
+ aria: {
497
+ hidden: true,
498
+ },
499
+ property: {
500
+ innerHTML: '&times;',
501
+ },
502
+ });
503
+
504
+ const label = createElement('span', {
505
+ property: {
506
+ textContent: option.textContent,
507
+ },
508
+ });
509
+
510
+ button.append(icon, label);
511
+ element.append(button);
512
+
513
+ return {
514
+ button,
515
+ element,
516
+ };
517
+ }
518
+
519
+ export function createComboboxSelectionItems(parameters: CreateSelectionItemsParameters): void {
520
+ const {container, items, options, state} = parameters;
521
+
522
+ const {length} = options;
523
+
524
+ for (let index = 0; index < length; index += 1) {
525
+ const option = options[index];
526
+
527
+ const item = createComboboxSelectionItem(option, state);
528
+
529
+ container.append(item.element);
530
+ items.push(item);
531
+ }
532
+ }
533
+
534
+ export function setComboboxDisabled(state: OuiComboboxState, value: boolean): void {
535
+ state.filter.element.readOnly = state.readonly || value;
536
+
537
+ setProperties('disabled', state, value);
538
+ }
539
+
540
+ function setProperties(
541
+ key: 'disabled' | 'readonly' | 'required',
542
+ state: OuiComboboxState,
543
+ value: boolean,
544
+ ): void {
545
+ state[key] = value;
546
+
547
+ const elements = [state.host, state.anchor.element, state.filter.element];
548
+ const {length} = elements;
549
+
550
+ for (let index = 0; index < length; index += 1) {
551
+ const element = elements[index];
552
+
553
+ setAria(
554
+ element,
555
+ key,
556
+ key !== 'readonly' || !(element instanceof HTMLButtonElement) ? value : undefined,
557
+ );
558
+ }
559
+ }
560
+
561
+ export function setComboboxReadonly(state: OuiComboboxState, value: boolean): void {
562
+ state.filter.element.readOnly = state.disabled || value;
563
+
564
+ setProperties('readonly', state, value);
565
+ }
566
+
567
+ export function setComboboxRequired(state: OuiComboboxState, value: boolean): void {
568
+ setProperties('required', state, value);
569
+ updateComboboxValidity(state);
570
+ }
571
+
572
+ export function updateComboboxTabindices(state: OuiComboboxState, active?: HTMLElement): void {
573
+ if (active == null || state.selection == null) {
574
+ return;
575
+ }
576
+
577
+ const next = state.selection.items.find(
578
+ item => getData(item.button, 'option', false) === active.id,
579
+ );
580
+
581
+ if (next == null) {
582
+ return;
583
+ }
584
+
585
+ const {length} = state.selection.items;
586
+
587
+ for (let index = 0; index < length; index += 1) {
588
+ const item = state.selection.items[index];
589
+
590
+ item.button.tabIndex = item === next ? 0 : -1;
591
+ }
592
+
593
+ next.button.focus();
594
+ }
595
+
596
+ export function updateComboboxAnchor(state: OuiComboboxState): void {
597
+ if (!state.multiple) {
598
+ return;
599
+ }
600
+
601
+ const localization = getComboboxLocalization('selected', state);
602
+
603
+ setAria(
604
+ state.anchor.element,
605
+ 'label',
606
+ typeof localization === 'function'
607
+ ? localization(state.options.selected.length)
608
+ : localization.replace('{amount}', state.options.selected.length.toString()),
609
+ );
610
+ }
611
+
612
+ export function updateComboboxAutocomplete(
613
+ preview: OuiComboboxPreview,
614
+ state: OuiComboboxState,
615
+ active?: HTMLElement,
616
+ ): void {
617
+ if (state.multiple) {
618
+ return;
619
+ }
620
+
621
+ const content = active?.textContent ?? state.filter.element.value;
622
+
623
+ if (
624
+ preview === 'typed' &&
625
+ !content.toLocaleLowerCase().startsWith(state.filter.element.value.toLocaleLowerCase())
626
+ ) {
627
+ return;
628
+ }
629
+
630
+ if (state.anchor.element instanceof HTMLInputElement) {
631
+ const contentLength = content.length;
632
+ const filterLength = state.filter.element.value.length;
633
+
634
+ if (preview === 'set' || preview === 'typed') {
635
+ state.anchor.element.value = content;
636
+
637
+ state.anchor.element.setSelectionRange(
638
+ preview === 'typed' ? filterLength : contentLength,
639
+ contentLength,
640
+ );
641
+ }
642
+ } else {
643
+ state.anchor.element.textContent = content;
644
+ }
645
+ }
646
+
647
+ export function updateComboboxMessage(state: OuiComboboxState, visible: number): void {
648
+ setAttribute(state.message, COMBOBOX_ATTRIBUTE_HIDDEN, visible > 0 ? '' : undefined);
649
+
650
+ if (visible === 0) {
651
+ const localization = getComboboxLocalization('empty', state);
652
+
653
+ state.message.textContent = typeof localization === 'function' ? localization() : localization;
654
+ } else {
655
+ const localization = getComboboxLocalization('results', state);
656
+
657
+ state.message.textContent =
658
+ typeof localization === 'function'
659
+ ? localization(visible)
660
+ : localization.replace('{amount}', visible.toString());
661
+ }
662
+
663
+ state.message.hidden = false;
664
+ }
665
+
666
+ export function updateComboboxPreview(
667
+ preview: OuiComboboxPreview,
668
+ state: OuiComboboxState,
669
+ active?: HTMLElement,
670
+ ): void {
671
+ const {length} = state.options.all;
672
+
673
+ for (let index = 0; index < length; index += 1) {
674
+ const option = state.options.all[index];
675
+
676
+ setAttribute(option, COMBOBOX_ATTRIBUTE_OPTION_ACTIVE, option === active ? '' : undefined);
677
+ }
678
+
679
+ state.active = active;
680
+
681
+ setAria(state.filter.element, 'activedescendant', state.floatable.open ? active?.id : undefined);
682
+
683
+ if (state.floatable.open) {
684
+ active?.scrollIntoView(comboboxScrollOptions);
685
+ }
686
+
687
+ if (preview !== 'none') {
688
+ updateComboboxAutocomplete(preview, state, active);
689
+ }
690
+ }
691
+
692
+ export function updateComboboxSelection(
693
+ state: OuiComboboxState,
694
+ active: HTMLElement | undefined,
695
+ added: boolean,
696
+ selected?: HTMLElement[],
697
+ ): void {
698
+ if (state.selection == null) {
699
+ return;
700
+ }
701
+
702
+ let options = active == null ? [] : [active];
703
+
704
+ if (selected != null) {
705
+ options = selected;
706
+
707
+ const {length} = state.selection.items;
708
+
709
+ for (let index = 0; index < length; index += 1) {
710
+ state.selection.items[index].element.remove();
711
+ }
712
+
713
+ state.selection.items.splice(0);
714
+ }
715
+
716
+ if (added) {
717
+ createComboboxSelectionItems({
718
+ options,
719
+ state,
720
+ container: state.selection.container,
721
+ items: state.selection.items,
722
+ });
723
+
724
+ return;
725
+ }
726
+
727
+ let current = state.selection.items.findIndex(
728
+ item => getData(item.button, 'option', false) === active?.id,
729
+ );
730
+
731
+ if (current === -1) {
732
+ return;
733
+ }
734
+
735
+ const item = state.selection.items[current];
736
+
737
+ item.element.remove();
738
+
739
+ state.selection.items.splice(current, 1);
740
+
741
+ if (state.selection.items.length === 0) {
742
+ state.anchor.element.focus();
743
+
744
+ return;
745
+ }
746
+
747
+ const {length} = state.selection.items;
748
+
749
+ if (current < 0) {
750
+ current = 0;
751
+ } else if (current >= length) {
752
+ current = length - 1;
753
+ }
754
+
755
+ for (let index = 0; index < length; index += 1) {
756
+ const {button} = state.selection.items[index];
757
+
758
+ button.tabIndex = index === current ? 0 : -1;
759
+ }
760
+
761
+ state.selection.items[current].button.focus();
762
+ }
763
+
764
+ export function updateComboboxSelectionItems(state: OuiComboboxState): void {
765
+ if (state.selection == null) {
766
+ return;
767
+ }
768
+
769
+ const localization = getComboboxLocalization('remove', state);
770
+
771
+ const {length} = state.selection.items;
772
+
773
+ for (let index = 0; index < length; index += 1) {
774
+ const item = state.selection.items[index];
775
+
776
+ const option = state.options.selected.find(
777
+ option => option.id === getData(item.button, 'option', false),
778
+ );
779
+
780
+ if (option == null) {
781
+ continue;
782
+ }
783
+
784
+ setAria(
785
+ item.button,
786
+ 'label',
787
+ typeof localization === 'function'
788
+ ? localization(option?.textContent ?? '', getData(option, 'value', false) ?? '')
789
+ : localization.replace('{label}', option?.textContent ?? ''),
790
+ );
791
+ }
792
+ }
793
+
794
+ // #endregion