@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.
- 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 +21 -18
- package/dist/js/combobox/misc.combobox.d.mts +2 -14
- 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 -8
- 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 -0
- package/dist/js/l10n.mjs +80 -0
- 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 +65 -452
- package/src/js/combobox/event.combobox.ts +129 -218
- package/src/js/combobox/index.ts +43 -30
- package/src/js/combobox/misc.combobox.ts +60 -94
- package/src/js/combobox/models.combobox.ts +7 -47
- package/src/js/combobox/state.combobox.ts +136 -125
- 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} +33 -33
- 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 +192 -0
- 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/combobox/i18n.combobox.d.mts +0 -2
- package/dist/js/combobox/i18n.combobox.mjs +0 -2
- package/dist/js/constants.combobox-DqNUGBLS.d.mts +0 -243
- package/dist/js/data.combobox-8RwBv5ey.mjs +0 -1077
- package/src/js/combobox/i18n.combobox.ts +0 -179
|
@@ -1,1077 +0,0 @@
|
|
|
1
|
-
import { createEmbeddedFloatable } from "./floatable/floatable.embedded.mjs";
|
|
2
|
-
import { COMBOBOX_ATTRIBUTE_ANCHOR, COMBOBOX_ATTRIBUTE_CONTENT, COMBOBOX_ATTRIBUTE_FILTER, COMBOBOX_ATTRIBUTE_FILTER_INPUT, COMBOBOX_ATTRIBUTE_HEADER, COMBOBOX_ATTRIBUTE_HIDDEN, COMBOBOX_ATTRIBUTE_LIST, COMBOBOX_ATTRIBUTE_MESSAGE, COMBOBOX_ATTRIBUTE_OPTION, COMBOBOX_ATTRIBUTE_OPTION_ACTIVE, COMBOBOX_ATTRIBUTE_SELECTION, COMBOBOX_ATTRIBUTE_SELECTION_ITEM, COMBOBOX_AUTOCOMPLETES, COMBOBOX_KEYS_ACTIVATION, COMBOBOX_KEYS_NAVIGATION, COMBOBOX_KEYS_SELECTION, COMBOBOX_MESSAGE_LABEL, COMBOBOX_MESSAGE_SELECT, COMBOBOX_SELECTOR_ANCHOR, COMBOBOX_SELECTOR_OPTION, COMBOBOX_SELECTOR_SELECTION_ITEM, COMBOBOX_TAGNAME, COMBOBOX_TYPES, comboboxScrollOptions } from "./combobox/constants.combobox.mjs";
|
|
3
|
-
import { getAria, setAria } from "@oscarpalmer/toretto/aria";
|
|
4
|
-
import { dispatch, on } from "@oscarpalmer/toretto/event";
|
|
5
|
-
import { getAttribute, setAttribute } from "@oscarpalmer/toretto/attribute";
|
|
6
|
-
import { clamp } from "@oscarpalmer/atoms/number";
|
|
7
|
-
import { getString } from "@oscarpalmer/atoms/string";
|
|
8
|
-
import { getData } from "@oscarpalmer/toretto/data";
|
|
9
|
-
import { createElement } from "@oscarpalmer/toretto/create";
|
|
10
|
-
import { dispatch as dispatch$1, findAncestor, getData as getData$1 } from "@oscarpalmer/toretto";
|
|
11
|
-
//#region src/js/combobox/state.combobox.ts
|
|
12
|
-
function attachCombobox(state) {
|
|
13
|
-
const { anchor, content, filter, label, listeners, select } = state;
|
|
14
|
-
label.element.htmlFor = filter.element.id;
|
|
15
|
-
label.element.id = `${state.id}_label`;
|
|
16
|
-
select.hidden = true;
|
|
17
|
-
select.insertAdjacentElement("afterend", anchor.container ?? anchor.element);
|
|
18
|
-
(anchor.container ?? anchor.element).insertAdjacentElement("afterend", content);
|
|
19
|
-
select.remove();
|
|
20
|
-
state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
|
|
21
|
-
position: "below-start",
|
|
22
|
-
reusable: false
|
|
23
|
-
});
|
|
24
|
-
state.floatable.update();
|
|
25
|
-
listeners.push(on(state.host, "focusout", () => {
|
|
26
|
-
setTimeout(() => {
|
|
27
|
-
onComboboxFocusout(state);
|
|
28
|
-
});
|
|
29
|
-
}), on(content, "click", (event) => {
|
|
30
|
-
onComboboxClickContent(event, content);
|
|
31
|
-
}), on(content, "toggle", () => {
|
|
32
|
-
setAria(anchor.element, "expanded", state.floatable.open);
|
|
33
|
-
setAria(filter.element, "activedescendant", state.floatable.open ? state.active?.id : void 0);
|
|
34
|
-
if (state.floatable.open) filter.element.focus();
|
|
35
|
-
}));
|
|
36
|
-
comboboxStates.map.set(state.host, state);
|
|
37
|
-
comboboxStates.map.set(state.content, state);
|
|
38
|
-
comboboxStates.set.add(state);
|
|
39
|
-
}
|
|
40
|
-
function createComboboxState(element, internals) {
|
|
41
|
-
const select = element.querySelector("select");
|
|
42
|
-
if (select == null || select.id === "") throw new Error(COMBOBOX_MESSAGE_SELECT);
|
|
43
|
-
const label = element.querySelector(`label[for="${select.id}"]`);
|
|
44
|
-
if (label == null) throw new Error(COMBOBOX_MESSAGE_LABEL);
|
|
45
|
-
element.id = select.id;
|
|
46
|
-
setAttribute(element, "name", select.name === "" ? select.id : select.name);
|
|
47
|
-
const disabled = getComboboxBooleanAria(element, "disabled") || select.disabled;
|
|
48
|
-
const multiple = element.multiple || select.multiple;
|
|
49
|
-
const readonly = getComboboxBooleanAria(element, "readonly");
|
|
50
|
-
const required = getComboboxBooleanAria(element, "required") || select.required;
|
|
51
|
-
const id = getComboboxId();
|
|
52
|
-
const type = multiple ? "select" : getComboboxAttributeValue(element, "type", COMBOBOX_TYPES, "select");
|
|
53
|
-
const autocomplete = getComboboxAutoComplete(element, type === "select");
|
|
54
|
-
const labelId = label.id;
|
|
55
|
-
label.htmlFor = id;
|
|
56
|
-
label.id = `${id}_label`;
|
|
57
|
-
generateComboboxIds(id, select);
|
|
58
|
-
const anchor = createComboboxAnchor({
|
|
59
|
-
autocomplete,
|
|
60
|
-
id,
|
|
61
|
-
label,
|
|
62
|
-
multiple,
|
|
63
|
-
select,
|
|
64
|
-
type
|
|
65
|
-
});
|
|
66
|
-
const content = createComboboxContent({
|
|
67
|
-
autocomplete,
|
|
68
|
-
id,
|
|
69
|
-
label,
|
|
70
|
-
multiple,
|
|
71
|
-
select,
|
|
72
|
-
type
|
|
73
|
-
});
|
|
74
|
-
const values = content.list.selected.length === 0 ? [""] : content.list.selected.map((option) => getData(option, "value", false) ?? "");
|
|
75
|
-
const state = {
|
|
76
|
-
autocomplete,
|
|
77
|
-
disabled,
|
|
78
|
-
id,
|
|
79
|
-
internals,
|
|
80
|
-
multiple,
|
|
81
|
-
readonly,
|
|
82
|
-
required,
|
|
83
|
-
select,
|
|
84
|
-
type,
|
|
85
|
-
anchor: {
|
|
86
|
-
container: anchor.container,
|
|
87
|
-
element: anchor.element
|
|
88
|
-
},
|
|
89
|
-
active: content.list.selected[0],
|
|
90
|
-
content: content.element,
|
|
91
|
-
filter: {
|
|
92
|
-
element: content.filter?.element ?? anchor.element,
|
|
93
|
-
value: ""
|
|
94
|
-
},
|
|
95
|
-
floatable: void 0,
|
|
96
|
-
groups: content.list.groups,
|
|
97
|
-
host: element,
|
|
98
|
-
label: {
|
|
99
|
-
element: label,
|
|
100
|
-
id: labelId
|
|
101
|
-
},
|
|
102
|
-
listeners: [],
|
|
103
|
-
localizations: {},
|
|
104
|
-
message: content.message,
|
|
105
|
-
name: element.name,
|
|
106
|
-
options: {
|
|
107
|
-
all: content.list.options,
|
|
108
|
-
selected: content.list.selected
|
|
109
|
-
},
|
|
110
|
-
selection: anchor.selection,
|
|
111
|
-
values: {
|
|
112
|
-
current: values.slice(),
|
|
113
|
-
initial: values.slice()
|
|
114
|
-
}
|
|
115
|
-
};
|
|
116
|
-
attachCombobox(state);
|
|
117
|
-
setComboboxDisabled(state, disabled);
|
|
118
|
-
setComboboxLimit("max", state, getAttribute(element, "max"));
|
|
119
|
-
setComboboxLimit("min", state, getAttribute(element, "min"));
|
|
120
|
-
setComboboxReadonly(state, readonly);
|
|
121
|
-
setComboboxRequired(state, required);
|
|
122
|
-
filterComboboxOptions(state);
|
|
123
|
-
if (content.list.selected.length > 0) updateComboboxPreview("set", state, content.list.selected[0]);
|
|
124
|
-
updateComboboxForm(state, state.values.current, false);
|
|
125
|
-
updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
|
|
126
|
-
updateComboboxSelectionItems(state);
|
|
127
|
-
return state;
|
|
128
|
-
}
|
|
129
|
-
function detachCombobox(state) {
|
|
130
|
-
const { content, floatable, host, label, select } = state;
|
|
131
|
-
const listeners = state.listeners.splice(0);
|
|
132
|
-
const { length } = listeners;
|
|
133
|
-
for (let index = 0; index < length; index += 1) listeners[index]();
|
|
134
|
-
floatable.destroy();
|
|
135
|
-
const anchor = state.anchor.container ?? state.anchor.element;
|
|
136
|
-
anchor.insertAdjacentElement("beforebegin", select);
|
|
137
|
-
anchor.remove();
|
|
138
|
-
content.remove();
|
|
139
|
-
label.element.htmlFor = select.id;
|
|
140
|
-
label.element.id = state.label.id;
|
|
141
|
-
select.hidden = false;
|
|
142
|
-
comboboxStates.map.delete(host);
|
|
143
|
-
comboboxStates.map.delete(content);
|
|
144
|
-
comboboxStates.set.delete(state);
|
|
145
|
-
}
|
|
146
|
-
const comboboxStates = {
|
|
147
|
-
map: /* @__PURE__ */ new WeakMap(),
|
|
148
|
-
set: /* @__PURE__ */ new Set()
|
|
149
|
-
};
|
|
150
|
-
//#endregion
|
|
151
|
-
//#region src/js/combobox/event.combobox.ts
|
|
152
|
-
function onComboboxClickContent(event, content) {
|
|
153
|
-
const state = comboboxStates.map.get(content);
|
|
154
|
-
if (state == null) return;
|
|
155
|
-
if (state.disabled) return;
|
|
156
|
-
const option = findAncestor(event, COMBOBOX_SELECTOR_OPTION);
|
|
157
|
-
if (!(option instanceof HTMLElement)) state.filter.element.focus();
|
|
158
|
-
else if (state.readonly) updateComboboxPreview("set", state, option);
|
|
159
|
-
else toggleComboboxOption(state, option);
|
|
160
|
-
}
|
|
161
|
-
function onComboboxClickDocument(event) {
|
|
162
|
-
const combobox = findAncestor(event, COMBOBOX_TAGNAME);
|
|
163
|
-
const state = comboboxStates.map.get(combobox);
|
|
164
|
-
if (combobox == null || state == null) return;
|
|
165
|
-
const label = findAncestor(event, "label");
|
|
166
|
-
if (label != null) {
|
|
167
|
-
if (label === state.label.element && combobox.type === "select") {
|
|
168
|
-
state.anchor.element.focus();
|
|
169
|
-
if (!state.disabled) state.floatable.show();
|
|
170
|
-
}
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
const selection = findAncestor(event, `[${COMBOBOX_ATTRIBUTE_SELECTION_ITEM}]`);
|
|
174
|
-
if (selection != null) {
|
|
175
|
-
const option = state.options.selected.find((option) => option.id === getData$1(selection, "option", false));
|
|
176
|
-
if (state.disabled) state.anchor.element.focus();
|
|
177
|
-
else if (state.readonly) updateComboboxTabindices(state, option);
|
|
178
|
-
else toggleComboboxOption(state, option);
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
if (findAncestor(event, COMBOBOX_SELECTOR_ANCHOR) != null && !state.disabled) {
|
|
182
|
-
state.floatable.show();
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
function onComboboxEnter(state) {
|
|
187
|
-
if (state.disabled || state.readonly) return;
|
|
188
|
-
if (hasTextSelection(state.anchor.element)) onComboboxSelection(state);
|
|
189
|
-
else toggleComboboxOption(state, state.active);
|
|
190
|
-
}
|
|
191
|
-
function onComboboxEscape(state) {
|
|
192
|
-
state.floatable.hide();
|
|
193
|
-
}
|
|
194
|
-
function onComboboxFilter(type, state, manual, emit) {
|
|
195
|
-
if (state.disabled || state.readonly) return;
|
|
196
|
-
const diff = state.filter.value !== state.filter.element.value;
|
|
197
|
-
if (!state.autocomplete.inline && state.filter.element.value.length > 0 && !(manual ?? false)) state.floatable.show();
|
|
198
|
-
const option = state.autocomplete.none ? state.active ?? state.options.all[0] : filterComboboxOptions(state);
|
|
199
|
-
let update;
|
|
200
|
-
if (type.includes("delete")) update = "none";
|
|
201
|
-
else if (type.includes("insert")) update = "typed";
|
|
202
|
-
if (update != null) if (state.autocomplete.none) applyComboboxSelection({
|
|
203
|
-
state,
|
|
204
|
-
added: false,
|
|
205
|
-
emit: (emit ?? true) && diff
|
|
206
|
-
});
|
|
207
|
-
else updateComboboxPreview(update, state, option);
|
|
208
|
-
if (diff) {
|
|
209
|
-
state.filter.value = state.filter.element.value;
|
|
210
|
-
dispatch$1(state.host, "input");
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
function onComboboxFocusout(state) {
|
|
214
|
-
if (state.disabled || state.readonly || state.type === "select" || state.host === document.activeElement || state.host.contains(document.activeElement)) return;
|
|
215
|
-
const option = filterComboboxOption(state, state.filter.element.value, true);
|
|
216
|
-
applyComboboxSelection({
|
|
217
|
-
option,
|
|
218
|
-
state,
|
|
219
|
-
added: false,
|
|
220
|
-
selected: option == null ? [] : [option]
|
|
221
|
-
});
|
|
222
|
-
}
|
|
223
|
-
function onComboboxInput(event) {
|
|
224
|
-
const combobox = findAncestor(event, COMBOBOX_TAGNAME);
|
|
225
|
-
const state = comboboxStates.map.get(combobox);
|
|
226
|
-
if (combobox != null && state != null && event.target === state.filter.element) onComboboxFilter(event.inputType, state);
|
|
227
|
-
}
|
|
228
|
-
function onComboboxKeydown(event) {
|
|
229
|
-
const combobox = findAncestor(event, COMBOBOX_TAGNAME);
|
|
230
|
-
const state = comboboxStates.map.get(combobox);
|
|
231
|
-
if (combobox == null || state == null) return;
|
|
232
|
-
const item = findAncestor(event, COMBOBOX_SELECTOR_SELECTION_ITEM);
|
|
233
|
-
if (item instanceof HTMLElement) {
|
|
234
|
-
onComboboxKeydownSelection(event, state, item);
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
onComboboxKeydownFilter(event, state);
|
|
238
|
-
}
|
|
239
|
-
function onComboboxKeydownFilter(event, state) {
|
|
240
|
-
const ignore = state.disabled || state.readonly;
|
|
241
|
-
const isSelect = state.type === "select";
|
|
242
|
-
let preventDefault = ignore;
|
|
243
|
-
switch (true) {
|
|
244
|
-
case event.key === " ":
|
|
245
|
-
if (!state.disabled) state.floatable.show();
|
|
246
|
-
break;
|
|
247
|
-
case event.key === "Enter":
|
|
248
|
-
preventDefault = state.floatable.open;
|
|
249
|
-
if (preventDefault) onComboboxEnter(state);
|
|
250
|
-
else if (!ignore && state.type === "select") state.floatable.show();
|
|
251
|
-
else if (!ignore) state.internals.form?.requestSubmit();
|
|
252
|
-
break;
|
|
253
|
-
case event.key === "Escape":
|
|
254
|
-
preventDefault = false;
|
|
255
|
-
onComboboxEscape(state);
|
|
256
|
-
break;
|
|
257
|
-
case !isSelect && COMBOBOX_KEYS_SELECTION.has(event.key):
|
|
258
|
-
if (!ignore && hasTextSelection(state.anchor.element)) onComboboxSelection(state);
|
|
259
|
-
break;
|
|
260
|
-
case COMBOBOX_KEYS_NAVIGATION.has(event.key):
|
|
261
|
-
if (!state.disabled) onComboboxNavigation(event, state);
|
|
262
|
-
break;
|
|
263
|
-
default:
|
|
264
|
-
preventDefault = false;
|
|
265
|
-
if (event.key === "Tab") onComboboxTab(state);
|
|
266
|
-
}
|
|
267
|
-
if (preventDefault) {
|
|
268
|
-
event.preventDefault();
|
|
269
|
-
event.stopPropagation();
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
function onComboboxKeydownSelection(event, state, item) {
|
|
273
|
-
if (COMBOBOX_KEYS_ACTIVATION.has(event.key)) {
|
|
274
|
-
event.preventDefault();
|
|
275
|
-
onComboboxRemoveSelection(state, item);
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
if (state.disabled || state.selection == null) return;
|
|
279
|
-
const { length } = state.selection.items;
|
|
280
|
-
const index = state.selection.items.findIndex((selection) => selection.button === item);
|
|
281
|
-
let next;
|
|
282
|
-
switch (event.key) {
|
|
283
|
-
case "ArrowDown":
|
|
284
|
-
state.anchor.element.focus();
|
|
285
|
-
state.anchor.element.click();
|
|
286
|
-
return;
|
|
287
|
-
case "ArrowRight":
|
|
288
|
-
case "ArrowLeft":
|
|
289
|
-
next = clamp(index + (event.key === "ArrowRight" ? 1 : -1), 0, length - 1, true);
|
|
290
|
-
break;
|
|
291
|
-
case "End":
|
|
292
|
-
next = length - 1;
|
|
293
|
-
break;
|
|
294
|
-
case "Home": next = 0;
|
|
295
|
-
}
|
|
296
|
-
if (next == null) return;
|
|
297
|
-
event.preventDefault();
|
|
298
|
-
for (let index = 0; index < length; index += 1) {
|
|
299
|
-
const { button } = state.selection.items[index];
|
|
300
|
-
const selected = index === next;
|
|
301
|
-
button.tabIndex = selected ? 0 : -1;
|
|
302
|
-
if (selected) button.focus();
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function onComboboxNavigation(event, state) {
|
|
306
|
-
let skip = false;
|
|
307
|
-
if (!state.floatable.open) {
|
|
308
|
-
skip = true;
|
|
309
|
-
state.floatable.show();
|
|
310
|
-
}
|
|
311
|
-
const visible = getComboboxVisibleOptions(state);
|
|
312
|
-
if (visible.length === 0) return;
|
|
313
|
-
let next;
|
|
314
|
-
if (skip || state.active == null) next = state.active ?? visible.at(event.key === "ArrowDown" ? 0 : -1);
|
|
315
|
-
else {
|
|
316
|
-
const current = visible.findIndex((option) => option.id === state.active?.id);
|
|
317
|
-
let index;
|
|
318
|
-
switch (event.key) {
|
|
319
|
-
case "ArrowDown":
|
|
320
|
-
case "ArrowUp":
|
|
321
|
-
index = clamp(current + (event.key === "ArrowDown" ? 1 : -1), 0, visible.length - 1, true);
|
|
322
|
-
break;
|
|
323
|
-
case "End":
|
|
324
|
-
index = visible.length - 1;
|
|
325
|
-
break;
|
|
326
|
-
case "Home":
|
|
327
|
-
index = 0;
|
|
328
|
-
break;
|
|
329
|
-
case "PageDown":
|
|
330
|
-
case "PageUp": index = clamp(current + (event.key === "PageDown" ? 10 : -10), 0, visible.length - 1);
|
|
331
|
-
}
|
|
332
|
-
if (index != null) next = visible[index];
|
|
333
|
-
}
|
|
334
|
-
let preview;
|
|
335
|
-
if (state.type === "autocomplete" && (state.autocomplete.both || state.autocomplete.inline)) preview = "set";
|
|
336
|
-
else preview = "none";
|
|
337
|
-
updateComboboxPreview(preview, state, next ?? state.active ?? visible[0]);
|
|
338
|
-
}
|
|
339
|
-
function onComboboxRemoveSelection(state, item) {
|
|
340
|
-
if (state.disabled || state.readonly) return;
|
|
341
|
-
const option = state.options.selected.find((option) => option.id === getData$1(item, "option", false));
|
|
342
|
-
if (option != null) toggleComboboxOption(state, option);
|
|
343
|
-
}
|
|
344
|
-
function onComboboxSelection(state) {
|
|
345
|
-
state.filter.value = state.anchor.element === state.filter.element ? state.filter.element.value : "";
|
|
346
|
-
toggleComboboxOption(state, filterComboboxOptions(state) ?? state.active);
|
|
347
|
-
}
|
|
348
|
-
function onComboboxTab(state) {
|
|
349
|
-
setTimeout(() => {
|
|
350
|
-
if (state.host === document.activeElement) return;
|
|
351
|
-
if (state.selection?.container.contains(document.activeElement) || !state.host.contains(document.activeElement)) state.floatable.hide();
|
|
352
|
-
});
|
|
353
|
-
}
|
|
354
|
-
on(document, "click", onComboboxClickDocument);
|
|
355
|
-
on(document, "input", onComboboxInput, { passive: false });
|
|
356
|
-
on(document, "keydown", onComboboxKeydown, { passive: false });
|
|
357
|
-
//#endregion
|
|
358
|
-
//#region src/js/combobox/misc.combobox.ts
|
|
359
|
-
function generateComboboxIds(prefix, select) {
|
|
360
|
-
const elements = [...select.children].flatMap((child) => child instanceof HTMLOptGroupElement ? [child, ...[...child.children].filter((groupChild) => groupChild instanceof HTMLOptionElement)] : child instanceof HTMLOptionElement ? [child] : []);
|
|
361
|
-
const { length } = elements;
|
|
362
|
-
for (let index = 0; index < length; index += 1) {
|
|
363
|
-
const element = elements[index];
|
|
364
|
-
if (element.id === "") element.id = `${prefix}${element instanceof HTMLOptGroupElement ? "_group_" : "_option_"}${index}`;
|
|
365
|
-
}
|
|
366
|
-
}
|
|
367
|
-
function getComboboxAttributeValue(element, attribute, possible, defaultValue) {
|
|
368
|
-
const value = getAttribute(element, attribute);
|
|
369
|
-
return possible.has(value) ? value : defaultValue;
|
|
370
|
-
}
|
|
371
|
-
function getComboboxAutoComplete(element, select) {
|
|
372
|
-
let value;
|
|
373
|
-
if (select) value = "list";
|
|
374
|
-
else value = getComboboxAttributeValue(element, "autocomplete", COMBOBOX_AUTOCOMPLETES, "none");
|
|
375
|
-
return {
|
|
376
|
-
value,
|
|
377
|
-
both: value === "both",
|
|
378
|
-
inline: value === "inline",
|
|
379
|
-
list: value === "list",
|
|
380
|
-
none: value === "none"
|
|
381
|
-
};
|
|
382
|
-
}
|
|
383
|
-
function getComboboxBooleanAria(element, attribute) {
|
|
384
|
-
const value = getAria(element, attribute);
|
|
385
|
-
return value === true || value === "true";
|
|
386
|
-
}
|
|
387
|
-
function getComboboxId() {
|
|
388
|
-
index += 1;
|
|
389
|
-
return `oui_combobox_${index}`;
|
|
390
|
-
}
|
|
391
|
-
function getComboboxVisibleOptions(state) {
|
|
392
|
-
return state.options.all.filter((option) => !option.hidden);
|
|
393
|
-
}
|
|
394
|
-
function hasTextSelection(element) {
|
|
395
|
-
return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
396
|
-
}
|
|
397
|
-
function setComboboxFilter(state, value) {
|
|
398
|
-
if (typeof value !== "string") return;
|
|
399
|
-
const type = value.length < state.filter.element.value.length ? "deleteContentBackward" : "insertText";
|
|
400
|
-
state.filter.element.value = value;
|
|
401
|
-
onComboboxFilter(type, state);
|
|
402
|
-
}
|
|
403
|
-
function setComboboxLimit(type, state, value) {
|
|
404
|
-
let actual = value;
|
|
405
|
-
if (typeof actual === "string") actual = Number.parseInt(actual, 10);
|
|
406
|
-
if (typeof actual === "number" && !Number.isNaN(actual) && actual >= 0 ? actual !== state[type] : actual == null) {
|
|
407
|
-
state[type] = actual;
|
|
408
|
-
setAttribute(state.host, type, actual);
|
|
409
|
-
updateComboboxValidity(state);
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
let index = 0;
|
|
413
|
-
//#endregion
|
|
414
|
-
//#region src/js/combobox/i18n.combobox.ts
|
|
415
|
-
function getComboboxLanguage(state) {
|
|
416
|
-
return (state.host.closest("[lang]")?.getAttribute("lang") ?? document.documentElement.lang).toLowerCase();
|
|
417
|
-
}
|
|
418
|
-
function getComboboxLocalization(key, state) {
|
|
419
|
-
const language = getComboboxLanguage(state);
|
|
420
|
-
const allLocalizations = [state.localizations, LOCALIZATIONS.globals];
|
|
421
|
-
const { length } = allLocalizations;
|
|
422
|
-
let localization;
|
|
423
|
-
let shortLanguage;
|
|
424
|
-
for (let index = 0; index < length; index += 1) {
|
|
425
|
-
const localizations = allLocalizations[index];
|
|
426
|
-
localization = localizations?.[language]?.[key];
|
|
427
|
-
if (localization != null) break;
|
|
428
|
-
shortLanguage ??= language.split("-")[0];
|
|
429
|
-
if (shortLanguage === language) continue;
|
|
430
|
-
localization = localizations?.[shortLanguage]?.[key];
|
|
431
|
-
if (localization != null) break;
|
|
432
|
-
}
|
|
433
|
-
return localization ?? LOCALIZATIONS.defaults[key];
|
|
434
|
-
}
|
|
435
|
-
function setComboboxLocalizations(first, second) {
|
|
436
|
-
updateComboboxLocalizations(typeof first === "string" ? { [first]: second } : first);
|
|
437
|
-
}
|
|
438
|
-
function updateComboboxLocalizations(values, destination) {
|
|
439
|
-
if (typeof values !== "object" || values == null) return;
|
|
440
|
-
const localizations = destination ?? LOCALIZATIONS.globals;
|
|
441
|
-
const languages = Object.keys(values);
|
|
442
|
-
const languagesLength = languages.length;
|
|
443
|
-
let updated = false;
|
|
444
|
-
for (let languagesIndex = 0; languagesIndex < languagesLength; languagesIndex += 1) {
|
|
445
|
-
const language = languages[languagesIndex];
|
|
446
|
-
const languageValue = values[language];
|
|
447
|
-
if (typeof languageValue !== "object" || languageValue == null) continue;
|
|
448
|
-
const normalizedLanguage = language.toLowerCase();
|
|
449
|
-
localizations[normalizedLanguage] ??= {};
|
|
450
|
-
const keys = Object.keys(languageValue);
|
|
451
|
-
const keysLength = keys.length;
|
|
452
|
-
for (let keysIndex = 0; keysIndex < keysLength; keysIndex += 1) {
|
|
453
|
-
const key = keys[keysIndex];
|
|
454
|
-
const keyValue = languageValue[key];
|
|
455
|
-
const normalizedKey = key.toLowerCase();
|
|
456
|
-
if (LOCALIZATIONS.keys.has(normalizedKey) && (typeof keyValue === "string" || typeof keyValue === "function")) {
|
|
457
|
-
updated = true;
|
|
458
|
-
localizations[normalizedLanguage][normalizedKey] = keyValue;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
if (!updated) return;
|
|
463
|
-
for (const state of comboboxStates.set) {
|
|
464
|
-
updateComboboxAnchor(state);
|
|
465
|
-
updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
|
|
466
|
-
updateComboboxSelectionItems(state);
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
const LOCALIZATIONS = {
|
|
470
|
-
defaults: {
|
|
471
|
-
empty: "No results found",
|
|
472
|
-
input: "Please enter a value",
|
|
473
|
-
invalid: "Please select an option from the list",
|
|
474
|
-
max: (max) => `Please select fewer options (maximum is ${max})`,
|
|
475
|
-
min: (min) => `Please select more options (minimum is ${min})`,
|
|
476
|
-
remove: (label) => `Remove ${label}`,
|
|
477
|
-
results: (amount) => `${amount} results available`,
|
|
478
|
-
selected: (amount) => `${amount} selected`
|
|
479
|
-
},
|
|
480
|
-
globals: {},
|
|
481
|
-
keys: /* @__PURE__ */ new Set([
|
|
482
|
-
"empty",
|
|
483
|
-
"input",
|
|
484
|
-
"invalid",
|
|
485
|
-
"max",
|
|
486
|
-
"min",
|
|
487
|
-
"remove",
|
|
488
|
-
"results",
|
|
489
|
-
"selected"
|
|
490
|
-
])
|
|
491
|
-
};
|
|
492
|
-
//#endregion
|
|
493
|
-
//#region src/js/combobox/form.combobox.ts
|
|
494
|
-
function updateComboboxForm(state, values, emit) {
|
|
495
|
-
let value;
|
|
496
|
-
if (state.multiple) {
|
|
497
|
-
const { length } = values;
|
|
498
|
-
value = new FormData();
|
|
499
|
-
for (let index = 0; index < length; index += 1) value.append(state.name, values[index]);
|
|
500
|
-
} else value = values[0];
|
|
501
|
-
state.values.current = values;
|
|
502
|
-
state.internals.setFormValue(value, JSON.stringify(values));
|
|
503
|
-
updateComboboxAnchor(state);
|
|
504
|
-
updateComboboxValidity(state);
|
|
505
|
-
if (emit ?? true) dispatch(state.host, "change");
|
|
506
|
-
}
|
|
507
|
-
function updateComboboxValidity(state) {
|
|
508
|
-
if (state.autocomplete.none) {
|
|
509
|
-
const valueMissing = state.required && state.filter.element.value.length === 0;
|
|
510
|
-
const localization = getComboboxLocalization("invalid", state);
|
|
511
|
-
state.internals.setValidity({ valueMissing }, typeof localization === "function" ? localization() : localization);
|
|
512
|
-
state.internals.checkValidity();
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
let error;
|
|
516
|
-
let message;
|
|
517
|
-
switch (true) {
|
|
518
|
-
case state.required && state.options.selected.length === 0: {
|
|
519
|
-
error = "badInput";
|
|
520
|
-
const localization = getComboboxLocalization("input", state);
|
|
521
|
-
message = typeof localization === "function" ? localization() : localization;
|
|
522
|
-
break;
|
|
523
|
-
}
|
|
524
|
-
case state.multiple && typeof state.max === "number" && state.options.selected.length > state.max: {
|
|
525
|
-
error = "rangeOverflow";
|
|
526
|
-
const localization = getComboboxLocalization("max", state);
|
|
527
|
-
message = typeof localization === "function" ? localization(state.max) : localization.replace("{max}", state.max.toString());
|
|
528
|
-
break;
|
|
529
|
-
}
|
|
530
|
-
case state.multiple && typeof state.min === "number" && state.options.selected.length < state.min: {
|
|
531
|
-
error = "rangeUnderflow";
|
|
532
|
-
const localization = getComboboxLocalization("min", state);
|
|
533
|
-
message = typeof localization === "function" ? localization(state.min) : localization.replace("{min}", state.min.toString());
|
|
534
|
-
break;
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
state.internals.setValidity(error == null ? {} : { [error]: true }, message);
|
|
538
|
-
state.internals.checkValidity();
|
|
539
|
-
}
|
|
540
|
-
//#endregion
|
|
541
|
-
//#region src/js/combobox/dom.combobox.ts
|
|
542
|
-
function createComboboxAnchor(parameters) {
|
|
543
|
-
const { autocomplete, id, label, select, type } = parameters;
|
|
544
|
-
let container;
|
|
545
|
-
let element;
|
|
546
|
-
let selection;
|
|
547
|
-
if (parameters.type === "autocomplete") element = createComboboxInput({
|
|
548
|
-
id,
|
|
549
|
-
label,
|
|
550
|
-
type,
|
|
551
|
-
autocomplete: autocomplete.value,
|
|
552
|
-
value: select.selectedOptions[0]?.textContent ?? ""
|
|
553
|
-
});
|
|
554
|
-
else ({container, element, selection} = createComboboxButton(parameters));
|
|
555
|
-
setAttribute(container ?? element, COMBOBOX_ATTRIBUTE_ANCHOR, "");
|
|
556
|
-
return {
|
|
557
|
-
container,
|
|
558
|
-
element,
|
|
559
|
-
selection
|
|
560
|
-
};
|
|
561
|
-
}
|
|
562
|
-
function createComboboxButton(parameters) {
|
|
563
|
-
const { id, label, multiple, select } = parameters;
|
|
564
|
-
const element = createElement("button", {
|
|
565
|
-
aria: {
|
|
566
|
-
controls: `${id}_content`,
|
|
567
|
-
expanded: false,
|
|
568
|
-
haspopup: "listbox",
|
|
569
|
-
labelledby: `${label.id} ${id}`
|
|
570
|
-
},
|
|
571
|
-
property: { textContent: multiple ? "" : select.selectedOptions[0]?.textContent ?? "" }
|
|
572
|
-
});
|
|
573
|
-
if (!multiple) return { element };
|
|
574
|
-
const container = createElement("div");
|
|
575
|
-
const selection = createComboboxSelection(id, select);
|
|
576
|
-
container.append(selection.container, element);
|
|
577
|
-
return {
|
|
578
|
-
container,
|
|
579
|
-
element,
|
|
580
|
-
selection
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
|
-
function createComboboxContent(parameters) {
|
|
584
|
-
const { autocomplete, id, label, multiple, select, type } = parameters;
|
|
585
|
-
const element = createElement("div", {
|
|
586
|
-
aria: { labelledby: label.id },
|
|
587
|
-
attribute: {
|
|
588
|
-
[COMBOBOX_ATTRIBUTE_CONTENT]: "",
|
|
589
|
-
popover: ""
|
|
590
|
-
},
|
|
591
|
-
property: {
|
|
592
|
-
id: `${id}_content`,
|
|
593
|
-
tabIndex: -1
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
const filter = createComboboxFilter({
|
|
597
|
-
id,
|
|
598
|
-
label,
|
|
599
|
-
type,
|
|
600
|
-
autocomplete: autocomplete.value
|
|
601
|
-
});
|
|
602
|
-
const list = createComboboxList({
|
|
603
|
-
autocomplete,
|
|
604
|
-
id,
|
|
605
|
-
label,
|
|
606
|
-
multiple,
|
|
607
|
-
select
|
|
608
|
-
});
|
|
609
|
-
const message = createComboboxMessage();
|
|
610
|
-
if (filter != null) element.append(filter.container);
|
|
611
|
-
element.append(list.element, message);
|
|
612
|
-
return {
|
|
613
|
-
element,
|
|
614
|
-
filter,
|
|
615
|
-
list,
|
|
616
|
-
message
|
|
617
|
-
};
|
|
618
|
-
}
|
|
619
|
-
function createComboboxFilter(parameters) {
|
|
620
|
-
const { autocomplete, id, label, type } = parameters;
|
|
621
|
-
if (type === "autocomplete") return;
|
|
622
|
-
const container = createElement("div", { attribute: { [COMBOBOX_ATTRIBUTE_FILTER]: "" } });
|
|
623
|
-
const element = createComboboxInput({
|
|
624
|
-
autocomplete,
|
|
625
|
-
id,
|
|
626
|
-
label,
|
|
627
|
-
type,
|
|
628
|
-
value: ""
|
|
629
|
-
});
|
|
630
|
-
setAttribute(element, COMBOBOX_ATTRIBUTE_FILTER_INPUT, "");
|
|
631
|
-
container.append(element);
|
|
632
|
-
return {
|
|
633
|
-
container,
|
|
634
|
-
element
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
function createComboboxGroup(parameters) {
|
|
638
|
-
const { autocomplete, group, groupOptions, selected } = parameters;
|
|
639
|
-
const element = createElement("li", {
|
|
640
|
-
aria: { label: group.label },
|
|
641
|
-
property: { role: "group" }
|
|
642
|
-
});
|
|
643
|
-
const list = createElement("ul", { property: { role: "none" } });
|
|
644
|
-
const header = createComboboxHeader(group);
|
|
645
|
-
element.append(header, list);
|
|
646
|
-
return {
|
|
647
|
-
element,
|
|
648
|
-
header,
|
|
649
|
-
options: createComboboxOptions({
|
|
650
|
-
autocomplete,
|
|
651
|
-
list,
|
|
652
|
-
selected,
|
|
653
|
-
group: header.id,
|
|
654
|
-
options: groupOptions
|
|
655
|
-
})
|
|
656
|
-
};
|
|
657
|
-
}
|
|
658
|
-
function createComboboxHeader(group) {
|
|
659
|
-
return createElement("div", {
|
|
660
|
-
aria: { hidden: true },
|
|
661
|
-
attribute: { [COMBOBOX_ATTRIBUTE_HEADER]: "" },
|
|
662
|
-
property: {
|
|
663
|
-
id: group.id,
|
|
664
|
-
textContent: group.label
|
|
665
|
-
}
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
function createComboboxInput(parameters) {
|
|
669
|
-
const { autocomplete, id, label, type, value } = parameters;
|
|
670
|
-
return createElement("input", {
|
|
671
|
-
aria: {
|
|
672
|
-
autocomplete,
|
|
673
|
-
controls: `${id}_list`,
|
|
674
|
-
expanded: type === "select",
|
|
675
|
-
haspopup: "listbox",
|
|
676
|
-
labelledby: label.id
|
|
677
|
-
},
|
|
678
|
-
attribute: {
|
|
679
|
-
autocorrect: "off",
|
|
680
|
-
autocomplete: "off",
|
|
681
|
-
form: "",
|
|
682
|
-
spellcheck: "false",
|
|
683
|
-
type: "text"
|
|
684
|
-
},
|
|
685
|
-
property: {
|
|
686
|
-
id,
|
|
687
|
-
value,
|
|
688
|
-
role: "combobox"
|
|
689
|
-
}
|
|
690
|
-
});
|
|
691
|
-
}
|
|
692
|
-
function createComboboxList(parameters) {
|
|
693
|
-
const { autocomplete, id, label, multiple, select } = parameters;
|
|
694
|
-
const element = createElement("ul", {
|
|
695
|
-
aria: {
|
|
696
|
-
labelledby: label.id,
|
|
697
|
-
multiselectable: multiple
|
|
698
|
-
},
|
|
699
|
-
attribute: {
|
|
700
|
-
[COMBOBOX_ATTRIBUTE_LIST]: "",
|
|
701
|
-
tabindex: -1
|
|
702
|
-
},
|
|
703
|
-
property: {
|
|
704
|
-
id: `${id}_list`,
|
|
705
|
-
role: "listbox"
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
const children = [...select.children];
|
|
709
|
-
const childrenLength = children.length;
|
|
710
|
-
const options = [];
|
|
711
|
-
const selected = [];
|
|
712
|
-
let groups;
|
|
713
|
-
for (let childIndex = 0; childIndex < childrenLength; childIndex += 1) {
|
|
714
|
-
const child = children[childIndex];
|
|
715
|
-
if (child instanceof HTMLOptionElement) {
|
|
716
|
-
const option = createComboboxOption({
|
|
717
|
-
autocomplete,
|
|
718
|
-
index: childIndex,
|
|
719
|
-
option: child
|
|
720
|
-
});
|
|
721
|
-
element.append(option);
|
|
722
|
-
options.push(option);
|
|
723
|
-
if (child.selected) selected.push(option);
|
|
724
|
-
continue;
|
|
725
|
-
}
|
|
726
|
-
if (!(child instanceof HTMLOptGroupElement)) continue;
|
|
727
|
-
const group = createComboboxGroup({
|
|
728
|
-
autocomplete,
|
|
729
|
-
selected,
|
|
730
|
-
group: child,
|
|
731
|
-
groupOptions: [...child.children]
|
|
732
|
-
});
|
|
733
|
-
groups ??= [];
|
|
734
|
-
element.append(group.element, ...group.options);
|
|
735
|
-
groups.push(group);
|
|
736
|
-
options.push(...group.options);
|
|
737
|
-
}
|
|
738
|
-
return {
|
|
739
|
-
element,
|
|
740
|
-
groups,
|
|
741
|
-
options,
|
|
742
|
-
selected
|
|
743
|
-
};
|
|
744
|
-
}
|
|
745
|
-
function createComboboxMessage() {
|
|
746
|
-
return createElement("div", {
|
|
747
|
-
aria: { live: "polite" },
|
|
748
|
-
attribute: { [COMBOBOX_ATTRIBUTE_MESSAGE]: "" },
|
|
749
|
-
property: {
|
|
750
|
-
hidden: true,
|
|
751
|
-
role: "status"
|
|
752
|
-
}
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
function createComboboxOption(parameters) {
|
|
756
|
-
const { autocomplete, group, option } = parameters;
|
|
757
|
-
return createElement("li", {
|
|
758
|
-
aria: { selected: !autocomplete.none && option.selected },
|
|
759
|
-
attribute: { [COMBOBOX_ATTRIBUTE_OPTION]: "" },
|
|
760
|
-
data: {
|
|
761
|
-
group,
|
|
762
|
-
value: option.value
|
|
763
|
-
},
|
|
764
|
-
property: {
|
|
765
|
-
id: option.id,
|
|
766
|
-
role: "option",
|
|
767
|
-
textContent: option.textContent
|
|
768
|
-
}
|
|
769
|
-
});
|
|
770
|
-
}
|
|
771
|
-
function createComboboxOptions(parameters) {
|
|
772
|
-
const { autocomplete, group, list, options, selected } = parameters;
|
|
773
|
-
const items = [];
|
|
774
|
-
for (let index = 0; index < options.length; index += 1) {
|
|
775
|
-
const option = options[index];
|
|
776
|
-
const item = createComboboxOption({
|
|
777
|
-
autocomplete,
|
|
778
|
-
group,
|
|
779
|
-
index,
|
|
780
|
-
option
|
|
781
|
-
});
|
|
782
|
-
items.push(item);
|
|
783
|
-
if (option.selected) selected.push(item);
|
|
784
|
-
list.append(item);
|
|
785
|
-
}
|
|
786
|
-
return items;
|
|
787
|
-
}
|
|
788
|
-
function createComboboxSelection(id, select) {
|
|
789
|
-
const container = createElement("ul", {
|
|
790
|
-
attribute: { [COMBOBOX_ATTRIBUTE_SELECTION]: "" },
|
|
791
|
-
property: { id: `${id}_selection` }
|
|
792
|
-
});
|
|
793
|
-
const options = [...select.selectedOptions];
|
|
794
|
-
const items = [];
|
|
795
|
-
createComboboxSelectionItems({
|
|
796
|
-
container,
|
|
797
|
-
items,
|
|
798
|
-
options
|
|
799
|
-
});
|
|
800
|
-
const { button } = items[0] ?? {};
|
|
801
|
-
if (button != null) button.tabIndex = 0;
|
|
802
|
-
return {
|
|
803
|
-
items,
|
|
804
|
-
container
|
|
805
|
-
};
|
|
806
|
-
}
|
|
807
|
-
function createComboboxSelectionItem(option, state) {
|
|
808
|
-
const element = createElement("li");
|
|
809
|
-
const localization = state == null ? option.textContent : getComboboxLocalization("remove", state);
|
|
810
|
-
const button = createElement("button", {
|
|
811
|
-
aria: { label: typeof localization === "function" ? localization(option.textContent ?? "", getData(option, "value", false) ?? "") : localization },
|
|
812
|
-
attribute: {
|
|
813
|
-
[COMBOBOX_ATTRIBUTE_SELECTION_ITEM]: "",
|
|
814
|
-
tabindex: -1
|
|
815
|
-
},
|
|
816
|
-
data: { option: option.id }
|
|
817
|
-
});
|
|
818
|
-
const icon = createElement("span", {
|
|
819
|
-
aria: { hidden: true },
|
|
820
|
-
property: { innerHTML: "×" }
|
|
821
|
-
});
|
|
822
|
-
const label = createElement("span", { property: { textContent: option.textContent } });
|
|
823
|
-
button.append(icon, label);
|
|
824
|
-
element.append(button);
|
|
825
|
-
return {
|
|
826
|
-
button,
|
|
827
|
-
element
|
|
828
|
-
};
|
|
829
|
-
}
|
|
830
|
-
function createComboboxSelectionItems(parameters) {
|
|
831
|
-
const { container, items, options, state } = parameters;
|
|
832
|
-
const { length } = options;
|
|
833
|
-
for (let index = 0; index < length; index += 1) {
|
|
834
|
-
const option = options[index];
|
|
835
|
-
const item = createComboboxSelectionItem(option, state);
|
|
836
|
-
container.append(item.element);
|
|
837
|
-
items.push(item);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
function setComboboxDisabled(state, value) {
|
|
841
|
-
state.filter.element.readOnly = state.readonly || value;
|
|
842
|
-
setProperties("disabled", state, value);
|
|
843
|
-
}
|
|
844
|
-
function setProperties(key, state, value) {
|
|
845
|
-
state[key] = value;
|
|
846
|
-
const elements = [
|
|
847
|
-
state.host,
|
|
848
|
-
state.anchor.element,
|
|
849
|
-
state.filter.element
|
|
850
|
-
];
|
|
851
|
-
const { length } = elements;
|
|
852
|
-
for (let index = 0; index < length; index += 1) {
|
|
853
|
-
const element = elements[index];
|
|
854
|
-
setAria(element, key, key !== "readonly" || !(element instanceof HTMLButtonElement) ? value : void 0);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
function setComboboxReadonly(state, value) {
|
|
858
|
-
state.filter.element.readOnly = state.disabled || value;
|
|
859
|
-
setProperties("readonly", state, value);
|
|
860
|
-
}
|
|
861
|
-
function setComboboxRequired(state, value) {
|
|
862
|
-
setProperties("required", state, value);
|
|
863
|
-
updateComboboxValidity(state);
|
|
864
|
-
}
|
|
865
|
-
function updateComboboxTabindices(state, active) {
|
|
866
|
-
if (active == null || state.selection == null) return;
|
|
867
|
-
const next = state.selection.items.find((item) => getData(item.button, "option", false) === active.id);
|
|
868
|
-
if (next == null) return;
|
|
869
|
-
const { length } = state.selection.items;
|
|
870
|
-
for (let index = 0; index < length; index += 1) {
|
|
871
|
-
const item = state.selection.items[index];
|
|
872
|
-
item.button.tabIndex = item === next ? 0 : -1;
|
|
873
|
-
}
|
|
874
|
-
next.button.focus();
|
|
875
|
-
}
|
|
876
|
-
function updateComboboxAnchor(state) {
|
|
877
|
-
if (!state.multiple) return;
|
|
878
|
-
const localization = getComboboxLocalization("selected", state);
|
|
879
|
-
setAria(state.anchor.element, "label", typeof localization === "function" ? localization(state.options.selected.length) : localization.replace("{amount}", state.options.selected.length.toString()));
|
|
880
|
-
}
|
|
881
|
-
function updateComboboxAutocomplete(preview, state, active) {
|
|
882
|
-
if (state.multiple) return;
|
|
883
|
-
const content = active?.textContent ?? state.filter.element.value;
|
|
884
|
-
if (preview === "typed" && !content.toLocaleLowerCase().startsWith(state.filter.element.value.toLocaleLowerCase())) return;
|
|
885
|
-
if (state.anchor.element instanceof HTMLInputElement) {
|
|
886
|
-
const contentLength = content.length;
|
|
887
|
-
const filterLength = state.filter.element.value.length;
|
|
888
|
-
if (preview === "set" || preview === "typed") {
|
|
889
|
-
state.anchor.element.value = content;
|
|
890
|
-
state.anchor.element.setSelectionRange(preview === "typed" ? filterLength : contentLength, contentLength);
|
|
891
|
-
}
|
|
892
|
-
} else state.anchor.element.textContent = content;
|
|
893
|
-
}
|
|
894
|
-
function updateComboboxMessage(state, visible) {
|
|
895
|
-
setAttribute(state.message, COMBOBOX_ATTRIBUTE_HIDDEN, visible > 0 ? "" : void 0);
|
|
896
|
-
if (visible === 0) {
|
|
897
|
-
const localization = getComboboxLocalization("empty", state);
|
|
898
|
-
state.message.textContent = typeof localization === "function" ? localization() : localization;
|
|
899
|
-
} else {
|
|
900
|
-
const localization = getComboboxLocalization("results", state);
|
|
901
|
-
state.message.textContent = typeof localization === "function" ? localization(visible) : localization.replace("{amount}", visible.toString());
|
|
902
|
-
}
|
|
903
|
-
state.message.hidden = false;
|
|
904
|
-
}
|
|
905
|
-
function updateComboboxPreview(preview, state, active) {
|
|
906
|
-
const { length } = state.options.all;
|
|
907
|
-
for (let index = 0; index < length; index += 1) {
|
|
908
|
-
const option = state.options.all[index];
|
|
909
|
-
setAttribute(option, COMBOBOX_ATTRIBUTE_OPTION_ACTIVE, option === active ? "" : void 0);
|
|
910
|
-
}
|
|
911
|
-
state.active = active;
|
|
912
|
-
setAria(state.filter.element, "activedescendant", state.floatable.open ? active?.id : void 0);
|
|
913
|
-
if (state.floatable.open) active?.scrollIntoView(comboboxScrollOptions);
|
|
914
|
-
if (preview !== "none") updateComboboxAutocomplete(preview, state, active);
|
|
915
|
-
}
|
|
916
|
-
function updateComboboxSelection(state, active, added, selected) {
|
|
917
|
-
if (state.selection == null) return;
|
|
918
|
-
let options = active == null ? [] : [active];
|
|
919
|
-
if (selected != null) {
|
|
920
|
-
options = selected;
|
|
921
|
-
const { length } = state.selection.items;
|
|
922
|
-
for (let index = 0; index < length; index += 1) state.selection.items[index].element.remove();
|
|
923
|
-
state.selection.items.splice(0);
|
|
924
|
-
}
|
|
925
|
-
if (added) {
|
|
926
|
-
createComboboxSelectionItems({
|
|
927
|
-
options,
|
|
928
|
-
state,
|
|
929
|
-
container: state.selection.container,
|
|
930
|
-
items: state.selection.items
|
|
931
|
-
});
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
let current = state.selection.items.findIndex((item) => getData(item.button, "option", false) === active?.id);
|
|
935
|
-
if (current === -1) return;
|
|
936
|
-
state.selection.items[current].element.remove();
|
|
937
|
-
state.selection.items.splice(current, 1);
|
|
938
|
-
if (state.selection.items.length === 0) {
|
|
939
|
-
state.anchor.element.focus();
|
|
940
|
-
return;
|
|
941
|
-
}
|
|
942
|
-
const { length } = state.selection.items;
|
|
943
|
-
if (current < 0) current = 0;
|
|
944
|
-
else if (current >= length) current = length - 1;
|
|
945
|
-
for (let index = 0; index < length; index += 1) {
|
|
946
|
-
const { button } = state.selection.items[index];
|
|
947
|
-
button.tabIndex = index === current ? 0 : -1;
|
|
948
|
-
}
|
|
949
|
-
state.selection.items[current].button.focus();
|
|
950
|
-
}
|
|
951
|
-
function updateComboboxSelectionItems(state) {
|
|
952
|
-
if (state.selection == null) return;
|
|
953
|
-
const localization = getComboboxLocalization("remove", state);
|
|
954
|
-
const { length } = state.selection.items;
|
|
955
|
-
for (let index = 0; index < length; index += 1) {
|
|
956
|
-
const item = state.selection.items[index];
|
|
957
|
-
const option = state.options.selected.find((option) => option.id === getData(item.button, "option", false));
|
|
958
|
-
if (option == null) continue;
|
|
959
|
-
setAria(item.button, "label", typeof localization === "function" ? localization(option?.textContent ?? "", getData(option, "value", false) ?? "") : localization.replace("{label}", option?.textContent ?? ""));
|
|
960
|
-
}
|
|
961
|
-
}
|
|
962
|
-
//#endregion
|
|
963
|
-
//#region src/js/combobox/data.combobox.ts
|
|
964
|
-
function applyComboboxSelection(parameters) {
|
|
965
|
-
const { added, emit, option, selected, state } = parameters;
|
|
966
|
-
const { length } = state.options.all;
|
|
967
|
-
for (let index = 0; index < length; index += 1) {
|
|
968
|
-
const option = state.options.all[index];
|
|
969
|
-
setAria(option, "selected", (selected ?? state.options.selected).includes(option));
|
|
970
|
-
}
|
|
971
|
-
const values = parameters.values ?? (state.autocomplete.none ? [state.filter.element.value] : (selected ?? state.options.selected).map((option) => getData(option, "value", false) ?? ""));
|
|
972
|
-
if (values.length === 0) values.push("");
|
|
973
|
-
if (selected != null) state.options.selected = selected;
|
|
974
|
-
if (!(values.length === state.values.current.length ? values.some((value) => !state.values.current.includes(value)) : true)) return;
|
|
975
|
-
updateComboboxForm(state, values, emit);
|
|
976
|
-
updateComboboxPreview("set", state, option);
|
|
977
|
-
updateComboboxSelection(state, option, added, selected);
|
|
978
|
-
}
|
|
979
|
-
function filterComboboxOption(state, value, textContent) {
|
|
980
|
-
const filterTextContent = textContent ?? false;
|
|
981
|
-
const filter = getString(value).toLocaleLowerCase();
|
|
982
|
-
filterComboboxOptions(state);
|
|
983
|
-
const { length } = state.options.all;
|
|
984
|
-
let match;
|
|
985
|
-
for (let index = 0; index < length; index += 1) {
|
|
986
|
-
const option = state.options.all[index];
|
|
987
|
-
if ((filterTextContent ? option.textContent : getData(option, "value", false) ?? "").toLocaleLowerCase() === filter) {
|
|
988
|
-
match = option;
|
|
989
|
-
break;
|
|
990
|
-
}
|
|
991
|
-
}
|
|
992
|
-
return match;
|
|
993
|
-
}
|
|
994
|
-
function filterComboboxOptions(state) {
|
|
995
|
-
function filter(options) {
|
|
996
|
-
const { length } = options;
|
|
997
|
-
let count = 0;
|
|
998
|
-
for (let index = 0; index < length; index += 1) {
|
|
999
|
-
const option = options[index];
|
|
1000
|
-
option.hidden = !state.autocomplete.none && !option.textContent.toLocaleLowerCase().includes(value);
|
|
1001
|
-
if (!option.hidden) {
|
|
1002
|
-
count += 1;
|
|
1003
|
-
visible.push(option);
|
|
1004
|
-
}
|
|
1005
|
-
}
|
|
1006
|
-
return count === 0;
|
|
1007
|
-
}
|
|
1008
|
-
const value = state.filter.element.value.toLocaleLowerCase();
|
|
1009
|
-
const visible = [];
|
|
1010
|
-
if (state.groups == null) filter(state.options.all);
|
|
1011
|
-
else {
|
|
1012
|
-
const groupsLength = state.groups.length;
|
|
1013
|
-
for (let groupIndex = 0; groupIndex < groupsLength; groupIndex += 1) {
|
|
1014
|
-
const { element, options } = state.groups[groupIndex];
|
|
1015
|
-
element.hidden = filter(options);
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
updateComboboxMessage(state, visible.length);
|
|
1019
|
-
return state.active != null && visible.includes(state.active) ? state.active : visible[0];
|
|
1020
|
-
}
|
|
1021
|
-
function setComboboxValue(state, value, emit) {
|
|
1022
|
-
if (state.autocomplete.none) {
|
|
1023
|
-
state.filter.element.value = getString(value);
|
|
1024
|
-
onComboboxFilter("insertText", state, true, emit);
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
if (filterComboboxOption(state, value) != null) setComboboxValues(state, [value], emit);
|
|
1028
|
-
}
|
|
1029
|
-
function setComboboxValues(state, value, emit) {
|
|
1030
|
-
if (!Array.isArray(value)) return;
|
|
1031
|
-
const values = (state.multiple ? value : value.slice(0, 1)).map((value) => getString(value).toLocaleLowerCase());
|
|
1032
|
-
if (values.length === 0) values.push("");
|
|
1033
|
-
if (state.autocomplete.none) {
|
|
1034
|
-
setComboboxValue(state, values[0], emit);
|
|
1035
|
-
return;
|
|
1036
|
-
}
|
|
1037
|
-
const { length } = state.options.all;
|
|
1038
|
-
const selected = [];
|
|
1039
|
-
for (let index = 0; index < length; index += 1) {
|
|
1040
|
-
const option = state.options.all[index];
|
|
1041
|
-
if (values.includes(getData(option, "value", false)?.toLocaleLowerCase() ?? "")) selected.push(option);
|
|
1042
|
-
}
|
|
1043
|
-
applyComboboxSelection({
|
|
1044
|
-
emit,
|
|
1045
|
-
selected,
|
|
1046
|
-
state,
|
|
1047
|
-
added: true,
|
|
1048
|
-
option: selected[0] ?? state.options.all[0],
|
|
1049
|
-
values: state.autocomplete.none ? values : void 0
|
|
1050
|
-
});
|
|
1051
|
-
}
|
|
1052
|
-
function toggleComboboxOption(state, option) {
|
|
1053
|
-
if (option == null || !state.multiple && state.options.selected.includes(option)) {
|
|
1054
|
-
state.floatable.hide();
|
|
1055
|
-
return;
|
|
1056
|
-
}
|
|
1057
|
-
const selected = state.options.selected.slice();
|
|
1058
|
-
let added = true;
|
|
1059
|
-
if (state.multiple) {
|
|
1060
|
-
const index = selected.indexOf(option);
|
|
1061
|
-
if (index === -1) selected.push(option);
|
|
1062
|
-
else {
|
|
1063
|
-
added = false;
|
|
1064
|
-
selected.splice(index, 1);
|
|
1065
|
-
}
|
|
1066
|
-
} else selected.splice(0, selected.length, option);
|
|
1067
|
-
applyComboboxSelection({
|
|
1068
|
-
added,
|
|
1069
|
-
option,
|
|
1070
|
-
selected,
|
|
1071
|
-
state,
|
|
1072
|
-
values: state.autocomplete.none && option != null ? [option.textContent] : void 0
|
|
1073
|
-
});
|
|
1074
|
-
if (!state.multiple) state.floatable.hide();
|
|
1075
|
-
}
|
|
1076
|
-
//#endregion
|
|
1077
|
-
export { onComboboxKeydown as $, updateComboboxTabindices as A, getComboboxBooleanAria as B, setComboboxRequired as C, updateComboboxPreview as D, updateComboboxMessage as E, setComboboxLocalizations as F, setComboboxLimit as G, getComboboxVisibleOptions as H, updateComboboxLocalizations as I, onComboboxEnter as J, onComboboxClickContent as K, generateComboboxIds as L, updateComboboxValidity as M, getComboboxLanguage as N, updateComboboxSelection as O, getComboboxLocalization as P, onComboboxInput as Q, getComboboxAttributeValue as R, setComboboxReadonly as S, updateComboboxAutocomplete as T, hasTextSelection as U, getComboboxId as V, setComboboxFilter as W, onComboboxFilter as X, onComboboxEscape as Y, onComboboxFocusout as Z, createComboboxOptions as _, setComboboxValues as a, attachCombobox as at, createComboboxSelectionItems as b, createComboboxButton as c, detachCombobox as ct, createComboboxGroup as d, onComboboxKeydownSelection as et, createComboboxHeader as f, createComboboxOption as g, createComboboxMessage as h, setComboboxValue as i, onComboboxTab as it, updateComboboxForm as j, updateComboboxSelectionItems as k, createComboboxContent as l, createComboboxList as m, filterComboboxOption as n, onComboboxRemoveSelection as nt, toggleComboboxOption as o, comboboxStates as ot, createComboboxInput as p, onComboboxClickDocument as q, filterComboboxOptions as r, onComboboxSelection as rt, createComboboxAnchor as s, createComboboxState as st, applyComboboxSelection as t, onComboboxNavigation as tt, createComboboxFilter as u, createComboboxSelection as v, updateComboboxAnchor as w, setComboboxDisabled as x, createComboboxSelectionItem as y, getComboboxAutoComplete as z };
|