10
10
* governing permissions and limitations under the License.
11
11
*/
12
12
13
- import { DOMAttributes , DOMProps , FocusableElement , FocusEvents , HoverEvents , Key , KeyboardEvents , PressEvent , PressEvents , RefObject , RouterOptions } from '@react-types/shared' ;
14
- import { filterDOMProps , mergeProps , useLinkProps , useRouter , useSlotId } from '@react-aria/utils' ;
13
+ import { DOMAttributes , DOMProps , FocusableElement , FocusEvents , HoverEvents , Key , KeyboardEvents , PressEvent , PressEvents , RefObject } from '@react-types/shared' ;
14
+ import { filterDOMProps , handleLinkClick , mergeProps , useLinkProps , useRouter , useSlotId } from '@react-aria/utils' ;
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 './utils' ;
18
+ import { MouseEvent , useRef } from 'react' ;
18
19
import { SelectionManager } from '@react-stately/selection' ;
19
20
import { TreeState } from '@react-stately/tree' ;
20
21
import { useSelectableItem } from '@react-aria/selection' ;
@@ -112,9 +113,10 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
112
113
'aria-haspopup' : hasPopup ,
113
114
onPressStart : pressStartProp ,
114
115
onPressUp : pressUpProp ,
115
- onPress : pressProp ,
116
- onPressChange,
116
+ onPress,
117
+ onPressChange : pressChangeProp ,
117
118
onPressEnd,
119
+ onClick : onClickProp ,
118
120
onHoverStart : hoverStartProp ,
119
121
onHoverChange,
120
122
onHoverEnd,
@@ -134,7 +136,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
134
136
let item = state . collection . getItem ( key ) ;
135
137
let onClose = props . onClose || data . onClose ;
136
138
let router = useRouter ( ) ;
137
- let performAction = ( e : PressEvent ) => {
139
+ let performAction = ( ) => {
138
140
if ( isTrigger ) {
139
141
return ;
140
142
}
@@ -150,10 +152,6 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
150
152
let onAction = data . onAction ;
151
153
onAction ( key ) ;
152
154
}
153
-
154
- if ( e . target instanceof HTMLAnchorElement && item ) {
155
- router . open ( e . target , e , item . props . href , item . props . routerOptions as RouterOptions ) ;
156
- }
157
155
} ;
158
156
159
157
let role = 'menuitem' ;
@@ -191,39 +189,41 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
191
189
}
192
190
193
191
let onPressStart = ( e : PressEvent ) => {
194
- if ( e . pointerType === 'keyboard' ) {
195
- performAction ( e ) ;
192
+ // Trigger native click event on keydown unless this is a link (the browser will trigger onClick then).
193
+ if ( e . pointerType === 'keyboard' && ! selectionManager . isLink ( key ) ) {
194
+ ( e . target as HTMLElement ) . click ( ) ;
196
195
}
197
196
198
197
pressStartProp ?.( e ) ;
199
198
} ;
200
-
201
- let maybeClose = ( ) => {
202
- // Pressing a menu item should close by default in single selection mode but not multiple
203
- // selection mode, except if overridden by the closeOnSelect prop.
204
- if ( ! isTrigger && onClose && ( closeOnSelect ?? ( selectionManager . selectionMode !== 'multiple' || selectionManager . isLink ( key ) ) ) ) {
205
- onClose ( ) ;
206
- }
199
+ let isPressedRef = useRef ( false ) ;
200
+ let onPressChange = ( isPressed : boolean ) => {
201
+ pressChangeProp ?.( isPressed ) ;
202
+ isPressedRef . current = isPressed ;
207
203
} ;
208
204
209
205
let onPressUp = ( e : PressEvent ) => {
210
206
// If interacting with mouse, allow the user to mouse down on the trigger button,
211
207
// drag, and release over an item (matching native behavior).
212
208
if ( e . pointerType === 'mouse' ) {
213
- performAction ( e ) ;
214
- maybeClose ( ) ;
209
+ if ( ! isPressedRef . current ) {
210
+ ( e . target as HTMLElement ) . click ( ) ;
211
+ }
212
+ }
213
+
214
+ // Pressing a menu item should close by default in single selection mode but not multiple
215
+ // selection mode, except if overridden by the closeOnSelect prop.
216
+ if ( e . pointerType !== 'keyboard' && ! isTrigger && onClose && ( closeOnSelect ?? ( selectionManager . selectionMode !== 'multiple' || selectionManager . isLink ( key ) ) ) ) {
217
+ onClose ( ) ;
215
218
}
216
219
217
220
pressUpProp ?.( e ) ;
218
221
} ;
219
222
220
- let onPress = ( e : PressEvent ) => {
221
- if ( e . pointerType !== 'keyboard' && e . pointerType !== 'mouse' ) {
222
- performAction ( e ) ;
223
- maybeClose ( ) ;
224
- }
225
-
226
- pressProp ?.( e ) ;
223
+ let onClick = ( e : MouseEvent < FocusableElement > ) => {
224
+ onClickProp ?.( e ) ;
225
+ performAction ( ) ;
226
+ handleLinkClick ( e , router , item ! . props . href , item ?. props . routerOptions ) ;
227
227
} ;
228
228
229
229
let { itemProps, isFocused} = useSelectableItem ( {
@@ -315,7 +315,8 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
315
315
keyboardProps ,
316
316
focusProps ,
317
317
// Prevent DOM focus from moving on mouse down when using virtual focus or this is a submenu/subdialog trigger.
318
- data . shouldUseVirtualFocus || isTrigger ? { onMouseDown : e => e . preventDefault ( ) } : undefined
318
+ data . shouldUseVirtualFocus || isTrigger ? { onMouseDown : e => e . preventDefault ( ) } : undefined ,
319
+ isDisabled ? undefined : { onClick}
319
320
) ,
320
321
// 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.
321
322
tabIndex : itemProps . tabIndex != null && isTriggerExpanded && ! data . shouldUseVirtualFocus ? - 1 : itemProps . tabIndex
0 commit comments