@react-aria/menu
Advanced tools
Comparing version
@@ -6,2 +6,3 @@ import { AriaMenuProps, MenuTriggerType } from "@react-types/menu"; | ||
import { MenuTriggerState, SubmenuTriggerState } from "@react-stately/menu"; | ||
import { SelectionManager } from "@react-stately/selection"; | ||
import { ReactNode } from "react"; | ||
@@ -21,2 +22,6 @@ import { AriaPopoverProps, OverlayProps } from "@react-aria/overlays"; | ||
keyboardDelegate?: KeyboardDelegate; | ||
/** | ||
* Whether the menu items should use virtual focus instead of being focused directly. | ||
*/ | ||
shouldUseVirtualFocus?: boolean; | ||
} | ||
@@ -83,3 +88,3 @@ /** | ||
/** The unique key for the menu item. */ | ||
key?: Key; | ||
key: Key; | ||
/** | ||
@@ -108,2 +113,4 @@ * Handler that is called when the menu should close after selecting an item. | ||
'aria-controls'?: string; | ||
/** Override of the selection manager. By default, `state.selectionManager` is used. */ | ||
selectionManager?: SelectionManager; | ||
} | ||
@@ -157,3 +164,3 @@ /** | ||
} | ||
interface SubmenuTriggerProps extends AriaMenuItemProps { | ||
interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> { | ||
/** Whether the submenu trigger is in an expanded state. */ | ||
@@ -160,0 +167,0 @@ isOpen: boolean; |
@@ -0,1 +1,2 @@ | ||
var $815e346b11b84016$exports = require("./utils.main.js"); | ||
var $6CumN$reactariautils = require("@react-aria/utils"); | ||
@@ -9,3 +10,2 @@ var $6CumN$reactariaselection = require("@react-aria/selection"); | ||
$parcel$export(module.exports, "menuData", () => $a3815f0132802737$export$6f49b4016bfc8d56); | ||
$parcel$export(module.exports, "useMenu", () => $a3815f0132802737$export$38eaa17faae8f579); | ||
@@ -24,3 +24,3 @@ /* | ||
const $a3815f0132802737$export$6f49b4016bfc8d56 = new WeakMap(); | ||
function $a3815f0132802737$export$38eaa17faae8f579(props, state, ref) { | ||
@@ -41,5 +41,6 @@ let { shouldFocusWrap: shouldFocusWrap = true, onKeyDown: onKeyDown, onKeyUp: onKeyUp, ...otherProps } = props; | ||
}); | ||
$a3815f0132802737$export$6f49b4016bfc8d56.set(state, { | ||
(0, $815e346b11b84016$exports.menuData).set(state, { | ||
onClose: props.onClose, | ||
onAction: props.onAction | ||
onAction: props.onAction, | ||
shouldUseVirtualFocus: props.shouldUseVirtualFocus | ||
}); | ||
@@ -54,4 +55,5 @@ return { | ||
onKeyDown: (e)=>{ | ||
var _listProps_onKeyDown; | ||
// don't clear the menu selected keys if the user is presses escape since escape closes the menu | ||
if (e.key !== 'Escape') listProps.onKeyDown(e); | ||
if (e.key !== 'Escape' || props.shouldUseVirtualFocus) (_listProps_onKeyDown = listProps.onKeyDown) === null || _listProps_onKeyDown === void 0 ? void 0 : _listProps_onKeyDown.call(listProps, e); | ||
} | ||
@@ -58,0 +60,0 @@ }) |
@@ -0,1 +1,2 @@ | ||
import {menuData as $fc79756100351201$export$6f49b4016bfc8d56} from "./utils.module.js"; | ||
import {filterDOMProps as $ieN2F$filterDOMProps, mergeProps as $ieN2F$mergeProps} from "@react-aria/utils"; | ||
@@ -16,3 +17,3 @@ import {useSelectableList as $ieN2F$useSelectableList} from "@react-aria/selection"; | ||
const $d5336fe17ce95402$export$6f49b4016bfc8d56 = new WeakMap(); | ||
function $d5336fe17ce95402$export$38eaa17faae8f579(props, state, ref) { | ||
@@ -33,5 +34,6 @@ let { shouldFocusWrap: shouldFocusWrap = true, onKeyDown: onKeyDown, onKeyUp: onKeyUp, ...otherProps } = props; | ||
}); | ||
$d5336fe17ce95402$export$6f49b4016bfc8d56.set(state, { | ||
(0, $fc79756100351201$export$6f49b4016bfc8d56).set(state, { | ||
onClose: props.onClose, | ||
onAction: props.onAction | ||
onAction: props.onAction, | ||
shouldUseVirtualFocus: props.shouldUseVirtualFocus | ||
}); | ||
@@ -46,4 +48,5 @@ return { | ||
onKeyDown: (e)=>{ | ||
var _listProps_onKeyDown; | ||
// don't clear the menu selected keys if the user is presses escape since escape closes the menu | ||
if (e.key !== 'Escape') listProps.onKeyDown(e); | ||
if (e.key !== 'Escape' || props.shouldUseVirtualFocus) (_listProps_onKeyDown = listProps.onKeyDown) === null || _listProps_onKeyDown === void 0 ? void 0 : _listProps_onKeyDown.call(listProps, e); | ||
} | ||
@@ -55,3 +58,3 @@ }) | ||
export {$d5336fe17ce95402$export$6f49b4016bfc8d56 as menuData, $d5336fe17ce95402$export$38eaa17faae8f579 as useMenu}; | ||
export {$d5336fe17ce95402$export$38eaa17faae8f579 as useMenu}; | ||
//# sourceMappingURL=useMenu.module.js.map |
@@ -1,2 +0,2 @@ | ||
var $a3815f0132802737$exports = require("./useMenu.main.js"); | ||
var $815e346b11b84016$exports = require("./utils.main.js"); | ||
var $byVdR$reactariautils = require("@react-aria/utils"); | ||
@@ -29,9 +29,10 @@ var $byVdR$reactstatelycollections = require("@react-stately/collections"); | ||
function $38191ed02615ec07$export$9d32628fc2aea7da(props, state, ref) { | ||
let { id: id, key: key, closeOnSelect: closeOnSelect, isVirtualized: isVirtualized, 'aria-haspopup': hasPopup, onPressStart: pressStartProp, onPressUp: pressUpProp, onPress: onPress, onPressChange: onPressChange, onPressEnd: onPressEnd, onHoverStart: hoverStartProp, onHoverChange: onHoverChange, onHoverEnd: onHoverEnd, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onFocus: onFocus, onFocusChange: onFocusChange, onBlur: onBlur } = props; | ||
let { id: id, key: key, closeOnSelect: closeOnSelect, isVirtualized: isVirtualized, 'aria-haspopup': hasPopup, onPressStart: pressStartProp, onPressUp: pressUpProp, onPress: pressProp, onPressChange: onPressChange, onPressEnd: onPressEnd, onHoverStart: hoverStartProp, onHoverChange: onHoverChange, onHoverEnd: onHoverEnd, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onFocus: onFocus, onFocusChange: onFocusChange, onBlur: onBlur, selectionManager: selectionManager = state.selectionManager } = props; | ||
let isTrigger = !!hasPopup; | ||
let isTriggerExpanded = isTrigger && props['aria-expanded'] === 'true'; | ||
var _props_isDisabled; | ||
let isDisabled = (_props_isDisabled = props.isDisabled) !== null && _props_isDisabled !== void 0 ? _props_isDisabled : state.selectionManager.isDisabled(key); | ||
let isDisabled = (_props_isDisabled = props.isDisabled) !== null && _props_isDisabled !== void 0 ? _props_isDisabled : selectionManager.isDisabled(key); | ||
var _props_isSelected; | ||
let isSelected = (_props_isSelected = props.isSelected) !== null && _props_isSelected !== void 0 ? _props_isSelected : state.selectionManager.isSelected(key); | ||
let data = (0, $a3815f0132802737$exports.menuData).get(state); | ||
let isSelected = (_props_isSelected = props.isSelected) !== null && _props_isSelected !== void 0 ? _props_isSelected : selectionManager.isSelected(key); | ||
let data = (0, $815e346b11b84016$exports.menuData).get(state); | ||
let item = state.collection.getItem(key); | ||
@@ -50,8 +51,8 @@ let onClose = props.onClose || data.onClose; | ||
} | ||
if (e.target instanceof HTMLAnchorElement) router.open(e.target, e, item.props.href, item.props.routerOptions); | ||
if (e.target instanceof HTMLAnchorElement && item) router.open(e.target, e, item.props.href, item.props.routerOptions); | ||
}; | ||
let role = 'menuitem'; | ||
if (!isTrigger) { | ||
if (state.selectionManager.selectionMode === 'single') role = 'menuitemradio'; | ||
else if (state.selectionManager.selectionMode === 'multiple') role = 'menuitemcheckbox'; | ||
if (selectionManager.selectionMode === 'single') role = 'menuitemradio'; | ||
else if (selectionManager.selectionMode === 'multiple') role = 'menuitemcheckbox'; | ||
} | ||
@@ -75,3 +76,3 @@ let labelId = (0, $byVdR$reactariautils.useSlotId)(); | ||
}; | ||
if (state.selectionManager.selectionMode !== 'none' && !isTrigger) ariaProps['aria-checked'] = isSelected; | ||
if (selectionManager.selectionMode !== 'none' && !isTrigger) ariaProps['aria-checked'] = isSelected; | ||
if (isVirtualized) { | ||
@@ -85,13 +86,26 @@ ariaProps['aria-posinset'] = item === null || item === void 0 ? void 0 : item.index; | ||
}; | ||
let maybeClose = ()=>{ | ||
// Pressing a menu item should close by default in single selection mode but not multiple | ||
// selection mode, except if overridden by the closeOnSelect prop. | ||
if (!isTrigger && onClose && (closeOnSelect !== null && closeOnSelect !== void 0 ? closeOnSelect : selectionManager.selectionMode !== 'multiple' || selectionManager.isLink(key))) onClose(); | ||
}; | ||
let onPressUp = (e)=>{ | ||
if (e.pointerType !== 'keyboard') { | ||
// If interacting with mouse, allow the user to mouse down on the trigger button, | ||
// drag, and release over an item (matching native behavior). | ||
if (e.pointerType === 'mouse') { | ||
performAction(e); | ||
// Pressing a menu item should close by default in single selection mode but not multiple | ||
// selection mode, except if overridden by the closeOnSelect prop. | ||
if (!isTrigger && onClose && (closeOnSelect !== null && closeOnSelect !== void 0 ? closeOnSelect : state.selectionManager.selectionMode !== 'multiple' || state.selectionManager.isLink(key))) onClose(); | ||
maybeClose(); | ||
} | ||
pressUpProp === null || pressUpProp === void 0 ? void 0 : pressUpProp(e); | ||
}; | ||
let onPress = (e)=>{ | ||
if (e.pointerType !== 'keyboard' && e.pointerType !== 'mouse') { | ||
performAction(e); | ||
maybeClose(); | ||
} | ||
pressProp === null || pressProp === void 0 ? void 0 : pressProp(e); | ||
}; | ||
let { itemProps: itemProps, isFocused: isFocused } = (0, $byVdR$reactariaselection.useSelectableItem)({ | ||
selectionManager: state.selectionManager, | ||
id: id, | ||
selectionManager: selectionManager, | ||
key: key, | ||
@@ -105,3 +119,4 @@ ref: ref, | ||
// actions are performed on key down rather than key up. | ||
linkBehavior: 'none' | ||
linkBehavior: 'none', | ||
shouldUseVirtualFocus: data.shouldUseVirtualFocus | ||
}); | ||
@@ -119,5 +134,6 @@ let { pressProps: pressProps, isPressed: isPressed } = (0, $byVdR$reactariainteractions.usePress)({ | ||
onHoverStart (e) { | ||
if (!(0, $byVdR$reactariainteractions.isFocusVisible)()) { | ||
state.selectionManager.setFocused(true); | ||
state.selectionManager.setFocusedKey(key); | ||
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog. | ||
if (!(0, $byVdR$reactariainteractions.isFocusVisible)() && !(isTriggerExpanded && hasPopup === 'dialog')) { | ||
selectionManager.setFocused(true); | ||
selectionManager.setFocusedKey(key); | ||
} | ||
@@ -139,3 +155,3 @@ hoverStartProp === null || hoverStartProp === void 0 ? void 0 : hoverStartProp(e); | ||
case ' ': | ||
if (!isDisabled && state.selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) onClose(); | ||
if (!isDisabled && selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) onClose(); | ||
break; | ||
@@ -159,5 +175,5 @@ case 'Enter': | ||
}); | ||
let domProps = (0, $byVdR$reactariautils.filterDOMProps)(item.props); | ||
let domProps = (0, $byVdR$reactariautils.filterDOMProps)(item === null || item === void 0 ? void 0 : item.props); | ||
delete domProps.id; | ||
let linkProps = (0, $byVdR$reactariautils.useLinkProps)(item.props); | ||
let linkProps = (0, $byVdR$reactariautils.useLinkProps)(item === null || item === void 0 ? void 0 : item.props); | ||
return { | ||
@@ -170,3 +186,4 @@ menuItemProps: { | ||
} : itemProps, pressProps, hoverProps, keyboardProps, focusProps), | ||
tabIndex: itemProps.tabIndex != null ? -1 : 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 | ||
}, | ||
@@ -173,0 +190,0 @@ labelProps: { |
@@ -1,2 +0,2 @@ | ||
import {menuData as $d5336fe17ce95402$export$6f49b4016bfc8d56} from "./useMenu.module.js"; | ||
import {menuData as $fc79756100351201$export$6f49b4016bfc8d56} from "./utils.module.js"; | ||
import {useRouter as $7Kjv5$useRouter, useSlotId as $7Kjv5$useSlotId, filterDOMProps as $7Kjv5$filterDOMProps, useLinkProps as $7Kjv5$useLinkProps, mergeProps as $7Kjv5$mergeProps} from "@react-aria/utils"; | ||
@@ -23,9 +23,10 @@ import {getItemCount as $7Kjv5$getItemCount} from "@react-stately/collections"; | ||
function $a2e5df62f93c7633$export$9d32628fc2aea7da(props, state, ref) { | ||
let { id: id, key: key, closeOnSelect: closeOnSelect, isVirtualized: isVirtualized, 'aria-haspopup': hasPopup, onPressStart: pressStartProp, onPressUp: pressUpProp, onPress: onPress, onPressChange: onPressChange, onPressEnd: onPressEnd, onHoverStart: hoverStartProp, onHoverChange: onHoverChange, onHoverEnd: onHoverEnd, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onFocus: onFocus, onFocusChange: onFocusChange, onBlur: onBlur } = props; | ||
let { id: id, key: key, closeOnSelect: closeOnSelect, isVirtualized: isVirtualized, 'aria-haspopup': hasPopup, onPressStart: pressStartProp, onPressUp: pressUpProp, onPress: pressProp, onPressChange: onPressChange, onPressEnd: onPressEnd, onHoverStart: hoverStartProp, onHoverChange: onHoverChange, onHoverEnd: onHoverEnd, onKeyDown: onKeyDown, onKeyUp: onKeyUp, onFocus: onFocus, onFocusChange: onFocusChange, onBlur: onBlur, selectionManager: selectionManager = state.selectionManager } = props; | ||
let isTrigger = !!hasPopup; | ||
let isTriggerExpanded = isTrigger && props['aria-expanded'] === 'true'; | ||
var _props_isDisabled; | ||
let isDisabled = (_props_isDisabled = props.isDisabled) !== null && _props_isDisabled !== void 0 ? _props_isDisabled : state.selectionManager.isDisabled(key); | ||
let isDisabled = (_props_isDisabled = props.isDisabled) !== null && _props_isDisabled !== void 0 ? _props_isDisabled : selectionManager.isDisabled(key); | ||
var _props_isSelected; | ||
let isSelected = (_props_isSelected = props.isSelected) !== null && _props_isSelected !== void 0 ? _props_isSelected : state.selectionManager.isSelected(key); | ||
let data = (0, $d5336fe17ce95402$export$6f49b4016bfc8d56).get(state); | ||
let isSelected = (_props_isSelected = props.isSelected) !== null && _props_isSelected !== void 0 ? _props_isSelected : selectionManager.isSelected(key); | ||
let data = (0, $fc79756100351201$export$6f49b4016bfc8d56).get(state); | ||
let item = state.collection.getItem(key); | ||
@@ -44,8 +45,8 @@ let onClose = props.onClose || data.onClose; | ||
} | ||
if (e.target instanceof HTMLAnchorElement) router.open(e.target, e, item.props.href, item.props.routerOptions); | ||
if (e.target instanceof HTMLAnchorElement && item) router.open(e.target, e, item.props.href, item.props.routerOptions); | ||
}; | ||
let role = 'menuitem'; | ||
if (!isTrigger) { | ||
if (state.selectionManager.selectionMode === 'single') role = 'menuitemradio'; | ||
else if (state.selectionManager.selectionMode === 'multiple') role = 'menuitemcheckbox'; | ||
if (selectionManager.selectionMode === 'single') role = 'menuitemradio'; | ||
else if (selectionManager.selectionMode === 'multiple') role = 'menuitemcheckbox'; | ||
} | ||
@@ -69,3 +70,3 @@ let labelId = (0, $7Kjv5$useSlotId)(); | ||
}; | ||
if (state.selectionManager.selectionMode !== 'none' && !isTrigger) ariaProps['aria-checked'] = isSelected; | ||
if (selectionManager.selectionMode !== 'none' && !isTrigger) ariaProps['aria-checked'] = isSelected; | ||
if (isVirtualized) { | ||
@@ -79,13 +80,26 @@ ariaProps['aria-posinset'] = item === null || item === void 0 ? void 0 : item.index; | ||
}; | ||
let maybeClose = ()=>{ | ||
// Pressing a menu item should close by default in single selection mode but not multiple | ||
// selection mode, except if overridden by the closeOnSelect prop. | ||
if (!isTrigger && onClose && (closeOnSelect !== null && closeOnSelect !== void 0 ? closeOnSelect : selectionManager.selectionMode !== 'multiple' || selectionManager.isLink(key))) onClose(); | ||
}; | ||
let onPressUp = (e)=>{ | ||
if (e.pointerType !== 'keyboard') { | ||
// If interacting with mouse, allow the user to mouse down on the trigger button, | ||
// drag, and release over an item (matching native behavior). | ||
if (e.pointerType === 'mouse') { | ||
performAction(e); | ||
// Pressing a menu item should close by default in single selection mode but not multiple | ||
// selection mode, except if overridden by the closeOnSelect prop. | ||
if (!isTrigger && onClose && (closeOnSelect !== null && closeOnSelect !== void 0 ? closeOnSelect : state.selectionManager.selectionMode !== 'multiple' || state.selectionManager.isLink(key))) onClose(); | ||
maybeClose(); | ||
} | ||
pressUpProp === null || pressUpProp === void 0 ? void 0 : pressUpProp(e); | ||
}; | ||
let onPress = (e)=>{ | ||
if (e.pointerType !== 'keyboard' && e.pointerType !== 'mouse') { | ||
performAction(e); | ||
maybeClose(); | ||
} | ||
pressProp === null || pressProp === void 0 ? void 0 : pressProp(e); | ||
}; | ||
let { itemProps: itemProps, isFocused: isFocused } = (0, $7Kjv5$useSelectableItem)({ | ||
selectionManager: state.selectionManager, | ||
id: id, | ||
selectionManager: selectionManager, | ||
key: key, | ||
@@ -99,3 +113,4 @@ ref: ref, | ||
// actions are performed on key down rather than key up. | ||
linkBehavior: 'none' | ||
linkBehavior: 'none', | ||
shouldUseVirtualFocus: data.shouldUseVirtualFocus | ||
}); | ||
@@ -113,5 +128,6 @@ let { pressProps: pressProps, isPressed: isPressed } = (0, $7Kjv5$usePress)({ | ||
onHoverStart (e) { | ||
if (!(0, $7Kjv5$isFocusVisible)()) { | ||
state.selectionManager.setFocused(true); | ||
state.selectionManager.setFocusedKey(key); | ||
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog. | ||
if (!(0, $7Kjv5$isFocusVisible)() && !(isTriggerExpanded && hasPopup === 'dialog')) { | ||
selectionManager.setFocused(true); | ||
selectionManager.setFocusedKey(key); | ||
} | ||
@@ -133,3 +149,3 @@ hoverStartProp === null || hoverStartProp === void 0 ? void 0 : hoverStartProp(e); | ||
case ' ': | ||
if (!isDisabled && state.selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) onClose(); | ||
if (!isDisabled && selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) onClose(); | ||
break; | ||
@@ -153,5 +169,5 @@ case 'Enter': | ||
}); | ||
let domProps = (0, $7Kjv5$filterDOMProps)(item.props); | ||
let domProps = (0, $7Kjv5$filterDOMProps)(item === null || item === void 0 ? void 0 : item.props); | ||
delete domProps.id; | ||
let linkProps = (0, $7Kjv5$useLinkProps)(item.props); | ||
let linkProps = (0, $7Kjv5$useLinkProps)(item === null || item === void 0 ? void 0 : item.props); | ||
return { | ||
@@ -164,3 +180,4 @@ menuItemProps: { | ||
} : itemProps, pressProps, hoverProps, keyboardProps, focusProps), | ||
tabIndex: itemProps.tabIndex != null ? -1 : 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 | ||
}, | ||
@@ -167,0 +184,0 @@ labelProps: { |
@@ -87,2 +87,3 @@ var $d1742ec2644a0949$exports = require("./intlStrings.main.js"); | ||
return { | ||
// @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 | ||
menuTriggerProps: { | ||
@@ -89,0 +90,0 @@ ...triggerProps, |
@@ -81,2 +81,3 @@ import $czs6v$intlStringsmodulejs from "./intlStrings.module.js"; | ||
return { | ||
// @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 | ||
menuTriggerProps: { | ||
@@ -83,0 +84,0 @@ ...triggerProps, |
@@ -54,3 +54,3 @@ var $g3RPq$react = require("react"); | ||
let menu = menuRef.current; | ||
if (isDisabled || !submenu || !isOpen || modality !== 'pointer') { | ||
if (isDisabled || !submenu || !isOpen || modality !== 'pointer' || !menu) { | ||
reset(); | ||
@@ -57,0 +57,0 @@ return; |
@@ -48,3 +48,3 @@ import {useRef as $fUfeP$useRef, useState as $fUfeP$useState, useEffect as $fUfeP$useEffect} from "react"; | ||
let menu = menuRef.current; | ||
if (isDisabled || !submenu || !isOpen || modality !== 'pointer') { | ||
if (isDisabled || !submenu || !isOpen || modality !== 'pointer' || !menu) { | ||
reset(); | ||
@@ -51,0 +51,0 @@ return; |
@@ -59,5 +59,6 @@ var $62347d8c4183e713$exports = require("./useSafelyMouseToSubmenu.main.js"); | ||
if (direction === 'ltr' && e.currentTarget.contains(e.target)) { | ||
var _ref_current; | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
ref.current.focus(); | ||
(_ref_current = ref.current) === null || _ref_current === void 0 ? void 0 : _ref_current.focus(); | ||
} | ||
@@ -67,5 +68,6 @@ break; | ||
if (direction === 'rtl' && e.currentTarget.contains(e.target)) { | ||
var _ref_current1; | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
ref.current.focus(); | ||
(_ref_current1 = ref.current) === null || _ref_current1 === void 0 ? void 0 : _ref_current1.focus(); | ||
} | ||
@@ -79,2 +81,3 @@ break; | ||
}; | ||
var _state_focusStrategy; | ||
let submenuProps = { | ||
@@ -86,3 +89,3 @@ id: overlayId, | ||
onClose: state.closeAll, | ||
autoFocus: state.focusStrategy, | ||
autoFocus: (_state_focusStrategy = state.focusStrategy) !== null && _state_focusStrategy !== void 0 ? _state_focusStrategy : undefined, | ||
onKeyDown: submenuKeyDown | ||
@@ -138,3 +141,4 @@ } | ||
let onBlur = (e)=>{ | ||
if (state.isOpen && parentMenuRef.current.contains(e.relatedTarget)) onSubmenuClose(); | ||
var _parentMenuRef_current; | ||
if (state.isOpen && ((_parentMenuRef_current = parentMenuRef.current) === null || _parentMenuRef_current === void 0 ? void 0 : _parentMenuRef_current.contains(e.relatedTarget))) onSubmenuClose(); | ||
}; | ||
@@ -141,0 +145,0 @@ let shouldCloseOnInteractOutside = (target)=>{ |
@@ -53,5 +53,6 @@ import {useSafelyMouseToSubmenu as $d275435c250248f8$export$85ec83e04c95f50a} from "./useSafelyMouseToSubmenu.module.js"; | ||
if (direction === 'ltr' && e.currentTarget.contains(e.target)) { | ||
var _ref_current; | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
ref.current.focus(); | ||
(_ref_current = ref.current) === null || _ref_current === void 0 ? void 0 : _ref_current.focus(); | ||
} | ||
@@ -61,5 +62,6 @@ break; | ||
if (direction === 'rtl' && e.currentTarget.contains(e.target)) { | ||
var _ref_current1; | ||
e.stopPropagation(); | ||
onSubmenuClose(); | ||
ref.current.focus(); | ||
(_ref_current1 = ref.current) === null || _ref_current1 === void 0 ? void 0 : _ref_current1.focus(); | ||
} | ||
@@ -73,2 +75,3 @@ break; | ||
}; | ||
var _state_focusStrategy; | ||
let submenuProps = { | ||
@@ -80,3 +83,3 @@ id: overlayId, | ||
onClose: state.closeAll, | ||
autoFocus: state.focusStrategy, | ||
autoFocus: (_state_focusStrategy = state.focusStrategy) !== null && _state_focusStrategy !== void 0 ? _state_focusStrategy : undefined, | ||
onKeyDown: submenuKeyDown | ||
@@ -132,3 +135,4 @@ } | ||
let onBlur = (e)=>{ | ||
if (state.isOpen && parentMenuRef.current.contains(e.relatedTarget)) onSubmenuClose(); | ||
var _parentMenuRef_current; | ||
if (state.isOpen && ((_parentMenuRef_current = parentMenuRef.current) === null || _parentMenuRef_current === void 0 ? void 0 : _parentMenuRef_current.contains(e.relatedTarget))) onSubmenuClose(); | ||
}; | ||
@@ -135,0 +139,0 @@ let shouldCloseOnInteractOutside = (target)=>{ |
{ | ||
"name": "@react-aria/menu", | ||
"version": "3.0.0-nightly-987f174ba-241015", | ||
"version": "3.0.0-nightly-993de98ad-241210", | ||
"description": "Spectrum UI components in React", | ||
@@ -25,19 +25,20 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@react-aria/focus": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-aria/i18n": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-aria/interactions": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-aria/overlays": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-aria/selection": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-aria/utils": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-stately/collections": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-stately/menu": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-stately/tree": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-types/button": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-types/menu": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-types/shared": "^3.0.0-nightly-987f174ba-241015", | ||
"@react-aria/focus": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-aria/i18n": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-aria/interactions": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-aria/overlays": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-aria/selection": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-aria/utils": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-stately/collections": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-stately/menu": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-stately/selection": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-stately/tree": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-types/button": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-types/menu": "^3.0.0-nightly-993de98ad-241210", | ||
"@react-types/shared": "^3.0.0-nightly-993de98ad-241210", | ||
"@swc/helpers": "^0.5.0" | ||
}, | ||
"peerDependencies": { | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0", | ||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0" | ||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", | ||
"react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" | ||
}, | ||
@@ -47,3 +48,3 @@ "publishConfig": { | ||
}, | ||
"stableVersion": "3.15.5" | ||
"stableVersion": "3.16.0" | ||
} |
@@ -14,4 +14,5 @@ /* | ||
import {AriaMenuProps} from '@react-types/menu'; | ||
import {DOMAttributes, Key, KeyboardDelegate, KeyboardEvents, RefObject} from '@react-types/shared'; | ||
import {DOMAttributes, KeyboardDelegate, KeyboardEvents, RefObject} from '@react-types/shared'; | ||
import {filterDOMProps, mergeProps} from '@react-aria/utils'; | ||
import {menuData} from './utils'; | ||
import {TreeState} from '@react-stately/tree'; | ||
@@ -28,3 +29,2 @@ import {useSelectableList} from '@react-aria/selection'; | ||
isVirtualized?: boolean, | ||
/** | ||
@@ -34,12 +34,9 @@ * An optional keyboard delegate implementation for type to select, | ||
*/ | ||
keyboardDelegate?: KeyboardDelegate | ||
keyboardDelegate?: KeyboardDelegate, | ||
/** | ||
* Whether the menu items should use virtual focus instead of being focused directly. | ||
*/ | ||
shouldUseVirtualFocus?: boolean | ||
} | ||
interface MenuData { | ||
onClose?: () => void, | ||
onAction?: (key: Key) => void | ||
} | ||
export const menuData = new WeakMap<TreeState<unknown>, MenuData>(); | ||
/** | ||
@@ -76,3 +73,4 @@ * Provides the behavior and accessibility implementation for a menu component. | ||
onClose: props.onClose, | ||
onAction: props.onAction | ||
onAction: props.onAction, | ||
shouldUseVirtualFocus: props.shouldUseVirtualFocus | ||
}); | ||
@@ -86,4 +84,4 @@ | ||
// don't clear the menu selected keys if the user is presses escape since escape closes the menu | ||
if (e.key !== 'Escape') { | ||
listProps.onKeyDown(e); | ||
if (e.key !== 'Escape' || props.shouldUseVirtualFocus) { | ||
listProps.onKeyDown?.(e); | ||
} | ||
@@ -90,0 +88,0 @@ } |
@@ -17,3 +17,4 @@ /* | ||
import {isFocusVisible, useFocus, useHover, useKeyboard, usePress} from '@react-aria/interactions'; | ||
import {menuData} from './useMenu'; | ||
import {menuData} from './utils'; | ||
import {SelectionManager} from '@react-stately/selection'; | ||
import {TreeState} from '@react-stately/tree'; | ||
@@ -62,3 +63,3 @@ import {useSelectableItem} from '@react-aria/selection'; | ||
/** The unique key for the menu item. */ | ||
key?: Key, | ||
key: Key, | ||
@@ -93,3 +94,6 @@ /** | ||
/** Identifies the menu item's popup element whose contents or presence is controlled by the menu item. */ | ||
'aria-controls'?: string | ||
'aria-controls'?: string, | ||
/** Override of the selection manager. By default, `state.selectionManager` is used. */ | ||
selectionManager?: SelectionManager | ||
} | ||
@@ -112,3 +116,3 @@ | ||
onPressUp: pressUpProp, | ||
onPress, | ||
onPress: pressProp, | ||
onPressChange, | ||
@@ -123,9 +127,11 @@ onPressEnd, | ||
onFocusChange, | ||
onBlur | ||
onBlur, | ||
selectionManager = state.selectionManager | ||
} = props; | ||
let isTrigger = !!hasPopup; | ||
let isDisabled = props.isDisabled ?? state.selectionManager.isDisabled(key); | ||
let isSelected = props.isSelected ?? state.selectionManager.isSelected(key); | ||
let data = menuData.get(state); | ||
let isTriggerExpanded = isTrigger && props['aria-expanded'] === 'true'; | ||
let isDisabled = props.isDisabled ?? selectionManager.isDisabled(key); | ||
let isSelected = props.isSelected ?? selectionManager.isSelected(key); | ||
let data = menuData.get(state)!; | ||
let item = state.collection.getItem(key); | ||
@@ -151,3 +157,3 @@ let onClose = props.onClose || data.onClose; | ||
if (e.target instanceof HTMLAnchorElement) { | ||
if (e.target instanceof HTMLAnchorElement && item) { | ||
router.open(e.target, e, item.props.href, item.props.routerOptions as RouterOptions); | ||
@@ -159,5 +165,5 @@ } | ||
if (!isTrigger) { | ||
if (state.selectionManager.selectionMode === 'single') { | ||
if (selectionManager.selectionMode === 'single') { | ||
role = 'menuitemradio'; | ||
} else if (state.selectionManager.selectionMode === 'multiple') { | ||
} else if (selectionManager.selectionMode === 'multiple') { | ||
role = 'menuitemcheckbox'; | ||
@@ -183,3 +189,3 @@ } | ||
if (state.selectionManager.selectionMode !== 'none' && !isTrigger) { | ||
if (selectionManager.selectionMode !== 'none' && !isTrigger) { | ||
ariaProps['aria-checked'] = isSelected; | ||
@@ -201,11 +207,16 @@ } | ||
let maybeClose = () => { | ||
// Pressing a menu item should close by default in single selection mode but not multiple | ||
// selection mode, except if overridden by the closeOnSelect prop. | ||
if (!isTrigger && onClose && (closeOnSelect ?? (selectionManager.selectionMode !== 'multiple' || selectionManager.isLink(key)))) { | ||
onClose(); | ||
} | ||
}; | ||
let onPressUp = (e: PressEvent) => { | ||
if (e.pointerType !== 'keyboard') { | ||
// If interacting with mouse, allow the user to mouse down on the trigger button, | ||
// drag, and release over an item (matching native behavior). | ||
if (e.pointerType === 'mouse') { | ||
performAction(e); | ||
// Pressing a menu item should close by default in single selection mode but not multiple | ||
// selection mode, except if overridden by the closeOnSelect prop. | ||
if (!isTrigger && onClose && (closeOnSelect ?? (state.selectionManager.selectionMode !== 'multiple' || state.selectionManager.isLink(key)))) { | ||
onClose(); | ||
} | ||
maybeClose(); | ||
} | ||
@@ -216,4 +227,14 @@ | ||
let onPress = (e: PressEvent) => { | ||
if (e.pointerType !== 'keyboard' && e.pointerType !== 'mouse') { | ||
performAction(e); | ||
maybeClose(); | ||
} | ||
pressProp?.(e); | ||
}; | ||
let {itemProps, isFocused} = useSelectableItem({ | ||
selectionManager: state.selectionManager, | ||
id, | ||
selectionManager: selectionManager, | ||
key, | ||
@@ -227,3 +248,4 @@ ref, | ||
// actions are performed on key down rather than key up. | ||
linkBehavior: 'none' | ||
linkBehavior: 'none', | ||
shouldUseVirtualFocus: data.shouldUseVirtualFocus | ||
}); | ||
@@ -242,5 +264,6 @@ | ||
onHoverStart(e) { | ||
if (!isFocusVisible()) { | ||
state.selectionManager.setFocused(true); | ||
state.selectionManager.setFocusedKey(key); | ||
// Hovering over an already expanded sub dialog trigger should keep focus in the dialog. | ||
if (!isFocusVisible() && !(isTriggerExpanded && hasPopup === 'dialog')) { | ||
selectionManager.setFocused(true); | ||
selectionManager.setFocusedKey(key); | ||
} | ||
@@ -264,3 +287,3 @@ hoverStartProp?.(e); | ||
case ' ': | ||
if (!isDisabled && state.selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) { | ||
if (!isDisabled && selectionManager.selectionMode === 'none' && !isTrigger && closeOnSelect !== false && onClose) { | ||
onClose(); | ||
@@ -288,5 +311,5 @@ } | ||
let {focusProps} = useFocus({onBlur, onFocus, onFocusChange}); | ||
let domProps = filterDOMProps(item.props); | ||
let domProps = filterDOMProps(item?.props); | ||
delete domProps.id; | ||
let linkProps = useLinkProps(item.props); | ||
let linkProps = useLinkProps(item?.props); | ||
@@ -297,3 +320,4 @@ return { | ||
...mergeProps(domProps, linkProps, isTrigger ? {onFocus: itemProps.onFocus, 'data-key': itemProps['data-key']} : itemProps, pressProps, hoverProps, keyboardProps, focusProps), | ||
tabIndex: itemProps.tabIndex != null ? -1 : 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 | ||
}, | ||
@@ -300,0 +324,0 @@ labelProps: { |
@@ -50,3 +50,3 @@ /* | ||
let { | ||
type = 'menu' as AriaMenuTriggerProps['type'], | ||
type = 'menu', | ||
isDisabled, | ||
@@ -132,2 +132,3 @@ trigger = 'press' | ||
return { | ||
// @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 | ||
menuTriggerProps: { | ||
@@ -134,0 +135,0 @@ ...triggerProps, |
@@ -66,3 +66,3 @@ | ||
if (isDisabled || !submenu || !isOpen || modality !== 'pointer') { | ||
if (isDisabled || !submenu || !isOpen || modality !== 'pointer' || !menu) { | ||
reset(); | ||
@@ -69,0 +69,0 @@ return; |
@@ -44,3 +44,3 @@ /* | ||
interface SubmenuTriggerProps extends AriaMenuItemProps { | ||
interface SubmenuTriggerProps extends Omit<AriaMenuItemProps, 'key'> { | ||
/** Whether the submenu trigger is in an expanded state. */ | ||
@@ -105,3 +105,3 @@ isOpen: boolean | ||
onSubmenuClose(); | ||
ref.current.focus(); | ||
ref.current?.focus(); | ||
} | ||
@@ -113,3 +113,3 @@ break; | ||
onSubmenuClose(); | ||
ref.current.focus(); | ||
ref.current?.focus(); | ||
} | ||
@@ -130,3 +130,3 @@ break; | ||
onClose: state.closeAll, | ||
autoFocus: state.focusStrategy, | ||
autoFocus: state.focusStrategy ?? undefined, | ||
onKeyDown: submenuKeyDown | ||
@@ -212,3 +212,3 @@ }) | ||
let onBlur = (e) => { | ||
if (state.isOpen && parentMenuRef.current.contains(e.relatedTarget)) { | ||
if (state.isOpen && parentMenuRef.current?.contains(e.relatedTarget)) { | ||
onSubmenuClose(); | ||
@@ -215,0 +215,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
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
Unidentified License
License(Experimental) Something that seems like a license was found, but its contents could not be matched with a known license.
Found 2 instances in 1 package
323342
5.63%227
2.71%3974
4.25%16
6.67%8
33.33%