forked from elastic/kibana
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhelpers.tsx
125 lines (119 loc) · 3.95 KB
/
helpers.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { FluidDragActions, Position } from 'react-beautiful-dnd';
import { KEYBOARD_DRAG_OFFSET } from '../../constants';
import { stopPropagationAndPreventDefault } from '../accessibility/helpers';
/**
* Temporarily disables tab focus on child links of the draggable to work
* around an issue where tab focus becomes stuck on the interactive children
*
* NOTE: This function is (intentionally) only effective when used in a key
* event handler, because it automatically restores focus capabilities on
* the next tick.
*/
export const temporarilyDisableInteractiveChildTabIndexes = (draggableElement: HTMLDivElement) => {
const interactiveChildren = draggableElement.querySelectorAll('a, button');
interactiveChildren.forEach((interactiveChild) => {
interactiveChild.setAttribute('tabindex', '-1'); // DOM mutation
});
// restore the default tabindexs on the next tick:
setTimeout(() => {
interactiveChildren.forEach((interactiveChild) => {
interactiveChild.setAttribute('tabindex', '0'); // DOM mutation
});
}, 0);
};
export const draggableKeyDownHandler = ({
beginDrag,
cancelDragActions,
closePopover,
draggableElement,
dragActions,
dragToLocation,
endDrag,
keyboardEvent,
openPopover,
setDragActions,
}: {
beginDrag: () => FluidDragActions | null;
cancelDragActions: () => void;
closePopover?: () => void;
draggableElement: HTMLDivElement;
dragActions: FluidDragActions | null;
dragToLocation: ({
// eslint-disable-next-line @typescript-eslint/no-shadow
dragActions,
position,
}: {
dragActions: FluidDragActions | null;
position: Position;
}) => void;
keyboardEvent: React.KeyboardEvent;
endDrag: (dragActions: FluidDragActions | null) => void;
openPopover?: () => void;
setDragActions: (value: React.SetStateAction<FluidDragActions | null>) => void;
}) => {
let currentPosition: DOMRect | null = null;
switch (keyboardEvent.key) {
case ' ':
if (!dragActions) {
// start dragging, because space was pressed
if (closePopover != null) {
closePopover();
}
setDragActions(beginDrag());
} else {
// end dragging, because space was pressed
endDrag(dragActions);
setDragActions(null);
}
break;
case 'Escape':
cancelDragActions();
break;
case 'Tab':
// IMPORTANT: we do NOT want to stop propagation and prevent default when Tab is pressed
temporarilyDisableInteractiveChildTabIndexes(draggableElement);
break;
case 'ArrowUp':
currentPosition = draggableElement.getBoundingClientRect();
dragToLocation({
dragActions,
position: { x: currentPosition.x, y: currentPosition.y - KEYBOARD_DRAG_OFFSET },
});
break;
case 'ArrowDown':
currentPosition = draggableElement.getBoundingClientRect();
dragToLocation({
dragActions,
position: { x: currentPosition.x, y: currentPosition.y + KEYBOARD_DRAG_OFFSET },
});
break;
case 'ArrowLeft':
currentPosition = draggableElement.getBoundingClientRect();
dragToLocation({
dragActions,
position: { x: currentPosition.x - KEYBOARD_DRAG_OFFSET, y: currentPosition.y },
});
break;
case 'ArrowRight':
currentPosition = draggableElement.getBoundingClientRect();
dragToLocation({
dragActions,
position: { x: currentPosition.x + KEYBOARD_DRAG_OFFSET, y: currentPosition.y },
});
break;
case 'Enter':
stopPropagationAndPreventDefault(keyboardEvent); // prevents the first item in the popover from getting an errant ENTER
if (!dragActions && openPopover != null) {
openPopover();
}
break;
default:
break;
}
};