@elliemae/ds-menu-button 3.57.0-next.26 → 3.57.0-next.27

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.
@@ -39,6 +39,7 @@ var import_Errors = require("../constants/Errors.js");
39
39
  var import_constants = require("../constants/index.js");
40
40
  var import_nodeGettersByCriterias = require("../utils/nodeGettersByCriterias.js");
41
41
  const useFocusTracker = () => {
42
+ const shouldFocusImmediately = import_react.default.useRef(false);
42
43
  const [focusRegion, setFocusRegion] = import_react.default.useState("");
43
44
  const focusedRegionPerformanceHelper = import_react.default.useRef("");
44
45
  const preventBlurReset = import_react.default.useRef(false);
@@ -47,10 +48,11 @@ const useFocusTracker = () => {
47
48
  );
48
49
  const racingConditionDebounceTrackFocus = import_react.default.useCallback(
49
50
  (0, import_lodash_es.throttle)(
50
- (newFocusRegion, focusNode) => {
51
+ (newFocusRegion, focusNode, flags) => {
51
52
  setFocusRegion(newFocusRegion);
52
53
  focusedRegionPerformanceHelper.current = newFocusRegion;
53
54
  focusedElementItemNode.current = focusNode;
55
+ shouldFocusImmediately.current = flags?.shouldFocusImmediately ?? false;
54
56
  return focusNode;
55
57
  },
56
58
  50,
@@ -59,7 +61,7 @@ const useFocusTracker = () => {
59
61
  []
60
62
  );
61
63
  const trackFocusTrigger = import_react.default.useCallback(
62
- () => racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.TRIGGER, null),
64
+ (options = { shouldFocusImmediately: false }) => racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.TRIGGER, null, options),
63
65
  [racingConditionDebounceTrackFocus]
64
66
  );
65
67
  const trackFocusNode = import_react.default.useCallback(
@@ -165,6 +167,7 @@ const useFocusTracker = () => {
165
167
  preventBlurReset,
166
168
  focusRegion,
167
169
  focusedElementItemNode,
170
+ shouldFocusImmediately,
168
171
  trackFocusTrigger,
169
172
  trackFocusNode,
170
173
  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 [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 (newFocusRegion: ValidRegionsValues, focusNode: DSMenuButtonT.PseudoFocusableMenuNodes | null) => {\n setFocusRegion(newFocusRegion);\n focusedRegionPerformanceHelper.current = newFocusRegion;\n focusedElementItemNode.current = focusNode;\n\n return focusNode;\n },\n 50,\n { leading: true, trailing: true },\n ),\n [],\n );\n\n const trackFocusTrigger = React.useCallback(\n () => racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.TRIGGER, null),\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 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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",
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 = { shouldFocusImmediately: false }) =>\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 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
  }
@@ -53,7 +53,8 @@ const useMenuItemEventsHandlers = ({
53
53
  trackFocusPreviousItem,
54
54
  trackFocusParentItem,
55
55
  trackFocusNode,
56
- focusedElementItemNode
56
+ focusedElementItemNode,
57
+ trackFocusTrigger
57
58
  } = focusTrackers;
58
59
  const openSubmenu = import_react.default.useCallback(
59
60
  ({
@@ -189,6 +190,11 @@ const useMenuItemEventsHandlers = ({
189
190
  const pseudoFocusedItemNode = focusedElementItemNode.current;
190
191
  if (pseudoFocusedItemNode === null) throw import_Errors.UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;
191
192
  if (pseudoFocusedItemNode.plainItem.onKeyDown) pseudoFocusedItemNode.plainItem.onKeyDown(event);
193
+ if (event.key === "Tab") {
194
+ trackFocusTrigger({ shouldFocusImmediately: true });
195
+ onOpinionatedClose();
196
+ return;
197
+ }
192
198
  if (event.key === "ArrowDown") {
193
199
  event?.preventDefault();
194
200
  trackFocusNextItem(pseudoFocusedItemNode);
@@ -250,6 +256,7 @@ const useMenuItemEventsHandlers = ({
250
256
  },
251
257
  [
252
258
  focusedElementItemNode,
259
+ trackFocusTrigger,
253
260
  trackFocusNextItem,
254
261
  trackFocusPreviousItem,
255
262
  handleMenuItemLeftArrowKeyDown,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.ts", "../../../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["/* eslint-disable no-console */\n/* eslint-disable max-lines */\n// in this files the returns are not useless, they are used to clearly demark the intended behavior shortcircuit for the next developer that reads the code\n/* eslint-disable no-useless-return */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\nimport React from 'react';\nimport { type DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isMultipleSelectNode,\n isRootNode,\n isSingleSelectNode,\n isWithSubmenuNode,\n isWithSubmenuOnlyNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { FORBIDDEN_BEHAVIOURS, UNEXPECTED_INTERNAL_ERRORS } from '../constants/Errors.js';\nimport { type DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\nimport { getNewSelectionMultipleSelect } from '../utils/multipleSelectionHelpers.js';\nimport { getSubMenusPath } from '../utils/nodeGettersByCriterias.js';\nimport { getNewSelectionSingleSelect } from '../utils/singleSelectionHelpers.js';\nimport type { useFocusTracker } from './useFocusTracker.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\n\ntype ItemKeydownHelpersArgs = {\n pseudoFocusedItemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n};\ntype SubmenuChangeMetainfo = {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.SyntheticEvent;\n};\n\ntype UseMenuItemEventsHandlersConfig = {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n focusTrackers: ReturnType<typeof useFocusTracker>;\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n};\n\nexport const useMenuItemEventsHandlers = ({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n}: UseMenuItemEventsHandlersConfig) => {\n const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;\n const { onOpinionatedClose } = menuOpenStatus;\n const {\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n trackFocusNextItem,\n trackFocusPreviousItem,\n trackFocusParentItem,\n trackFocusNode,\n focusedElementItemNode,\n } = focusTrackers;\n\n const openSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.WithSubmenuMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n if (!isWithSubmenuNode(itemNode)) {\n console.log('openSubmenu -> itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_OPEN_SUBMENU_OF_NODE_WITHOUT_SUBMENU;\n }\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath, itemNode];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeSubmenusAfterNode = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeCurrentSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n }) => {\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const subMenuNodesPath = getSubMenusPath(itemNode);\n // if length is 0, it means the parent is the root node, so we close all the submenus\n if (subMenuNodesPath.length === 0) {\n handleChangeOpenedSubItems([], metainfo);\n return { currentSubmenuWasMainMenu: true };\n }\n\n // we want to close current submenu, so we pop the last element\n // getSubMenusPath guarantees the order by walkParents + reverse at the end\n subMenuNodesPath.pop();\n handleChangeOpenedSubItems(subMenuNodesPath, metainfo);\n return { currentSubmenuWasMainMenu: false };\n },\n [handleChangeOpenedSubItems],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * Additional roles, states, and properties needed for the menu element are described in the Menu and Menubar Pattern.\n * https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n * Because menubar and menu elements are composite widgets as described in the practice for Keyboard Navigation Inside Components,\n * Tab and Shift + Tab do not move focus among the items in the menu.\n * Instead, the keyboard commands described in this section enable users to move focus among the elements in a menubar or menu.\n * Tab and Shift + Tab:\n * - Move focus into a menubar <- Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n * - When focus is on a menuitem in a menu or menubar, move focus out of the menu or menubar, and close all menus and submenus.\n * ************************************************************************************************************************************\n * this will be implemented by not setting document.activeElement to the menuitem but keeping it on the trigger button\n * why? because there is no javascript API to trigger a \"tab\"/\"shift+tab\" navigation and any algorithm trying to do so is fragile\n * also, this is enforced by w3.org too as per:\n * ************************************************************************************************************************************\n * Note that Tab and Shift + Tab do not move focus into a menu.\n * Unlike a menubar, a menu is not visually persistent,\n * and authors are responsible for ensuring focus moves to an item inside of a menu when the menu opens.\n *\n */\n const handleMenuItemEnterKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Enter:\n // - When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ...\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n } else {\n // - Otherwise, activates the item...\n\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n\n // \"activation\" is a concept,\n // WCAG here implies that for menuitemscheckbox and menuitemsradio this triggers the selection\n // for menuitems (that doesn't fit selection nor submenus) it triggers the action\n // this also triggers enter -> single-select-item-with-submenu as expected\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n onActivateItem!(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n }\n // ... and closes the menu.\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n }\n },\n [\n trackFocusFirstChildItem,\n openSubmenu,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n selectedNodes,\n onItemSelected,\n onActivateItem,\n ],\n );\n const handleMenuItemSpaceKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Space:\n // [X] (Optional): When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // [X] (Optional): When focus is on a menuitemcheckbox, changes the state without closing the menu.\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitemradio that is not checked, without closing the menu,\n // checks the focused menuitemradio and unchecks any other checked menuitemradio element in the same group.\n if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitem that does not have a submenu, activates the menuitem and closes the menu.\n onActivateItem?.(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n },\n [\n onActivateItem,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n trackFocusFirstChildItem,\n openSubmenu,\n selectedNodes,\n onItemSelected,\n ],\n );\n const handleMenuItemRightArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Right Arrow:\n // [N/A] When focus is in a menubar, moves focus to the next item, optionally wrapping from the last to the first.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a menu and on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // [N/A] When focus is in a menu and on an item that does not have a submenu, performs the following 3 actions:\n // [N/A] Closes the submenu and any parent menus.\n // [N/A] Moves focus to the next item in the menubar.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] If focus is now on a menuitem with a submenu, either:\n // [X] (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // /\\ we choose the recommended option\n // OR\n // [N/A] opens the submenu of that menuitem and places focus on the first item in the submenu.\n // ***********************************************************\n // Dimsum implements IoC, if APP side for whatever reason wants to open the new sub sub menu,\n // it's up to them to listen to the pseudo focus event and open the sub sub menu\n // ***********************************************************\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // Note that if the menubar were not present, e.g., the menus were opened from a menubutton,\n // Right Arrow would not do anything when focus is on an item that does not have a submenu.\n return;\n },\n [openSubmenu, trackFocusFirstChildItem],\n );\n\n const handleMenuItemLeftArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Left Arrow:\n // [N/A] When focus is in a menubar, moves focus to the previous item, optionally wrapping from the first to the last.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a submenu of an item in a menu, closes the submenu and returns focus to the parent menuitem.\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n if (!currentSubmenuWasMainMenu) {\n trackFocusParentItem(pseudoFocusedItemNode);\n }\n\n // [N/A] When focus is in a submenu of an item in a menubar, performs the following 3 actions:\n // [N/A] Closes the submenu.\n // [N/A] Moves focus to the previous item in the menubar.\n // [N/A] If focus is now on a menuitem with a submenu, either:\n // (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // or\n // opens the submenu of that menuitem and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n },\n [closeCurrentSubmenu, trackFocusParentItem],\n );\n\n const handleFocusableMenuItemKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLDivElement>>(\n (event) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n // if there is no focused item, we can't handle the logics\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item contains an `onKeyDown` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onKeyDown) pseudoFocusedItemNode.plainItem.onKeyDown(event);\n\n // Down Arrow:\n // [N/A] When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n // [X] When focus is in a menu, moves focus to the next item,\n // [X] optionally wrapping from the last to the first.\n if (event.key === 'ArrowDown') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusNextItem(pseudoFocusedItemNode);\n return;\n }\n // Up Arrow:\n // [X] When focus is in a menu, moves focus to the previous item,\n // [X] optionally wrapping from the first to the last.\n // [N/A] (Optional): When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the last item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n if (event.key === 'ArrowUp') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusPreviousItem(pseudoFocusedItemNode);\n return;\n }\n // Left Arrow:\n if (event.key === 'ArrowLeft') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemLeftArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Home: If arrow key wrapping is not supported, moves focus to the first item in the current menu or menubar.\n if (event.key === 'Home') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusFirstChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n // End: If arrow key wrapping is not supported, moves focus to the last item in the current menu or menubar.\n if (event.key === 'End') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusLastChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n if (event.key === 'Escape') {\n // if the item has no submenu, the alt key is transparent, we behave as if it's not even pressed\n if (event.altKey && isWithSubmenuNode(pseudoFocusedItemNode)) {\n // PUI-14639 - Implement submenu dismissal mechanism\n // Description: we need to add a mechanism to close submenus that automatically expand when a menu item is focused with the keyboard.\n // we have decided to implement a special shortcut (Alt/Option + Esc) as the dismissal mechanism.\n // This change is necessary to comply with WCAG success criterion 1.4.13.\n // https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus.html\n // if the item has a submenu, we close it\n closeSubmenusAfterNode({ itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // Escape: Close the menu that contains focus and return focus to the element or context, e.g., menu button or parent menuitem, from which the menu was opened\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusParentItem(pseudoFocusedItemNode);\n // if the menu we are trying to close is the main menu, we adittionally close the main menu itself\n if (currentSubmenuWasMainMenu) {\n // if parent is the root node, we close also the main menu\n onOpinionatedClose();\n }\n return;\n }\n\n /* ************************************************************************************\n * ******* ACTIVATION & SUBMENU keys doesn't apply if the item is \"disabled\" **********\n * ************************************************************************************ */\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // Enter:\n if (event.key === 'Enter') {\n handleMenuItemEnterKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Space:\n if (event.key === ' ') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Right Arrow:\n if (event.key === 'ArrowRight') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemRightArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n },\n [\n focusedElementItemNode,\n trackFocusNextItem,\n trackFocusPreviousItem,\n handleMenuItemLeftArrowKeyDown,\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n closeCurrentSubmenu,\n trackFocusParentItem,\n closeSubmenusAfterNode,\n onOpinionatedClose,\n handleMenuItemEnterKeyDown,\n handleMenuItemSpaceKeyDown,\n handleMenuItemRightArrowKeyDown,\n ],\n );\n const handleFocusableMenuItemClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item is disabled, we don't do anything\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // we need to handle which submenu needs to close automagically based on the click\n // starting from the itemNode, we check:\n // - the parent of the itemNode is root?\n // - yes -> we close all the submenus\n const parentMenuNode = pseudoFocusedItemNode.parent;\n if (isRootNode(parentMenuNode)) {\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n } else {\n // - no -> we close all the submenus that are not in the path to the root of the itemNode\n // this is necessary on click because the user can still interact with other (visual) depths of the menu\n // during keyboard navigation, the user is limited to the current (visual) depth\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...getSubMenusPath(pseudoFocusedItemNode)];\n // - if the clicked node has a submenu, keep it open\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n newOpenedItems.push(pseudoFocusedItemNode);\n }\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n }\n\n // to all effects and purposes, the click event is the same as the space key\n // the main difference between enter and space is that\n // - enter ALWAYS closes the menu excepts for submenus\n // - space closes the menu if \"activable\", selections doesn't, submenus doesn't\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n // if the item contains an `onClick` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onClick) pseudoFocusedItemNode.plainItem.onClick(event);\n },\n [focusedElementItemNode, handleMenuItemSpaceKeyDown, handleChangeOpenedSubItems],\n );\n\n // the \"menu-button\" widget is an opinionated widget with a specific behaviour to follow when a focusable item is \"focused\"\n // this function achieve the opinionated \"focus an item\" opinionated behaviour (regardless of why the item is focused)\n const opinionatedFocusBehaviour = React.useCallback(\n // we currently know only one \"event\" that can trigger this opinionated behaviour\n // - focus => React.FocusEvent <-- triggered by ATs \"focus mode\" or \"virtual cursor\" navigation\n // NOTE:\n // mouseEnter => React.MouseEvent\n // still ends up calling this function, but as a side effect of mouse-enter focusing the item, not as the main event\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n if (!isWithSubmenuNode(itemNode)) {\n // if the item has nosubmenu we close all the siblings submenus\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n return;\n }\n // if the item is disabled, we don't open the submenu, and there is nothing else to do\n if (itemNode.plainItem.disabled) return;\n // if not disabled and it has a submenu, we open it, and there is nothing else to do\n openSubmenu({ itemNode, event });\n },\n [handleChangeOpenedSubItems, openSubmenu, trackFocusNode],\n );\n\n // when an item is hovered, we focus it\n // this effectively achieves:\n // - the item is focused (it trigger the handleFocusableMenuItemNativeFocusEvent function because render runs node.focus())\n // - if the user presses a keyboard key, the interaction will work starting from the (hovered hence focused) item\n const handleFocusableMenuItemOnMouseEnter = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n },\n [trackFocusNode],\n );\n\n const handleFocusableMenuItemNativeFocusEvent = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n opinionatedFocusBehaviour(itemNode, event);\n },\n [opinionatedFocusBehaviour],\n );\n\n return React.useMemo(\n () => ({\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemClick,\n handleFocusableMenuItemOnMouseEnter,\n handleFocusableMenuItemNativeFocusEvent,\n }),\n [\n handleFocusableMenuItemClick,\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemNativeFocusEvent,\n handleFocusableMenuItemOnMouseEnter,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["/* eslint-disable no-console */\n/* eslint-disable max-lines */\n// in this files the returns are not useless, they are used to clearly demark the intended behavior shortcircuit for the next developer that reads the code\n/* eslint-disable no-useless-return */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\nimport React from 'react';\nimport { type DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isMultipleSelectNode,\n isRootNode,\n isSingleSelectNode,\n isWithSubmenuNode,\n isWithSubmenuOnlyNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { FORBIDDEN_BEHAVIOURS, UNEXPECTED_INTERNAL_ERRORS } from '../constants/Errors.js';\nimport { type DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\nimport { getNewSelectionMultipleSelect } from '../utils/multipleSelectionHelpers.js';\nimport { getSubMenusPath } from '../utils/nodeGettersByCriterias.js';\nimport { getNewSelectionSingleSelect } from '../utils/singleSelectionHelpers.js';\nimport type { useFocusTracker } from './useFocusTracker.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\n\ntype ItemKeydownHelpersArgs = {\n pseudoFocusedItemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n};\ntype SubmenuChangeMetainfo = {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.SyntheticEvent;\n};\n\ntype UseMenuItemEventsHandlersConfig = {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n focusTrackers: ReturnType<typeof useFocusTracker>;\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n};\n\nexport const useMenuItemEventsHandlers = ({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n}: UseMenuItemEventsHandlersConfig) => {\n const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;\n const { onOpinionatedClose } = menuOpenStatus;\n const {\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n trackFocusNextItem,\n trackFocusPreviousItem,\n trackFocusParentItem,\n trackFocusNode,\n focusedElementItemNode,\n trackFocusTrigger,\n } = focusTrackers;\n\n const openSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.WithSubmenuMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n if (!isWithSubmenuNode(itemNode)) {\n console.log('openSubmenu -> itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_OPEN_SUBMENU_OF_NODE_WITHOUT_SUBMENU;\n }\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath, itemNode];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeSubmenusAfterNode = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeCurrentSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n }) => {\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const subMenuNodesPath = getSubMenusPath(itemNode);\n // if length is 0, it means the parent is the root node, so we close all the submenus\n if (subMenuNodesPath.length === 0) {\n handleChangeOpenedSubItems([], metainfo);\n return { currentSubmenuWasMainMenu: true };\n }\n\n // we want to close current submenu, so we pop the last element\n // getSubMenusPath guarantees the order by walkParents + reverse at the end\n subMenuNodesPath.pop();\n handleChangeOpenedSubItems(subMenuNodesPath, metainfo);\n return { currentSubmenuWasMainMenu: false };\n },\n [handleChangeOpenedSubItems],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * Additional roles, states, and properties needed for the menu element are described in the Menu and Menubar Pattern.\n * https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n * Because menubar and menu elements are composite widgets as described in the practice for Keyboard Navigation Inside Components,\n * Tab and Shift + Tab do not move focus among the items in the menu.\n * Instead, the keyboard commands described in this section enable users to move focus among the elements in a menubar or menu.\n * Tab and Shift + Tab:\n * - Move focus into a menubar <- Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n * - When focus is on a menuitem in a menu or menubar, move focus out of the menu or menubar, and close all menus and submenus.\n * ************************************************************************************************************************************\n * this will be implemented by not setting document.activeElement to the menuitem but keeping it on the trigger button\n * why? because there is no javascript API to trigger a \"tab\"/\"shift+tab\" navigation and any algorithm trying to do so is fragile\n * also, this is enforced by w3.org too as per:\n * ************************************************************************************************************************************\n * Note that Tab and Shift + Tab do not move focus into a menu.\n * Unlike a menubar, a menu is not visually persistent,\n * and authors are responsible for ensuring focus moves to an item inside of a menu when the menu opens.\n *\n */\n const handleMenuItemEnterKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Enter:\n // - When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ...\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n } else {\n // - Otherwise, activates the item...\n\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n\n // \"activation\" is a concept,\n // WCAG here implies that for menuitemscheckbox and menuitemsradio this triggers the selection\n // for menuitems (that doesn't fit selection nor submenus) it triggers the action\n // this also triggers enter -> single-select-item-with-submenu as expected\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n onActivateItem!(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n }\n // ... and closes the menu.\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n }\n },\n [\n trackFocusFirstChildItem,\n openSubmenu,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n selectedNodes,\n onItemSelected,\n onActivateItem,\n ],\n );\n const handleMenuItemSpaceKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Space:\n // [X] (Optional): When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // [X] (Optional): When focus is on a menuitemcheckbox, changes the state without closing the menu.\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitemradio that is not checked, without closing the menu,\n // checks the focused menuitemradio and unchecks any other checked menuitemradio element in the same group.\n if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitem that does not have a submenu, activates the menuitem and closes the menu.\n onActivateItem?.(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n },\n [\n onActivateItem,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n trackFocusFirstChildItem,\n openSubmenu,\n selectedNodes,\n onItemSelected,\n ],\n );\n const handleMenuItemRightArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Right Arrow:\n // [N/A] When focus is in a menubar, moves focus to the next item, optionally wrapping from the last to the first.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a menu and on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // [N/A] When focus is in a menu and on an item that does not have a submenu, performs the following 3 actions:\n // [N/A] Closes the submenu and any parent menus.\n // [N/A] Moves focus to the next item in the menubar.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] If focus is now on a menuitem with a submenu, either:\n // [X] (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // /\\ we choose the recommended option\n // OR\n // [N/A] opens the submenu of that menuitem and places focus on the first item in the submenu.\n // ***********************************************************\n // Dimsum implements IoC, if APP side for whatever reason wants to open the new sub sub menu,\n // it's up to them to listen to the pseudo focus event and open the sub sub menu\n // ***********************************************************\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // Note that if the menubar were not present, e.g., the menus were opened from a menubutton,\n // Right Arrow would not do anything when focus is on an item that does not have a submenu.\n return;\n },\n [openSubmenu, trackFocusFirstChildItem],\n );\n\n const handleMenuItemLeftArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Left Arrow:\n // [N/A] When focus is in a menubar, moves focus to the previous item, optionally wrapping from the first to the last.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a submenu of an item in a menu, closes the submenu and returns focus to the parent menuitem.\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n if (!currentSubmenuWasMainMenu) {\n trackFocusParentItem(pseudoFocusedItemNode);\n }\n\n // [N/A] When focus is in a submenu of an item in a menubar, performs the following 3 actions:\n // [N/A] Closes the submenu.\n // [N/A] Moves focus to the previous item in the menubar.\n // [N/A] If focus is now on a menuitem with a submenu, either:\n // (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // or\n // opens the submenu of that menuitem and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n },\n [closeCurrentSubmenu, trackFocusParentItem],\n );\n\n const handleFocusableMenuItemKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLDivElement>>(\n (event) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n // if there is no focused item, we can't handle the logics\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item contains an `onKeyDown` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onKeyDown) pseudoFocusedItemNode.plainItem.onKeyDown(event);\n // TAB:\n // When focus is on a menuitem in a menu or menubar, move focus to the trigger so tab flows occurs from there.\n // Then close all menus and submenus.\n if (event.key === 'Tab') {\n trackFocusTrigger({ shouldFocusImmediately: true });\n onOpinionatedClose();\n return;\n }\n // Down Arrow:\n // [N/A] When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n // [X] When focus is in a menu, moves focus to the next item,\n // [X] optionally wrapping from the last to the first.\n if (event.key === 'ArrowDown') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusNextItem(pseudoFocusedItemNode);\n return;\n }\n // Up Arrow:\n // [X] When focus is in a menu, moves focus to the previous item,\n // [X] optionally wrapping from the first to the last.\n // [N/A] (Optional): When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the last item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n if (event.key === 'ArrowUp') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusPreviousItem(pseudoFocusedItemNode);\n return;\n }\n // Left Arrow:\n if (event.key === 'ArrowLeft') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemLeftArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Home: If arrow key wrapping is not supported, moves focus to the first item in the current menu or menubar.\n if (event.key === 'Home') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusFirstChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n // End: If arrow key wrapping is not supported, moves focus to the last item in the current menu or menubar.\n if (event.key === 'End') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusLastChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n if (event.key === 'Escape') {\n // if the item has no submenu, the alt key is transparent, we behave as if it's not even pressed\n if (event.altKey && isWithSubmenuNode(pseudoFocusedItemNode)) {\n // PUI-14639 - Implement submenu dismissal mechanism\n // Description: we need to add a mechanism to close submenus that automatically expand when a menu item is focused with the keyboard.\n // we have decided to implement a special shortcut (Alt/Option + Esc) as the dismissal mechanism.\n // This change is necessary to comply with WCAG success criterion 1.4.13.\n // https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus.html\n // if the item has a submenu, we close it\n closeSubmenusAfterNode({ itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // Escape: Close the menu that contains focus and return focus to the element or context, e.g., menu button or parent menuitem, from which the menu was opened\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusParentItem(pseudoFocusedItemNode);\n // if the menu we are trying to close is the main menu, we adittionally close the main menu itself\n if (currentSubmenuWasMainMenu) {\n // if parent is the root node, we close also the main menu\n onOpinionatedClose();\n }\n return;\n }\n\n /* ************************************************************************************\n * ******* ACTIVATION & SUBMENU keys doesn't apply if the item is \"disabled\" **********\n * ************************************************************************************ */\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // Enter:\n if (event.key === 'Enter') {\n handleMenuItemEnterKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Space:\n if (event.key === ' ') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Right Arrow:\n if (event.key === 'ArrowRight') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemRightArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n },\n [\n focusedElementItemNode,\n trackFocusTrigger,\n trackFocusNextItem,\n trackFocusPreviousItem,\n handleMenuItemLeftArrowKeyDown,\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n closeCurrentSubmenu,\n trackFocusParentItem,\n closeSubmenusAfterNode,\n onOpinionatedClose,\n handleMenuItemEnterKeyDown,\n handleMenuItemSpaceKeyDown,\n handleMenuItemRightArrowKeyDown,\n ],\n );\n const handleFocusableMenuItemClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item is disabled, we don't do anything\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // we need to handle which submenu needs to close automagically based on the click\n // starting from the itemNode, we check:\n // - the parent of the itemNode is root?\n // - yes -> we close all the submenus\n const parentMenuNode = pseudoFocusedItemNode.parent;\n if (isRootNode(parentMenuNode)) {\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n } else {\n // - no -> we close all the submenus that are not in the path to the root of the itemNode\n // this is necessary on click because the user can still interact with other (visual) depths of the menu\n // during keyboard navigation, the user is limited to the current (visual) depth\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...getSubMenusPath(pseudoFocusedItemNode)];\n // - if the clicked node has a submenu, keep it open\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n newOpenedItems.push(pseudoFocusedItemNode);\n }\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n }\n\n // to all effects and purposes, the click event is the same as the space key\n // the main difference between enter and space is that\n // - enter ALWAYS closes the menu excepts for submenus\n // - space closes the menu if \"activable\", selections doesn't, submenus doesn't\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n // if the item contains an `onClick` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onClick) pseudoFocusedItemNode.plainItem.onClick(event);\n },\n [focusedElementItemNode, handleMenuItemSpaceKeyDown, handleChangeOpenedSubItems],\n );\n\n // the \"menu-button\" widget is an opinionated widget with a specific behaviour to follow when a focusable item is \"focused\"\n // this function achieve the opinionated \"focus an item\" opinionated behaviour (regardless of why the item is focused)\n const opinionatedFocusBehaviour = React.useCallback(\n // we currently know only one \"event\" that can trigger this opinionated behaviour\n // - focus => React.FocusEvent <-- triggered by ATs \"focus mode\" or \"virtual cursor\" navigation\n // NOTE:\n // mouseEnter => React.MouseEvent\n // still ends up calling this function, but as a side effect of mouse-enter focusing the item, not as the main event\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n if (!isWithSubmenuNode(itemNode)) {\n // if the item has nosubmenu we close all the siblings submenus\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n return;\n }\n // if the item is disabled, we don't open the submenu, and there is nothing else to do\n if (itemNode.plainItem.disabled) return;\n // if not disabled and it has a submenu, we open it, and there is nothing else to do\n openSubmenu({ itemNode, event });\n },\n [handleChangeOpenedSubItems, openSubmenu, trackFocusNode],\n );\n\n // when an item is hovered, we focus it\n // this effectively achieves:\n // - the item is focused (it trigger the handleFocusableMenuItemNativeFocusEvent function because render runs node.focus())\n // - if the user presses a keyboard key, the interaction will work starting from the (hovered hence focused) item\n const handleFocusableMenuItemOnMouseEnter = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n },\n [trackFocusNode],\n );\n\n const handleFocusableMenuItemNativeFocusEvent = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n opinionatedFocusBehaviour(itemNode, event);\n },\n [opinionatedFocusBehaviour],\n );\n\n return React.useMemo(\n () => ({\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemClick,\n handleFocusableMenuItemOnMouseEnter,\n handleFocusableMenuItemNativeFocusEvent,\n }),\n [\n handleFocusableMenuItemClick,\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemNativeFocusEvent,\n handleFocusableMenuItemOnMouseEnter,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": "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",
6
6
  "names": ["React"]
7
7
  }
@@ -37,9 +37,7 @@ var import_resolveRef = require("../../../utils/resolveRef.js");
37
37
  var import_MenuBehaviouralContextProviderCTX = require("../../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js");
38
38
  var import_constants = require("../../DSMenuBehaviouralContextProvider/constants/index.js");
39
39
  const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }) => {
40
- const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger } = import_react.default.useContext(
41
- import_MenuBehaviouralContextProviderCTX.MenuBehaviouralContextProviderContext
42
- );
40
+ const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger, shouldFocusImmediately } = import_react.default.useContext(import_MenuBehaviouralContextProviderCTX.MenuBehaviouralContextProviderContext);
43
41
  const { onClick, onFocus, innerRef, onKeyDown } = propsWithDefault;
44
42
  const {
45
43
  refs: { setReference }
@@ -70,14 +68,18 @@ const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }) => {
70
68
  const handleFocusIfTriggerIsFocusRegion = import_react.default.useCallback(
71
69
  (node) => {
72
70
  if (reRenderButtonToAllowFocus && node) {
73
- requestAnimationFrame(() => {
71
+ if (!shouldFocusImmediately.current) {
72
+ requestAnimationFrame(() => {
73
+ node.focus();
74
+ });
75
+ } else {
74
76
  node.focus();
75
- });
77
+ }
76
78
  }
77
79
  setReference(node);
78
80
  if (innerRef) (0, import_resolveRef.resolveRef)(innerRef, node);
79
81
  },
80
- [innerRef, reRenderButtonToAllowFocus, setReference]
82
+ [innerRef, reRenderButtonToAllowFocus, setReference, shouldFocusImmediately]
81
83
  );
82
84
  const handleTriggerKeyDown = import_react.default.useCallback(
83
85
  (event, ...args) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/parts/DSOpinionatedButton/config/useTriggerEventsHandlers.ts", "../../../../../../../../scripts/build/transpile/react-shim.js"],
4
- "sourcesContent": ["import type { DSButtonV3T } from '@elliemae/ds-button-v2';\nimport { type useFloatingContext } from '@elliemae/ds-floating-context';\nimport React from 'react';\nimport { resolveRef } from '../../../utils/resolveRef.js';\nimport { MenuBehaviouralContextProviderContext } from '../../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { MENU_FOCUS_REGIONS } from '../../DSMenuBehaviouralContextProvider/constants/index.js';\nimport { type DSOpinionatedButtonT } from '../react-desc-prop-types.js';\n\ntype UseTriggerEventsHandlersConfig = {\n propsWithDefault: DSOpinionatedButtonT.InternalProps;\n floatingContext: ReturnType<typeof useFloatingContext>;\n};\n\nexport const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }: UseTriggerEventsHandlersConfig) => {\n // =============================================================================\n // on click -> open the submenu\n // if the props already include an onClick, we will wrap it\n // =============================================================================\n const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger } = React.useContext(\n MenuBehaviouralContextProviderContext,\n );\n const { onClick, onFocus, innerRef, onKeyDown } = propsWithDefault;\n const {\n refs: { setReference },\n } = floatingContext;\n\n const focusedRegionPerformanceHelper = React.useRef(focusRegion);\n // when the focus region is on the trigger, we need to re-render the button so it invokes the ref callback again to focus itself\n // we don't need to rerender at every focus region change, only when it changes to the trigger itself.\n // by checking the ref stored value against the current focus region, the flag will stay mostly false and change to true only when needed\n const reRenderButtonToAllowFocus =\n focusRegion === MENU_FOCUS_REGIONS.TRIGGER && focusedRegionPerformanceHelper.current !== focusRegion;\n focusedRegionPerformanceHelper.current = focusRegion;\n\n const mutableIsMenuOpen = React.useRef(isMenuOpen);\n // this is a boolean, so a primitive value,\n // we don't need to useEffect to change it\n // because we don't need to ensure referencial equality\n mutableIsMenuOpen.current = isMenuOpen;\n\n const handleOnClick = React.useCallback<Required<DSButtonV3T.Props>['onClick']>(\n (e, ...args) => {\n if (onClick) onClick(e, ...args);\n // no reason to change this function reference every time the menu opens/closes\n // so we are using a mutable ref to optimize this\n if (mutableIsMenuOpen.current) {\n onOpinionatedClose();\n } else {\n onOpinionatedOpen();\n }\n },\n [onClick, onOpinionatedClose, onOpinionatedOpen],\n );\n const handleOnFocus = React.useCallback<Required<DSButtonV3T.Props>['onFocus']>(\n (e, ...args) => {\n if (focusedRegionPerformanceHelper.current !== MENU_FOCUS_REGIONS.TRIGGER) trackFocusTrigger();\n if (onFocus) onFocus(e, ...args);\n },\n [onFocus, trackFocusTrigger],\n );\n\n const handleFocusIfTriggerIsFocusRegion = React.useCallback<React.RefCallback<HTMLButtonElement>>(\n (node) => {\n if (reRenderButtonToAllowFocus && node) {\n // when user presses \"Enter\" different things happen depending on top of what element the focus is\n // if it is in the trigger (this function) then it spawns the submenu and focuses the first item\n // if it is in a menu item, it activates the item and closes the menu, triggering this function again\n // So, why do we need the requestAnimationFrame?\n // pressing enter in an item will focus the trigger\n // -> current react behaviour means the menu button \"keyDown\" event will also trigger with \"Enter\"\n // -> which will open the menu again\n // as of react 18 and 28/october/2025, this requestAnimationFrame prevents the keyDown event from triggering on top of the button\n requestAnimationFrame(() => {\n node.focus();\n });\n }\n setReference(node);\n if (innerRef) resolveRef(innerRef, node);\n },\n [innerRef, reRenderButtonToAllowFocus, setReference],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * With focus on the button:\n * Enter: opens the menu and places focus on the first menu item.\n * Space: Opens the menu and places focus on the first menu item.\n * (Optional) Down Arrow: opens the menu and moves focus to the first menu item.\n * (Optional) Up Arrow: opens the menu and moves focus to the last menu item.\n */\n const handleTriggerKeyDown = React.useCallback<Required<DSButtonV3T.Props>['onKeyDown']>(\n (event, ...args) => {\n // DSButtonV3 triggers the onClick on Enter and Space, so we don't need to handle it here\n // if (event.key === 'Enter' || event.key === ' ') {\n // onOpinionatedOpen();\n // }\n if (event.key === 'ArrowDown') {\n onOpinionatedOpen();\n }\n if (event.key === 'ArrowUp') {\n onOpinionatedOpen({ focusLastChild: true });\n }\n if (event.key === 'Escape') {\n onOpinionatedClose();\n }\n if (onKeyDown) onKeyDown(event, ...args);\n },\n [onKeyDown, onOpinionatedOpen, onOpinionatedClose],\n );\n\n return React.useMemo(\n () => ({ handleTriggerKeyDown, handleOnClick, handleOnFocus, handleFocusIfTriggerIsFocusRegion }),\n [handleFocusIfTriggerIsFocusRegion, handleOnClick, handleOnFocus, handleTriggerKeyDown],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
- "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADEvB,mBAAkB;AAClB,wBAA2B;AAC3B,+CAAsD;AACtD,uBAAmC;AAQ5B,MAAM,2BAA2B,CAAC,EAAE,kBAAkB,gBAAgB,MAAsC;AAKjH,QAAM,EAAE,aAAa,YAAY,mBAAmB,oBAAoB,kBAAkB,IAAI,aAAAA,QAAM;AAAA,IAClG;AAAA,EACF;AACA,QAAM,EAAE,SAAS,SAAS,UAAU,UAAU,IAAI;AAClD,QAAM;AAAA,IACJ,MAAM,EAAE,aAAa;AAAA,EACvB,IAAI;AAEJ,QAAM,iCAAiC,aAAAA,QAAM,OAAO,WAAW;AAI/D,QAAM,6BACJ,gBAAgB,oCAAmB,WAAW,+BAA+B,YAAY;AAC3F,iCAA+B,UAAU;AAEzC,QAAM,oBAAoB,aAAAA,QAAM,OAAO,UAAU;AAIjD,oBAAkB,UAAU;AAE5B,QAAM,gBAAgB,aAAAA,QAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAG/B,UAAI,kBAAkB,SAAS;AAC7B,2BAAmB;AAAA,MACrB,OAAO;AACL,0BAAkB;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC,SAAS,oBAAoB,iBAAiB;AAAA,EACjD;AACA,QAAM,gBAAgB,aAAAA,QAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,+BAA+B,YAAY,oCAAmB,QAAS,mBAAkB;AAC7F,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAAA,IACjC;AAAA,IACA,CAAC,SAAS,iBAAiB;AAAA,EAC7B;AAEA,QAAM,oCAAoC,aAAAA,QAAM;AAAA,IAC9C,CAAC,SAAS;AACR,UAAI,8BAA8B,MAAM;AAStC,8BAAsB,MAAM;AAC1B,eAAK,MAAM;AAAA,QACb,CAAC;AAAA,MACH;AACA,mBAAa,IAAI;AACjB,UAAI,SAAU,mCAAW,UAAU,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,UAAU,4BAA4B,YAAY;AAAA,EACrD;AAWA,QAAM,uBAAuB,aAAAA,QAAM;AAAA,IACjC,CAAC,UAAU,SAAS;AAKlB,UAAI,MAAM,QAAQ,aAAa;AAC7B,0BAAkB;AAAA,MACpB;AACA,UAAI,MAAM,QAAQ,WAAW;AAC3B,0BAAkB,EAAE,gBAAgB,KAAK,CAAC;AAAA,MAC5C;AACA,UAAI,MAAM,QAAQ,UAAU;AAC1B,2BAAmB;AAAA,MACrB;AACA,UAAI,UAAW,WAAU,OAAO,GAAG,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,WAAW,mBAAmB,kBAAkB;AAAA,EACnD;AAEA,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO,EAAE,sBAAsB,eAAe,eAAe,kCAAkC;AAAA,IAC/F,CAAC,mCAAmC,eAAe,eAAe,oBAAoB;AAAA,EACxF;AACF;",
4
+ "sourcesContent": ["import type { DSButtonV3T } from '@elliemae/ds-button-v2';\nimport { type useFloatingContext } from '@elliemae/ds-floating-context';\nimport React from 'react';\nimport { resolveRef } from '../../../utils/resolveRef.js';\nimport { MenuBehaviouralContextProviderContext } from '../../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { MENU_FOCUS_REGIONS } from '../../DSMenuBehaviouralContextProvider/constants/index.js';\nimport { type DSOpinionatedButtonT } from '../react-desc-prop-types.js';\n\ntype UseTriggerEventsHandlersConfig = {\n propsWithDefault: DSOpinionatedButtonT.InternalProps;\n floatingContext: ReturnType<typeof useFloatingContext>;\n};\n\nexport const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }: UseTriggerEventsHandlersConfig) => {\n // =============================================================================\n // on click -> open the submenu\n // if the props already include an onClick, we will wrap it\n // =============================================================================\n const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger, shouldFocusImmediately } =\n React.useContext(MenuBehaviouralContextProviderContext);\n const { onClick, onFocus, innerRef, onKeyDown } = propsWithDefault;\n const {\n refs: { setReference },\n } = floatingContext;\n\n const focusedRegionPerformanceHelper = React.useRef(focusRegion);\n // when the focus region is on the trigger, we need to re-render the button so it invokes the ref callback again to focus itself\n // we don't need to rerender at every focus region change, only when it changes to the trigger itself.\n // by checking the ref stored value against the current focus region, the flag will stay mostly false and change to true only when needed\n const reRenderButtonToAllowFocus =\n focusRegion === MENU_FOCUS_REGIONS.TRIGGER && focusedRegionPerformanceHelper.current !== focusRegion;\n focusedRegionPerformanceHelper.current = focusRegion;\n\n const mutableIsMenuOpen = React.useRef(isMenuOpen);\n // this is a boolean, so a primitive value,\n // we don't need to useEffect to change it\n // because we don't need to ensure referencial equality\n mutableIsMenuOpen.current = isMenuOpen;\n\n const handleOnClick = React.useCallback<Required<DSButtonV3T.Props>['onClick']>(\n (e, ...args) => {\n if (onClick) onClick(e, ...args);\n // no reason to change this function reference every time the menu opens/closes\n // so we are using a mutable ref to optimize this\n if (mutableIsMenuOpen.current) {\n onOpinionatedClose();\n } else {\n onOpinionatedOpen();\n }\n },\n [onClick, onOpinionatedClose, onOpinionatedOpen],\n );\n const handleOnFocus = React.useCallback<Required<DSButtonV3T.Props>['onFocus']>(\n (e, ...args) => {\n if (focusedRegionPerformanceHelper.current !== MENU_FOCUS_REGIONS.TRIGGER) trackFocusTrigger();\n if (onFocus) onFocus(e, ...args);\n },\n [onFocus, trackFocusTrigger],\n );\n\n const handleFocusIfTriggerIsFocusRegion = React.useCallback<React.RefCallback<HTMLButtonElement>>(\n (node) => {\n if (reRenderButtonToAllowFocus && node) {\n // when user presses \"Enter\" different things happen depending on top of what element the focus is\n // if it is in the trigger (this function) then it spawns the submenu and focuses the first item\n // if it is in a menu item, it activates the item and closes the menu, triggering this function again\n // So, why do we need the requestAnimationFrame?\n // pressing enter in an item will focus the trigger\n // -> current react behaviour means the menu button \"keyDown\" event will also trigger with \"Enter\"\n // -> which will open the menu again\n // as of react 18 and 28/october/2025, this requestAnimationFrame prevents the keyDown event from triggering on top of the button\n if (!shouldFocusImmediately.current) {\n requestAnimationFrame(() => {\n node.focus();\n });\n } else {\n // In some cases, like when user presses \"Tab\" within a menu, we want to focus the trigger immediately.\n // So that the tab flow occurs from the trigger to the next focusable element in the page.\n // Therefore, we don't need to wait for the next frame.\n node.focus();\n }\n }\n setReference(node);\n if (innerRef) resolveRef(innerRef, node);\n },\n [innerRef, reRenderButtonToAllowFocus, setReference, shouldFocusImmediately],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * With focus on the button:\n * Enter: opens the menu and places focus on the first menu item.\n * Space: Opens the menu and places focus on the first menu item.\n * (Optional) Down Arrow: opens the menu and moves focus to the first menu item.\n * (Optional) Up Arrow: opens the menu and moves focus to the last menu item.\n */\n const handleTriggerKeyDown = React.useCallback<Required<DSButtonV3T.Props>['onKeyDown']>(\n (event, ...args) => {\n // DSButtonV3 triggers the onClick on Enter and Space, so we don't need to handle it here\n // if (event.key === 'Enter' || event.key === ' ') {\n // onOpinionatedOpen();\n // }\n if (event.key === 'ArrowDown') {\n onOpinionatedOpen();\n }\n if (event.key === 'ArrowUp') {\n onOpinionatedOpen({ focusLastChild: true });\n }\n if (event.key === 'Escape') {\n onOpinionatedClose();\n }\n if (onKeyDown) onKeyDown(event, ...args);\n },\n [onKeyDown, onOpinionatedOpen, onOpinionatedClose],\n );\n\n return React.useMemo(\n () => ({ handleTriggerKeyDown, handleOnClick, handleOnFocus, handleFocusIfTriggerIsFocusRegion }),\n [handleFocusIfTriggerIsFocusRegion, handleOnClick, handleOnFocus, handleTriggerKeyDown],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
5
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADEvB,mBAAkB;AAClB,wBAA2B;AAC3B,+CAAsD;AACtD,uBAAmC;AAQ5B,MAAM,2BAA2B,CAAC,EAAE,kBAAkB,gBAAgB,MAAsC;AAKjH,QAAM,EAAE,aAAa,YAAY,mBAAmB,oBAAoB,mBAAmB,uBAAuB,IAChH,aAAAA,QAAM,WAAW,8EAAqC;AACxD,QAAM,EAAE,SAAS,SAAS,UAAU,UAAU,IAAI;AAClD,QAAM;AAAA,IACJ,MAAM,EAAE,aAAa;AAAA,EACvB,IAAI;AAEJ,QAAM,iCAAiC,aAAAA,QAAM,OAAO,WAAW;AAI/D,QAAM,6BACJ,gBAAgB,oCAAmB,WAAW,+BAA+B,YAAY;AAC3F,iCAA+B,UAAU;AAEzC,QAAM,oBAAoB,aAAAA,QAAM,OAAO,UAAU;AAIjD,oBAAkB,UAAU;AAE5B,QAAM,gBAAgB,aAAAA,QAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAG/B,UAAI,kBAAkB,SAAS;AAC7B,2BAAmB;AAAA,MACrB,OAAO;AACL,0BAAkB;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC,SAAS,oBAAoB,iBAAiB;AAAA,EACjD;AACA,QAAM,gBAAgB,aAAAA,QAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,+BAA+B,YAAY,oCAAmB,QAAS,mBAAkB;AAC7F,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAAA,IACjC;AAAA,IACA,CAAC,SAAS,iBAAiB;AAAA,EAC7B;AAEA,QAAM,oCAAoC,aAAAA,QAAM;AAAA,IAC9C,CAAC,SAAS;AACR,UAAI,8BAA8B,MAAM;AAStC,YAAI,CAAC,uBAAuB,SAAS;AACnC,gCAAsB,MAAM;AAC1B,iBAAK,MAAM;AAAA,UACb,CAAC;AAAA,QACH,OAAO;AAIL,eAAK,MAAM;AAAA,QACb;AAAA,MACF;AACA,mBAAa,IAAI;AACjB,UAAI,SAAU,mCAAW,UAAU,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,UAAU,4BAA4B,cAAc,sBAAsB;AAAA,EAC7E;AAWA,QAAM,uBAAuB,aAAAA,QAAM;AAAA,IACjC,CAAC,UAAU,SAAS;AAKlB,UAAI,MAAM,QAAQ,aAAa;AAC7B,0BAAkB;AAAA,MACpB;AACA,UAAI,MAAM,QAAQ,WAAW;AAC3B,0BAAkB,EAAE,gBAAgB,KAAK,CAAC;AAAA,MAC5C;AACA,UAAI,MAAM,QAAQ,UAAU;AAC1B,2BAAmB;AAAA,MACrB;AACA,UAAI,UAAW,WAAU,OAAO,GAAG,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,WAAW,mBAAmB,kBAAkB;AAAA,EACnD;AAEA,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO,EAAE,sBAAsB,eAAe,eAAe,kCAAkC;AAAA,IAC/F,CAAC,mCAAmC,eAAe,eAAe,oBAAoB;AAAA,EACxF;AACF;",
6
6
  "names": ["React"]
7
7
  }
@@ -11,6 +11,7 @@ import { TREE_STRUCTURE_ERRORS } from "../constants/Errors.js";
11
11
  import { MENU_FOCUS_REGIONS } from "../constants/index.js";
12
12
  import { getFocusableSiblingsList } from "../utils/nodeGettersByCriterias.js";
13
13
  const useFocusTracker = () => {
14
+ const shouldFocusImmediately = React2.useRef(false);
14
15
  const [focusRegion, setFocusRegion] = React2.useState("");
15
16
  const focusedRegionPerformanceHelper = React2.useRef("");
16
17
  const preventBlurReset = React2.useRef(false);
@@ -19,10 +20,11 @@ const useFocusTracker = () => {
19
20
  );
20
21
  const racingConditionDebounceTrackFocus = React2.useCallback(
21
22
  throttle(
22
- (newFocusRegion, focusNode) => {
23
+ (newFocusRegion, focusNode, flags) => {
23
24
  setFocusRegion(newFocusRegion);
24
25
  focusedRegionPerformanceHelper.current = newFocusRegion;
25
26
  focusedElementItemNode.current = focusNode;
27
+ shouldFocusImmediately.current = flags?.shouldFocusImmediately ?? false;
26
28
  return focusNode;
27
29
  },
28
30
  50,
@@ -31,7 +33,7 @@ const useFocusTracker = () => {
31
33
  []
32
34
  );
33
35
  const trackFocusTrigger = React2.useCallback(
34
- () => racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.TRIGGER, null),
36
+ (options = { shouldFocusImmediately: false }) => racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.TRIGGER, null, options),
35
37
  [racingConditionDebounceTrackFocus]
36
38
  );
37
39
  const trackFocusNode = React2.useCallback(
@@ -137,6 +139,7 @@ const useFocusTracker = () => {
137
139
  preventBlurReset,
138
140
  focusRegion,
139
141
  focusedElementItemNode,
142
+ shouldFocusImmediately,
140
143
  trackFocusTrigger,
141
144
  trackFocusNode,
142
145
  trackFocusRegionReset,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../scripts/build/transpile/react-shim.js", "../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.ts"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* 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 [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 (newFocusRegion: ValidRegionsValues, focusNode: DSMenuButtonT.PseudoFocusableMenuNodes | null) => {\n setFocusRegion(newFocusRegion);\n focusedRegionPerformanceHelper.current = newFocusRegion;\n focusedElementItemNode.current = focusNode;\n\n return focusNode;\n },\n 50,\n { leading: true, trailing: true },\n ),\n [],\n );\n\n const trackFocusTrigger = React.useCallback(\n () => racingConditionDebounceTrackFocus(MENU_FOCUS_REGIONS.TRIGGER, null),\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 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"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* 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 = { shouldFocusImmediately: false }) =>\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 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"],
5
+ "mappings": "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",
6
6
  "names": ["React"]
7
7
  }
@@ -27,7 +27,8 @@ const useMenuItemEventsHandlers = ({
27
27
  trackFocusPreviousItem,
28
28
  trackFocusParentItem,
29
29
  trackFocusNode,
30
- focusedElementItemNode
30
+ focusedElementItemNode,
31
+ trackFocusTrigger
31
32
  } = focusTrackers;
32
33
  const openSubmenu = React2.useCallback(
33
34
  ({
@@ -163,6 +164,11 @@ const useMenuItemEventsHandlers = ({
163
164
  const pseudoFocusedItemNode = focusedElementItemNode.current;
164
165
  if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;
165
166
  if (pseudoFocusedItemNode.plainItem.onKeyDown) pseudoFocusedItemNode.plainItem.onKeyDown(event);
167
+ if (event.key === "Tab") {
168
+ trackFocusTrigger({ shouldFocusImmediately: true });
169
+ onOpinionatedClose();
170
+ return;
171
+ }
166
172
  if (event.key === "ArrowDown") {
167
173
  event?.preventDefault();
168
174
  trackFocusNextItem(pseudoFocusedItemNode);
@@ -224,6 +230,7 @@ const useMenuItemEventsHandlers = ({
224
230
  },
225
231
  [
226
232
  focusedElementItemNode,
233
+ trackFocusTrigger,
227
234
  trackFocusNextItem,
228
235
  trackFocusPreviousItem,
229
236
  handleMenuItemLeftArrowKeyDown,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../scripts/build/transpile/react-shim.js", "../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.ts"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable no-console */\n/* eslint-disable max-lines */\n// in this files the returns are not useless, they are used to clearly demark the intended behavior shortcircuit for the next developer that reads the code\n/* eslint-disable no-useless-return */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\nimport React from 'react';\nimport { type DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isMultipleSelectNode,\n isRootNode,\n isSingleSelectNode,\n isWithSubmenuNode,\n isWithSubmenuOnlyNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { FORBIDDEN_BEHAVIOURS, UNEXPECTED_INTERNAL_ERRORS } from '../constants/Errors.js';\nimport { type DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\nimport { getNewSelectionMultipleSelect } from '../utils/multipleSelectionHelpers.js';\nimport { getSubMenusPath } from '../utils/nodeGettersByCriterias.js';\nimport { getNewSelectionSingleSelect } from '../utils/singleSelectionHelpers.js';\nimport type { useFocusTracker } from './useFocusTracker.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\n\ntype ItemKeydownHelpersArgs = {\n pseudoFocusedItemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n};\ntype SubmenuChangeMetainfo = {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.SyntheticEvent;\n};\n\ntype UseMenuItemEventsHandlersConfig = {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n focusTrackers: ReturnType<typeof useFocusTracker>;\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n};\n\nexport const useMenuItemEventsHandlers = ({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n}: UseMenuItemEventsHandlersConfig) => {\n const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;\n const { onOpinionatedClose } = menuOpenStatus;\n const {\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n trackFocusNextItem,\n trackFocusPreviousItem,\n trackFocusParentItem,\n trackFocusNode,\n focusedElementItemNode,\n } = focusTrackers;\n\n const openSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.WithSubmenuMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n if (!isWithSubmenuNode(itemNode)) {\n console.log('openSubmenu -> itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_OPEN_SUBMENU_OF_NODE_WITHOUT_SUBMENU;\n }\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath, itemNode];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeSubmenusAfterNode = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeCurrentSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n }) => {\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const subMenuNodesPath = getSubMenusPath(itemNode);\n // if length is 0, it means the parent is the root node, so we close all the submenus\n if (subMenuNodesPath.length === 0) {\n handleChangeOpenedSubItems([], metainfo);\n return { currentSubmenuWasMainMenu: true };\n }\n\n // we want to close current submenu, so we pop the last element\n // getSubMenusPath guarantees the order by walkParents + reverse at the end\n subMenuNodesPath.pop();\n handleChangeOpenedSubItems(subMenuNodesPath, metainfo);\n return { currentSubmenuWasMainMenu: false };\n },\n [handleChangeOpenedSubItems],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * Additional roles, states, and properties needed for the menu element are described in the Menu and Menubar Pattern.\n * https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n * Because menubar and menu elements are composite widgets as described in the practice for Keyboard Navigation Inside Components,\n * Tab and Shift + Tab do not move focus among the items in the menu.\n * Instead, the keyboard commands described in this section enable users to move focus among the elements in a menubar or menu.\n * Tab and Shift + Tab:\n * - Move focus into a menubar <- Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n * - When focus is on a menuitem in a menu or menubar, move focus out of the menu or menubar, and close all menus and submenus.\n * ************************************************************************************************************************************\n * this will be implemented by not setting document.activeElement to the menuitem but keeping it on the trigger button\n * why? because there is no javascript API to trigger a \"tab\"/\"shift+tab\" navigation and any algorithm trying to do so is fragile\n * also, this is enforced by w3.org too as per:\n * ************************************************************************************************************************************\n * Note that Tab and Shift + Tab do not move focus into a menu.\n * Unlike a menubar, a menu is not visually persistent,\n * and authors are responsible for ensuring focus moves to an item inside of a menu when the menu opens.\n *\n */\n const handleMenuItemEnterKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Enter:\n // - When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ...\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n } else {\n // - Otherwise, activates the item...\n\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n\n // \"activation\" is a concept,\n // WCAG here implies that for menuitemscheckbox and menuitemsradio this triggers the selection\n // for menuitems (that doesn't fit selection nor submenus) it triggers the action\n // this also triggers enter -> single-select-item-with-submenu as expected\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n onActivateItem!(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n }\n // ... and closes the menu.\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n }\n },\n [\n trackFocusFirstChildItem,\n openSubmenu,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n selectedNodes,\n onItemSelected,\n onActivateItem,\n ],\n );\n const handleMenuItemSpaceKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Space:\n // [X] (Optional): When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // [X] (Optional): When focus is on a menuitemcheckbox, changes the state without closing the menu.\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitemradio that is not checked, without closing the menu,\n // checks the focused menuitemradio and unchecks any other checked menuitemradio element in the same group.\n if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitem that does not have a submenu, activates the menuitem and closes the menu.\n onActivateItem?.(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n },\n [\n onActivateItem,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n trackFocusFirstChildItem,\n openSubmenu,\n selectedNodes,\n onItemSelected,\n ],\n );\n const handleMenuItemRightArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Right Arrow:\n // [N/A] When focus is in a menubar, moves focus to the next item, optionally wrapping from the last to the first.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a menu and on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // [N/A] When focus is in a menu and on an item that does not have a submenu, performs the following 3 actions:\n // [N/A] Closes the submenu and any parent menus.\n // [N/A] Moves focus to the next item in the menubar.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] If focus is now on a menuitem with a submenu, either:\n // [X] (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // /\\ we choose the recommended option\n // OR\n // [N/A] opens the submenu of that menuitem and places focus on the first item in the submenu.\n // ***********************************************************\n // Dimsum implements IoC, if APP side for whatever reason wants to open the new sub sub menu,\n // it's up to them to listen to the pseudo focus event and open the sub sub menu\n // ***********************************************************\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // Note that if the menubar were not present, e.g., the menus were opened from a menubutton,\n // Right Arrow would not do anything when focus is on an item that does not have a submenu.\n return;\n },\n [openSubmenu, trackFocusFirstChildItem],\n );\n\n const handleMenuItemLeftArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Left Arrow:\n // [N/A] When focus is in a menubar, moves focus to the previous item, optionally wrapping from the first to the last.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a submenu of an item in a menu, closes the submenu and returns focus to the parent menuitem.\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n if (!currentSubmenuWasMainMenu) {\n trackFocusParentItem(pseudoFocusedItemNode);\n }\n\n // [N/A] When focus is in a submenu of an item in a menubar, performs the following 3 actions:\n // [N/A] Closes the submenu.\n // [N/A] Moves focus to the previous item in the menubar.\n // [N/A] If focus is now on a menuitem with a submenu, either:\n // (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // or\n // opens the submenu of that menuitem and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n },\n [closeCurrentSubmenu, trackFocusParentItem],\n );\n\n const handleFocusableMenuItemKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLDivElement>>(\n (event) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n // if there is no focused item, we can't handle the logics\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item contains an `onKeyDown` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onKeyDown) pseudoFocusedItemNode.plainItem.onKeyDown(event);\n\n // Down Arrow:\n // [N/A] When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n // [X] When focus is in a menu, moves focus to the next item,\n // [X] optionally wrapping from the last to the first.\n if (event.key === 'ArrowDown') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusNextItem(pseudoFocusedItemNode);\n return;\n }\n // Up Arrow:\n // [X] When focus is in a menu, moves focus to the previous item,\n // [X] optionally wrapping from the first to the last.\n // [N/A] (Optional): When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the last item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n if (event.key === 'ArrowUp') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusPreviousItem(pseudoFocusedItemNode);\n return;\n }\n // Left Arrow:\n if (event.key === 'ArrowLeft') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemLeftArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Home: If arrow key wrapping is not supported, moves focus to the first item in the current menu or menubar.\n if (event.key === 'Home') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusFirstChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n // End: If arrow key wrapping is not supported, moves focus to the last item in the current menu or menubar.\n if (event.key === 'End') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusLastChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n if (event.key === 'Escape') {\n // if the item has no submenu, the alt key is transparent, we behave as if it's not even pressed\n if (event.altKey && isWithSubmenuNode(pseudoFocusedItemNode)) {\n // PUI-14639 - Implement submenu dismissal mechanism\n // Description: we need to add a mechanism to close submenus that automatically expand when a menu item is focused with the keyboard.\n // we have decided to implement a special shortcut (Alt/Option + Esc) as the dismissal mechanism.\n // This change is necessary to comply with WCAG success criterion 1.4.13.\n // https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus.html\n // if the item has a submenu, we close it\n closeSubmenusAfterNode({ itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // Escape: Close the menu that contains focus and return focus to the element or context, e.g., menu button or parent menuitem, from which the menu was opened\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusParentItem(pseudoFocusedItemNode);\n // if the menu we are trying to close is the main menu, we adittionally close the main menu itself\n if (currentSubmenuWasMainMenu) {\n // if parent is the root node, we close also the main menu\n onOpinionatedClose();\n }\n return;\n }\n\n /* ************************************************************************************\n * ******* ACTIVATION & SUBMENU keys doesn't apply if the item is \"disabled\" **********\n * ************************************************************************************ */\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // Enter:\n if (event.key === 'Enter') {\n handleMenuItemEnterKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Space:\n if (event.key === ' ') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Right Arrow:\n if (event.key === 'ArrowRight') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemRightArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n },\n [\n focusedElementItemNode,\n trackFocusNextItem,\n trackFocusPreviousItem,\n handleMenuItemLeftArrowKeyDown,\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n closeCurrentSubmenu,\n trackFocusParentItem,\n closeSubmenusAfterNode,\n onOpinionatedClose,\n handleMenuItemEnterKeyDown,\n handleMenuItemSpaceKeyDown,\n handleMenuItemRightArrowKeyDown,\n ],\n );\n const handleFocusableMenuItemClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item is disabled, we don't do anything\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // we need to handle which submenu needs to close automagically based on the click\n // starting from the itemNode, we check:\n // - the parent of the itemNode is root?\n // - yes -> we close all the submenus\n const parentMenuNode = pseudoFocusedItemNode.parent;\n if (isRootNode(parentMenuNode)) {\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n } else {\n // - no -> we close all the submenus that are not in the path to the root of the itemNode\n // this is necessary on click because the user can still interact with other (visual) depths of the menu\n // during keyboard navigation, the user is limited to the current (visual) depth\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...getSubMenusPath(pseudoFocusedItemNode)];\n // - if the clicked node has a submenu, keep it open\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n newOpenedItems.push(pseudoFocusedItemNode);\n }\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n }\n\n // to all effects and purposes, the click event is the same as the space key\n // the main difference between enter and space is that\n // - enter ALWAYS closes the menu excepts for submenus\n // - space closes the menu if \"activable\", selections doesn't, submenus doesn't\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n // if the item contains an `onClick` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onClick) pseudoFocusedItemNode.plainItem.onClick(event);\n },\n [focusedElementItemNode, handleMenuItemSpaceKeyDown, handleChangeOpenedSubItems],\n );\n\n // the \"menu-button\" widget is an opinionated widget with a specific behaviour to follow when a focusable item is \"focused\"\n // this function achieve the opinionated \"focus an item\" opinionated behaviour (regardless of why the item is focused)\n const opinionatedFocusBehaviour = React.useCallback(\n // we currently know only one \"event\" that can trigger this opinionated behaviour\n // - focus => React.FocusEvent <-- triggered by ATs \"focus mode\" or \"virtual cursor\" navigation\n // NOTE:\n // mouseEnter => React.MouseEvent\n // still ends up calling this function, but as a side effect of mouse-enter focusing the item, not as the main event\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n if (!isWithSubmenuNode(itemNode)) {\n // if the item has nosubmenu we close all the siblings submenus\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n return;\n }\n // if the item is disabled, we don't open the submenu, and there is nothing else to do\n if (itemNode.plainItem.disabled) return;\n // if not disabled and it has a submenu, we open it, and there is nothing else to do\n openSubmenu({ itemNode, event });\n },\n [handleChangeOpenedSubItems, openSubmenu, trackFocusNode],\n );\n\n // when an item is hovered, we focus it\n // this effectively achieves:\n // - the item is focused (it trigger the handleFocusableMenuItemNativeFocusEvent function because render runs node.focus())\n // - if the user presses a keyboard key, the interaction will work starting from the (hovered hence focused) item\n const handleFocusableMenuItemOnMouseEnter = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n },\n [trackFocusNode],\n );\n\n const handleFocusableMenuItemNativeFocusEvent = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n opinionatedFocusBehaviour(itemNode, event);\n },\n [opinionatedFocusBehaviour],\n );\n\n return React.useMemo(\n () => ({\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemClick,\n handleFocusableMenuItemOnMouseEnter,\n handleFocusableMenuItemNativeFocusEvent,\n }),\n [\n handleFocusableMenuItemClick,\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemNativeFocusEvent,\n handleFocusableMenuItemOnMouseEnter,\n ],\n );\n};\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable no-console */\n/* eslint-disable max-lines */\n// in this files the returns are not useless, they are used to clearly demark the intended behavior shortcircuit for the next developer that reads the code\n/* eslint-disable no-useless-return */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\nimport React from 'react';\nimport { type DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isMultipleSelectNode,\n isRootNode,\n isSingleSelectNode,\n isWithSubmenuNode,\n isWithSubmenuOnlyNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { FORBIDDEN_BEHAVIOURS, UNEXPECTED_INTERNAL_ERRORS } from '../constants/Errors.js';\nimport { type DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\nimport { getNewSelectionMultipleSelect } from '../utils/multipleSelectionHelpers.js';\nimport { getSubMenusPath } from '../utils/nodeGettersByCriterias.js';\nimport { getNewSelectionSingleSelect } from '../utils/singleSelectionHelpers.js';\nimport type { useFocusTracker } from './useFocusTracker.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\n\ntype ItemKeydownHelpersArgs = {\n pseudoFocusedItemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n};\ntype SubmenuChangeMetainfo = {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.SyntheticEvent;\n};\n\ntype UseMenuItemEventsHandlersConfig = {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n focusTrackers: ReturnType<typeof useFocusTracker>;\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n};\n\nexport const useMenuItemEventsHandlers = ({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n}: UseMenuItemEventsHandlersConfig) => {\n const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;\n const { onOpinionatedClose } = menuOpenStatus;\n const {\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n trackFocusNextItem,\n trackFocusPreviousItem,\n trackFocusParentItem,\n trackFocusNode,\n focusedElementItemNode,\n trackFocusTrigger,\n } = focusTrackers;\n\n const openSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.WithSubmenuMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n if (!isWithSubmenuNode(itemNode)) {\n console.log('openSubmenu -> itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_OPEN_SUBMENU_OF_NODE_WITHOUT_SUBMENU;\n }\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath, itemNode];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeSubmenusAfterNode = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const closeCurrentSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n }) => {\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const subMenuNodesPath = getSubMenusPath(itemNode);\n // if length is 0, it means the parent is the root node, so we close all the submenus\n if (subMenuNodesPath.length === 0) {\n handleChangeOpenedSubItems([], metainfo);\n return { currentSubmenuWasMainMenu: true };\n }\n\n // we want to close current submenu, so we pop the last element\n // getSubMenusPath guarantees the order by walkParents + reverse at the end\n subMenuNodesPath.pop();\n handleChangeOpenedSubItems(subMenuNodesPath, metainfo);\n return { currentSubmenuWasMainMenu: false };\n },\n [handleChangeOpenedSubItems],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * Additional roles, states, and properties needed for the menu element are described in the Menu and Menubar Pattern.\n * https://www.w3.org/WAI/ARIA/apg/patterns/menubar/\n * Because menubar and menu elements are composite widgets as described in the practice for Keyboard Navigation Inside Components,\n * Tab and Shift + Tab do not move focus among the items in the menu.\n * Instead, the keyboard commands described in this section enable users to move focus among the elements in a menubar or menu.\n * Tab and Shift + Tab:\n * - Move focus into a menubar <- Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n * - When focus is on a menuitem in a menu or menubar, move focus out of the menu or menubar, and close all menus and submenus.\n * ************************************************************************************************************************************\n * this will be implemented by not setting document.activeElement to the menuitem but keeping it on the trigger button\n * why? because there is no javascript API to trigger a \"tab\"/\"shift+tab\" navigation and any algorithm trying to do so is fragile\n * also, this is enforced by w3.org too as per:\n * ************************************************************************************************************************************\n * Note that Tab and Shift + Tab do not move focus into a menu.\n * Unlike a menubar, a menu is not visually persistent,\n * and authors are responsible for ensuring focus moves to an item inside of a menu when the menu opens.\n *\n */\n const handleMenuItemEnterKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Enter:\n // - When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ...\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n } else {\n // - Otherwise, activates the item...\n\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n\n // \"activation\" is a concept,\n // WCAG here implies that for menuitemscheckbox and menuitemsradio this triggers the selection\n // for menuitems (that doesn't fit selection nor submenus) it triggers the action\n // this also triggers enter -> single-select-item-with-submenu as expected\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n onActivateItem!(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n }\n // ... and closes the menu.\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n }\n },\n [\n trackFocusFirstChildItem,\n openSubmenu,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n selectedNodes,\n onItemSelected,\n onActivateItem,\n ],\n );\n const handleMenuItemSpaceKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Space:\n // [X] (Optional): When focus is on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // ----------------------------------\n // 4. Although it is recommended that authors avoid doing so,\n // some implementations of navigation menubars may have menuitem elements that both perform a function and open a submenu.\n // In such implementations, Enter and Space perform a navigation function,\n // e.g., load new content, while Down Arrow, in a horizontal menubar, opens the submenu associated with that same menuitem.\n // ---------------------------------\n // Based on an investigation, the above statement is not specific to \"navigation\" nor to ONLY \"menubars\", it really is about \"menuitems\" in general\n // as long as the role=\"menuitem\" & aria-haspopup=\"${id-of-the-submenu}\" & aria-expanded=\"${true/false}\" (& aria-orientation=\"vertical/horizontal\" if non-default)\n // are set, the Screen Readers will provide the same announcement for the menuitem regardless of the parent role\n if (isWithSubmenuOnlyNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // [X] (Optional): When focus is on a menuitemcheckbox, changes the state without closing the menu.\n if (isMultipleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionMultipleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitemradio that is not checked, without closing the menu,\n // checks the focused menuitemradio and unchecks any other checked menuitemradio element in the same group.\n if (isSingleSelectNode(pseudoFocusedItemNode)) {\n // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-0.html#non-null-assertion-operator\n // useAdvancedValidation guarantees the non-null in this \"if\" branch\n const newSelection = getNewSelectionSingleSelect(selectedNodes!, pseudoFocusedItemNode);\n onItemSelected!(newSelection, { itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // [X] (Optional): When focus is on a menuitem that does not have a submenu, activates the menuitem and closes the menu.\n onActivateItem?.(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });\n\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n onOpinionatedClose();\n },\n [\n onActivateItem,\n handleChangeOpenedSubItems,\n onOpinionatedClose,\n trackFocusFirstChildItem,\n openSubmenu,\n selectedNodes,\n onItemSelected,\n ],\n );\n const handleMenuItemRightArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Right Arrow:\n // [N/A] When focus is in a menubar, moves focus to the next item, optionally wrapping from the last to the first.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a menu and on a menuitem that has a submenu, opens the submenu and places focus on its first item.\n // [N/A] When focus is in a menu and on an item that does not have a submenu, performs the following 3 actions:\n // [N/A] Closes the submenu and any parent menus.\n // [N/A] Moves focus to the next item in the menubar.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] If focus is now on a menuitem with a submenu, either:\n // [X] (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // /\\ we choose the recommended option\n // OR\n // [N/A] opens the submenu of that menuitem and places focus on the first item in the submenu.\n // ***********************************************************\n // Dimsum implements IoC, if APP side for whatever reason wants to open the new sub sub menu,\n // it's up to them to listen to the pseudo focus event and open the sub sub menu\n // ***********************************************************\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n openSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusFirstChildItem(pseudoFocusedItemNode);\n return;\n }\n // Note that if the menubar were not present, e.g., the menus were opened from a menubutton,\n // Right Arrow would not do anything when focus is on an item that does not have a submenu.\n return;\n },\n [openSubmenu, trackFocusFirstChildItem],\n );\n\n const handleMenuItemLeftArrowKeyDown = React.useCallback(\n ({ pseudoFocusedItemNode, event }: ItemKeydownHelpersArgs) => {\n // Left Arrow:\n // [N/A] When focus is in a menubar, moves focus to the previous item, optionally wrapping from the first to the last.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n\n // [X] When focus is in a submenu of an item in a menu, closes the submenu and returns focus to the parent menuitem.\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n if (!currentSubmenuWasMainMenu) {\n trackFocusParentItem(pseudoFocusedItemNode);\n }\n\n // [N/A] When focus is in a submenu of an item in a menubar, performs the following 3 actions:\n // [N/A] Closes the submenu.\n // [N/A] Moves focus to the previous item in the menubar.\n // [N/A] If focus is now on a menuitem with a submenu, either:\n // (Recommended) opens the submenu of that menuitem without moving focus into the submenu,\n // or\n // opens the submenu of that menuitem and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n },\n [closeCurrentSubmenu, trackFocusParentItem],\n );\n\n const handleFocusableMenuItemKeyDown = React.useCallback<React.KeyboardEventHandler<HTMLDivElement>>(\n (event) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n // if there is no focused item, we can't handle the logics\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item contains an `onKeyDown` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onKeyDown) pseudoFocusedItemNode.plainItem.onKeyDown(event);\n // TAB:\n // When focus is on a menuitem in a menu or menubar, move focus to the trigger so tab flows occurs from there.\n // Then close all menus and submenus.\n if (event.key === 'Tab') {\n trackFocusTrigger({ shouldFocusImmediately: true });\n onOpinionatedClose();\n return;\n }\n // Down Arrow:\n // [N/A] When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the first item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n // [X] When focus is in a menu, moves focus to the next item,\n // [X] optionally wrapping from the last to the first.\n if (event.key === 'ArrowDown') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusNextItem(pseudoFocusedItemNode);\n return;\n }\n // Up Arrow:\n // [X] When focus is in a menu, moves focus to the previous item,\n // [X] optionally wrapping from the first to the last.\n // [N/A] (Optional): When focus is on a menuitem in a menubar, and that menuitem has a submenu, opens the submenu and places focus on the last item in the submenu.\n // /\\ Not relevant for this component, this is ONLY FOR MENU, not for MENUBARS\n if (event.key === 'ArrowUp') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n trackFocusPreviousItem(pseudoFocusedItemNode);\n return;\n }\n // Left Arrow:\n if (event.key === 'ArrowLeft') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemLeftArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Home: If arrow key wrapping is not supported, moves focus to the first item in the current menu or menubar.\n if (event.key === 'Home') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusFirstChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n // End: If arrow key wrapping is not supported, moves focus to the last item in the current menu or menubar.\n if (event.key === 'End') {\n if (!pseudoFocusedItemNode.parent) {\n console.log('handleFocusableMenuItemKeyDown -> Home > focused item node', pseudoFocusedItemNode);\n // should be impossible, at most the .parent is the root node, which is not able to trigger this event\n throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_HAS_NO_PARENT;\n }\n trackFocusLastChildItem(pseudoFocusedItemNode.parent);\n return;\n }\n if (event.key === 'Escape') {\n // if the item has no submenu, the alt key is transparent, we behave as if it's not even pressed\n if (event.altKey && isWithSubmenuNode(pseudoFocusedItemNode)) {\n // PUI-14639 - Implement submenu dismissal mechanism\n // Description: we need to add a mechanism to close submenus that automatically expand when a menu item is focused with the keyboard.\n // we have decided to implement a special shortcut (Alt/Option + Esc) as the dismissal mechanism.\n // This change is necessary to comply with WCAG success criterion 1.4.13.\n // https://www.w3.org/WAI/WCAG21/Understanding/content-on-hover-or-focus.html\n // if the item has a submenu, we close it\n closeSubmenusAfterNode({ itemNode: pseudoFocusedItemNode, event });\n return;\n }\n // Escape: Close the menu that contains focus and return focus to the element or context, e.g., menu button or parent menuitem, from which the menu was opened\n const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });\n trackFocusParentItem(pseudoFocusedItemNode);\n // if the menu we are trying to close is the main menu, we adittionally close the main menu itself\n if (currentSubmenuWasMainMenu) {\n // if parent is the root node, we close also the main menu\n onOpinionatedClose();\n }\n return;\n }\n\n /* ************************************************************************************\n * ******* ACTIVATION & SUBMENU keys doesn't apply if the item is \"disabled\" **********\n * ************************************************************************************ */\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // Enter:\n if (event.key === 'Enter') {\n handleMenuItemEnterKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Space:\n if (event.key === ' ') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n // Right Arrow:\n if (event.key === 'ArrowRight') {\n event?.preventDefault(); // if scrollbar are present, this keydown event should not interact with the scrollbar\n handleMenuItemRightArrowKeyDown({ pseudoFocusedItemNode, event });\n return;\n }\n },\n [\n focusedElementItemNode,\n trackFocusTrigger,\n trackFocusNextItem,\n trackFocusPreviousItem,\n handleMenuItemLeftArrowKeyDown,\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n closeCurrentSubmenu,\n trackFocusParentItem,\n closeSubmenusAfterNode,\n onOpinionatedClose,\n handleMenuItemEnterKeyDown,\n handleMenuItemSpaceKeyDown,\n handleMenuItemRightArrowKeyDown,\n ],\n );\n const handleFocusableMenuItemClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item is disabled, we don't do anything\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // we need to handle which submenu needs to close automagically based on the click\n // starting from the itemNode, we check:\n // - the parent of the itemNode is root?\n // - yes -> we close all the submenus\n const parentMenuNode = pseudoFocusedItemNode.parent;\n if (isRootNode(parentMenuNode)) {\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n } else {\n // - no -> we close all the submenus that are not in the path to the root of the itemNode\n // this is necessary on click because the user can still interact with other (visual) depths of the menu\n // during keyboard navigation, the user is limited to the current (visual) depth\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...getSubMenusPath(pseudoFocusedItemNode)];\n // - if the clicked node has a submenu, keep it open\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n newOpenedItems.push(pseudoFocusedItemNode);\n }\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n }\n\n // to all effects and purposes, the click event is the same as the space key\n // the main difference between enter and space is that\n // - enter ALWAYS closes the menu excepts for submenus\n // - space closes the menu if \"activable\", selections doesn't, submenus doesn't\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n // if the item contains an `onClick` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onClick) pseudoFocusedItemNode.plainItem.onClick(event);\n },\n [focusedElementItemNode, handleMenuItemSpaceKeyDown, handleChangeOpenedSubItems],\n );\n\n // the \"menu-button\" widget is an opinionated widget with a specific behaviour to follow when a focusable item is \"focused\"\n // this function achieve the opinionated \"focus an item\" opinionated behaviour (regardless of why the item is focused)\n const opinionatedFocusBehaviour = React.useCallback(\n // we currently know only one \"event\" that can trigger this opinionated behaviour\n // - focus => React.FocusEvent <-- triggered by ATs \"focus mode\" or \"virtual cursor\" navigation\n // NOTE:\n // mouseEnter => React.MouseEvent\n // still ends up calling this function, but as a side effect of mouse-enter focusing the item, not as the main event\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n if (!isWithSubmenuNode(itemNode)) {\n // if the item has nosubmenu we close all the siblings submenus\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n return;\n }\n // if the item is disabled, we don't open the submenu, and there is nothing else to do\n if (itemNode.plainItem.disabled) return;\n // if not disabled and it has a submenu, we open it, and there is nothing else to do\n openSubmenu({ itemNode, event });\n },\n [handleChangeOpenedSubItems, openSubmenu, trackFocusNode],\n );\n\n // when an item is hovered, we focus it\n // this effectively achieves:\n // - the item is focused (it trigger the handleFocusableMenuItemNativeFocusEvent function because render runs node.focus())\n // - if the user presses a keyboard key, the interaction will work starting from the (hovered hence focused) item\n const handleFocusableMenuItemOnMouseEnter = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n },\n [trackFocusNode],\n );\n\n const handleFocusableMenuItemNativeFocusEvent = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n opinionatedFocusBehaviour(itemNode, event);\n },\n [opinionatedFocusBehaviour],\n );\n\n return React.useMemo(\n () => ({\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemClick,\n handleFocusableMenuItemOnMouseEnter,\n handleFocusableMenuItemNativeFocusEvent,\n }),\n [\n handleFocusableMenuItemClick,\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemNativeFocusEvent,\n handleFocusableMenuItemOnMouseEnter,\n ],\n );\n};\n"],
5
+ "mappings": "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",
6
6
  "names": ["React"]
7
7
  }
@@ -4,9 +4,7 @@ import { resolveRef } from "../../../utils/resolveRef.js";
4
4
  import { MenuBehaviouralContextProviderContext } from "../../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js";
5
5
  import { MENU_FOCUS_REGIONS } from "../../DSMenuBehaviouralContextProvider/constants/index.js";
6
6
  const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }) => {
7
- const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger } = React2.useContext(
8
- MenuBehaviouralContextProviderContext
9
- );
7
+ const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger, shouldFocusImmediately } = React2.useContext(MenuBehaviouralContextProviderContext);
10
8
  const { onClick, onFocus, innerRef, onKeyDown } = propsWithDefault;
11
9
  const {
12
10
  refs: { setReference }
@@ -37,14 +35,18 @@ const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }) => {
37
35
  const handleFocusIfTriggerIsFocusRegion = React2.useCallback(
38
36
  (node) => {
39
37
  if (reRenderButtonToAllowFocus && node) {
40
- requestAnimationFrame(() => {
38
+ if (!shouldFocusImmediately.current) {
39
+ requestAnimationFrame(() => {
40
+ node.focus();
41
+ });
42
+ } else {
41
43
  node.focus();
42
- });
44
+ }
43
45
  }
44
46
  setReference(node);
45
47
  if (innerRef) resolveRef(innerRef, node);
46
48
  },
47
- [innerRef, reRenderButtonToAllowFocus, setReference]
49
+ [innerRef, reRenderButtonToAllowFocus, setReference, shouldFocusImmediately]
48
50
  );
49
51
  const handleTriggerKeyDown = React2.useCallback(
50
52
  (event, ...args) => {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../../../scripts/build/transpile/react-shim.js", "../../../../../src/parts/DSOpinionatedButton/config/useTriggerEventsHandlers.ts"],
4
- "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import type { DSButtonV3T } from '@elliemae/ds-button-v2';\nimport { type useFloatingContext } from '@elliemae/ds-floating-context';\nimport React from 'react';\nimport { resolveRef } from '../../../utils/resolveRef.js';\nimport { MenuBehaviouralContextProviderContext } from '../../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { MENU_FOCUS_REGIONS } from '../../DSMenuBehaviouralContextProvider/constants/index.js';\nimport { type DSOpinionatedButtonT } from '../react-desc-prop-types.js';\n\ntype UseTriggerEventsHandlersConfig = {\n propsWithDefault: DSOpinionatedButtonT.InternalProps;\n floatingContext: ReturnType<typeof useFloatingContext>;\n};\n\nexport const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }: UseTriggerEventsHandlersConfig) => {\n // =============================================================================\n // on click -> open the submenu\n // if the props already include an onClick, we will wrap it\n // =============================================================================\n const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger } = React.useContext(\n MenuBehaviouralContextProviderContext,\n );\n const { onClick, onFocus, innerRef, onKeyDown } = propsWithDefault;\n const {\n refs: { setReference },\n } = floatingContext;\n\n const focusedRegionPerformanceHelper = React.useRef(focusRegion);\n // when the focus region is on the trigger, we need to re-render the button so it invokes the ref callback again to focus itself\n // we don't need to rerender at every focus region change, only when it changes to the trigger itself.\n // by checking the ref stored value against the current focus region, the flag will stay mostly false and change to true only when needed\n const reRenderButtonToAllowFocus =\n focusRegion === MENU_FOCUS_REGIONS.TRIGGER && focusedRegionPerformanceHelper.current !== focusRegion;\n focusedRegionPerformanceHelper.current = focusRegion;\n\n const mutableIsMenuOpen = React.useRef(isMenuOpen);\n // this is a boolean, so a primitive value,\n // we don't need to useEffect to change it\n // because we don't need to ensure referencial equality\n mutableIsMenuOpen.current = isMenuOpen;\n\n const handleOnClick = React.useCallback<Required<DSButtonV3T.Props>['onClick']>(\n (e, ...args) => {\n if (onClick) onClick(e, ...args);\n // no reason to change this function reference every time the menu opens/closes\n // so we are using a mutable ref to optimize this\n if (mutableIsMenuOpen.current) {\n onOpinionatedClose();\n } else {\n onOpinionatedOpen();\n }\n },\n [onClick, onOpinionatedClose, onOpinionatedOpen],\n );\n const handleOnFocus = React.useCallback<Required<DSButtonV3T.Props>['onFocus']>(\n (e, ...args) => {\n if (focusedRegionPerformanceHelper.current !== MENU_FOCUS_REGIONS.TRIGGER) trackFocusTrigger();\n if (onFocus) onFocus(e, ...args);\n },\n [onFocus, trackFocusTrigger],\n );\n\n const handleFocusIfTriggerIsFocusRegion = React.useCallback<React.RefCallback<HTMLButtonElement>>(\n (node) => {\n if (reRenderButtonToAllowFocus && node) {\n // when user presses \"Enter\" different things happen depending on top of what element the focus is\n // if it is in the trigger (this function) then it spawns the submenu and focuses the first item\n // if it is in a menu item, it activates the item and closes the menu, triggering this function again\n // So, why do we need the requestAnimationFrame?\n // pressing enter in an item will focus the trigger\n // -> current react behaviour means the menu button \"keyDown\" event will also trigger with \"Enter\"\n // -> which will open the menu again\n // as of react 18 and 28/october/2025, this requestAnimationFrame prevents the keyDown event from triggering on top of the button\n requestAnimationFrame(() => {\n node.focus();\n });\n }\n setReference(node);\n if (innerRef) resolveRef(innerRef, node);\n },\n [innerRef, reRenderButtonToAllowFocus, setReference],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * With focus on the button:\n * Enter: opens the menu and places focus on the first menu item.\n * Space: Opens the menu and places focus on the first menu item.\n * (Optional) Down Arrow: opens the menu and moves focus to the first menu item.\n * (Optional) Up Arrow: opens the menu and moves focus to the last menu item.\n */\n const handleTriggerKeyDown = React.useCallback<Required<DSButtonV3T.Props>['onKeyDown']>(\n (event, ...args) => {\n // DSButtonV3 triggers the onClick on Enter and Space, so we don't need to handle it here\n // if (event.key === 'Enter' || event.key === ' ') {\n // onOpinionatedOpen();\n // }\n if (event.key === 'ArrowDown') {\n onOpinionatedOpen();\n }\n if (event.key === 'ArrowUp') {\n onOpinionatedOpen({ focusLastChild: true });\n }\n if (event.key === 'Escape') {\n onOpinionatedClose();\n }\n if (onKeyDown) onKeyDown(event, ...args);\n },\n [onKeyDown, onOpinionatedOpen, onOpinionatedClose],\n );\n\n return React.useMemo(\n () => ({ handleTriggerKeyDown, handleOnClick, handleOnFocus, handleFocusIfTriggerIsFocusRegion }),\n [handleFocusIfTriggerIsFocusRegion, handleOnClick, handleOnFocus, handleTriggerKeyDown],\n );\n};\n"],
5
- "mappings": "AAAA,YAAY,WAAW;ACEvB,OAAOA,YAAW;AAClB,SAAS,kBAAkB;AAC3B,SAAS,6CAA6C;AACtD,SAAS,0BAA0B;AAQ5B,MAAM,2BAA2B,CAAC,EAAE,kBAAkB,gBAAgB,MAAsC;AAKjH,QAAM,EAAE,aAAa,YAAY,mBAAmB,oBAAoB,kBAAkB,IAAIA,OAAM;AAAA,IAClG;AAAA,EACF;AACA,QAAM,EAAE,SAAS,SAAS,UAAU,UAAU,IAAI;AAClD,QAAM;AAAA,IACJ,MAAM,EAAE,aAAa;AAAA,EACvB,IAAI;AAEJ,QAAM,iCAAiCA,OAAM,OAAO,WAAW;AAI/D,QAAM,6BACJ,gBAAgB,mBAAmB,WAAW,+BAA+B,YAAY;AAC3F,iCAA+B,UAAU;AAEzC,QAAM,oBAAoBA,OAAM,OAAO,UAAU;AAIjD,oBAAkB,UAAU;AAE5B,QAAM,gBAAgBA,OAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAG/B,UAAI,kBAAkB,SAAS;AAC7B,2BAAmB;AAAA,MACrB,OAAO;AACL,0BAAkB;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC,SAAS,oBAAoB,iBAAiB;AAAA,EACjD;AACA,QAAM,gBAAgBA,OAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,+BAA+B,YAAY,mBAAmB,QAAS,mBAAkB;AAC7F,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAAA,IACjC;AAAA,IACA,CAAC,SAAS,iBAAiB;AAAA,EAC7B;AAEA,QAAM,oCAAoCA,OAAM;AAAA,IAC9C,CAAC,SAAS;AACR,UAAI,8BAA8B,MAAM;AAStC,8BAAsB,MAAM;AAC1B,eAAK,MAAM;AAAA,QACb,CAAC;AAAA,MACH;AACA,mBAAa,IAAI;AACjB,UAAI,SAAU,YAAW,UAAU,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,UAAU,4BAA4B,YAAY;AAAA,EACrD;AAWA,QAAM,uBAAuBA,OAAM;AAAA,IACjC,CAAC,UAAU,SAAS;AAKlB,UAAI,MAAM,QAAQ,aAAa;AAC7B,0BAAkB;AAAA,MACpB;AACA,UAAI,MAAM,QAAQ,WAAW;AAC3B,0BAAkB,EAAE,gBAAgB,KAAK,CAAC;AAAA,MAC5C;AACA,UAAI,MAAM,QAAQ,UAAU;AAC1B,2BAAmB;AAAA,MACrB;AACA,UAAI,UAAW,WAAU,OAAO,GAAG,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,WAAW,mBAAmB,kBAAkB;AAAA,EACnD;AAEA,SAAOA,OAAM;AAAA,IACX,OAAO,EAAE,sBAAsB,eAAe,eAAe,kCAAkC;AAAA,IAC/F,CAAC,mCAAmC,eAAe,eAAe,oBAAoB;AAAA,EACxF;AACF;",
4
+ "sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import type { DSButtonV3T } from '@elliemae/ds-button-v2';\nimport { type useFloatingContext } from '@elliemae/ds-floating-context';\nimport React from 'react';\nimport { resolveRef } from '../../../utils/resolveRef.js';\nimport { MenuBehaviouralContextProviderContext } from '../../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { MENU_FOCUS_REGIONS } from '../../DSMenuBehaviouralContextProvider/constants/index.js';\nimport { type DSOpinionatedButtonT } from '../react-desc-prop-types.js';\n\ntype UseTriggerEventsHandlersConfig = {\n propsWithDefault: DSOpinionatedButtonT.InternalProps;\n floatingContext: ReturnType<typeof useFloatingContext>;\n};\n\nexport const useTriggerEventsHandlers = ({ propsWithDefault, floatingContext }: UseTriggerEventsHandlersConfig) => {\n // =============================================================================\n // on click -> open the submenu\n // if the props already include an onClick, we will wrap it\n // =============================================================================\n const { focusRegion, isMenuOpen, onOpinionatedOpen, onOpinionatedClose, trackFocusTrigger, shouldFocusImmediately } =\n React.useContext(MenuBehaviouralContextProviderContext);\n const { onClick, onFocus, innerRef, onKeyDown } = propsWithDefault;\n const {\n refs: { setReference },\n } = floatingContext;\n\n const focusedRegionPerformanceHelper = React.useRef(focusRegion);\n // when the focus region is on the trigger, we need to re-render the button so it invokes the ref callback again to focus itself\n // we don't need to rerender at every focus region change, only when it changes to the trigger itself.\n // by checking the ref stored value against the current focus region, the flag will stay mostly false and change to true only when needed\n const reRenderButtonToAllowFocus =\n focusRegion === MENU_FOCUS_REGIONS.TRIGGER && focusedRegionPerformanceHelper.current !== focusRegion;\n focusedRegionPerformanceHelper.current = focusRegion;\n\n const mutableIsMenuOpen = React.useRef(isMenuOpen);\n // this is a boolean, so a primitive value,\n // we don't need to useEffect to change it\n // because we don't need to ensure referencial equality\n mutableIsMenuOpen.current = isMenuOpen;\n\n const handleOnClick = React.useCallback<Required<DSButtonV3T.Props>['onClick']>(\n (e, ...args) => {\n if (onClick) onClick(e, ...args);\n // no reason to change this function reference every time the menu opens/closes\n // so we are using a mutable ref to optimize this\n if (mutableIsMenuOpen.current) {\n onOpinionatedClose();\n } else {\n onOpinionatedOpen();\n }\n },\n [onClick, onOpinionatedClose, onOpinionatedOpen],\n );\n const handleOnFocus = React.useCallback<Required<DSButtonV3T.Props>['onFocus']>(\n (e, ...args) => {\n if (focusedRegionPerformanceHelper.current !== MENU_FOCUS_REGIONS.TRIGGER) trackFocusTrigger();\n if (onFocus) onFocus(e, ...args);\n },\n [onFocus, trackFocusTrigger],\n );\n\n const handleFocusIfTriggerIsFocusRegion = React.useCallback<React.RefCallback<HTMLButtonElement>>(\n (node) => {\n if (reRenderButtonToAllowFocus && node) {\n // when user presses \"Enter\" different things happen depending on top of what element the focus is\n // if it is in the trigger (this function) then it spawns the submenu and focuses the first item\n // if it is in a menu item, it activates the item and closes the menu, triggering this function again\n // So, why do we need the requestAnimationFrame?\n // pressing enter in an item will focus the trigger\n // -> current react behaviour means the menu button \"keyDown\" event will also trigger with \"Enter\"\n // -> which will open the menu again\n // as of react 18 and 28/october/2025, this requestAnimationFrame prevents the keyDown event from triggering on top of the button\n if (!shouldFocusImmediately.current) {\n requestAnimationFrame(() => {\n node.focus();\n });\n } else {\n // In some cases, like when user presses \"Tab\" within a menu, we want to focus the trigger immediately.\n // So that the tab flow occurs from the trigger to the next focusable element in the page.\n // Therefore, we don't need to wait for the next frame.\n node.focus();\n }\n }\n setReference(node);\n if (innerRef) resolveRef(innerRef, node);\n },\n [innerRef, reRenderButtonToAllowFocus, setReference, shouldFocusImmediately],\n );\n\n /*\n * 13th August 2024:\n * https://www.w3.org/WAI/ARIA/apg/patterns/menu-button/\n * With focus on the button:\n * Enter: opens the menu and places focus on the first menu item.\n * Space: Opens the menu and places focus on the first menu item.\n * (Optional) Down Arrow: opens the menu and moves focus to the first menu item.\n * (Optional) Up Arrow: opens the menu and moves focus to the last menu item.\n */\n const handleTriggerKeyDown = React.useCallback<Required<DSButtonV3T.Props>['onKeyDown']>(\n (event, ...args) => {\n // DSButtonV3 triggers the onClick on Enter and Space, so we don't need to handle it here\n // if (event.key === 'Enter' || event.key === ' ') {\n // onOpinionatedOpen();\n // }\n if (event.key === 'ArrowDown') {\n onOpinionatedOpen();\n }\n if (event.key === 'ArrowUp') {\n onOpinionatedOpen({ focusLastChild: true });\n }\n if (event.key === 'Escape') {\n onOpinionatedClose();\n }\n if (onKeyDown) onKeyDown(event, ...args);\n },\n [onKeyDown, onOpinionatedOpen, onOpinionatedClose],\n );\n\n return React.useMemo(\n () => ({ handleTriggerKeyDown, handleOnClick, handleOnFocus, handleFocusIfTriggerIsFocusRegion }),\n [handleFocusIfTriggerIsFocusRegion, handleOnClick, handleOnFocus, handleTriggerKeyDown],\n );\n};\n"],
5
+ "mappings": "AAAA,YAAY,WAAW;ACEvB,OAAOA,YAAW;AAClB,SAAS,kBAAkB;AAC3B,SAAS,6CAA6C;AACtD,SAAS,0BAA0B;AAQ5B,MAAM,2BAA2B,CAAC,EAAE,kBAAkB,gBAAgB,MAAsC;AAKjH,QAAM,EAAE,aAAa,YAAY,mBAAmB,oBAAoB,mBAAmB,uBAAuB,IAChHA,OAAM,WAAW,qCAAqC;AACxD,QAAM,EAAE,SAAS,SAAS,UAAU,UAAU,IAAI;AAClD,QAAM;AAAA,IACJ,MAAM,EAAE,aAAa;AAAA,EACvB,IAAI;AAEJ,QAAM,iCAAiCA,OAAM,OAAO,WAAW;AAI/D,QAAM,6BACJ,gBAAgB,mBAAmB,WAAW,+BAA+B,YAAY;AAC3F,iCAA+B,UAAU;AAEzC,QAAM,oBAAoBA,OAAM,OAAO,UAAU;AAIjD,oBAAkB,UAAU;AAE5B,QAAM,gBAAgBA,OAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAG/B,UAAI,kBAAkB,SAAS;AAC7B,2BAAmB;AAAA,MACrB,OAAO;AACL,0BAAkB;AAAA,MACpB;AAAA,IACF;AAAA,IACA,CAAC,SAAS,oBAAoB,iBAAiB;AAAA,EACjD;AACA,QAAM,gBAAgBA,OAAM;AAAA,IAC1B,CAAC,MAAM,SAAS;AACd,UAAI,+BAA+B,YAAY,mBAAmB,QAAS,mBAAkB;AAC7F,UAAI,QAAS,SAAQ,GAAG,GAAG,IAAI;AAAA,IACjC;AAAA,IACA,CAAC,SAAS,iBAAiB;AAAA,EAC7B;AAEA,QAAM,oCAAoCA,OAAM;AAAA,IAC9C,CAAC,SAAS;AACR,UAAI,8BAA8B,MAAM;AAStC,YAAI,CAAC,uBAAuB,SAAS;AACnC,gCAAsB,MAAM;AAC1B,iBAAK,MAAM;AAAA,UACb,CAAC;AAAA,QACH,OAAO;AAIL,eAAK,MAAM;AAAA,QACb;AAAA,MACF;AACA,mBAAa,IAAI;AACjB,UAAI,SAAU,YAAW,UAAU,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,UAAU,4BAA4B,cAAc,sBAAsB;AAAA,EAC7E;AAWA,QAAM,uBAAuBA,OAAM;AAAA,IACjC,CAAC,UAAU,SAAS;AAKlB,UAAI,MAAM,QAAQ,aAAa;AAC7B,0BAAkB;AAAA,MACpB;AACA,UAAI,MAAM,QAAQ,WAAW;AAC3B,0BAAkB,EAAE,gBAAgB,KAAK,CAAC;AAAA,MAC5C;AACA,UAAI,MAAM,QAAQ,UAAU;AAC1B,2BAAmB;AAAA,MACrB;AACA,UAAI,UAAW,WAAU,OAAO,GAAG,IAAI;AAAA,IACzC;AAAA,IACA,CAAC,WAAW,mBAAmB,kBAAkB;AAAA,EACnD;AAEA,SAAOA,OAAM;AAAA,IACX,OAAO,EAAE,sBAAsB,eAAe,eAAe,kCAAkC;AAAA,IAC/F,CAAC,mCAAmC,eAAe,eAAe,oBAAoB;AAAA,EACxF;AACF;",
6
6
  "names": ["React"]
7
7
  }
@@ -4,7 +4,10 @@ export declare const useFocusTracker: () => {
4
4
  preventBlurReset: React.MutableRefObject<boolean>;
5
5
  focusRegion: "" | "trigger" | `item-dsid-${string}` | `item-dsid-${number}`;
6
6
  focusedElementItemNode: React.MutableRefObject<DSMenuButtonT.PseudoFocusableMenuNodes | null>;
7
- trackFocusTrigger: () => DSMenuButtonT.PseudoFocusableMenuNodes | null;
7
+ shouldFocusImmediately: React.MutableRefObject<boolean>;
8
+ trackFocusTrigger: (options?: {
9
+ shouldFocusImmediately: boolean;
10
+ }) => DSMenuButtonT.PseudoFocusableMenuNodes | null;
8
11
  trackFocusNode: (nodeToFocus: DSMenuButtonT.PseudoFocusableMenuNodes) => void;
9
12
  trackFocusRegionReset: () => DSMenuButtonT.PseudoFocusableMenuNodes | null;
10
13
  trackFocusFirstChildItem: (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => DSMenuButtonT.PseudoFocusableMenuNodes | null;
@@ -0,0 +1,2 @@
1
+ export const DSMenuButtonControlledTestRenderer: React.FunctionComponent<React.JSX.IntrinsicAttributes>;
2
+ import React from 'react';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elliemae/ds-menu-button",
3
- "version": "3.57.0-next.26",
3
+ "version": "3.57.0-next.27",
4
4
  "license": "MIT",
5
5
  "description": "ICE MT - Dimsum - Menu Button",
6
6
  "files": [
@@ -36,23 +36,23 @@
36
36
  "indent": 4
37
37
  },
38
38
  "dependencies": {
39
- "@elliemae/ds-button-v2": "3.57.0-next.26",
40
- "@elliemae/ds-floating-context": "3.57.0-next.26",
41
- "@elliemae/ds-hooks-on-blur-out": "3.57.0-next.26",
42
- "@elliemae/ds-grid": "3.57.0-next.26",
43
- "@elliemae/ds-props-helpers": "3.57.0-next.26",
44
- "@elliemae/ds-menu-items-commons": "3.57.0-next.26",
45
- "@elliemae/ds-tree-model": "3.57.0-next.26",
46
- "@elliemae/ds-system": "3.57.0-next.26",
47
- "@elliemae/ds-icons": "3.57.0-next.26"
39
+ "@elliemae/ds-button-v2": "3.57.0-next.27",
40
+ "@elliemae/ds-hooks-on-blur-out": "3.57.0-next.27",
41
+ "@elliemae/ds-menu-items-commons": "3.57.0-next.27",
42
+ "@elliemae/ds-grid": "3.57.0-next.27",
43
+ "@elliemae/ds-system": "3.57.0-next.27",
44
+ "@elliemae/ds-floating-context": "3.57.0-next.27",
45
+ "@elliemae/ds-icons": "3.57.0-next.27",
46
+ "@elliemae/ds-tree-model": "3.57.0-next.27",
47
+ "@elliemae/ds-props-helpers": "3.57.0-next.27"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@elliemae/pui-cli": "9.0.0-next.65",
51
51
  "@elliemae/pui-theme": "~2.13.0",
52
52
  "jest": "~29.7.0",
53
53
  "styled-components": "~5.3.9",
54
- "@elliemae/ds-typescript-helpers": "3.57.0-next.26",
55
- "@elliemae/ds-monorepo-devops": "3.57.0-next.26"
54
+ "@elliemae/ds-monorepo-devops": "3.57.0-next.27",
55
+ "@elliemae/ds-typescript-helpers": "3.57.0-next.27"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "@testing-library/jest-dom": "^6.6.3",
@@ -68,7 +68,7 @@
68
68
  },
69
69
  "scripts": {
70
70
  "dev": "cross-env NODE_ENV=development node ../../../scripts/build/build.mjs --watch",
71
- "test": "pui-cli test --passWithNoTests --coverage=\"false\"",
71
+ "test": "[ -n \"$CI\" ] || playwright test -c ./playwright.config.mjs && pui-cli test --passWithNoTests --coverage=\"false\"",
72
72
  "lint": "node ../../../scripts/lint.mjs --fix",
73
73
  "lint:strict": "node ../../../scripts/lint-strict.mjs",
74
74
  "dts": "node ../../../scripts/dts.mjs",