@oscarpalmer/oui 0.35.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/css/combobox.css +1 -1
- package/dist/css/listbox.css +152 -0
- package/dist/js/combobox/constants.combobox.d.mts +2 -2
- package/dist/js/combobox/constants.combobox.mjs +40 -33
- package/dist/js/combobox/dom.combobox.d.mts +8 -91
- package/dist/js/combobox/dom.combobox.mjs +204 -2
- package/dist/js/combobox/event.combobox.d.mts +14 -8
- package/dist/js/combobox/event.combobox.mjs +2 -2
- package/dist/js/combobox/index.d.mts +1 -1
- package/dist/js/combobox/index.mjs +20 -18
- package/dist/js/combobox/misc.combobox.d.mts +2 -2
- package/dist/js/combobox/misc.combobox.mjs +2 -2
- package/dist/js/combobox/models.combobox.d.mts +44 -2
- package/dist/js/combobox/state.combobox.d.mts +9 -10
- package/dist/js/combobox/state.combobox.mjs +2 -2
- package/dist/js/constants.combobox-DmRAMC64.d.mts +26 -0
- package/dist/js/constants.listbox-DRsXUxd3.d.mts +14 -0
- package/dist/js/index-EfkUSals.d.mts +139 -0
- package/dist/js/index.d.mts +4 -2
- package/dist/js/index.mjs +4 -2
- package/dist/js/internal/attributable.mjs +4 -3
- package/dist/js/internal/listbox/listbox.constants.d.mts +11 -0
- package/dist/js/internal/listbox/listbox.constants.mjs +23 -0
- package/dist/js/internal/listbox/listbox.data.d.mts +20 -0
- package/dist/js/internal/listbox/listbox.data.mjs +2 -0
- package/dist/js/internal/listbox/listbox.dom.d.mts +66 -0
- package/dist/js/internal/listbox/listbox.dom.mjs +2 -0
- package/dist/js/internal/listbox/listbox.event.d.mts +5 -0
- package/dist/js/internal/listbox/listbox.event.mjs +63 -0
- package/dist/js/internal/listbox/listbox.form.d.mts +6 -0
- package/dist/js/internal/listbox/listbox.form.mjs +52 -0
- package/dist/js/internal/listbox/listbox.misc.d.mts +12 -0
- package/dist/js/internal/listbox/listbox.misc.mjs +2 -0
- package/dist/js/internal/listbox/listbox.model.d.mts +2 -0
- package/dist/js/internal/listbox/listbox.model.mjs +1 -0
- package/dist/js/internal/listbox/listbox.state.d.mts +7 -0
- package/dist/js/internal/listbox/listbox.state.mjs +40 -0
- package/dist/js/internal/misc.d.mts +6 -0
- package/dist/js/internal/misc.mjs +13 -0
- package/dist/js/l10n-Bn-7RcwR.d.mts +20 -0
- package/dist/js/l10n.d.mts +2 -20
- package/dist/js/listbox/constants.listbox.d.mts +2 -0
- package/dist/js/listbox/constants.listbox.mjs +40 -0
- package/dist/js/listbox/event.listbox.d.mts +8 -0
- package/dist/js/listbox/event.listbox.mjs +2 -0
- package/dist/js/listbox/index.d.mts +116 -0
- package/dist/js/listbox/index.mjs +189 -0
- package/dist/js/listbox/misc.listbox.d.mts +2 -0
- package/dist/js/listbox/misc.listbox.mjs +2 -0
- package/dist/js/listbox/state.listbox.d.mts +11 -0
- package/dist/js/listbox/state.listbox.mjs +2 -0
- package/dist/js/listbox.data-BWd0PCy7.mjs +424 -0
- package/dist/js/listbox.model-CRReGOmu.d.mts +125 -0
- package/dist/js/misc.listbox-BI-XsTOf.mjs +197 -0
- package/dist/js/misc.listbox-gnoJhrTr.d.mts +20 -0
- package/dist/js/sortable/sortable.embedded.mjs +4 -2
- package/dist/js/state.combobox-4xq0FPCF.mjs +365 -0
- package/package.json +7 -3
- package/src/css/combobox.scss +1 -1
- package/src/css/listbox.scss +149 -0
- package/src/js/combobox/constants.combobox.ts +46 -25
- package/src/js/combobox/dom.combobox.ts +36 -454
- package/src/js/combobox/event.combobox.ts +129 -218
- package/src/js/combobox/index.ts +37 -30
- package/src/js/combobox/misc.combobox.ts +30 -114
- package/src/js/combobox/models.combobox.ts +7 -47
- package/src/js/combobox/state.combobox.ts +126 -127
- package/src/js/index.ts +1 -0
- package/src/js/internal/listbox/listbox.constants.ts +25 -0
- package/src/js/{combobox/data.combobox.ts → internal/listbox/listbox.data.ts} +94 -43
- package/src/js/internal/listbox/listbox.dom.ts +445 -0
- package/src/js/internal/listbox/listbox.event.ts +129 -0
- package/src/js/{combobox/form.combobox.ts → internal/listbox/listbox.form.ts} +12 -13
- package/src/js/internal/listbox/listbox.misc.ts +135 -0
- package/src/js/internal/listbox/listbox.model.ts +169 -0
- package/src/js/internal/listbox/listbox.state.ts +82 -0
- package/src/js/internal/misc.ts +27 -0
- package/src/js/l10n.ts +8 -8
- package/src/js/listbox/constants.listbox.ts +61 -0
- package/src/js/listbox/event.listbox.ts +106 -0
- package/src/js/listbox/index.ts +302 -0
- package/src/js/listbox/misc.listbox.ts +70 -0
- package/src/js/listbox/state.listbox.ts +196 -0
- package/dist/js/combobox/data.combobox.d.mts +0 -18
- package/dist/js/combobox/data.combobox.mjs +0 -2
- package/dist/js/combobox/form.combobox.d.mts +0 -6
- package/dist/js/combobox/form.combobox.mjs +0 -2
- package/dist/js/constants.combobox-h_kyDegw.d.mts +0 -248
- package/dist/js/data.combobox-B8HMkDOh.mjs +0 -1030
|
@@ -1,70 +1,28 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
3
|
-
import type {AnyAriaBooleanAttribute} from '@oscarpalmer/toretto/models';
|
|
4
|
-
import type {OuiComboboxElement} from '.';
|
|
5
|
-
import {updateLocalizations} from '../l10n';
|
|
6
|
-
import {COMBOBOX_AUTOCOMPLETES} from './constants.combobox';
|
|
7
|
-
import {
|
|
8
|
-
updateComboboxAnchor,
|
|
9
|
-
updateComboboxMessage,
|
|
10
|
-
updateComboboxSelectionItems,
|
|
11
|
-
} from './dom.combobox';
|
|
12
|
-
import {onComboboxFilter} from './event.combobox';
|
|
13
|
-
import {updateComboboxValidity} from './form.combobox';
|
|
1
|
+
import {updateListboxMessage, updateListboxValueLabel} from '../internal/listbox/listbox.dom';
|
|
14
2
|
import type {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '
|
|
3
|
+
OuiListboxAutoComplete,
|
|
4
|
+
OuiListboxAutoCompleteType,
|
|
5
|
+
OuiListboxLocalizations,
|
|
6
|
+
} from '../internal/listbox/listbox.model';
|
|
7
|
+
import {getAttributeValue} from '../internal/misc';
|
|
8
|
+
import {OuiLocalizations, updateLocalizations} from '../l10n';
|
|
9
|
+
import {COMBOBOX_AUTOCOMPLETES} from './constants.combobox';
|
|
10
|
+
import {updateComboboxSelectionItems} from './dom.combobox';
|
|
11
|
+
import type {OuiComboboxElement} from './index';
|
|
20
12
|
import {comboboxLocalizations, comboboxStates} from './state.combobox';
|
|
21
13
|
|
|
22
14
|
// #region Functions
|
|
23
15
|
|
|
24
|
-
export function generateComboboxIds(prefix: string, select: HTMLSelectElement): void {
|
|
25
|
-
const elements = [...select.children].flatMap(child =>
|
|
26
|
-
child instanceof HTMLOptGroupElement
|
|
27
|
-
? [
|
|
28
|
-
child,
|
|
29
|
-
...[...child.children].filter(groupChild => groupChild instanceof HTMLOptionElement),
|
|
30
|
-
]
|
|
31
|
-
: child instanceof HTMLOptionElement
|
|
32
|
-
? [child]
|
|
33
|
-
: [],
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
const {length} = elements;
|
|
37
|
-
|
|
38
|
-
for (let index = 0; index < length; index += 1) {
|
|
39
|
-
const element = elements[index];
|
|
40
|
-
|
|
41
|
-
if (element.id === '') {
|
|
42
|
-
element.id = `${prefix}${element instanceof HTMLOptGroupElement ? '_group_' : '_option_'}${index}`;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function getComboboxAttributeValue<Value>(
|
|
48
|
-
element: HTMLElement,
|
|
49
|
-
attribute: string,
|
|
50
|
-
possible: Set<Value>,
|
|
51
|
-
defaultValue: Value,
|
|
52
|
-
): Value {
|
|
53
|
-
const value = getAttribute(element, attribute);
|
|
54
|
-
|
|
55
|
-
return possible.has(value as Value) ? (value as Value) : defaultValue;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
16
|
export function getComboboxAutoComplete(
|
|
59
17
|
element: OuiComboboxElement,
|
|
60
18
|
select: boolean,
|
|
61
|
-
):
|
|
62
|
-
let value:
|
|
19
|
+
): OuiListboxAutoComplete {
|
|
20
|
+
let value: OuiListboxAutoCompleteType;
|
|
63
21
|
|
|
64
22
|
if (select) {
|
|
65
23
|
value = 'list';
|
|
66
24
|
} else {
|
|
67
|
-
value =
|
|
25
|
+
value = getAttributeValue(element, 'autocomplete', COMBOBOX_AUTOCOMPLETES, 'none');
|
|
68
26
|
}
|
|
69
27
|
|
|
70
28
|
return {
|
|
@@ -76,67 +34,16 @@ export function getComboboxAutoComplete(
|
|
|
76
34
|
};
|
|
77
35
|
}
|
|
78
36
|
|
|
79
|
-
export function getComboboxBooleanAria(
|
|
80
|
-
element: HTMLElement,
|
|
81
|
-
attribute: AnyAriaBooleanAttribute,
|
|
82
|
-
): boolean {
|
|
83
|
-
const value = getAria(element, attribute);
|
|
84
|
-
|
|
85
|
-
return value === true || value === 'true';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
37
|
export function getComboboxId(): string {
|
|
89
38
|
index += 1;
|
|
90
39
|
|
|
91
40
|
return `oui_combobox_${index}`;
|
|
92
41
|
}
|
|
93
42
|
|
|
94
|
-
export function getComboboxVisibleOptions(state: OuiComboboxState): HTMLElement[] {
|
|
95
|
-
return state.options.all.filter(option => !option.hidden);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
43
|
export function hasTextSelection(element: HTMLElement): element is HTMLInputElement {
|
|
99
44
|
return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
100
45
|
}
|
|
101
46
|
|
|
102
|
-
export function setComboboxFilter(state: OuiComboboxState, value: unknown): void {
|
|
103
|
-
if (typeof value !== 'string') {
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
const type: InputEvent['inputType'] =
|
|
108
|
-
value.length < state.filter.element.value.length ? 'deleteContentBackward' : 'insertText';
|
|
109
|
-
|
|
110
|
-
state.filter.element.value = value;
|
|
111
|
-
|
|
112
|
-
onComboboxFilter(type, state);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export function setComboboxLimit(
|
|
116
|
-
type: 'max' | 'min',
|
|
117
|
-
state: OuiComboboxState,
|
|
118
|
-
value?: unknown,
|
|
119
|
-
): void {
|
|
120
|
-
let actual = value;
|
|
121
|
-
|
|
122
|
-
if (typeof actual === 'string') {
|
|
123
|
-
actual = Number.parseInt(actual, 10);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const updateLimit =
|
|
127
|
-
typeof actual === 'number' && !Number.isNaN(actual) && actual >= 0
|
|
128
|
-
? actual !== state[type]
|
|
129
|
-
: actual == null;
|
|
130
|
-
|
|
131
|
-
if (updateLimit) {
|
|
132
|
-
state[type] = actual as number | undefined;
|
|
133
|
-
|
|
134
|
-
setAttribute(state.host, type, actual);
|
|
135
|
-
|
|
136
|
-
updateComboboxValidity(state);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
47
|
/**
|
|
141
48
|
* Set combobox localizations for a specific language
|
|
142
49
|
*
|
|
@@ -145,7 +52,7 @@ export function setComboboxLimit(
|
|
|
145
52
|
*/
|
|
146
53
|
export function setComboboxLocalizations(
|
|
147
54
|
language: string,
|
|
148
|
-
localizations: Partial<
|
|
55
|
+
localizations: Partial<OuiListboxLocalizations>,
|
|
149
56
|
): void;
|
|
150
57
|
|
|
151
58
|
/**
|
|
@@ -154,25 +61,34 @@ export function setComboboxLocalizations(
|
|
|
154
61
|
* @param localizations Localizations for one or more languages
|
|
155
62
|
*/
|
|
156
63
|
export function setComboboxLocalizations(
|
|
157
|
-
localizations: Record<string, Partial<
|
|
64
|
+
localizations: Record<string, Partial<OuiListboxLocalizations>>,
|
|
158
65
|
): void;
|
|
159
66
|
|
|
160
67
|
export function setComboboxLocalizations(
|
|
161
|
-
first: string | Record<string, Partial<
|
|
162
|
-
second?: Partial<
|
|
68
|
+
first: string | Record<string, Partial<OuiListboxLocalizations>>,
|
|
69
|
+
second?: Partial<OuiListboxLocalizations>,
|
|
70
|
+
): void {
|
|
71
|
+
updateComboboxLocalizations(comboboxLocalizations, comboboxLocalizations.values, first, second);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function updateComboboxLocalizations(
|
|
75
|
+
instance: OuiLocalizations<OuiListboxLocalizations>,
|
|
76
|
+
destination: Record<string, Partial<OuiListboxLocalizations>>,
|
|
77
|
+
first: string | Record<string, Partial<OuiListboxLocalizations>>,
|
|
78
|
+
second?: Partial<OuiListboxLocalizations>,
|
|
163
79
|
): void {
|
|
164
80
|
updateLocalizations(
|
|
165
|
-
|
|
81
|
+
instance,
|
|
166
82
|
typeof first === 'string'
|
|
167
83
|
? {
|
|
168
84
|
[first]: second!,
|
|
169
85
|
}
|
|
170
86
|
: first,
|
|
171
|
-
|
|
87
|
+
destination,
|
|
172
88
|
() => {
|
|
173
89
|
for (const state of comboboxStates.set) {
|
|
174
|
-
|
|
175
|
-
|
|
90
|
+
updateListboxMessage(state);
|
|
91
|
+
updateListboxValueLabel(state);
|
|
176
92
|
updateComboboxSelectionItems(state);
|
|
177
93
|
}
|
|
178
94
|
},
|
|
@@ -1,18 +1,13 @@
|
|
|
1
|
-
import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
|
|
2
1
|
import type {OuiFloatable} from '../floatable/floatable.embedded';
|
|
3
|
-
import type {
|
|
2
|
+
import type {OuiListboxState, OuiListboxType} from '../internal/listbox/listbox.model';
|
|
3
|
+
|
|
4
|
+
// #region Types
|
|
4
5
|
|
|
5
6
|
export type OuiComboboxAnchor = {
|
|
6
7
|
container?: HTMLElement;
|
|
7
8
|
element: HTMLElement;
|
|
8
9
|
};
|
|
9
10
|
|
|
10
|
-
export type OuiComboboxAutoComplete = Record<OuiComboboxAutoCompleteType, boolean> & {
|
|
11
|
-
value: OuiComboboxAutoCompleteType;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export type OuiComboboxAutoCompleteType = 'both' | 'inline' | 'list' | 'none';
|
|
15
|
-
|
|
16
11
|
export type OuiComboboxFilter = {
|
|
17
12
|
element: HTMLInputElement;
|
|
18
13
|
value: string;
|
|
@@ -29,24 +24,11 @@ export type OuiComboboxLabel = {
|
|
|
29
24
|
id: string;
|
|
30
25
|
};
|
|
31
26
|
|
|
32
|
-
export type OuiComboboxLocalizations = {
|
|
33
|
-
empty: string | (() => string);
|
|
34
|
-
input: string | (() => string);
|
|
35
|
-
invalid: string | (() => string);
|
|
36
|
-
max: string | ((max: number) => string);
|
|
37
|
-
min: string | ((min: number) => string);
|
|
38
|
-
remove: string | ((label: string, value: string) => string);
|
|
39
|
-
results: string | ((amount: number) => string);
|
|
40
|
-
selected: string | ((amount: number) => string);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
27
|
export type OuiComboboxOptions = {
|
|
44
28
|
all: HTMLElement[];
|
|
45
29
|
selected: HTMLElement[];
|
|
46
30
|
};
|
|
47
31
|
|
|
48
|
-
export type OuiComboboxPreview = 'none' | 'set' | 'typed';
|
|
49
|
-
|
|
50
32
|
export type OuiComboboxSelection = {
|
|
51
33
|
container: HTMLElement;
|
|
52
34
|
items: OuiComboboxSelectionItem[];
|
|
@@ -58,37 +40,15 @@ export type OuiComboboxSelectionItem = {
|
|
|
58
40
|
};
|
|
59
41
|
|
|
60
42
|
export type OuiComboboxState = {
|
|
61
|
-
active?: HTMLElement;
|
|
62
43
|
anchor: OuiComboboxAnchor;
|
|
63
|
-
autocomplete: OuiComboboxAutoComplete;
|
|
64
|
-
content: HTMLElement;
|
|
65
|
-
disabled: boolean;
|
|
66
|
-
filter: OuiComboboxFilter;
|
|
67
44
|
floatable: OuiFloatable;
|
|
68
|
-
groups?: OuiComboboxGroup[];
|
|
69
|
-
host: OuiComboboxElement;
|
|
70
|
-
id: string;
|
|
71
|
-
internals: ElementInternals;
|
|
72
|
-
label: OuiComboboxLabel;
|
|
73
|
-
listeners: RemovableEventListener[];
|
|
74
|
-
localizations: Record<string, Partial<OuiComboboxLocalizations>>;
|
|
75
|
-
max?: number;
|
|
76
|
-
min?: number;
|
|
77
|
-
message: HTMLElement;
|
|
78
|
-
name: string;
|
|
79
|
-
multiple: boolean;
|
|
80
|
-
options: OuiComboboxOptions;
|
|
81
|
-
readonly: boolean;
|
|
82
|
-
required: boolean;
|
|
83
|
-
select: HTMLSelectElement;
|
|
84
45
|
selection?: OuiComboboxSelection;
|
|
85
|
-
type:
|
|
86
|
-
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
export type OuiComboboxType = 'autocomplete' | 'select';
|
|
46
|
+
type: OuiListboxType;
|
|
47
|
+
} & OuiListboxState;
|
|
90
48
|
|
|
91
49
|
export type OuiComboboxValues = {
|
|
92
50
|
current: string[];
|
|
93
51
|
initial: string[];
|
|
94
52
|
};
|
|
53
|
+
|
|
54
|
+
// #endregion
|
|
@@ -1,90 +1,41 @@
|
|
|
1
|
+
import {noop} from '@oscarpalmer/atoms/function';
|
|
1
2
|
import {setAria} from '@oscarpalmer/toretto/aria';
|
|
2
|
-
import {
|
|
3
|
+
import {setAttributes} from '@oscarpalmer/toretto/attribute';
|
|
3
4
|
import {getData} from '@oscarpalmer/toretto/data';
|
|
4
5
|
import {on} from '@oscarpalmer/toretto/event';
|
|
5
6
|
import {createEmbeddedFloatable} from '../floatable/floatable.embedded';
|
|
7
|
+
import {LISTBOX_TYPES} from '../internal/listbox/listbox.constants';
|
|
8
|
+
import {generateListboxIds} from '../internal/listbox/listbox.misc';
|
|
9
|
+
import type {
|
|
10
|
+
OuiListboxCallbacks,
|
|
11
|
+
OuiListboxLocalizations,
|
|
12
|
+
OuiListboxStates,
|
|
13
|
+
} from '../internal/listbox/listbox.model';
|
|
14
|
+
import {initializeListboxState} from '../internal/listbox/listbox.state';
|
|
15
|
+
import {getAttributeValue, getBooleanAriaValue} from '../internal/misc';
|
|
6
16
|
import {getLocalizations} from '../l10n';
|
|
17
|
+
import {COMBOBOX_ATTRIBUTES, COMBOBOX_MESSAGES} from './constants.combobox';
|
|
18
|
+
import {createComboboxAnchor, createComboboxContent} from './dom.combobox';
|
|
7
19
|
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
updateComboboxSelectionItems,
|
|
22
|
-
} from './dom.combobox';
|
|
23
|
-
import {onComboboxClickContent, onComboboxFocusout} from './event.combobox';
|
|
24
|
-
import {updateComboboxForm} from './form.combobox';
|
|
20
|
+
onComboboxActivation,
|
|
21
|
+
onComboboxApply,
|
|
22
|
+
onComboboxEnter,
|
|
23
|
+
onComboboxEscape,
|
|
24
|
+
onComboboxFilter,
|
|
25
|
+
onComboboxNavigation,
|
|
26
|
+
onComboboxPreview,
|
|
27
|
+
onComboboxSelection,
|
|
28
|
+
onComboboxTab,
|
|
29
|
+
onComboboxToggle,
|
|
30
|
+
preventComboboxActivation,
|
|
31
|
+
skipComboboxNavigation,
|
|
32
|
+
} from './event.combobox';
|
|
25
33
|
import type {OuiComboboxElement} from './index';
|
|
26
|
-
import {
|
|
27
|
-
|
|
28
|
-
getComboboxAttributeValue,
|
|
29
|
-
getComboboxAutoComplete,
|
|
30
|
-
getComboboxBooleanAria,
|
|
31
|
-
getComboboxId,
|
|
32
|
-
getComboboxVisibleOptions,
|
|
33
|
-
setComboboxLimit,
|
|
34
|
-
} from './misc.combobox';
|
|
35
|
-
import type {OuiComboboxLocalizations, OuiComboboxState} from './models.combobox';
|
|
34
|
+
import {getComboboxAutoComplete, getComboboxId} from './misc.combobox';
|
|
35
|
+
import type {OuiComboboxState} from './models.combobox';
|
|
36
36
|
|
|
37
37
|
// #region Functions
|
|
38
38
|
|
|
39
|
-
export function attachCombobox(state: OuiComboboxState): void {
|
|
40
|
-
const {anchor, content, filter, label, listeners, select} = state;
|
|
41
|
-
|
|
42
|
-
label.element.htmlFor = filter.element.id;
|
|
43
|
-
label.element.id = `${state.id}_label`;
|
|
44
|
-
|
|
45
|
-
select.hidden = true;
|
|
46
|
-
|
|
47
|
-
select.insertAdjacentElement('afterend', anchor.container ?? anchor.element);
|
|
48
|
-
(anchor.container ?? anchor.element).insertAdjacentElement('afterend', content);
|
|
49
|
-
|
|
50
|
-
select.remove();
|
|
51
|
-
|
|
52
|
-
state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
|
|
53
|
-
position: 'below-start',
|
|
54
|
-
reusable: false,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
state.floatable.update();
|
|
58
|
-
|
|
59
|
-
listeners.push(
|
|
60
|
-
on(state.host, 'focusout', () => {
|
|
61
|
-
setTimeout(() => {
|
|
62
|
-
onComboboxFocusout(state);
|
|
63
|
-
});
|
|
64
|
-
}),
|
|
65
|
-
on(content, 'click', event => {
|
|
66
|
-
onComboboxClickContent(event, content);
|
|
67
|
-
}),
|
|
68
|
-
on(content, 'toggle', () => {
|
|
69
|
-
setAria(anchor.element, 'expanded', state.floatable.open);
|
|
70
|
-
|
|
71
|
-
setAria(
|
|
72
|
-
filter.element,
|
|
73
|
-
'activedescendant',
|
|
74
|
-
state.floatable.open ? state.active?.id : undefined,
|
|
75
|
-
);
|
|
76
|
-
|
|
77
|
-
if (state.floatable.open) {
|
|
78
|
-
filter.element.focus();
|
|
79
|
-
}
|
|
80
|
-
}),
|
|
81
|
-
);
|
|
82
|
-
|
|
83
|
-
comboboxStates.map.set(state.host, state);
|
|
84
|
-
comboboxStates.map.set(state.content, state);
|
|
85
|
-
comboboxStates.set.add(state);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
39
|
export function createComboboxState(
|
|
89
40
|
element: OuiComboboxElement,
|
|
90
41
|
internals: ElementInternals,
|
|
@@ -92,45 +43,58 @@ export function createComboboxState(
|
|
|
92
43
|
const select = element.querySelector('select');
|
|
93
44
|
|
|
94
45
|
if (select == null || select.id === '') {
|
|
95
|
-
throw new Error(
|
|
46
|
+
throw new Error(COMBOBOX_MESSAGES.select);
|
|
96
47
|
}
|
|
97
48
|
|
|
98
49
|
const label = element.querySelector(`label[for="${select.id}"]`) as HTMLLabelElement;
|
|
99
50
|
|
|
100
51
|
if (label == null) {
|
|
101
|
-
throw new Error(
|
|
52
|
+
throw new Error(COMBOBOX_MESSAGES.label);
|
|
102
53
|
}
|
|
103
54
|
|
|
104
55
|
element.id = select.id;
|
|
105
56
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const disabled = getComboboxBooleanAria(element, 'disabled') || select.disabled;
|
|
57
|
+
const disabled = getBooleanAriaValue(element, 'disabled') || select.disabled;
|
|
109
58
|
const multiple = element.multiple || select.multiple;
|
|
110
|
-
const readonly =
|
|
111
|
-
const required =
|
|
59
|
+
const readonly = getBooleanAriaValue(element, 'readonly');
|
|
60
|
+
const required = getBooleanAriaValue(element, 'required') || select.required;
|
|
112
61
|
|
|
113
62
|
const id = getComboboxId();
|
|
114
|
-
const type = multiple
|
|
115
|
-
? 'select'
|
|
116
|
-
: getComboboxAttributeValue(element, 'type', COMBOBOX_TYPES, 'select');
|
|
63
|
+
const type = multiple ? 'select' : getAttributeValue(element, 'type', LISTBOX_TYPES, 'select');
|
|
117
64
|
|
|
118
65
|
const autocomplete = getComboboxAutoComplete(element, type === 'select');
|
|
119
66
|
|
|
67
|
+
setAttributes(element, {
|
|
68
|
+
autocomplete: autocomplete.value,
|
|
69
|
+
name: select.name === '' ? select.id : select.name,
|
|
70
|
+
});
|
|
71
|
+
|
|
120
72
|
const labelId = label.id;
|
|
121
73
|
|
|
122
74
|
label.htmlFor = id;
|
|
123
75
|
label.id = `${id}_label`;
|
|
124
76
|
|
|
125
|
-
|
|
77
|
+
generateListboxIds(id, select);
|
|
126
78
|
|
|
127
|
-
const
|
|
128
|
-
|
|
79
|
+
const parameters = {
|
|
80
|
+
autocomplete,
|
|
81
|
+
element,
|
|
82
|
+
id,
|
|
83
|
+
label,
|
|
84
|
+
multiple,
|
|
85
|
+
select,
|
|
86
|
+
type,
|
|
87
|
+
attributes: COMBOBOX_ATTRIBUTES,
|
|
88
|
+
listbox: false,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const anchor = createComboboxAnchor(parameters);
|
|
92
|
+
const content = createComboboxContent(parameters);
|
|
93
|
+
|
|
94
|
+
const {selected} = content.list.options;
|
|
129
95
|
|
|
130
96
|
const values =
|
|
131
|
-
|
|
132
|
-
? ['']
|
|
133
|
-
: content.list.selected.map(option => getData(option, 'value', false) ?? '');
|
|
97
|
+
selected.length === 0 ? [''] : selected.map(option => getData(option, 'value', false) ?? '');
|
|
134
98
|
|
|
135
99
|
const state: OuiComboboxState = {
|
|
136
100
|
autocomplete,
|
|
@@ -142,12 +106,15 @@ export function createComboboxState(
|
|
|
142
106
|
required,
|
|
143
107
|
select,
|
|
144
108
|
type,
|
|
109
|
+
active: selected[0],
|
|
145
110
|
anchor: {
|
|
146
111
|
container: anchor.container,
|
|
147
112
|
element: anchor.element,
|
|
148
113
|
},
|
|
149
|
-
|
|
114
|
+
attributes: COMBOBOX_ATTRIBUTES,
|
|
115
|
+
callbacks: comboboxCallbacks as OuiComboboxState['callbacks'],
|
|
150
116
|
content: content.element,
|
|
117
|
+
elements: [element, anchor.element, content.filter?.element],
|
|
151
118
|
filter: {
|
|
152
119
|
element: content.filter?.element ?? (anchor.element as HTMLInputElement),
|
|
153
120
|
value: '',
|
|
@@ -159,51 +126,66 @@ export function createComboboxState(
|
|
|
159
126
|
element: label,
|
|
160
127
|
id: labelId,
|
|
161
128
|
},
|
|
129
|
+
list: content.list.element,
|
|
130
|
+
listbox: false,
|
|
162
131
|
listeners: [],
|
|
163
|
-
localizations: {
|
|
132
|
+
localizations: {
|
|
133
|
+
component: {},
|
|
134
|
+
global: comboboxLocalizations,
|
|
135
|
+
},
|
|
164
136
|
message: content.message,
|
|
165
137
|
name: element.name,
|
|
166
138
|
options: {
|
|
167
|
-
|
|
168
|
-
|
|
139
|
+
selected,
|
|
140
|
+
all: content.list.options.all,
|
|
169
141
|
},
|
|
170
142
|
selection: anchor.selection,
|
|
171
143
|
values: {
|
|
172
144
|
current: values.slice(),
|
|
173
145
|
initial: values.slice(),
|
|
146
|
+
label: content.value,
|
|
174
147
|
},
|
|
175
148
|
};
|
|
176
149
|
|
|
177
|
-
|
|
150
|
+
return initializeListboxState(state, comboboxStates, onAttachCombobox);
|
|
151
|
+
}
|
|
178
152
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
setComboboxLimit('min', state, getAttribute(element, 'min'));
|
|
182
|
-
setComboboxReadonly(state, readonly);
|
|
183
|
-
setComboboxRequired(state, required);
|
|
153
|
+
export function onAttachCombobox(state: OuiComboboxState): void {
|
|
154
|
+
const {anchor, content, filter, select} = state;
|
|
184
155
|
|
|
185
|
-
|
|
156
|
+
select.hidden = true;
|
|
186
157
|
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
158
|
+
select.insertAdjacentElement('afterend', anchor.container ?? anchor.element);
|
|
159
|
+
(anchor.container ?? anchor.element).insertAdjacentElement('afterend', content);
|
|
190
160
|
|
|
191
|
-
|
|
192
|
-
updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
|
|
193
|
-
updateComboboxSelectionItems(state);
|
|
161
|
+
select.remove();
|
|
194
162
|
|
|
195
|
-
|
|
196
|
-
|
|
163
|
+
state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
|
|
164
|
+
position: 'below-start',
|
|
165
|
+
reusable: false,
|
|
166
|
+
});
|
|
197
167
|
|
|
198
|
-
|
|
199
|
-
const {content, floatable, host, label, select} = state;
|
|
168
|
+
state.floatable.update();
|
|
200
169
|
|
|
201
|
-
|
|
202
|
-
|
|
170
|
+
state.listeners.push(
|
|
171
|
+
on(content, 'toggle', () => {
|
|
172
|
+
setAria(anchor.element, 'expanded', state.floatable.open);
|
|
203
173
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
174
|
+
setAria(
|
|
175
|
+
filter.element,
|
|
176
|
+
'activedescendant',
|
|
177
|
+
state.floatable.open ? state.active?.id : undefined,
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
if (state.floatable.open) {
|
|
181
|
+
filter.element.focus();
|
|
182
|
+
}
|
|
183
|
+
}),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export function onDetachCombobox(state: OuiComboboxState): void {
|
|
188
|
+
const {content, floatable, label, select} = state;
|
|
207
189
|
|
|
208
190
|
floatable.destroy();
|
|
209
191
|
|
|
@@ -218,17 +200,34 @@ export function detachCombobox(state: OuiComboboxState): void {
|
|
|
218
200
|
label.element.id = state.label.id;
|
|
219
201
|
|
|
220
202
|
select.hidden = false;
|
|
221
|
-
|
|
222
|
-
comboboxStates.map.delete(host);
|
|
223
|
-
comboboxStates.map.delete(content);
|
|
224
|
-
comboboxStates.set.delete(state);
|
|
225
203
|
}
|
|
226
204
|
|
|
227
205
|
// #endregion
|
|
228
206
|
|
|
229
207
|
// #region Variables
|
|
230
208
|
|
|
231
|
-
|
|
209
|
+
const comboboxCallbacks: OuiListboxCallbacks<OuiComboboxState> = {
|
|
210
|
+
data: {
|
|
211
|
+
onApply: onComboboxApply,
|
|
212
|
+
onFilter: onComboboxFilter,
|
|
213
|
+
onForm: noop,
|
|
214
|
+
onPreview: onComboboxPreview,
|
|
215
|
+
onToggle: onComboboxToggle,
|
|
216
|
+
},
|
|
217
|
+
keydown: {
|
|
218
|
+
onActivation: onComboboxActivation,
|
|
219
|
+
onEnter: onComboboxEnter,
|
|
220
|
+
onEscape: onComboboxEscape,
|
|
221
|
+
onNavigation: onComboboxNavigation,
|
|
222
|
+
onSelection: onComboboxSelection,
|
|
223
|
+
onTab: onComboboxTab,
|
|
224
|
+
onToggle: onComboboxToggle,
|
|
225
|
+
preventActivation: preventComboboxActivation,
|
|
226
|
+
skipNavigation: skipComboboxNavigation,
|
|
227
|
+
},
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
export const comboboxLocalizations = getLocalizations<OuiListboxLocalizations>({
|
|
232
231
|
empty: 'No results found',
|
|
233
232
|
input: 'Please enter a value',
|
|
234
233
|
invalid: 'Please select an option from the list',
|
|
@@ -239,9 +238,9 @@ export const comboboxLocalizations = getLocalizations<OuiComboboxLocalizations>(
|
|
|
239
238
|
selected: amount => `${amount} selected`,
|
|
240
239
|
});
|
|
241
240
|
|
|
242
|
-
export const comboboxStates = {
|
|
243
|
-
map: new WeakMap
|
|
244
|
-
set: new Set
|
|
241
|
+
export const comboboxStates: OuiListboxStates<OuiComboboxState> = {
|
|
242
|
+
map: new WeakMap(),
|
|
243
|
+
set: new Set(),
|
|
245
244
|
};
|
|
246
245
|
|
|
247
246
|
// #endregion
|
package/src/js/index.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from './combobox/index';
|
|
|
5
5
|
export * from './dialog';
|
|
6
6
|
export * from './floatable/floatable.standalone';
|
|
7
7
|
export * from './focus-trap/focus-trap.standalone';
|
|
8
|
+
export * from './listbox/index';
|
|
8
9
|
export * from './movable/movable.standalone';
|
|
9
10
|
export * from './popover';
|
|
10
11
|
export * from './resizable/resizable.standalone';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type {OuiListboxType} from './listbox.model';
|
|
2
|
+
|
|
3
|
+
// #region Variables
|
|
4
|
+
|
|
5
|
+
const keysActivation = new Set([' ', 'Backspace', 'Delete', 'Enter']);
|
|
6
|
+
|
|
7
|
+
const keysNavigation = new Set(['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp']);
|
|
8
|
+
|
|
9
|
+
const keysSelection = new Set(['ArrowRight', 'End']);
|
|
10
|
+
|
|
11
|
+
export const LISTBOX_KEYS = {
|
|
12
|
+
activation: keysActivation,
|
|
13
|
+
navigation: keysNavigation,
|
|
14
|
+
selection: keysSelection,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const LISTBOX_TYPES = new Set<OuiListboxType>(['autocomplete', 'select']);
|
|
18
|
+
|
|
19
|
+
export const listboxScrollOptions: ScrollIntoViewOptions = {
|
|
20
|
+
behavior: 'auto',
|
|
21
|
+
block: 'nearest',
|
|
22
|
+
inline: 'nearest',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// #endregion
|