@react-aria/menu
Advanced tools
Comparing version 3.0.0-nightly-50c7ada5d-241223 to 3.0.0-nightly-527c98a84-250305
@@ -65,2 +65,4 @@ import { AriaMenuProps, MenuTriggerType } from "@react-types/menu"; | ||
isFocused: boolean; | ||
/** Whether the item is keyboard focused. */ | ||
isFocusVisible: boolean; | ||
/** Whether the item is currently selected. */ | ||
@@ -160,2 +162,4 @@ isSelected: boolean; | ||
delay?: number; | ||
/** Whether the submenu trigger uses virtual focus. */ | ||
shouldUseVirtualFocus?: boolean; | ||
} | ||
@@ -162,0 +166,0 @@ interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> { |
@@ -130,3 +130,3 @@ var $815e346b11b84016$exports = require("./utils.main.js"); | ||
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog. | ||
if (!(0, $byVdR$reactariainteractions.isFocusVisible)() && !(isTriggerExpanded && hasPopup === 'dialog')) { | ||
if (!(0, $byVdR$reactariainteractions.isFocusVisible)() && !(isTriggerExpanded && hasPopup)) { | ||
selectionManager.setFocused(true); | ||
@@ -177,6 +177,10 @@ selectionManager.setFocusedKey(key); | ||
onFocus: itemProps.onFocus, | ||
'data-collection': itemProps['data-collection'], | ||
'data-key': itemProps['data-key'] | ||
} : itemProps, pressProps, hoverProps, keyboardProps, focusProps), | ||
} : itemProps, pressProps, hoverProps, keyboardProps, focusProps, // Prevent DOM focus from moving on mouse down when using virtual focus or this is a submenu/subdialog trigger. | ||
data.shouldUseVirtualFocus || isTrigger ? { | ||
onMouseDown: (e)=>e.preventDefault() | ||
} : undefined), | ||
// 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. | ||
tabIndex: itemProps.tabIndex != null && isTriggerExpanded ? -1 : itemProps.tabIndex | ||
tabIndex: itemProps.tabIndex != null && isTriggerExpanded && !data.shouldUseVirtualFocus ? -1 : itemProps.tabIndex | ||
}, | ||
@@ -193,2 +197,3 @@ labelProps: { | ||
isFocused: isFocused, | ||
isFocusVisible: isFocused && selectionManager.isFocused && (0, $byVdR$reactariainteractions.isFocusVisible)() && !isTriggerExpanded, | ||
isSelected: isSelected, | ||
@@ -195,0 +200,0 @@ isPressed: isPressed, |
@@ -124,3 +124,3 @@ import {menuData as $fc79756100351201$export$6f49b4016bfc8d56} from "./utils.module.js"; | ||
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog. | ||
if (!(0, $7Kjv5$isFocusVisible)() && !(isTriggerExpanded && hasPopup === 'dialog')) { | ||
if (!(0, $7Kjv5$isFocusVisible)() && !(isTriggerExpanded && hasPopup)) { | ||
selectionManager.setFocused(true); | ||
@@ -171,6 +171,10 @@ selectionManager.setFocusedKey(key); | ||
onFocus: itemProps.onFocus, | ||
'data-collection': itemProps['data-collection'], | ||
'data-key': itemProps['data-key'] | ||
} : itemProps, pressProps, hoverProps, keyboardProps, focusProps), | ||
} : itemProps, pressProps, hoverProps, keyboardProps, focusProps, // Prevent DOM focus from moving on mouse down when using virtual focus or this is a submenu/subdialog trigger. | ||
data.shouldUseVirtualFocus || isTrigger ? { | ||
onMouseDown: (e)=>e.preventDefault() | ||
} : undefined), | ||
// 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. | ||
tabIndex: itemProps.tabIndex != null && isTriggerExpanded ? -1 : itemProps.tabIndex | ||
tabIndex: itemProps.tabIndex != null && isTriggerExpanded && !data.shouldUseVirtualFocus ? -1 : itemProps.tabIndex | ||
}, | ||
@@ -187,2 +191,3 @@ labelProps: { | ||
isFocused: isFocused, | ||
isFocusVisible: isFocused && selectionManager.isFocused && (0, $7Kjv5$isFocusVisible)() && !isTriggerExpanded, | ||
isSelected: isSelected, | ||
@@ -189,0 +194,0 @@ isPressed: isPressed, |
var $d1742ec2644a0949$exports = require("./intlStrings.main.js"); | ||
var $jo7gW$reactariautils = require("@react-aria/utils"); | ||
var $jo7gW$reactariainteractions = require("@react-aria/interactions"); | ||
var $jo7gW$reactariai18n = require("@react-aria/i18n"); | ||
var $jo7gW$reactariainteractions = require("@react-aria/interactions"); | ||
var $jo7gW$reactariaoverlays = require("@react-aria/overlays"); | ||
@@ -74,7 +74,12 @@ | ||
let pressProps = { | ||
preventFocusOnPress: true, | ||
onPressStart (e) { | ||
// For consistency with native, open the menu on mouse/key down, but touch up. | ||
if (e.pointerType !== 'touch' && e.pointerType !== 'keyboard' && !isDisabled) // If opened with a screen reader, auto focus the first item. | ||
// Otherwise, the menu itself will be focused. | ||
state.open(e.pointerType === 'virtual' ? 'first' : null); | ||
if (e.pointerType !== 'touch' && e.pointerType !== 'keyboard' && !isDisabled) { | ||
// Ensure trigger has focus before opening the menu so it can be restored by FocusScope on close. | ||
(0, $jo7gW$reactariautils.focusWithoutScrolling)(e.target); | ||
// If opened with a screen reader, auto focus the first item. | ||
// Otherwise, the menu itself will be focused. | ||
state.open(e.pointerType === 'virtual' ? 'first' : null); | ||
} | ||
}, | ||
@@ -81,0 +86,0 @@ onPress (e) { |
import $czs6v$intlStringsmodulejs from "./intlStrings.module.js"; | ||
import {useId as $czs6v$useId} from "@react-aria/utils"; | ||
import {useId as $czs6v$useId, focusWithoutScrolling as $czs6v$focusWithoutScrolling} from "@react-aria/utils"; | ||
import {useLongPress as $czs6v$useLongPress} from "@react-aria/interactions"; | ||
import {useLocalizedStringFormatter as $czs6v$useLocalizedStringFormatter} from "@react-aria/i18n"; | ||
import {useLongPress as $czs6v$useLongPress} from "@react-aria/interactions"; | ||
import {useOverlayTrigger as $czs6v$useOverlayTrigger} from "@react-aria/overlays"; | ||
@@ -68,7 +68,12 @@ | ||
let pressProps = { | ||
preventFocusOnPress: true, | ||
onPressStart (e) { | ||
// For consistency with native, open the menu on mouse/key down, but touch up. | ||
if (e.pointerType !== 'touch' && e.pointerType !== 'keyboard' && !isDisabled) // If opened with a screen reader, auto focus the first item. | ||
// Otherwise, the menu itself will be focused. | ||
state.open(e.pointerType === 'virtual' ? 'first' : null); | ||
if (e.pointerType !== 'touch' && e.pointerType !== 'keyboard' && !isDisabled) { | ||
// Ensure trigger has focus before opening the menu so it can be restored by FocusScope on close. | ||
(0, $czs6v$focusWithoutScrolling)(e.target); | ||
// If opened with a screen reader, auto focus the first item. | ||
// Otherwise, the menu itself will be focused. | ||
state.open(e.pointerType === 'virtual' ? 'first' : null); | ||
} | ||
}, | ||
@@ -75,0 +80,0 @@ onPress (e) { |
var $g3RPq$react = require("react"); | ||
var $g3RPq$reactariautils = require("@react-aria/utils"); | ||
var $g3RPq$reactariainteractions = require("@react-aria/interactions"); | ||
var $g3RPq$reactariautils = require("@react-aria/utils"); | ||
@@ -44,2 +44,7 @@ | ||
let modality = (0, $g3RPq$reactariainteractions.useInteractionModality)(); | ||
// Prevent mouse down over safe triangle. Clicking while pointer-events: none is applied | ||
// will cause focus to move unexpectedly since it will go to an element behind the menu. | ||
let onPointerDown = (0, $g3RPq$reactariautils.useEffectEvent)((e)=>{ | ||
if (preventPointerEvents) e.preventDefault(); | ||
}); | ||
(0, $g3RPq$react.useEffect)(()=>{ | ||
@@ -118,4 +123,6 @@ if (preventPointerEvents && menuRef.current) menuRef.current.style.pointerEvents = 'none'; | ||
window.addEventListener('pointermove', onPointerMove); | ||
window.addEventListener('pointerdown', onPointerDown, true); | ||
return ()=>{ | ||
window.removeEventListener('pointermove', onPointerMove); | ||
window.removeEventListener('pointerdown', onPointerDown, true); | ||
clearTimeout(timeout.current); | ||
@@ -131,2 +138,3 @@ clearTimeout(autoCloseTimeout.current); | ||
setPreventPointerEvents, | ||
onPointerDown, | ||
submenuRef | ||
@@ -133,0 +141,0 @@ ]); |
import {useRef as $fUfeP$useRef, useState as $fUfeP$useState, useEffect as $fUfeP$useEffect} from "react"; | ||
import {useResizeObserver as $fUfeP$useResizeObserver, useEffectEvent as $fUfeP$useEffectEvent} from "@react-aria/utils"; | ||
import {useInteractionModality as $fUfeP$useInteractionModality} from "@react-aria/interactions"; | ||
import {useResizeObserver as $fUfeP$useResizeObserver} from "@react-aria/utils"; | ||
@@ -38,2 +38,7 @@ | ||
let modality = (0, $fUfeP$useInteractionModality)(); | ||
// Prevent mouse down over safe triangle. Clicking while pointer-events: none is applied | ||
// will cause focus to move unexpectedly since it will go to an element behind the menu. | ||
let onPointerDown = (0, $fUfeP$useEffectEvent)((e)=>{ | ||
if (preventPointerEvents) e.preventDefault(); | ||
}); | ||
(0, $fUfeP$useEffect)(()=>{ | ||
@@ -112,4 +117,6 @@ if (preventPointerEvents && menuRef.current) menuRef.current.style.pointerEvents = 'none'; | ||
window.addEventListener('pointermove', onPointerMove); | ||
window.addEventListener('pointerdown', onPointerDown, true); | ||
return ()=>{ | ||
window.removeEventListener('pointermove', onPointerMove); | ||
window.removeEventListener('pointerdown', onPointerDown, true); | ||
clearTimeout(timeout.current); | ||
@@ -125,2 +132,3 @@ clearTimeout(autoCloseTimeout.current); | ||
setPreventPointerEvents, | ||
onPointerDown, | ||
submenuRef | ||
@@ -127,0 +135,0 @@ ]); |
var $62347d8c4183e713$exports = require("./useSafelyMouseToSubmenu.main.js"); | ||
var $23MMN$reactariautils = require("@react-aria/utils"); | ||
var $23MMN$react = require("react"); | ||
var $23MMN$reactariautils = require("@react-aria/utils"); | ||
var $23MMN$reactariai18n = require("@react-aria/i18n"); | ||
@@ -27,3 +27,3 @@ | ||
function $5f4753043c9f6cdf$export$7138b0d059a6e743(props, state, ref) { | ||
let { parentMenuRef: parentMenuRef, submenuRef: submenuRef, type: type = 'menu', isDisabled: isDisabled, delay: delay = 200 } = props; | ||
let { parentMenuRef: parentMenuRef, submenuRef: submenuRef, type: type = 'menu', isDisabled: isDisabled, delay: delay = 200, shouldUseVirtualFocus: shouldUseVirtualFocus } = props; | ||
let submenuTriggerId = (0, $23MMN$reactariautils.useId)(); | ||
@@ -57,9 +57,12 @@ let overlayId = (0, $23MMN$reactariautils.useId)(); | ||
let submenuKeyDown = (e)=>{ | ||
// If focus is not within the menu, assume virtual focus is being used. | ||
// This means some other input element is also within the popover, so we shouldn't close the menu. | ||
if (!e.currentTarget.contains(document.activeElement)) return; | ||
switch(e.key){ | ||
case 'ArrowLeft': | ||
if (direction === 'ltr' && e.currentTarget.contains(e.target)) { | ||
var _ref_current; | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
(_ref_current = ref.current) === null || _ref_current === void 0 ? void 0 : _ref_current.focus(); | ||
if (!shouldUseVirtualFocus && ref.current) (0, $23MMN$reactariautils.focusWithoutScrolling)(ref.current); | ||
} | ||
@@ -69,11 +72,16 @@ break; | ||
if (direction === 'rtl' && e.currentTarget.contains(e.target)) { | ||
var _ref_current1; | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
(_ref_current1 = ref.current) === null || _ref_current1 === void 0 ? void 0 : _ref_current1.focus(); | ||
if (!shouldUseVirtualFocus && ref.current) (0, $23MMN$reactariautils.focusWithoutScrolling)(ref.current); | ||
} | ||
break; | ||
case 'Escape': | ||
e.stopPropagation(); | ||
state.closeAll(); | ||
var _submenuRef_current; | ||
// TODO: can remove this when we fix collection event leaks | ||
if ((_submenuRef_current = submenuRef.current) === null || _submenuRef_current === void 0 ? void 0 : _submenuRef_current.contains(e.target)) { | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
if (!shouldUseVirtualFocus && ref.current) (0, $23MMN$reactariautils.focusWithoutScrolling)(ref.current); | ||
} | ||
break; | ||
@@ -98,4 +106,5 @@ } | ||
if (direction === 'ltr') { | ||
e.preventDefault(); | ||
if (!state.isOpen) onSubmenuOpen('first'); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) submenuRef.current.focus(); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) (0, $23MMN$reactariautils.focusWithoutScrolling)(submenuRef.current); | ||
} else if (state.isOpen) onSubmenuClose(); | ||
@@ -108,4 +117,5 @@ else e.continuePropagation(); | ||
if (direction === 'rtl') { | ||
e.preventDefault(); | ||
if (!state.isOpen) onSubmenuOpen('first'); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) submenuRef.current.focus(); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) (0, $23MMN$reactariautils.focusWithoutScrolling)(submenuRef.current); | ||
} else if (state.isOpen) onSubmenuClose(); | ||
@@ -115,5 +125,2 @@ else e.continuePropagation(); | ||
break; | ||
case 'Escape': | ||
state.closeAll(); | ||
break; | ||
default: | ||
@@ -172,3 +179,2 @@ e.continuePropagation(); | ||
isNonModal: true, | ||
disableFocusManagement: true, | ||
shouldCloseOnInteractOutside: shouldCloseOnInteractOutside | ||
@@ -175,0 +181,0 @@ } |
import {useSafelyMouseToSubmenu as $d275435c250248f8$export$85ec83e04c95f50a} from "./useSafelyMouseToSubmenu.module.js"; | ||
import {useId as $dXlYe$useId, useEffectEvent as $dXlYe$useEffectEvent, useLayoutEffect as $dXlYe$useLayoutEffect, focusWithoutScrolling as $dXlYe$focusWithoutScrolling} from "@react-aria/utils"; | ||
import {useRef as $dXlYe$useRef, useCallback as $dXlYe$useCallback} from "react"; | ||
import {useId as $dXlYe$useId, useEffectEvent as $dXlYe$useEffectEvent, useLayoutEffect as $dXlYe$useLayoutEffect} from "@react-aria/utils"; | ||
import {useLocale as $dXlYe$useLocale} from "@react-aria/i18n"; | ||
@@ -21,3 +21,3 @@ | ||
function $0065b146e7192841$export$7138b0d059a6e743(props, state, ref) { | ||
let { parentMenuRef: parentMenuRef, submenuRef: submenuRef, type: type = 'menu', isDisabled: isDisabled, delay: delay = 200 } = props; | ||
let { parentMenuRef: parentMenuRef, submenuRef: submenuRef, type: type = 'menu', isDisabled: isDisabled, delay: delay = 200, shouldUseVirtualFocus: shouldUseVirtualFocus } = props; | ||
let submenuTriggerId = (0, $dXlYe$useId)(); | ||
@@ -51,9 +51,12 @@ let overlayId = (0, $dXlYe$useId)(); | ||
let submenuKeyDown = (e)=>{ | ||
// If focus is not within the menu, assume virtual focus is being used. | ||
// This means some other input element is also within the popover, so we shouldn't close the menu. | ||
if (!e.currentTarget.contains(document.activeElement)) return; | ||
switch(e.key){ | ||
case 'ArrowLeft': | ||
if (direction === 'ltr' && e.currentTarget.contains(e.target)) { | ||
var _ref_current; | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
(_ref_current = ref.current) === null || _ref_current === void 0 ? void 0 : _ref_current.focus(); | ||
if (!shouldUseVirtualFocus && ref.current) (0, $dXlYe$focusWithoutScrolling)(ref.current); | ||
} | ||
@@ -63,11 +66,16 @@ break; | ||
if (direction === 'rtl' && e.currentTarget.contains(e.target)) { | ||
var _ref_current1; | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
(_ref_current1 = ref.current) === null || _ref_current1 === void 0 ? void 0 : _ref_current1.focus(); | ||
if (!shouldUseVirtualFocus && ref.current) (0, $dXlYe$focusWithoutScrolling)(ref.current); | ||
} | ||
break; | ||
case 'Escape': | ||
e.stopPropagation(); | ||
state.closeAll(); | ||
var _submenuRef_current; | ||
// TODO: can remove this when we fix collection event leaks | ||
if ((_submenuRef_current = submenuRef.current) === null || _submenuRef_current === void 0 ? void 0 : _submenuRef_current.contains(e.target)) { | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
if (!shouldUseVirtualFocus && ref.current) (0, $dXlYe$focusWithoutScrolling)(ref.current); | ||
} | ||
break; | ||
@@ -92,4 +100,5 @@ } | ||
if (direction === 'ltr') { | ||
e.preventDefault(); | ||
if (!state.isOpen) onSubmenuOpen('first'); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) submenuRef.current.focus(); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) (0, $dXlYe$focusWithoutScrolling)(submenuRef.current); | ||
} else if (state.isOpen) onSubmenuClose(); | ||
@@ -102,4 +111,5 @@ else e.continuePropagation(); | ||
if (direction === 'rtl') { | ||
e.preventDefault(); | ||
if (!state.isOpen) onSubmenuOpen('first'); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) submenuRef.current.focus(); | ||
if (type === 'menu' && !!(submenuRef === null || submenuRef === void 0 ? void 0 : submenuRef.current) && document.activeElement === (ref === null || ref === void 0 ? void 0 : ref.current)) (0, $dXlYe$focusWithoutScrolling)(submenuRef.current); | ||
} else if (state.isOpen) onSubmenuClose(); | ||
@@ -109,5 +119,2 @@ else e.continuePropagation(); | ||
break; | ||
case 'Escape': | ||
state.closeAll(); | ||
break; | ||
default: | ||
@@ -166,3 +173,2 @@ e.continuePropagation(); | ||
isNonModal: true, | ||
disableFocusManagement: true, | ||
shouldCloseOnInteractOutside: shouldCloseOnInteractOutside | ||
@@ -169,0 +175,0 @@ } |
{ | ||
"name": "@react-aria/menu", | ||
"version": "3.0.0-nightly-50c7ada5d-241223", | ||
"version": "3.0.0-nightly-527c98a84-250305", | ||
"description": "Spectrum UI components in React", | ||
@@ -25,15 +25,15 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@react-aria/focus": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/i18n": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/interactions": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/overlays": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/selection": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/utils": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-stately/collections": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-stately/menu": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-stately/selection": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-stately/tree": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-types/button": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-types/menu": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-types/shared": "3.0.0-nightly-50c7ada5d-241223", | ||
"@react-aria/focus": "3.0.0-nightly-527c98a84-250305", | ||
"@react-aria/i18n": "3.0.0-nightly-527c98a84-250305", | ||
"@react-aria/interactions": "3.0.0-nightly-527c98a84-250305", | ||
"@react-aria/overlays": "3.0.0-nightly-527c98a84-250305", | ||
"@react-aria/selection": "3.0.0-nightly-527c98a84-250305", | ||
"@react-aria/utils": "3.0.0-nightly-527c98a84-250305", | ||
"@react-stately/collections": "3.0.0-nightly-527c98a84-250305", | ||
"@react-stately/menu": "3.0.0-nightly-527c98a84-250305", | ||
"@react-stately/selection": "3.0.0-nightly-527c98a84-250305", | ||
"@react-stately/tree": "3.0.0-nightly-527c98a84-250305", | ||
"@react-types/button": "3.0.0-nightly-527c98a84-250305", | ||
"@react-types/menu": "3.0.0-nightly-527c98a84-250305", | ||
"@react-types/shared": "3.0.0-nightly-527c98a84-250305", | ||
"@swc/helpers": "^0.5.0" | ||
@@ -40,0 +40,0 @@ }, |
@@ -37,2 +37,4 @@ /* | ||
isFocused: boolean, | ||
/** Whether the item is keyboard focused. */ | ||
isFocusVisible: boolean, | ||
/** Whether the item is currently selected. */ | ||
@@ -255,3 +257,3 @@ isSelected: boolean, | ||
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog. | ||
if (!isFocusVisible() && !(isTriggerExpanded && hasPopup === 'dialog')) { | ||
if (!isFocusVisible() && !(isTriggerExpanded && hasPopup)) { | ||
selectionManager.setFocused(true); | ||
@@ -307,5 +309,17 @@ selectionManager.setFocusedKey(key); | ||
...ariaProps, | ||
...mergeProps(domProps, linkProps, isTrigger ? {onFocus: itemProps.onFocus, 'data-key': itemProps['data-key']} : itemProps, pressProps, hoverProps, keyboardProps, focusProps), | ||
...mergeProps( | ||
domProps, | ||
linkProps, | ||
isTrigger | ||
? {onFocus: itemProps.onFocus, 'data-collection': itemProps['data-collection'], 'data-key': itemProps['data-key']} | ||
: itemProps, | ||
pressProps, | ||
hoverProps, | ||
keyboardProps, | ||
focusProps, | ||
// Prevent DOM focus from moving on mouse down when using virtual focus or this is a submenu/subdialog trigger. | ||
data.shouldUseVirtualFocus || isTrigger ? {onMouseDown: e => e.preventDefault()} : undefined | ||
), | ||
// 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. | ||
tabIndex: itemProps.tabIndex != null && isTriggerExpanded ? -1 : itemProps.tabIndex | ||
tabIndex: itemProps.tabIndex != null && isTriggerExpanded && !data.shouldUseVirtualFocus ? -1 : itemProps.tabIndex | ||
}, | ||
@@ -322,2 +336,3 @@ labelProps: { | ||
isFocused, | ||
isFocusVisible: isFocused && selectionManager.isFocused && isFocusVisible() && !isTriggerExpanded, | ||
isSelected, | ||
@@ -324,0 +339,0 @@ isPressed, |
@@ -15,2 +15,4 @@ /* | ||
import {AriaMenuOptions} from './useMenu'; | ||
import {FocusableElement, RefObject} from '@react-types/shared'; | ||
import {focusWithoutScrolling, useId} from '@react-aria/utils'; | ||
// @ts-ignore | ||
@@ -20,6 +22,4 @@ import intlMessages from '../intl/*.json'; | ||
import {MenuTriggerType} from '@react-types/menu'; | ||
import {RefObject} from '@react-types/shared'; | ||
import {useId} from '@react-aria/utils'; | ||
import {PressProps, useLongPress} from '@react-aria/interactions'; | ||
import {useLocalizedStringFormatter} from '@react-aria/i18n'; | ||
import {useLongPress} from '@react-aria/interactions'; | ||
import {useOverlayTrigger} from '@react-aria/overlays'; | ||
@@ -113,6 +113,10 @@ | ||
let pressProps = { | ||
let pressProps: PressProps = { | ||
preventFocusOnPress: true, | ||
onPressStart(e) { | ||
// For consistency with native, open the menu on mouse/key down, but touch up. | ||
if (e.pointerType !== 'touch' && e.pointerType !== 'keyboard' && !isDisabled) { | ||
// Ensure trigger has focus before opening the menu so it can be restored by FocusScope on close. | ||
focusWithoutScrolling(e.target as FocusableElement); | ||
// If opened with a screen reader, auto focus the first item. | ||
@@ -119,0 +123,0 @@ // Otherwise, the menu itself will be focused. |
import {RefObject} from '@react-types/shared'; | ||
import {useEffect, useRef, useState} from 'react'; | ||
import {useEffectEvent, useResizeObserver} from '@react-aria/utils'; | ||
import {useInteractionModality} from '@react-aria/interactions'; | ||
import {useResizeObserver} from '@react-aria/utils'; | ||
@@ -54,2 +54,10 @@ interface SafelyMouseToSubmenuOptions { | ||
// Prevent mouse down over safe triangle. Clicking while pointer-events: none is applied | ||
// will cause focus to move unexpectedly since it will go to an element behind the menu. | ||
let onPointerDown = useEffectEvent((e: PointerEvent) => { | ||
if (preventPointerEvents) { | ||
e.preventDefault(); | ||
} | ||
}); | ||
useEffect(() => { | ||
@@ -154,4 +162,13 @@ if (preventPointerEvents && menuRef.current) { | ||
// Prevent pointer down over the safe triangle. See above comment. | ||
// Do not enable in tests, because JSDom doesn't do hit testing. | ||
if (process.env.NODE_ENV !== 'test') { | ||
window.addEventListener('pointerdown', onPointerDown, true); | ||
} | ||
return () => { | ||
window.removeEventListener('pointermove', onPointerMove); | ||
if (process.env.NODE_ENV !== 'test') { | ||
window.removeEventListener('pointerdown', onPointerDown, true); | ||
} | ||
clearTimeout(timeout.current); | ||
@@ -162,3 +179,3 @@ clearTimeout(autoCloseTimeout.current); | ||
}, [isDisabled, isOpen, menuRef, modality, setPreventPointerEvents, submenuRef]); | ||
}, [isDisabled, isOpen, menuRef, modality, setPreventPointerEvents, onPointerDown, submenuRef]); | ||
} |
@@ -17,5 +17,5 @@ /* | ||
import {FocusableElement, FocusStrategy, KeyboardEvent, Node, PressEvent, RefObject} from '@react-types/shared'; | ||
import {focusWithoutScrolling, useEffectEvent, useId, useLayoutEffect} from '@react-aria/utils'; | ||
import type {SubmenuTriggerState} from '@react-stately/menu'; | ||
import {useCallback, useRef} from 'react'; | ||
import {useEffectEvent, useId, useLayoutEffect} from '@react-aria/utils'; | ||
import {useLocale} from '@react-aria/i18n'; | ||
@@ -42,3 +42,5 @@ import {useSafelyMouseToSubmenu} from './useSafelyMouseToSubmenu'; | ||
*/ | ||
delay?: number | ||
delay?: number, | ||
/** Whether the submenu trigger uses virtual focus. */ | ||
shouldUseVirtualFocus?: boolean | ||
} | ||
@@ -72,3 +74,3 @@ | ||
export function useSubmenuTrigger<T>(props: AriaSubmenuTriggerProps, state: SubmenuTriggerState, ref: RefObject<FocusableElement | null>): SubmenuTriggerAria<T> { | ||
let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200} = props; | ||
let {parentMenuRef, submenuRef, type = 'menu', isDisabled, delay = 200, shouldUseVirtualFocus} = props; | ||
let submenuTriggerId = useId(); | ||
@@ -102,8 +104,17 @@ let overlayId = useId(); | ||
let submenuKeyDown = (e: KeyboardEvent) => { | ||
// If focus is not within the menu, assume virtual focus is being used. | ||
// This means some other input element is also within the popover, so we shouldn't close the menu. | ||
if (!e.currentTarget.contains(document.activeElement)) { | ||
return; | ||
} | ||
switch (e.key) { | ||
case 'ArrowLeft': | ||
if (direction === 'ltr' && e.currentTarget.contains(e.target as Element)) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
ref.current?.focus(); | ||
if (!shouldUseVirtualFocus && ref.current) { | ||
focusWithoutScrolling(ref.current); | ||
} | ||
} | ||
@@ -113,10 +124,19 @@ break; | ||
if (direction === 'rtl' && e.currentTarget.contains(e.target as Element)) { | ||
e.preventDefault(); | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
ref.current?.focus(); | ||
if (!shouldUseVirtualFocus && ref.current) { | ||
focusWithoutScrolling(ref.current); | ||
} | ||
} | ||
break; | ||
case 'Escape': | ||
e.stopPropagation(); | ||
state.closeAll(); | ||
// TODO: can remove this when we fix collection event leaks | ||
if (submenuRef.current?.contains(e.target as Element)) { | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
if (!shouldUseVirtualFocus && ref.current) { | ||
focusWithoutScrolling(ref.current); | ||
} | ||
} | ||
break; | ||
@@ -142,2 +162,3 @@ } | ||
if (direction === 'ltr') { | ||
e.preventDefault(); | ||
if (!state.isOpen) { | ||
@@ -148,3 +169,3 @@ onSubmenuOpen('first'); | ||
if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) { | ||
submenuRef.current.focus(); | ||
focusWithoutScrolling(submenuRef.current); | ||
} | ||
@@ -162,2 +183,3 @@ } else if (state.isOpen) { | ||
if (direction === 'rtl') { | ||
e.preventDefault(); | ||
if (!state.isOpen) { | ||
@@ -168,3 +190,3 @@ onSubmenuOpen('first'); | ||
if (type === 'menu' && !!submenuRef?.current && document.activeElement === ref?.current) { | ||
submenuRef.current.focus(); | ||
focusWithoutScrolling(submenuRef.current); | ||
} | ||
@@ -178,5 +200,2 @@ } else if (state.isOpen) { | ||
break; | ||
case 'Escape': | ||
state.closeAll(); | ||
break; | ||
default: | ||
@@ -218,3 +237,3 @@ e.continuePropagation(); | ||
let onBlur = (e) => { | ||
if (state.isOpen && parentMenuRef.current?.contains(e.relatedTarget)) { | ||
if (state.isOpen && (parentMenuRef.current?.contains(e.relatedTarget))) { | ||
onSubmenuClose(); | ||
@@ -250,3 +269,2 @@ } | ||
isNonModal: true, | ||
disableFocusManagement: true, | ||
shouldCloseOnInteractOutside | ||
@@ -253,0 +271,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
338636
4100
278
+ Added@internationalized/date@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@internationalized/message@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@internationalized/number@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@internationalized/string@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/focus@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/i18n@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/interactions@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/overlays@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/selection@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/ssr@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/utils@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-aria/visually-hidden@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/collections@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/flags@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/menu@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/overlays@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/selection@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/tree@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-stately/utils@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-types/button@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-types/menu@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-types/overlays@3.0.0-nightly-527c98a84-250305(transitive)
+ Added@react-types/shared@3.0.0-nightly-527c98a84-250305(transitive)
- Removed@internationalized/date@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@internationalized/message@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@internationalized/number@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@internationalized/string@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/focus@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/i18n@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/interactions@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/overlays@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/selection@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/ssr@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/utils@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-aria/visually-hidden@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-stately/collections@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-stately/menu@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-stately/overlays@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-stately/selection@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-stately/tree@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-stately/utils@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-types/button@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-types/menu@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-types/overlays@3.0.0-nightly-50c7ada5d-241223(transitive)
- Removed@react-types/shared@3.0.0-nightly-50c7ada5d-241223(transitive)
Updated@react-aria/interactions@3.0.0-nightly-527c98a84-250305
Updated@react-stately/collections@3.0.0-nightly-527c98a84-250305
Updated@react-stately/selection@3.0.0-nightly-527c98a84-250305