@oscarpalmer/oui 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/combobox.css +93 -42
- package/dist/js/combobox/constants.combobox.d.mts +2 -0
- package/dist/js/combobox/constants.combobox.mjs +46 -0
- package/dist/js/combobox/data.combobox.d.mts +18 -0
- package/dist/js/combobox/data.combobox.mjs +2 -0
- package/dist/js/combobox/dom.combobox.d.mts +110 -0
- package/dist/js/combobox/dom.combobox.mjs +2 -0
- package/dist/js/combobox/event.combobox.d.mts +17 -0
- package/dist/js/combobox/event.combobox.mjs +2 -0
- package/dist/js/combobox/form.combobox.d.mts +6 -0
- package/dist/js/combobox/form.combobox.mjs +2 -0
- package/dist/js/combobox/i18n.combobox.d.mts +2 -0
- package/dist/js/combobox/i18n.combobox.mjs +2 -0
- package/dist/js/combobox/index.d.mts +2 -0
- package/dist/js/combobox/index.mjs +194 -0
- package/dist/js/combobox/misc.combobox.d.mts +14 -0
- package/dist/js/combobox/misc.combobox.mjs +2 -0
- package/dist/js/combobox/models.combobox.d.mts +2 -0
- package/dist/js/combobox/models.combobox.mjs +1 -0
- package/dist/js/combobox/state.combobox.d.mts +11 -0
- package/dist/js/combobox/state.combobox.mjs +2 -0
- package/dist/js/constants.combobox-DqNUGBLS.d.mts +243 -0
- package/dist/js/data.combobox-8RwBv5ey.mjs +1077 -0
- package/dist/js/dialog.mjs +3 -2
- package/dist/js/floatable/floatable.embedded.mjs +4 -4
- package/dist/js/focus-trap/focus-trap.embedded.mjs +3 -3
- package/dist/js/index.d.mts +3 -3
- package/dist/js/index.mjs +3 -2
- package/dist/js/internal/draggable.mjs +6 -6
- package/dist/js/internal/navigable.mjs +3 -3
- package/dist/js/menu.mjs +3 -2
- package/dist/js/movable/movable.embedded.mjs +3 -3
- package/dist/js/popover.mjs +1 -1
- package/dist/js/resizable/resizable.embedded.mjs +7 -7
- package/dist/js/sortable/sortable.embedded.mjs +7 -6
- package/dist/js/splitter.mjs +7 -13
- package/dist/js/tooltip.mjs +3 -3
- package/package.json +6 -6
- package/src/css/combobox.scss +98 -31
- package/src/js/combobox/constants.combobox.ts +62 -0
- package/src/js/combobox/data.combobox.ts +236 -0
- package/src/js/combobox/dom.combobox.ts +794 -0
- package/src/js/combobox/event.combobox.ts +454 -0
- package/src/js/combobox/form.combobox.ts +115 -0
- package/src/js/combobox/i18n.combobox.ts +179 -0
- package/src/js/combobox/index.ts +307 -0
- package/src/js/combobox/misc.combobox.ts +138 -0
- package/src/js/combobox/models.combobox.ts +94 -0
- package/src/js/combobox/state.combobox.ts +235 -0
- package/src/js/index.ts +1 -1
- package/src/js/internal/draggable.ts +3 -3
- package/src/js/internal/navigable.ts +10 -3
- package/src/js/movable/movable.embedded.ts +2 -2
- package/src/js/resizable/resizable.embedded.ts +3 -3
- package/src/js/sortable/sortable.embedded.ts +2 -2
- package/src/js/splitter.ts +2 -2
- package/dist/js/combobox.d.mts +0 -91
- package/dist/js/combobox.mjs +0 -855
- package/src/js/combobox.ts +0 -1634
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type {OuiComboboxAutoCompleteType, OuiComboboxType} from './models.combobox';
|
|
2
|
+
|
|
3
|
+
// #region Variables
|
|
4
|
+
|
|
5
|
+
export const COMBOBOX_TAGNAME = 'oui-combobox';
|
|
6
|
+
|
|
7
|
+
export const COMBOBOX_ATTRIBUTE_ANCHOR = `${COMBOBOX_TAGNAME}-anchor`;
|
|
8
|
+
|
|
9
|
+
export const COMBOBOX_ATTRIBUTE_CONTENT = `${COMBOBOX_TAGNAME}-content`;
|
|
10
|
+
|
|
11
|
+
export const COMBOBOX_ATTRIBUTE_FILTER = `${COMBOBOX_TAGNAME}-filter`;
|
|
12
|
+
|
|
13
|
+
export const COMBOBOX_ATTRIBUTE_FILTER_INPUT = `${COMBOBOX_ATTRIBUTE_FILTER}-input`;
|
|
14
|
+
|
|
15
|
+
export const COMBOBOX_ATTRIBUTE_HEADER = `${COMBOBOX_TAGNAME}-header`;
|
|
16
|
+
|
|
17
|
+
export const COMBOBOX_ATTRIBUTE_HIDDEN = `${COMBOBOX_TAGNAME}-hidden`;
|
|
18
|
+
|
|
19
|
+
export const COMBOBOX_ATTRIBUTE_LIST = `${COMBOBOX_TAGNAME}-list`;
|
|
20
|
+
|
|
21
|
+
export const COMBOBOX_ATTRIBUTE_MESSAGE = `${COMBOBOX_TAGNAME}-message`;
|
|
22
|
+
|
|
23
|
+
export const COMBOBOX_ATTRIBUTE_OPTION = `${COMBOBOX_TAGNAME}-option`;
|
|
24
|
+
|
|
25
|
+
export const COMBOBOX_ATTRIBUTE_OPTION_ACTIVE = `${COMBOBOX_TAGNAME}-option-active`;
|
|
26
|
+
|
|
27
|
+
export const COMBOBOX_ATTRIBUTE_SELECTION = `${COMBOBOX_TAGNAME}-selection`;
|
|
28
|
+
|
|
29
|
+
export const COMBOBOX_ATTRIBUTE_SELECTION_ITEM = `${COMBOBOX_TAGNAME}-selection-item`;
|
|
30
|
+
|
|
31
|
+
export const COMBOBOX_AUTOCOMPLETES = new Set<OuiComboboxAutoCompleteType>([
|
|
32
|
+
'both',
|
|
33
|
+
'inline',
|
|
34
|
+
'list',
|
|
35
|
+
'none',
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
export const COMBOBOX_KEYS_ACTIVATION = new Set([' ', 'Backspace', 'Delete', 'Enter']);
|
|
39
|
+
|
|
40
|
+
export const COMBOBOX_KEYS_NAVIGATION = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
|
|
41
|
+
|
|
42
|
+
export const COMBOBOX_KEYS_SELECTION = new Set(['ArrowRight', 'End']);
|
|
43
|
+
|
|
44
|
+
export const COMBOBOX_MESSAGE_LABEL = `<${COMBOBOX_TAGNAME}> must contain a <label> element for the <select> element`;
|
|
45
|
+
|
|
46
|
+
export const COMBOBOX_MESSAGE_SELECT = `<${COMBOBOX_TAGNAME}> must contain a valid <select> element`;
|
|
47
|
+
|
|
48
|
+
export const COMBOBOX_SELECTOR_ANCHOR = `[${COMBOBOX_ATTRIBUTE_ANCHOR}]`;
|
|
49
|
+
|
|
50
|
+
export const COMBOBOX_SELECTOR_SELECTION_ITEM = `[${COMBOBOX_ATTRIBUTE_SELECTION_ITEM}]`;
|
|
51
|
+
|
|
52
|
+
export const COMBOBOX_SELECTOR_OPTION = `[${COMBOBOX_ATTRIBUTE_OPTION}]`;
|
|
53
|
+
|
|
54
|
+
export const COMBOBOX_TYPES = new Set<OuiComboboxType>(['autocomplete', 'select']);
|
|
55
|
+
|
|
56
|
+
export const comboboxScrollOptions: ScrollIntoViewOptions = {
|
|
57
|
+
behavior: 'auto',
|
|
58
|
+
block: 'nearest',
|
|
59
|
+
inline: 'nearest',
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// #endregion
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import {getString} from '@oscarpalmer/atoms/string';
|
|
2
|
+
import {setAria} from '@oscarpalmer/toretto/aria';
|
|
3
|
+
import {getData} from '@oscarpalmer/toretto/data';
|
|
4
|
+
import {
|
|
5
|
+
updateComboboxMessage,
|
|
6
|
+
updateComboboxPreview,
|
|
7
|
+
updateComboboxSelection,
|
|
8
|
+
} from './dom.combobox';
|
|
9
|
+
import {onComboboxFilter} from './event.combobox';
|
|
10
|
+
import {updateComboboxForm} from './form.combobox';
|
|
11
|
+
import type {OuiComboboxState} from './models.combobox';
|
|
12
|
+
|
|
13
|
+
// #region Types
|
|
14
|
+
|
|
15
|
+
type ApplySelectionParameters = {
|
|
16
|
+
added: boolean;
|
|
17
|
+
emit?: boolean;
|
|
18
|
+
option?: HTMLElement;
|
|
19
|
+
selected?: HTMLElement[];
|
|
20
|
+
state: OuiComboboxState;
|
|
21
|
+
values?: string[];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// #endregion
|
|
25
|
+
|
|
26
|
+
// #region Functions
|
|
27
|
+
|
|
28
|
+
export function applyComboboxSelection(parameters: ApplySelectionParameters): void {
|
|
29
|
+
const {added, emit, option, selected, state} = parameters;
|
|
30
|
+
|
|
31
|
+
const {length} = state.options.all;
|
|
32
|
+
|
|
33
|
+
for (let index = 0; index < length; index += 1) {
|
|
34
|
+
const option = state.options.all[index];
|
|
35
|
+
|
|
36
|
+
setAria(option, 'selected', (selected ?? state.options.selected).includes(option));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const values =
|
|
40
|
+
parameters.values ??
|
|
41
|
+
(state.autocomplete.none
|
|
42
|
+
? [state.filter.element.value]
|
|
43
|
+
: (selected ?? state.options.selected).map(option => getData(option, 'value', false) ?? ''));
|
|
44
|
+
|
|
45
|
+
if (values.length === 0) {
|
|
46
|
+
values.push('');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (selected != null) {
|
|
50
|
+
state.options.selected = selected;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const update =
|
|
54
|
+
values.length === state.values.current.length
|
|
55
|
+
? values.some(value => !state.values.current.includes(value))
|
|
56
|
+
: true;
|
|
57
|
+
|
|
58
|
+
if (!update) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
updateComboboxForm(state, values, emit);
|
|
63
|
+
updateComboboxPreview('set', state, option);
|
|
64
|
+
updateComboboxSelection(state, option, added, selected);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function filterComboboxOption(
|
|
68
|
+
state: OuiComboboxState,
|
|
69
|
+
value: unknown,
|
|
70
|
+
textContent?: boolean,
|
|
71
|
+
): HTMLElement | undefined {
|
|
72
|
+
const filterTextContent = textContent ?? false;
|
|
73
|
+
|
|
74
|
+
const filter = getString(value).toLocaleLowerCase();
|
|
75
|
+
|
|
76
|
+
filterComboboxOptions(state);
|
|
77
|
+
|
|
78
|
+
const {length} = state.options.all;
|
|
79
|
+
let match: HTMLElement | undefined;
|
|
80
|
+
|
|
81
|
+
for (let index = 0; index < length; index += 1) {
|
|
82
|
+
const option = state.options.all[index];
|
|
83
|
+
|
|
84
|
+
const value = (
|
|
85
|
+
filterTextContent ? option.textContent : (getData(option, 'value', false) ?? '')
|
|
86
|
+
).toLocaleLowerCase();
|
|
87
|
+
|
|
88
|
+
if (value === filter) {
|
|
89
|
+
match = option;
|
|
90
|
+
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return match;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function filterComboboxOptions(state: OuiComboboxState): HTMLElement | undefined {
|
|
99
|
+
function filter(options: HTMLElement[]): boolean {
|
|
100
|
+
const {length} = options;
|
|
101
|
+
|
|
102
|
+
let count = 0;
|
|
103
|
+
|
|
104
|
+
for (let index = 0; index < length; index += 1) {
|
|
105
|
+
const option = options[index];
|
|
106
|
+
|
|
107
|
+
option.hidden =
|
|
108
|
+
!state.autocomplete.none && !option.textContent.toLocaleLowerCase().includes(value);
|
|
109
|
+
|
|
110
|
+
if (!option.hidden) {
|
|
111
|
+
count += 1;
|
|
112
|
+
|
|
113
|
+
visible.push(option);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return count === 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const value = state.filter.element.value.toLocaleLowerCase();
|
|
121
|
+
|
|
122
|
+
const visible: HTMLElement[] = [];
|
|
123
|
+
|
|
124
|
+
if (state.groups == null) {
|
|
125
|
+
filter(state.options.all);
|
|
126
|
+
} else {
|
|
127
|
+
const groupsLength = state.groups.length;
|
|
128
|
+
|
|
129
|
+
for (let groupIndex = 0; groupIndex < groupsLength; groupIndex += 1) {
|
|
130
|
+
const {element, options} = state.groups[groupIndex];
|
|
131
|
+
|
|
132
|
+
element.hidden = filter(options);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
updateComboboxMessage(state, visible.length);
|
|
137
|
+
|
|
138
|
+
return state.active != null && visible.includes(state.active) ? state.active : visible[0];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function setComboboxValue(state: OuiComboboxState, value: unknown, emit?: boolean): void {
|
|
142
|
+
if (state.autocomplete.none) {
|
|
143
|
+
state.filter.element.value = getString(value);
|
|
144
|
+
|
|
145
|
+
onComboboxFilter('insertText', state, true, emit);
|
|
146
|
+
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const option = filterComboboxOption(state, value);
|
|
151
|
+
|
|
152
|
+
if (option != null) {
|
|
153
|
+
setComboboxValues(state, [value], emit);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function setComboboxValues(state: OuiComboboxState, value: unknown, emit?: boolean): void {
|
|
158
|
+
if (!Array.isArray(value)) {
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const values = (state.multiple ? value : value.slice(0, 1)).map(value =>
|
|
163
|
+
getString(value).toLocaleLowerCase(),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (values.length === 0) {
|
|
167
|
+
values.push('');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (state.autocomplete.none) {
|
|
171
|
+
setComboboxValue(state, values[0], emit);
|
|
172
|
+
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const {length} = state.options.all;
|
|
177
|
+
|
|
178
|
+
const selected: HTMLElement[] = [];
|
|
179
|
+
|
|
180
|
+
for (let index = 0; index < length; index += 1) {
|
|
181
|
+
const option = state.options.all[index];
|
|
182
|
+
|
|
183
|
+
if (values.includes(getData(option, 'value', false)?.toLocaleLowerCase() ?? '')) {
|
|
184
|
+
selected.push(option);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
applyComboboxSelection({
|
|
189
|
+
emit,
|
|
190
|
+
selected,
|
|
191
|
+
state,
|
|
192
|
+
added: true,
|
|
193
|
+
option: selected[0] ?? state.options.all[0],
|
|
194
|
+
values: state.autocomplete.none ? values : undefined,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export function toggleComboboxOption(state: OuiComboboxState, option?: HTMLElement): void {
|
|
199
|
+
if (option == null || (!state.multiple && state.options.selected.includes(option))) {
|
|
200
|
+
state.floatable.hide();
|
|
201
|
+
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const selected = state.options.selected.slice();
|
|
206
|
+
|
|
207
|
+
let added = true;
|
|
208
|
+
|
|
209
|
+
if (state.multiple) {
|
|
210
|
+
const index = selected.indexOf(option);
|
|
211
|
+
|
|
212
|
+
if (index === -1) {
|
|
213
|
+
selected.push(option);
|
|
214
|
+
} else {
|
|
215
|
+
added = false;
|
|
216
|
+
|
|
217
|
+
selected.splice(index, 1);
|
|
218
|
+
}
|
|
219
|
+
} else {
|
|
220
|
+
selected.splice(0, selected.length, option);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
applyComboboxSelection({
|
|
224
|
+
added,
|
|
225
|
+
option,
|
|
226
|
+
selected,
|
|
227
|
+
state,
|
|
228
|
+
values: state.autocomplete.none && option != null ? [option.textContent] : undefined,
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
if (!state.multiple) {
|
|
232
|
+
state.floatable.hide();
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// #endregion
|