@react-aria/menu 3.17.0 → 3.18.1
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/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/useMenuItem.main.js +8 -3
- package/dist/useMenuItem.main.js.map +1 -1
- package/dist/useMenuItem.mjs +8 -3
- package/dist/useMenuItem.module.js +8 -3
- package/dist/useMenuItem.module.js.map +1 -1
- package/dist/useMenuTrigger.main.js +9 -4
- package/dist/useMenuTrigger.main.js.map +1 -1
- package/dist/useMenuTrigger.mjs +10 -5
- package/dist/useMenuTrigger.module.js +10 -5
- package/dist/useMenuTrigger.module.js.map +1 -1
- package/dist/useSafelyMouseToSubmenu.main.js +9 -1
- package/dist/useSafelyMouseToSubmenu.main.js.map +1 -1
- package/dist/useSafelyMouseToSubmenu.mjs +9 -1
- package/dist/useSafelyMouseToSubmenu.module.js +9 -1
- package/dist/useSafelyMouseToSubmenu.module.js.map +1 -1
- package/dist/useSubmenuTrigger.main.js +20 -14
- package/dist/useSubmenuTrigger.main.js.map +1 -1
- package/dist/useSubmenuTrigger.mjs +20 -14
- package/dist/useSubmenuTrigger.module.js +20 -14
- package/dist/useSubmenuTrigger.module.js.map +1 -1
- package/package.json +15 -15
- package/src/useMenuItem.ts +18 -3
- package/src/useMenuTrigger.ts +8 -4
- package/src/useSafelyMouseToSubmenu.ts +19 -2
- package/src/useSubmenuTrigger.ts +32 -14
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
import {RefObject} from '@react-types/shared';
|
|
3
3
|
import {useEffect, useRef, useState} from 'react';
|
|
4
|
+
import {useEffectEvent, useResizeObserver} from '@react-aria/utils';
|
|
4
5
|
import {useInteractionModality} from '@react-aria/interactions';
|
|
5
|
-
import {useResizeObserver} from '@react-aria/utils';
|
|
6
6
|
|
|
7
7
|
interface SafelyMouseToSubmenuOptions {
|
|
8
8
|
/** Ref for the parent menu. */
|
|
@@ -51,6 +51,14 @@ export function useSafelyMouseToSubmenu(options: SafelyMouseToSubmenuOptions) {
|
|
|
51
51
|
|
|
52
52
|
let modality = useInteractionModality();
|
|
53
53
|
|
|
54
|
+
// Prevent mouse down over safe triangle. Clicking while pointer-events: none is applied
|
|
55
|
+
// will cause focus to move unexpectedly since it will go to an element behind the menu.
|
|
56
|
+
let onPointerDown = useEffectEvent((e: PointerEvent) => {
|
|
57
|
+
if (preventPointerEvents) {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
54
62
|
useEffect(() => {
|
|
55
63
|
if (preventPointerEvents && menuRef.current) {
|
|
56
64
|
(menuRef.current as HTMLElement).style.pointerEvents = 'none';
|
|
@@ -150,12 +158,21 @@ export function useSafelyMouseToSubmenu(options: SafelyMouseToSubmenuOptions) {
|
|
|
150
158
|
|
|
151
159
|
window.addEventListener('pointermove', onPointerMove);
|
|
152
160
|
|
|
161
|
+
// Prevent pointer down over the safe triangle. See above comment.
|
|
162
|
+
// Do not enable in tests, because JSDom doesn't do hit testing.
|
|
163
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
164
|
+
window.addEventListener('pointerdown', onPointerDown, true);
|
|
165
|
+
}
|
|
166
|
+
|
|
153
167
|
return () => {
|
|
154
168
|
window.removeEventListener('pointermove', onPointerMove);
|
|
169
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
170
|
+
window.removeEventListener('pointerdown', onPointerDown, true);
|
|
171
|
+
}
|
|
155
172
|
clearTimeout(timeout.current);
|
|
156
173
|
clearTimeout(autoCloseTimeout.current);
|
|
157
174
|
movementsTowardsSubmenuCount.current = ALLOWED_INVALID_MOVEMENTS;
|
|
158
175
|
};
|
|
159
176
|
|
|
160
|
-
}, [isDisabled, isOpen, menuRef, modality, setPreventPointerEvents, submenuRef]);
|
|
177
|
+
}, [isDisabled, isOpen, menuRef, modality, setPreventPointerEvents, onPointerDown, submenuRef]);
|
|
161
178
|
}
|
package/src/useSubmenuTrigger.ts
CHANGED
|
@@ -14,9 +14,9 @@ import {AriaMenuItemProps} from './useMenuItem';
|
|
|
14
14
|
import {AriaMenuOptions} from './useMenu';
|
|
15
15
|
import type {AriaPopoverProps, OverlayProps} from '@react-aria/overlays';
|
|
16
16
|
import {FocusableElement, FocusStrategy, KeyboardEvent, Node, PressEvent, RefObject} from '@react-types/shared';
|
|
17
|
+
import {focusWithoutScrolling, useEffectEvent, useId, useLayoutEffect} from '@react-aria/utils';
|
|
17
18
|
import type {SubmenuTriggerState} from '@react-stately/menu';
|
|
18
19
|
import {useCallback, useRef} from 'react';
|
|
19
|
-
import {useEffectEvent, useId, useLayoutEffect} from '@react-aria/utils';
|
|
20
20
|
import {useLocale} from '@react-aria/i18n';
|
|
21
21
|
import {useSafelyMouseToSubmenu} from './useSafelyMouseToSubmenu';
|
|
22
22
|
|
|
@@ -38,7 +38,9 @@ export interface AriaSubmenuTriggerProps {
|
|
|
38
38
|
* The delay time in milliseconds for the submenu to appear after hovering over the trigger.
|
|
39
39
|
* @default 200
|
|
40
40
|
*/
|
|
41
|
-
delay?: number
|
|
41
|
+
delay?: number,
|
|
42
|
+
/** Whether the submenu trigger uses virtual focus. */
|
|
43
|
+
shouldUseVirtualFocus?: boolean
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> {
|
|
@@ -67,7 +69,7 @@ export interface SubmenuTriggerAria<T> {
|
|
|
67
69
|
* @param ref - Ref to the submenu trigger element.
|
|
68
70
|
*/
|
|
69
71
|
export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: SubmenuTriggerState, ref: RefObject<FocusableElement | null>): SubmenuTriggerAria<T> {
|
|
70
|
-
let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200} = props;
|
|
72
|
+
let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200, shouldUseVirtualFocus} = props;
|
|
71
73
|
let submenuTriggerId = useId();
|
|
72
74
|
let overlayId = useId();
|
|
73
75
|
let {direction} = useLocale();
|
|
@@ -96,24 +98,42 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
96
98
|
}, [cancelOpenTimeout]);
|
|
97
99
|
|
|
98
100
|
let submenuKeyDown = (e: KeyboardEvent) => {
|
|
101
|
+
// If focus is not within the menu, assume virtual focus is being used.
|
|
102
|
+
// This means some other input element is also within the popover, so we shouldn't close the menu.
|
|
103
|
+
if (!e.currentTarget.contains(document.activeElement)) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
99
107
|
switch (e.key) {
|
|
100
108
|
case 'ArrowLeft':
|
|
101
109
|
if (direction === 'ltr' && e.currentTarget.contains(e.target as Element)) {
|
|
110
|
+
e.preventDefault();
|
|
102
111
|
e.stopPropagation();
|
|
103
112
|
onSubmenuClose();
|
|
104
|
-
ref.current
|
|
113
|
+
if (!shouldUseVirtualFocus && ref.current) {
|
|
114
|
+
focusWithoutScrolling(ref.current);
|
|
115
|
+
}
|
|
105
116
|
}
|
|
106
117
|
break;
|
|
107
118
|
case 'ArrowRight':
|
|
108
119
|
if (direction === 'rtl' && e.currentTarget.contains(e.target as Element)) {
|
|
120
|
+
e.preventDefault();
|
|
109
121
|
e.stopPropagation();
|
|
110
122
|
onSubmenuClose();
|
|
111
|
-
ref.current
|
|
123
|
+
if (!shouldUseVirtualFocus && ref.current) {
|
|
124
|
+
focusWithoutScrolling(ref.current);
|
|
125
|
+
}
|
|
112
126
|
}
|
|
113
127
|
break;
|
|
114
128
|
case 'Escape':
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
// TODO: can remove this when we fix collection event leaks
|
|
130
|
+
if (submenuRef.current?.contains(e.target as Element)) {
|
|
131
|
+
e.stopPropagation();
|
|
132
|
+
onSubmenuClose();
|
|
133
|
+
if (!shouldUseVirtualFocus && ref.current) {
|
|
134
|
+
focusWithoutScrolling(ref.current);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
117
137
|
break;
|
|
118
138
|
}
|
|
119
139
|
};
|
|
@@ -134,12 +154,13 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
134
154
|
case 'ArrowRight':
|
|
135
155
|
if (!isDisabled) {
|
|
136
156
|
if (direction === 'ltr') {
|
|
157
|
+
e.preventDefault();
|
|
137
158
|
if (!state.isOpen) {
|
|
138
159
|
onSubmenuOpen('first');
|
|
139
160
|
}
|
|
140
161
|
|
|
141
162
|
if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) {
|
|
142
|
-
submenuRef.current
|
|
163
|
+
focusWithoutScrolling(submenuRef.current);
|
|
143
164
|
}
|
|
144
165
|
} else if (state.isOpen) {
|
|
145
166
|
onSubmenuClose();
|
|
@@ -152,12 +173,13 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
152
173
|
case 'ArrowLeft':
|
|
153
174
|
if (!isDisabled) {
|
|
154
175
|
if (direction === 'rtl') {
|
|
176
|
+
e.preventDefault();
|
|
155
177
|
if (!state.isOpen) {
|
|
156
178
|
onSubmenuOpen('first');
|
|
157
179
|
}
|
|
158
180
|
|
|
159
181
|
if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) {
|
|
160
|
-
submenuRef.current
|
|
182
|
+
focusWithoutScrolling(submenuRef.current);
|
|
161
183
|
}
|
|
162
184
|
} else if (state.isOpen) {
|
|
163
185
|
onSubmenuClose();
|
|
@@ -166,9 +188,6 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
166
188
|
}
|
|
167
189
|
}
|
|
168
190
|
break;
|
|
169
|
-
case 'Escape':
|
|
170
|
-
state.closeAll();
|
|
171
|
-
break;
|
|
172
191
|
default:
|
|
173
192
|
e.continuePropagation();
|
|
174
193
|
break;
|
|
@@ -205,7 +224,7 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
205
224
|
};
|
|
206
225
|
|
|
207
226
|
let onBlur = (e) => {
|
|
208
|
-
if (state.isOpen && parentMenuRef.current?.contains(e.relatedTarget)) {
|
|
227
|
+
if (state.isOpen && (parentMenuRef.current?.contains(e.relatedTarget))) {
|
|
209
228
|
onSubmenuClose();
|
|
210
229
|
}
|
|
211
230
|
};
|
|
@@ -236,7 +255,6 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
236
255
|
submenuProps,
|
|
237
256
|
popoverProps: {
|
|
238
257
|
isNonModal: true,
|
|
239
|
-
disableFocusManagement: true,
|
|
240
258
|
shouldCloseOnInteractOutside
|
|
241
259
|
}
|
|
242
260
|
};
|