@oscarpalmer/oui 0.27.0 → 0.29.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/floatable.css +9 -0
- package/dist/js/dialog.d.mts +3 -1
- package/dist/js/dialog.mjs +35 -19
- package/dist/js/{draggable-zszaW1qS.d.mts → draggable-fIYXd8eO.d.mts} +3 -3
- package/dist/js/floatable/floatable.embedded.d.mts +2 -2
- package/dist/js/floatable/floatable.embedded.mjs +17 -16
- package/dist/js/floatable/floatable.standalone.d.mts +1 -1
- package/dist/js/{floatable.embedded-YxGwzgbE.d.mts → floatable.embedded-DUR8bQBK.d.mts} +1 -2
- package/dist/js/focus-trap/focus-trap.embedded.mjs +6 -5
- package/dist/js/index.d.mts +4 -3
- package/dist/js/index.mjs +6 -2
- package/dist/js/internal/attributable.mjs +3 -4
- package/dist/js/internal/draggable.d.mts +1 -1
- package/dist/js/internal/draggable.mjs +12 -9
- package/dist/js/internal/navigable.d.mts +32 -0
- package/dist/js/internal/navigable.mjs +107 -0
- package/dist/js/menu.d.mts +1 -0
- package/dist/js/menu.mjs +168 -0
- package/dist/js/movable/movable.embedded.d.mts +1 -1
- package/dist/js/movable/movable.embedded.mjs +8 -13
- package/dist/js/movable/movable.standalone.d.mts +1 -1
- package/dist/js/popover.d.mts +1 -1
- package/dist/js/popover.mjs +27 -12
- package/dist/js/resizable/resizable.embedded.d.mts +2 -0
- package/dist/js/resizable/resizable.embedded.mjs +191 -0
- package/dist/js/resizable/resizable.standalone.d.mts +2 -0
- package/dist/js/resizable/resizable.standalone.mjs +2 -0
- package/dist/js/resizable.embedded-CXrUVPzR.d.mts +35 -0
- package/dist/js/sortable/sortable.embedded.d.mts +1 -1
- package/dist/js/sortable/sortable.embedded.mjs +15 -13
- package/dist/js/sortable/sortable.standalone.d.mts +1 -1
- package/dist/js/splitter.mjs +30 -26
- package/dist/js/toolbar.mjs +12 -88
- package/dist/js/tooltip.d.mts +1 -1
- package/dist/js/tooltip.mjs +12 -17
- package/package.json +11 -3
- package/src/css/floatable.scss +9 -0
- package/src/css/form.scss +6 -6
- package/src/js/dialog.ts +50 -24
- package/src/js/floatable/floatable.embedded.ts +19 -18
- package/src/js/focus-trap/focus-trap.embedded.ts +6 -5
- package/src/js/index.ts +3 -1
- package/src/js/internal/attributable.ts +3 -5
- package/src/js/internal/draggable.ts +13 -10
- package/src/js/internal/navigable.ts +247 -0
- package/src/js/menu.ts +345 -0
- package/src/js/movable/movable.embedded.ts +10 -13
- package/src/js/popover.ts +41 -15
- package/src/js/resizable/resizable.embedded.ts +390 -0
- package/src/js/resizable/resizable.standalone.ts +5 -0
- package/src/js/sortable/sortable.embedded.ts +17 -22
- package/src/js/splitter.ts +41 -36
- package/src/js/toolbar.ts +27 -178
- package/src/js/tooltip.ts +18 -30
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
|
+
import {getAria} from '@oscarpalmer/toretto/aria';
|
|
3
|
+
import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
2
4
|
import {on} from '@oscarpalmer/toretto/event';
|
|
3
5
|
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
4
6
|
import type {RemovableEventListener} from '@oscarpalmer/toretto/models';
|
|
7
|
+
import {setStyle, setStyles} from '@oscarpalmer/toretto/style';
|
|
5
8
|
|
|
6
9
|
// #region Types
|
|
7
10
|
|
|
@@ -45,13 +48,15 @@ export class OuiFloatable {
|
|
|
45
48
|
const {anchor, content} = state;
|
|
46
49
|
|
|
47
50
|
if (standalone) {
|
|
48
|
-
|
|
51
|
+
setAttribute(content, ATTRIBUTE_FLOATABLE, '');
|
|
49
52
|
|
|
50
53
|
content.popover = 'manual';
|
|
54
|
+
} else {
|
|
55
|
+
content.popover ??= 'auto';
|
|
51
56
|
}
|
|
52
57
|
|
|
53
|
-
anchor
|
|
54
|
-
content
|
|
58
|
+
setStyle(anchor, 'anchorName', state.name);
|
|
59
|
+
setStyle(content, 'positionAnchor', state.name);
|
|
55
60
|
}
|
|
56
61
|
|
|
57
62
|
/**
|
|
@@ -211,11 +216,11 @@ function createInstance(
|
|
|
211
216
|
export function getOnBeforeToggleListener(element: HTMLElement): RemovableEventListener {
|
|
212
217
|
return on(
|
|
213
218
|
element,
|
|
214
|
-
|
|
219
|
+
'beforetoggle',
|
|
215
220
|
event => {
|
|
216
221
|
const {newState, source} = event;
|
|
217
222
|
|
|
218
|
-
if (newState ===
|
|
223
|
+
if (newState === 'open' && source != null && getAria(source, 'disabled') === 'true') {
|
|
219
224
|
event.preventDefault();
|
|
220
225
|
}
|
|
221
226
|
},
|
|
@@ -248,9 +253,11 @@ function getPosition(
|
|
|
248
253
|
return options.position;
|
|
249
254
|
}
|
|
250
255
|
|
|
251
|
-
const attribute =
|
|
256
|
+
const attribute = getAttribute(anchor, options.attribute);
|
|
252
257
|
|
|
253
|
-
return attribute
|
|
258
|
+
return attribute != null && attribute in AREAS
|
|
259
|
+
? (attribute as OuiFloatablePosition)
|
|
260
|
+
: options.position;
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
function getState(
|
|
@@ -281,10 +288,12 @@ function setPosition(state: OuiFloatableState, override?: OuiFloatablePosition):
|
|
|
281
288
|
|
|
282
289
|
const area = AREAS[position];
|
|
283
290
|
|
|
284
|
-
state.content
|
|
285
|
-
state.content.style.positionArea = area;
|
|
291
|
+
setAttribute(state.content, ATTRIBUTE_POSITION, POSITIONS[area]);
|
|
286
292
|
|
|
287
|
-
state.content
|
|
293
|
+
setStyles(state.content, {
|
|
294
|
+
positionAnchor: state.name,
|
|
295
|
+
positionArea: area,
|
|
296
|
+
});
|
|
288
297
|
}
|
|
289
298
|
|
|
290
299
|
// #endregion
|
|
@@ -306,24 +315,16 @@ const AREAS: Record<OuiFloatablePosition, string> = {
|
|
|
306
315
|
'start-top': 'span-end start',
|
|
307
316
|
};
|
|
308
317
|
|
|
309
|
-
const ARIA_DISABLED = 'aria-disabled';
|
|
310
|
-
|
|
311
318
|
export const ATTRIBUTE_FLOATABLE = 'oui-floatable';
|
|
312
319
|
|
|
313
320
|
const ATTRIBUTE_POSITION = 'oui-position';
|
|
314
321
|
|
|
315
|
-
const EVENT_BEFORE = 'beforetoggle';
|
|
316
|
-
|
|
317
|
-
export const FLOATABLE_STATE_OPEN = 'open';
|
|
318
|
-
|
|
319
322
|
const MESSAGE = 'Anchor and content must be an HTMLElement or SVGElement';
|
|
320
323
|
|
|
321
324
|
const POSITIONS = Object.fromEntries(
|
|
322
325
|
Object.entries(AREAS).map(([position, area]) => [area, position]),
|
|
323
326
|
) as Record<string, OuiFloatablePosition>;
|
|
324
327
|
|
|
325
|
-
const TRUE = 'true';
|
|
326
|
-
|
|
327
328
|
const instances = new WeakMap<HTMLElement, OuiFloatable>();
|
|
328
329
|
|
|
329
330
|
let index = 0;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {isPlainObject} from '@oscarpalmer/atoms/is';
|
|
2
2
|
import {clamp} from '@oscarpalmer/atoms/number';
|
|
3
|
+
import {setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
3
4
|
import {on} from '@oscarpalmer/toretto/event';
|
|
4
5
|
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
5
6
|
import {getFocusable} from '@oscarpalmer/toretto/focusable';
|
|
@@ -26,9 +27,9 @@ export class OuiFocusTrap {
|
|
|
26
27
|
*/
|
|
27
28
|
set contain(value: boolean) {
|
|
28
29
|
if (value === true) {
|
|
29
|
-
this.#state.element
|
|
30
|
+
setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_CONTAIN, '');
|
|
30
31
|
} else if (value === false) {
|
|
31
|
-
this.#state.element
|
|
32
|
+
setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_CONTAIN, undefined);
|
|
32
33
|
}
|
|
33
34
|
}
|
|
34
35
|
|
|
@@ -62,9 +63,9 @@ export class OuiFocusTrap {
|
|
|
62
63
|
*/
|
|
63
64
|
set noescape(value: boolean) {
|
|
64
65
|
if (value === true) {
|
|
65
|
-
this.#state.element
|
|
66
|
+
setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_NOESCAPE, '');
|
|
66
67
|
} else if (value === false) {
|
|
67
|
-
this.#state.element
|
|
68
|
+
setAttribute(this.#state.element, ATTRIBUTE_FOCUSTRAP_NOESCAPE, undefined);
|
|
68
69
|
}
|
|
69
70
|
}
|
|
70
71
|
|
|
@@ -74,7 +75,7 @@ export class OuiFocusTrap {
|
|
|
74
75
|
|
|
75
76
|
state.element.tabIndex = -1;
|
|
76
77
|
|
|
77
|
-
state.element
|
|
78
|
+
setAttribute(state.element, ATTRIBUTE_FOCUSTRAP, '');
|
|
78
79
|
|
|
79
80
|
this.contain = state.options.contain;
|
|
80
81
|
this.noescape = state.options.noescape;
|
package/src/js/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import './
|
|
1
|
+
import './menu';
|
|
2
2
|
import './toolbar';
|
|
3
3
|
|
|
4
4
|
export * from './dialog';
|
|
@@ -6,5 +6,7 @@ export * from './floatable/floatable.standalone';
|
|
|
6
6
|
export * from './focus-trap/focus-trap.standalone';
|
|
7
7
|
export * from './movable/movable.standalone';
|
|
8
8
|
export * from './popover';
|
|
9
|
+
export * from './resizable/resizable.standalone';
|
|
10
|
+
export * from './sortable/sortable.standalone';
|
|
9
11
|
export * from './splitter';
|
|
10
12
|
export * from './tooltip';
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
2
|
-
|
|
3
1
|
export function attributable(
|
|
4
2
|
attribute: string,
|
|
5
3
|
add: (element: HTMLElement) => void,
|
|
@@ -11,7 +9,7 @@ export function attributable(
|
|
|
11
9
|
for (let index = 0; index < length; index += 1) {
|
|
12
10
|
const entry = entries[index];
|
|
13
11
|
|
|
14
|
-
if (entry.type === 'attributes' &&
|
|
12
|
+
if (entry.type === 'attributes' && entry.target instanceof Element) {
|
|
15
13
|
if (entry.target.hasAttribute(attribute)) {
|
|
16
14
|
handleAdded(attribute, [entry.target], add);
|
|
17
15
|
} else {
|
|
@@ -41,7 +39,7 @@ function handleAdded(
|
|
|
41
39
|
nodes: Node[] | NodeList,
|
|
42
40
|
add: (element: HTMLElement) => void,
|
|
43
41
|
): void {
|
|
44
|
-
const elements = [...nodes].filter(
|
|
42
|
+
const elements = [...nodes].filter(node => node instanceof Element) as HTMLElement[];
|
|
45
43
|
|
|
46
44
|
const {length} = elements;
|
|
47
45
|
|
|
@@ -59,7 +57,7 @@ function handleAdded(
|
|
|
59
57
|
}
|
|
60
58
|
|
|
61
59
|
function handleRemoved(nodes: Node[] | NodeList, remove: (element: HTMLElement) => void): void {
|
|
62
|
-
const elements = [...nodes].filter(
|
|
60
|
+
const elements = [...nodes].filter(node => node instanceof Element) as HTMLElement[];
|
|
63
61
|
|
|
64
62
|
const {length} = elements;
|
|
65
63
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type {EventPosition, PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
2
|
import {clamp} from '@oscarpalmer/atoms/number';
|
|
3
|
+
import {getAttribute, setAttribute} from '@oscarpalmer/toretto/attribute';
|
|
3
4
|
import {getPosition, on} from '@oscarpalmer/toretto/event';
|
|
4
5
|
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
5
|
-
import {
|
|
6
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
7
|
+
import {setStyle, setStyles, toggleStyles, type StyleToggler} from '@oscarpalmer/toretto/style';
|
|
6
8
|
import supportsTouch from '@oscarpalmer/toretto/touch';
|
|
7
9
|
import type {OuiMovable} from '../movable/movable.embedded';
|
|
8
10
|
import type {OuiSortable} from '../sortable/sortable.embedded';
|
|
9
|
-
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
10
11
|
|
|
11
12
|
// #region Types
|
|
12
13
|
|
|
@@ -63,7 +64,7 @@ export abstract class OuiDraggable<ExtraState extends PlainObject = PlainObject>
|
|
|
63
64
|
setContainer(item);
|
|
64
65
|
setDirection(item);
|
|
65
66
|
|
|
66
|
-
|
|
67
|
+
setAttribute(element, attribute, '');
|
|
67
68
|
|
|
68
69
|
let states = globals.states.get(element);
|
|
69
70
|
|
|
@@ -425,9 +426,11 @@ function onPointerdown(event: MouseEvent | TouchEvent): void {
|
|
|
425
426
|
|
|
426
427
|
const next = getNextPosition(item, position);
|
|
427
428
|
|
|
428
|
-
dragged
|
|
429
|
-
|
|
430
|
-
|
|
429
|
+
setStyles(dragged, {
|
|
430
|
+
position: 'fixed',
|
|
431
|
+
inset: `${next.y}px auto auto ${next.x}px`,
|
|
432
|
+
transform: 'translate3d(0, 0, 0)',
|
|
433
|
+
});
|
|
431
434
|
|
|
432
435
|
item.state.options.drag.onBegin?.(event, globals, item, element, handle, next);
|
|
433
436
|
|
|
@@ -450,7 +453,7 @@ function onPointermove(event: MouseEvent | TouchEvent): void {
|
|
|
450
453
|
const next = getNextPosition(current, position);
|
|
451
454
|
|
|
452
455
|
if (globals.element != null) {
|
|
453
|
-
globals.element.node
|
|
456
|
+
setStyle(globals.element.node, 'inset', `${next.y}px auto auto ${next.x}px`);
|
|
454
457
|
}
|
|
455
458
|
|
|
456
459
|
const detail = current.state.options.drag.onMove(event, globals, current, {
|
|
@@ -529,7 +532,7 @@ function setContainer(item: OuiDraggableItem): void {
|
|
|
529
532
|
}
|
|
530
533
|
|
|
531
534
|
const selector =
|
|
532
|
-
container ?? item.instance.element
|
|
535
|
+
container ?? getAttribute(item.instance.element, item.state.options.container.attribute);
|
|
533
536
|
|
|
534
537
|
if (typeof selector !== 'string') {
|
|
535
538
|
return;
|
|
@@ -559,7 +562,7 @@ function setContainerElement(item: OuiDraggableItem, element: HTMLElement): void
|
|
|
559
562
|
}
|
|
560
563
|
|
|
561
564
|
function setDirection(item: OuiDraggableItem): void {
|
|
562
|
-
const direction = item.state.element
|
|
565
|
+
const direction = getAttribute(item.state.element, item.state.options.direction.attribute);
|
|
563
566
|
|
|
564
567
|
if (horizontals.has(direction as OuiDraggableDirection)) {
|
|
565
568
|
item.state.vertical = false;
|
|
@@ -650,7 +653,7 @@ on(document, 'keydown', onKeydown);
|
|
|
650
653
|
on(document, 'mousedown', onPointerdown);
|
|
651
654
|
on(document, 'pointermove', onPointermove);
|
|
652
655
|
on(document, 'pointerup', onPointerup);
|
|
653
|
-
on(document, 'touchcancel', onPointerup
|
|
656
|
+
on(document, 'touchcancel', onPointerup);
|
|
654
657
|
on(document, 'touchstart', onPointerdown, {passive: false});
|
|
655
658
|
|
|
656
659
|
// #endregion
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import {clamp} from '@oscarpalmer/atoms/number';
|
|
2
|
+
import {getAria, setRole} from '@oscarpalmer/toretto/aria';
|
|
3
|
+
import {on} from '@oscarpalmer/toretto/event';
|
|
4
|
+
import {findAncestor} from '@oscarpalmer/toretto/find';
|
|
5
|
+
import {isHTMLOrSVGElement} from '@oscarpalmer/toretto/is';
|
|
6
|
+
import type {AriaRole, RemovableEventListener} from '@oscarpalmer/toretto/models';
|
|
7
|
+
|
|
8
|
+
// #region Types
|
|
9
|
+
|
|
10
|
+
export class OuiNavigable {
|
|
11
|
+
active: OuiNavigableActive = {
|
|
12
|
+
index: 0,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
element: HTMLElement;
|
|
16
|
+
|
|
17
|
+
listeners: RemovableEventListener[];
|
|
18
|
+
|
|
19
|
+
options: OuiNavigableOptions;
|
|
20
|
+
|
|
21
|
+
orientation: OuiNavigableOrientation;
|
|
22
|
+
|
|
23
|
+
constructor(element: HTMLElement, options: OuiNavigableOptions) {
|
|
24
|
+
this.element = element;
|
|
25
|
+
this.options = options;
|
|
26
|
+
|
|
27
|
+
this.listeners = [
|
|
28
|
+
on(element, 'focusin', event => {
|
|
29
|
+
onFocusin(this, event);
|
|
30
|
+
}),
|
|
31
|
+
on(element, 'keydown', event => {
|
|
32
|
+
onKeydown(this, event);
|
|
33
|
+
}),
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
this.orientation = getOrientation(element, options);
|
|
37
|
+
|
|
38
|
+
if (options.role != null) {
|
|
39
|
+
setRole(element, options.role);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
destroy(): void {
|
|
44
|
+
const listeners = this.listeners.splice(0);
|
|
45
|
+
|
|
46
|
+
for (const listener of listeners) {
|
|
47
|
+
listener();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.active.element = undefined;
|
|
51
|
+
this.element = undefined as never;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type OuiNavigableActive = {
|
|
56
|
+
element?: HTMLElement;
|
|
57
|
+
index: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export type OuiNavigableOptions = {
|
|
61
|
+
role?: AriaRole;
|
|
62
|
+
selector: string;
|
|
63
|
+
vertical: boolean;
|
|
64
|
+
onDeactivate?: (event: KeyboardEvent, navigable: OuiNavigable) => void;
|
|
65
|
+
onPreventNavigation?: (event: KeyboardEvent, navigable: OuiNavigable) => boolean;
|
|
66
|
+
onShouldActivate?: (event: KeyboardEvent, navigable: OuiNavigable) => boolean;
|
|
67
|
+
onShouldDeactivate?: (event: KeyboardEvent, navigable: OuiNavigable) => boolean | undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
type OuiNavigableOrientation = 'horizontal' | 'vertical';
|
|
71
|
+
|
|
72
|
+
// #endregion
|
|
73
|
+
|
|
74
|
+
// #region Functions
|
|
75
|
+
|
|
76
|
+
function getItems(container: HTMLElement, selector: string): HTMLElement[] {
|
|
77
|
+
return [...container.querySelectorAll(selector)].filter(isValidItem);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function getNavigable(element: HTMLElement, options: OuiNavigableOptions): OuiNavigable {
|
|
81
|
+
return new OuiNavigable(element, options);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getOrientation(
|
|
85
|
+
element: HTMLElement,
|
|
86
|
+
options: OuiNavigableOptions,
|
|
87
|
+
): OuiNavigableOrientation {
|
|
88
|
+
const attribute = getAria(element, 'orientation');
|
|
89
|
+
|
|
90
|
+
return ORIENTATIONS.has(attribute as OuiNavigableOrientation)
|
|
91
|
+
? (attribute as OuiNavigableOrientation)
|
|
92
|
+
: options.vertical
|
|
93
|
+
? ORIENTATION_VERTICAL
|
|
94
|
+
: ORIENTATION_HORIZONTAL;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function isValidItem(element: Element): element is HTMLElement {
|
|
98
|
+
return 'disabled' in element ? element.disabled !== true : true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function onFocusin(navigable: OuiNavigable, event: FocusEvent): void {
|
|
102
|
+
const item = findAncestor(event, navigable.options.selector);
|
|
103
|
+
|
|
104
|
+
if (!isHTMLOrSVGElement(item) || !isValidItem(item)) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const items = getItems(navigable.element, navigable.options.selector);
|
|
109
|
+
const index = items.indexOf(item);
|
|
110
|
+
|
|
111
|
+
if (index === -1) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
navigable.active.element = item;
|
|
116
|
+
navigable.active.index = index;
|
|
117
|
+
|
|
118
|
+
updateNavigableFocus(navigable, false, items);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function onKeydown(navigable: OuiNavigable, event: KeyboardEvent): void {
|
|
122
|
+
const escaped = event.key === 'Escape';
|
|
123
|
+
const stopPropagation = navigable.options.onShouldDeactivate?.(event, navigable);
|
|
124
|
+
|
|
125
|
+
if (escaped || stopPropagation != null) {
|
|
126
|
+
if (escaped || stopPropagation) {
|
|
127
|
+
event.stopPropagation();
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
navigable.options.onDeactivate?.(event, navigable);
|
|
131
|
+
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!KEYS_ALL.has(event.key)) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const mustActivate = KEYS_ACTIVATE.has(event.key);
|
|
140
|
+
const shouldActivate = navigable.options.onShouldActivate?.(event, navigable) === true;
|
|
141
|
+
|
|
142
|
+
if (
|
|
143
|
+
mustActivate ||
|
|
144
|
+
shouldActivate ||
|
|
145
|
+
navigable.options.onPreventNavigation?.(event, navigable) === true
|
|
146
|
+
) {
|
|
147
|
+
event.stopPropagation();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (mustActivate || shouldActivate) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const items = getItems(navigable.element, navigable.options.selector);
|
|
155
|
+
const {length} = items;
|
|
156
|
+
|
|
157
|
+
if (length === 0) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
let index: number | undefined;
|
|
162
|
+
|
|
163
|
+
if (KEYS_ABSOLUTE.has(event.key)) {
|
|
164
|
+
index = event.key === 'Home' ? 0 : items.length - 1;
|
|
165
|
+
} else {
|
|
166
|
+
const direction = NAVIGABLE_KEYS_HORIZONTAL.has(event.key)
|
|
167
|
+
? ORIENTATION_HORIZONTAL
|
|
168
|
+
: ORIENTATION_VERTICAL;
|
|
169
|
+
|
|
170
|
+
if (navigable.orientation === direction) {
|
|
171
|
+
if (navigable.active.element instanceof HTMLSelectElement) {
|
|
172
|
+
event.preventDefault();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
index = clamp(
|
|
176
|
+
navigable.active.index + (KEYS_NEXT.has(event.key) ? 1 : -1),
|
|
177
|
+
0,
|
|
178
|
+
length - 1,
|
|
179
|
+
true,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (index == null || index === navigable.active.index) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
navigable.active.element = items[index];
|
|
189
|
+
navigable.active.index = index;
|
|
190
|
+
|
|
191
|
+
updateNavigableFocus(navigable, true, items);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export function updateNavigableFocus(
|
|
195
|
+
navigable: OuiNavigable,
|
|
196
|
+
focus: boolean,
|
|
197
|
+
items?: HTMLElement[],
|
|
198
|
+
): void {
|
|
199
|
+
const updated = items ?? getItems(navigable.element, navigable.options.selector);
|
|
200
|
+
const {length} = updated;
|
|
201
|
+
|
|
202
|
+
if (length === 0) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
for (let index = 0; index < length; index += 1) {
|
|
207
|
+
updated[index].tabIndex = index === navigable.active.index ? 0 : -1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
navigable.active.element ??= updated[navigable.active.index];
|
|
211
|
+
|
|
212
|
+
if (focus) {
|
|
213
|
+
navigable.active.element?.focus();
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// #endregion
|
|
218
|
+
|
|
219
|
+
// #region Variables
|
|
220
|
+
|
|
221
|
+
const KEYS_ABSOLUTE = new Set(['End', 'Home']);
|
|
222
|
+
|
|
223
|
+
const KEYS_ACTIVATE = new Set(['Enter', ' ']);
|
|
224
|
+
|
|
225
|
+
export const NAVIGABLE_KEYS_HORIZONTAL = new Set(['ArrowRight', 'ArrowLeft']);
|
|
226
|
+
|
|
227
|
+
export const NAVIGABLE_KEYS_VERTICAL = new Set(['ArrowDown', 'ArrowUp']);
|
|
228
|
+
|
|
229
|
+
const KEYS_ALL = new Set([
|
|
230
|
+
...KEYS_ABSOLUTE,
|
|
231
|
+
...KEYS_ACTIVATE,
|
|
232
|
+
...NAVIGABLE_KEYS_HORIZONTAL,
|
|
233
|
+
...NAVIGABLE_KEYS_VERTICAL,
|
|
234
|
+
]);
|
|
235
|
+
|
|
236
|
+
const KEYS_NEXT = new Set(['ArrowDown', 'ArrowRight']);
|
|
237
|
+
|
|
238
|
+
const ORIENTATION_HORIZONTAL: OuiNavigableOrientation = 'horizontal';
|
|
239
|
+
|
|
240
|
+
const ORIENTATION_VERTICAL: OuiNavigableOrientation = 'vertical';
|
|
241
|
+
|
|
242
|
+
const ORIENTATIONS = new Set<OuiNavigableOrientation>([
|
|
243
|
+
ORIENTATION_HORIZONTAL,
|
|
244
|
+
ORIENTATION_VERTICAL,
|
|
245
|
+
]);
|
|
246
|
+
|
|
247
|
+
// #endregion
|