@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,62 +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 {COMBOBOX_AUTOCOMPLETES} from './constants.combobox';
|
|
6
|
-
import {onComboboxFilter} from './event.combobox';
|
|
7
|
-
import {updateComboboxValidity} from './form.combobox';
|
|
1
|
+
import {updateListboxMessage, updateListboxValueLabel} from '../internal/listbox/listbox.dom';
|
|
8
2
|
import type {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} 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';
|
|
12
|
+
import {comboboxLocalizations, comboboxStates} from './state.combobox';
|
|
13
13
|
|
|
14
14
|
// #region Functions
|
|
15
15
|
|
|
16
|
-
export function generateComboboxIds(prefix: string, select: HTMLSelectElement): void {
|
|
17
|
-
const elements = [...select.children].flatMap(child =>
|
|
18
|
-
child instanceof HTMLOptGroupElement
|
|
19
|
-
? [
|
|
20
|
-
child,
|
|
21
|
-
...[...child.children].filter(groupChild => groupChild instanceof HTMLOptionElement),
|
|
22
|
-
]
|
|
23
|
-
: child instanceof HTMLOptionElement
|
|
24
|
-
? [child]
|
|
25
|
-
: [],
|
|
26
|
-
);
|
|
27
|
-
|
|
28
|
-
const {length} = elements;
|
|
29
|
-
|
|
30
|
-
for (let index = 0; index < length; index += 1) {
|
|
31
|
-
const element = elements[index];
|
|
32
|
-
|
|
33
|
-
if (element.id === '') {
|
|
34
|
-
element.id = `${prefix}${element instanceof HTMLOptGroupElement ? '_group_' : '_option_'}${index}`;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function getComboboxAttributeValue<Value>(
|
|
40
|
-
element: HTMLElement,
|
|
41
|
-
attribute: string,
|
|
42
|
-
possible: Set<Value>,
|
|
43
|
-
defaultValue: Value,
|
|
44
|
-
): Value {
|
|
45
|
-
const value = getAttribute(element, attribute);
|
|
46
|
-
|
|
47
|
-
return possible.has(value as Value) ? (value as Value) : defaultValue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
16
|
export function getComboboxAutoComplete(
|
|
51
17
|
element: OuiComboboxElement,
|
|
52
18
|
select: boolean,
|
|
53
|
-
):
|
|
54
|
-
let value:
|
|
19
|
+
): OuiListboxAutoComplete {
|
|
20
|
+
let value: OuiListboxAutoCompleteType;
|
|
55
21
|
|
|
56
22
|
if (select) {
|
|
57
23
|
value = 'list';
|
|
58
24
|
} else {
|
|
59
|
-
value =
|
|
25
|
+
value = getAttributeValue(element, 'autocomplete', COMBOBOX_AUTOCOMPLETES, 'none');
|
|
60
26
|
}
|
|
61
27
|
|
|
62
28
|
return {
|
|
@@ -68,65 +34,65 @@ export function getComboboxAutoComplete(
|
|
|
68
34
|
};
|
|
69
35
|
}
|
|
70
36
|
|
|
71
|
-
export function getComboboxBooleanAria(
|
|
72
|
-
element: HTMLElement,
|
|
73
|
-
attribute: AnyAriaBooleanAttribute,
|
|
74
|
-
): boolean {
|
|
75
|
-
const value = getAria(element, attribute);
|
|
76
|
-
|
|
77
|
-
return value === true || value === 'true';
|
|
78
|
-
}
|
|
79
|
-
|
|
80
37
|
export function getComboboxId(): string {
|
|
81
38
|
index += 1;
|
|
82
39
|
|
|
83
40
|
return `oui_combobox_${index}`;
|
|
84
41
|
}
|
|
85
42
|
|
|
86
|
-
export function getComboboxVisibleOptions(state: OuiComboboxState): HTMLElement[] {
|
|
87
|
-
return state.options.all.filter(option => !option.hidden);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
43
|
export function hasTextSelection(element: HTMLElement): element is HTMLInputElement {
|
|
91
44
|
return element instanceof HTMLInputElement && element.selectionStart !== element.selectionEnd;
|
|
92
45
|
}
|
|
93
46
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Set combobox localizations for a specific language
|
|
49
|
+
*
|
|
50
|
+
* @param language Language code
|
|
51
|
+
* @param localizations Localizations for language
|
|
52
|
+
*/
|
|
53
|
+
export function setComboboxLocalizations(
|
|
54
|
+
language: string,
|
|
55
|
+
localizations: Partial<OuiListboxLocalizations>,
|
|
56
|
+
): void;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Set combobox localizations for one or more languages
|
|
60
|
+
*
|
|
61
|
+
* @param localizations Localizations for one or more languages
|
|
62
|
+
*/
|
|
63
|
+
export function setComboboxLocalizations(
|
|
64
|
+
localizations: Record<string, Partial<OuiListboxLocalizations>>,
|
|
65
|
+
): void;
|
|
66
|
+
|
|
67
|
+
export function setComboboxLocalizations(
|
|
68
|
+
first: string | Record<string, Partial<OuiListboxLocalizations>>,
|
|
69
|
+
second?: Partial<OuiListboxLocalizations>,
|
|
70
|
+
): void {
|
|
71
|
+
updateComboboxLocalizations(comboboxLocalizations, comboboxLocalizations.values, first, second);
|
|
105
72
|
}
|
|
106
73
|
|
|
107
|
-
export function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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>,
|
|
111
79
|
): void {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
updateComboboxValidity(state);
|
|
129
|
-
}
|
|
80
|
+
updateLocalizations(
|
|
81
|
+
instance,
|
|
82
|
+
typeof first === 'string'
|
|
83
|
+
? {
|
|
84
|
+
[first]: second!,
|
|
85
|
+
}
|
|
86
|
+
: first,
|
|
87
|
+
destination,
|
|
88
|
+
() => {
|
|
89
|
+
for (const state of comboboxStates.set) {
|
|
90
|
+
updateListboxMessage(state);
|
|
91
|
+
updateListboxValueLabel(state);
|
|
92
|
+
updateComboboxSelectionItems(state);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
);
|
|
130
96
|
}
|
|
131
97
|
|
|
132
98
|
// #endregion
|
|
@@ -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,89 +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';
|
|
16
|
+
import {getLocalizations} from '../l10n';
|
|
17
|
+
import {COMBOBOX_ATTRIBUTES, COMBOBOX_MESSAGES} from './constants.combobox';
|
|
18
|
+
import {createComboboxAnchor, createComboboxContent} from './dom.combobox';
|
|
6
19
|
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
updateComboboxSelectionItems,
|
|
21
|
-
} from './dom.combobox';
|
|
22
|
-
import {onComboboxClickContent, onComboboxFocusout} from './event.combobox';
|
|
23
|
-
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';
|
|
24
33
|
import type {OuiComboboxElement} from './index';
|
|
25
|
-
import {
|
|
26
|
-
generateComboboxIds,
|
|
27
|
-
getComboboxAttributeValue,
|
|
28
|
-
getComboboxAutoComplete,
|
|
29
|
-
getComboboxBooleanAria,
|
|
30
|
-
getComboboxId,
|
|
31
|
-
getComboboxVisibleOptions,
|
|
32
|
-
setComboboxLimit,
|
|
33
|
-
} from './misc.combobox';
|
|
34
|
+
import {getComboboxAutoComplete, getComboboxId} from './misc.combobox';
|
|
34
35
|
import type {OuiComboboxState} from './models.combobox';
|
|
35
36
|
|
|
36
37
|
// #region Functions
|
|
37
38
|
|
|
38
|
-
export function attachCombobox(state: OuiComboboxState): void {
|
|
39
|
-
const {anchor, content, filter, label, listeners, select} = state;
|
|
40
|
-
|
|
41
|
-
label.element.htmlFor = filter.element.id;
|
|
42
|
-
label.element.id = `${state.id}_label`;
|
|
43
|
-
|
|
44
|
-
select.hidden = true;
|
|
45
|
-
|
|
46
|
-
select.insertAdjacentElement('afterend', anchor.container ?? anchor.element);
|
|
47
|
-
(anchor.container ?? anchor.element).insertAdjacentElement('afterend', content);
|
|
48
|
-
|
|
49
|
-
select.remove();
|
|
50
|
-
|
|
51
|
-
state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
|
|
52
|
-
position: 'below-start',
|
|
53
|
-
reusable: false,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
state.floatable.update();
|
|
57
|
-
|
|
58
|
-
listeners.push(
|
|
59
|
-
on(state.host, 'focusout', () => {
|
|
60
|
-
setTimeout(() => {
|
|
61
|
-
onComboboxFocusout(state);
|
|
62
|
-
});
|
|
63
|
-
}),
|
|
64
|
-
on(content, 'click', event => {
|
|
65
|
-
onComboboxClickContent(event, content);
|
|
66
|
-
}),
|
|
67
|
-
on(content, 'toggle', () => {
|
|
68
|
-
setAria(anchor.element, 'expanded', state.floatable.open);
|
|
69
|
-
|
|
70
|
-
setAria(
|
|
71
|
-
filter.element,
|
|
72
|
-
'activedescendant',
|
|
73
|
-
state.floatable.open ? state.active?.id : undefined,
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
if (state.floatable.open) {
|
|
77
|
-
filter.element.focus();
|
|
78
|
-
}
|
|
79
|
-
}),
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
comboboxStates.map.set(state.host, state);
|
|
83
|
-
comboboxStates.map.set(state.content, state);
|
|
84
|
-
comboboxStates.set.add(state);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
39
|
export function createComboboxState(
|
|
88
40
|
element: OuiComboboxElement,
|
|
89
41
|
internals: ElementInternals,
|
|
@@ -91,45 +43,58 @@ export function createComboboxState(
|
|
|
91
43
|
const select = element.querySelector('select');
|
|
92
44
|
|
|
93
45
|
if (select == null || select.id === '') {
|
|
94
|
-
throw new Error(
|
|
46
|
+
throw new Error(COMBOBOX_MESSAGES.select);
|
|
95
47
|
}
|
|
96
48
|
|
|
97
49
|
const label = element.querySelector(`label[for="${select.id}"]`) as HTMLLabelElement;
|
|
98
50
|
|
|
99
51
|
if (label == null) {
|
|
100
|
-
throw new Error(
|
|
52
|
+
throw new Error(COMBOBOX_MESSAGES.label);
|
|
101
53
|
}
|
|
102
54
|
|
|
103
55
|
element.id = select.id;
|
|
104
56
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const disabled = getComboboxBooleanAria(element, 'disabled') || select.disabled;
|
|
57
|
+
const disabled = getBooleanAriaValue(element, 'disabled') || select.disabled;
|
|
108
58
|
const multiple = element.multiple || select.multiple;
|
|
109
|
-
const readonly =
|
|
110
|
-
const required =
|
|
59
|
+
const readonly = getBooleanAriaValue(element, 'readonly');
|
|
60
|
+
const required = getBooleanAriaValue(element, 'required') || select.required;
|
|
111
61
|
|
|
112
62
|
const id = getComboboxId();
|
|
113
|
-
const type = multiple
|
|
114
|
-
? 'select'
|
|
115
|
-
: getComboboxAttributeValue(element, 'type', COMBOBOX_TYPES, 'select');
|
|
63
|
+
const type = multiple ? 'select' : getAttributeValue(element, 'type', LISTBOX_TYPES, 'select');
|
|
116
64
|
|
|
117
65
|
const autocomplete = getComboboxAutoComplete(element, type === 'select');
|
|
118
66
|
|
|
67
|
+
setAttributes(element, {
|
|
68
|
+
autocomplete: autocomplete.value,
|
|
69
|
+
name: select.name === '' ? select.id : select.name,
|
|
70
|
+
});
|
|
71
|
+
|
|
119
72
|
const labelId = label.id;
|
|
120
73
|
|
|
121
74
|
label.htmlFor = id;
|
|
122
75
|
label.id = `${id}_label`;
|
|
123
76
|
|
|
124
|
-
|
|
77
|
+
generateListboxIds(id, select);
|
|
125
78
|
|
|
126
|
-
const
|
|
127
|
-
|
|
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;
|
|
128
95
|
|
|
129
96
|
const values =
|
|
130
|
-
|
|
131
|
-
? ['']
|
|
132
|
-
: content.list.selected.map(option => getData(option, 'value', false) ?? '');
|
|
97
|
+
selected.length === 0 ? [''] : selected.map(option => getData(option, 'value', false) ?? '');
|
|
133
98
|
|
|
134
99
|
const state: OuiComboboxState = {
|
|
135
100
|
autocomplete,
|
|
@@ -141,12 +106,15 @@ export function createComboboxState(
|
|
|
141
106
|
required,
|
|
142
107
|
select,
|
|
143
108
|
type,
|
|
109
|
+
active: selected[0],
|
|
144
110
|
anchor: {
|
|
145
111
|
container: anchor.container,
|
|
146
112
|
element: anchor.element,
|
|
147
113
|
},
|
|
148
|
-
|
|
114
|
+
attributes: COMBOBOX_ATTRIBUTES,
|
|
115
|
+
callbacks: comboboxCallbacks as OuiComboboxState['callbacks'],
|
|
149
116
|
content: content.element,
|
|
117
|
+
elements: [element, anchor.element, content.filter?.element],
|
|
150
118
|
filter: {
|
|
151
119
|
element: content.filter?.element ?? (anchor.element as HTMLInputElement),
|
|
152
120
|
value: '',
|
|
@@ -158,51 +126,66 @@ export function createComboboxState(
|
|
|
158
126
|
element: label,
|
|
159
127
|
id: labelId,
|
|
160
128
|
},
|
|
129
|
+
list: content.list.element,
|
|
130
|
+
listbox: false,
|
|
161
131
|
listeners: [],
|
|
162
|
-
localizations: {
|
|
132
|
+
localizations: {
|
|
133
|
+
component: {},
|
|
134
|
+
global: comboboxLocalizations,
|
|
135
|
+
},
|
|
163
136
|
message: content.message,
|
|
164
137
|
name: element.name,
|
|
165
138
|
options: {
|
|
166
|
-
|
|
167
|
-
|
|
139
|
+
selected,
|
|
140
|
+
all: content.list.options.all,
|
|
168
141
|
},
|
|
169
142
|
selection: anchor.selection,
|
|
170
143
|
values: {
|
|
171
144
|
current: values.slice(),
|
|
172
145
|
initial: values.slice(),
|
|
146
|
+
label: content.value,
|
|
173
147
|
},
|
|
174
148
|
};
|
|
175
149
|
|
|
176
|
-
|
|
150
|
+
return initializeListboxState(state, comboboxStates, onAttachCombobox);
|
|
151
|
+
}
|
|
177
152
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
setComboboxLimit('min', state, getAttribute(element, 'min'));
|
|
181
|
-
setComboboxReadonly(state, readonly);
|
|
182
|
-
setComboboxRequired(state, required);
|
|
153
|
+
export function onAttachCombobox(state: OuiComboboxState): void {
|
|
154
|
+
const {anchor, content, filter, select} = state;
|
|
183
155
|
|
|
184
|
-
|
|
156
|
+
select.hidden = true;
|
|
185
157
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
}
|
|
158
|
+
select.insertAdjacentElement('afterend', anchor.container ?? anchor.element);
|
|
159
|
+
(anchor.container ?? anchor.element).insertAdjacentElement('afterend', content);
|
|
189
160
|
|
|
190
|
-
|
|
191
|
-
updateComboboxMessage(state, getComboboxVisibleOptions(state).length);
|
|
192
|
-
updateComboboxSelectionItems(state);
|
|
161
|
+
select.remove();
|
|
193
162
|
|
|
194
|
-
|
|
195
|
-
|
|
163
|
+
state.floatable = createEmbeddedFloatable(anchor.container ?? anchor.element, content, {
|
|
164
|
+
position: 'below-start',
|
|
165
|
+
reusable: false,
|
|
166
|
+
});
|
|
196
167
|
|
|
197
|
-
|
|
198
|
-
const {content, floatable, host, label, select} = state;
|
|
168
|
+
state.floatable.update();
|
|
199
169
|
|
|
200
|
-
|
|
201
|
-
|
|
170
|
+
state.listeners.push(
|
|
171
|
+
on(content, 'toggle', () => {
|
|
172
|
+
setAria(anchor.element, 'expanded', state.floatable.open);
|
|
202
173
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
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;
|
|
206
189
|
|
|
207
190
|
floatable.destroy();
|
|
208
191
|
|
|
@@ -217,19 +200,47 @@ export function detachCombobox(state: OuiComboboxState): void {
|
|
|
217
200
|
label.element.id = state.label.id;
|
|
218
201
|
|
|
219
202
|
select.hidden = false;
|
|
220
|
-
|
|
221
|
-
comboboxStates.map.delete(host);
|
|
222
|
-
comboboxStates.map.delete(content);
|
|
223
|
-
comboboxStates.set.delete(state);
|
|
224
203
|
}
|
|
225
204
|
|
|
226
205
|
// #endregion
|
|
227
206
|
|
|
228
207
|
// #region Variables
|
|
229
208
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
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>({
|
|
231
|
+
empty: 'No results found',
|
|
232
|
+
input: 'Please enter a value',
|
|
233
|
+
invalid: 'Please select an option from the list',
|
|
234
|
+
max: max => `Please select fewer options (maximum is ${max})`,
|
|
235
|
+
min: min => `Please select more options (minimum is ${min})`,
|
|
236
|
+
remove: label => `Remove ${label}`,
|
|
237
|
+
results: amount => `${amount} results available`,
|
|
238
|
+
selected: amount => `${amount} selected`,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
export const comboboxStates: OuiListboxStates<OuiComboboxState> = {
|
|
242
|
+
map: new WeakMap(),
|
|
243
|
+
set: new Set(),
|
|
233
244
|
};
|
|
234
245
|
|
|
235
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
|