@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
package/src/js/l10n.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import type {GenericCallback} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import {getString} from '@oscarpalmer/atoms/string';
|
|
3
|
+
|
|
4
|
+
// #region Types
|
|
5
|
+
|
|
6
|
+
type LocalizationParameters<Localization> = Localization extends GenericCallback
|
|
7
|
+
? Parameters<Localization>
|
|
8
|
+
: never;
|
|
9
|
+
|
|
10
|
+
export class OuiLocalizations<Localizations extends OuiLocalizationsValues> {
|
|
11
|
+
readonly defaults: Localizations;
|
|
12
|
+
|
|
13
|
+
readonly keys: Set<keyof Localizations>;
|
|
14
|
+
|
|
15
|
+
readonly values: Record<string, Partial<Localizations>> = {};
|
|
16
|
+
|
|
17
|
+
constructor(defaults: Localizations) {
|
|
18
|
+
this.defaults = defaults;
|
|
19
|
+
this.keys = new Set(Object.keys(defaults) as Array<keyof Localizations>);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get<Key extends keyof Localizations>(
|
|
23
|
+
element: Element,
|
|
24
|
+
key: Key,
|
|
25
|
+
localizations: Record<string, Partial<Localizations>>,
|
|
26
|
+
): string | GenericCallback {
|
|
27
|
+
return getLocalization(this, element, key, localizations);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
set(language: string, localizations: Partial<Localizations>): void;
|
|
31
|
+
|
|
32
|
+
set(localizations: Record<string, Partial<Localizations>>): void;
|
|
33
|
+
|
|
34
|
+
set(
|
|
35
|
+
first: string | Record<string, Partial<Localizations>>,
|
|
36
|
+
second?: Partial<Localizations>,
|
|
37
|
+
): void {
|
|
38
|
+
updateLocalizations(
|
|
39
|
+
this,
|
|
40
|
+
typeof first === 'string'
|
|
41
|
+
? {
|
|
42
|
+
[first]: second!,
|
|
43
|
+
}
|
|
44
|
+
: first,
|
|
45
|
+
this.values,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type OuiLocalizationsValues = Record<string, string | GenericCallback>;
|
|
51
|
+
|
|
52
|
+
// #endregion
|
|
53
|
+
|
|
54
|
+
// #region Functions
|
|
55
|
+
|
|
56
|
+
export function getLanguage(element: Element): string {
|
|
57
|
+
return (
|
|
58
|
+
element.closest('[lang]')?.getAttribute('lang') ?? document.documentElement.lang
|
|
59
|
+
).toLowerCase();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function getLocalization<
|
|
63
|
+
Localizations extends OuiLocalizationsValues,
|
|
64
|
+
Key extends keyof Localizations,
|
|
65
|
+
>(
|
|
66
|
+
localizations: OuiLocalizations<Localizations>,
|
|
67
|
+
element: Element,
|
|
68
|
+
key: Key,
|
|
69
|
+
value: Record<string, Partial<Localizations>>,
|
|
70
|
+
): Localizations[Key] | GenericCallback {
|
|
71
|
+
const language = getLanguage(element);
|
|
72
|
+
|
|
73
|
+
const values = [value, localizations.values];
|
|
74
|
+
const {length} = values;
|
|
75
|
+
|
|
76
|
+
let localization: Localizations[Key] | undefined;
|
|
77
|
+
let shortLanguage: string | undefined;
|
|
78
|
+
|
|
79
|
+
for (let index = 0; index < length; index += 1) {
|
|
80
|
+
const value = values[index];
|
|
81
|
+
|
|
82
|
+
localization = value?.[language]?.[key];
|
|
83
|
+
|
|
84
|
+
if (localization != null) {
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
shortLanguage ??= language.split('-')[0];
|
|
89
|
+
|
|
90
|
+
if (shortLanguage === language) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
localization = value?.[shortLanguage]?.[key];
|
|
95
|
+
|
|
96
|
+
if (localization != null) {
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return localization ?? localizations.defaults[key];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function getLocalizations<Localizations extends OuiLocalizationsValues>(
|
|
105
|
+
defaults: Localizations,
|
|
106
|
+
): OuiLocalizations<Localizations> {
|
|
107
|
+
return new OuiLocalizations(defaults);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function localize<
|
|
111
|
+
Localizations extends OuiLocalizationsValues,
|
|
112
|
+
Key extends keyof Localizations,
|
|
113
|
+
Localization extends Localizations[Key],
|
|
114
|
+
>(
|
|
115
|
+
localization: Localization,
|
|
116
|
+
data: Record<string, unknown>,
|
|
117
|
+
parameters: LocalizationParameters<Localization>,
|
|
118
|
+
): string {
|
|
119
|
+
if (typeof localization === 'function') {
|
|
120
|
+
return localization(...parameters);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const keys = Object.keys(data);
|
|
124
|
+
const {length} = keys;
|
|
125
|
+
|
|
126
|
+
let localized = localization as string;
|
|
127
|
+
|
|
128
|
+
for (let index = 0; index < length; index += 1) {
|
|
129
|
+
const key = keys[index];
|
|
130
|
+
const value = data[key];
|
|
131
|
+
|
|
132
|
+
localized = localized.replaceAll(`{${key}}`, getString(value));
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return localized;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function updateLocalizations<Localizations extends OuiLocalizationsValues>(
|
|
139
|
+
instance: OuiLocalizations<Localizations>,
|
|
140
|
+
values: Record<string, Partial<Localizations>>,
|
|
141
|
+
destination: Record<string, Partial<Localizations>>,
|
|
142
|
+
onAfter?: () => void,
|
|
143
|
+
): void {
|
|
144
|
+
if (typeof values !== 'object' || values == null) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const localizations = destination ?? instance.values;
|
|
149
|
+
|
|
150
|
+
const languages = Object.keys(values);
|
|
151
|
+
const languagesLength = languages.length;
|
|
152
|
+
|
|
153
|
+
let updated = false;
|
|
154
|
+
|
|
155
|
+
for (let languagesIndex = 0; languagesIndex < languagesLength; languagesIndex += 1) {
|
|
156
|
+
const language = languages[languagesIndex];
|
|
157
|
+
const languageValue = values[language];
|
|
158
|
+
|
|
159
|
+
if (typeof languageValue !== 'object' || languageValue == null) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const normalizedLanguage = language.toLowerCase();
|
|
164
|
+
|
|
165
|
+
localizations[normalizedLanguage] ??= {};
|
|
166
|
+
|
|
167
|
+
const keys = Object.keys(languageValue);
|
|
168
|
+
const keysLength = keys.length;
|
|
169
|
+
|
|
170
|
+
for (let keysIndex = 0; keysIndex < keysLength; keysIndex += 1) {
|
|
171
|
+
const key = keys[keysIndex];
|
|
172
|
+
const keyValue = languageValue[key];
|
|
173
|
+
|
|
174
|
+
const normalizedKey = key.toLowerCase() as keyof Localizations;
|
|
175
|
+
|
|
176
|
+
if (
|
|
177
|
+
instance.keys.has(normalizedKey) &&
|
|
178
|
+
(typeof keyValue === 'string' || typeof keyValue === 'function')
|
|
179
|
+
) {
|
|
180
|
+
updated = true;
|
|
181
|
+
|
|
182
|
+
localizations[normalizedLanguage][normalizedKey] = keyValue as never;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (updated && typeof onAfter === 'function') {
|
|
188
|
+
onAfter();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// #endregion
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type {OuiListboxAttributes} from '../internal/listbox/listbox.model';
|
|
2
|
+
|
|
3
|
+
// #region Variables
|
|
4
|
+
|
|
5
|
+
export const LISTBOX_TAGNAME = 'oui-listbox';
|
|
6
|
+
|
|
7
|
+
const attributeContent = `${LISTBOX_TAGNAME}-content`;
|
|
8
|
+
|
|
9
|
+
const attributeFilter = `${LISTBOX_TAGNAME}-filter`;
|
|
10
|
+
|
|
11
|
+
const attributeFilterInput = `${attributeFilter}-input`;
|
|
12
|
+
|
|
13
|
+
const attributeGroup = `${LISTBOX_TAGNAME}-group`;
|
|
14
|
+
|
|
15
|
+
const attributeHeader = `${LISTBOX_TAGNAME}-header`;
|
|
16
|
+
|
|
17
|
+
const attributeHidden = `${LISTBOX_TAGNAME}-hidden`;
|
|
18
|
+
|
|
19
|
+
const attributeList = `${LISTBOX_TAGNAME}-list`;
|
|
20
|
+
|
|
21
|
+
const attributeListEmpty = `${attributeList}-empty`;
|
|
22
|
+
|
|
23
|
+
const attributeMessage = `${LISTBOX_TAGNAME}-message`;
|
|
24
|
+
|
|
25
|
+
const attributeOption = `${LISTBOX_TAGNAME}-option`;
|
|
26
|
+
|
|
27
|
+
const attributeOptionActive = `${LISTBOX_TAGNAME}-option-active`;
|
|
28
|
+
|
|
29
|
+
const messageLabel = `<${LISTBOX_TAGNAME}> must contain a <label> element for the <select> element`;
|
|
30
|
+
|
|
31
|
+
const messageSelect = `<${LISTBOX_TAGNAME}> must contain a valid <select> element`;
|
|
32
|
+
|
|
33
|
+
const selectorFilter = `[${attributeFilterInput}]`;
|
|
34
|
+
|
|
35
|
+
const selectorOption = `[${attributeOption}]`;
|
|
36
|
+
|
|
37
|
+
export const LISTBOX_ATTRIBUTES: OuiListboxAttributes = {
|
|
38
|
+
active: attributeOptionActive,
|
|
39
|
+
content: attributeContent,
|
|
40
|
+
empty: attributeListEmpty,
|
|
41
|
+
filter: attributeFilter,
|
|
42
|
+
group: attributeGroup,
|
|
43
|
+
header: attributeHeader,
|
|
44
|
+
hidden: attributeHidden,
|
|
45
|
+
input: attributeFilterInput,
|
|
46
|
+
list: attributeList,
|
|
47
|
+
message: attributeMessage,
|
|
48
|
+
option: attributeOption,
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const LISTBOX_MESSAGES = {
|
|
52
|
+
label: messageLabel,
|
|
53
|
+
select: messageSelect,
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const LISTBOX_SELECTORS = {
|
|
57
|
+
filter: selectorFilter,
|
|
58
|
+
option: selectorOption,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// #endregion
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import {on} from '@oscarpalmer/toretto/event';
|
|
2
|
+
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
3
|
+
import {
|
|
4
|
+
applyListboxSelection,
|
|
5
|
+
filterListbox,
|
|
6
|
+
toggleListboxOption,
|
|
7
|
+
} from '../internal/listbox/listbox.data';
|
|
8
|
+
import {updateListboxPreview} from '../internal/listbox/listbox.dom';
|
|
9
|
+
import {processListboxKeydown} from '../internal/listbox/listbox.event';
|
|
10
|
+
import type {OuiListboxPreview, OuiListboxState} from '../internal/listbox/listbox.model';
|
|
11
|
+
import {LISTBOX_SELECTORS, LISTBOX_TAGNAME} from './constants.listbox';
|
|
12
|
+
import {listboxStates} from './state.listbox';
|
|
13
|
+
|
|
14
|
+
// #region Types
|
|
15
|
+
|
|
16
|
+
// #region Functions
|
|
17
|
+
|
|
18
|
+
function onListboxClick(event: Event): void {
|
|
19
|
+
const listbox = findAncestor(event, LISTBOX_TAGNAME);
|
|
20
|
+
const state = listboxStates.map.get(listbox!);
|
|
21
|
+
|
|
22
|
+
if (listbox == null || state == null) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const option = findAncestor(event, LISTBOX_SELECTORS.option) as HTMLElement | null;
|
|
27
|
+
|
|
28
|
+
if (option == null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (state.disabled) {
|
|
33
|
+
state.filter.element.focus();
|
|
34
|
+
} else if (state.readonly) {
|
|
35
|
+
updateListboxPreview('none', state, option, false);
|
|
36
|
+
} else {
|
|
37
|
+
toggleListboxOption(state, option);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function onListboxEnter(state: OuiListboxState): void {
|
|
42
|
+
if (!state.disabled && !state.readonly) {
|
|
43
|
+
toggleListboxOption(state, state.active);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function onListboxFilter(
|
|
48
|
+
preview: OuiListboxPreview,
|
|
49
|
+
state: OuiListboxState,
|
|
50
|
+
_: boolean,
|
|
51
|
+
active?: HTMLElement,
|
|
52
|
+
): void {
|
|
53
|
+
updateListboxPreview(preview, state, active);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function onListboxInput(event: InputEvent): void {
|
|
57
|
+
const listbox = findAncestor(event, LISTBOX_TAGNAME);
|
|
58
|
+
const state = listboxStates.map.get(listbox!);
|
|
59
|
+
|
|
60
|
+
if (state?.filter.element === event.target) {
|
|
61
|
+
filterListbox(event.inputType, state);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function onListboxKeydown(event: KeyboardEvent): void {
|
|
66
|
+
const listbox = findAncestor(event, LISTBOX_TAGNAME);
|
|
67
|
+
const state = listboxStates.map.get(listbox!);
|
|
68
|
+
|
|
69
|
+
if (state?.filter.element === event.target) {
|
|
70
|
+
processListboxKeydown(event, state);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function onListboxNavigation(
|
|
75
|
+
preview: OuiListboxPreview,
|
|
76
|
+
state: OuiListboxState,
|
|
77
|
+
active?: HTMLElement,
|
|
78
|
+
): void {
|
|
79
|
+
if (state.multiple || state.readonly) {
|
|
80
|
+
updateListboxPreview(preview, state, active);
|
|
81
|
+
} else {
|
|
82
|
+
applyListboxSelection({
|
|
83
|
+
state,
|
|
84
|
+
option: active,
|
|
85
|
+
added: true,
|
|
86
|
+
selected: active == null ? [] : [active],
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function onListboxToggle(state: OuiListboxState): void {
|
|
92
|
+
state.filter.element.focus();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// #endregion
|
|
96
|
+
|
|
97
|
+
// #region Initialization
|
|
98
|
+
|
|
99
|
+
on(document, 'click', onListboxClick);
|
|
100
|
+
on(document, 'input', onListboxInput);
|
|
101
|
+
|
|
102
|
+
on(document, 'keydown', onListboxKeydown, {
|
|
103
|
+
passive: false,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// #endregion
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import {parse} from '@oscarpalmer/atoms/string';
|
|
2
|
+
import {getAttribute} from '@oscarpalmer/toretto/attribute';
|
|
3
|
+
import {setListboxValue, setListboxValues} from '../internal/listbox/listbox.data';
|
|
4
|
+
import {
|
|
5
|
+
setListboxDisabled,
|
|
6
|
+
setListboxFilter,
|
|
7
|
+
setListboxLimit,
|
|
8
|
+
setListboxReadonly,
|
|
9
|
+
setListboxRequired,
|
|
10
|
+
} from '../internal/listbox/listbox.misc';
|
|
11
|
+
import type {OuiListboxLocalizations, OuiListboxState} from '../internal/listbox/listbox.model';
|
|
12
|
+
import {attachListbox, detachListbox} from '../internal/listbox/listbox.state';
|
|
13
|
+
import {LISTBOX_TAGNAME} from './constants.listbox';
|
|
14
|
+
import {updateListboxLocalizations} from './misc.listbox';
|
|
15
|
+
import {createListboxState, listboxStates, onAttachListbox, onDetachListbox} from './state.listbox';
|
|
16
|
+
|
|
17
|
+
// #region Types
|
|
18
|
+
|
|
19
|
+
declare global {
|
|
20
|
+
interface HTMLElementTagNameMap {
|
|
21
|
+
[LISTBOX_TAGNAME]: OuiListboxElement;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class OuiListboxElement extends HTMLElement {
|
|
26
|
+
static formAssociated = true;
|
|
27
|
+
|
|
28
|
+
static observedAttributes = ['max', 'min', 'required', 'value'];
|
|
29
|
+
|
|
30
|
+
readonly #internals: ElementInternals;
|
|
31
|
+
|
|
32
|
+
#restored?: string[];
|
|
33
|
+
|
|
34
|
+
#state!: OuiListboxState;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Is the listbox disabled?
|
|
38
|
+
*/
|
|
39
|
+
get disabled(): boolean {
|
|
40
|
+
return this.#state?.disabled ?? false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Should the listbox be disabled?
|
|
45
|
+
*/
|
|
46
|
+
set disabled(value: boolean) {
|
|
47
|
+
if (typeof value === 'boolean' && value !== this.disabled) {
|
|
48
|
+
setListboxDisabled(this.#state, value);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Current filter value
|
|
54
|
+
*/
|
|
55
|
+
get filter(): string {
|
|
56
|
+
return this.#state.filter.element.value;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Set the filter value
|
|
61
|
+
*/
|
|
62
|
+
set filter(value: string) {
|
|
63
|
+
setListboxFilter(this.#state, value);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Maximum number of selected options
|
|
68
|
+
*/
|
|
69
|
+
get max(): number | undefined {
|
|
70
|
+
return this.#state?.max;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Set the maximum number of selected options
|
|
75
|
+
*/
|
|
76
|
+
set max(value: number) {
|
|
77
|
+
setListboxLimit('max', this.#state, value);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Minimum number of selected options
|
|
82
|
+
*/
|
|
83
|
+
get min(): number | undefined {
|
|
84
|
+
return this.#state?.min;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Set the minimum number of selected options
|
|
89
|
+
*/
|
|
90
|
+
set min(value: number) {
|
|
91
|
+
setListboxLimit('min', this.#state, value);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Does the listbox allow multiple values?
|
|
96
|
+
*/
|
|
97
|
+
get multiple(): boolean {
|
|
98
|
+
return this.hasAttribute('multiple');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Name of the listbox
|
|
103
|
+
*/
|
|
104
|
+
get name(): string {
|
|
105
|
+
return getAttribute(this, 'name') ?? '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Is the listbox readonly?
|
|
110
|
+
*/
|
|
111
|
+
get readonly(): boolean {
|
|
112
|
+
return this.#state?.readonly ?? false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Should the listbox be readonly?
|
|
117
|
+
*/
|
|
118
|
+
set readonly(value: boolean) {
|
|
119
|
+
if (typeof value === 'boolean' && value !== this.readonly) {
|
|
120
|
+
setListboxReadonly(this.#state, value);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Is the listbox required?
|
|
126
|
+
*/
|
|
127
|
+
get required(): boolean {
|
|
128
|
+
return this.#state?.required ?? false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Should the listbox be required?
|
|
133
|
+
*/
|
|
134
|
+
set required(value: boolean) {
|
|
135
|
+
if (typeof value === 'boolean' && value !== this.required) {
|
|
136
|
+
setListboxRequired(this.#state, value);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Selected options
|
|
142
|
+
*/
|
|
143
|
+
get selected(): HTMLElement[] {
|
|
144
|
+
return this.#state.options.selected.slice();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Current value
|
|
149
|
+
*/
|
|
150
|
+
get value(): string {
|
|
151
|
+
return this.#state.values.current[0];
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Set the value
|
|
156
|
+
*/
|
|
157
|
+
set value(value: unknown) {
|
|
158
|
+
setListboxValue(this.#state, value);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Current values
|
|
163
|
+
*/
|
|
164
|
+
get values(): string[] {
|
|
165
|
+
return this.#state.values.current.slice();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Set the values
|
|
170
|
+
*/
|
|
171
|
+
set values(values: unknown[]) {
|
|
172
|
+
setListboxValues(this.#state, values);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
constructor() {
|
|
176
|
+
super();
|
|
177
|
+
|
|
178
|
+
this.#internals = this.attachInternals();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
attributeChangedCallback(name: string, _: string | null, value: string | null): void {
|
|
182
|
+
if (this.#state == null) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
switch (name) {
|
|
187
|
+
case 'required':
|
|
188
|
+
this[name] = this.hasAttribute(name);
|
|
189
|
+
break;
|
|
190
|
+
|
|
191
|
+
case 'max':
|
|
192
|
+
case 'min':
|
|
193
|
+
setListboxLimit(name, this.#state, value);
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case 'value':
|
|
197
|
+
this.value = value ?? '';
|
|
198
|
+
break;
|
|
199
|
+
|
|
200
|
+
default:
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Clear all selected options
|
|
207
|
+
*/
|
|
208
|
+
clear(): void {
|
|
209
|
+
setListboxValues(this.#state, []);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
connectedCallback(): void {
|
|
213
|
+
if (this.#state == null) {
|
|
214
|
+
this.#initialize();
|
|
215
|
+
} else {
|
|
216
|
+
attachListbox(this.#state, listboxStates, onAttachListbox);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
disconnectedCallback(): void {
|
|
221
|
+
if (this.#state != null) {
|
|
222
|
+
detachListbox(this.#state, listboxStates, onDetachListbox);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
formDisabledCallback(disabled: boolean): void {
|
|
227
|
+
this.disabled = disabled;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
formResetCallback(): void {
|
|
231
|
+
setListboxValues(this.#state, this.#state.values.initial);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
formStateRestoreCallback(state: string | File | FormData, reason: unknown): void {
|
|
235
|
+
if (reason !== 'restore') {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const parsed = typeof state === 'string' ? parse(state) : undefined;
|
|
240
|
+
|
|
241
|
+
if (!Array.isArray(parsed)) {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (this.#state == null) {
|
|
246
|
+
this.#restored = parsed;
|
|
247
|
+
} else {
|
|
248
|
+
setListboxValues(this.#state, parsed);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Set listbox localizations for a specific language
|
|
254
|
+
*
|
|
255
|
+
* @param language Language code
|
|
256
|
+
* @param localizations Localizations for language
|
|
257
|
+
*/
|
|
258
|
+
setLocalizations(language: string, localizations: Partial<OuiListboxLocalizations>): void;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Set listbox localizations for one or more languages
|
|
262
|
+
*
|
|
263
|
+
* @param localizations Localizations for one or more languages
|
|
264
|
+
*/
|
|
265
|
+
setLocalizations(localizations: Record<string, Partial<OuiListboxLocalizations>>): void;
|
|
266
|
+
|
|
267
|
+
setLocalizations(
|
|
268
|
+
first: string | Record<string, Partial<OuiListboxLocalizations>>,
|
|
269
|
+
second?: Partial<OuiListboxLocalizations>,
|
|
270
|
+
): void {
|
|
271
|
+
updateListboxLocalizations(
|
|
272
|
+
this.#state.localizations.global,
|
|
273
|
+
this.#state.localizations.component,
|
|
274
|
+
first,
|
|
275
|
+
second,
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
#initialize(): void {
|
|
280
|
+
this.#state = createListboxState(this, this.#internals);
|
|
281
|
+
|
|
282
|
+
if (this.#restored != null) {
|
|
283
|
+
setListboxValues(this.#state, this.#restored);
|
|
284
|
+
|
|
285
|
+
this.#restored = undefined;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// #endregion
|
|
291
|
+
|
|
292
|
+
// #region Initialization
|
|
293
|
+
|
|
294
|
+
customElements.define(LISTBOX_TAGNAME, OuiListboxElement);
|
|
295
|
+
|
|
296
|
+
// #endregion
|
|
297
|
+
|
|
298
|
+
// #region Exports
|
|
299
|
+
|
|
300
|
+
export {setListboxLocalizations} from './misc.listbox';
|
|
301
|
+
|
|
302
|
+
// #endregion
|