@oscarpalmer/oui 0.35.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.
- package/dist/css/combobox.css +1 -1
- package/dist/css/listbox.css +152 -0
- package/dist/js/combobox/constants.combobox.d.mts +2 -2
- package/dist/js/combobox/constants.combobox.mjs +40 -33
- package/dist/js/combobox/dom.combobox.d.mts +8 -91
- package/dist/js/combobox/dom.combobox.mjs +204 -2
- package/dist/js/combobox/event.combobox.d.mts +14 -8
- package/dist/js/combobox/event.combobox.mjs +2 -2
- package/dist/js/combobox/index.d.mts +1 -1
- package/dist/js/combobox/index.mjs +20 -18
- package/dist/js/combobox/misc.combobox.d.mts +2 -2
- package/dist/js/combobox/misc.combobox.mjs +2 -2
- package/dist/js/combobox/models.combobox.d.mts +44 -2
- package/dist/js/combobox/state.combobox.d.mts +9 -10
- package/dist/js/combobox/state.combobox.mjs +2 -2
- package/dist/js/constants.combobox-DmRAMC64.d.mts +26 -0
- package/dist/js/constants.listbox-DRsXUxd3.d.mts +14 -0
- package/dist/js/index-EfkUSals.d.mts +139 -0
- package/dist/js/index.d.mts +4 -2
- package/dist/js/index.mjs +4 -2
- package/dist/js/internal/attributable.mjs +4 -3
- package/dist/js/internal/listbox/listbox.constants.d.mts +11 -0
- package/dist/js/internal/listbox/listbox.constants.mjs +23 -0
- package/dist/js/internal/listbox/listbox.data.d.mts +20 -0
- package/dist/js/internal/listbox/listbox.data.mjs +2 -0
- package/dist/js/internal/listbox/listbox.dom.d.mts +66 -0
- package/dist/js/internal/listbox/listbox.dom.mjs +2 -0
- package/dist/js/internal/listbox/listbox.event.d.mts +5 -0
- package/dist/js/internal/listbox/listbox.event.mjs +63 -0
- package/dist/js/internal/listbox/listbox.form.d.mts +6 -0
- package/dist/js/internal/listbox/listbox.form.mjs +52 -0
- package/dist/js/internal/listbox/listbox.misc.d.mts +12 -0
- package/dist/js/internal/listbox/listbox.misc.mjs +2 -0
- package/dist/js/internal/listbox/listbox.model.d.mts +2 -0
- package/dist/js/internal/listbox/listbox.model.mjs +1 -0
- package/dist/js/internal/listbox/listbox.state.d.mts +7 -0
- package/dist/js/internal/listbox/listbox.state.mjs +40 -0
- package/dist/js/internal/misc.d.mts +6 -0
- package/dist/js/internal/misc.mjs +13 -0
- package/dist/js/l10n-Bn-7RcwR.d.mts +20 -0
- package/dist/js/l10n.d.mts +2 -20
- package/dist/js/listbox/constants.listbox.d.mts +2 -0
- package/dist/js/listbox/constants.listbox.mjs +40 -0
- package/dist/js/listbox/event.listbox.d.mts +8 -0
- package/dist/js/listbox/event.listbox.mjs +2 -0
- package/dist/js/listbox/index.d.mts +116 -0
- package/dist/js/listbox/index.mjs +189 -0
- package/dist/js/listbox/misc.listbox.d.mts +2 -0
- package/dist/js/listbox/misc.listbox.mjs +2 -0
- package/dist/js/listbox/state.listbox.d.mts +11 -0
- package/dist/js/listbox/state.listbox.mjs +2 -0
- package/dist/js/listbox.data-BWd0PCy7.mjs +424 -0
- package/dist/js/listbox.model-CRReGOmu.d.mts +125 -0
- package/dist/js/misc.listbox-BI-XsTOf.mjs +197 -0
- package/dist/js/misc.listbox-gnoJhrTr.d.mts +20 -0
- package/dist/js/sortable/sortable.embedded.mjs +4 -2
- package/dist/js/state.combobox-4xq0FPCF.mjs +365 -0
- package/package.json +7 -3
- package/src/css/combobox.scss +1 -1
- package/src/css/listbox.scss +149 -0
- package/src/js/combobox/constants.combobox.ts +46 -25
- package/src/js/combobox/dom.combobox.ts +36 -454
- package/src/js/combobox/event.combobox.ts +129 -218
- package/src/js/combobox/index.ts +37 -30
- package/src/js/combobox/misc.combobox.ts +30 -114
- package/src/js/combobox/models.combobox.ts +7 -47
- package/src/js/combobox/state.combobox.ts +126 -127
- package/src/js/index.ts +1 -0
- package/src/js/internal/listbox/listbox.constants.ts +25 -0
- package/src/js/{combobox/data.combobox.ts → internal/listbox/listbox.data.ts} +94 -43
- package/src/js/internal/listbox/listbox.dom.ts +445 -0
- package/src/js/internal/listbox/listbox.event.ts +129 -0
- package/src/js/{combobox/form.combobox.ts → internal/listbox/listbox.form.ts} +12 -13
- package/src/js/internal/listbox/listbox.misc.ts +135 -0
- package/src/js/internal/listbox/listbox.model.ts +169 -0
- package/src/js/internal/listbox/listbox.state.ts +82 -0
- package/src/js/internal/misc.ts +27 -0
- package/src/js/l10n.ts +8 -8
- package/src/js/listbox/constants.listbox.ts +61 -0
- package/src/js/listbox/event.listbox.ts +106 -0
- package/src/js/listbox/index.ts +302 -0
- package/src/js/listbox/misc.listbox.ts +70 -0
- package/src/js/listbox/state.listbox.ts +196 -0
- package/dist/js/combobox/data.combobox.d.mts +0 -18
- package/dist/js/combobox/data.combobox.mjs +0 -2
- package/dist/js/combobox/form.combobox.d.mts +0 -6
- package/dist/js/combobox/form.combobox.mjs +0 -2
- package/dist/js/constants.combobox-h_kyDegw.d.mts +0 -248
- package/dist/js/data.combobox-B8HMkDOh.mjs +0 -1030
|
@@ -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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
} from './
|
|
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:
|
|
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
|
|
29
|
-
|
|
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(
|
|
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
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
68
|
-
state
|
|
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
|
-
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
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
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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 =
|
|
192
|
+
const option = filterListboxOption(state, value);
|
|
151
193
|
|
|
152
194
|
if (option != null) {
|
|
153
|
-
|
|
195
|
+
setListboxValues(state, [value], emit);
|
|
154
196
|
}
|
|
155
197
|
}
|
|
156
198
|
|
|
157
|
-
export function
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
272
|
+
applyListboxSelection({
|
|
224
273
|
added,
|
|
225
274
|
option,
|
|
226
275
|
selected,
|
|
227
276
|
state,
|
|
228
|
-
|
|
277
|
+
scroll: false,
|
|
278
|
+
values:
|
|
279
|
+
state.autocomplete.none && !state.listbox && option != null
|
|
280
|
+
? [option.textContent]
|
|
281
|
+
: undefined,
|
|
229
282
|
});
|
|
230
283
|
|
|
231
|
-
|
|
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
|