@oscarpalmer/oui 0.34.0 → 0.36.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 (93) hide show
  1. package/dist/css/combobox.css +1 -1
  2. package/dist/css/listbox.css +152 -0
  3. package/dist/js/combobox/constants.combobox.d.mts +2 -2
  4. package/dist/js/combobox/constants.combobox.mjs +40 -33
  5. package/dist/js/combobox/dom.combobox.d.mts +8 -91
  6. package/dist/js/combobox/dom.combobox.mjs +204 -2
  7. package/dist/js/combobox/event.combobox.d.mts +14 -8
  8. package/dist/js/combobox/event.combobox.mjs +2 -2
  9. package/dist/js/combobox/index.d.mts +1 -1
  10. package/dist/js/combobox/index.mjs +21 -18
  11. package/dist/js/combobox/misc.combobox.d.mts +2 -14
  12. package/dist/js/combobox/misc.combobox.mjs +2 -2
  13. package/dist/js/combobox/models.combobox.d.mts +44 -2
  14. package/dist/js/combobox/state.combobox.d.mts +9 -8
  15. package/dist/js/combobox/state.combobox.mjs +2 -2
  16. package/dist/js/constants.combobox-DmRAMC64.d.mts +26 -0
  17. package/dist/js/constants.listbox-DRsXUxd3.d.mts +14 -0
  18. package/dist/js/index-EfkUSals.d.mts +139 -0
  19. package/dist/js/index.d.mts +4 -2
  20. package/dist/js/index.mjs +4 -2
  21. package/dist/js/internal/attributable.mjs +4 -3
  22. package/dist/js/internal/listbox/listbox.constants.d.mts +11 -0
  23. package/dist/js/internal/listbox/listbox.constants.mjs +23 -0
  24. package/dist/js/internal/listbox/listbox.data.d.mts +20 -0
  25. package/dist/js/internal/listbox/listbox.data.mjs +2 -0
  26. package/dist/js/internal/listbox/listbox.dom.d.mts +66 -0
  27. package/dist/js/internal/listbox/listbox.dom.mjs +2 -0
  28. package/dist/js/internal/listbox/listbox.event.d.mts +5 -0
  29. package/dist/js/internal/listbox/listbox.event.mjs +63 -0
  30. package/dist/js/internal/listbox/listbox.form.d.mts +6 -0
  31. package/dist/js/internal/listbox/listbox.form.mjs +52 -0
  32. package/dist/js/internal/listbox/listbox.misc.d.mts +12 -0
  33. package/dist/js/internal/listbox/listbox.misc.mjs +2 -0
  34. package/dist/js/internal/listbox/listbox.model.d.mts +2 -0
  35. package/dist/js/internal/listbox/listbox.model.mjs +1 -0
  36. package/dist/js/internal/listbox/listbox.state.d.mts +7 -0
  37. package/dist/js/internal/listbox/listbox.state.mjs +40 -0
  38. package/dist/js/internal/misc.d.mts +6 -0
  39. package/dist/js/internal/misc.mjs +13 -0
  40. package/dist/js/l10n-Bn-7RcwR.d.mts +20 -0
  41. package/dist/js/l10n.d.mts +2 -0
  42. package/dist/js/l10n.mjs +80 -0
  43. package/dist/js/listbox/constants.listbox.d.mts +2 -0
  44. package/dist/js/listbox/constants.listbox.mjs +40 -0
  45. package/dist/js/listbox/event.listbox.d.mts +8 -0
  46. package/dist/js/listbox/event.listbox.mjs +2 -0
  47. package/dist/js/listbox/index.d.mts +116 -0
  48. package/dist/js/listbox/index.mjs +189 -0
  49. package/dist/js/listbox/misc.listbox.d.mts +2 -0
  50. package/dist/js/listbox/misc.listbox.mjs +2 -0
  51. package/dist/js/listbox/state.listbox.d.mts +11 -0
  52. package/dist/js/listbox/state.listbox.mjs +2 -0
  53. package/dist/js/listbox.data-BWd0PCy7.mjs +424 -0
  54. package/dist/js/listbox.model-CRReGOmu.d.mts +125 -0
  55. package/dist/js/misc.listbox-BI-XsTOf.mjs +197 -0
  56. package/dist/js/misc.listbox-gnoJhrTr.d.mts +20 -0
  57. package/dist/js/sortable/sortable.embedded.mjs +4 -2
  58. package/dist/js/state.combobox-4xq0FPCF.mjs +365 -0
  59. package/package.json +7 -3
  60. package/src/css/combobox.scss +1 -1
  61. package/src/css/listbox.scss +149 -0
  62. package/src/js/combobox/constants.combobox.ts +46 -25
  63. package/src/js/combobox/dom.combobox.ts +65 -452
  64. package/src/js/combobox/event.combobox.ts +129 -218
  65. package/src/js/combobox/index.ts +43 -30
  66. package/src/js/combobox/misc.combobox.ts +60 -94
  67. package/src/js/combobox/models.combobox.ts +7 -47
  68. package/src/js/combobox/state.combobox.ts +136 -125
  69. package/src/js/index.ts +1 -0
  70. package/src/js/internal/listbox/listbox.constants.ts +25 -0
  71. package/src/js/{combobox/data.combobox.ts → internal/listbox/listbox.data.ts} +94 -43
  72. package/src/js/internal/listbox/listbox.dom.ts +445 -0
  73. package/src/js/internal/listbox/listbox.event.ts +129 -0
  74. package/src/js/{combobox/form.combobox.ts → internal/listbox/listbox.form.ts} +33 -33
  75. package/src/js/internal/listbox/listbox.misc.ts +135 -0
  76. package/src/js/internal/listbox/listbox.model.ts +169 -0
  77. package/src/js/internal/listbox/listbox.state.ts +82 -0
  78. package/src/js/internal/misc.ts +27 -0
  79. package/src/js/l10n.ts +192 -0
  80. package/src/js/listbox/constants.listbox.ts +61 -0
  81. package/src/js/listbox/event.listbox.ts +106 -0
  82. package/src/js/listbox/index.ts +302 -0
  83. package/src/js/listbox/misc.listbox.ts +70 -0
  84. package/src/js/listbox/state.listbox.ts +196 -0
  85. package/dist/js/combobox/data.combobox.d.mts +0 -18
  86. package/dist/js/combobox/data.combobox.mjs +0 -2
  87. package/dist/js/combobox/form.combobox.d.mts +0 -6
  88. package/dist/js/combobox/form.combobox.mjs +0 -2
  89. package/dist/js/combobox/i18n.combobox.d.mts +0 -2
  90. package/dist/js/combobox/i18n.combobox.mjs +0 -2
  91. package/dist/js/constants.combobox-DqNUGBLS.d.mts +0 -243
  92. package/dist/js/data.combobox-8RwBv5ey.mjs +0 -1077
  93. package/src/js/combobox/i18n.combobox.ts +0 -179
@@ -1,23 +1,22 @@
1
1
  import {getString} from '@oscarpalmer/atoms/string';
2
2
  import {setAria} from '@oscarpalmer/toretto/aria';
3
+ import {setAttribute} from '@oscarpalmer/toretto/attribute';
3
4
  import {getData} from '@oscarpalmer/toretto/data';
4
- import {
5
- updateComboboxMessage,
6
- updateComboboxPreview,
7
- updateComboboxSelection,
8
- } from './dom.combobox';
9
- import {onComboboxFilter} from './event.combobox';
10
- import {updateComboboxForm} from './form.combobox';
11
- import type {OuiComboboxState} from './models.combobox';
5
+ import {dispatch} from '@oscarpalmer/toretto/event';
6
+ import {updateListboxMessage, updateListboxPreview, updateListboxValueLabel} from './listbox.dom';
7
+ import {updateListboxForm} from './listbox.form';
8
+ import {setListboxFilter} from './listbox.misc';
9
+ import type {OuiListboxPreview, OuiListboxState} from './listbox.model';
12
10
 
13
11
  // #region Types
14
12
 
15
- type ApplySelectionParameters = {
13
+ type ApplySelectionParameters<State extends OuiListboxState = OuiListboxState> = {
16
14
  added: boolean;
17
15
  emit?: boolean;
18
16
  option?: HTMLElement;
17
+ scroll?: boolean;
19
18
  selected?: HTMLElement[];
20
- state: OuiComboboxState;
19
+ state: State;
21
20
  values?: string[];
22
21
  };
23
22
 
@@ -25,20 +24,26 @@ type ApplySelectionParameters = {
25
24
 
26
25
  // #region Functions
27
26
 
28
- export function applyComboboxSelection(parameters: ApplySelectionParameters): void {
29
- const {added, emit, option, selected, state} = parameters;
30
-
27
+ export function applyListboxSelection<State extends OuiListboxState>(
28
+ parameters: ApplySelectionParameters<State>,
29
+ ): void {
30
+ const {added, emit, option, scroll, selected, state} = parameters;
31
31
  const {length} = state.options.all;
32
32
 
33
33
  for (let index = 0; index < length; index += 1) {
34
34
  const option = state.options.all[index];
35
35
 
36
- setAria(option, 'selected', (selected ?? state.options.selected).includes(option));
36
+ setAria(
37
+ option,
38
+ 'selected',
39
+ (state.type === 'select' || !state.autocomplete.none) &&
40
+ (selected ?? state.options.selected).includes(option),
41
+ );
37
42
  }
38
43
 
39
44
  const values =
40
45
  parameters.values ??
41
- (state.autocomplete.none
46
+ (state.autocomplete.none && !state.listbox
42
47
  ? [state.filter.element.value]
43
48
  : (selected ?? state.options.selected).map(option => getData(option, 'value', false) ?? ''));
44
49
 
@@ -59,13 +64,44 @@ export function applyComboboxSelection(parameters: ApplySelectionParameters): vo
59
64
  return;
60
65
  }
61
66
 
62
- updateComboboxForm(state, values, emit);
63
- updateComboboxPreview('set', state, option);
64
- updateComboboxSelection(state, option, added, selected);
67
+ updateListboxForm(state, values, emit);
68
+ updateListboxMessage(state);
69
+ updateListboxPreview('set', state, option, scroll);
70
+ updateListboxValueLabel(state);
71
+
72
+ state.callbacks.data.onApply(state, option, values, added, selected, emit);
65
73
  }
66
74
 
67
- export function filterComboboxOption(
68
- state: OuiComboboxState,
75
+ export function filterListbox(type: InputEvent['inputType'], state: OuiListboxState): void {
76
+ if (state.disabled || state.readonly) {
77
+ return;
78
+ }
79
+
80
+ const update = state.filter.element.value !== state.filter.value;
81
+
82
+ const option = filterListboxOptions(state);
83
+
84
+ let preview: OuiListboxPreview | undefined;
85
+
86
+ if (type.includes('delete')) {
87
+ preview = 'none';
88
+ } else if (type.includes('insert')) {
89
+ preview = 'typed';
90
+ }
91
+
92
+ if (preview != null) {
93
+ state.callbacks.data.onFilter(preview, state, update, option);
94
+ }
95
+
96
+ if (update) {
97
+ state.filter.value = state.filter.element.value;
98
+
99
+ dispatch(state.host, 'input');
100
+ }
101
+ }
102
+
103
+ export function filterListboxOption(
104
+ state: OuiListboxState,
69
105
  value: unknown,
70
106
  textContent?: boolean,
71
107
  ): HTMLElement | undefined {
@@ -73,9 +109,10 @@ export function filterComboboxOption(
73
109
 
74
110
  const filter = getString(value).toLocaleLowerCase();
75
111
 
76
- filterComboboxOptions(state);
112
+ filterListboxOptions(state);
77
113
 
78
114
  const {length} = state.options.all;
115
+
79
116
  let match: HTMLElement | undefined;
80
117
 
81
118
  for (let index = 0; index < length; index += 1) {
@@ -95,7 +132,7 @@ export function filterComboboxOption(
95
132
  return match;
96
133
  }
97
134
 
98
- export function filterComboboxOptions(state: OuiComboboxState): HTMLElement | undefined {
135
+ export function filterListboxOptions(state: OuiListboxState): HTMLElement | undefined {
99
136
  function filter(options: HTMLElement[]): boolean {
100
137
  const {length} = options;
101
138
 
@@ -105,7 +142,8 @@ export function filterComboboxOptions(state: OuiComboboxState): HTMLElement | un
105
142
  const option = options[index];
106
143
 
107
144
  option.hidden =
108
- !state.autocomplete.none && !option.textContent.toLocaleLowerCase().includes(value);
145
+ (state.type === 'select' || !state.autocomplete.none) &&
146
+ !option.textContent.toLocaleLowerCase().includes(value);
109
147
 
110
148
  if (!option.hidden) {
111
149
  count += 1;
@@ -133,28 +171,36 @@ export function filterComboboxOptions(state: OuiComboboxState): HTMLElement | un
133
171
  }
134
172
  }
135
173
 
136
- updateComboboxMessage(state, visible.length);
174
+ setAttribute(state.list, state.attributes.empty, visible.length === 0 ? '' : undefined);
175
+
176
+ updateListboxMessage(state);
137
177
 
138
178
  return state.active != null && visible.includes(state.active) ? state.active : visible[0];
139
179
  }
140
180
 
141
- export function setComboboxValue(state: OuiComboboxState, value: unknown, emit?: boolean): void {
142
- if (state.autocomplete.none) {
143
- state.filter.element.value = getString(value);
144
-
145
- onComboboxFilter('insertText', state, true, emit);
181
+ export function setListboxValue<State extends OuiListboxState>(
182
+ state: State,
183
+ value: unknown,
184
+ emit?: boolean,
185
+ ): void {
186
+ if (state.autocomplete.none && !state.listbox) {
187
+ setListboxFilter(state, getString(value));
146
188
 
147
189
  return;
148
190
  }
149
191
 
150
- const option = filterComboboxOption(state, value);
192
+ const option = filterListboxOption(state, value);
151
193
 
152
194
  if (option != null) {
153
- setComboboxValues(state, [value], emit);
195
+ setListboxValues(state, [value], emit);
154
196
  }
155
197
  }
156
198
 
157
- export function setComboboxValues(state: OuiComboboxState, value: unknown, emit?: boolean): void {
199
+ export function setListboxValues<State extends OuiListboxState>(
200
+ state: State,
201
+ value: unknown[],
202
+ emit?: boolean,
203
+ ): void {
158
204
  if (!Array.isArray(value)) {
159
205
  return;
160
206
  }
@@ -167,8 +213,8 @@ export function setComboboxValues(state: OuiComboboxState, value: unknown, emit?
167
213
  values.push('');
168
214
  }
169
215
 
170
- if (state.autocomplete.none) {
171
- setComboboxValue(state, values[0], emit);
216
+ if (state.autocomplete.none && !state.listbox) {
217
+ setListboxValue(state, values[0], emit);
172
218
 
173
219
  return;
174
220
  }
@@ -185,19 +231,22 @@ export function setComboboxValues(state: OuiComboboxState, value: unknown, emit?
185
231
  }
186
232
  }
187
233
 
188
- applyComboboxSelection({
234
+ applyListboxSelection({
189
235
  emit,
190
236
  selected,
191
237
  state,
192
238
  added: true,
193
239
  option: selected[0] ?? state.options.all[0],
194
- values: state.autocomplete.none ? values : undefined,
240
+ values: state.autocomplete.none && !state.listbox ? values : undefined,
195
241
  });
196
242
  }
197
243
 
198
- export function toggleComboboxOption(state: OuiComboboxState, option?: HTMLElement): void {
244
+ export function toggleListboxOption<State extends OuiListboxState>(
245
+ state: State,
246
+ option?: HTMLElement,
247
+ ): void {
199
248
  if (option == null || (!state.multiple && state.options.selected.includes(option))) {
200
- state.floatable.hide();
249
+ state.callbacks.data.onToggle(state);
201
250
 
202
251
  return;
203
252
  }
@@ -220,17 +269,19 @@ export function toggleComboboxOption(state: OuiComboboxState, option?: HTMLEleme
220
269
  selected.splice(0, selected.length, option);
221
270
  }
222
271
 
223
- applyComboboxSelection({
272
+ applyListboxSelection({
224
273
  added,
225
274
  option,
226
275
  selected,
227
276
  state,
228
- values: state.autocomplete.none && option != null ? [option.textContent] : undefined,
277
+ scroll: false,
278
+ values:
279
+ state.autocomplete.none && !state.listbox && option != null
280
+ ? [option.textContent]
281
+ : undefined,
229
282
  });
230
283
 
231
- if (!state.multiple) {
232
- state.floatable.hide();
233
- }
284
+ state.callbacks.data.onToggle(state);
234
285
  }
235
286
 
236
287
  // #endregion
@@ -0,0 +1,445 @@
1
+ import {createElement, setAria, setAttribute} from '@oscarpalmer/toretto';
2
+ import {getLocalization, localize} from '../../l10n';
3
+ import {listboxScrollOptions} from './listbox.constants';
4
+ import {getListboxVisibleOptions} from './listbox.misc';
5
+ import type {
6
+ OuiListboxAttributes,
7
+ OuiListboxAutoComplete,
8
+ OuiListboxGroup,
9
+ OuiListboxLocalizations,
10
+ OuiListboxOptions,
11
+ OuiListboxPreview,
12
+ OuiListboxState,
13
+ OuiListboxType,
14
+ } from './listbox.model';
15
+
16
+ // #region Types
17
+
18
+ export type CreateListboxContent = {
19
+ element: HTMLDivElement;
20
+ filter?: CreateListboxFilter;
21
+ list: CreateListboxList;
22
+ message: HTMLDivElement;
23
+ value: HTMLSpanElement;
24
+ };
25
+
26
+ export type CreateListboxContentParameters = {
27
+ attributes: OuiListboxAttributes;
28
+ autocomplete: OuiListboxAutoComplete;
29
+ element: HTMLElement;
30
+ id: string;
31
+ label: HTMLLabelElement;
32
+ listbox: boolean;
33
+ multiple: boolean;
34
+ select: HTMLSelectElement;
35
+ type: OuiListboxType;
36
+ };
37
+
38
+ export type CreateListboxFilter = {
39
+ container: HTMLElement;
40
+ element: HTMLInputElement;
41
+ };
42
+
43
+ type CreateListboxGroupParameters = {
44
+ group: HTMLOptGroupElement;
45
+ groupOptions: HTMLOptionElement[];
46
+ original: CreateListboxContentParameters;
47
+ selected: HTMLElement[];
48
+ type: OuiListboxType;
49
+ };
50
+
51
+ export type CreateListboxList = {
52
+ element: HTMLUListElement;
53
+ groups?: OuiListboxGroup[];
54
+ options: OuiListboxOptions;
55
+ };
56
+
57
+ type CreateListboxOptionParameters = {
58
+ group?: string;
59
+ index: number;
60
+ option: HTMLOptionElement;
61
+ original: CreateListboxContentParameters;
62
+ type: OuiListboxType;
63
+ };
64
+
65
+ type CreateListboxOptionsParameters = {
66
+ group?: string;
67
+ list: HTMLElement;
68
+ options: HTMLOptionElement[];
69
+ original: CreateListboxContentParameters;
70
+ selected: HTMLElement[];
71
+ type: OuiListboxType;
72
+ };
73
+
74
+ // #endregion
75
+
76
+ // #region Functions
77
+
78
+ export function createListboxContent(
79
+ parameters: CreateListboxContentParameters,
80
+ ): CreateListboxContent {
81
+ const element = createElement('div', {
82
+ attribute: {
83
+ [parameters.attributes.content]: '',
84
+ },
85
+ });
86
+
87
+ const filter = createListboxFilter(parameters);
88
+ const list = createListboxList(parameters);
89
+ const message = createListboxMessage(parameters.attributes.message);
90
+ const value = createListboxValueLabel(parameters.id);
91
+
92
+ if (filter != null) {
93
+ element.append(filter.container);
94
+ }
95
+
96
+ element.append(list.element, message, value);
97
+
98
+ return {
99
+ element,
100
+ filter,
101
+ list,
102
+ message,
103
+ value,
104
+ };
105
+ }
106
+
107
+ export function createListboxFilter(
108
+ parameters: CreateListboxContentParameters,
109
+ ): CreateListboxFilter | undefined {
110
+ if (parameters.type === 'autocomplete') {
111
+ return;
112
+ }
113
+
114
+ const container = createElement('div', {
115
+ attribute: {
116
+ [parameters.attributes.filter]: '',
117
+ },
118
+ });
119
+
120
+ const element = createListboxInput(parameters, '');
121
+
122
+ setAttribute(element, parameters.attributes.input, '');
123
+
124
+ container.append(element);
125
+
126
+ return {container, element};
127
+ }
128
+
129
+ export function createListboxGroup(parameters: CreateListboxGroupParameters): OuiListboxGroup {
130
+ const {group, groupOptions, selected, type} = parameters;
131
+
132
+ const element = createElement('li', {
133
+ aria: {
134
+ label: group.label,
135
+ },
136
+ property: {
137
+ role: 'group',
138
+ },
139
+ });
140
+
141
+ const list = createElement('ul', {
142
+ property: {
143
+ role: 'none',
144
+ },
145
+ });
146
+
147
+ const header = createListboxHeader(parameters, group);
148
+
149
+ element.append(header, list);
150
+
151
+ return {
152
+ element,
153
+ header,
154
+ options: createListboxOptions({
155
+ list,
156
+ selected,
157
+ type,
158
+ group: header.id,
159
+ options: groupOptions,
160
+ original: parameters.original,
161
+ }),
162
+ };
163
+ }
164
+
165
+ export function createListboxHeader(
166
+ parameters: CreateListboxGroupParameters,
167
+ group: HTMLOptGroupElement,
168
+ ): HTMLElement {
169
+ return createElement('div', {
170
+ aria: {
171
+ hidden: true,
172
+ },
173
+ attribute: {
174
+ [parameters.original.attributes.header]: '',
175
+ },
176
+ property: {
177
+ id: group.id,
178
+ textContent: group.label,
179
+ },
180
+ });
181
+ }
182
+
183
+ export function createListboxInput(
184
+ parameters: CreateListboxContentParameters,
185
+ value: string,
186
+ ): HTMLInputElement {
187
+ const {autocomplete, id, label, listbox, type} = parameters;
188
+
189
+ const isAutocomplete = type === 'autocomplete';
190
+
191
+ const input = createElement('input', {
192
+ aria: {
193
+ autocomplete: autocomplete.value,
194
+ controls: `${id}_list`,
195
+ haspopup: isAutocomplete ? 'listbox' : undefined,
196
+ labelledby: label.id,
197
+ },
198
+ attribute: {
199
+ autocorrect: 'off',
200
+ autocomplete: 'off',
201
+ form: '',
202
+ spellcheck: 'false',
203
+ type: 'text',
204
+ },
205
+ property: {
206
+ id,
207
+ value,
208
+ role: isAutocomplete || listbox ? 'combobox' : 'searchbox',
209
+ },
210
+ });
211
+
212
+ if (isAutocomplete || listbox) {
213
+ setAria(input, 'expanded', listbox);
214
+ }
215
+
216
+ return input;
217
+ }
218
+
219
+ export function createListboxList(parameters: CreateListboxContentParameters): CreateListboxList {
220
+ const {attributes, id, label, multiple, select, type} = parameters;
221
+
222
+ const element = createElement('ul', {
223
+ aria: {
224
+ labelledby: label.id,
225
+ multiselectable: multiple,
226
+ },
227
+ attribute: {
228
+ [attributes.list]: '',
229
+ tabindex: -1,
230
+ },
231
+ property: {
232
+ id: `${id}_list`,
233
+ role: 'listbox',
234
+ },
235
+ });
236
+
237
+ const children = [...select.children];
238
+ const childrenLength = children.length;
239
+
240
+ const options: HTMLElement[] = [];
241
+ const selected: HTMLElement[] = [];
242
+
243
+ let groups: OuiListboxGroup[] | undefined;
244
+
245
+ for (let childIndex = 0; childIndex < childrenLength; childIndex += 1) {
246
+ const child = children[childIndex];
247
+
248
+ if (child instanceof HTMLOptionElement) {
249
+ const option = createListboxOption({
250
+ type,
251
+ option: child,
252
+ original: parameters,
253
+ index: childIndex,
254
+ });
255
+
256
+ element.append(option);
257
+ options.push(option);
258
+
259
+ if (child.selected) {
260
+ selected.push(option);
261
+ }
262
+
263
+ continue;
264
+ }
265
+
266
+ if (!(child instanceof HTMLOptGroupElement)) {
267
+ continue;
268
+ }
269
+
270
+ const group = createListboxGroup({
271
+ selected,
272
+ type,
273
+ group: child,
274
+ groupOptions: [...child.children] as HTMLOptionElement[],
275
+ original: parameters,
276
+ });
277
+
278
+ groups ??= [];
279
+
280
+ element.append(group.element);
281
+ groups.push(group);
282
+ options.push(...group.options);
283
+ }
284
+
285
+ return {
286
+ element,
287
+ groups,
288
+ options: {
289
+ selected,
290
+ all: options,
291
+ },
292
+ };
293
+ }
294
+
295
+ export function createListboxMessage(attribute: string): HTMLDivElement {
296
+ return createElement('div', {
297
+ aria: {
298
+ live: 'polite',
299
+ },
300
+ attribute: {
301
+ [attribute]: '',
302
+ },
303
+ property: {
304
+ hidden: true,
305
+ role: 'status',
306
+ },
307
+ });
308
+ }
309
+
310
+ export function createListboxOption(parameters: CreateListboxOptionParameters): HTMLElement {
311
+ const {group, option, original} = parameters;
312
+
313
+ return createElement('li', {
314
+ aria: {
315
+ selected: (parameters.type === 'select' || !original.autocomplete.none) && option.selected,
316
+ },
317
+ attribute: {
318
+ [original.attributes.option]: '',
319
+ },
320
+ data: {
321
+ group,
322
+ value: option.value,
323
+ },
324
+ property: {
325
+ id: option.id,
326
+ role: 'option',
327
+ textContent: option.textContent,
328
+ },
329
+ });
330
+ }
331
+
332
+ export function createListboxOptions(parameters: CreateListboxOptionsParameters): HTMLElement[] {
333
+ const {group, list, options, original, selected, type} = parameters;
334
+
335
+ const items: HTMLElement[] = [];
336
+
337
+ for (let index = 0; index < options.length; index += 1) {
338
+ const option = options[index];
339
+
340
+ const item = createListboxOption({group, index, option, original, type});
341
+
342
+ items.push(item);
343
+
344
+ if (option.selected) {
345
+ selected.push(item);
346
+ }
347
+
348
+ list.append(item);
349
+ }
350
+
351
+ return items;
352
+ }
353
+
354
+ export function createListboxValueLabel(id: string): HTMLSpanElement {
355
+ return createElement('span', {
356
+ property: {
357
+ hidden: true,
358
+ id: `${id}_value`,
359
+ },
360
+ });
361
+ }
362
+
363
+ export function updateListboxMessage(state: OuiListboxState): void {
364
+ const {attributes, host, localizations, message} = state;
365
+
366
+ const visible = getListboxVisibleOptions(state).length;
367
+
368
+ setAttribute(message, attributes.hidden, visible > 0 ? '' : undefined);
369
+
370
+ let data: Record<string, unknown> = {};
371
+ let parameters: unknown[] = [];
372
+
373
+ let key: keyof OuiListboxLocalizations | undefined;
374
+
375
+ if (visible === 0) {
376
+ key = 'empty';
377
+ } else {
378
+ data = {amount: visible};
379
+ key = 'results';
380
+ parameters = [visible];
381
+ }
382
+
383
+ const localization = getLocalization(localizations.global, host, key, localizations.component);
384
+
385
+ message.textContent = localize(localization, data, parameters);
386
+
387
+ message.hidden = false;
388
+ }
389
+
390
+ export function updateListboxPreview<State extends OuiListboxState>(
391
+ preview: OuiListboxPreview,
392
+ state: State,
393
+ active?: HTMLElement,
394
+ scroll?: boolean,
395
+ ): void {
396
+ const {length} = state.options.all;
397
+
398
+ for (let index = 0; index < length; index += 1) {
399
+ const option = state.options.all[index];
400
+
401
+ setAttribute(option, state.attributes.active, option === active ? '' : undefined);
402
+ }
403
+
404
+ state.active = active;
405
+
406
+ setAria(
407
+ state.filter.element,
408
+ 'activedescendant',
409
+ state.content.checkVisibility() ? active?.id : undefined,
410
+ );
411
+
412
+ if (scroll ?? true) {
413
+ active?.scrollIntoView(listboxScrollOptions);
414
+ }
415
+
416
+ if (preview !== 'none') {
417
+ state.callbacks.data.onPreview(preview, state, active);
418
+ }
419
+ }
420
+
421
+ export function updateListboxValueLabel<State extends OuiListboxState>(state: State): void {
422
+ let label: string;
423
+
424
+ if (state.multiple) {
425
+ const localization = getLocalization(
426
+ state.localizations.global,
427
+ state.host,
428
+ 'selected',
429
+ state.localizations.component,
430
+ );
431
+
432
+ const {length} = state.options.selected;
433
+
434
+ label = localize(localization, {amount: length}, [length]);
435
+ } else {
436
+ label =
437
+ state.autocomplete.none && !state.listbox
438
+ ? state.filter.element.value
439
+ : (state.options.selected[0]?.textContent ?? '');
440
+ }
441
+
442
+ state.values.label.textContent = label;
443
+ }
444
+
445
+ // #endregion