@oscarpalmer/oui 0.35.0 → 0.37.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 (128) hide show
  1. package/dist/css/badge.css +18 -15
  2. package/dist/css/button.css +35 -31
  3. package/dist/css/color.css +20 -20
  4. package/dist/css/combobox.css +134 -35
  5. package/dist/css/content.css +29 -10
  6. package/dist/css/floatable.css +10 -10
  7. package/dist/css/form.css +393 -129
  8. package/dist/css/listbox.css +176 -0
  9. package/dist/css/message.css +13 -10
  10. package/dist/css/reset.css +29 -88
  11. package/dist/css/splitter.css +2 -2
  12. package/dist/css/tabs.css +103 -0
  13. package/dist/js/combobox/constants.combobox.d.mts +2 -2
  14. package/dist/js/combobox/constants.combobox.mjs +40 -33
  15. package/dist/js/combobox/dom.combobox.d.mts +8 -91
  16. package/dist/js/combobox/dom.combobox.mjs +204 -2
  17. package/dist/js/combobox/event.combobox.d.mts +14 -8
  18. package/dist/js/combobox/event.combobox.mjs +2 -2
  19. package/dist/js/combobox/index.d.mts +1 -1
  20. package/dist/js/combobox/index.mjs +20 -18
  21. package/dist/js/combobox/misc.combobox.d.mts +2 -2
  22. package/dist/js/combobox/misc.combobox.mjs +2 -2
  23. package/dist/js/combobox/models.combobox.d.mts +44 -2
  24. package/dist/js/combobox/state.combobox.d.mts +9 -10
  25. package/dist/js/combobox/state.combobox.mjs +2 -2
  26. package/dist/js/constants.combobox-DmRAMC64.d.mts +26 -0
  27. package/dist/js/constants.listbox-DRsXUxd3.d.mts +14 -0
  28. package/dist/js/focus-trap/focus-trap.embedded.mjs +6 -10
  29. package/dist/js/index-EfkUSals.d.mts +139 -0
  30. package/dist/js/index.d.mts +5 -2
  31. package/dist/js/index.mjs +5 -2
  32. package/dist/js/internal/attributable.mjs +4 -3
  33. package/dist/js/internal/draggable.mjs +16 -22
  34. package/dist/js/internal/listbox/listbox.constants.d.mts +11 -0
  35. package/dist/js/internal/listbox/listbox.constants.mjs +23 -0
  36. package/dist/js/internal/listbox/listbox.data.d.mts +20 -0
  37. package/dist/js/internal/listbox/listbox.data.mjs +2 -0
  38. package/dist/js/internal/listbox/listbox.dom.d.mts +66 -0
  39. package/dist/js/internal/listbox/listbox.dom.mjs +2 -0
  40. package/dist/js/internal/listbox/listbox.event.d.mts +5 -0
  41. package/dist/js/internal/listbox/listbox.event.mjs +63 -0
  42. package/dist/js/internal/listbox/listbox.form.d.mts +6 -0
  43. package/dist/js/internal/listbox/listbox.form.mjs +52 -0
  44. package/dist/js/internal/listbox/listbox.misc.d.mts +12 -0
  45. package/dist/js/internal/listbox/listbox.misc.mjs +2 -0
  46. package/dist/js/internal/listbox/listbox.model.d.mts +2 -0
  47. package/dist/js/internal/listbox/listbox.model.mjs +1 -0
  48. package/dist/js/internal/listbox/listbox.state.d.mts +7 -0
  49. package/dist/js/internal/listbox/listbox.state.mjs +40 -0
  50. package/dist/js/internal/misc.d.mts +6 -0
  51. package/dist/js/internal/misc.mjs +13 -0
  52. package/dist/js/internal/navigable.d.mts +11 -5
  53. package/dist/js/internal/navigable.mjs +44 -18
  54. package/dist/js/l10n-Bn-7RcwR.d.mts +20 -0
  55. package/dist/js/l10n.d.mts +2 -20
  56. package/dist/js/listbox/constants.listbox.d.mts +2 -0
  57. package/dist/js/listbox/constants.listbox.mjs +40 -0
  58. package/dist/js/listbox/event.listbox.d.mts +8 -0
  59. package/dist/js/listbox/event.listbox.mjs +2 -0
  60. package/dist/js/listbox/index.d.mts +116 -0
  61. package/dist/js/listbox/index.mjs +189 -0
  62. package/dist/js/listbox/misc.listbox.d.mts +2 -0
  63. package/dist/js/listbox/misc.listbox.mjs +2 -0
  64. package/dist/js/listbox/state.listbox.d.mts +11 -0
  65. package/dist/js/listbox/state.listbox.mjs +2 -0
  66. package/dist/js/listbox.data-BWd0PCy7.mjs +424 -0
  67. package/dist/js/listbox.model-CRReGOmu.d.mts +125 -0
  68. package/dist/js/misc.listbox-BI-XsTOf.mjs +197 -0
  69. package/dist/js/misc.listbox-gnoJhrTr.d.mts +20 -0
  70. package/dist/js/movable/movable.embedded.mjs +3 -6
  71. package/dist/js/resizable/resizable.embedded.mjs +16 -26
  72. package/dist/js/sortable/sortable.embedded.mjs +4 -2
  73. package/dist/js/splitter.mjs +15 -12
  74. package/dist/js/state.combobox-4xq0FPCF.mjs +365 -0
  75. package/dist/js/tabs.d.mts +25 -0
  76. package/dist/js/tabs.mjs +240 -0
  77. package/dist/js/tooltip.mjs +13 -12
  78. package/package.json +7 -3
  79. package/src/css/badge.scss +6 -18
  80. package/src/css/button.scss +6 -45
  81. package/src/css/color.scss +20 -20
  82. package/src/css/combobox.scss +21 -52
  83. package/src/css/content.scss +30 -3
  84. package/src/css/floatable.scss +17 -17
  85. package/src/css/form.scss +84 -72
  86. package/src/css/listbox.scss +120 -0
  87. package/src/css/message.scss +13 -28
  88. package/src/css/mixins.scss +253 -28
  89. package/src/css/reset.scss +37 -86
  90. package/src/css/splitter.scss +2 -2
  91. package/src/css/tabs.scss +85 -0
  92. package/src/js/combobox/constants.combobox.ts +46 -25
  93. package/src/js/combobox/dom.combobox.ts +36 -454
  94. package/src/js/combobox/event.combobox.ts +129 -218
  95. package/src/js/combobox/index.ts +37 -30
  96. package/src/js/combobox/misc.combobox.ts +30 -114
  97. package/src/js/combobox/models.combobox.ts +7 -47
  98. package/src/js/combobox/state.combobox.ts +126 -127
  99. package/src/js/focus-trap/focus-trap.embedded.ts +11 -19
  100. package/src/js/index.ts +2 -0
  101. package/src/js/internal/draggable.ts +29 -33
  102. package/src/js/internal/listbox/listbox.constants.ts +25 -0
  103. package/src/js/{combobox/data.combobox.ts → internal/listbox/listbox.data.ts} +94 -43
  104. package/src/js/internal/listbox/listbox.dom.ts +445 -0
  105. package/src/js/internal/listbox/listbox.event.ts +129 -0
  106. package/src/js/{combobox/form.combobox.ts → internal/listbox/listbox.form.ts} +13 -14
  107. package/src/js/internal/listbox/listbox.misc.ts +135 -0
  108. package/src/js/internal/listbox/listbox.model.ts +169 -0
  109. package/src/js/internal/listbox/listbox.state.ts +82 -0
  110. package/src/js/internal/misc.ts +27 -0
  111. package/src/js/internal/navigable.ts +80 -23
  112. package/src/js/l10n.ts +8 -8
  113. package/src/js/listbox/constants.listbox.ts +61 -0
  114. package/src/js/listbox/event.listbox.ts +106 -0
  115. package/src/js/listbox/index.ts +302 -0
  116. package/src/js/listbox/misc.listbox.ts +70 -0
  117. package/src/js/listbox/state.listbox.ts +196 -0
  118. package/src/js/movable/movable.embedded.ts +11 -13
  119. package/src/js/resizable/resizable.embedded.ts +25 -39
  120. package/src/js/splitter.ts +21 -18
  121. package/src/js/tabs.ts +447 -0
  122. package/src/js/tooltip.ts +20 -17
  123. package/dist/js/combobox/data.combobox.d.mts +0 -18
  124. package/dist/js/combobox/data.combobox.mjs +0 -2
  125. package/dist/js/combobox/form.combobox.d.mts +0 -6
  126. package/dist/js/combobox/form.combobox.mjs +0 -2
  127. package/dist/js/constants.combobox-h_kyDegw.d.mts +0 -248
  128. package/dist/js/data.combobox-B8HMkDOh.mjs +0 -1030
@@ -0,0 +1,445 @@
1
+ import {createElement, setAria, setAttribute} from '@oscarpalmer/toretto';
2
+ import {getLocalization, localize} from '../../l10n';
3
+ import {listboxScrollOptions} from './listbox.constants';
4
+ import {getListboxVisibleOptions} from './listbox.misc';
5
+ import type {
6
+ OuiListboxAttributes,
7
+ OuiListboxAutoComplete,
8
+ OuiListboxGroup,
9
+ OuiListboxLocalizations,
10
+ OuiListboxOptions,
11
+ OuiListboxPreview,
12
+ OuiListboxState,
13
+ OuiListboxType,
14
+ } from './listbox.model';
15
+
16
+ // #region Types
17
+
18
+ export type CreateListboxContent = {
19
+ element: HTMLDivElement;
20
+ filter?: CreateListboxFilter;
21
+ list: CreateListboxList;
22
+ message: HTMLDivElement;
23
+ value: HTMLSpanElement;
24
+ };
25
+
26
+ export type CreateListboxContentParameters = {
27
+ attributes: OuiListboxAttributes;
28
+ autocomplete: OuiListboxAutoComplete;
29
+ element: HTMLElement;
30
+ id: string;
31
+ label: HTMLLabelElement;
32
+ listbox: boolean;
33
+ multiple: boolean;
34
+ select: HTMLSelectElement;
35
+ type: OuiListboxType;
36
+ };
37
+
38
+ export type CreateListboxFilter = {
39
+ container: HTMLElement;
40
+ element: HTMLInputElement;
41
+ };
42
+
43
+ type CreateListboxGroupParameters = {
44
+ group: HTMLOptGroupElement;
45
+ groupOptions: HTMLOptionElement[];
46
+ original: CreateListboxContentParameters;
47
+ selected: HTMLElement[];
48
+ type: OuiListboxType;
49
+ };
50
+
51
+ export type CreateListboxList = {
52
+ element: HTMLUListElement;
53
+ groups?: OuiListboxGroup[];
54
+ options: OuiListboxOptions;
55
+ };
56
+
57
+ type CreateListboxOptionParameters = {
58
+ group?: string;
59
+ index: number;
60
+ option: HTMLOptionElement;
61
+ original: CreateListboxContentParameters;
62
+ type: OuiListboxType;
63
+ };
64
+
65
+ type CreateListboxOptionsParameters = {
66
+ group?: string;
67
+ list: HTMLElement;
68
+ options: HTMLOptionElement[];
69
+ original: CreateListboxContentParameters;
70
+ selected: HTMLElement[];
71
+ type: OuiListboxType;
72
+ };
73
+
74
+ // #endregion
75
+
76
+ // #region Functions
77
+
78
+ export function createListboxContent(
79
+ parameters: CreateListboxContentParameters,
80
+ ): CreateListboxContent {
81
+ const element = createElement('div', {
82
+ attribute: {
83
+ [parameters.attributes.content]: '',
84
+ },
85
+ });
86
+
87
+ const filter = createListboxFilter(parameters);
88
+ const list = createListboxList(parameters);
89
+ const message = createListboxMessage(parameters.attributes.message);
90
+ const value = createListboxValueLabel(parameters.id);
91
+
92
+ if (filter != null) {
93
+ element.append(filter.container);
94
+ }
95
+
96
+ element.append(list.element, message, value);
97
+
98
+ return {
99
+ element,
100
+ filter,
101
+ list,
102
+ message,
103
+ value,
104
+ };
105
+ }
106
+
107
+ export function createListboxFilter(
108
+ parameters: CreateListboxContentParameters,
109
+ ): CreateListboxFilter | undefined {
110
+ if (parameters.type === 'autocomplete') {
111
+ return;
112
+ }
113
+
114
+ const container = createElement('div', {
115
+ attribute: {
116
+ [parameters.attributes.filter]: '',
117
+ },
118
+ });
119
+
120
+ const element = createListboxInput(parameters, '');
121
+
122
+ setAttribute(element, parameters.attributes.input, '');
123
+
124
+ container.append(element);
125
+
126
+ return {container, element};
127
+ }
128
+
129
+ export function createListboxGroup(parameters: CreateListboxGroupParameters): OuiListboxGroup {
130
+ const {group, groupOptions, selected, type} = parameters;
131
+
132
+ const element = createElement('li', {
133
+ aria: {
134
+ label: group.label,
135
+ },
136
+ property: {
137
+ role: 'group',
138
+ },
139
+ });
140
+
141
+ const list = createElement('ul', {
142
+ property: {
143
+ role: 'none',
144
+ },
145
+ });
146
+
147
+ const header = createListboxHeader(parameters, group);
148
+
149
+ element.append(header, list);
150
+
151
+ return {
152
+ element,
153
+ header,
154
+ options: createListboxOptions({
155
+ list,
156
+ selected,
157
+ type,
158
+ group: header.id,
159
+ options: groupOptions,
160
+ original: parameters.original,
161
+ }),
162
+ };
163
+ }
164
+
165
+ export function createListboxHeader(
166
+ parameters: CreateListboxGroupParameters,
167
+ group: HTMLOptGroupElement,
168
+ ): HTMLElement {
169
+ return createElement('div', {
170
+ aria: {
171
+ hidden: true,
172
+ },
173
+ attribute: {
174
+ [parameters.original.attributes.header]: '',
175
+ },
176
+ property: {
177
+ id: group.id,
178
+ textContent: group.label,
179
+ },
180
+ });
181
+ }
182
+
183
+ export function createListboxInput(
184
+ parameters: CreateListboxContentParameters,
185
+ value: string,
186
+ ): HTMLInputElement {
187
+ const {autocomplete, id, label, listbox, type} = parameters;
188
+
189
+ const isAutocomplete = type === 'autocomplete';
190
+
191
+ const input = createElement('input', {
192
+ aria: {
193
+ autocomplete: autocomplete.value,
194
+ controls: `${id}_list`,
195
+ haspopup: isAutocomplete ? 'listbox' : undefined,
196
+ labelledby: label.id,
197
+ },
198
+ attribute: {
199
+ autocorrect: 'off',
200
+ autocomplete: 'off',
201
+ form: '',
202
+ spellcheck: 'false',
203
+ type: 'text',
204
+ },
205
+ property: {
206
+ id,
207
+ value,
208
+ role: isAutocomplete || listbox ? 'combobox' : 'searchbox',
209
+ },
210
+ });
211
+
212
+ if (isAutocomplete || listbox) {
213
+ setAria(input, 'expanded', listbox);
214
+ }
215
+
216
+ return input;
217
+ }
218
+
219
+ export function createListboxList(parameters: CreateListboxContentParameters): CreateListboxList {
220
+ const {attributes, id, label, multiple, select, type} = parameters;
221
+
222
+ const element = createElement('ul', {
223
+ aria: {
224
+ labelledby: label.id,
225
+ multiselectable: multiple,
226
+ },
227
+ attribute: {
228
+ [attributes.list]: '',
229
+ tabindex: -1,
230
+ },
231
+ property: {
232
+ id: `${id}_list`,
233
+ role: 'listbox',
234
+ },
235
+ });
236
+
237
+ const children = [...select.children];
238
+ const childrenLength = children.length;
239
+
240
+ const options: HTMLElement[] = [];
241
+ const selected: HTMLElement[] = [];
242
+
243
+ let groups: OuiListboxGroup[] | undefined;
244
+
245
+ for (let childIndex = 0; childIndex < childrenLength; childIndex += 1) {
246
+ const child = children[childIndex];
247
+
248
+ if (child instanceof HTMLOptionElement) {
249
+ const option = createListboxOption({
250
+ type,
251
+ option: child,
252
+ original: parameters,
253
+ index: childIndex,
254
+ });
255
+
256
+ element.append(option);
257
+ options.push(option);
258
+
259
+ if (child.selected) {
260
+ selected.push(option);
261
+ }
262
+
263
+ continue;
264
+ }
265
+
266
+ if (!(child instanceof HTMLOptGroupElement)) {
267
+ continue;
268
+ }
269
+
270
+ const group = createListboxGroup({
271
+ selected,
272
+ type,
273
+ group: child,
274
+ groupOptions: [...child.children] as HTMLOptionElement[],
275
+ original: parameters,
276
+ });
277
+
278
+ groups ??= [];
279
+
280
+ element.append(group.element);
281
+ groups.push(group);
282
+ options.push(...group.options);
283
+ }
284
+
285
+ return {
286
+ element,
287
+ groups,
288
+ options: {
289
+ selected,
290
+ all: options,
291
+ },
292
+ };
293
+ }
294
+
295
+ export function createListboxMessage(attribute: string): HTMLDivElement {
296
+ return createElement('div', {
297
+ aria: {
298
+ live: 'polite',
299
+ },
300
+ attribute: {
301
+ [attribute]: '',
302
+ },
303
+ property: {
304
+ hidden: true,
305
+ role: 'status',
306
+ },
307
+ });
308
+ }
309
+
310
+ export function createListboxOption(parameters: CreateListboxOptionParameters): HTMLElement {
311
+ const {group, option, original} = parameters;
312
+
313
+ return createElement('li', {
314
+ aria: {
315
+ selected: (parameters.type === 'select' || !original.autocomplete.none) && option.selected,
316
+ },
317
+ attribute: {
318
+ [original.attributes.option]: '',
319
+ },
320
+ data: {
321
+ group,
322
+ value: option.value,
323
+ },
324
+ property: {
325
+ id: option.id,
326
+ role: 'option',
327
+ textContent: option.textContent,
328
+ },
329
+ });
330
+ }
331
+
332
+ export function createListboxOptions(parameters: CreateListboxOptionsParameters): HTMLElement[] {
333
+ const {group, list, options, original, selected, type} = parameters;
334
+
335
+ const items: HTMLElement[] = [];
336
+
337
+ for (let index = 0; index < options.length; index += 1) {
338
+ const option = options[index];
339
+
340
+ const item = createListboxOption({group, index, option, original, type});
341
+
342
+ items.push(item);
343
+
344
+ if (option.selected) {
345
+ selected.push(item);
346
+ }
347
+
348
+ list.append(item);
349
+ }
350
+
351
+ return items;
352
+ }
353
+
354
+ export function createListboxValueLabel(id: string): HTMLSpanElement {
355
+ return createElement('span', {
356
+ property: {
357
+ hidden: true,
358
+ id: `${id}_value`,
359
+ },
360
+ });
361
+ }
362
+
363
+ export function updateListboxMessage(state: OuiListboxState): void {
364
+ const {attributes, host, localizations, message} = state;
365
+
366
+ const visible = getListboxVisibleOptions(state).length;
367
+
368
+ setAttribute(message, attributes.hidden, visible > 0 ? '' : undefined);
369
+
370
+ let data: Record<string, unknown> = {};
371
+ let parameters: unknown[] = [];
372
+
373
+ let key: keyof OuiListboxLocalizations | undefined;
374
+
375
+ if (visible === 0) {
376
+ key = 'empty';
377
+ } else {
378
+ data = {amount: visible};
379
+ key = 'results';
380
+ parameters = [visible];
381
+ }
382
+
383
+ const localization = getLocalization(localizations.global, host, key, localizations.component);
384
+
385
+ message.textContent = localize(localization, data, parameters);
386
+
387
+ message.hidden = false;
388
+ }
389
+
390
+ export function updateListboxPreview<State extends OuiListboxState>(
391
+ preview: OuiListboxPreview,
392
+ state: State,
393
+ active?: HTMLElement,
394
+ scroll?: boolean,
395
+ ): void {
396
+ const {length} = state.options.all;
397
+
398
+ for (let index = 0; index < length; index += 1) {
399
+ const option = state.options.all[index];
400
+
401
+ setAttribute(option, state.attributes.active, option === active ? '' : undefined);
402
+ }
403
+
404
+ state.active = active;
405
+
406
+ setAria(
407
+ state.filter.element,
408
+ 'activedescendant',
409
+ state.content.checkVisibility() ? active?.id : undefined,
410
+ );
411
+
412
+ if (scroll ?? true) {
413
+ active?.scrollIntoView(listboxScrollOptions);
414
+ }
415
+
416
+ if (preview !== 'none') {
417
+ state.callbacks.data.onPreview(preview, state, active);
418
+ }
419
+ }
420
+
421
+ export function updateListboxValueLabel<State extends OuiListboxState>(state: State): void {
422
+ let label: string;
423
+
424
+ if (state.multiple) {
425
+ const localization = getLocalization(
426
+ state.localizations.global,
427
+ state.host,
428
+ 'selected',
429
+ state.localizations.component,
430
+ );
431
+
432
+ const {length} = state.options.selected;
433
+
434
+ label = localize(localization, {amount: length}, [length]);
435
+ } else {
436
+ label =
437
+ state.autocomplete.none && !state.listbox
438
+ ? state.filter.element.value
439
+ : (state.options.selected[0]?.textContent ?? '');
440
+ }
441
+
442
+ state.values.label.textContent = label;
443
+ }
444
+
445
+ // #endregion
@@ -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
- dispatch(state.host, 'change');
35
+ dispatch(state.host, 'oui: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