@oscarpalmer/oui 0.36.0 → 0.38.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/a11y.css +1 -0
- package/dist/css/badge.css +19 -15
- package/dist/css/button.css +40 -31
- package/dist/css/color-picker.css +377 -0
- package/dist/css/color.css +20 -21
- package/dist/css/combobox.css +140 -34
- package/dist/css/content.css +30 -10
- package/dist/css/floatable.css +12 -10
- package/dist/css/form.css +400 -129
- package/dist/css/listbox.css +43 -17
- package/dist/css/message.css +15 -10
- package/dist/css/mixins/badge.mixins.css +2 -0
- package/dist/css/mixins/button.mixins.css +2 -0
- package/dist/css/mixins/message.mixins.css +1 -0
- package/dist/css/mixins/misc.mixins.css +2 -0
- package/dist/css/mixins/shadow.mixins.css +1 -0
- package/dist/css/reset.css +31 -90
- package/dist/css/splitter.css +3 -3
- package/dist/css/tabs.css +103 -0
- package/dist/js/color-picker.d.mts +213 -0
- package/dist/js/color-picker.mjs +924 -0
- package/dist/js/combobox/dom.combobox.mjs +8 -4
- package/dist/js/combobox/event.combobox.mjs +1 -1
- package/dist/js/combobox/index.d.mts +1 -1
- package/dist/js/combobox/index.mjs +56 -2
- package/dist/js/combobox/misc.combobox.d.mts +1 -1
- package/dist/js/combobox/misc.combobox.mjs +1 -1
- package/dist/js/combobox/state.combobox.d.mts +1 -1
- package/dist/js/combobox/state.combobox.mjs +1 -1
- package/dist/js/dialog.mjs +2 -2
- package/dist/js/floatable/floatable.embedded.mjs +13 -10
- package/dist/js/focus-trap/focus-trap.embedded.mjs +9 -13
- package/dist/js/{index-EfkUSals.d.mts → index-DJdFlTLm.d.mts} +39 -1
- package/dist/js/index.d.mts +4 -2
- package/dist/js/index.mjs +5 -3
- package/dist/js/internal/draggable.mjs +20 -26
- package/dist/js/internal/listbox/listbox.data.mjs +1 -1
- package/dist/js/internal/listbox/listbox.dom.mjs +1 -1
- package/dist/js/internal/listbox/listbox.event.mjs +1 -1
- package/dist/js/internal/listbox/listbox.form.mjs +17 -16
- package/dist/js/internal/listbox/listbox.misc.mjs +1 -1
- package/dist/js/internal/listbox/listbox.state.mjs +4 -4
- package/dist/js/internal/navigable.d.mts +11 -5
- package/dist/js/internal/navigable.mjs +46 -19
- package/dist/js/listbox/event.listbox.mjs +1 -1
- package/dist/js/listbox/index.d.mts +38 -0
- package/dist/js/listbox/index.mjs +56 -2
- package/dist/js/listbox/misc.listbox.mjs +1 -1
- package/dist/js/listbox/state.listbox.mjs +1 -1
- package/dist/js/{listbox.data-BWd0PCy7.mjs → listbox.data-C7CRa5Ya.mjs} +5 -4
- package/dist/js/menu.mjs +1 -1
- package/dist/js/{misc.listbox-BI-XsTOf.mjs → misc.listbox-Cyz2CRDy.mjs} +2 -3
- package/dist/js/movable/movable.embedded.mjs +5 -8
- package/dist/js/popover.mjs +2 -2
- package/dist/js/resizable/resizable.embedded.mjs +21 -31
- package/dist/js/sortable/sortable.embedded.mjs +4 -4
- package/dist/js/splitter.mjs +20 -17
- package/dist/js/{state.combobox-4xq0FPCF.mjs → state.combobox-Qm3FWzYW.mjs} +3 -3
- package/dist/js/tabs.d.mts +25 -0
- package/dist/js/tabs.mjs +237 -0
- package/dist/js/tooltip.mjs +16 -15
- package/package.json +7 -5
- package/src/css/a11y.scss +2 -2
- package/src/css/badge.scss +8 -20
- package/src/css/button.scss +8 -47
- package/src/css/color-picker.scss +344 -0
- package/src/css/color.scss +20 -22
- package/src/css/combobox.scss +29 -57
- package/src/css/content.scss +34 -6
- package/src/css/floatable.scss +22 -19
- package/src/css/form.scss +94 -80
- package/src/css/listbox.scss +6 -34
- package/src/css/message.scss +16 -30
- package/src/css/mixins/badge.mixins.scss +53 -0
- package/src/css/mixins/button.mixins.scss +112 -0
- package/src/css/mixins/input.mixins.scss +114 -0
- package/src/css/mixins/message.mixins.scss +41 -0
- package/src/css/mixins/misc.mixins.scss +48 -0
- package/src/css/mixins/shadow.mixins.scss +57 -0
- package/src/css/reset.scss +39 -88
- package/src/css/splitter.scss +3 -3
- package/src/css/tabs.scss +74 -0
- package/src/js/color-picker.ts +1653 -0
- package/src/js/combobox/dom.combobox.ts +4 -0
- package/src/js/combobox/index.ts +63 -1
- package/src/js/dialog.ts +1 -1
- package/src/js/floatable/floatable.embedded.ts +18 -14
- package/src/js/focus-trap/focus-trap.embedded.ts +11 -19
- package/src/js/index.ts +2 -0
- package/src/js/internal/draggable.ts +29 -33
- package/src/js/internal/listbox/listbox.dom.ts +2 -1
- package/src/js/internal/listbox/listbox.form.ts +32 -22
- package/src/js/internal/listbox/listbox.state.ts +3 -3
- package/src/js/internal/navigable.ts +82 -23
- package/src/js/listbox/index.ts +62 -0
- package/src/js/listbox/state.listbox.ts +0 -2
- package/src/js/movable/movable.embedded.ts +11 -13
- package/src/js/popover.ts +1 -1
- package/src/js/resizable/resizable.embedded.ts +25 -39
- package/src/js/splitter.ts +21 -18
- package/src/js/tabs.ts +441 -0
- package/src/js/tooltip.ts +20 -17
- package/src/css/mixins.scss +0 -194
- /package/dist/css/{mixins.css → mixins/input.mixins.css} +0 -0
|
@@ -73,6 +73,7 @@ export function createComboboxButton(parameters: CreateListboxContentParameters)
|
|
|
73
73
|
},
|
|
74
74
|
property: {
|
|
75
75
|
textContent: multiple ? '' : (select.selectedOptions[0]?.textContent ?? ''),
|
|
76
|
+
type: 'button',
|
|
76
77
|
},
|
|
77
78
|
});
|
|
78
79
|
|
|
@@ -189,6 +190,9 @@ export function createComboboxSelectionItem(
|
|
|
189
190
|
data: {
|
|
190
191
|
option: option.id,
|
|
191
192
|
},
|
|
193
|
+
property: {
|
|
194
|
+
type: 'button',
|
|
195
|
+
},
|
|
192
196
|
});
|
|
193
197
|
|
|
194
198
|
const icon = createElement('span', {
|
package/src/js/combobox/index.ts
CHANGED
|
@@ -69,6 +69,20 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
69
69
|
setListboxFilter(this.#state, value);
|
|
70
70
|
}
|
|
71
71
|
|
|
72
|
+
/**
|
|
73
|
+
* Form element associated with the combobox
|
|
74
|
+
*/
|
|
75
|
+
get form(): HTMLFormElement | null {
|
|
76
|
+
return this.#internals.form;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Label elements associated with the combobox
|
|
81
|
+
*/
|
|
82
|
+
get labels(): NodeList {
|
|
83
|
+
return this.#internals.labels;
|
|
84
|
+
}
|
|
85
|
+
|
|
72
86
|
/**
|
|
73
87
|
* Maximum number of selected options
|
|
74
88
|
*/
|
|
@@ -157,6 +171,20 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
157
171
|
return this.#state.type;
|
|
158
172
|
}
|
|
159
173
|
|
|
174
|
+
/**
|
|
175
|
+
* Validation message for the combobox
|
|
176
|
+
*/
|
|
177
|
+
get validationMessage(): string {
|
|
178
|
+
return this.#internals.validationMessage;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Validity state for the combobox
|
|
183
|
+
*/
|
|
184
|
+
get validity(): ValidityState {
|
|
185
|
+
return this.#internals.validity;
|
|
186
|
+
}
|
|
187
|
+
|
|
160
188
|
/**
|
|
161
189
|
* Current value
|
|
162
190
|
*/
|
|
@@ -185,6 +213,13 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
185
213
|
setListboxValues(this.#state, values);
|
|
186
214
|
}
|
|
187
215
|
|
|
216
|
+
/**
|
|
217
|
+
* Can the combobox be validated?
|
|
218
|
+
*/
|
|
219
|
+
get willValidate(): boolean {
|
|
220
|
+
return this.#internals.willValidate;
|
|
221
|
+
}
|
|
222
|
+
|
|
188
223
|
constructor() {
|
|
189
224
|
super();
|
|
190
225
|
|
|
@@ -215,6 +250,15 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
215
250
|
}
|
|
216
251
|
}
|
|
217
252
|
|
|
253
|
+
/**
|
|
254
|
+
* Check if the combobox is valid
|
|
255
|
+
*
|
|
256
|
+
* @returns `true` if the combobox is valid, otherwise `false`
|
|
257
|
+
*/
|
|
258
|
+
checkValidity(): boolean {
|
|
259
|
+
return this.#internals.checkValidity();
|
|
260
|
+
}
|
|
261
|
+
|
|
218
262
|
/**
|
|
219
263
|
* Clear all selected options
|
|
220
264
|
*/
|
|
@@ -244,7 +288,7 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
244
288
|
setListboxValues(this.#state, this.#state.values.initial);
|
|
245
289
|
}
|
|
246
290
|
|
|
247
|
-
formStateRestoreCallback(state:
|
|
291
|
+
formStateRestoreCallback(state: unknown, reason: unknown): void {
|
|
248
292
|
if (reason !== 'restore') {
|
|
249
293
|
return;
|
|
250
294
|
}
|
|
@@ -262,6 +306,24 @@ export class OuiComboboxElement extends HTMLElement {
|
|
|
262
306
|
}
|
|
263
307
|
}
|
|
264
308
|
|
|
309
|
+
/**
|
|
310
|
+
* Check if the combobox is valid and report any validation errors
|
|
311
|
+
*
|
|
312
|
+
* @returns `true` if the combobox is valid, otherwise `false`
|
|
313
|
+
*/
|
|
314
|
+
reportValidity(): boolean {
|
|
315
|
+
return this.#internals.reportValidity();
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Set a custom validation message for the combobox
|
|
320
|
+
*
|
|
321
|
+
* @param message Validation message
|
|
322
|
+
*/
|
|
323
|
+
setCustomValidity(message: string): void {
|
|
324
|
+
this.#internals.setValidity({customError: message !== ''}, message, this);
|
|
325
|
+
}
|
|
326
|
+
|
|
265
327
|
/**
|
|
266
328
|
* Set combobox localizations for a specific language
|
|
267
329
|
*
|
package/src/js/dialog.ts
CHANGED
|
@@ -199,7 +199,7 @@ function getState(element: HTMLDialogElement): OuiDialogState {
|
|
|
199
199
|
}
|
|
200
200
|
}),
|
|
201
201
|
],
|
|
202
|
-
modal: getAria(element, 'modal') ===
|
|
202
|
+
modal: getAria(element, 'modal') === true || element.matches(':modal'),
|
|
203
203
|
};
|
|
204
204
|
|
|
205
205
|
state.dialog = new OuiDialog(state);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
-
import {getAria} from '@oscarpalmer/toretto/aria';
|
|
2
|
+
import {getAria, setAria} from '@oscarpalmer/toretto/aria';
|
|
3
3
|
import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
4
4
|
import {on} from '@oscarpalmer/toretto/event';
|
|
5
5
|
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
@@ -45,18 +45,7 @@ export class OuiFloatable {
|
|
|
45
45
|
constructor(state: OuiFloatableState, standalone: boolean) {
|
|
46
46
|
this.#state = state;
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (standalone) {
|
|
51
|
-
setAttribute(content, ATTRIBUTE_FLOATABLE, '');
|
|
52
|
-
|
|
53
|
-
content.popover = 'manual';
|
|
54
|
-
} else {
|
|
55
|
-
content.popover ??= 'auto';
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
setStyle(anchor, 'anchorName', state.name);
|
|
59
|
-
setStyle(content, 'positionAnchor', state.name);
|
|
48
|
+
this.#initialize(state, standalone);
|
|
60
49
|
}
|
|
61
50
|
|
|
62
51
|
/**
|
|
@@ -103,6 +92,21 @@ export class OuiFloatable {
|
|
|
103
92
|
setPosition(this.#state, position);
|
|
104
93
|
}
|
|
105
94
|
}
|
|
95
|
+
|
|
96
|
+
#initialize(state: OuiFloatableState, standalone: boolean): void {
|
|
97
|
+
const {anchor, content} = state;
|
|
98
|
+
|
|
99
|
+
if (standalone) {
|
|
100
|
+
setAttribute(content, ATTRIBUTE_FLOATABLE, '');
|
|
101
|
+
|
|
102
|
+
content.popover = 'manual';
|
|
103
|
+
} else {
|
|
104
|
+
content.popover ??= 'auto';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
setStyle(anchor, 'anchorName', state.name);
|
|
108
|
+
setStyle(content, 'positionAnchor', state.name);
|
|
109
|
+
}
|
|
106
110
|
}
|
|
107
111
|
|
|
108
112
|
export type OuiFloatableOptions = {
|
|
@@ -220,7 +224,7 @@ export function getOnBeforeToggleListener(element: HTMLElement): RemovableEventL
|
|
|
220
224
|
event => {
|
|
221
225
|
const {newState, source} = event;
|
|
222
226
|
|
|
223
|
-
if (newState === 'open' && source != null && getAria(source, 'disabled') ===
|
|
227
|
+
if (newState === 'open' && source != null && getAria(source, 'disabled') === true) {
|
|
224
228
|
event.preventDefault();
|
|
225
229
|
}
|
|
226
230
|
},
|
|
@@ -285,31 +285,23 @@ function onTab(event: KeyboardEvent): void {
|
|
|
285
285
|
|
|
286
286
|
const instance = instances.get(element);
|
|
287
287
|
|
|
288
|
-
if (instance == null) {
|
|
288
|
+
if (instance == null || dispatch(element, 'oui:focus-trap').defaultPrevented) {
|
|
289
289
|
return;
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (dispatched.defaultPrevented) {
|
|
296
|
-
return;
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (!instance.disabled) {
|
|
300
|
-
event.preventDefault();
|
|
301
|
-
}
|
|
292
|
+
if (!instance.disabled) {
|
|
293
|
+
event.preventDefault();
|
|
294
|
+
}
|
|
302
295
|
|
|
303
|
-
|
|
296
|
+
const elements = getFocusable(element);
|
|
304
297
|
|
|
305
|
-
|
|
298
|
+
const index = elements.indexOf(event.target as HTMLElement);
|
|
306
299
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
});
|
|
300
|
+
if (index === -1 || elements.length === 0) {
|
|
301
|
+
tabToTrap(instance, elements, element);
|
|
302
|
+
} else {
|
|
303
|
+
tabToElement(instance, elements, index, event.shiftKey ? -1 : 1);
|
|
304
|
+
}
|
|
313
305
|
}
|
|
314
306
|
|
|
315
307
|
export function removeFocusTrap(element: HTMLElement): void {
|
package/src/js/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import './menu';
|
|
2
2
|
import './toolbar';
|
|
3
3
|
|
|
4
|
+
export * from './color-picker';
|
|
4
5
|
export * from './combobox/index';
|
|
5
6
|
export * from './dialog';
|
|
6
7
|
export * from './floatable/floatable.standalone';
|
|
@@ -11,4 +12,5 @@ export * from './popover';
|
|
|
11
12
|
export * from './resizable/resizable.standalone';
|
|
12
13
|
export * from './sortable/sortable.standalone';
|
|
13
14
|
export * from './splitter';
|
|
15
|
+
export * from './tabs';
|
|
14
16
|
export * from './tooltip';
|
|
@@ -258,7 +258,7 @@ function cancelDrag(item?: OuiDraggableItem): void {
|
|
|
258
258
|
|
|
259
259
|
item.state.options.drag.onCancel?.(globals, item);
|
|
260
260
|
|
|
261
|
-
dispatch(item.state.element,
|
|
261
|
+
dispatch(item.state.element, `oui:${item.state.name}:cancel`, {
|
|
262
262
|
detail: {
|
|
263
263
|
element: item.state.options.getOrigin(),
|
|
264
264
|
},
|
|
@@ -406,35 +406,33 @@ function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
|
406
406
|
globals.offset = item.state.options.position.getOffset(globals, item, element, position);
|
|
407
407
|
globals.original = item.state.options.position.getOriginal(globals, item, position);
|
|
408
408
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
item.state.options.drag.onCancel?.(globals, item);
|
|
409
|
+
if (
|
|
410
|
+
dispatch(item.state.element, `oui:${item.state.name}:begin`, {
|
|
411
|
+
detail: {
|
|
412
|
+
element,
|
|
413
|
+
position: {...globals.original},
|
|
414
|
+
},
|
|
415
|
+
}).defaultPrevented
|
|
416
|
+
) {
|
|
417
|
+
item.state.options.drag.onCancel?.(globals, item);
|
|
419
418
|
|
|
420
|
-
|
|
419
|
+
reset();
|
|
421
420
|
|
|
422
|
-
|
|
423
|
-
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
424
423
|
|
|
425
|
-
|
|
424
|
+
const next = getNextPosition(item, position);
|
|
426
425
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
426
|
+
setStyles(dragged, {
|
|
427
|
+
position: 'fixed',
|
|
428
|
+
inset: `${next.y}px auto auto ${next.x}px`,
|
|
429
|
+
transform: 'translate3d(0, 0, 0)',
|
|
430
|
+
});
|
|
432
431
|
|
|
433
|
-
|
|
432
|
+
item.state.options.drag.onBegin?.(event, globals, item, element, handle, next);
|
|
434
433
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
});
|
|
434
|
+
item.state.styling.set();
|
|
435
|
+
styleToggler.set();
|
|
438
436
|
}
|
|
439
437
|
|
|
440
438
|
function onPointermove(event: MouseEvent | TouchEvent): void {
|
|
@@ -459,15 +457,13 @@ function onPointermove(event: MouseEvent | TouchEvent): void {
|
|
|
459
457
|
original: position,
|
|
460
458
|
});
|
|
461
459
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
470
|
-
});
|
|
460
|
+
if (
|
|
461
|
+
dispatch(current.state.element, `oui:${current.state.name}:move`, {
|
|
462
|
+
detail,
|
|
463
|
+
}).defaultPrevented
|
|
464
|
+
) {
|
|
465
|
+
cancelDrag(current);
|
|
466
|
+
}
|
|
471
467
|
}
|
|
472
468
|
|
|
473
469
|
function onPointerup(event: MouseEvent | TouchEvent): void {
|
|
@@ -199,13 +199,14 @@ export function createListboxInput(
|
|
|
199
199
|
autocorrect: 'off',
|
|
200
200
|
autocomplete: 'off',
|
|
201
201
|
form: '',
|
|
202
|
+
inputmode: 'text',
|
|
202
203
|
spellcheck: 'false',
|
|
203
|
-
type: 'text',
|
|
204
204
|
},
|
|
205
205
|
property: {
|
|
206
206
|
id,
|
|
207
207
|
value,
|
|
208
208
|
role: isAutocomplete || listbox ? 'combobox' : 'searchbox',
|
|
209
|
+
type: 'text',
|
|
209
210
|
},
|
|
210
211
|
});
|
|
211
212
|
|
|
@@ -32,24 +32,38 @@ export function updateListboxForm<State extends OuiListboxState>(
|
|
|
32
32
|
updateListboxValidity(state);
|
|
33
33
|
|
|
34
34
|
if (emit ?? true) {
|
|
35
|
-
dispatch(state.host, 'change');
|
|
35
|
+
dispatch(state.host, 'oui:change');
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
export function updateListboxValidity(state: OuiListboxState): void {
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
const {
|
|
41
|
+
autocomplete,
|
|
42
|
+
filter,
|
|
43
|
+
host,
|
|
44
|
+
internals,
|
|
45
|
+
listbox,
|
|
46
|
+
localizations,
|
|
47
|
+
max,
|
|
48
|
+
min,
|
|
49
|
+
multiple,
|
|
50
|
+
options,
|
|
51
|
+
required,
|
|
52
|
+
} = state;
|
|
53
|
+
|
|
54
|
+
if (autocomplete.none && !listbox) {
|
|
55
|
+
const valueMissing = required && filter.element.value.length === 0;
|
|
42
56
|
|
|
43
57
|
const localization = getLocalization(
|
|
44
|
-
|
|
45
|
-
|
|
58
|
+
localizations.global,
|
|
59
|
+
host,
|
|
46
60
|
'invalid',
|
|
47
|
-
|
|
61
|
+
localizations.component,
|
|
48
62
|
);
|
|
49
63
|
|
|
50
|
-
|
|
64
|
+
internals.setValidity({valueMissing}, localize(localization, {}, []));
|
|
51
65
|
|
|
52
|
-
|
|
66
|
+
internals.checkValidity();
|
|
53
67
|
|
|
54
68
|
return;
|
|
55
69
|
}
|
|
@@ -60,31 +74,27 @@ export function updateListboxValidity(state: OuiListboxState): void {
|
|
|
60
74
|
let parameters: unknown[] = [];
|
|
61
75
|
|
|
62
76
|
switch (true) {
|
|
63
|
-
case
|
|
77
|
+
case required && options.selected.length === 0: {
|
|
64
78
|
error = 'badInput';
|
|
65
79
|
key = 'input';
|
|
66
80
|
|
|
67
81
|
break;
|
|
68
82
|
}
|
|
69
83
|
|
|
70
|
-
case
|
|
71
|
-
|
|
72
|
-
state.options.selected.length > state.max: {
|
|
73
|
-
data = {amount: state.max};
|
|
84
|
+
case multiple && typeof max === 'number' && options.selected.length > max: {
|
|
85
|
+
data = {amount: max};
|
|
74
86
|
error = 'rangeOverflow';
|
|
75
87
|
key = 'max';
|
|
76
|
-
parameters = [
|
|
88
|
+
parameters = [max];
|
|
77
89
|
|
|
78
90
|
break;
|
|
79
91
|
}
|
|
80
92
|
|
|
81
|
-
case
|
|
82
|
-
|
|
83
|
-
state.options.selected.length < state.min: {
|
|
84
|
-
data = {amount: state.min};
|
|
93
|
+
case multiple && typeof min === 'number' && options.selected.length < min: {
|
|
94
|
+
data = {amount: min};
|
|
85
95
|
error = 'rangeUnderflow';
|
|
86
96
|
key = 'min';
|
|
87
|
-
parameters = [
|
|
97
|
+
parameters = [min];
|
|
88
98
|
|
|
89
99
|
break;
|
|
90
100
|
}
|
|
@@ -96,11 +106,11 @@ export function updateListboxValidity(state: OuiListboxState): void {
|
|
|
96
106
|
const localization =
|
|
97
107
|
key == null
|
|
98
108
|
? undefined
|
|
99
|
-
: getLocalization(
|
|
109
|
+
: getLocalization(localizations.global, host, key, localizations.component);
|
|
100
110
|
|
|
101
111
|
const message = localization == null ? undefined : localize(localization, data, parameters);
|
|
102
112
|
|
|
103
|
-
|
|
113
|
+
internals.setValidity(
|
|
104
114
|
error == null
|
|
105
115
|
? {}
|
|
106
116
|
: {
|
|
@@ -109,7 +119,7 @@ export function updateListboxValidity(state: OuiListboxState): void {
|
|
|
109
119
|
message,
|
|
110
120
|
);
|
|
111
121
|
|
|
112
|
-
|
|
122
|
+
internals.checkValidity();
|
|
113
123
|
}
|
|
114
124
|
|
|
115
125
|
// #endregion
|
|
@@ -18,10 +18,10 @@ export function attachListbox<State extends OuiListboxState>(
|
|
|
18
18
|
states: OuiListboxStates<State>,
|
|
19
19
|
onAttach: (state: State) => void,
|
|
20
20
|
): void {
|
|
21
|
-
const {filter, host, label} = state;
|
|
21
|
+
const {filter, host, id, label} = state;
|
|
22
22
|
|
|
23
23
|
label.element.htmlFor = filter.element.id;
|
|
24
|
-
label.element.id = `${
|
|
24
|
+
label.element.id = `${id}_label`;
|
|
25
25
|
|
|
26
26
|
onAttach(state);
|
|
27
27
|
|
|
@@ -73,7 +73,7 @@ export function initializeListboxState<State extends OuiListboxState>(
|
|
|
73
73
|
updateListboxValueLabel(state);
|
|
74
74
|
|
|
75
75
|
if (state.options.selected.length > 0) {
|
|
76
|
-
updateListboxPreview('none', state, state.options.selected[0]);
|
|
76
|
+
updateListboxPreview('none', state, state.options.selected[0], false);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
return state;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {clamp} from '@oscarpalmer/atoms/number';
|
|
2
|
-
import {getAria, setRole} from '@oscarpalmer/toretto/aria';
|
|
3
|
-
import {
|
|
2
|
+
import {getAria, setAria, setRole} from '@oscarpalmer/toretto/aria';
|
|
3
|
+
import {getAttribute} from '@oscarpalmer/toretto/attribute';
|
|
4
|
+
import {on} from '@oscarpalmer/toretto/event';
|
|
4
5
|
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
5
6
|
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
6
7
|
import type {AriaRole, RemovableEventListener} from '@oscarpalmer/toretto/models';
|
|
@@ -25,6 +26,16 @@ export class OuiNavigable {
|
|
|
25
26
|
this.options = options;
|
|
26
27
|
|
|
27
28
|
this.listeners = [
|
|
29
|
+
on(
|
|
30
|
+
element,
|
|
31
|
+
'click',
|
|
32
|
+
event => {
|
|
33
|
+
onClick(this, event);
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
passive: false,
|
|
37
|
+
},
|
|
38
|
+
),
|
|
28
39
|
on(element, 'focusin', event => {
|
|
29
40
|
onFocusin(this, event);
|
|
30
41
|
}),
|
|
@@ -40,7 +51,9 @@ export class OuiNavigable {
|
|
|
40
51
|
),
|
|
41
52
|
];
|
|
42
53
|
|
|
43
|
-
this.orientation =
|
|
54
|
+
this.orientation = options?.orientation ?? getNavigableOrientation(element, options.vertical);
|
|
55
|
+
|
|
56
|
+
setAria(element, 'orientation', this.orientation);
|
|
44
57
|
|
|
45
58
|
if (options.role != null) {
|
|
46
59
|
setRole(element, options.role);
|
|
@@ -57,6 +70,14 @@ export class OuiNavigable {
|
|
|
57
70
|
this.active.element = undefined;
|
|
58
71
|
this.element = undefined as never;
|
|
59
72
|
}
|
|
73
|
+
|
|
74
|
+
navigate(index: number, focus?: boolean): void {
|
|
75
|
+
const items = getItems(this.element, this.options.selector);
|
|
76
|
+
|
|
77
|
+
if (index >= 0 && index < items.length) {
|
|
78
|
+
onNavigate(this, index, items, focus ?? false);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
60
81
|
}
|
|
61
82
|
|
|
62
83
|
type OuiNavigableActive = {
|
|
@@ -64,17 +85,25 @@ type OuiNavigableActive = {
|
|
|
64
85
|
index: number;
|
|
65
86
|
};
|
|
66
87
|
|
|
88
|
+
type OuiNavigableCallback<ReturnType> = (
|
|
89
|
+
event: KeyboardEvent,
|
|
90
|
+
navigable: OuiNavigable,
|
|
91
|
+
) => ReturnType;
|
|
92
|
+
|
|
67
93
|
export type OuiNavigableOptions = {
|
|
94
|
+
orientation?: OuiNavigableOrientation;
|
|
68
95
|
role?: AriaRole;
|
|
69
96
|
selector: string;
|
|
70
97
|
vertical: boolean;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
98
|
+
onActivate?: (navigable: OuiNavigable) => void;
|
|
99
|
+
onDeactivate?: OuiNavigableCallback<void>;
|
|
100
|
+
onNavigation?: (navigable: OuiNavigable) => void;
|
|
101
|
+
onPreventNavigation?: OuiNavigableCallback<boolean>;
|
|
102
|
+
onShouldActivate?: OuiNavigableCallback<boolean>;
|
|
103
|
+
onShouldDeactivate?: OuiNavigableCallback<boolean | undefined>;
|
|
75
104
|
};
|
|
76
105
|
|
|
77
|
-
type OuiNavigableOrientation = 'horizontal' | 'vertical';
|
|
106
|
+
export type OuiNavigableOrientation = 'horizontal' | 'vertical';
|
|
78
107
|
|
|
79
108
|
// #endregion
|
|
80
109
|
|
|
@@ -88,15 +117,17 @@ export function getNavigable(element: HTMLElement, options: OuiNavigableOptions)
|
|
|
88
117
|
return new OuiNavigable(element, options);
|
|
89
118
|
}
|
|
90
119
|
|
|
91
|
-
function
|
|
120
|
+
export function getNavigableOrientation(
|
|
92
121
|
element: HTMLElement,
|
|
93
|
-
|
|
122
|
+
vertical: boolean,
|
|
123
|
+
aria?: boolean,
|
|
94
124
|
): OuiNavigableOrientation {
|
|
95
|
-
const attribute =
|
|
125
|
+
const attribute =
|
|
126
|
+
(aria ?? true) ? getAria(element, 'orientation') : getAttribute(element, 'orientation');
|
|
96
127
|
|
|
97
128
|
return ORIENTATIONS.has(attribute as OuiNavigableOrientation)
|
|
98
129
|
? (attribute as OuiNavigableOrientation)
|
|
99
|
-
:
|
|
130
|
+
: vertical
|
|
100
131
|
? ORIENTATION_VERTICAL
|
|
101
132
|
: ORIENTATION_HORIZONTAL;
|
|
102
133
|
}
|
|
@@ -105,6 +136,39 @@ function isValidItem(element: Element): element is HTMLElement {
|
|
|
105
136
|
return 'disabled' in element ? element.disabled !== true : true;
|
|
106
137
|
}
|
|
107
138
|
|
|
139
|
+
function onClick(navigable: OuiNavigable, event: Event): void {
|
|
140
|
+
const item = findAncestor(event, navigable.options.selector) as HTMLElement;
|
|
141
|
+
const items = getItems(navigable.element, navigable.options.selector);
|
|
142
|
+
|
|
143
|
+
const index = items.indexOf(item);
|
|
144
|
+
|
|
145
|
+
if (index === -1) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (index !== navigable.active.index) {
|
|
150
|
+
onNavigate(navigable, index, items, false);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
navigable.options.onActivate?.(navigable);
|
|
154
|
+
|
|
155
|
+
item.focus();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function onNavigate(
|
|
159
|
+
navigable: OuiNavigable,
|
|
160
|
+
index: number,
|
|
161
|
+
items: HTMLElement[],
|
|
162
|
+
focus: boolean,
|
|
163
|
+
): void {
|
|
164
|
+
navigable.active.element = items[index];
|
|
165
|
+
navigable.active.index = index;
|
|
166
|
+
|
|
167
|
+
updateNavigableFocus(navigable, focus, items);
|
|
168
|
+
|
|
169
|
+
navigable.options.onNavigation?.(navigable);
|
|
170
|
+
}
|
|
171
|
+
|
|
108
172
|
function onFocusin(navigable: OuiNavigable, event: FocusEvent): void {
|
|
109
173
|
const item = findAncestor(event, navigable.options.selector);
|
|
110
174
|
|
|
@@ -155,6 +219,8 @@ function onKeydown(navigable: OuiNavigable, event: KeyboardEvent): void {
|
|
|
155
219
|
}
|
|
156
220
|
|
|
157
221
|
if (mustActivate || shouldActivate) {
|
|
222
|
+
navigable.options.onActivate?.(navigable);
|
|
223
|
+
|
|
158
224
|
return;
|
|
159
225
|
}
|
|
160
226
|
|
|
@@ -165,6 +231,8 @@ function onKeydown(navigable: OuiNavigable, event: KeyboardEvent): void {
|
|
|
165
231
|
return;
|
|
166
232
|
}
|
|
167
233
|
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
|
|
168
236
|
let index: number | undefined;
|
|
169
237
|
|
|
170
238
|
if (KEYS_ABSOLUTE.has(event.key)) {
|
|
@@ -188,14 +256,9 @@ function onKeydown(navigable: OuiNavigable, event: KeyboardEvent): void {
|
|
|
188
256
|
}
|
|
189
257
|
}
|
|
190
258
|
|
|
191
|
-
if (index
|
|
192
|
-
|
|
259
|
+
if (index != null && index !== navigable.active.index) {
|
|
260
|
+
onNavigate(navigable, index, items, true);
|
|
193
261
|
}
|
|
194
|
-
|
|
195
|
-
navigable.active.element = items[index];
|
|
196
|
-
navigable.active.index = index;
|
|
197
|
-
|
|
198
|
-
updateNavigableFocus(navigable, true, items);
|
|
199
262
|
}
|
|
200
263
|
|
|
201
264
|
export function updateNavigableFocus(
|
|
@@ -219,10 +282,6 @@ export function updateNavigableFocus(
|
|
|
219
282
|
if (focus) {
|
|
220
283
|
navigable.active.element?.focus();
|
|
221
284
|
}
|
|
222
|
-
|
|
223
|
-
dispatch(navigable.active.element, 'navigable:change', {
|
|
224
|
-
detail: {...navigable.active},
|
|
225
|
-
});
|
|
226
285
|
}
|
|
227
286
|
|
|
228
287
|
// #endregion
|