@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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { getLocalizations, updateLocalizations } from "./l10n.mjs";
|
|
2
|
+
import { b as updateListboxValueLabel, c as createListboxContent, n as filterListbox, s as toggleListboxOption, t as applyListboxSelection, v as updateListboxMessage, x as generateListboxIds, y as updateListboxPreview } from "./listbox.data-BWd0PCy7.mjs";
|
|
3
|
+
import { initializeListboxState } from "./internal/listbox/listbox.state.mjs";
|
|
4
|
+
import { getBooleanAriaValue } from "./internal/misc.mjs";
|
|
5
|
+
import { processListboxKeydown } from "./internal/listbox/listbox.event.mjs";
|
|
6
|
+
import { LISTBOX_ATTRIBUTES, LISTBOX_MESSAGES, LISTBOX_SELECTORS, LISTBOX_TAGNAME } from "./listbox/constants.listbox.mjs";
|
|
7
|
+
import { on } from "@oscarpalmer/toretto/event";
|
|
8
|
+
import { setAttribute, setAttributes } from "@oscarpalmer/toretto/attribute";
|
|
9
|
+
import { findAncestor } from "@oscarpalmer/toretto/find";
|
|
10
|
+
import { getData } from "@oscarpalmer/toretto/data";
|
|
11
|
+
import { noop } from "@oscarpalmer/atoms/function";
|
|
12
|
+
//#region src/js/listbox/event.listbox.ts
|
|
13
|
+
function onListboxClick(event) {
|
|
14
|
+
const listbox = findAncestor(event, LISTBOX_TAGNAME);
|
|
15
|
+
const state = listboxStates.map.get(listbox);
|
|
16
|
+
if (listbox == null || state == null) return;
|
|
17
|
+
const option = findAncestor(event, LISTBOX_SELECTORS.option);
|
|
18
|
+
if (option == null) return;
|
|
19
|
+
if (state.disabled) state.filter.element.focus();
|
|
20
|
+
else if (state.readonly) updateListboxPreview("none", state, option, false);
|
|
21
|
+
else toggleListboxOption(state, option);
|
|
22
|
+
}
|
|
23
|
+
function onListboxEnter(state) {
|
|
24
|
+
if (!state.disabled && !state.readonly) toggleListboxOption(state, state.active);
|
|
25
|
+
}
|
|
26
|
+
function onListboxFilter(preview, state, _, active) {
|
|
27
|
+
updateListboxPreview(preview, state, active);
|
|
28
|
+
}
|
|
29
|
+
function onListboxInput(event) {
|
|
30
|
+
const listbox = findAncestor(event, LISTBOX_TAGNAME);
|
|
31
|
+
const state = listboxStates.map.get(listbox);
|
|
32
|
+
if (state?.filter.element === event.target) filterListbox(event.inputType, state);
|
|
33
|
+
}
|
|
34
|
+
function onListboxKeydown(event) {
|
|
35
|
+
const listbox = findAncestor(event, LISTBOX_TAGNAME);
|
|
36
|
+
const state = listboxStates.map.get(listbox);
|
|
37
|
+
if (state?.filter.element === event.target) processListboxKeydown(event, state);
|
|
38
|
+
}
|
|
39
|
+
function onListboxNavigation(preview, state, active) {
|
|
40
|
+
if (state.multiple || state.readonly) updateListboxPreview(preview, state, active);
|
|
41
|
+
else applyListboxSelection({
|
|
42
|
+
state,
|
|
43
|
+
option: active,
|
|
44
|
+
added: true,
|
|
45
|
+
selected: active == null ? [] : [active]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function onListboxToggle(state) {
|
|
49
|
+
state.filter.element.focus();
|
|
50
|
+
}
|
|
51
|
+
on(document, "click", onListboxClick);
|
|
52
|
+
on(document, "input", onListboxInput);
|
|
53
|
+
on(document, "keydown", onListboxKeydown, { passive: false });
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/js/listbox/state.listbox.ts
|
|
56
|
+
function createListboxState(element, internals) {
|
|
57
|
+
const select = element.querySelector("select");
|
|
58
|
+
if (select == null) throw new Error(LISTBOX_MESSAGES.select);
|
|
59
|
+
const label = element.querySelector(`label[for="${select.id}"]`);
|
|
60
|
+
if (label == null) throw new Error(LISTBOX_MESSAGES.label);
|
|
61
|
+
element.id = select.id;
|
|
62
|
+
setAttribute(element, "name", select.name === "" ? select.id : select.name);
|
|
63
|
+
const disabled = getBooleanAriaValue(element, "disabled") || select.disabled;
|
|
64
|
+
const multiple = element.multiple || select.multiple;
|
|
65
|
+
const readonly = getBooleanAriaValue(element, "readonly");
|
|
66
|
+
const required = getBooleanAriaValue(element, "required") || select.required;
|
|
67
|
+
setAttributes(element, {
|
|
68
|
+
autocomplete: "none",
|
|
69
|
+
name: select.name === "" ? select.id : select.name
|
|
70
|
+
});
|
|
71
|
+
const id = getListboxId();
|
|
72
|
+
const labelId = label.id;
|
|
73
|
+
label.htmlFor = id;
|
|
74
|
+
label.id = `${id}_label`;
|
|
75
|
+
generateListboxIds(id, select);
|
|
76
|
+
const type = "select";
|
|
77
|
+
const content = createListboxContent({
|
|
78
|
+
element,
|
|
79
|
+
id,
|
|
80
|
+
label,
|
|
81
|
+
multiple,
|
|
82
|
+
select,
|
|
83
|
+
type,
|
|
84
|
+
attributes: LISTBOX_ATTRIBUTES,
|
|
85
|
+
autocomplete: listboxAutoComplete,
|
|
86
|
+
listbox: true
|
|
87
|
+
});
|
|
88
|
+
const { selected } = content.list.options;
|
|
89
|
+
const values = selected.length === 0 ? [""] : selected.map((option) => getData(option, "value", false) ?? "");
|
|
90
|
+
const state = {
|
|
91
|
+
disabled,
|
|
92
|
+
id,
|
|
93
|
+
internals,
|
|
94
|
+
message: content.message,
|
|
95
|
+
multiple,
|
|
96
|
+
readonly,
|
|
97
|
+
required,
|
|
98
|
+
select,
|
|
99
|
+
type,
|
|
100
|
+
active: selected[0],
|
|
101
|
+
attributes: LISTBOX_ATTRIBUTES,
|
|
102
|
+
autocomplete: listboxAutoComplete,
|
|
103
|
+
callbacks: listboxCallbacks,
|
|
104
|
+
content: content.element,
|
|
105
|
+
elements: [element, content.filter.element],
|
|
106
|
+
filter: {
|
|
107
|
+
element: content.filter.element,
|
|
108
|
+
value: ""
|
|
109
|
+
},
|
|
110
|
+
host: element,
|
|
111
|
+
label: {
|
|
112
|
+
id: labelId,
|
|
113
|
+
element: label
|
|
114
|
+
},
|
|
115
|
+
list: content.list.element,
|
|
116
|
+
listeners: [],
|
|
117
|
+
listbox: true,
|
|
118
|
+
localizations: {
|
|
119
|
+
component: {},
|
|
120
|
+
global: listboxLocalizations
|
|
121
|
+
},
|
|
122
|
+
name: element.name,
|
|
123
|
+
options: content.list.options,
|
|
124
|
+
values: {
|
|
125
|
+
current: values.slice(),
|
|
126
|
+
initial: values.slice(),
|
|
127
|
+
label: content.value
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
return initializeListboxState(state, listboxStates, onAttachListbox);
|
|
131
|
+
}
|
|
132
|
+
function onAttachListbox(state) {
|
|
133
|
+
const { content, select } = state;
|
|
134
|
+
select.hidden = true;
|
|
135
|
+
select.insertAdjacentElement("afterend", content);
|
|
136
|
+
select.remove();
|
|
137
|
+
}
|
|
138
|
+
function onDetachListbox(state) {
|
|
139
|
+
const { content, select } = state;
|
|
140
|
+
content.insertAdjacentElement("beforebegin", select);
|
|
141
|
+
content.remove();
|
|
142
|
+
select.hidden = false;
|
|
143
|
+
}
|
|
144
|
+
const listboxAutoComplete = {
|
|
145
|
+
both: false,
|
|
146
|
+
inline: false,
|
|
147
|
+
list: false,
|
|
148
|
+
none: true
|
|
149
|
+
};
|
|
150
|
+
const listboxCallbacks = {
|
|
151
|
+
data: {
|
|
152
|
+
onApply: noop,
|
|
153
|
+
onFilter: onListboxFilter,
|
|
154
|
+
onForm: noop,
|
|
155
|
+
onPreview: noop,
|
|
156
|
+
onToggle: onListboxToggle
|
|
157
|
+
},
|
|
158
|
+
keydown: {
|
|
159
|
+
onEnter: onListboxEnter,
|
|
160
|
+
onNavigation: onListboxNavigation,
|
|
161
|
+
onToggle: onListboxToggle
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
const listboxLocalizations = getLocalizations({
|
|
165
|
+
empty: "No results found",
|
|
166
|
+
input: "",
|
|
167
|
+
invalid: "Please select an option from the list",
|
|
168
|
+
max: (max) => `Please select fewer options (maximum is ${max})`,
|
|
169
|
+
min: (min) => `Please select more options (minimum is ${min})`,
|
|
170
|
+
remove: "",
|
|
171
|
+
results: (amount) => `${amount} results available`,
|
|
172
|
+
selected: (amount) => `${amount} selected`
|
|
173
|
+
});
|
|
174
|
+
const listboxStates = {
|
|
175
|
+
map: /* @__PURE__ */ new WeakMap(),
|
|
176
|
+
set: /* @__PURE__ */ new Set()
|
|
177
|
+
};
|
|
178
|
+
//#endregion
|
|
179
|
+
//#region src/js/listbox/misc.listbox.ts
|
|
180
|
+
function getListboxId() {
|
|
181
|
+
index += 1;
|
|
182
|
+
return `oui_listbox_${index}`;
|
|
183
|
+
}
|
|
184
|
+
function setListboxLocalizations(first, second) {
|
|
185
|
+
updateListboxLocalizations(listboxLocalizations, listboxLocalizations.values, first, second);
|
|
186
|
+
}
|
|
187
|
+
function updateListboxLocalizations(instance, destination, first, second) {
|
|
188
|
+
updateLocalizations(instance, typeof first === "string" ? { [first]: second } : first, destination, () => {
|
|
189
|
+
for (const state of listboxStates.set) {
|
|
190
|
+
updateListboxMessage(state);
|
|
191
|
+
updateListboxValueLabel(state);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
let index = 0;
|
|
196
|
+
//#endregion
|
|
197
|
+
export { listboxLocalizations as a, onDetachListbox as c, onListboxNavigation as d, onListboxToggle as f, createListboxState as i, onListboxEnter as l, setListboxLocalizations as n, listboxStates as o, updateListboxLocalizations as r, onAttachListbox as s, getListboxId as t, onListboxFilter as u };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { t as OuiLocalizations } from "./l10n-Bn-7RcwR.mjs";
|
|
2
|
+
import { o as OuiListboxLocalizations } from "./listbox.model-CRReGOmu.mjs";
|
|
3
|
+
//#region src/js/listbox/misc.listbox.d.ts
|
|
4
|
+
declare function getListboxId(): string;
|
|
5
|
+
/**
|
|
6
|
+
* Set listbox localizations for a specific language
|
|
7
|
+
*
|
|
8
|
+
* @param language Language code
|
|
9
|
+
* @param localizations Localizations for language
|
|
10
|
+
*/
|
|
11
|
+
declare function setListboxLocalizations(language: string, localizations: Partial<OuiListboxLocalizations>): void;
|
|
12
|
+
/**
|
|
13
|
+
* Set listbox localizations for one or more languages
|
|
14
|
+
*
|
|
15
|
+
* @param localizations Localizations for one or more languages
|
|
16
|
+
*/
|
|
17
|
+
declare function setListboxLocalizations(localizations: Record<string, Partial<OuiListboxLocalizations>>): void;
|
|
18
|
+
declare function updateListboxLocalizations(instance: OuiLocalizations<OuiListboxLocalizations>, destination: Record<string, Partial<OuiListboxLocalizations>>, first: string | Record<string, Partial<OuiListboxLocalizations>>, second?: Partial<OuiListboxLocalizations>): void;
|
|
19
|
+
//#endregion
|
|
20
|
+
export { setListboxLocalizations as n, updateListboxLocalizations as r, getListboxId as t };
|
|
@@ -98,8 +98,10 @@ function onEnd(event, globals, item, reset) {
|
|
|
98
98
|
};
|
|
99
99
|
const target = getElementFromPosition(position)[0];
|
|
100
100
|
let element = findAncestor(target, SELECTOR_ITEM);
|
|
101
|
-
if (element?.hasAttribute(ATTRIBUTE_PLACEHOLDER_POSITION))
|
|
102
|
-
|
|
101
|
+
if (element?.hasAttribute(ATTRIBUTE_PLACEHOLDER_POSITION)) {
|
|
102
|
+
if (getAttribute(element, ATTRIBUTE_PLACEHOLDER_POSITION) === "after") element = element.previousElementSibling;
|
|
103
|
+
else element = element.nextElementSibling;
|
|
104
|
+
}
|
|
103
105
|
let insert = "insert";
|
|
104
106
|
let toInstance;
|
|
105
107
|
let toState;
|
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
import { createEmbeddedFloatable } from "./floatable/floatable.embedded.mjs";
|
|
2
|
+
import { getLocalizations, updateLocalizations } from "./l10n.mjs";
|
|
3
|
+
import { LISTBOX_KEYS, LISTBOX_TYPES } from "./internal/listbox/listbox.constants.mjs";
|
|
4
|
+
import { b as updateListboxValueLabel, i as filterListboxOptions, n as filterListbox, r as filterListboxOption, s as toggleListboxOption, t as applyListboxSelection, v as updateListboxMessage, x as generateListboxIds, y as updateListboxPreview } from "./listbox.data-BWd0PCy7.mjs";
|
|
5
|
+
import { initializeListboxState } from "./internal/listbox/listbox.state.mjs";
|
|
6
|
+
import { COMBOBOX_ATTRIBUTES, COMBOBOX_ATTRIBUTE_SELECTION_ITEM, COMBOBOX_AUTOCOMPLETES, COMBOBOX_MESSAGES, COMBOBOX_SELECTORS, COMBOBOX_TAGNAME } from "./combobox/constants.combobox.mjs";
|
|
7
|
+
import { getAttributeValue, getBooleanAriaValue } from "./internal/misc.mjs";
|
|
8
|
+
import { createComboboxAnchor, createComboboxContent, updateComboboxAutocomplete, updateComboboxSelection, updateComboboxSelectionItems, updateComboboxTabindices } from "./combobox/dom.combobox.mjs";
|
|
9
|
+
import { processListboxKeydown } from "./internal/listbox/listbox.event.mjs";
|
|
10
|
+
import { setAria } from "@oscarpalmer/toretto/aria";
|
|
11
|
+
import { on } from "@oscarpalmer/toretto/event";
|
|
12
|
+
import { setAttributes } from "@oscarpalmer/toretto/attribute";
|
|
13
|
+
import { clamp } from "@oscarpalmer/atoms/number";
|
|
14
|
+
import { getData } from "@oscarpalmer/toretto/data";
|
|
15
|
+
import { findAncestor, getData as getData$1 } from "@oscarpalmer/toretto";
|
|
16
|
+
import { noop } from "@oscarpalmer/atoms/function";
|
|
17
|
+
//#region src/js/combobox/misc.combobox.ts
|
|
18
|
+
function getComboboxAutoComplete(element, select) {
|
|
19
|
+
let value;
|
|
20
|
+
if (select) value = "list";
|
|
21
|
+
else value = getAttributeValue(element, "autocomplete", COMBOBOX_AUTOCOMPLETES, "none");
|
|
22
|
+
return {
|
|
23
|
+
value,
|
|
24
|
+
both: value === "both",
|
|
25
|
+
inline: value === "inline",
|
|
26
|
+
list: value === "list",
|
|
27
|
+
none: value === "none"
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function getComboboxId() {
|
|
31
|
+
index += 1;
|
|
32
|
+
return `oui_combobox_${index}`;
|
|
33
|
+
}
|
|
34
|
+
function hasTextSelection(element) {
|
|
35
|
+
return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
36
|
+
}
|
|
37
|
+
function setComboboxLocalizations(first, second) {
|
|
38
|
+
updateComboboxLocalizations(comboboxLocalizations, comboboxLocalizations.values, first, second);
|
|
39
|
+
}
|
|
40
|
+
function updateComboboxLocalizations(instance, destination, first, second) {
|
|
41
|
+
updateLocalizations(instance, typeof first === "string" ? { [first]: second } : first, destination, () => {
|
|
42
|
+
for (const state of comboboxStates.set) {
|
|
43
|
+
updateListboxMessage(state);
|
|
44
|
+
updateListboxValueLabel(state);
|
|
45
|
+
updateComboboxSelectionItems(state);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
let index = 0;
|
|
50
|
+
//#endregion
|
|
51
|
+
//#region src/js/combobox/event.combobox.ts
|
|
52
|
+
function onComboboxActivation(state) {
|
|
53
|
+
state.floatable.show();
|
|
54
|
+
}
|
|
55
|
+
function onComboboxApply(state, option, _, added, selected) {
|
|
56
|
+
updateComboboxSelection(state, option, added, selected);
|
|
57
|
+
}
|
|
58
|
+
function onComboboxClickContent(event, state) {
|
|
59
|
+
if (state == null) return;
|
|
60
|
+
if (state.disabled) return;
|
|
61
|
+
const option = findAncestor(event, COMBOBOX_SELECTORS.option);
|
|
62
|
+
if (!(option instanceof HTMLElement)) state.filter.element.focus();
|
|
63
|
+
else if (state.readonly) onComboboxPreview("set", state, option);
|
|
64
|
+
else toggleListboxOption(state, option);
|
|
65
|
+
}
|
|
66
|
+
function onComboboxClickDocument(event) {
|
|
67
|
+
const combobox = findAncestor(event, COMBOBOX_TAGNAME);
|
|
68
|
+
const state = comboboxStates.map.get(combobox);
|
|
69
|
+
if (combobox == null || state == null) return;
|
|
70
|
+
if (findAncestor(event, COMBOBOX_SELECTORS.content) != null) {
|
|
71
|
+
onComboboxClickContent(event, state);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const label = findAncestor(event, "label");
|
|
75
|
+
if (label != null) {
|
|
76
|
+
if (label === state.label.element && combobox.type === "select") {
|
|
77
|
+
state.anchor.element.focus();
|
|
78
|
+
if (!state.disabled) state.floatable.show();
|
|
79
|
+
}
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const selection = findAncestor(event, `[${COMBOBOX_ATTRIBUTE_SELECTION_ITEM}]`);
|
|
83
|
+
if (selection != null) {
|
|
84
|
+
const option = state.options.selected.find((option) => option.id === getData$1(selection, "option", false));
|
|
85
|
+
if (state.disabled) state.anchor.element.focus();
|
|
86
|
+
else if (state.readonly) updateComboboxTabindices(state, option);
|
|
87
|
+
else toggleListboxOption(state, option);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (findAncestor(event, COMBOBOX_SELECTORS.anchor) != null && !state.disabled) {
|
|
91
|
+
state.floatable.show();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function onComboboxEnter(state) {
|
|
96
|
+
if (state.disabled || state.readonly) return;
|
|
97
|
+
if (hasTextSelection(state.anchor.element)) onComboboxSelection(state, true);
|
|
98
|
+
else toggleListboxOption(state, state.active);
|
|
99
|
+
}
|
|
100
|
+
function onComboboxEscape(state) {
|
|
101
|
+
state.floatable.hide();
|
|
102
|
+
}
|
|
103
|
+
function onComboboxFilter(preview, state, update, active, emit) {
|
|
104
|
+
if (state.autocomplete.none) applyListboxSelection({
|
|
105
|
+
state,
|
|
106
|
+
added: false,
|
|
107
|
+
emit: (emit ?? true) && update
|
|
108
|
+
});
|
|
109
|
+
else updateListboxPreview(preview, state, active);
|
|
110
|
+
}
|
|
111
|
+
function onComboboxFocusout(event) {
|
|
112
|
+
setTimeout(() => {
|
|
113
|
+
const combobox = findAncestor(event, COMBOBOX_TAGNAME);
|
|
114
|
+
const state = comboboxStates.map.get(combobox);
|
|
115
|
+
if (combobox == null || state == null || state.disabled || state.readonly || state.type === "select" || state.host === document.activeElement || state.host.contains(document.activeElement)) return;
|
|
116
|
+
const option = filterListboxOption(state, state.filter.element.value, true);
|
|
117
|
+
applyListboxSelection({
|
|
118
|
+
option,
|
|
119
|
+
state,
|
|
120
|
+
added: false,
|
|
121
|
+
selected: option == null ? [] : [option]
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
function onComboboxInput(event) {
|
|
126
|
+
const combobox = findAncestor(event, COMBOBOX_TAGNAME);
|
|
127
|
+
const state = comboboxStates.map.get(combobox);
|
|
128
|
+
if (combobox != null && state != null && event.target === state.filter.element) filterListbox(event.inputType, state);
|
|
129
|
+
}
|
|
130
|
+
function onComboboxKeydown(event) {
|
|
131
|
+
const combobox = findAncestor(event, COMBOBOX_TAGNAME);
|
|
132
|
+
const state = comboboxStates.map.get(combobox);
|
|
133
|
+
if (combobox == null || state == null) return;
|
|
134
|
+
const item = findAncestor(event, COMBOBOX_SELECTORS.selectionItem);
|
|
135
|
+
if (item instanceof HTMLElement) onComboboxKeydownSelection(event, state, item);
|
|
136
|
+
else processListboxKeydown(event, state);
|
|
137
|
+
}
|
|
138
|
+
function onComboboxKeydownSelection(event, state, item) {
|
|
139
|
+
if (LISTBOX_KEYS.activation.has(event.key)) {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
onComboboxRemoveSelection(state, item);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (state.disabled || state.selection == null) return;
|
|
145
|
+
const { length } = state.selection.items;
|
|
146
|
+
const index = state.selection.items.findIndex((selection) => selection.button === item);
|
|
147
|
+
let next;
|
|
148
|
+
switch (event.key) {
|
|
149
|
+
case "ArrowDown":
|
|
150
|
+
state.anchor.element.focus();
|
|
151
|
+
state.anchor.element.click();
|
|
152
|
+
return;
|
|
153
|
+
case "ArrowRight":
|
|
154
|
+
case "ArrowLeft":
|
|
155
|
+
next = clamp(index + (event.key === "ArrowRight" ? 1 : -1), 0, length - 1, true);
|
|
156
|
+
break;
|
|
157
|
+
case "End":
|
|
158
|
+
next = length - 1;
|
|
159
|
+
break;
|
|
160
|
+
case "Home": next = 0;
|
|
161
|
+
}
|
|
162
|
+
if (next == null) return;
|
|
163
|
+
event.preventDefault();
|
|
164
|
+
for (let index = 0; index < length; index += 1) {
|
|
165
|
+
const { button } = state.selection.items[index];
|
|
166
|
+
const selected = index === next;
|
|
167
|
+
button.tabIndex = selected ? 0 : -1;
|
|
168
|
+
if (selected) button.focus();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function onComboboxPreview(preview, state, active) {
|
|
172
|
+
updateComboboxAutocomplete(preview, state, active);
|
|
173
|
+
}
|
|
174
|
+
function onComboboxRemoveSelection(state, item) {
|
|
175
|
+
if (state.disabled || state.readonly) return;
|
|
176
|
+
const option = state.options.selected.find((option) => option.id === getData$1(item, "option", false));
|
|
177
|
+
if (option != null) toggleListboxOption(state, option);
|
|
178
|
+
}
|
|
179
|
+
function onComboboxNavigation(preview, state, active) {
|
|
180
|
+
updateListboxPreview(preview, state, active);
|
|
181
|
+
}
|
|
182
|
+
function onComboboxSelection(state, hasSelection) {
|
|
183
|
+
if (!(hasSelection ?? hasTextSelection(state.anchor.element))) return;
|
|
184
|
+
state.filter.value = state.anchor.element === state.filter.element ? state.filter.element.value : "";
|
|
185
|
+
toggleListboxOption(state, filterListboxOptions(state) ?? state.active);
|
|
186
|
+
}
|
|
187
|
+
function onComboboxTab(state) {
|
|
188
|
+
setTimeout(() => {
|
|
189
|
+
if (state.host === document.activeElement) return;
|
|
190
|
+
if (state.selection?.container.contains(document.activeElement) || !state.host.contains(document.activeElement)) state.floatable.hide();
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
function onComboboxToggle(state) {
|
|
194
|
+
if (!state.multiple) state.floatable.hide();
|
|
195
|
+
}
|
|
196
|
+
function preventComboboxActivation(state) {
|
|
197
|
+
return state.floatable.open;
|
|
198
|
+
}
|
|
199
|
+
function skipComboboxNavigation(state) {
|
|
200
|
+
if (state.floatable.open) return false;
|
|
201
|
+
state.floatable.show();
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
on(document, "click", onComboboxClickDocument);
|
|
205
|
+
on(document, "focusout", onComboboxFocusout);
|
|
206
|
+
on(document, "input", onComboboxInput, { passive: false });
|
|
207
|
+
on(document, "keydown", onComboboxKeydown, { passive: false });
|
|
208
|
+
//#endregion
|
|
209
|
+
//#region src/js/combobox/state.combobox.ts
|
|
210
|
+
function createComboboxState(element, internals) {
|
|
211
|
+
const select = element.querySelector("select");
|
|
212
|
+
if (select == null || select.id === "") throw new Error(COMBOBOX_MESSAGES.select);
|
|
213
|
+
const label = element.querySelector(`label[for="${select.id}"]`);
|
|
214
|
+
if (label == null) throw new Error(COMBOBOX_MESSAGES.label);
|
|
215
|
+
element.id = select.id;
|
|
216
|
+
const disabled = getBooleanAriaValue(element, "disabled") || select.disabled;
|
|
217
|
+
const multiple = element.multiple || select.multiple;
|
|
218
|
+
const readonly = getBooleanAriaValue(element, "readonly");
|
|
219
|
+
const required = getBooleanAriaValue(element, "required") || select.required;
|
|
220
|
+
const id = getComboboxId();
|
|
221
|
+
const type = multiple ? "select" : getAttributeValue(element, "type", LISTBOX_TYPES, "select");
|
|
222
|
+
const autocomplete = getComboboxAutoComplete(element, type === "select");
|
|
223
|
+
setAttributes(element, {
|
|
224
|
+
autocomplete: autocomplete.value,
|
|
225
|
+
name: select.name === "" ? select.id : select.name
|
|
226
|
+
});
|
|
227
|
+
const labelId = label.id;
|
|
228
|
+
label.htmlFor = id;
|
|
229
|
+
label.id = `${id}_label`;
|
|
230
|
+
generateListboxIds(id, select);
|
|
231
|
+
const parameters = {
|
|
232
|
+
autocomplete,
|
|
233
|
+
element,
|
|
234
|
+
id,
|
|
235
|
+
label,
|
|
236
|
+
multiple,
|
|
237
|
+
select,
|
|
238
|
+
type,
|
|
239
|
+
attributes: COMBOBOX_ATTRIBUTES,
|
|
240
|
+
listbox: false
|
|
241
|
+
};
|
|
242
|
+
const anchor = createComboboxAnchor(parameters);
|
|
243
|
+
const content = createComboboxContent(parameters);
|
|
244
|
+
const { selected } = content.list.options;
|
|
245
|
+
const values = selected.length === 0 ? [""] : selected.map((option) => getData(option, "value", false) ?? "");
|
|
246
|
+
const state = {
|
|
247
|
+
autocomplete,
|
|
248
|
+
disabled,
|
|
249
|
+
id,
|
|
250
|
+
internals,
|
|
251
|
+
multiple,
|
|
252
|
+
readonly,
|
|
253
|
+
required,
|
|
254
|
+
select,
|
|
255
|
+
type,
|
|
256
|
+
active: selected[0],
|
|
257
|
+
anchor: {
|
|
258
|
+
container: anchor.container,
|
|
259
|
+
element: anchor.element
|
|
260
|
+
},
|
|
261
|
+
attributes: COMBOBOX_ATTRIBUTES,
|
|
262
|
+
callbacks: comboboxCallbacks,
|
|
263
|
+
content: content.element,
|
|
264
|
+
elements: [
|
|
265
|
+
element,
|
|
266
|
+
anchor.element,
|
|
267
|
+
content.filter?.element
|
|
268
|
+
],
|
|
269
|
+
filter: {
|
|
270
|
+
element: content.filter?.element ?? anchor.element,
|
|
271
|
+
value: ""
|
|
272
|
+
},
|
|
273
|
+
floatable: void 0,
|
|
274
|
+
groups: content.list.groups,
|
|
275
|
+
host: element,
|
|
276
|
+
label: {
|
|
277
|
+
element: label,
|
|
278
|
+
id: labelId
|
|
279
|
+
},
|
|
280
|
+
list: content.list.element,
|
|
281
|
+
listbox: false,
|
|
282
|
+
listeners: [],
|
|
283
|
+
localizations: {
|
|
284
|
+
component: {},
|
|
285
|
+
global: comboboxLocalizations
|
|
286
|
+
},
|
|
287
|
+
message: content.message,
|
|
288
|
+
name: element.name,
|
|
289
|
+
options: {
|
|
290
|
+
selected,
|
|
291
|
+
all: content.list.options.all
|
|
292
|
+
},
|
|
293
|
+
selection: anchor.selection,
|
|
294
|
+
values: {
|
|
295
|
+
current: values.slice(),
|
|
296
|
+
initial: values.slice(),
|
|
297
|
+
label: content.value
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
return initializeListboxState(state, comboboxStates, onAttachCombobox);
|
|
301
|
+
}
|
|
302
|
+
function onAttachCombobox(state) {
|
|
303
|
+
const { anchor, content, filter, select } = state;
|
|
304
|
+
select.hidden = true;
|
|
305
|
+
select.insertAdjacentElement("afterend", anchor.container ?? anchor.element);
|
|
306
|
+
(anchor.container ?? anchor.element).insertAdjacentElement("afterend", content);
|
|
307
|
+
select.remove();
|
|
308
|
+
state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
|
|
309
|
+
position: "below-start",
|
|
310
|
+
reusable: false
|
|
311
|
+
});
|
|
312
|
+
state.floatable.update();
|
|
313
|
+
state.listeners.push(on(content, "toggle", () => {
|
|
314
|
+
setAria(anchor.element, "expanded", state.floatable.open);
|
|
315
|
+
setAria(filter.element, "activedescendant", state.floatable.open ? state.active?.id : void 0);
|
|
316
|
+
if (state.floatable.open) filter.element.focus();
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
function onDetachCombobox(state) {
|
|
320
|
+
const { content, floatable, label, select } = state;
|
|
321
|
+
floatable.destroy();
|
|
322
|
+
const anchor = state.anchor.container ?? state.anchor.element;
|
|
323
|
+
anchor.insertAdjacentElement("beforebegin", select);
|
|
324
|
+
anchor.remove();
|
|
325
|
+
content.remove();
|
|
326
|
+
label.element.htmlFor = select.id;
|
|
327
|
+
label.element.id = state.label.id;
|
|
328
|
+
select.hidden = false;
|
|
329
|
+
}
|
|
330
|
+
const comboboxCallbacks = {
|
|
331
|
+
data: {
|
|
332
|
+
onApply: onComboboxApply,
|
|
333
|
+
onFilter: onComboboxFilter,
|
|
334
|
+
onForm: noop,
|
|
335
|
+
onPreview: onComboboxPreview,
|
|
336
|
+
onToggle: onComboboxToggle
|
|
337
|
+
},
|
|
338
|
+
keydown: {
|
|
339
|
+
onActivation: onComboboxActivation,
|
|
340
|
+
onEnter: onComboboxEnter,
|
|
341
|
+
onEscape: onComboboxEscape,
|
|
342
|
+
onNavigation: onComboboxNavigation,
|
|
343
|
+
onSelection: onComboboxSelection,
|
|
344
|
+
onTab: onComboboxTab,
|
|
345
|
+
onToggle: onComboboxToggle,
|
|
346
|
+
preventActivation: preventComboboxActivation,
|
|
347
|
+
skipNavigation: skipComboboxNavigation
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
const comboboxLocalizations = getLocalizations({
|
|
351
|
+
empty: "No results found",
|
|
352
|
+
input: "Please enter a value",
|
|
353
|
+
invalid: "Please select an option from the list",
|
|
354
|
+
max: (max) => `Please select fewer options (maximum is ${max})`,
|
|
355
|
+
min: (min) => `Please select more options (minimum is ${min})`,
|
|
356
|
+
remove: (label) => `Remove ${label}`,
|
|
357
|
+
results: (amount) => `${amount} results available`,
|
|
358
|
+
selected: (amount) => `${amount} selected`
|
|
359
|
+
});
|
|
360
|
+
const comboboxStates = {
|
|
361
|
+
map: /* @__PURE__ */ new WeakMap(),
|
|
362
|
+
set: /* @__PURE__ */ new Set()
|
|
363
|
+
};
|
|
364
|
+
//#endregion
|
|
365
|
+
export { skipComboboxNavigation as C, setComboboxLocalizations as D, hasTextSelection as E, updateComboboxLocalizations as O, preventComboboxActivation as S, getComboboxId as T, onComboboxPreview as _, onDetachCombobox as a, onComboboxTab as b, onComboboxClickContent as c, onComboboxFilter as d, onComboboxFocusout as f, onComboboxNavigation as g, onComboboxKeydownSelection as h, onAttachCombobox as i, onComboboxEnter as l, onComboboxKeydown as m, comboboxStates as n, onComboboxActivation as o, onComboboxInput as p, createComboboxState as r, onComboboxApply as s, comboboxLocalizations as t, onComboboxEscape as u, onComboboxRemoveSelection as v, getComboboxAutoComplete as w, onComboboxToggle as x, onComboboxSelection as y };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oscarpalmer/oui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Styles, components, and utilities for building websites and applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"css",
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
"types": "./dist/js/focus-trap/focus-trap.standalone.d.mts",
|
|
50
50
|
"default": "./dist/js/focus-trap/focus-trap.standalone.mjs"
|
|
51
51
|
},
|
|
52
|
+
"./listbox": {
|
|
53
|
+
"types": "./dist/js/listbox/index.d.mts",
|
|
54
|
+
"default": "./dist/js/listbox/index.mjs"
|
|
55
|
+
},
|
|
52
56
|
"./movable": {
|
|
53
57
|
"types": "./dist/js/movable/movable.standalone.d.mts",
|
|
54
58
|
"default": "./dist/js/movable/movable.standalone.mjs"
|
|
@@ -95,10 +99,10 @@
|
|
|
95
99
|
},
|
|
96
100
|
"dependencies": {
|
|
97
101
|
"@oscarpalmer/atoms": "^0.188.1",
|
|
98
|
-
"@oscarpalmer/toretto": "^0.
|
|
102
|
+
"@oscarpalmer/toretto": "^0.51"
|
|
99
103
|
},
|
|
100
104
|
"devDependencies": {
|
|
101
|
-
"@oxlint/plugins": "^1.
|
|
105
|
+
"@oxlint/plugins": "^1.78",
|
|
102
106
|
"@vitest/coverage-istanbul": "^4.1",
|
|
103
107
|
"autoprefixer": "^10.5",
|
|
104
108
|
"concurrently": "^10",
|