@elliemae/ds-menu-button 3.80.0-next.50 → 3.80.0-next.53
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/cjs/config/useSplitInherithedProps.js +13 -2
- package/dist/cjs/config/useSplitInherithedProps.js.map +2 -2
- package/dist/cjs/parts/DSFlyoutMenu/DSFlyoutMenu.js +25 -2
- package/dist/cjs/parts/DSFlyoutMenu/DSFlyoutMenu.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js +4 -1
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useGlobalEvents.js +4 -3
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useGlobalEvents.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js +27 -10
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuOpenStatus.js +14 -8
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuOpenStatus.js.map +2 -2
- package/dist/cjs/parts/DSOpinionatedButton/config/useTriggerEventsHandlers.js +19 -9
- package/dist/cjs/parts/DSOpinionatedButton/config/useTriggerEventsHandlers.js.map +2 -2
- package/dist/cjs/react-desc-prop-types.js +9 -2
- package/dist/cjs/react-desc-prop-types.js.map +2 -2
- package/dist/esm/config/useSplitInherithedProps.js +13 -2
- package/dist/esm/config/useSplitInherithedProps.js.map +2 -2
- package/dist/esm/parts/DSFlyoutMenu/DSFlyoutMenu.js +25 -2
- package/dist/esm/parts/DSFlyoutMenu/DSFlyoutMenu.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js +4 -1
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useGlobalEvents.js +4 -3
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useGlobalEvents.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js +27 -10
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuOpenStatus.js +14 -8
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuOpenStatus.js.map +2 -2
- package/dist/esm/parts/DSOpinionatedButton/config/useTriggerEventsHandlers.js +19 -9
- package/dist/esm/parts/DSOpinionatedButton/config/useTriggerEventsHandlers.js.map +2 -2
- package/dist/esm/react-desc-prop-types.js +9 -2
- package/dist/esm/react-desc-prop-types.js.map +2 -2
- package/dist/types/config/useSplitInherithedProps.d.ts +3 -2
- package/dist/types/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.d.ts +3 -1
- package/dist/types/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.d.ts +1 -0
- package/dist/types/parts/DSMenuBehaviouralContextProvider/config/useMenuOpenStatus.d.ts +12 -4
- package/dist/types/react-desc-prop-types.d.ts +13 -2
- package/package.json +12 -12
|
@@ -79,6 +79,7 @@ const useSplitInherithedProps = ({
|
|
|
79
79
|
onActivateItem,
|
|
80
80
|
onOpen,
|
|
81
81
|
onClose,
|
|
82
|
+
a11yDangerouslyConsumerOwnedMenuOpenState,
|
|
82
83
|
maxHeight,
|
|
83
84
|
// this is not shared with anything at all, it's instead a property of the "root" node, each node (with a submenu) can have its own maxHeight
|
|
84
85
|
...buttonInheritedProps
|
|
@@ -98,6 +99,14 @@ const useSplitInherithedProps = ({
|
|
|
98
99
|
}
|
|
99
100
|
return [];
|
|
100
101
|
}, [selectedItems, treeRootNode]);
|
|
102
|
+
const onCloseAndDismissal = import_react.default.useCallback(
|
|
103
|
+
(metainfo) => {
|
|
104
|
+
if (onClose) onClose(metainfo);
|
|
105
|
+
if (metainfo.reason === "outside-click" || metainfo.reason === "escape")
|
|
106
|
+
onClickOutside(metainfo.event);
|
|
107
|
+
},
|
|
108
|
+
[onClose, onClickOutside]
|
|
109
|
+
);
|
|
101
110
|
const menuSpecificProps = import_react.default.useMemo(
|
|
102
111
|
() => ({
|
|
103
112
|
options,
|
|
@@ -133,7 +142,8 @@ const useSplitInherithedProps = ({
|
|
|
133
142
|
onItemSelected,
|
|
134
143
|
onActivateItem,
|
|
135
144
|
onOpen,
|
|
136
|
-
onClose
|
|
145
|
+
onClose: onCloseAndDismissal,
|
|
146
|
+
a11yDangerouslyConsumerOwnedMenuOpenState
|
|
137
147
|
},
|
|
138
148
|
opinionatedButtonProps: {
|
|
139
149
|
...buttonInheritedProps,
|
|
@@ -149,7 +159,8 @@ const useSplitInherithedProps = ({
|
|
|
149
159
|
onItemSelected,
|
|
150
160
|
onActivateItem,
|
|
151
161
|
onOpen,
|
|
152
|
-
|
|
162
|
+
onCloseAndDismissal,
|
|
163
|
+
a11yDangerouslyConsumerOwnedMenuOpenState,
|
|
153
164
|
buttonInheritedProps,
|
|
154
165
|
innerRefSnatchingNode,
|
|
155
166
|
menuSpecificProps
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../src/config/useSplitInherithedProps.ts", "../../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["/* *******************************************************\n * From official source definition\n *\n * Menu Button Pattern\n * About This Pattern:\n * A menu button is a button\n * that opens a menu\n * (as described in the Menu and Menubar Pattern).\n *\n * Since the menu button is a button, it inherits all the props from the Button component.\n * Since the menu button MUST have a menu, it also have a set of specific props used to handle the menu.\n * Because we build with atomic composition in mind, the \"logic layer\" is separated and has a yet another set of props.\n *\n * This is effectively an OOP \"extension\" of the Button component,\n * so it has the same props,\n * BUT also a few more to handle the specific behavior of the menu.\n *\n * this hooks is meant to take all the DSMenuButtonT.InternalProps with default already merged\n * and spit out menuSpecificProps & buttonInheritedProps\n ******************************************************* */\nimport type {} from '@elliemae/ds-system';\nimport type {} from '@elliemae/pui-theme';\nimport React from 'react';\nimport { type DSMenuButtonT } from '../react-desc-prop-types.js';\nimport { isObjectAMenuNode, isSelectionableNode } from '../utils/nodesTypeguardsAndGetters.js';\nimport { resolveRef } from '../utils/resolveRef.js';\n\n/**\n * Converts selected options to a map of selected nodes.\n * @param {DSMenuButtonT.SelectionableMenuNodes[]} selectedItems - The selected options, as provided by the user (either items or nodes).\n * @param {DSMenuButtonT.MenuNode} treeRootNode - The root node of the menu tree.\n * @returns {DSMenuButtonT.SelectionableMenuNodes[]} - The map of selected nodes.\n * @throws Will throw an error if a selected option is not found in the tree.\n */\nconst convertSelectedOptionsToNodes = (\n selectedItems: DSMenuButtonT.SelectionableMenuNodes[] | DSMenuButtonT.SelectionableMenuItemInterface[],\n treeRootNode: DSMenuButtonT.MenuNode,\n): DSMenuButtonT.SelectionableMenuNodes[] => {\n const convertedSelection: DSMenuButtonT.SelectionableMenuNodes[] = [];\n\n selectedItems.forEach((selectedItem) => {\n const needsConversion = !isObjectAMenuNode(selectedItem);\n if (!needsConversion) {\n convertedSelection.push(selectedItem);\n return;\n }\n\n const { dsId } = selectedItem;\n const selectedNode = treeRootNode.findNode((node: DSMenuButtonT.MenuNode) => node.dsId === dsId);\n if (!selectedNode) {\n throw new Error(`Selected option with dsId ${dsId} not found in the menu tree.`);\n }\n if (!isSelectionableNode(selectedNode)) {\n // eslint-disable-next-line no-console\n console.warn(`Selected option with dsId ${dsId} is not a selectionable node and will be ignored.`);\n return;\n }\n convertedSelection.push(selectedNode);\n });\n\n return convertedSelection;\n};\n\ntype UseSplitInherithedPropsConfig = {\n propsWithDefault: DSMenuButtonT.InternalProps;\n focusableNodes: DSMenuButtonT.PseudoFocusableMenuNodes[];\n treeRootNode: DSMenuButtonT.MenuNode;\n};\nexport const useSplitInherithedProps = ({\n propsWithDefault,\n focusableNodes,\n treeRootNode,\n}: UseSplitInherithedPropsConfig) => {\n const buttonDOMNodeRef = React.useRef<HTMLElement | null>(null);\n // when this component has been wrote\n // =============================================================================\n // MENU BUTTON props are:\n // Required\n // optionsTree, menuSpecificProps\n // Default (we already have a value because props for this hook are already merged with default props)\n // onClickOutside, onOptionClick, openedSubmenus, onSubmenuToggle, isLoading, isSkeleton, selectedOptions\n // Optional (may or may not be present)\n // ItemRenderer\n // =============================================================================\n // Props for the DSMenuBehaviouralContextProvider\n // =============================================================================\n // Required\n // selectedItems, onItemSelected, onActivateItem,\n // Optional\n // onDisplayedSubmenuChange, onOpen, onClose\n\n const {\n options,\n onClickOutside,\n onOptionClick,\n openedSubmenus,\n onSubmenuToggle,\n isLoading,\n isSkeleton,\n ItemRenderer,\n floatingConfig,\n innerRef,\n selectedItems,\n onDisplayedSubmenuChange,\n onItemSelected,\n onActivateItem,\n onOpen,\n onClose,\n maxHeight, // this is not shared with anything at all, it's instead a property of the \"root\" node, each node (with a submenu) can have its own maxHeight\n ...buttonInheritedProps\n } = propsWithDefault;\n\n // the button is allowed to receive innerRef, but we also need to invoke the setButtonDOMNode to store the button node\n // so we create a functional ref to do both\n const innerRefSnatchingNode: Required<DSMenuButtonT.Props>['innerRef'] = React.useCallback(\n (node: HTMLButtonElement) => {\n buttonDOMNodeRef.current = node;\n if (innerRef) {\n resolveRef(innerRef, node);\n }\n },\n [innerRef],\n );\n // in the WIDGET API, the user can provide selectedItems as an array of \"items\" or an array of \"nodes\"\n // the component needs the \"nodes\" to handle the logic and rendering & accessibility\n // if the user provides \"items\", we convert them to \"nodes\" here\n const selectedNodesMap = React.useMemo(() => {\n if (selectedItems) {\n return convertSelectedOptionsToNodes(selectedItems, treeRootNode);\n }\n return [];\n }, [selectedItems, treeRootNode]);\n\n const menuSpecificProps = React.useMemo(\n () => ({\n options,\n onClickOutside,\n onOptionClick,\n openedSubmenus,\n onSubmenuToggle,\n isLoading,\n isSkeleton,\n ItemRenderer,\n ...(floatingConfig !== undefined ? { floatingConfig } : {}),\n }),\n [\n options,\n onClickOutside,\n onOptionClick,\n openedSubmenus,\n onSubmenuToggle,\n isLoading,\n isSkeleton,\n ItemRenderer,\n floatingConfig,\n ],\n );\n\n return React.useMemo(\n () => ({\n menuBehaviouralLayerProps: {\n buttonDOMNodeRef,\n selectedNodes: selectedNodesMap,\n focusableNodes,\n optionsTree: treeRootNode,\n onDisplayedSubmenuChange,\n onItemSelected,\n onActivateItem,\n onOpen,\n onClose,\n },\n opinionatedButtonProps: {\n ...(buttonInheritedProps as DSMenuButtonT.ButtonInheritedProps),\n innerRef: innerRefSnatchingNode,\n menuSpecificProps,\n },\n }),\n [\n selectedNodesMap,\n focusableNodes,\n treeRootNode,\n onDisplayedSubmenuChange,\n onItemSelected,\n onActivateItem,\n onOpen,\n
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADsBvB,mBAAkB;AAElB,uCAAuD;AACvD,wBAA2B;AAS3B,MAAM,gCAAgC,CACpC,eACA,iBAC2C;AAC3C,QAAM,qBAA6D,CAAC;AAEpE,gBAAc,QAAQ,CAAC,iBAAiB;AACtC,UAAM,kBAAkB,KAAC,oDAAkB,YAAY;AACvD,QAAI,CAAC,iBAAiB;AACpB,yBAAmB,KAAK,YAAY;AACpC;AAAA,IACF;AAEA,UAAM,EAAE,KAAK,IAAI;AACjB,UAAM,eAAe,aAAa,SAAS,CAAC,SAAiC,KAAK,SAAS,IAAI;AAC/F,QAAI,CAAC,cAAc;AACjB,YAAM,IAAI,MAAM,6BAA6B,IAAI,8BAA8B;AAAA,IACjF;AACA,QAAI,KAAC,sDAAoB,YAAY,GAAG;AAEtC,cAAQ,KAAK,6BAA6B,IAAI,mDAAmD;AACjG;AAAA,IACF;AACA,uBAAmB,KAAK,YAAY;AAAA,EACtC,CAAC;AAED,SAAO;AACT;AAOO,MAAM,0BAA0B,CAAC;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AACF,MAAqC;AACnC,QAAM,mBAAmB,aAAAA,QAAM,OAA2B,IAAI;AAkB9D,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA;AAAA,IACA,GAAG;AAAA,EACL,IAAI;AAIJ,QAAM,wBAAmE,aAAAA,QAAM;AAAA,IAC7E,CAAC,SAA4B;AAC3B,uBAAiB,UAAU;AAC3B,UAAI,UAAU;AACZ,0CAAW,UAAU,IAAI;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,CAAC,QAAQ;AAAA,EACX;AAIA,QAAM,mBAAmB,aAAAA,QAAM,QAAQ,MAAM;AAC3C,QAAI,eAAe;AACjB,aAAO,8BAA8B,eAAe,YAAY;AAAA,IAClE;AACA,WAAO,CAAC;AAAA,EACV,GAAG,CAAC,eAAe,YAAY,CAAC;
|
|
4
|
+
"sourcesContent": ["/* *******************************************************\n * From official source definition\n *\n * Menu Button Pattern\n * About This Pattern:\n * A menu button is a button\n * that opens a menu\n * (as described in the Menu and Menubar Pattern).\n *\n * Since the menu button is a button, it inherits all the props from the Button component.\n * Since the menu button MUST have a menu, it also have a set of specific props used to handle the menu.\n * Because we build with atomic composition in mind, the \"logic layer\" is separated and has a yet another set of props.\n *\n * This is effectively an OOP \"extension\" of the Button component,\n * so it has the same props,\n * BUT also a few more to handle the specific behavior of the menu.\n *\n * this hooks is meant to take all the DSMenuButtonT.InternalProps with default already merged\n * and spit out menuSpecificProps & buttonInheritedProps\n ******************************************************* */\nimport type {} from '@elliemae/ds-system';\nimport type {} from '@elliemae/pui-theme';\nimport React from 'react';\nimport { type DSMenuButtonT } from '../react-desc-prop-types.js';\nimport { isObjectAMenuNode, isSelectionableNode } from '../utils/nodesTypeguardsAndGetters.js';\nimport { resolveRef } from '../utils/resolveRef.js';\n\n/**\n * Converts selected options to a map of selected nodes.\n * @param {DSMenuButtonT.SelectionableMenuNodes[]} selectedItems - The selected options, as provided by the user (either items or nodes).\n * @param {DSMenuButtonT.MenuNode} treeRootNode - The root node of the menu tree.\n * @returns {DSMenuButtonT.SelectionableMenuNodes[]} - The map of selected nodes.\n * @throws Will throw an error if a selected option is not found in the tree.\n */\nconst convertSelectedOptionsToNodes = (\n selectedItems: DSMenuButtonT.SelectionableMenuNodes[] | DSMenuButtonT.SelectionableMenuItemInterface[],\n treeRootNode: DSMenuButtonT.MenuNode,\n): DSMenuButtonT.SelectionableMenuNodes[] => {\n const convertedSelection: DSMenuButtonT.SelectionableMenuNodes[] = [];\n\n selectedItems.forEach((selectedItem) => {\n const needsConversion = !isObjectAMenuNode(selectedItem);\n if (!needsConversion) {\n convertedSelection.push(selectedItem);\n return;\n }\n\n const { dsId } = selectedItem;\n const selectedNode = treeRootNode.findNode((node: DSMenuButtonT.MenuNode) => node.dsId === dsId);\n if (!selectedNode) {\n throw new Error(`Selected option with dsId ${dsId} not found in the menu tree.`);\n }\n if (!isSelectionableNode(selectedNode)) {\n // eslint-disable-next-line no-console\n console.warn(`Selected option with dsId ${dsId} is not a selectionable node and will be ignored.`);\n return;\n }\n convertedSelection.push(selectedNode);\n });\n\n return convertedSelection;\n};\n\ntype UseSplitInherithedPropsConfig = {\n propsWithDefault: DSMenuButtonT.InternalProps;\n focusableNodes: DSMenuButtonT.PseudoFocusableMenuNodes[];\n treeRootNode: DSMenuButtonT.MenuNode;\n};\nexport const useSplitInherithedProps = ({\n propsWithDefault,\n focusableNodes,\n treeRootNode,\n}: UseSplitInherithedPropsConfig) => {\n const buttonDOMNodeRef = React.useRef<HTMLElement | null>(null);\n // when this component has been wrote\n // =============================================================================\n // MENU BUTTON props are:\n // Required\n // optionsTree, menuSpecificProps\n // Default (we already have a value because props for this hook are already merged with default props)\n // onClickOutside, onOptionClick, openedSubmenus, onSubmenuToggle, isLoading, isSkeleton, selectedOptions\n // Optional (may or may not be present)\n // ItemRenderer\n // =============================================================================\n // Props for the DSMenuBehaviouralContextProvider\n // =============================================================================\n // Required\n // selectedItems, onItemSelected, onActivateItem,\n // Optional\n // onDisplayedSubmenuChange, onOpen, onClose\n\n const {\n options,\n onClickOutside,\n onOptionClick,\n openedSubmenus,\n onSubmenuToggle,\n isLoading,\n isSkeleton,\n ItemRenderer,\n floatingConfig,\n innerRef,\n selectedItems,\n onDisplayedSubmenuChange,\n onItemSelected,\n onActivateItem,\n onOpen,\n onClose,\n a11yDangerouslyConsumerOwnedMenuOpenState,\n maxHeight, // this is not shared with anything at all, it's instead a property of the \"root\" node, each node (with a submenu) can have its own maxHeight\n ...buttonInheritedProps\n } = propsWithDefault;\n\n // the button is allowed to receive innerRef, but we also need to invoke the setButtonDOMNode to store the button node\n // so we create a functional ref to do both\n const innerRefSnatchingNode: Required<DSMenuButtonT.Props>['innerRef'] = React.useCallback(\n (node: HTMLButtonElement) => {\n buttonDOMNodeRef.current = node;\n if (innerRef) {\n resolveRef(innerRef, node);\n }\n },\n [innerRef],\n );\n // in the WIDGET API, the user can provide selectedItems as an array of \"items\" or an array of \"nodes\"\n // the component needs the \"nodes\" to handle the logic and rendering & accessibility\n // if the user provides \"items\", we convert them to \"nodes\" here\n const selectedNodesMap = React.useMemo(() => {\n if (selectedItems) {\n return convertSelectedOptionsToNodes(selectedItems, treeRootNode);\n }\n return [];\n }, [selectedItems, treeRootNode]);\n\n // `onClickOutside` is the dismissal channel \u2014 a pointer interaction outside the menu, or the Escape key \u2014\n // it is a subset of the close intents, so it is derived from `onClose`'s `reason` instead of a second code path\n const onCloseAndDismissal = React.useCallback<Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onClose']>(\n (metainfo) => {\n if (onClose) onClose(metainfo);\n if (metainfo.reason === 'outside-click' || metainfo.reason === 'escape')\n onClickOutside(metainfo.event as Parameters<typeof onClickOutside>[0]);\n },\n [onClose, onClickOutside],\n );\n\n const menuSpecificProps = React.useMemo(\n () => ({\n options,\n onClickOutside,\n onOptionClick,\n openedSubmenus,\n onSubmenuToggle,\n isLoading,\n isSkeleton,\n ItemRenderer,\n ...(floatingConfig !== undefined ? { floatingConfig } : {}),\n }),\n [\n options,\n onClickOutside,\n onOptionClick,\n openedSubmenus,\n onSubmenuToggle,\n isLoading,\n isSkeleton,\n ItemRenderer,\n floatingConfig,\n ],\n );\n\n return React.useMemo(\n () => ({\n menuBehaviouralLayerProps: {\n buttonDOMNodeRef,\n selectedNodes: selectedNodesMap,\n focusableNodes,\n optionsTree: treeRootNode,\n onDisplayedSubmenuChange,\n onItemSelected,\n onActivateItem,\n onOpen,\n onClose: onCloseAndDismissal,\n a11yDangerouslyConsumerOwnedMenuOpenState,\n },\n opinionatedButtonProps: {\n ...(buttonInheritedProps as DSMenuButtonT.ButtonInheritedProps),\n innerRef: innerRefSnatchingNode,\n menuSpecificProps,\n },\n }),\n [\n selectedNodesMap,\n focusableNodes,\n treeRootNode,\n onDisplayedSubmenuChange,\n onItemSelected,\n onActivateItem,\n onOpen,\n onCloseAndDismissal,\n a11yDangerouslyConsumerOwnedMenuOpenState,\n buttonInheritedProps,\n innerRefSnatchingNode,\n menuSpecificProps,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["React"]
|
|
7
7
|
}
|
|
@@ -74,8 +74,14 @@ const DSFlyoutMenu = (props) => {
|
|
|
74
74
|
} = import_react.default.useContext(import_MenuButtonContextProviderCTX.MenuButtonContextProvider);
|
|
75
75
|
const {
|
|
76
76
|
instanceUid,
|
|
77
|
-
globalEventsHelpers: { mainMenuRef, handleSubmenusRefChange }
|
|
77
|
+
globalEventsHelpers: { mainMenuRef, handleSubmenusRefChange },
|
|
78
|
+
openIntentPendingRef,
|
|
79
|
+
menuItemEventsHandlers: { handleMenuTabKeyDownCapture },
|
|
80
|
+
focusRegion,
|
|
81
|
+
trackFocusFirstChildItem,
|
|
82
|
+
propsWithDefault: { focusableNodes, a11yDangerouslyConsumerOwnedMenuOpenState }
|
|
78
83
|
} = import_react.default.useContext(import_MenuBehaviouralContextProviderCTX.MenuBehaviouralContextProviderContext);
|
|
84
|
+
const lastMountedRootNodeRef = import_react.default.useRef(null);
|
|
79
85
|
const [didAnimationEnd, setDidAnimationEnd] = import_react.default.useState(false);
|
|
80
86
|
const { setFloatingRef, floatingStyles, floatingContext, ItemRenderer, isMenuOpen, itemNode } = propsWithDefault;
|
|
81
87
|
const ariaPositionsByDsId = import_react.default.useMemo(
|
|
@@ -87,11 +93,27 @@ const DSFlyoutMenu = (props) => {
|
|
|
87
93
|
(node) => {
|
|
88
94
|
if ((0, import_nodesTypeguardsAndGetters.isRootNode)(itemNode)) {
|
|
89
95
|
mainMenuRef.current = node;
|
|
96
|
+
if (node && node !== lastMountedRootNodeRef.current) {
|
|
97
|
+
lastMountedRootNodeRef.current = node;
|
|
98
|
+
const wasIntentArmed = openIntentPendingRef.current;
|
|
99
|
+
openIntentPendingRef.current = false;
|
|
100
|
+
if (a11yDangerouslyConsumerOwnedMenuOpenState !== void 0 && !wasIntentArmed && !focusRegion.startsWith("item-") && focusableNodes.length > 0)
|
|
101
|
+
trackFocusFirstChildItem(itemNode);
|
|
102
|
+
}
|
|
90
103
|
return;
|
|
91
104
|
}
|
|
92
105
|
handleSubmenusRefChange(node, itemNode.dsId);
|
|
93
106
|
},
|
|
94
|
-
[
|
|
107
|
+
[
|
|
108
|
+
a11yDangerouslyConsumerOwnedMenuOpenState,
|
|
109
|
+
focusableNodes,
|
|
110
|
+
focusRegion,
|
|
111
|
+
handleSubmenusRefChange,
|
|
112
|
+
itemNode,
|
|
113
|
+
mainMenuRef,
|
|
114
|
+
openIntentPendingRef,
|
|
115
|
+
trackFocusFirstChildItem
|
|
116
|
+
]
|
|
95
117
|
);
|
|
96
118
|
const handleAnimationStartTrigger = import_react.default.useCallback(() => {
|
|
97
119
|
setDidAnimationEnd(false);
|
|
@@ -115,6 +137,7 @@ const DSFlyoutMenu = (props) => {
|
|
|
115
137
|
StyledMenu,
|
|
116
138
|
{
|
|
117
139
|
role: "menu",
|
|
140
|
+
onKeyDownCapture: (0, import_nodesTypeguardsAndGetters.isRootNode)(itemNode) ? handleMenuTabKeyDownCapture : void 0,
|
|
118
141
|
innerRef: handleRefChange,
|
|
119
142
|
$maxHeight: itemNode.plainItem.maxHeight ?? import_react_desc_prop_types.menuSpecificDefaultProps.maxHeight,
|
|
120
143
|
getOwnerProps,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../src/parts/DSFlyoutMenu/DSFlyoutMenu.tsx", "../../../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["import { FloatingWrapper } from '@elliemae/ds-floating-context';\nimport { Grid } from '@elliemae/ds-grid';\nimport { describe } from '@elliemae/ds-props-helpers';\nimport { styled, xStyledCommonProps } from '@elliemae/ds-system';\n\nimport React from 'react';\nimport { type DSMenuButtonT, menuSpecificDefaultProps } from '../../react-desc-prop-types.js';\nimport { MenuBehaviouralContextProviderContext } from '../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { DSMenuItemRendererFactory } from '../DSMenuItemRendererFactory/index.js';\nimport { useFlyoutMenu } from './config/useFlyoutMenu.js';\nimport { DSFlyoutMenuName, FLYOUT_MENU_SLOTS } from './constants/index.js';\nimport { DSFlyoutMenuPropTypesSchema, type DSFlyoutMenuT } from './react-desc-prop-types.js';\nimport { getAriaSetSizeAndPosInSetBySiblingList, isRootNode } from '../../utils/nodesTypeguardsAndGetters.js';\nimport { MenuButtonContextProvider } from '../../MenuButtonContextProviderCTX.js';\nconst StyledWrapper = styled(Grid, {\n name: DSFlyoutMenuName,\n slot: FLYOUT_MENU_SLOTS.ROOT,\n})`\n min-width: 1px;\n background-color: white;\n padding: ${({ theme }) => theme.space.xxxs} 0;\n ${xStyledCommonProps}\n`;\n\nconst StyledFloatingWrapper = styled(FloatingWrapper, {\n name: DSFlyoutMenuName,\n slot: FLYOUT_MENU_SLOTS.FLOATING_WRAPPER,\n})``;\n\nconst StyledMenu = styled('div', { name: DSFlyoutMenuName, slot: FLYOUT_MENU_SLOTS.LIST_WRAPPER })<{\n $maxHeight: string | number;\n}>`\n max-height: ${({ $maxHeight }) => (typeof $maxHeight === 'number' ? `${$maxHeight}px` : $maxHeight)};\n overflow-y: auto;\n padding: 0;\n margin: 0;\n`;\n\nconst NoComponentPlaceholder = () => null;\n\nconst DSFlyoutMenu: React.ComponentType<DSFlyoutMenuT.Props> = (props) => {\n const { propsWithDefault, xstyledProps } = useFlyoutMenu(props);\n const {\n ownerProps: { getOwnerProps, getOwnerPropsArguments },\n } = React.useContext(MenuButtonContextProvider);\n const {\n instanceUid,\n globalEventsHelpers: { mainMenuRef, handleSubmenusRefChange },\n } = React.useContext(MenuBehaviouralContextProviderContext);\n const [didAnimationEnd, setDidAnimationEnd] = React.useState(false);\n\n const { setFloatingRef, floatingStyles, floatingContext, ItemRenderer, isMenuOpen, itemNode } = propsWithDefault;\n const ariaPositionsByDsId = React.useMemo(\n // DSTree doesn't support children having a polymorphic type, so we need to cast it manually (same as below)\n () => getAriaSetSizeAndPosInSetBySiblingList(itemNode.children as DSMenuButtonT.MenuNode[]),\n [itemNode.children],\n );\n const handleRefChange = React.useCallback(\n (node: HTMLDivElement) => {\n if (isRootNode(itemNode)) {\n mainMenuRef.current = node;\n return;\n }\n handleSubmenusRefChange(node, itemNode.dsId);\n },\n [handleSubmenusRefChange
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;
|
|
4
|
+
"sourcesContent": ["import { FloatingWrapper } from '@elliemae/ds-floating-context';\nimport { Grid } from '@elliemae/ds-grid';\nimport { describe } from '@elliemae/ds-props-helpers';\nimport { styled, xStyledCommonProps } from '@elliemae/ds-system';\n\nimport React from 'react';\nimport { type DSMenuButtonT, menuSpecificDefaultProps } from '../../react-desc-prop-types.js';\nimport { MenuBehaviouralContextProviderContext } from '../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { DSMenuItemRendererFactory } from '../DSMenuItemRendererFactory/index.js';\nimport { useFlyoutMenu } from './config/useFlyoutMenu.js';\nimport { DSFlyoutMenuName, FLYOUT_MENU_SLOTS } from './constants/index.js';\nimport { DSFlyoutMenuPropTypesSchema, type DSFlyoutMenuT } from './react-desc-prop-types.js';\nimport { getAriaSetSizeAndPosInSetBySiblingList, isRootNode } from '../../utils/nodesTypeguardsAndGetters.js';\nimport { MenuButtonContextProvider } from '../../MenuButtonContextProviderCTX.js';\nconst StyledWrapper = styled(Grid, {\n name: DSFlyoutMenuName,\n slot: FLYOUT_MENU_SLOTS.ROOT,\n})`\n min-width: 1px;\n background-color: white;\n padding: ${({ theme }) => theme.space.xxxs} 0;\n ${xStyledCommonProps}\n`;\n\nconst StyledFloatingWrapper = styled(FloatingWrapper, {\n name: DSFlyoutMenuName,\n slot: FLYOUT_MENU_SLOTS.FLOATING_WRAPPER,\n})``;\n\nconst StyledMenu = styled('div', { name: DSFlyoutMenuName, slot: FLYOUT_MENU_SLOTS.LIST_WRAPPER })<{\n $maxHeight: string | number;\n}>`\n max-height: ${({ $maxHeight }) => (typeof $maxHeight === 'number' ? `${$maxHeight}px` : $maxHeight)};\n overflow-y: auto;\n padding: 0;\n margin: 0;\n`;\n\nconst NoComponentPlaceholder = () => null;\n\nconst DSFlyoutMenu: React.ComponentType<DSFlyoutMenuT.Props> = (props) => {\n const { propsWithDefault, xstyledProps } = useFlyoutMenu(props);\n const {\n ownerProps: { getOwnerProps, getOwnerPropsArguments },\n } = React.useContext(MenuButtonContextProvider);\n const {\n instanceUid,\n globalEventsHelpers: { mainMenuRef, handleSubmenusRefChange },\n openIntentPendingRef,\n menuItemEventsHandlers: { handleMenuTabKeyDownCapture },\n focusRegion,\n trackFocusFirstChildItem,\n propsWithDefault: { focusableNodes, a11yDangerouslyConsumerOwnedMenuOpenState },\n } = React.useContext(MenuBehaviouralContextProviderContext);\n const lastMountedRootNodeRef = React.useRef<HTMLDivElement | null>(null);\n const [didAnimationEnd, setDidAnimationEnd] = React.useState(false);\n\n const { setFloatingRef, floatingStyles, floatingContext, ItemRenderer, isMenuOpen, itemNode } = propsWithDefault;\n const ariaPositionsByDsId = React.useMemo(\n // DSTree doesn't support children having a polymorphic type, so we need to cast it manually (same as below)\n () => getAriaSetSizeAndPosInSetBySiblingList(itemNode.children as DSMenuButtonT.MenuNode[]),\n [itemNode.children],\n );\n const handleRefChange = React.useCallback(\n (node: HTMLDivElement) => {\n if (isRootNode(itemNode)) {\n mainMenuRef.current = node;\n // The menu just mounted (open). When the consumer owns the open state, a menu can be opened by something that\n // never went through the widget's own intents (an external button, an effect...), so nobody declared where the\n // focus goes. Declare the default intent, like every open does: \"the first item\" \u2014 the items focus\n // themselves from their own ref callbacks, as they do for every other focus intent.\n // - only on a real mount: the ref re-attaches the SAME node whenever the options change while open\n // - never when an open intent already armed the tracker, nor when focus is already on an item\n if (node && node !== lastMountedRootNodeRef.current) {\n lastMountedRootNodeRef.current = node;\n const wasIntentArmed = openIntentPendingRef.current;\n // eslint-disable-next-line no-param-reassign\n openIntentPendingRef.current = false;\n if (\n a11yDangerouslyConsumerOwnedMenuOpenState !== undefined &&\n !wasIntentArmed &&\n !focusRegion.startsWith('item-') &&\n focusableNodes.length > 0\n )\n trackFocusFirstChildItem(itemNode as unknown as DSMenuButtonT.PseudoFocusableMenuNodes);\n }\n return;\n }\n handleSubmenusRefChange(node, itemNode.dsId);\n },\n [\n a11yDangerouslyConsumerOwnedMenuOpenState,\n focusableNodes,\n focusRegion,\n handleSubmenusRefChange,\n itemNode,\n mainMenuRef,\n openIntentPendingRef,\n trackFocusFirstChildItem,\n ],\n );\n\n const handleAnimationStartTrigger = React.useCallback(() => {\n setDidAnimationEnd(false);\n }, []);\n const handleAnimationEnd = React.useCallback(() => {\n setDidAnimationEnd(true);\n }, []);\n\n return (\n <StyledFloatingWrapper\n innerRef={setFloatingRef}\n floatingStyles={floatingStyles}\n isOpen={isMenuOpen}\n context={floatingContext}\n onAnimationStartTriggered={handleAnimationStartTrigger}\n onAnimationEnd={handleAnimationEnd}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n // Adding a data-testid for legacy e2e tests\n data-testid=\"ds-floating-wrapper-root\"\n >\n <StyledWrapper getOwnerProps={getOwnerProps} getOwnerPropsArguments={getOwnerPropsArguments} {...xstyledProps}>\n <StyledMenu\n role=\"menu\"\n // Tab is handled in the capture phase of the ROOT menu only: nested submenus are React descendants of it (their\n // events reach it too), attaching it to them as well would run the handler twice per keypress\n onKeyDownCapture={isRootNode(itemNode) ? handleMenuTabKeyDownCapture : undefined}\n innerRef={handleRefChange}\n $maxHeight={itemNode.plainItem.maxHeight ?? menuSpecificDefaultProps.maxHeight}\n getOwnerProps={getOwnerProps}\n getOwnerPropsArguments={getOwnerPropsArguments}\n >\n {itemNode.children.map((optionNode) => {\n const ariaPosition = ariaPositionsByDsId.get(optionNode.dsId);\n return (\n <DSMenuItemRendererFactory\n key={`flyout-menu-item-${optionNode.dsId}-${instanceUid}`}\n // DSTree doesn't support children having a polymorphic type, so we need to cast it manually\n itemNode={optionNode as DSMenuButtonT.MenuNode}\n ItemRenderer={ItemRenderer}\n // we are passing the DSFlyoutMenu component as a prop to the DSMenuItemRendererFactory\n // this solves the circular-ish dependency\n // the conditional `didAnimationEnd` solves the visual artefact of nested FloatingWrapper wrongly positioned due to animation\n FlyoutMenuCircularDepInject={didAnimationEnd ? DSFlyoutMenu : NoComponentPlaceholder}\n aria-posinset={ariaPosition?.ariaPosInSet}\n aria-setsize={ariaPosition?.ariaSetSize}\n />\n );\n })}\n </StyledMenu>\n </StyledWrapper>\n </StyledFloatingWrapper>\n );\n};\n\nDSFlyoutMenu.displayName = DSFlyoutMenuName;\nconst DSFlyoutMenuWithSchema = describe(DSFlyoutMenu);\nDSFlyoutMenuWithSchema.propTypes = DSFlyoutMenuPropTypesSchema;\n\nexport { DSFlyoutMenu, DSFlyoutMenuWithSchema };\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["import_react_desc_prop_types", "React"]
|
|
7
7
|
}
|
|
@@ -40,6 +40,7 @@ var import_constants = require("../constants/index.js");
|
|
|
40
40
|
var import_nodeGettersByCriterias = require("../utils/nodeGettersByCriterias.js");
|
|
41
41
|
const useFocusTracker = () => {
|
|
42
42
|
const shouldFocusImmediately = import_react.default.useRef(false);
|
|
43
|
+
const shouldRefocusTrigger = import_react.default.useRef(true);
|
|
43
44
|
const [focusRegion, setFocusRegion] = import_react.default.useState("");
|
|
44
45
|
const focusedRegionPerformanceHelper = import_react.default.useRef("");
|
|
45
46
|
const preventBlurReset = import_react.default.useRef(false);
|
|
@@ -53,6 +54,7 @@ const useFocusTracker = () => {
|
|
|
53
54
|
focusedRegionPerformanceHelper.current = newFocusRegion;
|
|
54
55
|
focusedElementItemNode.current = focusNode;
|
|
55
56
|
shouldFocusImmediately.current = flags?.shouldFocusImmediately ?? false;
|
|
57
|
+
shouldRefocusTrigger.current = flags?.refocusTrigger ?? true;
|
|
56
58
|
return focusNode;
|
|
57
59
|
},
|
|
58
60
|
50,
|
|
@@ -61,7 +63,7 @@ const useFocusTracker = () => {
|
|
|
61
63
|
[]
|
|
62
64
|
);
|
|
63
65
|
const trackFocusTrigger = import_react.default.useCallback(
|
|
64
|
-
(options = {
|
|
66
|
+
(options = {}) => racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.TRIGGER, null, options),
|
|
65
67
|
[racingConditionDebounceTrackFocus]
|
|
66
68
|
);
|
|
67
69
|
const trackFocusNode = import_react.default.useCallback(
|
|
@@ -168,6 +170,7 @@ const useFocusTracker = () => {
|
|
|
168
170
|
focusRegion,
|
|
169
171
|
focusedElementItemNode,
|
|
170
172
|
shouldFocusImmediately,
|
|
173
|
+
shouldRefocusTrigger,
|
|
171
174
|
trackFocusTrigger,
|
|
172
175
|
trackFocusNode,
|
|
173
176
|
trackFocusRegionReset,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.ts", "../../../../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["/* eslint-disable no-console */\n/* eslint-disable max-lines */\n/* eslint-disable max-statements */\n/* eslint-disable complexity */\n/* eslint-disable prettier/prettier */\nimport { throttle } from 'lodash-es';\nimport React from 'react';\nimport type { DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isGroup,\n isMenuNodeAllowedToHaveChildren,\n isRootNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { TREE_STRUCTURE_ERRORS } from '../constants/Errors.js';\nimport { MENU_FOCUS_REGIONS } from '../constants/index.js';\nimport { getFocusableSiblingsList } from '../utils/nodeGettersByCriterias.js';\n\ntype MenuFocusRegionsValues = (typeof MENU_FOCUS_REGIONS)[keyof typeof MENU_FOCUS_REGIONS];\n// if MenuFocusRegionsValues may be a string or a function that returns a string, we want only the resolved strings\ntype ValidRegionsValues<T extends MenuFocusRegionsValues = MenuFocusRegionsValues> = T extends string\n ? T\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends (...args: any) => any\n ? ReturnType<T>\n : never;\n\nexport const useFocusTracker = () => {\n const shouldFocusImmediately = React.useRef(false);\n const [focusRegion, setFocusRegion] = React.useState<ValidRegionsValues>('');\n // we want to keep the focus region trackers as stable as possible to avoid unnecessary re-renders\n // there is no need to change the trackers reference when the focus region changes,\n // since changing the focus region is always triggered by a final user interaction (so after reacts reconciliation)\n const focusedRegionPerformanceHelper = React.useRef('') as React.MutableRefObject<ValidRegionsValues>;\n const preventBlurReset = React.useRef(false);\n\n const focusedElementItemNode = React.useRef(\n null,\n ) as React.MutableRefObject<DSMenuButtonT.PseudoFocusableMenuNodes | null>;\n\n // typescript with debounce doesn't work well, so we need to disable the exhaustive deps rule here\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const racingConditionDebounceTrackFocus = React.useCallback(\n throttle(\n (\n newFocusRegion: ValidRegionsValues,\n focusNode: DSMenuButtonT.PseudoFocusableMenuNodes | null,\n flags?: { shouldFocusImmediately?: boolean },\n ) => {\n setFocusRegion(newFocusRegion);\n focusedRegionPerformanceHelper.current = newFocusRegion;\n focusedElementItemNode.current = focusNode;\n\n shouldFocusImmediately.current = flags?.shouldFocusImmediately ?? false;\n\n return focusNode;\n },\n 50,\n { leading: true, trailing: true },\n ),\n [],\n );\n\n const trackFocusTrigger = React.useCallback(\n (options
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADKvB,uBAAyB;AACzB,mBAAkB;AAElB,uCAKO;AACP,oBAAsC;AACtC,uBAAmC;AACnC,oCAAyC;AAWlC,MAAM,kBAAkB,MAAM;AACnC,QAAM,yBAAyB,aAAAA,QAAM,OAAO,KAAK;
|
|
4
|
+
"sourcesContent": ["/* eslint-disable no-console */\n/* eslint-disable max-lines */\n/* eslint-disable max-statements */\n/* eslint-disable complexity */\n/* eslint-disable prettier/prettier */\nimport { throttle } from 'lodash-es';\nimport React from 'react';\nimport type { DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isGroup,\n isMenuNodeAllowedToHaveChildren,\n isRootNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { TREE_STRUCTURE_ERRORS } from '../constants/Errors.js';\nimport { MENU_FOCUS_REGIONS } from '../constants/index.js';\nimport { getFocusableSiblingsList } from '../utils/nodeGettersByCriterias.js';\n\ntype MenuFocusRegionsValues = (typeof MENU_FOCUS_REGIONS)[keyof typeof MENU_FOCUS_REGIONS];\n// if MenuFocusRegionsValues may be a string or a function that returns a string, we want only the resolved strings\ntype ValidRegionsValues<T extends MenuFocusRegionsValues = MenuFocusRegionsValues> = T extends string\n ? T\n : // eslint-disable-next-line @typescript-eslint/no-explicit-any\n T extends (...args: any) => any\n ? ReturnType<T>\n : never;\n\nexport const useFocusTracker = () => {\n const shouldFocusImmediately = React.useRef(false);\n /*\n * Separates \"recording that focus is on the trigger\" from \"requesting focus return\n * to the trigger\": the trigger's own focus event tracks the region passively, while\n * menu-driven intents (close, keyboard nav) actively refocus. Without the split, the\n * deferred refocus can land after focus already legitimately moved on (e.g. the user\n * Tabbed away) and steal it back.\n */\n const shouldRefocusTrigger = React.useRef(true);\n const [focusRegion, setFocusRegion] = React.useState<ValidRegionsValues>('');\n // we want to keep the focus region trackers as stable as possible to avoid unnecessary re-renders\n // there is no need to change the trackers reference when the focus region changes,\n // since changing the focus region is always triggered by a final user interaction (so after reacts reconciliation)\n const focusedRegionPerformanceHelper = React.useRef('') as React.MutableRefObject<ValidRegionsValues>;\n const preventBlurReset = React.useRef(false);\n\n const focusedElementItemNode = React.useRef(\n null,\n ) as React.MutableRefObject<DSMenuButtonT.PseudoFocusableMenuNodes | null>;\n\n // typescript with debounce doesn't work well, so we need to disable the exhaustive deps rule here\n // eslint-disable-next-line react-hooks/exhaustive-deps\n const racingConditionDebounceTrackFocus = React.useCallback(\n throttle(\n (\n newFocusRegion: ValidRegionsValues,\n focusNode: DSMenuButtonT.PseudoFocusableMenuNodes | null,\n flags?: { shouldFocusImmediately?: boolean; refocusTrigger?: boolean },\n ) => {\n setFocusRegion(newFocusRegion);\n focusedRegionPerformanceHelper.current = newFocusRegion;\n focusedElementItemNode.current = focusNode;\n\n shouldFocusImmediately.current = flags?.shouldFocusImmediately ?? false;\n shouldRefocusTrigger.current = flags?.refocusTrigger ?? true;\n\n return focusNode;\n },\n 50,\n { leading: true, trailing: true },\n ),\n [],\n );\n\n const trackFocusTrigger = React.useCallback(\n (options: { shouldFocusImmediately?: boolean; refocusTrigger?: boolean } = {}) =>\n racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.TRIGGER, null, options),\n [racingConditionDebounceTrackFocus],\n );\n\n const trackFocusNode = React.useCallback(\n (nodeToFocus: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n const newFocusRegion = MENU_FOCUS_REGIONS.ITEM_BY_DSID(nodeToFocus.dsId);\n racingConditionDebounceTrackFocus(newFocusRegion, nodeToFocus);\n },\n [racingConditionDebounceTrackFocus],\n );\n\n const trackFocusRegionReset = React.useCallback(\n () => racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.RESET, null),\n [racingConditionDebounceTrackFocus],\n );\n\n const trackFocusFirstChildItem = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n if (!isMenuNodeAllowedToHaveChildren(itemNode)) {\n console.log('focus first child item > itemNode', itemNode);\n throw TREE_STRUCTURE_ERRORS.NODE_CANNOT_HAVE_CHILDREN;\n }\n if (itemNode.children.length === 0) {\n console.log(itemNode);\n throw new Error('No children found in the item node');\n }\n const focusableChildrenNodes = getFocusableSiblingsList(itemNode.children[0]);\n if (focusableChildrenNodes.length === 0) {\n console.log('focus first child item > itemNode', itemNode);\n throw new Error('No focusable nodes found in the children of the item node');\n }\n\n const newFocusedNode = focusableChildrenNodes[0];\n return racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.ITEM_BY_DSID(newFocusedNode.dsId), newFocusedNode);\n },\n [racingConditionDebounceTrackFocus],\n );\n\n const trackFocusLastChildItem = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n if (!isMenuNodeAllowedToHaveChildren(itemNode)) {\n console.log('focus last child item > itemNode', itemNode);\n throw new Error('Item node is not allowed to have children');\n }\n if (itemNode.children.length === 0) {\n console.log(itemNode);\n throw new Error('No children found in the item node');\n }\n const focusableChildrenNodes = getFocusableSiblingsList(itemNode.children[0]);\n if (focusableChildrenNodes.length === 0) {\n console.log('focus last child item > itemNode', itemNode);\n throw new Error('No focusable nodes found in the children of the item node');\n }\n return racingConditionDebounceTrackFocus(\n MENU_FOCUS_REGIONS.ITEM_BY_DSID(focusableChildrenNodes[focusableChildrenNodes.length - 1].dsId),\n focusableChildrenNodes[focusableChildrenNodes.length - 1],\n );\n },\n [racingConditionDebounceTrackFocus],\n );\n\n const trackFocusNextItem = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n const focusableSiblingsNodes = getFocusableSiblingsList(itemNode);\n // we find the current item node index in the focusableSiblingsNodes array\n const currIndex = focusableSiblingsNodes.findIndex((node) => node.dsId === itemNode.dsId);\n\n const nextIndex = currIndex + 1 < focusableSiblingsNodes.length ? currIndex + 1 : 0;\n const newFocusedNode = focusableSiblingsNodes[nextIndex];\n return racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.ITEM_BY_DSID(newFocusedNode.dsId), newFocusedNode);\n },\n [racingConditionDebounceTrackFocus],\n );\n\n const trackFocusPreviousItem = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n const focusableSiblingsNodes = getFocusableSiblingsList(itemNode);\n // we find the current item node index in the focusableSiblingsNodes array\n const currIndex = focusableSiblingsNodes.findIndex((node) => node.dsId === itemNode.dsId);\n\n const previousIndex = currIndex - 1 >= 0 ? currIndex - 1 : focusableSiblingsNodes.length - 1;\n const newFocusNode = focusableSiblingsNodes[previousIndex];\n return racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.ITEM_BY_DSID(newFocusNode.dsId), newFocusNode);\n },\n [racingConditionDebounceTrackFocus],\n );\n\n const trackFocusParentItem = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n const { parent } = itemNode;\n // this typecast is required because we are reading the parent property from the itemNode\n // while this function can receive any PseudoFocusableMenuNodes,\n // the parent property may be a non-pseudo focusable node (specifically when the parent property is group node)\n const parentNode = parent;\n if (!parentNode) {\n console.log('focus parent item', { itemNode, parentNode });\n throw new Error(`No parent node found for the item node`);\n }\n\n let nodeToFocus: DSMenuButtonT.PseudoFocusableMenuNodes | null = parentNode;\n // if parent is SingleSelectGroupNode then we can't focus it\n if (isGroup(parentNode)) {\n const groupParent = parentNode.parent;\n if (!isFocusableNode(groupParent) && !isRootNode(groupParent)) {\n console.log('focus parent item', { itemNode, parentNode, groupParent });\n throw new Error('No focusable parent node found for the item node');\n }\n nodeToFocus = groupParent;\n }\n\n const focusableNode = isRootNode(nodeToFocus) ? null : nodeToFocus;\n const newFocusNode = focusableNode;\n if (!newFocusNode) trackFocusTrigger();\n else trackFocusNode(newFocusNode);\n\n return focusableNode;\n },\n [trackFocusNode, trackFocusTrigger],\n );\n\n return React.useMemo(\n () => ({\n preventBlurReset,\n focusRegion,\n focusedElementItemNode,\n shouldFocusImmediately,\n shouldRefocusTrigger,\n trackFocusTrigger,\n trackFocusNode,\n trackFocusRegionReset,\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n trackFocusNextItem,\n trackFocusPreviousItem,\n trackFocusParentItem,\n }),\n [\n focusRegion,\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n trackFocusNextItem,\n trackFocusNode,\n trackFocusParentItem,\n trackFocusPreviousItem,\n trackFocusRegionReset,\n trackFocusTrigger,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": ["React"]
|
|
7
7
|
}
|
|
@@ -54,19 +54,20 @@ const useGlobalEvents = ({
|
|
|
54
54
|
);
|
|
55
55
|
import_react.default.useEffect(() => {
|
|
56
56
|
const closeMenuAndSubmenus = (e) => {
|
|
57
|
+
if (!isMenuOpen) return;
|
|
57
58
|
handleChangeOpenedSubItems([], { itemNode: null, event: e });
|
|
58
|
-
onOpinionatedClose({ skipFocus: true });
|
|
59
|
+
onOpinionatedClose({ skipFocus: true, reason: "window-blur", event: e });
|
|
59
60
|
};
|
|
60
61
|
const detectIfClickIsOutsideMenuAndCloseIfSo = (e) => {
|
|
61
62
|
if (!isMenuOpen) return;
|
|
62
|
-
if (buttonDOMNodeRef.current
|
|
63
|
+
if (buttonDOMNodeRef.current?.contains(e.target)) return;
|
|
63
64
|
const isClickInsideMainMenu = mainMenuRef.current?.contains(e.target);
|
|
64
65
|
if (isClickInsideMainMenu) return;
|
|
65
66
|
const submenuNodesToCheck = Object.values(submenusRefs.current).filter(Boolean);
|
|
66
67
|
const isClickInsideAnySubmenu = submenuNodesToCheck.some((node) => node.contains(e.target));
|
|
67
68
|
if (isClickInsideAnySubmenu) return;
|
|
68
69
|
handleChangeOpenedSubItems([], { itemNode: null, event: e });
|
|
69
|
-
onOpinionatedClose({ skipFocus: true });
|
|
70
|
+
onOpinionatedClose({ skipFocus: true, reason: "outside-click", event: e });
|
|
70
71
|
};
|
|
71
72
|
window.addEventListener("blur", closeMenuAndSubmenus);
|
|
72
73
|
document.addEventListener("mousedown", detectIfClickIsOutsideMenuAndCloseIfSo);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useGlobalEvents.ts", "../../../../../../../../scripts/build/transpile/react-shim.js"],
|
|
4
|
-
"sourcesContent": ["import React from 'react';\nimport type { DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\nimport type { DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\n\ntype UseGlobalEventsConfig = {\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n};\n\nexport const useGlobalEvents = ({\n menuOpenStatus,\n handleChangeOpenedSubItems,\n propsWithDefault,\n}: UseGlobalEventsConfig) => {\n const { onOpinionatedClose, isMenuOpen } = menuOpenStatus;\n const { buttonDOMNodeRef } = propsWithDefault;\n const mainMenuRef = React.useRef<HTMLDivElement | null>(null);\n // we are using \"portal\" but then we check the native evenet to check if \"on click outside\" the menu\n // for such reason, we need to track the current HTML NODES of the submenus (topmost parent)\n // this will allow us to check if the click event is outside the menu or any of it's submenus\n const submenusRefs = React.useRef<Record<DSMenuButtonT.WithSubmenuMenuNodes['dsId'], HTMLElement>>({});\n const handleSubmenusRefChange = React.useCallback(\n (htmlNode: HTMLElement | null, dsId: DSMenuButtonT.WithSubmenuMenuNodes['dsId']) => {\n if (htmlNode) {\n submenusRefs.current[dsId] = htmlNode;\n } else {\n delete submenusRefs.current[dsId];\n }\n },\n [],\n );\n React.useEffect(() => {\n // when the window loses focus, close all the menus and submenus\n const closeMenuAndSubmenus = (e: FocusEvent) => {\n handleChangeOpenedSubItems([], { itemNode: null, event: e });\n onOpinionatedClose({ skipFocus: true });\n };\n // when the user clicks \"outside\" the menu and submenus, close all the menus and submenus\n const detectIfClickIsOutsideMenuAndCloseIfSo = (e: MouseEvent | TouchEvent) => {\n // we are running this on literally ALL the clicks that happen on the window\n // we don't want to be consuming resources that are not strictly necessary\n // if the main menu is not open, we don't need to check if the click is outside the menu ofc\n if (!isMenuOpen) return;\n // we discard the fastest to check \"trigger\" click first\n if (buttonDOMNodeRef.current
|
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAAkB;AAWX,MAAM,kBAAkB,CAAC;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,MAA6B;AAC3B,QAAM,EAAE,oBAAoB,WAAW,IAAI;AAC3C,QAAM,EAAE,iBAAiB,IAAI;AAC7B,QAAM,cAAc,aAAAA,QAAM,OAA8B,IAAI;AAI5D,QAAM,eAAe,aAAAA,QAAM,OAAwE,CAAC,CAAC;AACrG,QAAM,0BAA0B,aAAAA,QAAM;AAAA,IACpC,CAAC,UAA8B,SAAqD;AAClF,UAAI,UAAU;AACZ,qBAAa,QAAQ,IAAI,IAAI;AAAA,MAC/B,OAAO;AACL,eAAO,aAAa,QAAQ,IAAI;AAAA,MAClC;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AACA,eAAAA,QAAM,UAAU,MAAM;AAEpB,UAAM,uBAAuB,CAAC,MAAkB;
|
|
4
|
+
"sourcesContent": ["import React from 'react';\nimport type { DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\nimport type { DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\n\ntype UseGlobalEventsConfig = {\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n};\n\nexport const useGlobalEvents = ({\n menuOpenStatus,\n handleChangeOpenedSubItems,\n propsWithDefault,\n}: UseGlobalEventsConfig) => {\n const { onOpinionatedClose, isMenuOpen } = menuOpenStatus;\n const { buttonDOMNodeRef } = propsWithDefault;\n const mainMenuRef = React.useRef<HTMLDivElement | null>(null);\n // we are using \"portal\" but then we check the native evenet to check if \"on click outside\" the menu\n // for such reason, we need to track the current HTML NODES of the submenus (topmost parent)\n // this will allow us to check if the click event is outside the menu or any of it's submenus\n const submenusRefs = React.useRef<Record<DSMenuButtonT.WithSubmenuMenuNodes['dsId'], HTMLElement>>({});\n const handleSubmenusRefChange = React.useCallback(\n (htmlNode: HTMLElement | null, dsId: DSMenuButtonT.WithSubmenuMenuNodes['dsId']) => {\n if (htmlNode) {\n submenusRefs.current[dsId] = htmlNode;\n } else {\n delete submenusRefs.current[dsId];\n }\n },\n [],\n );\n React.useEffect(() => {\n // when the window loses focus, close all the menus and submenus\n const closeMenuAndSubmenus = (e: FocusEvent) => {\n // same guard as the click-outside handler: every instance listens to window blur,\n // a closed menu has nothing to close (and must not report `onDisplayedSubmenuChange`/`onClose`)\n if (!isMenuOpen) return;\n handleChangeOpenedSubItems([], { itemNode: null, event: e });\n onOpinionatedClose({ skipFocus: true, reason: 'window-blur', event: e });\n };\n // when the user clicks \"outside\" the menu and submenus, close all the menus and submenus\n const detectIfClickIsOutsideMenuAndCloseIfSo = (e: MouseEvent | TouchEvent) => {\n // we are running this on literally ALL the clicks that happen on the window\n // we don't want to be consuming resources that are not strictly necessary\n // if the main menu is not open, we don't need to check if the click is outside the menu ofc\n if (!isMenuOpen) return;\n // we discard the fastest to check \"trigger\" click first \u2014 the trigger CONTAINS its content (icons, text...),\n // a pointer going down on any of it is a trigger interaction (the click that follows toggles the menu),\n // not an outside one: closing here would make the click re-open the menu we just closed\n if (buttonDOMNodeRef.current?.contains(e.target as Node)) return;\n // fastest check is the mainmenu, cuz we don't loop through all the submenus\n const isClickInsideMainMenu = mainMenuRef.current?.contains(e.target as Node);\n if (isClickInsideMainMenu) return;\n const submenuNodesToCheck = Object.values(submenusRefs.current).filter(Boolean);\n // we check if the event.target is a child of any of our known HTML nodes (via native .contains)\n const isClickInsideAnySubmenu = submenuNodesToCheck.some((node) => node.contains(e.target as Node));\n if (isClickInsideAnySubmenu) return;\n handleChangeOpenedSubItems([], { itemNode: null, event: e });\n onOpinionatedClose({ skipFocus: true, reason: 'outside-click', event: e });\n };\n\n window.addEventListener('blur', closeMenuAndSubmenus);\n document.addEventListener('mousedown', detectIfClickIsOutsideMenuAndCloseIfSo);\n document.addEventListener('touchstart', detectIfClickIsOutsideMenuAndCloseIfSo);\n return () => {\n window.removeEventListener('blur', closeMenuAndSubmenus);\n document.removeEventListener('mousedown', detectIfClickIsOutsideMenuAndCloseIfSo);\n document.removeEventListener('touchstart', detectIfClickIsOutsideMenuAndCloseIfSo);\n };\n }, [buttonDOMNodeRef, handleChangeOpenedSubItems, isMenuOpen, onOpinionatedClose]);\n\n return React.useMemo(\n () => ({\n mainMenuRef,\n submenusRefs,\n handleSubmenusRefChange,\n }),\n [handleSubmenusRefChange],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAAkB;AAWX,MAAM,kBAAkB,CAAC;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AACF,MAA6B;AAC3B,QAAM,EAAE,oBAAoB,WAAW,IAAI;AAC3C,QAAM,EAAE,iBAAiB,IAAI;AAC7B,QAAM,cAAc,aAAAA,QAAM,OAA8B,IAAI;AAI5D,QAAM,eAAe,aAAAA,QAAM,OAAwE,CAAC,CAAC;AACrG,QAAM,0BAA0B,aAAAA,QAAM;AAAA,IACpC,CAAC,UAA8B,SAAqD;AAClF,UAAI,UAAU;AACZ,qBAAa,QAAQ,IAAI,IAAI;AAAA,MAC/B,OAAO;AACL,eAAO,aAAa,QAAQ,IAAI;AAAA,MAClC;AAAA,IACF;AAAA,IACA,CAAC;AAAA,EACH;AACA,eAAAA,QAAM,UAAU,MAAM;AAEpB,UAAM,uBAAuB,CAAC,MAAkB;AAG9C,UAAI,CAAC,WAAY;AACjB,iCAA2B,CAAC,GAAG,EAAE,UAAU,MAAM,OAAO,EAAE,CAAC;AAC3D,yBAAmB,EAAE,WAAW,MAAM,QAAQ,eAAe,OAAO,EAAE,CAAC;AAAA,IACzE;AAEA,UAAM,yCAAyC,CAAC,MAA+B;AAI7E,UAAI,CAAC,WAAY;AAIjB,UAAI,iBAAiB,SAAS,SAAS,EAAE,MAAc,EAAG;AAE1D,YAAM,wBAAwB,YAAY,SAAS,SAAS,EAAE,MAAc;AAC5E,UAAI,sBAAuB;AAC3B,YAAM,sBAAsB,OAAO,OAAO,aAAa,OAAO,EAAE,OAAO,OAAO;AAE9E,YAAM,0BAA0B,oBAAoB,KAAK,CAAC,SAAS,KAAK,SAAS,EAAE,MAAc,CAAC;AAClG,UAAI,wBAAyB;AAC7B,iCAA2B,CAAC,GAAG,EAAE,UAAU,MAAM,OAAO,EAAE,CAAC;AAC3D,yBAAmB,EAAE,WAAW,MAAM,QAAQ,iBAAiB,OAAO,EAAE,CAAC;AAAA,IAC3E;AAEA,WAAO,iBAAiB,QAAQ,oBAAoB;AACpD,aAAS,iBAAiB,aAAa,sCAAsC;AAC7E,aAAS,iBAAiB,cAAc,sCAAsC;AAC9E,WAAO,MAAM;AACX,aAAO,oBAAoB,QAAQ,oBAAoB;AACvD,eAAS,oBAAoB,aAAa,sCAAsC;AAChF,eAAS,oBAAoB,cAAc,sCAAsC;AAAA,IACnF;AAAA,EACF,GAAG,CAAC,kBAAkB,4BAA4B,YAAY,kBAAkB,CAAC;AAEjF,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA,CAAC,uBAAuB;AAAA,EAC1B;AACF;",
|
|
6
6
|
"names": ["React"]
|
|
7
7
|
}
|
|
@@ -38,13 +38,17 @@ var import_Errors = require("../constants/Errors.js");
|
|
|
38
38
|
var import_multipleSelectionHelpers = require("../utils/multipleSelectionHelpers.js");
|
|
39
39
|
var import_nodeGettersByCriterias = require("../utils/nodeGettersByCriterias.js");
|
|
40
40
|
var import_singleSelectionHelpers = require("../utils/singleSelectionHelpers.js");
|
|
41
|
+
const invokeItemOnClickOnKeyboardActivation = (pseudoFocusedItemNode, event) => {
|
|
42
|
+
if (event?.type === "keydown" && pseudoFocusedItemNode.plainItem.onClick)
|
|
43
|
+
pseudoFocusedItemNode.plainItem.onClick(event);
|
|
44
|
+
};
|
|
41
45
|
const useMenuItemEventsHandlers = ({
|
|
42
46
|
propsWithDefault,
|
|
43
47
|
focusTrackers,
|
|
44
48
|
menuOpenStatus,
|
|
45
49
|
handleChangeOpenedSubItems
|
|
46
50
|
}) => {
|
|
47
|
-
const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;
|
|
51
|
+
const { onItemSelected, onActivateItem, selectedNodes, buttonDOMNodeRef } = propsWithDefault;
|
|
48
52
|
const { onOpinionatedClose } = menuOpenStatus;
|
|
49
53
|
const {
|
|
50
54
|
trackFocusFirstChildItem,
|
|
@@ -54,7 +58,8 @@ const useMenuItemEventsHandlers = ({
|
|
|
54
58
|
trackFocusParentItem,
|
|
55
59
|
trackFocusNode,
|
|
56
60
|
focusedElementItemNode,
|
|
57
|
-
trackFocusTrigger
|
|
61
|
+
trackFocusTrigger,
|
|
62
|
+
trackFocusRegionReset
|
|
58
63
|
} = focusTrackers;
|
|
59
64
|
const openSubmenu = import_react.default.useCallback(
|
|
60
65
|
({
|
|
@@ -116,10 +121,11 @@ const useMenuItemEventsHandlers = ({
|
|
|
116
121
|
} else {
|
|
117
122
|
onActivateItem(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
|
118
123
|
}
|
|
124
|
+
invokeItemOnClickOnKeyboardActivation(pseudoFocusedItemNode, event);
|
|
119
125
|
const metainfo = { itemNode: pseudoFocusedItemNode, event };
|
|
120
126
|
const newOpenedItems = [];
|
|
121
127
|
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
|
122
|
-
onOpinionatedClose();
|
|
128
|
+
onOpinionatedClose({ reason: "item-activation", event });
|
|
123
129
|
}
|
|
124
130
|
},
|
|
125
131
|
[
|
|
@@ -142,18 +148,21 @@ const useMenuItemEventsHandlers = ({
|
|
|
142
148
|
if ((0, import_nodesTypeguardsAndGetters.isMultipleSelectNode)(pseudoFocusedItemNode)) {
|
|
143
149
|
const newSelection = (0, import_multipleSelectionHelpers.getNewSelectionMultipleSelect)(selectedNodes, pseudoFocusedItemNode);
|
|
144
150
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
|
151
|
+
invokeItemOnClickOnKeyboardActivation(pseudoFocusedItemNode, event);
|
|
145
152
|
return;
|
|
146
153
|
}
|
|
147
154
|
if ((0, import_nodesTypeguardsAndGetters.isSingleSelectNode)(pseudoFocusedItemNode)) {
|
|
148
155
|
const newSelection = (0, import_singleSelectionHelpers.getNewSelectionSingleSelect)(selectedNodes, pseudoFocusedItemNode);
|
|
149
156
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
|
157
|
+
invokeItemOnClickOnKeyboardActivation(pseudoFocusedItemNode, event);
|
|
150
158
|
return;
|
|
151
159
|
}
|
|
152
160
|
onActivateItem?.(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
|
161
|
+
invokeItemOnClickOnKeyboardActivation(pseudoFocusedItemNode, event);
|
|
153
162
|
const metainfo = { itemNode: pseudoFocusedItemNode, event };
|
|
154
163
|
const newOpenedItems = [];
|
|
155
164
|
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
|
156
|
-
onOpinionatedClose();
|
|
165
|
+
onOpinionatedClose({ reason: "item-activation", event });
|
|
157
166
|
},
|
|
158
167
|
[
|
|
159
168
|
onActivateItem,
|
|
@@ -185,16 +194,21 @@ const useMenuItemEventsHandlers = ({
|
|
|
185
194
|
},
|
|
186
195
|
[closeCurrentSubmenu, trackFocusParentItem]
|
|
187
196
|
);
|
|
197
|
+
const handleMenuTabKeyDownCapture = import_react.default.useCallback(
|
|
198
|
+
(event) => {
|
|
199
|
+
if (event.key !== "Tab") return;
|
|
200
|
+
buttonDOMNodeRef.current?.focus();
|
|
201
|
+
trackFocusRegionReset();
|
|
202
|
+
onOpinionatedClose({ skipFocus: true, reason: "tab", event });
|
|
203
|
+
},
|
|
204
|
+
[buttonDOMNodeRef, onOpinionatedClose, trackFocusRegionReset]
|
|
205
|
+
);
|
|
188
206
|
const handleFocusableMenuItemKeyDown = import_react.default.useCallback(
|
|
189
207
|
(event) => {
|
|
208
|
+
if (event.key === "Tab") return;
|
|
190
209
|
const pseudoFocusedItemNode = focusedElementItemNode.current;
|
|
191
210
|
if (pseudoFocusedItemNode === null) throw import_Errors.UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;
|
|
192
211
|
if (pseudoFocusedItemNode.plainItem.onKeyDown) pseudoFocusedItemNode.plainItem.onKeyDown(event);
|
|
193
|
-
if (event.key === "Tab") {
|
|
194
|
-
trackFocusTrigger({ shouldFocusImmediately: true });
|
|
195
|
-
onOpinionatedClose();
|
|
196
|
-
return;
|
|
197
|
-
}
|
|
198
212
|
if (event.key === "ArrowDown") {
|
|
199
213
|
event?.preventDefault();
|
|
200
214
|
trackFocusNextItem(pseudoFocusedItemNode);
|
|
@@ -227,6 +241,7 @@ const useMenuItemEventsHandlers = ({
|
|
|
227
241
|
return;
|
|
228
242
|
}
|
|
229
243
|
if (event.key === "Escape") {
|
|
244
|
+
event.stopPropagation();
|
|
230
245
|
if (event.altKey && (0, import_nodesTypeguardsAndGetters.isWithSubmenuNode)(pseudoFocusedItemNode)) {
|
|
231
246
|
closeSubmenusAfterNode({ itemNode: pseudoFocusedItemNode, event });
|
|
232
247
|
return;
|
|
@@ -234,7 +249,7 @@ const useMenuItemEventsHandlers = ({
|
|
|
234
249
|
const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });
|
|
235
250
|
if (currentSubmenuWasMainMenu) {
|
|
236
251
|
trackFocusTrigger({ shouldFocusImmediately: true });
|
|
237
|
-
onOpinionatedClose({ skipFocus: true });
|
|
252
|
+
onOpinionatedClose({ skipFocus: true, reason: "escape", event });
|
|
238
253
|
} else {
|
|
239
254
|
trackFocusParentItem(pseudoFocusedItemNode);
|
|
240
255
|
}
|
|
@@ -338,12 +353,14 @@ const useMenuItemEventsHandlers = ({
|
|
|
338
353
|
);
|
|
339
354
|
return import_react.default.useMemo(
|
|
340
355
|
() => ({
|
|
356
|
+
handleMenuTabKeyDownCapture,
|
|
341
357
|
handleFocusableMenuItemKeyDown,
|
|
342
358
|
handleFocusableMenuItemClick,
|
|
343
359
|
handleFocusableMenuItemOnMouseEnter,
|
|
344
360
|
handleFocusableMenuItemNativeFocusEvent
|
|
345
361
|
}),
|
|
346
362
|
[
|
|
363
|
+
handleMenuTabKeyDownCapture,
|
|
347
364
|
handleFocusableMenuItemClick,
|
|
348
365
|
handleFocusableMenuItemKeyDown,
|
|
349
366
|
handleFocusableMenuItemNativeFocusEvent,
|