-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathuseDatePickerGroup.ts
More file actions
138 lines (124 loc) · 4.2 KB
/
useDatePickerGroup.ts
File metadata and controls
138 lines (124 loc) · 4.2 KB
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
126
127
128
129
130
131
132
133
134
135
136
137
138
import {createFocusManager, getFocusableTreeWalker} from '@react-aria/focus';
import {DateFieldState, DatePickerState, DateRangePickerState} from '@react-stately/datepicker';
import {DOMAttributes, FocusableElement, KeyboardEvent, RefObject} from '@react-types/shared';
import {getEventTarget, mergeProps, nodeContains} from '@react-aria/utils';
import {useLocale} from '@react-aria/i18n';
import {useMemo} from 'react';
import {usePress} from '@react-aria/interactions';
export function useDatePickerGroup(state: DatePickerState | DateRangePickerState | DateFieldState, ref: RefObject<Element | null>, disableArrowNavigation?: boolean): DOMAttributes<FocusableElement> {
let {direction} = useLocale();
let focusManager = useMemo(() => createFocusManager(ref), [ref]);
// Open the popover on alt + arrow down
let onKeyDown = (e: KeyboardEvent) => {
if (!nodeContains(e.currentTarget, getEventTarget(e) as Element)) {
return;
}
if (e.altKey && (e.key === 'ArrowDown' || e.key === 'ArrowUp') && 'setOpen' in state) {
e.preventDefault();
e.stopPropagation();
state.setOpen(true);
}
if (disableArrowNavigation) {
return;
}
switch (e.key) {
case 'ArrowLeft':
e.preventDefault();
e.stopPropagation();
if (direction === 'rtl') {
if (ref.current) {
let target = getEventTarget(e) as FocusableElement;
let prev = findNextSegment(ref.current, target.getBoundingClientRect().left, -1);
if (prev) {
prev.focus();
}
}
} else {
focusManager.focusPrevious();
}
break;
case 'ArrowRight':
e.preventDefault();
e.stopPropagation();
if (direction === 'rtl') {
if (ref.current) {
let target = getEventTarget(e) as FocusableElement;
let next = findNextSegment(ref.current, target.getBoundingClientRect().left, 1);
if (next) {
next.focus();
}
}
} else {
focusManager.focusNext();
}
break;
}
};
// Focus the first placeholder segment from the end on mouse down/touch up in the field.
let focusLast = () => {
if (!ref.current) {
return;
}
// Try to find the segment prior to the element that was clicked on.
let target = window.event ? getEventTarget(window.event) as FocusableElement : null;
let walker = getFocusableTreeWalker(ref.current, {tabbable: true});
if (target) {
walker.currentNode = target;
target = walker.previousNode() as FocusableElement;
}
// If no target found, find the last element from the end.
if (!target) {
let last: FocusableElement;
do {
last = walker.lastChild() as FocusableElement;
if (last) {
target = last;
}
} while (last);
}
// Now go backwards until we find an element that is not a placeholder.
while (target?.hasAttribute('data-placeholder')) {
let prev = walker.previousNode() as FocusableElement;
if (prev && prev.hasAttribute('data-placeholder')) {
target = prev;
} else {
break;
}
}
if (target) {
target.focus();
}
};
let {pressProps} = usePress({
preventFocusOnPress: true,
allowTextSelectionOnPress: true,
onPressStart(e) {
if (e.pointerType === 'mouse') {
focusLast();
}
},
onPress(e) {
if (e.pointerType === 'touch' || e.pointerType === 'pen') {
focusLast();
}
}
});
return mergeProps(pressProps, {onKeyDown});
}
function findNextSegment(group: Element, fromX: number, direction: number) {
let walker = getFocusableTreeWalker(group, {tabbable: true});
let node = walker.nextNode();
let closest: FocusableElement | null = null;
let closestDistance = Infinity;
while (node) {
let x = (node as Element).getBoundingClientRect().left;
let distance = x - fromX;
let absoluteDistance = Math.abs(distance);
if (Math.sign(distance) === direction && absoluteDistance < closestDistance) {
closest = node as FocusableElement;
closestDistance = absoluteDistance;
}
node = walker.nextNode();
}
return closest;
}