@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.
Files changed (89) hide show
  1. package/dist/css/combobox.css +1 -1
  2. package/dist/css/listbox.css +152 -0
  3. package/dist/js/combobox/constants.combobox.d.mts +2 -2
  4. package/dist/js/combobox/constants.combobox.mjs +40 -33
  5. package/dist/js/combobox/dom.combobox.d.mts +8 -91
  6. package/dist/js/combobox/dom.combobox.mjs +204 -2
  7. package/dist/js/combobox/event.combobox.d.mts +14 -8
  8. package/dist/js/combobox/event.combobox.mjs +2 -2
  9. package/dist/js/combobox/index.d.mts +1 -1
  10. package/dist/js/combobox/index.mjs +20 -18
  11. package/dist/js/combobox/misc.combobox.d.mts +2 -2
  12. package/dist/js/combobox/misc.combobox.mjs +2 -2
  13. package/dist/js/combobox/models.combobox.d.mts +44 -2
  14. package/dist/js/combobox/state.combobox.d.mts +9 -10
  15. package/dist/js/combobox/state.combobox.mjs +2 -2
  16. package/dist/js/constants.combobox-DmRAMC64.d.mts +26 -0
  17. package/dist/js/constants.listbox-DRsXUxd3.d.mts +14 -0
  18. package/dist/js/index-EfkUSals.d.mts +139 -0
  19. package/dist/js/index.d.mts +4 -2
  20. package/dist/js/index.mjs +4 -2
  21. package/dist/js/internal/attributable.mjs +4 -3
  22. package/dist/js/internal/listbox/listbox.constants.d.mts +11 -0
  23. package/dist/js/internal/listbox/listbox.constants.mjs +23 -0
  24. package/dist/js/internal/listbox/listbox.data.d.mts +20 -0
  25. package/dist/js/internal/listbox/listbox.data.mjs +2 -0
  26. package/dist/js/internal/listbox/listbox.dom.d.mts +66 -0
  27. package/dist/js/internal/listbox/listbox.dom.mjs +2 -0
  28. package/dist/js/internal/listbox/listbox.event.d.mts +5 -0
  29. package/dist/js/internal/listbox/listbox.event.mjs +63 -0
  30. package/dist/js/internal/listbox/listbox.form.d.mts +6 -0
  31. package/dist/js/internal/listbox/listbox.form.mjs +52 -0
  32. package/dist/js/internal/listbox/listbox.misc.d.mts +12 -0
  33. package/dist/js/internal/listbox/listbox.misc.mjs +2 -0
  34. package/dist/js/internal/listbox/listbox.model.d.mts +2 -0
  35. package/dist/js/internal/listbox/listbox.model.mjs +1 -0
  36. package/dist/js/internal/listbox/listbox.state.d.mts +7 -0
  37. package/dist/js/internal/listbox/listbox.state.mjs +40 -0
  38. package/dist/js/internal/misc.d.mts +6 -0
  39. package/dist/js/internal/misc.mjs +13 -0
  40. package/dist/js/l10n-Bn-7RcwR.d.mts +20 -0
  41. package/dist/js/l10n.d.mts +2 -20
  42. package/dist/js/listbox/constants.listbox.d.mts +2 -0
  43. package/dist/js/listbox/constants.listbox.mjs +40 -0
  44. package/dist/js/listbox/event.listbox.d.mts +8 -0
  45. package/dist/js/listbox/event.listbox.mjs +2 -0
  46. package/dist/js/listbox/index.d.mts +116 -0
  47. package/dist/js/listbox/index.mjs +189 -0
  48. package/dist/js/listbox/misc.listbox.d.mts +2 -0
  49. package/dist/js/listbox/misc.listbox.mjs +2 -0
  50. package/dist/js/listbox/state.listbox.d.mts +11 -0
  51. package/dist/js/listbox/state.listbox.mjs +2 -0
  52. package/dist/js/listbox.data-BWd0PCy7.mjs +424 -0
  53. package/dist/js/listbox.model-CRReGOmu.d.mts +125 -0
  54. package/dist/js/misc.listbox-BI-XsTOf.mjs +197 -0
  55. package/dist/js/misc.listbox-gnoJhrTr.d.mts +20 -0
  56. package/dist/js/sortable/sortable.embedded.mjs +4 -2
  57. package/dist/js/state.combobox-4xq0FPCF.mjs +365 -0
  58. package/package.json +7 -3
  59. package/src/css/combobox.scss +1 -1
  60. package/src/css/listbox.scss +149 -0
  61. package/src/js/combobox/constants.combobox.ts +46 -25
  62. package/src/js/combobox/dom.combobox.ts +36 -454
  63. package/src/js/combobox/event.combobox.ts +129 -218
  64. package/src/js/combobox/index.ts +37 -30
  65. package/src/js/combobox/misc.combobox.ts +30 -114
  66. package/src/js/combobox/models.combobox.ts +7 -47
  67. package/src/js/combobox/state.combobox.ts +126 -127
  68. package/src/js/index.ts +1 -0
  69. package/src/js/internal/listbox/listbox.constants.ts +25 -0
  70. package/src/js/{combobox/data.combobox.ts → internal/listbox/listbox.data.ts} +94 -43
  71. package/src/js/internal/listbox/listbox.dom.ts +445 -0
  72. package/src/js/internal/listbox/listbox.event.ts +129 -0
  73. package/src/js/{combobox/form.combobox.ts → internal/listbox/listbox.form.ts} +12 -13
  74. package/src/js/internal/listbox/listbox.misc.ts +135 -0
  75. package/src/js/internal/listbox/listbox.model.ts +169 -0
  76. package/src/js/internal/listbox/listbox.state.ts +82 -0
  77. package/src/js/internal/misc.ts +27 -0
  78. package/src/js/l10n.ts +8 -8
  79. package/src/js/listbox/constants.listbox.ts +61 -0
  80. package/src/js/listbox/event.listbox.ts +106 -0
  81. package/src/js/listbox/index.ts +302 -0
  82. package/src/js/listbox/misc.listbox.ts +70 -0
  83. package/src/js/listbox/state.listbox.ts +196 -0
  84. package/dist/js/combobox/data.combobox.d.mts +0 -18
  85. package/dist/js/combobox/data.combobox.mjs +0 -2
  86. package/dist/js/combobox/form.combobox.d.mts +0 -6
  87. package/dist/js/combobox/form.combobox.mjs +0 -2
  88. package/dist/js/constants.combobox-h_kyDegw.d.mts +0 -248
  89. package/dist/js/data.combobox-B8HMkDOh.mjs +0 -1030
@@ -0,0 +1,129 @@
1
+ import {clamp} from '@oscarpalmer/atoms/number';
2
+ import {LISTBOX_KEYS} from './listbox.constants';
3
+ import {getListboxVisibleOptions} from './listbox.misc';
4
+ import type {OuiListboxPreview, OuiListboxState} from './listbox.model';
5
+
6
+ // #region Functions
7
+
8
+ export function processListboxKeydown<State extends OuiListboxState>(
9
+ event: KeyboardEvent,
10
+ state: State,
11
+ ): void {
12
+ const ignore = state.disabled || state.readonly;
13
+ const isSelect = state.type === 'select';
14
+
15
+ let preventDefault = ignore;
16
+
17
+ switch (true) {
18
+ case event.key === ' ': {
19
+ if (!state.disabled) {
20
+ state.callbacks.keydown.onActivation?.(state);
21
+ }
22
+
23
+ break;
24
+ }
25
+
26
+ case event.key === 'Enter': {
27
+ preventDefault = state.callbacks.keydown.preventActivation?.(state) ?? true;
28
+
29
+ if (preventDefault) {
30
+ state.callbacks.keydown.onEnter?.(state);
31
+ } else if (!ignore && state.type === 'select') {
32
+ state.callbacks.keydown.onActivation?.(state);
33
+ } else if (!ignore) {
34
+ state.internals.form?.requestSubmit();
35
+ }
36
+
37
+ break;
38
+ }
39
+
40
+ case event.key === 'Escape': {
41
+ preventDefault = false;
42
+
43
+ state.callbacks.keydown.onEscape?.(state);
44
+
45
+ break;
46
+ }
47
+
48
+ case !isSelect && LISTBOX_KEYS.selection.has(event.key): {
49
+ if (!ignore) {
50
+ state.callbacks.keydown.onSelection?.(state);
51
+ }
52
+
53
+ break;
54
+ }
55
+
56
+ case LISTBOX_KEYS.navigation.has(event.key): {
57
+ if (!state.disabled) {
58
+ onNavigation(event, state);
59
+ }
60
+
61
+ break;
62
+ }
63
+
64
+ default: {
65
+ preventDefault = false;
66
+
67
+ if (event.key === 'Tab') {
68
+ state.callbacks.keydown.onTab?.(state);
69
+ }
70
+
71
+ break;
72
+ }
73
+ }
74
+
75
+ if (preventDefault) {
76
+ event.preventDefault();
77
+ event.stopPropagation();
78
+ }
79
+ }
80
+
81
+ function onNavigation<State extends OuiListboxState>(event: KeyboardEvent, state: State): void {
82
+ const skip = state.callbacks.keydown.skipNavigation?.(state) ?? false;
83
+ const visible = getListboxVisibleOptions(state);
84
+
85
+ if (visible.length === 0) {
86
+ return;
87
+ }
88
+
89
+ let next: HTMLElement | undefined;
90
+
91
+ if (skip || state.active == null) {
92
+ next = state.active ?? visible.at(event.key === 'ArrowDown' ? 0 : -1);
93
+ } else {
94
+ const current = visible.findIndex(option => option.id === state.active?.id);
95
+
96
+ let index: number | undefined;
97
+
98
+ switch (event.key) {
99
+ case 'ArrowDown':
100
+ case 'ArrowUp':
101
+ index = clamp(current + (event.key === 'ArrowDown' ? 1 : -1), 0, visible.length - 1, true);
102
+ break;
103
+
104
+ case 'PageDown':
105
+ case 'PageUp':
106
+ index = clamp(current + (event.key === 'PageDown' ? 10 : -10), 0, visible.length - 1);
107
+ break;
108
+
109
+ default:
110
+ break;
111
+ }
112
+
113
+ if (index != null) {
114
+ next = visible[index];
115
+ }
116
+ }
117
+
118
+ let preview: OuiListboxPreview;
119
+
120
+ if (state.type === 'autocomplete' && (state.autocomplete.both || state.autocomplete.inline)) {
121
+ preview = 'set';
122
+ } else {
123
+ preview = 'none';
124
+ }
125
+
126
+ state.callbacks.keydown.onNavigation(preview, state, next ?? state.active ?? visible[0]);
127
+ }
128
+
129
+ // #endregion
@@ -1,13 +1,11 @@
1
1
  import {dispatch} from '@oscarpalmer/toretto/event';
2
- import {getLocalization, localize} from '../l10n';
3
- import {updateComboboxAnchor} from './dom.combobox';
4
- import type {OuiComboboxState} from './models.combobox';
5
- import {comboboxLocalizations} from './state.combobox';
2
+ import {getLocalization, localize} from '../../l10n';
3
+ import type {OuiListboxState} from './listbox.model';
6
4
 
7
5
  // #region Functions
8
6
 
9
- export function updateComboboxForm(
10
- state: OuiComboboxState,
7
+ export function updateListboxForm<State extends OuiListboxState>(
8
+ state: State,
11
9
  values: string[],
12
10
  emit?: boolean,
13
11
  ): void {
@@ -29,23 +27,24 @@ export function updateComboboxForm(
29
27
 
30
28
  state.internals.setFormValue(value, JSON.stringify(values));
31
29
 
32
- updateComboboxAnchor(state);
33
- updateComboboxValidity(state);
30
+ state.callbacks.data.onForm(state);
31
+
32
+ updateListboxValidity(state);
34
33
 
35
34
  if (emit ?? true) {
36
35
  dispatch(state.host, 'change');
37
36
  }
38
37
  }
39
38
 
40
- export function updateComboboxValidity(state: OuiComboboxState): void {
41
- if (state.autocomplete.none) {
39
+ export function updateListboxValidity(state: OuiListboxState): void {
40
+ if (state.autocomplete.none && !state.listbox) {
42
41
  const valueMissing = state.required && state.filter.element.value.length === 0;
43
42
 
44
43
  const localization = getLocalization(
45
- comboboxLocalizations,
44
+ state.localizations.global,
46
45
  state.host,
47
46
  'invalid',
48
- state.localizations,
47
+ state.localizations.component,
49
48
  );
50
49
 
51
50
  state.internals.setValidity({valueMissing}, localize(localization, {}, []));
@@ -97,7 +96,7 @@ export function updateComboboxValidity(state: OuiComboboxState): void {
97
96
  const localization =
98
97
  key == null
99
98
  ? undefined
100
- : getLocalization(comboboxLocalizations, state.host, key, state.localizations);
99
+ : getLocalization(state.localizations.global, state.host, key, state.localizations.component);
101
100
 
102
101
  const message = localization == null ? undefined : localize(localization, data, parameters);
103
102
 
@@ -0,0 +1,135 @@
1
+ import {setAria} from '@oscarpalmer/toretto/aria';
2
+ import {setAttribute} from '@oscarpalmer/toretto/attribute';
3
+ import {filterListbox} from './listbox.data';
4
+ import {updateListboxValidity} from './listbox.form';
5
+ import type {OuiListboxState} from './listbox.model';
6
+
7
+ // #region Functions
8
+
9
+ export function generateListboxIds(prefix: string, select: HTMLSelectElement): void {
10
+ const elements = [...select.children].flatMap(child =>
11
+ child instanceof HTMLOptGroupElement
12
+ ? [
13
+ child,
14
+ ...[...child.children].filter(groupChild => groupChild instanceof HTMLOptionElement),
15
+ ]
16
+ : child instanceof HTMLOptionElement
17
+ ? [child]
18
+ : [],
19
+ );
20
+
21
+ const {length} = elements;
22
+
23
+ for (let index = 0; index < length; index += 1) {
24
+ const element = elements[index];
25
+
26
+ if (element.id === '') {
27
+ element.id = `${prefix}${element instanceof HTMLOptGroupElement ? '_group_' : '_option_'}${index}`;
28
+ }
29
+ }
30
+ }
31
+
32
+ export function getListboxVisibleOptions(state: OuiListboxState): HTMLElement[] {
33
+ return state.options.all.filter(option => !option.hidden);
34
+ }
35
+
36
+ export function setListboxDisabled<State extends OuiListboxState>(
37
+ state: State,
38
+ value: boolean,
39
+ ): void {
40
+ state.filter.element.readOnly = state.readonly || value;
41
+
42
+ setProperties('disabled', state, value);
43
+ }
44
+
45
+ export function setListboxFilter<State extends OuiListboxState>(
46
+ state: State,
47
+ value: unknown,
48
+ ): void {
49
+ if (typeof value !== 'string') {
50
+ return;
51
+ }
52
+
53
+ const type: InputEvent['inputType'] =
54
+ value.length < state.filter.element.value.length ? 'deleteContentBackward' : 'insertText';
55
+
56
+ state.filter.element.value = value;
57
+
58
+ filterListbox(type, state);
59
+ }
60
+
61
+ export function setListboxLimit<State extends OuiListboxState>(
62
+ type: 'max' | 'min',
63
+ state: State,
64
+ value?: unknown,
65
+ ): void {
66
+ let actual = value;
67
+
68
+ if (typeof actual === 'string') {
69
+ actual = Number.parseInt(actual, 10);
70
+ }
71
+
72
+ const updateLimit =
73
+ typeof actual === 'number' && !Number.isNaN(actual) && actual >= 0
74
+ ? actual !== state[type]
75
+ : actual == null;
76
+
77
+ if (updateLimit) {
78
+ state[type] = actual as number | undefined;
79
+
80
+ setAttribute(state.host, type, actual);
81
+
82
+ updateListboxValidity(state);
83
+ }
84
+ }
85
+
86
+ export function setListboxMultiple(element: HTMLElement, value: boolean): void {
87
+ if (value) {
88
+ element.setAttribute('multiple', '');
89
+ } else {
90
+ element.removeAttribute('multiple');
91
+ }
92
+ }
93
+
94
+ function setProperties<State extends OuiListboxState>(
95
+ key: 'disabled' | 'readonly' | 'required',
96
+ state: State,
97
+ value: boolean,
98
+ ): void {
99
+ state[key] = value;
100
+
101
+ const {length} = state.elements;
102
+
103
+ for (let index = 0; index < length; index += 1) {
104
+ const element = state.elements[index];
105
+
106
+ if (element == null) {
107
+ continue;
108
+ }
109
+
110
+ setAria(
111
+ element,
112
+ key,
113
+ key !== 'readonly' || !(element instanceof HTMLButtonElement) ? value : undefined,
114
+ );
115
+ }
116
+ }
117
+
118
+ export function setListboxReadonly<State extends OuiListboxState>(
119
+ state: State,
120
+ value: boolean,
121
+ ): void {
122
+ state.filter.element.readOnly = state.disabled || value;
123
+
124
+ setProperties('readonly', state, value);
125
+ }
126
+
127
+ export function setListboxRequired<State extends OuiListboxState>(
128
+ state: State,
129
+ value: boolean,
130
+ ): void {
131
+ setProperties('required', state, value);
132
+ updateListboxValidity(state);
133
+ }
134
+
135
+ // #endregion
@@ -0,0 +1,169 @@
1
+ import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
2
+ import type {OuiLocalizations, OuiLocalizationsValues} from '../../l10n';
3
+
4
+ // #region Types
5
+
6
+ export type OuiListboxAttributes = {
7
+ active: string;
8
+ content: string;
9
+ empty: string;
10
+ filter: string;
11
+ group: string;
12
+ header: string;
13
+ hidden: string;
14
+ input: string;
15
+ list: string;
16
+ message: string;
17
+ option: string;
18
+ };
19
+
20
+ export type OuiListboxAutoComplete = Record<OuiListboxAutoCompleteType, boolean> & {
21
+ value?: OuiListboxAutoCompleteType;
22
+ };
23
+
24
+ export type OuiListboxAutoCompleteType = 'both' | 'inline' | 'list' | 'none';
25
+
26
+ export type OuiListboxCallbacks<State extends OuiListboxState = OuiListboxState> = {
27
+ data: OuiListboxCallbacksData<State>;
28
+ keydown: OuiListboxCallbacksKeydown<State>;
29
+ };
30
+
31
+ type OuiListboxCallbacksData<State extends OuiListboxState = OuiListboxState> = {
32
+ onApply: OuiListboxCallbacksDataOnApply<State>;
33
+ onFilter: OuiListboxCallbacksDataOnFilter<State>;
34
+ onForm: (state: State) => void;
35
+ onPreview: OuiListboxCallbacksDataOnPreview<State>;
36
+ onToggle: (state: State) => void;
37
+ };
38
+
39
+ type OuiListboxCallbacksDataOnApply<State extends OuiListboxState = OuiListboxState> = (
40
+ state: State,
41
+ option: HTMLElement | undefined,
42
+ values: string[],
43
+ added: boolean,
44
+ selected?: HTMLElement[],
45
+ emit?: boolean,
46
+ ) => void;
47
+
48
+ type OuiListboxCallbacksDataOnFilter<State extends OuiListboxState = OuiListboxState> = (
49
+ preview: OuiListboxPreview,
50
+ state: State,
51
+ update: boolean,
52
+ active?: HTMLElement,
53
+ emit?: boolean,
54
+ ) => void;
55
+
56
+ type OuiListboxCallbacksDataOnPreview<State extends OuiListboxState = OuiListboxState> = (
57
+ preview: OuiListboxPreview,
58
+ state: State,
59
+ active?: HTMLElement,
60
+ ) => void;
61
+
62
+ type OuiListboxCallbacksKeydown<State extends OuiListboxState = OuiListboxState> = {
63
+ onActivation?: (state: State) => void;
64
+ onEnter?: (state: State) => void;
65
+ onEscape?: (state: State) => void;
66
+ onNavigation: OuiListboxCallbacksDataOnNavigation<State>;
67
+ onSelection?: (state: State) => void;
68
+ onTab?: (state: State) => void;
69
+ onToggle?: (state: State) => void;
70
+ preventActivation?: (state: State) => boolean;
71
+ skipNavigation?: (state: State) => boolean;
72
+ };
73
+
74
+ type OuiListboxCallbacksDataOnNavigation<State extends OuiListboxState = OuiListboxState> = (
75
+ preview: OuiListboxPreview,
76
+ state: State,
77
+ active?: HTMLElement,
78
+ ) => void;
79
+
80
+ type OuiListboxFilter = {
81
+ element: HTMLInputElement;
82
+ value: string;
83
+ };
84
+
85
+ type OuiListboxFooter = {
86
+ container: HTMLElement;
87
+ message: HTMLElement;
88
+ };
89
+
90
+ export type OuiListboxGroup = {
91
+ element: HTMLElement;
92
+ header: HTMLElement;
93
+ options: HTMLElement[];
94
+ };
95
+
96
+ type OuiListboxLabel = {
97
+ element: HTMLLabelElement;
98
+ id: string;
99
+ };
100
+
101
+ type OuiListboxLocalization<Localizations extends OuiLocalizationsValues> = {
102
+ component: Record<string, Partial<Localizations>>;
103
+ global: OuiLocalizations<Localizations>;
104
+ };
105
+
106
+ export type OuiListboxLocalizations = {
107
+ empty: string | (() => string);
108
+ input: string | (() => string);
109
+ invalid: string | (() => string);
110
+ max: string | ((max: number) => string);
111
+ min: string | ((min: number) => string);
112
+ remove: string | ((label: string, value: string) => string);
113
+ results: string | ((amount: number) => string);
114
+ selected: string | ((amount: number) => string);
115
+ };
116
+
117
+ export type OuiListboxOptions = {
118
+ all: HTMLElement[];
119
+ selected: HTMLElement[];
120
+ };
121
+
122
+ export type OuiListboxPreview = 'none' | 'set' | 'typed';
123
+
124
+ export type OuiListboxState = {
125
+ active?: HTMLElement;
126
+ attributes: OuiListboxAttributes;
127
+ autocomplete: OuiListboxAutoComplete;
128
+ callbacks: OuiListboxCallbacks;
129
+ content: HTMLDivElement;
130
+ disabled: boolean;
131
+ elements: Array<HTMLElement | undefined>;
132
+ filter: OuiListboxFilter;
133
+ footer?: OuiListboxFooter;
134
+ groups?: OuiListboxGroup[];
135
+ host: HTMLElement;
136
+ id: string;
137
+ internals: ElementInternals;
138
+ label: OuiListboxLabel;
139
+ list: HTMLUListElement;
140
+ listbox: boolean;
141
+ listeners: RemovableEventListener[];
142
+ localizations: OuiListboxLocalization<OuiListboxLocalizations>;
143
+ max?: number;
144
+ min?: number;
145
+ message: HTMLElement;
146
+ multiple: boolean;
147
+ name: string;
148
+ options: OuiListboxOptions;
149
+ readonly: boolean;
150
+ required: boolean;
151
+ select: HTMLSelectElement;
152
+ type: OuiListboxType;
153
+ values: OuiListboxValues;
154
+ };
155
+
156
+ export type OuiListboxStates<State extends OuiListboxState = OuiListboxState> = {
157
+ map: WeakMap<HTMLElement, State>;
158
+ set: Set<State>;
159
+ };
160
+
161
+ export type OuiListboxType = 'autocomplete' | 'select';
162
+
163
+ type OuiListboxValues = {
164
+ current: string[];
165
+ initial: string[];
166
+ label: HTMLElement;
167
+ };
168
+
169
+ // #endregion
@@ -0,0 +1,82 @@
1
+ import {getAttribute} from '@oscarpalmer/toretto/attribute';
2
+ import {filterListboxOptions} from './listbox.data';
3
+ import {updateListboxMessage, updateListboxPreview, updateListboxValueLabel} from './listbox.dom';
4
+ import {updateListboxForm} from './listbox.form';
5
+ import {
6
+ setListboxDisabled,
7
+ setListboxLimit,
8
+ setListboxMultiple,
9
+ setListboxReadonly,
10
+ setListboxRequired,
11
+ } from './listbox.misc';
12
+ import type {OuiListboxState, OuiListboxStates} from './listbox.model';
13
+
14
+ // #region Functions
15
+
16
+ export function attachListbox<State extends OuiListboxState>(
17
+ state: State,
18
+ states: OuiListboxStates<State>,
19
+ onAttach: (state: State) => void,
20
+ ): void {
21
+ const {filter, host, label} = state;
22
+
23
+ label.element.htmlFor = filter.element.id;
24
+ label.element.id = `${state.id}_label`;
25
+
26
+ onAttach(state);
27
+
28
+ states.map.set(host, state);
29
+ states.set.add(state);
30
+ }
31
+
32
+ export function detachListbox<State extends OuiListboxState>(
33
+ state: State,
34
+ states: OuiListboxStates<State>,
35
+ onDetach: (state: State) => void,
36
+ ): void {
37
+ const {host, label, select} = state;
38
+
39
+ const listeners = state.listeners.splice(0);
40
+ const {length} = listeners;
41
+
42
+ for (let index = 0; index < length; index += 1) {
43
+ listeners[index]();
44
+ }
45
+
46
+ label.element.htmlFor = select.id;
47
+ label.element.id = label.id;
48
+
49
+ onDetach(state);
50
+
51
+ states.map.delete(host);
52
+ states.set.delete(state);
53
+ }
54
+
55
+ export function initializeListboxState<State extends OuiListboxState>(
56
+ state: State,
57
+ states: OuiListboxStates<State>,
58
+ onAttach: (state: State) => void,
59
+ ): State {
60
+ attachListbox(state, states, onAttach);
61
+
62
+ setListboxDisabled(state, state.disabled);
63
+ setListboxLimit('max', state, getAttribute(state.host, 'max'));
64
+ setListboxLimit('min', state, getAttribute(state.host, 'min'));
65
+ setListboxMultiple(state.host, state.multiple);
66
+ setListboxReadonly(state, state.readonly);
67
+ setListboxRequired(state, state.required);
68
+
69
+ filterListboxOptions(state);
70
+
71
+ updateListboxForm(state, state.values.current, false);
72
+ updateListboxMessage(state);
73
+ updateListboxValueLabel(state);
74
+
75
+ if (state.options.selected.length > 0) {
76
+ updateListboxPreview('none', state, state.options.selected[0]);
77
+ }
78
+
79
+ return state;
80
+ }
81
+
82
+ // #endregion
@@ -0,0 +1,27 @@
1
+ import {getAria} from '@oscarpalmer/toretto/aria';
2
+ import {getAttribute} from '@oscarpalmer/toretto/attribute';
3
+ import type {AnyAriaBooleanAttribute} from '@oscarpalmer/toretto/models';
4
+
5
+ // #region Functions
6
+
7
+ export function getAttributeValue<Value>(
8
+ element: HTMLElement,
9
+ attribute: string,
10
+ possible: Set<Value>,
11
+ defaultValue: Value,
12
+ ): Value {
13
+ const value = getAttribute(element, attribute);
14
+
15
+ return possible.has(value as Value) ? (value as Value) : defaultValue;
16
+ }
17
+
18
+ export function getBooleanAriaValue(
19
+ element: HTMLElement,
20
+ attribute: AnyAriaBooleanAttribute,
21
+ ): boolean {
22
+ const value = getAria(element, attribute);
23
+
24
+ return value === true || value === 'true';
25
+ }
26
+
27
+ // #endregion
package/src/js/l10n.ts CHANGED
@@ -7,9 +7,7 @@ type LocalizationParameters<Localization> = Localization extends GenericCallback
7
7
  ? Parameters<Localization>
8
8
  : never;
9
9
 
10
- type OuiLocalizationValues = Record<string, string | GenericCallback>;
11
-
12
- export class OuiLocalizations<Localizations extends OuiLocalizationValues> {
10
+ export class OuiLocalizations<Localizations extends OuiLocalizationsValues> {
13
11
  readonly defaults: Localizations;
14
12
 
15
13
  readonly keys: Set<keyof Localizations>;
@@ -49,6 +47,8 @@ export class OuiLocalizations<Localizations extends OuiLocalizationValues> {
49
47
  }
50
48
  }
51
49
 
50
+ export type OuiLocalizationsValues = Record<string, string | GenericCallback>;
51
+
52
52
  // #endregion
53
53
 
54
54
  // #region Functions
@@ -60,7 +60,7 @@ export function getLanguage(element: Element): string {
60
60
  }
61
61
 
62
62
  export function getLocalization<
63
- Localizations extends OuiLocalizationValues,
63
+ Localizations extends OuiLocalizationsValues,
64
64
  Key extends keyof Localizations,
65
65
  >(
66
66
  localizations: OuiLocalizations<Localizations>,
@@ -101,14 +101,14 @@ export function getLocalization<
101
101
  return localization ?? localizations.defaults[key];
102
102
  }
103
103
 
104
- export function getLocalizations<Localizations extends OuiLocalizationValues>(
104
+ export function getLocalizations<Localizations extends OuiLocalizationsValues>(
105
105
  defaults: Localizations,
106
106
  ): OuiLocalizations<Localizations> {
107
107
  return new OuiLocalizations(defaults);
108
108
  }
109
109
 
110
110
  export function localize<
111
- Localizations extends OuiLocalizationValues,
111
+ Localizations extends OuiLocalizationsValues,
112
112
  Key extends keyof Localizations,
113
113
  Localization extends Localizations[Key],
114
114
  >(
@@ -135,10 +135,10 @@ export function localize<
135
135
  return localized;
136
136
  }
137
137
 
138
- export function updateLocalizations<Localizations extends OuiLocalizationValues>(
138
+ export function updateLocalizations<Localizations extends OuiLocalizationsValues>(
139
139
  instance: OuiLocalizations<Localizations>,
140
140
  values: Record<string, Partial<Localizations>>,
141
- destination?: Record<string, Partial<Localizations>>,
141
+ destination: Record<string, Partial<Localizations>>,
142
142
  onAfter?: () => void,
143
143
  ): void {
144
144
  if (typeof values !== 'object' || values == null) {