@elliemae/ds-menu-button 3.46.0-rc.1 → 3.46.0-rc.3
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/cjs/config/useMenuButton.js +4 -17
- package/dist/cjs/config/useMenuButton.js.map +2 -2
- package/dist/cjs/config/useSplitInherithedProps.js +31 -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 +11 -1
- package/dist/cjs/parts/DSFlyoutMenu/DSFlyoutMenu.js.map +2 -2
- 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/useMenuBehaviouralContextProvider.js +2 -0
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js.map +2 -2
- package/dist/cjs/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js +56 -29
- 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 +9 -4
- package/dist/cjs/parts/DSMenuItemRendererFactory/ActivableMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js +9 -4
- package/dist/cjs/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js +11 -6
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js +14 -7
- package/dist/cjs/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js +11 -6
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js +11 -6
- package/dist/cjs/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js +9 -4
- package/dist/cjs/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js.map +2 -2
- package/dist/cjs/react-desc-prop-types.js +9 -11
- 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/esm/config/useMenuButton.js +4 -17
- package/dist/esm/config/useMenuButton.js.map +2 -2
- package/dist/esm/config/useSplitInherithedProps.js +31 -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 +11 -1
- package/dist/esm/parts/DSFlyoutMenu/DSFlyoutMenu.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/useMenuBehaviouralContextProvider.js +2 -0
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js.map +2 -2
- package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js +56 -29
- 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 +9 -4
- package/dist/esm/parts/DSMenuItemRendererFactory/ActivableMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js +9 -4
- package/dist/esm/parts/DSMenuItemRendererFactory/ActivableWithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js +11 -6
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js +14 -7
- package/dist/esm/parts/DSMenuItemRendererFactory/MultipleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js +11 -6
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js +11 -6
- package/dist/esm/parts/DSMenuItemRendererFactory/SingleSelectWithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js +9 -4
- package/dist/esm/parts/DSMenuItemRendererFactory/WithSubmenuMenuItem.js.map +2 -2
- package/dist/esm/react-desc-prop-types.js +9 -11
- 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/types/config/useSplitInherithedProps.d.ts +8 -8
- package/dist/types/index.d.ts +1 -1
- package/dist/types/parts/DSMenuBehaviouralContextProvider/config/useAdvancedValidation.d.ts +6 -0
- 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/react-desc-prop-types.d.ts +15 -15
- package/dist/types/utils/nodesTypeguardsAndGetters.d.ts +113 -1
- package/package.json +13 -13
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
3
|
"sources": ["../../../../../../../scripts/build/transpile/react-shim.js", "../../../../src/parts/DSFlyoutMenu/DSFlyoutMenu.tsx"],
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { FloatingWrapper } from '@elliemae/ds-floating-context';\nimport { Grid } from '@elliemae/ds-grid';\nimport { describe } from '@elliemae/ds-props-helpers';\nimport { styled, xStyledCommonProps } from '@elliemae/ds-system';\nimport React from 'react';\nimport type { DSMenuButtonT } from '../../react-desc-prop-types.js';\nimport { MenuBehaviouralContextProviderContext } from '../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { DSMenuItemRendererFactory } from '../DSMenuItemRendererFactory/index.js';\nimport { useFlyoutMenu } from './config/useFlyoutMenu.js';\nimport { DSFlyoutMenuName, FLYOUT_MENU_SLOTS } from './constants/index.js';\nimport { DSFlyoutMenuPropTypesSchema, type DSFlyoutMenuT } from './react-desc-prop-types.js';\nimport { isRootNode } from '../../utils/nodesTypeguardsAndGetters.js';\n\nconst StyledWrapper = styled(Grid, { name: DSFlyoutMenuName, slot: FLYOUT_MENU_SLOTS.ROOT })`\n min-width: 150px;\n background-color: white;\n padding: ${({ theme }) => theme.space.xxxs} 0;\n ${xStyledCommonProps}\n`;\n\nconst StyledMenu = styled('div', { name: DSFlyoutMenuName, slot: FLYOUT_MENU_SLOTS.LIST_WRAPPER })`\n padding: 0;\n margin: 0;\n`;\n\nconst DSFlyoutMenu: React.ComponentType<DSFlyoutMenuT.Props> = (props) => {\n const { propsWithDefault, xstyledProps } = useFlyoutMenu(props);\n const {\n instanceUid,\n globalEventsHelpers: { mainMenuRef, handleSubmenusRefChange },\n } = React.useContext(MenuBehaviouralContextProviderContext);\n\n const { setFloatingRef, floatingStyles, floatingContext, ItemRenderer, isMenuOpen, itemNode } = propsWithDefault;\n\n const handleRefChange = React.useCallback(\n (node: HTMLDivElement) => {\n if (isRootNode(itemNode)) {\n mainMenuRef.current = node;\n return;\n }\n handleSubmenusRefChange(node, itemNode.dsId);\n },\n [handleSubmenusRefChange, itemNode, mainMenuRef],\n );\n\n return (\n <FloatingWrapper\n innerRef={setFloatingRef}\n floatingStyles={floatingStyles}\n isOpen={isMenuOpen}\n context={floatingContext}\n >\n <StyledWrapper getOwnerProps={() => propsWithDefault} getOwnerPropsArguments={() => ({})} {...xstyledProps}>\n <StyledMenu role=\"menu\" innerRef={handleRefChange}>\n {itemNode.children.map((optionNode) => (\n <DSMenuItemRendererFactory\n key={`flyout-menu-item-${optionNode.dsId}-${instanceUid}`}\n // DSTree doesn't support children having a polymorphic type, so we need to cast it manually\n itemNode={optionNode as DSMenuButtonT.MenuNode}\n ItemRenderer={ItemRenderer}\n // we are passing the DSFlyoutMenu component as a prop to the DSMenuItemRendererFactory\n // this solves the circular-ish dependency\n FlyoutMenuCircularDepInject={DSFlyoutMenu}\n />\n ))}\n </StyledMenu>\n </StyledWrapper>\n </FloatingWrapper>\n );\n};\n\nDSFlyoutMenu.displayName = DSFlyoutMenuName;\nconst DSFlyoutMenuWithSchema = describe(DSFlyoutMenu);\nDSFlyoutMenuWithSchema.propTypes = DSFlyoutMenuPropTypesSchema;\n\nexport { DSFlyoutMenu, DSFlyoutMenuWithSchema };\n"],
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { FloatingWrapper } from '@elliemae/ds-floating-context';\nimport { Grid } from '@elliemae/ds-grid';\nimport { describe } from '@elliemae/ds-props-helpers';\nimport { styled, xStyledCommonProps } from '@elliemae/ds-system';\nimport React from 'react';\nimport type { DSMenuButtonT } from '../../react-desc-prop-types.js';\nimport { MenuBehaviouralContextProviderContext } from '../DSMenuBehaviouralContextProvider/MenuBehaviouralContextProviderCTX.js';\nimport { DSMenuItemRendererFactory } from '../DSMenuItemRendererFactory/index.js';\nimport { useFlyoutMenu } from './config/useFlyoutMenu.js';\nimport { DSFlyoutMenuName, FLYOUT_MENU_SLOTS } from './constants/index.js';\nimport { DSFlyoutMenuPropTypesSchema, type DSFlyoutMenuT } from './react-desc-prop-types.js';\nimport { isRootNode } from '../../utils/nodesTypeguardsAndGetters.js';\n\nconst StyledWrapper = styled(Grid, { name: DSFlyoutMenuName, slot: FLYOUT_MENU_SLOTS.ROOT })`\n min-width: 150px;\n background-color: white;\n padding: ${({ theme }) => theme.space.xxxs} 0;\n ${xStyledCommonProps}\n`;\n\nconst StyledMenu = styled('div', { name: DSFlyoutMenuName, slot: FLYOUT_MENU_SLOTS.LIST_WRAPPER })`\n padding: 0;\n margin: 0;\n`;\n\nconst NoComponentPlaceholder = () => null;\n\nconst DSFlyoutMenu: React.ComponentType<DSFlyoutMenuT.Props> = (props) => {\n const { propsWithDefault, xstyledProps } = useFlyoutMenu(props);\n const {\n instanceUid,\n globalEventsHelpers: { mainMenuRef, handleSubmenusRefChange },\n } = React.useContext(MenuBehaviouralContextProviderContext);\n const [didAnimationEnd, setDidAnimationEnd] = React.useState(false);\n\n const { setFloatingRef, floatingStyles, floatingContext, ItemRenderer, isMenuOpen, itemNode } = propsWithDefault;\n\n const handleRefChange = React.useCallback(\n (node: HTMLDivElement) => {\n if (isRootNode(itemNode)) {\n mainMenuRef.current = node;\n return;\n }\n handleSubmenusRefChange(node, itemNode.dsId);\n },\n [handleSubmenusRefChange, itemNode, mainMenuRef],\n );\n\n const handleAnimationStartTrigger = React.useCallback(() => {\n setDidAnimationEnd(false);\n }, []);\n const handleAnimationEnd = React.useCallback(() => {\n setDidAnimationEnd(true);\n }, []);\n\n return (\n <FloatingWrapper\n innerRef={setFloatingRef}\n floatingStyles={floatingStyles}\n isOpen={isMenuOpen}\n context={floatingContext}\n onAnimationStartTriggered={handleAnimationStartTrigger}\n onAnimationEnd={handleAnimationEnd}\n >\n <StyledWrapper getOwnerProps={() => propsWithDefault} getOwnerPropsArguments={() => ({})} {...xstyledProps}>\n <StyledMenu role=\"menu\" innerRef={handleRefChange}>\n {itemNode.children.map((optionNode) => (\n <DSMenuItemRendererFactory\n key={`flyout-menu-item-${optionNode.dsId}-${instanceUid}`}\n // DSTree doesn't support children having a polymorphic type, so we need to cast it manually\n itemNode={optionNode as DSMenuButtonT.MenuNode}\n ItemRenderer={ItemRenderer}\n // we are passing the DSFlyoutMenu component as a prop to the DSMenuItemRendererFactory\n // this solves the circular-ish dependency\n // the conditional `didAnimationEnd` solves the visual artefact of nested FloatingWrapper wrongly positioned due to animation\n FlyoutMenuCircularDepInject={didAnimationEnd ? DSFlyoutMenu : NoComponentPlaceholder}\n />\n ))}\n </StyledMenu>\n </StyledWrapper>\n </FloatingWrapper>\n );\n};\n\nDSFlyoutMenu.displayName = DSFlyoutMenuName;\nconst DSFlyoutMenuWithSchema = describe(DSFlyoutMenu);\nDSFlyoutMenuWithSchema.propTypes = DSFlyoutMenuPropTypesSchema;\n\nexport { DSFlyoutMenu, DSFlyoutMenuWithSchema };\n"],
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACmEX;AAnEZ,SAAS,uBAAuB;AAChC,SAAS,YAAY;AACrB,SAAS,gBAAgB;AACzB,SAAS,QAAQ,0BAA0B;AAC3C,OAAOA,YAAW;AAElB,SAAS,6CAA6C;AACtD,SAAS,iCAAiC;AAC1C,SAAS,qBAAqB;AAC9B,SAAS,kBAAkB,yBAAyB;AACpD,SAAS,mCAAuD;AAChE,SAAS,kBAAkB;AAE3B,MAAM,gBAAgB,OAAO,MAAM,EAAE,MAAM,kBAAkB,MAAM,kBAAkB,KAAK,CAAC;AAAA;AAAA;AAAA,aAG9E,CAAC,EAAE,MAAM,MAAM,MAAM,MAAM,IAAI;AAAA,IACxC,kBAAkB;AAAA;AAGtB,MAAM,aAAa,OAAO,OAAO,EAAE,MAAM,kBAAkB,MAAM,kBAAkB,aAAa,CAAC;AAAA;AAAA;AAAA;AAKjG,MAAM,yBAAyB,MAAM;AAErC,MAAM,eAAyD,CAAC,UAAU;AACxE,QAAM,EAAE,kBAAkB,aAAa,IAAI,cAAc,KAAK;AAC9D,QAAM;AAAA,IACJ;AAAA,IACA,qBAAqB,EAAE,aAAa,wBAAwB;AAAA,EAC9D,IAAIA,OAAM,WAAW,qCAAqC;AAC1D,QAAM,CAAC,iBAAiB,kBAAkB,IAAIA,OAAM,SAAS,KAAK;AAElE,QAAM,EAAE,gBAAgB,gBAAgB,iBAAiB,cAAc,YAAY,SAAS,IAAI;AAEhG,QAAM,kBAAkBA,OAAM;AAAA,IAC5B,CAAC,SAAyB;AACxB,UAAI,WAAW,QAAQ,GAAG;AACxB,oBAAY,UAAU;AACtB;AAAA,MACF;AACA,8BAAwB,MAAM,SAAS,IAAI;AAAA,IAC7C;AAAA,IACA,CAAC,yBAAyB,UAAU,WAAW;AAAA,EACjD;AAEA,QAAM,8BAA8BA,OAAM,YAAY,MAAM;AAC1D,uBAAmB,KAAK;AAAA,EAC1B,GAAG,CAAC,CAAC;AACL,QAAM,qBAAqBA,OAAM,YAAY,MAAM;AACjD,uBAAmB,IAAI;AAAA,EACzB,GAAG,CAAC,CAAC;AAEL,SACE;AAAA,IAAC;AAAA;AAAA,MACC,UAAU;AAAA,MACV;AAAA,MACA,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,2BAA2B;AAAA,MAC3B,gBAAgB;AAAA,MAEhB,8BAAC,iBAAc,eAAe,MAAM,kBAAkB,wBAAwB,OAAO,CAAC,IAAK,GAAG,cAC5F,8BAAC,cAAW,MAAK,QAAO,UAAU,iBAC/B,mBAAS,SAAS,IAAI,CAAC,eACtB;AAAA,QAAC;AAAA;AAAA,UAGC,UAAU;AAAA,UACV;AAAA,UAIA,6BAA6B,kBAAkB,eAAe;AAAA;AAAA,QAPzD,oBAAoB,WAAW,IAAI,IAAI,WAAW;AAAA,MAQzD,CACD,GACH,GACF;AAAA;AAAA,EACF;AAEJ;AAEA,aAAa,cAAc;AAC3B,MAAM,yBAAyB,SAAS,YAAY;AACpD,uBAAuB,YAAY;",
|
6
6
|
"names": ["React"]
|
7
7
|
}
|
@@ -0,0 +1,56 @@
|
|
1
|
+
import * as React from "react";
|
2
|
+
import { useEffect } from "react";
|
3
|
+
import { isActivableNode, isSelectionableNode } from "../../../utils/nodesTypeguardsAndGetters.js";
|
4
|
+
const useAdvancedValidation = ({ propsWithDefault }) => {
|
5
|
+
const { onActivateItem, onItemSelected, selectedNodes, optionsTree } = propsWithDefault;
|
6
|
+
useEffect(() => {
|
7
|
+
const allNodes = optionsTree.flatten();
|
8
|
+
const nodesByDsId = {};
|
9
|
+
const duplicatedIdsNodes = {};
|
10
|
+
let hasActivation = false;
|
11
|
+
let hasSelection = false;
|
12
|
+
let hasDuplicateDsId = false;
|
13
|
+
allNodes.forEach((node) => {
|
14
|
+
const { dsId } = node;
|
15
|
+
if (!Array.isArray(nodesByDsId[dsId])) {
|
16
|
+
nodesByDsId[dsId] = [node];
|
17
|
+
} else {
|
18
|
+
nodesByDsId[dsId].push(node);
|
19
|
+
hasDuplicateDsId = true;
|
20
|
+
duplicatedIdsNodes[dsId] = nodesByDsId[dsId];
|
21
|
+
}
|
22
|
+
if (isActivableNode(node)) {
|
23
|
+
hasActivation = true;
|
24
|
+
}
|
25
|
+
if (isSelectionableNode(node)) {
|
26
|
+
hasSelection = true;
|
27
|
+
}
|
28
|
+
});
|
29
|
+
if (hasDuplicateDsId) {
|
30
|
+
console.log("duplicatedIdsNodes", duplicatedIdsNodes);
|
31
|
+
throw new Error(
|
32
|
+
`DSMenuButton:
|
33
|
+
The following dsId are repeated:
|
34
|
+
${Object.keys(duplicatedIdsNodes).join(
|
35
|
+
"\n"
|
36
|
+
)}
|
37
|
+
|
38
|
+
The dsId must be unique.`
|
39
|
+
);
|
40
|
+
}
|
41
|
+
if (hasActivation && !onActivateItem) {
|
42
|
+
throw new Error(
|
43
|
+
"DSMenuButton:\nSome items are activable, but onActivateItem is not provided.\n\nYou must provide onActivateItem to handle activation."
|
44
|
+
);
|
45
|
+
}
|
46
|
+
if (hasSelection && (!onItemSelected || !selectedNodes)) {
|
47
|
+
throw new Error(
|
48
|
+
"DSMenuButton:\nSome items are selectionable, but onItemSelected or selectedNodes are not provided.\n\nYou must provide onItemSelected and selectedNodes to handle selection."
|
49
|
+
);
|
50
|
+
}
|
51
|
+
}, [onActivateItem, onItemSelected, selectedNodes, optionsTree]);
|
52
|
+
};
|
53
|
+
export {
|
54
|
+
useAdvancedValidation
|
55
|
+
};
|
56
|
+
//# sourceMappingURL=useAdvancedValidation.js.map
|
@@ -0,0 +1,7 @@
|
|
1
|
+
{
|
2
|
+
"version": 3,
|
3
|
+
"sources": ["../../../../../../../../scripts/build/transpile/react-shim.js", "../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useAdvancedValidation.ts"],
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "import { useEffect } from 'react';\nimport { type DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport { type DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\nimport { isActivableNode, isSelectionableNode } from '../../../utils/nodesTypeguardsAndGetters.js';\n\ntype UseAdvancedValidationConfigT = {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n};\nexport const useAdvancedValidation = ({ propsWithDefault }: UseAdvancedValidationConfigT): void => {\n const { onActivateItem, onItemSelected, selectedNodes, optionsTree } = propsWithDefault;\n // =============================================================================\n // SPECIAL VALIDATIONS\n // =============================================================================\n // We iterate onces, validate everything in one go for performance reasons\n // =============================================================================\n // - No repeated dsId\n // (only validating focusableNodes may work, but to avoid complexity we validate the whole tree)\n // - if any item is \"selectionable\", selection IoC props must be provided\n // - if any item is \"activable\", activation IoC props must be provided\n // =============================================================================\n // we want to validate only when dependencies change, should improve performance if app is using memoization\n // and be virtually a free overhead if the app is not using memoization\n // =============================================================================\n useEffect(() => {\n const allNodes = optionsTree.flatten();\n const nodesByDsId: { [key: DSMenuButtonT.MenuNode['dsId']]: DSMenuButtonT.MenuNode[] } = {};\n const duplicatedIdsNodes: { [key: DSMenuButtonT.MenuNode['dsId']]: DSMenuButtonT.MenuNode[] } = {};\n let hasActivation = false;\n let hasSelection = false;\n let hasDuplicateDsId = false;\n allNodes.forEach((node) => {\n const { dsId } = node;\n if (!Array.isArray(nodesByDsId[dsId])) {\n nodesByDsId[dsId] = [node];\n } else {\n nodesByDsId[dsId].push(node);\n hasDuplicateDsId = true;\n duplicatedIdsNodes[dsId] = nodesByDsId[dsId];\n }\n\n if (isActivableNode(node)) {\n hasActivation = true;\n }\n if (isSelectionableNode(node)) {\n hasSelection = true;\n }\n });\n if (hasDuplicateDsId) {\n // eslint-disable-next-line no-console\n console.log('duplicatedIdsNodes', duplicatedIdsNodes);\n throw new Error(\n `DSMenuButton:\\nThe following dsId are repeated:\\n${Object.keys(duplicatedIdsNodes).join(\n '\\n',\n )}\\n\\nThe dsId must be unique.`,\n );\n }\n if (hasActivation && !onActivateItem) {\n throw new Error(\n 'DSMenuButton:\\nSome items are activable, but onActivateItem is not provided.\\n\\nYou must provide onActivateItem to handle activation.',\n );\n }\n if (hasSelection && (!onItemSelected || !selectedNodes)) {\n throw new Error(\n 'DSMenuButton:\\nSome items are selectionable, but onItemSelected or selectedNodes are not provided.\\n\\nYou must provide onItemSelected and selectedNodes to handle selection.',\n );\n }\n }, [onActivateItem, onItemSelected, selectedNodes, optionsTree]);\n};\n"],
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACAvB,SAAS,iBAAiB;AAG1B,SAAS,iBAAiB,2BAA2B;AAK9C,MAAM,wBAAwB,CAAC,EAAE,iBAAiB,MAA0C;AACjG,QAAM,EAAE,gBAAgB,gBAAgB,eAAe,YAAY,IAAI;AAcvE,YAAU,MAAM;AACd,UAAM,WAAW,YAAY,QAAQ;AACrC,UAAM,cAAmF,CAAC;AAC1F,UAAM,qBAA0F,CAAC;AACjG,QAAI,gBAAgB;AACpB,QAAI,eAAe;AACnB,QAAI,mBAAmB;AACvB,aAAS,QAAQ,CAAC,SAAS;AACzB,YAAM,EAAE,KAAK,IAAI;AACjB,UAAI,CAAC,MAAM,QAAQ,YAAY,IAAI,CAAC,GAAG;AACrC,oBAAY,IAAI,IAAI,CAAC,IAAI;AAAA,MAC3B,OAAO;AACL,oBAAY,IAAI,EAAE,KAAK,IAAI;AAC3B,2BAAmB;AACnB,2BAAmB,IAAI,IAAI,YAAY,IAAI;AAAA,MAC7C;AAEA,UAAI,gBAAgB,IAAI,GAAG;AACzB,wBAAgB;AAAA,MAClB;AACA,UAAI,oBAAoB,IAAI,GAAG;AAC7B,uBAAe;AAAA,MACjB;AAAA,IACF,CAAC;AACD,QAAI,kBAAkB;AAEpB,cAAQ,IAAI,sBAAsB,kBAAkB;AACpD,YAAM,IAAI;AAAA,QACR;AAAA;AAAA,EAAoD,OAAO,KAAK,kBAAkB,EAAE;AAAA,UAClF;AAAA,QACF,CAAC;AAAA;AAAA;AAAA,MACH;AAAA,IACF;AACA,QAAI,iBAAiB,CAAC,gBAAgB;AACpC,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,QAAI,iBAAiB,CAAC,kBAAkB,CAAC,gBAAgB;AACvD,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF,GAAG,CAAC,gBAAgB,gBAAgB,eAAe,WAAW,CAAC;AACjE;",
|
6
|
+
"names": []
|
7
|
+
}
|
package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.js
CHANGED
@@ -10,6 +10,7 @@ import { useGlobalEvents } from "./useGlobalEvents.js";
|
|
10
10
|
import { useMenuItemEventsHandlers } from "./useMenuItemEventsHandlers.js";
|
11
11
|
import { useMenuOpenStatus } from "./useMenuOpenStatus.js";
|
12
12
|
import { useValidateProps } from "./useValidateProps.js";
|
13
|
+
import { useAdvancedValidation } from "./useAdvancedValidation.js";
|
13
14
|
const useMenuBehaviouralContextProvider = (propsFromUser) => {
|
14
15
|
const propsWithDefault = React2.useMemo(
|
15
16
|
() => ({
|
@@ -19,6 +20,7 @@ const useMenuBehaviouralContextProvider = (propsFromUser) => {
|
|
19
20
|
[propsFromUser]
|
20
21
|
);
|
21
22
|
useValidateProps(propsWithDefault, DSMenuBehaviouralContextProviderPropTypesSchema);
|
23
|
+
useAdvancedValidation({ propsWithDefault });
|
22
24
|
const { onDisplayedSubmenuChange } = propsWithDefault;
|
23
25
|
const instanceUid = React2.useMemo(() => `ds-menu-behavioural-context-provider-${uid(5)}`, []);
|
24
26
|
const [openedSubItems, setOpenedSubItems] = React2.useState([]);
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
3
|
"sources": ["../../../../../../../../scripts/build/transpile/react-shim.js", "../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useMenuBehaviouralContextProvider.ts"],
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "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"],
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;ACAvB,OAAOA,YAAW;AAClB,SAAS,WAAW;AAEpB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,iCAAiC;AAC1C,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "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';\nimport { useAdvancedValidation } from './useAdvancedValidation.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"],
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACAvB,OAAOA,YAAW;AAClB,SAAS,WAAW;AAEpB;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AACP,SAAS,uBAAuB;AAChC,SAAS,uBAAuB;AAChC,SAAS,iCAAiC;AAC1C,SAAS,yBAAyB;AAClC,SAAS,wBAAwB;AACjC,SAAS,6BAA6B;AAa/B,MAAM,oCAAoC,CAAC,kBAA2D;AAO3G,QAAM,mBAAmBA,OAAM;AAAA,IAC7B,OAAO;AAAA,MACL,GAAG;AAAA,MACH,GAAG;AAAA,IACL;AAAA,IACA,CAAC,aAAa;AAAA,EAChB;AACA,mBAAiB,kBAAkB,+CAA+C;AAKlF,wBAAsB,EAAE,iBAAiB,CAAC;AAY1C,QAAM,EAAE,yBAAyB,IAAI;AACrC,QAAM,cAAcA,OAAM,QAAQ,MAAM,wCAAwC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;AAC5F,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,OAAM,SAA+C,CAAC,CAAC;AAEnG,QAAM,6BAA6BA,OAAM;AAAA,IAGvC,CAAC,gBAAgB,aAAa;AAC5B,wBAAkB,cAAc;AAChC,iCAA2B,gBAAgB,QAAQ;AAAA,IACrD;AAAA,IACA,CAAC,wBAAwB;AAAA,EAC3B;AAKA,QAAM,gBAAgB,gBAAgB;AACtC,QAAM,iBAAiB,kBAAkB,EAAE,kBAAkB,cAAc,CAAC;AAO5E,QAAM,yBAAyB,0BAA0B;AAAA,IACvD;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAID,QAAM,sBAAsB,gBAAgB,EAAE,gBAAgB,4BAA4B,iBAAiB,CAAC;AAE5G,SAAOA,OAAM;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
|
}
|
@@ -18,7 +18,7 @@ const useMenuItemEventsHandlers = ({
|
|
18
18
|
menuOpenStatus,
|
19
19
|
handleChangeOpenedSubItems
|
20
20
|
}) => {
|
21
|
-
const { onItemSelected, onActivateItem,
|
21
|
+
const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;
|
22
22
|
const { onOpinionatedClose } = menuOpenStatus;
|
23
23
|
const {
|
24
24
|
trackFocusFirstChildItem,
|
@@ -69,10 +69,10 @@ const useMenuItemEventsHandlers = ({
|
|
69
69
|
trackFocusFirstChildItem(pseudoFocusedItemNode);
|
70
70
|
} else {
|
71
71
|
if (isMultipleSelectNode(pseudoFocusedItemNode)) {
|
72
|
-
const newSelection = getNewSelectionMultipleSelect(
|
72
|
+
const newSelection = getNewSelectionMultipleSelect(selectedNodes, pseudoFocusedItemNode);
|
73
73
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
74
74
|
} else if (isSingleSelectNode(pseudoFocusedItemNode)) {
|
75
|
-
const newSelection = getNewSelectionSingleSelect(
|
75
|
+
const newSelection = getNewSelectionSingleSelect(selectedNodes, pseudoFocusedItemNode);
|
76
76
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
77
77
|
} else {
|
78
78
|
onActivateItem(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
@@ -88,7 +88,7 @@ const useMenuItemEventsHandlers = ({
|
|
88
88
|
openSubmenu,
|
89
89
|
handleChangeOpenedSubItems,
|
90
90
|
onOpinionatedClose,
|
91
|
-
|
91
|
+
selectedNodes,
|
92
92
|
onItemSelected,
|
93
93
|
onActivateItem
|
94
94
|
]
|
@@ -101,16 +101,16 @@ const useMenuItemEventsHandlers = ({
|
|
101
101
|
return;
|
102
102
|
}
|
103
103
|
if (isMultipleSelectNode(pseudoFocusedItemNode)) {
|
104
|
-
const newSelection = getNewSelectionMultipleSelect(
|
104
|
+
const newSelection = getNewSelectionMultipleSelect(selectedNodes, pseudoFocusedItemNode);
|
105
105
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
106
106
|
return;
|
107
107
|
}
|
108
108
|
if (isSingleSelectNode(pseudoFocusedItemNode)) {
|
109
|
-
const newSelection = getNewSelectionSingleSelect(
|
109
|
+
const newSelection = getNewSelectionSingleSelect(selectedNodes, pseudoFocusedItemNode);
|
110
110
|
onItemSelected(newSelection, { itemNode: pseudoFocusedItemNode, event });
|
111
111
|
return;
|
112
112
|
}
|
113
|
-
onActivateItem(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
113
|
+
onActivateItem?.(pseudoFocusedItemNode, { itemNode: pseudoFocusedItemNode, event });
|
114
114
|
const metainfo = { itemNode: pseudoFocusedItemNode, event };
|
115
115
|
const newOpenedItems = [];
|
116
116
|
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
@@ -122,7 +122,7 @@ const useMenuItemEventsHandlers = ({
|
|
122
122
|
onOpinionatedClose,
|
123
123
|
trackFocusFirstChildItem,
|
124
124
|
openSubmenu,
|
125
|
-
|
125
|
+
selectedNodes,
|
126
126
|
onItemSelected
|
127
127
|
]
|
128
128
|
);
|
@@ -221,25 +221,6 @@ const useMenuItemEventsHandlers = ({
|
|
221
221
|
onOpinionatedClose
|
222
222
|
]
|
223
223
|
);
|
224
|
-
const handleFocusableMenuItemOnMouseEnter = React2.useCallback(
|
225
|
-
(itemNode, event) => {
|
226
|
-
if (!isFocusableNode(itemNode)) {
|
227
|
-
console.log("handleFocusableMenuItemOnMouseEnter > itemNode:", itemNode);
|
228
|
-
throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;
|
229
|
-
}
|
230
|
-
trackFocusNode(itemNode);
|
231
|
-
if (!isWithSubmenuNode(itemNode)) {
|
232
|
-
const subMenuNodesPath = getSubMenusPath(itemNode);
|
233
|
-
const metainfo = { itemNode, event };
|
234
|
-
const newOpenedItems = [...subMenuNodesPath];
|
235
|
-
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
236
|
-
return;
|
237
|
-
}
|
238
|
-
if (itemNode.plainItem.disabled) return;
|
239
|
-
openSubmenu({ itemNode, event });
|
240
|
-
},
|
241
|
-
[handleChangeOpenedSubItems, openSubmenu, trackFocusNode]
|
242
|
-
);
|
243
224
|
const handleFocusableMenuItemClick = React2.useCallback(
|
244
225
|
(event) => {
|
245
226
|
const pseudoFocusedItemNode = focusedElementItemNode.current;
|
@@ -263,13 +244,59 @@ const useMenuItemEventsHandlers = ({
|
|
263
244
|
},
|
264
245
|
[focusedElementItemNode, handleMenuItemSpaceKeyDown, handleChangeOpenedSubItems]
|
265
246
|
);
|
247
|
+
const opinionatedFocusBehaviour = React2.useCallback(
|
248
|
+
// we currently know only one "event" that can trigger this opinionated behaviour
|
249
|
+
// - focus => React.FocusEvent <-- triggered by ATs "focus mode" or "virtual cursor" navigation
|
250
|
+
// NOTE:
|
251
|
+
// mouseEnter => React.MouseEvent
|
252
|
+
// still ends up calling this function, but as a side effect of mouse-enter focusing the item, not as the main event
|
253
|
+
(itemNode, event) => {
|
254
|
+
if (!isFocusableNode(itemNode)) {
|
255
|
+
console.log("handleFocusableMenuItemOnMouseEnter > itemNode:", itemNode);
|
256
|
+
throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;
|
257
|
+
}
|
258
|
+
trackFocusNode(itemNode);
|
259
|
+
if (!isWithSubmenuNode(itemNode)) {
|
260
|
+
const subMenuNodesPath = getSubMenusPath(itemNode);
|
261
|
+
const metainfo = { itemNode, event };
|
262
|
+
const newOpenedItems = [...subMenuNodesPath];
|
263
|
+
handleChangeOpenedSubItems(newOpenedItems, metainfo);
|
264
|
+
return;
|
265
|
+
}
|
266
|
+
if (itemNode.plainItem.disabled) return;
|
267
|
+
openSubmenu({ itemNode, event });
|
268
|
+
},
|
269
|
+
[handleChangeOpenedSubItems, openSubmenu, trackFocusNode]
|
270
|
+
);
|
271
|
+
const handleFocusableMenuItemOnMouseEnter = React2.useCallback(
|
272
|
+
(itemNode) => {
|
273
|
+
if (!isFocusableNode(itemNode)) {
|
274
|
+
console.log("handleFocusableMenuItemOnMouseEnter > itemNode:", itemNode);
|
275
|
+
throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;
|
276
|
+
}
|
277
|
+
trackFocusNode(itemNode);
|
278
|
+
},
|
279
|
+
[trackFocusNode]
|
280
|
+
);
|
281
|
+
const handleFocusableMenuItemNativeFocusEvent = React2.useCallback(
|
282
|
+
(itemNode, event) => {
|
283
|
+
opinionatedFocusBehaviour(itemNode, event);
|
284
|
+
},
|
285
|
+
[opinionatedFocusBehaviour]
|
286
|
+
);
|
266
287
|
return React2.useMemo(
|
267
288
|
() => ({
|
268
289
|
handleFocusableMenuItemKeyDown,
|
269
290
|
handleFocusableMenuItemClick,
|
270
|
-
handleFocusableMenuItemOnMouseEnter
|
291
|
+
handleFocusableMenuItemOnMouseEnter,
|
292
|
+
handleFocusableMenuItemNativeFocusEvent
|
271
293
|
}),
|
272
|
-
[
|
294
|
+
[
|
295
|
+
handleFocusableMenuItemClick,
|
296
|
+
handleFocusableMenuItemKeyDown,
|
297
|
+
handleFocusableMenuItemNativeFocusEvent,
|
298
|
+
handleFocusableMenuItemOnMouseEnter
|
299
|
+
]
|
273
300
|
);
|
274
301
|
};
|
275
302
|
export {
|
package/dist/esm/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.js.map
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
3
|
"sources": ["../../../../../../../../scripts/build/transpile/react-shim.js", "../../../../../src/parts/DSMenuBehaviouralContextProvider/config/useMenuItemEventsHandlers.ts"],
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable no-console */\n/* eslint-disable max-lines */\n// in this files the returns are not useless, they are used to clearly demark the intended behavior shortcircuit for the next developer that reads the code\n/* eslint-disable no-useless-return */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\nimport React from 'react';\nimport { type DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isMultipleSelectNode,\n isRootNode,\n isSingleSelectNode,\n isWithSubmenuNode,\n isWithSubmenuOnlyNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { FORBIDDEN_BEHAVIOURS, UNEXPECTED_INTERNAL_ERRORS } from '../constants/Errors.js';\nimport { type DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\nimport { getNewSelectionMultipleSelect } from '../utils/multipleSelectionHelpers.js';\nimport { getSubMenusPath } from '../utils/nodeGettersByCriterias.js';\nimport { getNewSelectionSingleSelect } from '../utils/singleSelectionHelpers.js';\nimport type { useFocusTracker } from './useFocusTracker.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\n\ntype ItemKeydownHelpersArgs = {\n pseudoFocusedItemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n};\ntype SubmenuChangeMetainfo = {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.SyntheticEvent;\n};\n\ntype UseMenuItemEventsHandlersConfig = {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n focusTrackers: ReturnType<typeof useFocusTracker>;\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n};\n\nexport const useMenuItemEventsHandlers = ({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n}: UseMenuItemEventsHandlersConfig) => {\n const { onItemSelected, onActivateItem, 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"],
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;ACMvB,OAAOA,YAAW;AAElB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,sBAAsB,kCAAkC;AAEjE,SAAS,qCAAqC;AAC9C,SAAS,uBAAuB;AAChC,SAAS,mCAAmC;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,cAAcA,OAAM;AAAA,IACxB,CAAC;AAAA,MACC;AAAA,MACA;AAAA,IACF,MAGM;AACJ,UAAI,CAAC,kBAAkB,QAAQ,GAAG;AAChC,gBAAQ,IAAI,4BAA4B,QAAQ;AAChD,cAAM,qBAAqB;AAAA,MAC7B;AACA,YAAM,mBAAmB,gBAAgB,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,sBAAsBA,OAAM;AAAA,IAChC,CAAC;AAAA,MACC;AAAA,MACA;AAAA,IACF,MAGM;AACJ,YAAM,WAAkC,EAAE,UAAU,MAAM;AAC1D,YAAM,mBAAmB,gBAAgB,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;AAsBA,QAAM,6BAA6BA,OAAM;AAAA,IACvC,CAAC,EAAE,uBAAuB,MAAM,MAA8B;AAI5D,UAAI,sBAAsB,qBAAqB,GAAG;AAChD,oBAAY,EAAE,UAAU,uBAAuB,MAAM,CAAC;AACtD,iCAAyB,qBAAqB;AAAA,MAChD,OAAO;AAiBL,YAAI,qBAAqB,qBAAqB,GAAG;
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable no-console */\n/* eslint-disable max-lines */\n// in this files the returns are not useless, they are used to clearly demark the intended behavior shortcircuit for the next developer that reads the code\n/* eslint-disable no-useless-return */\n/* eslint-disable complexity */\n/* eslint-disable max-statements */\nimport React from 'react';\nimport { type DSMenuButtonT } from '../../../react-desc-prop-types.js';\nimport {\n isFocusableNode,\n isMultipleSelectNode,\n isRootNode,\n isSingleSelectNode,\n isWithSubmenuNode,\n isWithSubmenuOnlyNode,\n} from '../../../utils/nodesTypeguardsAndGetters.js';\nimport { FORBIDDEN_BEHAVIOURS, UNEXPECTED_INTERNAL_ERRORS } from '../constants/Errors.js';\nimport { type DSMenuBehaviouralContextProviderT } from '../react-desc-prop-types.js';\nimport { getNewSelectionMultipleSelect } from '../utils/multipleSelectionHelpers.js';\nimport { getSubMenusPath } from '../utils/nodeGettersByCriterias.js';\nimport { getNewSelectionSingleSelect } from '../utils/singleSelectionHelpers.js';\nimport type { useFocusTracker } from './useFocusTracker.js';\nimport type { useMenuOpenStatus } from './useMenuOpenStatus.js';\n\ntype ItemKeydownHelpersArgs = {\n pseudoFocusedItemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent;\n};\ntype SubmenuChangeMetainfo = {\n itemNode: DSMenuButtonT.PseudoFocusableMenuNodes;\n event?: React.SyntheticEvent;\n};\n\ntype UseMenuItemEventsHandlersConfig = {\n propsWithDefault: DSMenuBehaviouralContextProviderT.InternalProps;\n focusTrackers: ReturnType<typeof useFocusTracker>;\n menuOpenStatus: ReturnType<typeof useMenuOpenStatus>;\n handleChangeOpenedSubItems: Required<DSMenuButtonT.MenuBehaviouralLayerOptionalProps>['onDisplayedSubmenuChange'];\n};\n\nexport const useMenuItemEventsHandlers = ({\n propsWithDefault,\n focusTrackers,\n menuOpenStatus,\n handleChangeOpenedSubItems,\n}: UseMenuItemEventsHandlersConfig) => {\n const { onItemSelected, onActivateItem, selectedNodes } = propsWithDefault;\n const { onOpinionatedClose } = menuOpenStatus;\n const {\n trackFocusFirstChildItem,\n trackFocusLastChildItem,\n trackFocusNextItem,\n trackFocusPreviousItem,\n trackFocusParentItem,\n trackFocusNode,\n focusedElementItemNode,\n } = focusTrackers;\n\n const openSubmenu = React.useCallback(\n ({\n itemNode,\n event,\n }: {\n itemNode: DSMenuButtonT.WithSubmenuMenuNodes;\n event?: React.KeyboardEvent | React.MouseEvent | React.FocusEvent;\n }) => {\n if (!isWithSubmenuNode(itemNode)) {\n console.log('openSubmenu -> itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_OPEN_SUBMENU_OF_NODE_WITHOUT_SUBMENU;\n }\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath, itemNode];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n },\n [handleChangeOpenedSubItems],\n );\n const 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 // 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 // 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 const handleFocusableMenuItemClick = React.useCallback(\n (event: React.MouseEvent<HTMLDivElement>) => {\n const pseudoFocusedItemNode = focusedElementItemNode.current;\n if (pseudoFocusedItemNode === null) throw UNEXPECTED_INTERNAL_ERRORS.FOCUSED_ITEM_NODE_NOT_SET;\n // if the item is disabled, we don't do anything\n if (pseudoFocusedItemNode.plainItem.disabled) return;\n // we need to handle which submenu needs to close automagically based on the click\n // starting from the itemNode, we check:\n // - the parent of the itemNode is root?\n // - yes -> we close all the submenus\n const parentMenuNode = pseudoFocusedItemNode.parent;\n if (isRootNode(parentMenuNode)) {\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n } else {\n // - no -> we close all the submenus that are not in the path to the root of the itemNode\n // this is necessary on click because the user can still interact with other (visual) depths of the menu\n // during keyboard navigation, the user is limited to the current (visual) depth\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...getSubMenusPath(pseudoFocusedItemNode)];\n // - if the clicked node has a submenu, keep it open\n if (isWithSubmenuNode(pseudoFocusedItemNode)) {\n newOpenedItems.push(pseudoFocusedItemNode);\n }\n const metainfo: SubmenuChangeMetainfo = { itemNode: pseudoFocusedItemNode, event };\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n }\n\n // to all effects and purposes, the click event is the same as the space key\n // the main difference between enter and space is that\n // - enter ALWAYS closes the menu excepts for submenus\n // - space closes the menu if \"activable\", selections doesn't, submenus doesn't\n handleMenuItemSpaceKeyDown({ pseudoFocusedItemNode, event });\n // if the item contains an `onClick` prop, we trigger it\n if (pseudoFocusedItemNode.plainItem.onClick) pseudoFocusedItemNode.plainItem.onClick(event);\n },\n [focusedElementItemNode, handleMenuItemSpaceKeyDown, handleChangeOpenedSubItems],\n );\n\n // the \"menu-button\" widget is an opinionated widget with a specific behaviour to follow when a focusable item is \"focused\"\n // this function achieve the opinionated \"focus an item\" opinionated behaviour (regardless of why the item is focused)\n const opinionatedFocusBehaviour = React.useCallback(\n // we currently know only one \"event\" that can trigger this opinionated behaviour\n // - focus => React.FocusEvent <-- triggered by ATs \"focus mode\" or \"virtual cursor\" navigation\n // NOTE:\n // mouseEnter => React.MouseEvent\n // still ends up calling this function, but as a side effect of mouse-enter focusing the item, not as the main event\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n if (!isWithSubmenuNode(itemNode)) {\n // if the item has nosubmenu we close all the siblings submenus\n const subMenuNodesPath = getSubMenusPath(itemNode);\n const metainfo: SubmenuChangeMetainfo = { itemNode, event };\n const newOpenedItems: DSMenuButtonT.WithSubmenuMenuNodes[] = [...subMenuNodesPath];\n handleChangeOpenedSubItems(newOpenedItems, metainfo);\n return;\n }\n // if the item is disabled, we don't open the submenu, and there is nothing else to do\n if (itemNode.plainItem.disabled) return;\n // if not disabled and it has a submenu, we open it, and there is nothing else to do\n openSubmenu({ itemNode, event });\n },\n [handleChangeOpenedSubItems, openSubmenu, trackFocusNode],\n );\n\n // when an item is hovered, we focus it\n // this effectively achieves:\n // - the item is focused (it trigger the handleFocusableMenuItemNativeFocusEvent function because render runs node.focus())\n // - if the user presses a keyboard key, the interaction will work starting from the (hovered hence focused) item\n const handleFocusableMenuItemOnMouseEnter = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes) => {\n if (!isFocusableNode(itemNode)) {\n console.log('handleFocusableMenuItemOnMouseEnter > itemNode:', itemNode);\n throw FORBIDDEN_BEHAVIOURS.TRYING_TO_FOCUS_NON_FOCUSABLE_NODE;\n }\n // on hover -> we track the hovered item as the focused one on top of which events should work on\n trackFocusNode(itemNode);\n },\n [trackFocusNode],\n );\n\n const handleFocusableMenuItemNativeFocusEvent = React.useCallback(\n (itemNode: DSMenuButtonT.PseudoFocusableMenuNodes, event: React.FocusEvent) => {\n opinionatedFocusBehaviour(itemNode, event);\n },\n [opinionatedFocusBehaviour],\n );\n\n return React.useMemo(\n () => ({\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemClick,\n handleFocusableMenuItemOnMouseEnter,\n handleFocusableMenuItemNativeFocusEvent,\n }),\n [\n handleFocusableMenuItemClick,\n handleFocusableMenuItemKeyDown,\n handleFocusableMenuItemNativeFocusEvent,\n handleFocusableMenuItemOnMouseEnter,\n ],\n );\n};\n"],
|
5
|
+
"mappings": "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",
|
6
6
|
"names": ["React"]
|
7
7
|
}
|
@@ -11,6 +11,9 @@ const DSMenuBehaviouralContextProviderPropTypes = {
|
|
11
11
|
focusableNodes: PropTypes.arrayOf(PropTypes.object).description(
|
12
12
|
"an array of nodes that can receive pseudoFocus, obtainable via ds-tree flatten + filter(isFocusable)"
|
13
13
|
).isRequired,
|
14
|
+
selectedNodes: PropTypes.arrayOf(PropTypes.object).description(
|
15
|
+
"an array of tree-nodes that have to be marked as selected in the GUI, required if the menu has selectionable items"
|
16
|
+
),
|
14
17
|
...MenuBehaviouralLayerPropTypes
|
15
18
|
};
|
16
19
|
const DSMenuBehaviouralContextProviderPropTypesSchema = DSMenuBehaviouralContextProviderPropTypes;
|
@@ -1,7 +1,7 @@
|
|
1
1
|
{
|
2
2
|
"version": 3,
|
3
3
|
"sources": ["../../../../../../../scripts/build/transpile/react-shim.js", "../../../../src/parts/DSMenuBehaviouralContextProvider/react-desc-prop-types.ts"],
|
4
|
-
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-empty-interface */\nimport type { DSPropTypesSchema, ValidationMap } from '@elliemae/ds-props-helpers';\nimport { PropTypes } from '@elliemae/ds-props-helpers';\nimport { type TypescriptHelpersT } from '@elliemae/ds-typescript-helpers';\nimport { type DSMenuButtonT, MenuBehaviouralLayerPropTypes } from '../../react-desc-prop-types.js';\n\nexport declare namespace DSMenuBehaviouralContextProviderT {\n /*\n * everything that is specifically added in this namespace declaration and is not part of the original component\n * is because the original component is an \"opinionated widget\" that offloads a chunk of responsibilities from dev to dimsum\n * as such, the widget generates those interfaces,\n * but the app developer is not supposed to know about them\n * untill they go atomic composition (this namespace)\n */\n export interface RequiredProps extends DSMenuButtonT.MenuBehaviouralLayerRequiredProps {\n buttonDOMNodeRef: React.MutableRefObject<HTMLElement | null>;\n optionsTree: DSMenuButtonT.MenuNode;\n children: TypescriptHelpersT.ReactChildrenComplete;\n focusableNodes: DSMenuButtonT.PseudoFocusableMenuNodes[];\n }\n\n export interface DefaultProps extends DSMenuButtonT.MenuBehaviouralLayerDefaultProps {}\n\n export interface OptionalProps extends DSMenuButtonT.MenuBehaviouralLayerOptionalProps {}\n export interface Props extends Partial<DefaultProps>, OptionalProps, RequiredProps {}\n\n export interface InternalProps extends DefaultProps, OptionalProps, RequiredProps {}\n}\n\nexport const defaultProps: DSMenuBehaviouralContextProviderT.DefaultProps = {};\n\nexport const DSMenuBehaviouralContextProviderPropTypes: DSPropTypesSchema<DSMenuBehaviouralContextProviderT.Props> = {\n buttonDOMNodeRef: PropTypes.object.description(\n 'The DOM node of the button as a mutable Refs (required for performance reasons)',\n ).isRequired,\n children: PropTypes.node.description('the menu trigger and the menu components').isRequired,\n optionsTree: PropTypes.object.description('DSTree node to build branches from').isRequired,\n focusableNodes: PropTypes.arrayOf(PropTypes.object).description(\n 'an array of nodes that can receive pseudoFocus, obtainable via ds-tree flatten + filter(isFocusable)',\n ).isRequired,\n ...MenuBehaviouralLayerPropTypes,\n};\n\nexport const DSMenuBehaviouralContextProviderPropTypesSchema =\n DSMenuBehaviouralContextProviderPropTypes as unknown as ValidationMap<DSMenuBehaviouralContextProviderT.Props>;\n"],
|
5
|
-
"mappings": "AAAA,YAAY,WAAW;ACEvB,SAAS,iBAAiB;AAE1B,SAA6B,qCAAqC;
|
4
|
+
"sourcesContent": ["import * as React from 'react';\nexport { React };\n", "/* eslint-disable @typescript-eslint/no-empty-interface */\nimport type { DSPropTypesSchema, ValidationMap } from '@elliemae/ds-props-helpers';\nimport { PropTypes } from '@elliemae/ds-props-helpers';\nimport { type TypescriptHelpersT } from '@elliemae/ds-typescript-helpers';\nimport { type DSMenuButtonT, MenuBehaviouralLayerPropTypes } from '../../react-desc-prop-types.js';\n\nexport declare namespace DSMenuBehaviouralContextProviderT {\n /*\n * everything that is specifically added in this namespace declaration and is not part of the original component\n * is because the original component is an \"opinionated widget\" that offloads a chunk of responsibilities from dev to dimsum\n * as such, the widget generates those interfaces,\n * but the app developer is not supposed to know about them\n * untill they go atomic composition (this namespace)\n */\n export interface RequiredProps extends DSMenuButtonT.MenuBehaviouralLayerRequiredProps {\n buttonDOMNodeRef: React.MutableRefObject<HTMLElement | null>;\n optionsTree: DSMenuButtonT.MenuNode;\n children: TypescriptHelpersT.ReactChildrenComplete;\n focusableNodes: DSMenuButtonT.PseudoFocusableMenuNodes[];\n }\n\n export interface DefaultProps extends DSMenuButtonT.MenuBehaviouralLayerDefaultProps {}\n\n export interface OptionalProps extends DSMenuButtonT.MenuBehaviouralLayerOptionalProps {\n selectedNodes?: DSMenuButtonT.SelectionableMenuNodes[];\n }\n export interface Props extends Partial<DefaultProps>, OptionalProps, RequiredProps {}\n\n export interface InternalProps extends DefaultProps, OptionalProps, RequiredProps {}\n}\n\nexport const defaultProps: DSMenuBehaviouralContextProviderT.DefaultProps = {};\n\nexport const DSMenuBehaviouralContextProviderPropTypes: DSPropTypesSchema<DSMenuBehaviouralContextProviderT.Props> = {\n buttonDOMNodeRef: PropTypes.object.description(\n 'The DOM node of the button as a mutable Refs (required for performance reasons)',\n ).isRequired,\n children: PropTypes.node.description('the menu trigger and the menu components').isRequired,\n optionsTree: PropTypes.object.description('DSTree node to build branches from').isRequired,\n focusableNodes: PropTypes.arrayOf(PropTypes.object).description(\n 'an array of nodes that can receive pseudoFocus, obtainable via ds-tree flatten + filter(isFocusable)',\n ).isRequired,\n selectedNodes: PropTypes.arrayOf(PropTypes.object).description(\n 'an array of tree-nodes that have to be marked as selected in the GUI, required if the menu has selectionable items',\n ),\n ...MenuBehaviouralLayerPropTypes,\n};\n\nexport const DSMenuBehaviouralContextProviderPropTypesSchema =\n DSMenuBehaviouralContextProviderPropTypes as unknown as ValidationMap<DSMenuBehaviouralContextProviderT.Props>;\n"],
|
5
|
+
"mappings": "AAAA,YAAY,WAAW;ACEvB,SAAS,iBAAiB;AAE1B,SAA6B,qCAAqC;AA2B3D,MAAM,eAA+D,CAAC;AAEtE,MAAM,4CAAwG;AAAA,EACnH,kBAAkB,UAAU,OAAO;AAAA,IACjC;AAAA,EACF,EAAE;AAAA,EACF,UAAU,UAAU,KAAK,YAAY,0CAA0C,EAAE;AAAA,EACjF,aAAa,UAAU,OAAO,YAAY,oCAAoC,EAAE;AAAA,EAChF,gBAAgB,UAAU,QAAQ,UAAU,MAAM,EAAE;AAAA,IAClD;AAAA,EACF,EAAE;AAAA,EACF,eAAe,UAAU,QAAQ,UAAU,MAAM,EAAE;AAAA,IACjD;AAAA,EACF;AAAA,EACA,GAAG;AACL;AAEO,MAAM,kDACX;",
|
6
6
|
"names": []
|
7
7
|
}
|
@@ -8,13 +8,13 @@ import { UNEXPECTED_INTERNAL_ERRORS } from "../constants/Errors.js";
|
|
8
8
|
import { getChildrenByCriterias } from "./nodeGettersByCriterias.js";
|
9
9
|
const getIsMultipleSelectNodeWithSubmenuSelected = ({
|
10
10
|
itemNode,
|
11
|
-
|
11
|
+
selectedNodes
|
12
12
|
}) => {
|
13
|
-
if (
|
13
|
+
if (selectedNodes.length === 0) return false;
|
14
14
|
const { multiSelectionableChilrens } = getChildrenByCriterias(itemNode);
|
15
15
|
const selectedMultiChildren = [];
|
16
16
|
let isSelectedItself = false;
|
17
|
-
|
17
|
+
selectedNodes.forEach((itemMarkesAsSelected) => {
|
18
18
|
if (itemMarkesAsSelected.dsId === itemNode.dsId) isSelectedItself = true;
|
19
19
|
if (isMultipleSelectNode(itemMarkesAsSelected)) {
|
20
20
|
if (multiSelectionableChilrens.some((child) => child.dsId === itemMarkesAsSelected.dsId))
|
@@ -27,9 +27,9 @@ const getIsMultipleSelectNodeWithSubmenuSelected = ({
|
|
27
27
|
if (selectedMultiChildren.length !== multiSelectionableChilrens.length) return "mixed";
|
28
28
|
return true;
|
29
29
|
};
|
30
|
-
const getNewSelectionForMultipleSelectOnlyNode = (
|
31
|
-
const isDeselectingItem =
|
32
|
-
let newSelection = isDeselectingItem ?
|
30
|
+
const getNewSelectionForMultipleSelectOnlyNode = (selectedNodes, selectedItem) => {
|
31
|
+
const isDeselectingItem = selectedNodes.find((item) => item.dsId === selectedItem.dsId) ?? false;
|
32
|
+
let newSelection = isDeselectingItem ? selectedNodes.filter(({ dsId }) => dsId !== selectedItem.dsId) : [...selectedNodes, selectedItem];
|
33
33
|
const notSelectedMultiParents = [];
|
34
34
|
const selectedMultiParents = [];
|
35
35
|
const multiParentsToDeselect = [];
|
@@ -42,7 +42,7 @@ const getNewSelectionForMultipleSelectOnlyNode = (selectedItems, selectedItem) =
|
|
42
42
|
selectedMultiParents.push(typedParent);
|
43
43
|
const isParentSelected = getIsMultipleSelectNodeWithSubmenuSelected({
|
44
44
|
itemNode: typedParent,
|
45
|
-
|
45
|
+
selectedNodes: newSelection
|
46
46
|
});
|
47
47
|
if (isParentSelected === false) multiParentsToDeselect.push(typedParent);
|
48
48
|
} else notSelectedMultiParents.push(typedParent);
|
@@ -56,13 +56,13 @@ const getNewSelectionForMultipleSelectOnlyNode = (selectedItems, selectedItem) =
|
|
56
56
|
}
|
57
57
|
return newSelection;
|
58
58
|
};
|
59
|
-
const getNewSelectionForMultipleSelectionWithSubmenuNode = (
|
59
|
+
const getNewSelectionForMultipleSelectionWithSubmenuNode = (selectedNodes, selectedItem) => {
|
60
60
|
const currentStatus = getIsMultipleSelectNodeWithSubmenuSelected({
|
61
61
|
itemNode: selectedItem,
|
62
|
-
|
62
|
+
selectedNodes
|
63
63
|
});
|
64
64
|
const isDeselectingItem = currentStatus === true;
|
65
|
-
let newSelection = isDeselectingItem ?
|
65
|
+
let newSelection = isDeselectingItem ? selectedNodes.filter(({ dsId }) => dsId !== selectedItem.dsId) : [...selectedNodes, selectedItem];
|
66
66
|
selectedItem.walk((child) => {
|
67
67
|
const typedChild = child;
|
68
68
|
if (isDeselectingItem) {
|
@@ -84,7 +84,7 @@ const getNewSelectionForMultipleSelectionWithSubmenuNode = (selectedItems, selec
|
|
84
84
|
selectedMultiParents.push(typedParent);
|
85
85
|
const isParentSelected = getIsMultipleSelectNodeWithSubmenuSelected({
|
86
86
|
itemNode: typedParent,
|
87
|
-
|
87
|
+
selectedNodes: newSelection
|
88
88
|
});
|
89
89
|
if (isParentSelected === false) multiParentsToDeselect.push(typedParent);
|
90
90
|
} else notSelectedMultiParents.push(typedParent);
|
@@ -98,11 +98,11 @@ const getNewSelectionForMultipleSelectionWithSubmenuNode = (selectedItems, selec
|
|
98
98
|
}
|
99
99
|
return newSelection;
|
100
100
|
};
|
101
|
-
const getNewSelectionMultipleSelect = (
|
101
|
+
const getNewSelectionMultipleSelect = (selectedNodes, selectedItem) => {
|
102
102
|
if (isMultipleSelectOnlyNode(selectedItem))
|
103
|
-
return getNewSelectionForMultipleSelectOnlyNode(
|
103
|
+
return getNewSelectionForMultipleSelectOnlyNode(selectedNodes, selectedItem);
|
104
104
|
if (isMultipleSelectWithSubmenuNode(selectedItem))
|
105
|
-
return getNewSelectionForMultipleSelectionWithSubmenuNode(
|
105
|
+
return getNewSelectionForMultipleSelectionWithSubmenuNode(selectedNodes, selectedItem);
|
106
106
|
console.log(selectedItem);
|
107
107
|
throw UNEXPECTED_INTERNAL_ERRORS.CALCULATING_MULTI_SELECTION_OF_NON_MULTI_SELECTION_NODE;
|
108
108
|
};
|