@react-aria/menu 3.15.5 → 3.16.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/types.d.ts +5 -2
- package/dist/types.d.ts.map +1 -1
- package/dist/useMenu.main.js +2 -1
- package/dist/useMenu.main.js.map +1 -1
- package/dist/useMenu.mjs +2 -1
- package/dist/useMenu.module.js +2 -1
- package/dist/useMenu.module.js.map +1 -1
- package/dist/useMenuItem.main.js +19 -16
- package/dist/useMenuItem.main.js.map +1 -1
- package/dist/useMenuItem.mjs +19 -16
- package/dist/useMenuItem.module.js +19 -16
- package/dist/useMenuItem.module.js.map +1 -1
- package/dist/useMenuTrigger.main.js +1 -0
- package/dist/useMenuTrigger.main.js.map +1 -1
- package/dist/useMenuTrigger.mjs +1 -0
- package/dist/useMenuTrigger.module.js +1 -0
- package/dist/useMenuTrigger.module.js.map +1 -1
- package/dist/useSafelyMouseToSubmenu.main.js +1 -1
- package/dist/useSafelyMouseToSubmenu.main.js.map +1 -1
- package/dist/useSafelyMouseToSubmenu.mjs +1 -1
- package/dist/useSafelyMouseToSubmenu.module.js +1 -1
- package/dist/useSafelyMouseToSubmenu.module.js.map +1 -1
- package/dist/useSubmenuTrigger.main.js +8 -4
- package/dist/useSubmenuTrigger.main.js.map +1 -1
- package/dist/useSubmenuTrigger.mjs +8 -4
- package/dist/useSubmenuTrigger.module.js +8 -4
- package/dist/useSubmenuTrigger.module.js.map +1 -1
- package/package.json +17 -16
- package/src/useMenu.ts +1 -1
- package/src/useMenuItem.ts +27 -19
- package/src/useMenuTrigger.ts +2 -1
- package/src/useSafelyMouseToSubmenu.ts +1 -1
- package/src/useSubmenuTrigger.ts +5 -5
package/src/useMenuItem.ts
CHANGED
|
@@ -15,6 +15,7 @@ import {filterDOMProps, mergeProps, useLinkProps, useRouter, useSlotId} from '@r
|
|
|
15
15
|
import {getItemCount} from '@react-stately/collections';
|
|
16
16
|
import {isFocusVisible, useFocus, useHover, useKeyboard, usePress} from '@react-aria/interactions';
|
|
17
17
|
import {menuData} from './useMenu';
|
|
18
|
+
import {SelectionManager} from '@react-stately/selection';
|
|
18
19
|
import {TreeState} from '@react-stately/tree';
|
|
19
20
|
import {useSelectableItem} from '@react-aria/selection';
|
|
20
21
|
|
|
@@ -58,7 +59,7 @@ export interface AriaMenuItemProps extends DOMProps, PressEvents, HoverEvents, K
|
|
|
58
59
|
'aria-label'?: string,
|
|
59
60
|
|
|
60
61
|
/** The unique key for the menu item. */
|
|
61
|
-
key
|
|
62
|
+
key: Key,
|
|
62
63
|
|
|
63
64
|
/**
|
|
64
65
|
* Handler that is called when the menu should close after selecting an item.
|
|
@@ -88,7 +89,10 @@ export interface AriaMenuItemProps extends DOMProps, PressEvents, HoverEvents, K
|
|
|
88
89
|
'aria-expanded'?: boolean | 'true' | 'false',
|
|
89
90
|
|
|
90
91
|
/** Identifies the menu item's popup element whose contents or presence is controlled by the menu item. */
|
|
91
|
-
'aria-controls'?: string
|
|
92
|
+
'aria-controls'?: string,
|
|
93
|
+
|
|
94
|
+
/** Override of the selection manager. By default, `state.selectionManager` is used. */
|
|
95
|
+
selectionManager?: SelectionManager
|
|
92
96
|
}
|
|
93
97
|
|
|
94
98
|
/**
|
|
@@ -116,13 +120,15 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
116
120
|
onKeyUp,
|
|
117
121
|
onFocus,
|
|
118
122
|
onFocusChange,
|
|
119
|
-
onBlur
|
|
123
|
+
onBlur,
|
|
124
|
+
selectionManager = state.selectionManager
|
|
120
125
|
} = props;
|
|
121
126
|
|
|
122
127
|
let isTrigger = !!hasPopup;
|
|
123
|
-
let
|
|
124
|
-
let
|
|
125
|
-
let
|
|
128
|
+
let isTriggerExpanded = isTrigger && props['aria-expanded'] === 'true';
|
|
129
|
+
let isDisabled = props.isDisabled ?? selectionManager.isDisabled(key);
|
|
130
|
+
let isSelected = props.isSelected ?? selectionManager.isSelected(key);
|
|
131
|
+
let data = menuData.get(state)!;
|
|
126
132
|
let item = state.collection.getItem(key);
|
|
127
133
|
let onClose = props.onClose || data.onClose;
|
|
128
134
|
let router = useRouter();
|
|
@@ -143,16 +149,16 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
143
149
|
onAction(key);
|
|
144
150
|
}
|
|
145
151
|
|
|
146
|
-
if (e.target instanceof HTMLAnchorElement) {
|
|
152
|
+
if (e.target instanceof HTMLAnchorElement && item) {
|
|
147
153
|
router.open(e.target, e, item.props.href, item.props.routerOptions as RouterOptions);
|
|
148
154
|
}
|
|
149
155
|
};
|
|
150
156
|
|
|
151
157
|
let role = 'menuitem';
|
|
152
158
|
if (!isTrigger) {
|
|
153
|
-
if (
|
|
159
|
+
if (selectionManager.selectionMode === 'single') {
|
|
154
160
|
role = 'menuitemradio';
|
|
155
|
-
} else if (
|
|
161
|
+
} else if (selectionManager.selectionMode === 'multiple') {
|
|
156
162
|
role = 'menuitemcheckbox';
|
|
157
163
|
}
|
|
158
164
|
}
|
|
@@ -173,7 +179,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
173
179
|
'aria-expanded': props['aria-expanded']
|
|
174
180
|
};
|
|
175
181
|
|
|
176
|
-
if (
|
|
182
|
+
if (selectionManager.selectionMode !== 'none' && !isTrigger) {
|
|
177
183
|
ariaProps['aria-checked'] = isSelected;
|
|
178
184
|
}
|
|
179
185
|
|
|
@@ -196,7 +202,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
196
202
|
|
|
197
203
|
// Pressing a menu item should close by default in single selection mode but not multiple
|
|
198
204
|
// selection mode, except if overridden by the closeOnSelect prop.
|
|
199
|
-
if (!isTrigger && onClose && (closeOnSelect ?? (
|
|
205
|
+
if (!isTrigger && onClose && (closeOnSelect ?? (selectionManager.selectionMode !== 'multiple' || selectionManager.isLink(key)))) {
|
|
200
206
|
onClose();
|
|
201
207
|
}
|
|
202
208
|
}
|
|
@@ -205,7 +211,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
205
211
|
};
|
|
206
212
|
|
|
207
213
|
let {itemProps, isFocused} = useSelectableItem({
|
|
208
|
-
selectionManager:
|
|
214
|
+
selectionManager: selectionManager,
|
|
209
215
|
key,
|
|
210
216
|
ref,
|
|
211
217
|
shouldSelectOnPressUp: true,
|
|
@@ -228,9 +234,10 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
228
234
|
let {hoverProps} = useHover({
|
|
229
235
|
isDisabled,
|
|
230
236
|
onHoverStart(e) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog.
|
|
238
|
+
if (!isFocusVisible() && !(isTriggerExpanded && hasPopup === 'dialog')) {
|
|
239
|
+
selectionManager.setFocused(true);
|
|
240
|
+
selectionManager.setFocusedKey(key);
|
|
234
241
|
}
|
|
235
242
|
hoverStartProp?.(e);
|
|
236
243
|
},
|
|
@@ -249,7 +256,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
249
256
|
|
|
250
257
|
switch (e.key) {
|
|
251
258
|
case ' ':
|
|
252
|
-
if (!isDisabled &&
|
|
259
|
+
if (!isDisabled && selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) {
|
|
253
260
|
onClose();
|
|
254
261
|
}
|
|
255
262
|
break;
|
|
@@ -272,15 +279,16 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
|
|
|
272
279
|
});
|
|
273
280
|
|
|
274
281
|
let {focusProps} = useFocus({onBlur, onFocus, onFocusChange});
|
|
275
|
-
let domProps = filterDOMProps(item
|
|
282
|
+
let domProps = filterDOMProps(item?.props);
|
|
276
283
|
delete domProps.id;
|
|
277
|
-
let linkProps = useLinkProps(item
|
|
284
|
+
let linkProps = useLinkProps(item?.props);
|
|
278
285
|
|
|
279
286
|
return {
|
|
280
287
|
menuItemProps: {
|
|
281
288
|
...ariaProps,
|
|
282
289
|
...mergeProps(domProps, linkProps, isTrigger ? {onFocus: itemProps.onFocus, 'data-key': itemProps['data-key']} : itemProps, pressProps, hoverProps, keyboardProps, focusProps),
|
|
283
|
-
|
|
290
|
+
// If a submenu is expanded, set the tabIndex to -1 so that shift tabbing goes out of the menu instead of the parent menu item.
|
|
291
|
+
tabIndex: itemProps.tabIndex != null && isTriggerExpanded ? -1 : itemProps.tabIndex
|
|
284
292
|
},
|
|
285
293
|
labelProps: {
|
|
286
294
|
id: labelId
|
package/src/useMenuTrigger.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface MenuTriggerAria<T> {
|
|
|
47
47
|
*/
|
|
48
48
|
export function useMenuTrigger<T>(props: AriaMenuTriggerProps, state: MenuTriggerState, ref: RefObject<Element | null>): MenuTriggerAria<T> {
|
|
49
49
|
let {
|
|
50
|
-
type = 'menu'
|
|
50
|
+
type = 'menu',
|
|
51
51
|
isDisabled,
|
|
52
52
|
trigger = 'press'
|
|
53
53
|
} = props;
|
|
@@ -128,6 +128,7 @@ export function useMenuTrigger<T>(props: AriaMenuTriggerProps, state: MenuTrigge
|
|
|
128
128
|
delete triggerProps.onPress;
|
|
129
129
|
|
|
130
130
|
return {
|
|
131
|
+
// @ts-ignore - TODO we pass out both DOMAttributes AND AriaButtonProps, but useButton will discard the longPress event handlers, it's only through PressResponder magic that this works for RSP and RAC. it does not work in aria examples
|
|
131
132
|
menuTriggerProps: {
|
|
132
133
|
...triggerProps,
|
|
133
134
|
...(trigger === 'press' ? pressProps : longPressProps),
|
|
@@ -63,7 +63,7 @@ export function useSafelyMouseToSubmenu(options: SafelyMouseToSubmenuOptions) {
|
|
|
63
63
|
let submenu = submenuRef.current;
|
|
64
64
|
let menu = menuRef.current;
|
|
65
65
|
|
|
66
|
-
if (isDisabled || !submenu || !isOpen || modality !== 'pointer') {
|
|
66
|
+
if (isDisabled || !submenu || !isOpen || modality !== 'pointer' || !menu) {
|
|
67
67
|
reset();
|
|
68
68
|
return;
|
|
69
69
|
}
|
package/src/useSubmenuTrigger.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface AriaSubmenuTriggerProps {
|
|
|
41
41
|
delay?: number
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
interface SubmenuTriggerProps extends AriaMenuItemProps {
|
|
44
|
+
interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> {
|
|
45
45
|
/** Whether the submenu trigger is in an expanded state. */
|
|
46
46
|
isOpen: boolean
|
|
47
47
|
}
|
|
@@ -101,14 +101,14 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
101
101
|
if (direction === 'ltr' && e.currentTarget.contains(e.target as Element)) {
|
|
102
102
|
e.stopPropagation();
|
|
103
103
|
onSubmenuClose();
|
|
104
|
-
ref.current
|
|
104
|
+
ref.current?.focus();
|
|
105
105
|
}
|
|
106
106
|
break;
|
|
107
107
|
case 'ArrowRight':
|
|
108
108
|
if (direction === 'rtl' && e.currentTarget.contains(e.target as Element)) {
|
|
109
109
|
e.stopPropagation();
|
|
110
110
|
onSubmenuClose();
|
|
111
|
-
ref.current
|
|
111
|
+
ref.current?.focus();
|
|
112
112
|
}
|
|
113
113
|
break;
|
|
114
114
|
case 'Escape':
|
|
@@ -124,7 +124,7 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
124
124
|
submenuLevel: state.submenuLevel,
|
|
125
125
|
...(type === 'menu' && {
|
|
126
126
|
onClose: state.closeAll,
|
|
127
|
-
autoFocus: state.focusStrategy,
|
|
127
|
+
autoFocus: state.focusStrategy ?? undefined,
|
|
128
128
|
onKeyDown: submenuKeyDown
|
|
129
129
|
})
|
|
130
130
|
};
|
|
@@ -205,7 +205,7 @@ export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: Subm
|
|
|
205
205
|
};
|
|
206
206
|
|
|
207
207
|
let onBlur = (e) => {
|
|
208
|
-
if (state.isOpen && parentMenuRef.current
|
|
208
|
+
if (state.isOpen && parentMenuRef.current?.contains(e.relatedTarget)) {
|
|
209
209
|
onSubmenuClose();
|
|
210
210
|
}
|
|
211
211
|
};
|