@elliemae/ds-menu-button 3.46.0-rc.0 → 3.46.0-rc.10
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/cjs/config/useMenuButton.js +10 -19
- package/dist/cjs/config/useMenuButton.js.map +2 -2
- package/dist/cjs/config/useSplitInherithedProps.js +33 -15
- package/dist/cjs/config/useSplitInherithedProps.js.map +2 -2
- package/dist/cjs/index.js +2 -0
- package/dist/cjs/index.js.map +2 -2
- package/dist/cjs/parts/DSFlyoutMenu/DSFlyoutMenu.js +36 -12
- package/dist/cjs/parts/DSFlyoutMenu/DSFlyoutMenu.js.map +3 -3
- package/dist/cjs/parts/DSFlyoutMenu/react-desc-prop-types.js +1 -1
- package/dist/cjs/parts/DSFlyoutMenu/react-desc-prop-types.js.map +3 -3
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useAdvancedValidation.js +86 -0
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useAdvancedValidation.js.map +7 -0
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js +40 -30
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js +2 -0
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js +77 -33
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/react-desc-prop-types.js +3 -0
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/react-desc-prop-types.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/utils/multipleSelectionHelpers.js +14 -14
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/utils/multipleSelectionHelpers.js.map +1 -1
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/utils/singleSelectionHelpers.js +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/utils/singleSelectionHelpers.js.map +1 -1
- package/dist/cjs/parts/DSMenuItemRendererFactory/ActivableMenuItem.js +13 -9
- package/dist/cjs/parts/DSMenuItemRendererFactory/ActivableMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js +13 -9
- package/dist/cjs/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/DSMenuItemRendererFactory.js +10 -9
- package/dist/cjs/parts/DSMenuItemRendererFactory/DSMenuItemRendererFactory.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js +15 -11
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js +18 -12
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js +15 -11
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js +15 -11
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/SkeletonMenuItem.js +112 -0
- package/dist/cjs/parts/DSMenuItemRendererFactory/SkeletonMenuItem.js.map +7 -0
- package/dist/cjs/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js +13 -9
- package/dist/cjs/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/config/useMenuItemRendererFactory.js +1 -1
- package/dist/cjs/parts/DSMenuItemRendererFactory/config/useMenuItemRendererFactory.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/focusNodeRacingConditionSolved.js +43 -0
- package/dist/cjs/parts/DSMenuItemRendererFactory/focusNodeRacingConditionSolved.js.map +7 -0
- package/dist/cjs/parts/DSMenuItemRendererFactory/react-desc-prop-types.js.map +2 -2
- package/dist/cjs/parts/DSOpinionatedButton/react-desc-prop-types.js.map +2 -2
- package/dist/cjs/react-desc-prop-types.js +16 -15
- package/dist/cjs/react-desc-prop-types.js.map +2 -2
- package/dist/cjs/utils/nodesTypeguardsAndGetters.js +18 -56
- package/dist/cjs/utils/nodesTypeguardsAndGetters.js.map +2 -2
- package/dist/cjs/utils/useOptionsArrayToDsTree.js +10 -3
- package/dist/cjs/utils/useOptionsArrayToDsTree.js.map +2 -2
- package/dist/esm/config/useMenuButton.js +11 -20
- package/dist/esm/config/useMenuButton.js.map +2 -2
- package/dist/esm/config/useSplitInherithedProps.js +33 -15
- package/dist/esm/config/useSplitInherithedProps.js.map +2 -2
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +2 -2
- package/dist/esm/parts/DSFlyoutMenu/DSFlyoutMenu.js +34 -10
- package/dist/esm/parts/DSFlyoutMenu/DSFlyoutMenu.js.map +2 -2
- package/dist/esm/parts/DSFlyoutMenu/react-desc-prop-types.js +1 -4
- package/dist/esm/parts/DSFlyoutMenu/react-desc-prop-types.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useAdvancedValidation.js +56 -0
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useAdvancedValidation.js.map +7 -0
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js +40 -30
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js +2 -0
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js +77 -33
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/react-desc-prop-types.js +3 -0
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/react-desc-prop-types.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/utils/multipleSelectionHelpers.js +14 -14
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/utils/multipleSelectionHelpers.js.map +1 -1
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/utils/singleSelectionHelpers.js +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/utils/singleSelectionHelpers.js.map +1 -1
- package/dist/esm/parts/DSMenuItemRendererFactory/ActivableMenuItem.js +13 -9
- package/dist/esm/parts/DSMenuItemRendererFactory/ActivableMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js +13 -9
- package/dist/esm/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/DSMenuItemRendererFactory.js +19 -18
- package/dist/esm/parts/DSMenuItemRendererFactory/DSMenuItemRendererFactory.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js +15 -11
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js +18 -12
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js +15 -11
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js +15 -11
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/SkeletonMenuItem.js +82 -0
- package/dist/esm/parts/DSMenuItemRendererFactory/SkeletonMenuItem.js.map +7 -0
- package/dist/esm/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js +13 -9
- package/dist/esm/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/config/useMenuItemRendererFactory.js +1 -1
- package/dist/esm/parts/DSMenuItemRendererFactory/config/useMenuItemRendererFactory.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/focusNodeRacingConditionSolved.js +13 -0
- package/dist/esm/parts/DSMenuItemRendererFactory/focusNodeRacingConditionSolved.js.map +7 -0
- package/dist/esm/parts/DSMenuItemRendererFactory/react-desc-prop-types.js.map +2 -2
- package/dist/esm/parts/DSOpinionatedButton/react-desc-prop-types.js +1 -1
- package/dist/esm/parts/DSOpinionatedButton/react-desc-prop-types.js.map +2 -2
- package/dist/esm/react-desc-prop-types.js +16 -15
- package/dist/esm/react-desc-prop-types.js.map +2 -2
- package/dist/esm/utils/nodesTypeguardsAndGetters.js +18 -56
- package/dist/esm/utils/nodesTypeguardsAndGetters.js.map +2 -2
- package/dist/esm/utils/useOptionsArrayToDsTree.js +10 -3
- package/dist/esm/utils/useOptionsArrayToDsTree.js.map +2 -2
- package/dist/types/config/useSplitInherithedProps.d.ts +8 -9
- package/dist/types/index.d.ts +1 -1
- package/dist/types/parts/DSFlyoutMenu/react-desc-prop-types.d.ts +1 -1
- package/dist/types/parts/DSMenuBehaviouralContextProvider/config/useAdvancedValidation.d.ts +6 -0
- package/dist/types/parts/DSMenuBehaviouralContextProvider/config/useFocusTracker.d.ts +6 -6
- package/dist/types/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.d.ts +2 -1
- package/dist/types/parts/DSMenuBehaviouralContextProvider/react-desc-prop-types.d.ts +1 -0
- package/dist/types/parts/DSMenuBehaviouralContextProvider/utils/multipleSelectionHelpers.d.ts +3 -3
- package/dist/types/parts/DSMenuBehaviouralContextProvider/utils/singleSelectionHelpers.d.ts +2 -2
- package/dist/types/parts/DSMenuItemRendererFactory/SkeletonMenuItem.d.ts +5 -0
- package/dist/types/parts/DSMenuItemRendererFactory/focusNodeRacingConditionSolved.d.ts +2 -0
- package/dist/types/parts/DSMenuItemRendererFactory/react-desc-prop-types.d.ts +2 -2
- package/dist/types/parts/DSOpinionatedButton/config/useOpinionatedButton.d.ts +1 -1
- package/dist/types/parts/DSOpinionatedButton/react-desc-prop-types.d.ts +1 -1
- package/dist/types/react-desc-prop-types.d.ts +23 -17
- package/dist/types/utils/nodesTypeguardsAndGetters.d.ts +113 -1
- package/dist/types/utils/useOptionsArrayToDsTree.d.ts +2 -1
- package/package.json +12 -13
@@ -32,6 +32,7 @@ __export(useFocusTracker_exports, {
|
|
32
32
|
});
|
33
33
|
module.exports = __toCommonJS(useFocusTracker_exports);
|
34
34
|
var React = __toESM(require("react"));
|
35
|
+
var import_lodash = require("lodash");
|
35
36
|
var import_react = __toESM(require("react"));
|
36
37
|
var import_nodesTypeguardsAndGetters = require("../../../utils/nodesTypeguardsAndGetters.js");
|
37
38
|
var import_Errors = require("../constants/Errors.js");
|
@@ -44,24 +45,34 @@ const useFocusTracker = () => {
|
|
44
45
|
const focusedElementItemNode = import_react.default.useRef(
|
45
46
|
null
|
46
47
|
);
|
47
|
-
const
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
48
|
+
const racingConditionDebounceTrackFocus = import_react.default.useCallback(
|
49
|
+
(0, import_lodash.throttle)(
|
50
|
+
(newFocusRegion, focusNode) => {
|
51
|
+
setFocusRegion(newFocusRegion);
|
52
|
+
focusedRegionPerformanceHelper.current = newFocusRegion;
|
53
|
+
focusedElementItemNode.current = focusNode;
|
54
|
+
return focusNode;
|
55
|
+
},
|
56
|
+
50,
|
57
|
+
{ leading: true, trailing: true }
|
58
|
+
),
|
59
|
+
[]
|
60
|
+
);
|
61
|
+
const trackFocusTrigger = import_react.default.useCallback(
|
62
|
+
() => racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.TRIGGER, null),
|
63
|
+
[racingConditionDebounceTrackFocus]
|
64
|
+
);
|
65
|
+
const trackFocusNode = import_react.default.useCallback(
|
66
|
+
(nodeToFocus) => {
|
67
|
+
const newFocusRegion = import_constants.MENU_FOCUS_REGIONS.ITEM_BY_DSID(nodeToFocus.dsId);
|
68
|
+
racingConditionDebounceTrackFocus(newFocusRegion, nodeToFocus);
|
69
|
+
},
|
70
|
+
[racingConditionDebounceTrackFocus]
|
71
|
+
);
|
72
|
+
const trackFocusRegionReset = import_react.default.useCallback(
|
73
|
+
() => racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.RESET, null),
|
74
|
+
[racingConditionDebounceTrackFocus]
|
75
|
+
);
|
65
76
|
const trackFocusFirstChildItem = import_react.default.useCallback(
|
66
77
|
(itemNode) => {
|
67
78
|
if (!(0, import_nodesTypeguardsAndGetters.isMenuNodeAllowedToHaveChildren)(itemNode)) {
|
@@ -78,10 +89,9 @@ const useFocusTracker = () => {
|
|
78
89
|
throw new Error("No focusable nodes found in the children of the item node");
|
79
90
|
}
|
80
91
|
const newFocusedNode = focusableChildrenNodes[0];
|
81
|
-
|
82
|
-
return newFocusedNode;
|
92
|
+
return racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.ITEM_BY_DSID(newFocusedNode.dsId), newFocusedNode);
|
83
93
|
},
|
84
|
-
[
|
94
|
+
[racingConditionDebounceTrackFocus]
|
85
95
|
);
|
86
96
|
const trackFocusLastChildItem = import_react.default.useCallback(
|
87
97
|
(itemNode) => {
|
@@ -98,10 +108,12 @@ const useFocusTracker = () => {
|
|
98
108
|
console.log("focus last child item > itemNode", itemNode);
|
99
109
|
throw new Error("No focusable nodes found in the children of the item node");
|
100
110
|
}
|
101
|
-
|
102
|
-
|
111
|
+
return racingConditionDebounceTrackFocus(
|
112
|
+
import_constants.MENU_FOCUS_REGIONS.ITEM_BY_DSID(focusableChildrenNodes[focusableChildrenNodes.length - 1].dsId),
|
113
|
+
focusableChildrenNodes[focusableChildrenNodes.length - 1]
|
114
|
+
);
|
103
115
|
},
|
104
|
-
[
|
116
|
+
[racingConditionDebounceTrackFocus]
|
105
117
|
);
|
106
118
|
const trackFocusNextItem = import_react.default.useCallback(
|
107
119
|
(itemNode) => {
|
@@ -109,10 +121,9 @@ const useFocusTracker = () => {
|
|
109
121
|
const currIndex = focusableSiblingsNodes.findIndex((node) => node.dsId === itemNode.dsId);
|
110
122
|
const nextIndex = currIndex + 1 < focusableSiblingsNodes.length ? currIndex + 1 : 0;
|
111
123
|
const newFocusedNode = focusableSiblingsNodes[nextIndex];
|
112
|
-
|
113
|
-
return newFocusedNode;
|
124
|
+
return racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.ITEM_BY_DSID(newFocusedNode.dsId), newFocusedNode);
|
114
125
|
},
|
115
|
-
[
|
126
|
+
[racingConditionDebounceTrackFocus]
|
116
127
|
);
|
117
128
|
const trackFocusPreviousItem = import_react.default.useCallback(
|
118
129
|
(itemNode) => {
|
@@ -120,10 +131,9 @@ const useFocusTracker = () => {
|
|
120
131
|
const currIndex = focusableSiblingsNodes.findIndex((node) => node.dsId === itemNode.dsId);
|
121
132
|
const previousIndex = currIndex - 1 >= 0 ? currIndex - 1 : focusableSiblingsNodes.length - 1;
|
122
133
|
const newFocusNode = focusableSiblingsNodes[previousIndex];
|
123
|
-
|
124
|
-
return newFocusNode;
|
134
|
+
return racingConditionDebounceTrackFocus(import_constants.MENU_FOCUS_REGIONS.ITEM_BY_DSID(newFocusNode.dsId), newFocusNode);
|
125
135
|
},
|
126
|
-
[
|
136
|
+
[racingConditionDebounceTrackFocus]
|
127
137
|
);
|
128
138
|
const trackFocusParentItem = import_react.default.useCallback(
|
129
139
|
(itemNode) => {
|
@@ -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 */\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 const
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADIvB,mBAAkB;AAElB,uCAKO;AACP,oBAAsC;AACtC,uBAAmC;AACnC,oCAAyC;AAWlC,MAAM,kBAAkB,MAAM;AACnC,QAAM,CAAC,aAAa,cAAc,IAAI,aAAAA,QAAM,SAA6B,EAAE;AAI3E,QAAM,iCAAiC,aAAAA,QAAM,OAAO,EAAE;AACtD,QAAM,mBAAmB,aAAAA,QAAM,OAAO,KAAK;AAE3C,QAAM,yBAAyB,aAAAA,QAAM;AAAA,IACnC;AAAA,EACF;
|
4
|
+
"sourcesContent": ["/* eslint-disable no-console */\n/* eslint-disable max-lines */\n/* eslint-disable max-statements */\n/* eslint-disable complexity */\nimport { throttle } from 'lodash';\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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",
|
6
6
|
"names": ["React"]
|
7
7
|
}
|
package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js
CHANGED
@@ -35,6 +35,7 @@ var React = __toESM(require("react"));
|
|
35
35
|
var import_react = __toESM(require("react"));
|
36
36
|
var import_uid = require("uid");
|
37
37
|
var import_react_desc_prop_types = require("../react-desc-prop-types.js");
|
38
|
+
var import_useAdvancedValidation = require("./useAdvancedValidation.js");
|
38
39
|
var import_useFocusTracker = require("./useFocusTracker.js");
|
39
40
|
var import_useGlobalEvents = require("./useGlobalEvents.js");
|
40
41
|
var import_useMenuItemEventsHandlers = require("./useMenuItemEventsHandlers.js");
|
@@ -49,6 +50,7 @@ const useMenuBehaviouralContextProvider = (propsFromUser) => {
|
|
49
50
|
[propsFromUser]
|
50
51
|
);
|
51
52
|
(0, import_useValidateProps.useValidateProps)(propsWithDefault, import_react_desc_prop_types.DSMenuBehaviouralContextProviderPropTypesSchema);
|
53
|
+
(0, import_useAdvancedValidation.useAdvancedValidation)({ propsWithDefault });
|
52
54
|
const { onDisplayedSubmenuChange } = propsWithDefault;
|
53
55
|
const instanceUid = import_react.default.useMemo(() => `ds-menu-behavioural-context-provider-${(0, import_uid.uid)(5)}`, []);
|
54
56
|
const [openedSubItems, setOpenedSubItems] = import_react.default.useState([]);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
3
|
"sources": ["../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.ts", "../../../../../../../../scripts/build/transpile/react-shim.js"],
|
4
|
-
"sourcesContent": ["import React from 'react';\nimport { uid } from 'uid';\nimport type { DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n DSMenuBehaviouralContextProviderPropTypesSchema,\n defaultProps,\n type DSMenuBehaviouralContextProviderT,\n} from '../react-desc-prop-types.js';\nimport { useFocusTracker } from './useFocusTracker.js';\nimport { useGlobalEvents } from './useGlobalEvents.js';\nimport { useMenuItemEventsHandlers } from './useMenuItemEventsHandlers.js';\nimport { useMenuOpenStatus } from './useMenuOpenStatus.js';\nimport { useValidateProps } from './useValidateProps.js';\n\nexport interface MenuBehaviouralContextProviderCTX\n extends ReturnType<typeof useFocusTracker>,\n ReturnType<typeof useMenuOpenStatus> {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n instanceUid: string;\n menuItemEventsHandlers: ReturnType<typeof useMenuItemEventsHandlers>;\n openedSubItems: DSMenuButtonT.WithSubmenuMenuNodes[];\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n globalEventsHelpers: ReturnType<typeof useGlobalEvents>;\n}\n\nexport const useMenuBehaviouralContextProvider = (propsFromUser: DSMenuBehaviouralContextProviderT.Props) => {\n // =============================================================================\n // MERGE WITH DEFAULT AND VALIDATE PROPS\n // =============================================================================\n // the component deals with HTML DOM elements,\n // Deep compare of HTML DOM elements is pointless and extremely costly\n // for this specific case, we are better of not using `useMemoMergePropsWithDefault`\n const propsWithDefault = React.useMemo(\n () => ({\n ...defaultProps,\n ...propsFromUser,\n }),\n [propsFromUser],\n ) as DSMenuBehaviouralContextProviderT.InternalProps;\n useValidateProps(propsWithDefault, DSMenuBehaviouralContextProviderPropTypesSchema);\n // =============================================================================\n // XSTYLED PROPS\n // =============================================================================\n // NO XSTYLED PROPS\n // this is 100% a pure logic context provider, it's not a visual component\n\n // =============================================================================\n // AD HOC PER COMPONENT LOGIC\n // =============================================================================\n // custom code goes here, this is an example\n const { onDisplayedSubmenuChange } = propsWithDefault;\n const instanceUid = React.useMemo(() => `ds-menu-behavioural-context-provider-${uid(5)}`, []);\n const [openedSubItems, setOpenedSubItems] = React.useState<DSMenuButtonT.WithSubmenuMenuNodes[]>([]);\n\n const handleChangeOpenedSubItems = React.useCallback<\n Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange']\n >(\n (newOpenedItems, metainfo) => {\n setOpenedSubItems(newOpenedItems);\n onDisplayedSubmenuChange?.(newOpenedItems, metainfo);\n },\n [onDisplayedSubmenuChange],\n );\n\n // =============================================================================\n // HELPERS HOOKS CONFIGS\n // =============================================================================\n const focusTrackers = useFocusTracker();\n const menuOpenStatus = useMenuOpenStatus({ propsWithDefault, focusTrackers });\n\n // why item events handlers declared in the menu behaviour context provider and not in the menu item itself?\n // 1 - each menu item depending on it's own type (single select, multiple select, with submenu) will have different behaviours\n // having each menu item to declare it's own events handlers would make the single event handler way easier to create per se\n // BUT we would lose the big picture of how the menu items are interacting with each other\n // 2 - having each item to declare it's own event handler repeatdly is a waste of resources (even if it's a useless micro-optimization, it hurts me)\n const menuItemEventsHandlers = useMenuItemEventsHandlers({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n });\n\n // handle \"onClickOutside\" + on window blur...\n // the helpers contain a way to register the submenu refs/mainmenu refs, used in checking \"if click is outside\"\n const globalEventsHelpers = useGlobalEvents({ menuOpenStatus, handleChangeOpenedSubItems, propsWithDefault });\n\n return React.useMemo<MenuBehaviouralContextProviderCTX>(\n () => ({\n propsWithDefault,\n instanceUid,\n ...focusTrackers,\n ...menuOpenStatus,\n menuItemEventsHandlers,\n openedSubItems,\n handleChangeOpenedSubItems,\n globalEventsHelpers,\n }),\n [\n propsWithDefault,\n instanceUid,\n focusTrackers,\n menuOpenStatus,\n menuItemEventsHandlers,\n openedSubItems,\n handleChangeOpenedSubItems,\n globalEventsHelpers,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAAkB;AAClB,iBAAoB;AAEpB,mCAIO;AACP,6BAAgC;AAChC,6BAAgC;AAChC,uCAA0C;AAC1C,+BAAkC;AAClC,8BAAiC;AAa1B,MAAM,oCAAoC,CAAC,kBAA2D;AAO3G,QAAM,mBAAmB,aAAAA,QAAM;AAAA,IAC7B,OAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAAA,IACA,CAAC,aAAa;AAAA,EAChB;AACA,gDAAiB,kBAAkB,4EAA+C;
|
4
|
+
"sourcesContent": ["import React from 'react';\nimport { uid } from 'uid';\nimport type { DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n DSMenuBehaviouralContextProviderPropTypesSchema,\n defaultProps,\n type DSMenuBehaviouralContextProviderT,\n} from '../react-desc-prop-types.js';\nimport { useAdvancedValidation } from './useAdvancedValidation.js';\nimport { useFocusTracker } from './useFocusTracker.js';\nimport { useGlobalEvents } from './useGlobalEvents.js';\nimport { useMenuItemEventsHandlers } from './useMenuItemEventsHandlers.js';\nimport { useMenuOpenStatus } from './useMenuOpenStatus.js';\nimport { useValidateProps } from './useValidateProps.js';\n\nexport interface MenuBehaviouralContextProviderCTX\n extends ReturnType<typeof useFocusTracker>,\n ReturnType<typeof useMenuOpenStatus> {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n instanceUid: string;\n menuItemEventsHandlers: ReturnType<typeof useMenuItemEventsHandlers>;\n openedSubItems: DSMenuButtonT.WithSubmenuMenuNodes[];\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n globalEventsHelpers: ReturnType<typeof useGlobalEvents>;\n}\n\nexport const useMenuBehaviouralContextProvider = (propsFromUser: DSMenuBehaviouralContextProviderT.Props) => {\n // =============================================================================\n // MERGE WITH DEFAULT AND VALIDATE PROPS\n // =============================================================================\n // the component deals with HTML DOM elements,\n // Deep compare of HTML DOM elements is pointless and extremely costly\n // for this specific case, we are better of not using `useMemoMergePropsWithDefault`\n const propsWithDefault = React.useMemo(\n () => ({\n ...defaultProps,\n ...propsFromUser,\n }),\n [propsFromUser],\n ) as DSMenuBehaviouralContextProviderT.InternalProps;\n useValidateProps(propsWithDefault, DSMenuBehaviouralContextProviderPropTypesSchema);\n\n // =============================================================================\n // SPECIAL VALIDATIONS\n // =============================================================================\n useAdvancedValidation({ propsWithDefault });\n\n // =============================================================================\n // XSTYLED PROPS\n // =============================================================================\n // NO XSTYLED PROPS\n // this is 100% a pure logic context provider, it's not a visual component\n\n // =============================================================================\n // AD HOC PER COMPONENT LOGIC\n // =============================================================================\n // custom code goes here, this is an example\n const { onDisplayedSubmenuChange } = propsWithDefault;\n const instanceUid = React.useMemo(() => `ds-menu-behavioural-context-provider-${uid(5)}`, []);\n const [openedSubItems, setOpenedSubItems] = React.useState<DSMenuButtonT.WithSubmenuMenuNodes[]>([]);\n\n const handleChangeOpenedSubItems = React.useCallback<\n Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange']\n >(\n (newOpenedItems, metainfo) => {\n setOpenedSubItems(newOpenedItems);\n onDisplayedSubmenuChange?.(newOpenedItems, metainfo);\n },\n [onDisplayedSubmenuChange],\n );\n\n // =============================================================================\n // HELPERS HOOKS CONFIGS\n // =============================================================================\n const focusTrackers = useFocusTracker();\n const menuOpenStatus = useMenuOpenStatus({ propsWithDefault, focusTrackers });\n\n // why item events handlers declared in the menu behaviour context provider and not in the menu item itself?\n // 1 - each menu item depending on it's own type (single select, multiple select, with submenu) will have different behaviours\n // having each menu item to declare it's own events handlers would make the single event handler way easier to create per se\n // BUT we would lose the big picture of how the menu items are interacting with each other\n // 2 - having each item to declare it's own event handler repeatdly is a waste of resources (even if it's a useless micro-optimization, it hurts me)\n const menuItemEventsHandlers = useMenuItemEventsHandlers({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n });\n\n // handle \"onClickOutside\" + on window blur...\n // the helpers contain a way to register the submenu refs/mainmenu refs, used in checking \"if click is outside\"\n const globalEventsHelpers = useGlobalEvents({ menuOpenStatus, handleChangeOpenedSubItems, propsWithDefault });\n\n return React.useMemo<MenuBehaviouralContextProviderCTX>(\n () => ({\n propsWithDefault,\n instanceUid,\n ...focusTrackers,\n ...menuOpenStatus,\n menuItemEventsHandlers,\n openedSubItems,\n handleChangeOpenedSubItems,\n globalEventsHelpers,\n }),\n [\n propsWithDefault,\n instanceUid,\n focusTrackers,\n menuOpenStatus,\n menuItemEventsHandlers,\n openedSubItems,\n handleChangeOpenedSubItems,\n globalEventsHelpers,\n ],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADAvB,mBAAkB;AAClB,iBAAoB;AAEpB,mCAIO;AACP,mCAAsC;AACtC,6BAAgC;AAChC,6BAAgC;AAChC,uCAA0C;AAC1C,+BAAkC;AAClC,8BAAiC;AAa1B,MAAM,oCAAoC,CAAC,kBAA2D;AAO3G,QAAM,mBAAmB,aAAAA,QAAM;AAAA,IAC7B,OAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAAA,IACA,CAAC,aAAa;AAAA,EAChB;AACA,gDAAiB,kBAAkB,4EAA+C;AAKlF,0DAAsB,EAAE,iBAAiB,CAAC;AAY1C,QAAM,EAAE,yBAAyB,IAAI;AACrC,QAAM,cAAc,aAAAA,QAAM,QAAQ,MAAM,4CAAwC,gBAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5F,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,aAAAA,QAAM,SAA+C,CAAC,CAAC;AAEnG,QAAM,6BAA6B,aAAAA,QAAM;AAAA,IAGvC,CAAC,gBAAgB,aAAa;AAC5B,wBAAkB,cAAc;AAChC,iCAA2B,gBAAgB,QAAQ;AAAA,IACrD;AAAA,IACA,CAAC,wBAAwB;AAAA,EAC3B;AAKA,QAAM,oBAAgB,wCAAgB;AACtC,QAAM,qBAAiB,4CAAkB,EAAE,kBAAkB,cAAc,CAAC;AAO5E,QAAM,6BAAyB,4DAA0B;AAAA,IACvD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAID,QAAM,0BAAsB,wCAAgB,EAAE,gBAAgB,4BAA4B,iBAAiB,CAAC;AAE5G,SAAO,aAAAA,QAAM;AAAA,IACX,OAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,GAAG;AAAA,MACH,GAAG;AAAA,MACH;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,MACE;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AACF;",
|
6
6
|
"names": ["React"]
|
7
7
|
}
|
@@ -44,7 +44,7 @@ const useMenuItemEventsHandlers = ({
|
|
44
44
|
menuOpenStatus,
|
45
45
|
handleChangeOpenedSubItems
|
46
46
|
}) => {
|
47
|
-
const { onItemSelected, onActivateItem,
|
47
|
+
const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;
|
48
48
|
const { onOpinionatedClose } = menuOpenStatus;
|
49
49
|
const {
|
50
50
|
trackFocusFirstChildItem,
|
@@ -71,6 +71,18 @@ const useMenuItemEventsHandlers = ({
|
|
71
71
|
},
|
72
72
|
[handleChangeOpenedSubItems]
|
73
73
|
);
|
74
|
+
const closeSubmenusAfterNode = import_react.default.useCallback(
|
75
|
+
({
|
76
|
+
itemNode,
|
77
|
+
event
|
78
|
+
}) => {
|
79
|
+
const subMenuNodesPath = (0, import_nodeGettersByCriterias.getSubMenusPath)(itemNode);
|
80
|
+
const metainfo = { itemNode, event };
|
81
|
+
const newOpenedItems = [...subMenuNodesPath];
|
82
|
+
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
83
|
+
},
|
84
|
+
[handleChangeOpenedSubItems]
|
85
|
+
);
|
74
86
|
const closeCurrentSubmenu = import_react.default.useCallback(
|
75
87
|
({
|
76
88
|
itemNode,
|
@@ -95,10 +107,10 @@ const useMenuItemEventsHandlers = ({
|
|
95
107
|
trackFocusFirstChildItem(pseudoFocusedItemNode);
|
96
108
|
} else {
|
97
109
|
if ((0, import_nodesTypeguardsAndGetters.isMultipleSelectNode)(pseudoFocusedItemNode)) {
|
98
|
-
const newSelection = (0, import_multipleSelectionHelpers.getNewSelectionMultipleSelect)(
|
110
|
+
const newSelection = (0, import_multipleSelectionHelpers.getNewSelectionMultipleSelect)(selectedNodes, pseudoFocusedItemNode);
|
99
111
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
100
112
|
} else if ((0, import_nodesTypeguardsAndGetters.isSingleSelectNode)(pseudoFocusedItemNode)) {
|
101
|
-
const newSelection = (0, import_singleSelectionHelpers.getNewSelectionSingleSelect)(
|
113
|
+
const newSelection = (0, import_singleSelectionHelpers.getNewSelectionSingleSelect)(selectedNodes, pseudoFocusedItemNode);
|
102
114
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
103
115
|
} else {
|
104
116
|
onActivateItem(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
@@ -114,7 +126,7 @@ const useMenuItemEventsHandlers = ({
|
|
114
126
|
openSubmenu,
|
115
127
|
handleChangeOpenedSubItems,
|
116
128
|
onOpinionatedClose,
|
117
|
-
|
129
|
+
selectedNodes,
|
118
130
|
onItemSelected,
|
119
131
|
onActivateItem
|
120
132
|
]
|
@@ -127,16 +139,16 @@ const useMenuItemEventsHandlers = ({
|
|
127
139
|
return;
|
128
140
|
}
|
129
141
|
if ((0, import_nodesTypeguardsAndGetters.isMultipleSelectNode)(pseudoFocusedItemNode)) {
|
130
|
-
const newSelection = (0, import_multipleSelectionHelpers.getNewSelectionMultipleSelect)(
|
142
|
+
const newSelection = (0, import_multipleSelectionHelpers.getNewSelectionMultipleSelect)(selectedNodes, pseudoFocusedItemNode);
|
131
143
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
132
144
|
return;
|
133
145
|
}
|
134
146
|
if ((0, import_nodesTypeguardsAndGetters.isSingleSelectNode)(pseudoFocusedItemNode)) {
|
135
|
-
const newSelection = (0, import_singleSelectionHelpers.getNewSelectionSingleSelect)(
|
147
|
+
const newSelection = (0, import_singleSelectionHelpers.getNewSelectionSingleSelect)(selectedNodes, pseudoFocusedItemNode);
|
136
148
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
137
149
|
return;
|
138
150
|
}
|
139
|
-
onActivateItem(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
151
|
+
onActivateItem?.(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
140
152
|
const metainfo = { itemNode: pseudoFocusedItemNode, event };
|
141
153
|
const newOpenedItems = [];
|
142
154
|
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
@@ -148,7 +160,7 @@ const useMenuItemEventsHandlers = ({
|
|
148
160
|
onOpinionatedClose,
|
149
161
|
trackFocusFirstChildItem,
|
150
162
|
openSubmenu,
|
151
|
-
|
163
|
+
selectedNodes,
|
152
164
|
onItemSelected
|
153
165
|
]
|
154
166
|
);
|
@@ -209,6 +221,10 @@ const useMenuItemEventsHandlers = ({
|
|
209
221
|
return;
|
210
222
|
}
|
211
223
|
if (event.key === "Escape") {
|
224
|
+
if (event.altKey && (0, import_nodesTypeguardsAndGetters.isWithSubmenuNode)(pseudoFocusedItemNode)) {
|
225
|
+
closeSubmenusAfterNode({ itemNode: pseudoFocusedItemNode, event });
|
226
|
+
return;
|
227
|
+
}
|
212
228
|
const { currentSubmenuWasMainMenu } = closeCurrentSubmenu({ itemNode: pseudoFocusedItemNode, event });
|
213
229
|
trackFocusParentItem(pseudoFocusedItemNode);
|
214
230
|
if (currentSubmenuWasMainMenu) {
|
@@ -234,38 +250,20 @@ const useMenuItemEventsHandlers = ({
|
|
234
250
|
},
|
235
251
|
[
|
236
252
|
focusedElementItemNode,
|
237
|
-
handleMenuItemEnterKeyDown,
|
238
|
-
handleMenuItemSpaceKeyDown,
|
239
253
|
trackFocusNextItem,
|
240
254
|
trackFocusPreviousItem,
|
241
|
-
handleMenuItemRightArrowKeyDown,
|
242
255
|
handleMenuItemLeftArrowKeyDown,
|
243
256
|
trackFocusFirstChildItem,
|
244
257
|
trackFocusLastChildItem,
|
245
258
|
closeCurrentSubmenu,
|
246
259
|
trackFocusParentItem,
|
247
|
-
|
260
|
+
closeSubmenusAfterNode,
|
261
|
+
onOpinionatedClose,
|
262
|
+
handleMenuItemEnterKeyDown,
|
263
|
+
handleMenuItemSpaceKeyDown,
|
264
|
+
handleMenuItemRightArrowKeyDown
|
248
265
|
]
|
249
266
|
);
|
250
|
-
const handleFocusableMenuItemOnMouseEnter = import_react.default.useCallback(
|
251
|
-
(itemNode, event) => {
|
252
|
-
if (!(0, import_nodesTypeguardsAndGetters.isFocusableNode)(itemNode)) {
|
253
|
-
console.log("handleFocusableMenuItemOnMouseEnter > itemNode:", itemNode);
|
254
|
-
throw import_Errors.FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;
|
255
|
-
}
|
256
|
-
trackFocusNode(itemNode);
|
257
|
-
if (!(0, import_nodesTypeguardsAndGetters.isWithSubmenuNode)(itemNode)) {
|
258
|
-
const subMenuNodesPath = (0, import_nodeGettersByCriterias.getSubMenusPath)(itemNode);
|
259
|
-
const metainfo = { itemNode, event };
|
260
|
-
const newOpenedItems = [...subMenuNodesPath];
|
261
|
-
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
262
|
-
return;
|
263
|
-
}
|
264
|
-
if (itemNode.plainItem.disabled) return;
|
265
|
-
openSubmenu({ itemNode, event });
|
266
|
-
},
|
267
|
-
[handleChangeOpenedSubItems, openSubmenu, trackFocusNode]
|
268
|
-
);
|
269
267
|
const handleFocusableMenuItemClick = import_react.default.useCallback(
|
270
268
|
(event) => {
|
271
269
|
const pseudoFocusedItemNode = focusedElementItemNode.current;
|
@@ -289,13 +287,59 @@ const useMenuItemEventsHandlers = ({
|
|
289
287
|
},
|
290
288
|
[focusedElementItemNode, handleMenuItemSpaceKeyDown, handleChangeOpenedSubItems]
|
291
289
|
);
|
290
|
+
const opinionatedFocusBehaviour = import_react.default.useCallback(
|
291
|
+
// we currently know only one "event" that can trigger this opinionated behaviour
|
292
|
+
// - focus => React.FocusEvent <-- triggered by ATs "focus mode" or "virtual cursor" navigation
|
293
|
+
// NOTE:
|
294
|
+
// mouseEnter => React.MouseEvent
|
295
|
+
// still ends up calling this function, but as a side effect of mouse-enter focusing the item, not as the main event
|
296
|
+
(itemNode, event) => {
|
297
|
+
if (!(0, import_nodesTypeguardsAndGetters.isFocusableNode)(itemNode)) {
|
298
|
+
console.log("handleFocusableMenuItemOnMouseEnter > itemNode:", itemNode);
|
299
|
+
throw import_Errors.FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;
|
300
|
+
}
|
301
|
+
trackFocusNode(itemNode);
|
302
|
+
if (!(0, import_nodesTypeguardsAndGetters.isWithSubmenuNode)(itemNode)) {
|
303
|
+
const subMenuNodesPath = (0, import_nodeGettersByCriterias.getSubMenusPath)(itemNode);
|
304
|
+
const metainfo = { itemNode, event };
|
305
|
+
const newOpenedItems = [...subMenuNodesPath];
|
306
|
+
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
307
|
+
return;
|
308
|
+
}
|
309
|
+
if (itemNode.plainItem.disabled) return;
|
310
|
+
openSubmenu({ itemNode, event });
|
311
|
+
},
|
312
|
+
[handleChangeOpenedSubItems, openSubmenu, trackFocusNode]
|
313
|
+
);
|
314
|
+
const handleFocusableMenuItemOnMouseEnter = import_react.default.useCallback(
|
315
|
+
(itemNode) => {
|
316
|
+
if (!(0, import_nodesTypeguardsAndGetters.isFocusableNode)(itemNode)) {
|
317
|
+
console.log("handleFocusableMenuItemOnMouseEnter > itemNode:", itemNode);
|
318
|
+
throw import_Errors.FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;
|
319
|
+
}
|
320
|
+
trackFocusNode(itemNode);
|
321
|
+
},
|
322
|
+
[trackFocusNode]
|
323
|
+
);
|
324
|
+
const handleFocusableMenuItemNativeFocusEvent = import_react.default.useCallback(
|
325
|
+
(itemNode, event) => {
|
326
|
+
opinionatedFocusBehaviour(itemNode, event);
|
327
|
+
},
|
328
|
+
[opinionatedFocusBehaviour]
|
329
|
+
);
|
292
330
|
return import_react.default.useMemo(
|
293
331
|
() => ({
|
294
332
|
handleFocusableMenuItemKeyDown,
|
295
333
|
handleFocusableMenuItemClick,
|
296
|
-
handleFocusableMenuItemOnMouseEnter
|
334
|
+
handleFocusableMenuItemOnMouseEnter,
|
335
|
+
handleFocusableMenuItemNativeFocusEvent
|
297
336
|
}),
|
298
|
-
[
|
337
|
+
[
|
338
|
+
handleFocusableMenuItemClick,
|
339
|
+
handleFocusableMenuItemKeyDown,
|
340
|
+
handleFocusableMenuItemNativeFocusEvent,
|
341
|
+
handleFocusableMenuItemOnMouseEnter
|
342
|
+
]
|
299
343
|
);
|
300
344
|
};
|
301
345
|
//# sourceMappingURL=useMenuItemEventsHandlers.js.map
|
package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js.map
CHANGED
@@ -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, selectedItems } = 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;\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 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 * 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 const newSelection = getNewSelectionMultipleSelect(selectedItems, pseudoFocusedItemNode);\n onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else if (isSingleSelectNode(pseudoFocusedItemNode)) {\n const newSelection = getNewSelectionSingleSelect(selectedItems, pseudoFocusedItemNode);\n onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });\n } else {\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 selectedItems,\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 const newSelection = getNewSelectionMultipleSelect(selectedItems, 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 const newSelection = getNewSelectionSingleSelect(selectedItems, 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 selectedItems,\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 // 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 if (event.key === 'Escape') {\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 * ******* 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 handleMenuItemEnterKeyDown,\n handleMenuItemSpaceKeyDown,\n trackFocusNextItem,\n trackFocusPreviousItem,\n handleMenuItemRightArrowKeyDown,\n handleMenuItemLeftArrowKeyDown,\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n closeCurrentSubmenu,\n trackFocusParentItem,\n onOpinionatedClose,\n ],\n );\n\n const handleFocusableMenuItemOnMouseEnter = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.MouseEvent) => {\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\n if (itemNode.plainItem.disabled) return;\n // if not disabled and it has a submenu, we open it\n openSubmenu({ itemNode, event });\n },\n [handleChangeOpenedSubItems, openSubmenu, trackFocusNode],\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 return React.useMemo(\n () => ({\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemClick,\n handleFocusableMenuItemOnMouseEnter,\n }),\n [handleFocusableMenuItemClick, handleFocusableMenuItemKeyDown, handleFocusableMenuItemOnMouseEnter],\n );\n};\n", "import * as React from 'react';\nexport { React };\n"],
|
5
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;ACAA,YAAuB;ADMvB,mBAAkB;AAElB,uCAOO;AACP,oBAAiE;AAEjE,sCAA8C;AAC9C,oCAAgC;AAChC,oCAA4C;AAoBrC,MAAM,4BAA4B,CAAC;AAAA,EACxC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,MAAuC;AACrC,QAAM,EAAE,gBAAgB,gBAAgB,cAAc,IAAI;AAC1D,QAAM,EAAE,mBAAmB,IAAI;AAC/B,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,cAAc,aAAAA,QAAM;AAAA,IACxB,CAAC;AAAA,MACC;AAAA,MACA;AAAA,IACF,MAGM;AACJ,UAAI,KAAC,oDAAkB,QAAQ,GAAG;AAChC,gBAAQ,IAAI,4BAA4B,QAAQ;AAChD,cAAM,mCAAqB;AAAA,MAC7B;AACA,YAAM,uBAAmB,+CAAgB,QAAQ;AACjD,YAAM,WAAkC,EAAE,UAAU,MAAM;AAC1D,YAAM,iBAAuD,CAAC,GAAG,kBAAkB,QAAQ;AAC3F,iCAA2B,gBAAgB,QAAQ;AAAA,IACrD;AAAA,IACA,CAAC,0BAA0B;AAAA,EAC7B;AACA,QAAM,sBAAsB,aAAAA,QAAM;AAAA,IAChC,CAAC;AAAA,MACC;AAAA,MACA;AAAA,IACF,MAGM;AACJ,YAAM,WAAkC,EAAE,UAAU,MAAM;AAC1D,YAAM,uBAAmB,+CAAgB,QAAQ;AAEjD,UAAI,iBAAiB,WAAW,GAAG;AACjC,mCAA2B,CAAC,GAAG,QAAQ;AACvC,eAAO,EAAE,2BAA2B,KAAK;AAAA,MAC3C;AAIA,uBAAiB,IAAI;AACrB,iCAA2B,kBAAkB,QAAQ;AACrD,aAAO,EAAE,2BAA2B,MAAM;AAAA,IAC5C;AAAA,IACA,CAAC,0BAA0B;AAAA,EAC7B;
|
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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",
|
6
6
|
"names": ["React"]
|
7
7
|
}
|
@@ -46,6 +46,9 @@ const DSMenuBehaviouralContextProviderPropTypes = {
|
|
46
46
|
focusableNodes: import_ds_props_helpers.PropTypes.arrayOf(import_ds_props_helpers.PropTypes.object).description(
|
47
47
|
"an array of nodes that can receive pseudoFocus, obtainable via ds-tree flatten + filter(isFocusable)"
|
48
48
|
).isRequired,
|
49
|
+
selectedNodes: import_ds_props_helpers.PropTypes.arrayOf(import_ds_props_helpers.PropTypes.object).description(
|
50
|
+
"an array of tree-nodes that have to be marked as selected in the GUI, required if the menu has selectionable items"
|
51
|
+
),
|
49
52
|
...import_react_desc_prop_types.MenuBehaviouralLayerPropTypes
|
50
53
|
};
|
51
54
|
const DSMenuBehaviouralContextProviderPropTypesSchema = DSMenuBehaviouralContextProviderPropTypes;
|