1010 * governing permissions and limitations under the License.
1111 */
1212
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' ;
1515import { getItemCount } from '@react-stately/collections' ;
1616import { isFocusVisible , useFocus , useHover , useKeyboard , usePress } from '@react-aria/interactions' ;
1717import { menuData } from './utils' ;
18+ import { MouseEvent , useRef } from 'react' ;
1819import { SelectionManager } from '@react-stately/selection' ;
1920import { TreeState } from '@react-stately/tree' ;
2021import { useSelectableItem } from '@react-aria/selection' ;
@@ -112,9 +113,10 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
112113 'aria-haspopup' : hasPopup ,
113114 onPressStart : pressStartProp ,
114115 onPressUp : pressUpProp ,
115- onPress : pressProp ,
116- onPressChange,
116+ onPress,
117+ onPressChange : pressChangeProp ,
117118 onPressEnd,
119+ onClick : onClickProp ,
118120 onHoverStart : hoverStartProp ,
119121 onHoverChange,
120122 onHoverEnd,
@@ -134,7 +136,7 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
134136 let item = state . collection . getItem ( key ) ;
135137 let onClose = props . onClose || data . onClose ;
136138 let router = useRouter ( ) ;
137- let performAction = ( e : PressEvent ) => {
139+ let performAction = ( ) => {
138140 if ( isTrigger ) {
139141 return ;
140142 }
@@ -150,10 +152,6 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
150152 let onAction = data . onAction ;
151153 onAction ( key ) ;
152154 }
153-
154- if ( e . target instanceof HTMLAnchorElement && item ) {
155- router . open ( e . target , e , item . props . href , item . props . routerOptions as RouterOptions ) ;
156- }
157155 } ;
158156
159157 let role = 'menuitem' ;
@@ -191,39 +189,41 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
191189 }
192190
193191 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 ( ) ;
196195 }
197196
198197 pressStartProp ?.( e ) ;
199198 } ;
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 ;
207203 } ;
208204
209205 let onPressUp = ( e : PressEvent ) => {
210206 // If interacting with mouse, allow the user to mouse down on the trigger button,
211207 // drag, and release over an item (matching native behavior).
212208 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 ( ) ;
215218 }
216219
217220 pressUpProp ?.( e ) ;
218221 } ;
219222
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 ) ;
227227 } ;
228228
229229 let { itemProps, isFocused} = useSelectableItem ( {
@@ -315,7 +315,8 @@ export function useMenuItem<T>(props: AriaMenuItemProps, state: TreeState<T>, re
315315 keyboardProps ,
316316 focusProps ,
317317 // 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}
319320 ) ,
320321 // 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.
321322 tabIndex : itemProps . tabIndex != null && isTriggerExpanded && ! data . shouldUseVirtualFocus ? - 1 : itemProps . tabIndex
0 commit comments