@linzjs/step-ag-grid 1.4.2 → 1.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/index.js +1610 -3048
  2. package/dist/index.js.map +1 -1
  3. package/dist/src/components/GridLoadableCell.d.ts +2 -0
  4. package/dist/src/components/gridForm/GridFormDropDown.d.ts +1 -1
  5. package/dist/src/components/gridForm/GridFormMultiSelect.d.ts +1 -1
  6. package/dist/src/components/gridPopoverEdit/GridPopoverMenu.d.ts +1 -1
  7. package/dist/src/react-menu3/components/ControlledMenu.d.ts +2 -0
  8. package/dist/src/react-menu3/components/FocusableItem.d.ts +1 -0
  9. package/dist/src/react-menu3/components/Menu.d.ts +2 -0
  10. package/dist/src/react-menu3/components/MenuButton.d.ts +1 -0
  11. package/dist/src/react-menu3/components/MenuDivider.d.ts +2 -0
  12. package/dist/src/react-menu3/components/MenuGroup.d.ts +2 -0
  13. package/dist/src/react-menu3/components/MenuHeader.d.ts +2 -0
  14. package/dist/src/react-menu3/components/MenuItem.d.ts +1 -0
  15. package/dist/src/react-menu3/components/MenuList.d.ts +30 -0
  16. package/dist/src/react-menu3/components/MenuRadioGroup.d.ts +2 -0
  17. package/dist/src/react-menu3/components/SubMenu.d.ts +1 -0
  18. package/dist/src/react-menu3/hooks/index.d.ts +9 -0
  19. package/dist/src/react-menu3/hooks/useBEM.d.ts +9 -0
  20. package/dist/src/react-menu3/hooks/useCombinedRef.d.ts +2 -0
  21. package/dist/src/react-menu3/hooks/useIsomorphicLayoutEffect.d.ts +3 -0
  22. package/dist/src/react-menu3/hooks/useItemEffect.d.ts +2 -0
  23. package/dist/src/react-menu3/hooks/useItemState.d.ts +6 -0
  24. package/dist/src/react-menu3/hooks/useItems.d.ts +5 -0
  25. package/dist/src/react-menu3/hooks/useMenuChange.d.ts +1 -0
  26. package/dist/src/react-menu3/hooks/useMenuState.d.ts +11 -0
  27. package/dist/src/react-menu3/hooks/useMenuStateAndFocus.d.ts +10 -0
  28. package/dist/src/react-menu3/index.d.ts +11 -0
  29. package/dist/src/react-menu3/positionUtils/getPositionHelpers.d.ts +10 -0
  30. package/dist/src/react-menu3/positionUtils/index.d.ts +3 -0
  31. package/dist/src/react-menu3/positionUtils/placeArrowHorizontal.d.ts +7 -0
  32. package/dist/src/react-menu3/positionUtils/placeArrowVertical.d.ts +7 -0
  33. package/dist/src/react-menu3/positionUtils/placeLeftorRight.d.ts +21 -0
  34. package/dist/src/react-menu3/positionUtils/placeToporBottom.d.ts +21 -0
  35. package/dist/src/react-menu3/positionUtils/positionContextMenu.d.ts +8 -0
  36. package/dist/src/react-menu3/positionUtils/positionMenu.d.ts +21 -0
  37. package/dist/src/react-menu3/style-utils/index.d.ts +11 -0
  38. package/dist/src/react-menu3/utils/constants.d.ts +50 -0
  39. package/dist/src/react-menu3/utils/index.d.ts +4 -0
  40. package/dist/src/react-menu3/utils/propTypes.d.ts +55 -0
  41. package/dist/src/react-menu3/utils/utils.d.ts +27 -0
  42. package/dist/src/react-menu3/utils/withHovering.d.ts +1 -0
  43. package/dist/step-ag-grid.esm.js +1521 -2958
  44. package/dist/step-ag-grid.esm.js.map +1 -1
  45. package/package.json +18 -9
  46. package/src/components/GridLoadableCell.scss +4 -0
  47. package/src/components/GridLoadableCell.tsx +5 -1
  48. package/src/components/GridPopoverHook.tsx +4 -50
  49. package/src/components/gridForm/GridFormDropDown.tsx +3 -3
  50. package/src/components/gridForm/GridFormMultiSelect.tsx +4 -4
  51. package/src/components/gridForm/GridFormPopoutMenu.tsx +1 -1
  52. package/src/components/gridPopoverEdit/GridPopoverMenu.scss +6 -2
  53. package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +1 -1
  54. package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +7 -9
  55. package/src/react-menu3/components/ControlledMenu.tsx +260 -0
  56. package/src/react-menu3/components/FocusableItem.tsx +70 -0
  57. package/src/react-menu3/components/Menu.tsx +96 -0
  58. package/src/react-menu3/components/MenuButton.tsx +33 -0
  59. package/src/react-menu3/components/MenuDivider.tsx +21 -0
  60. package/src/react-menu3/components/MenuGroup.tsx +38 -0
  61. package/src/react-menu3/components/MenuHeader.tsx +21 -0
  62. package/src/react-menu3/components/MenuItem.tsx +130 -0
  63. package/src/react-menu3/components/MenuList.tsx +434 -0
  64. package/src/react-menu3/components/MenuRadioGroup.tsx +33 -0
  65. package/src/react-menu3/components/SubMenu.tsx +243 -0
  66. package/src/react-menu3/hooks/index.ts +9 -0
  67. package/src/react-menu3/hooks/useBEM.ts +32 -0
  68. package/src/react-menu3/hooks/useCombinedRef.ts +30 -0
  69. package/src/react-menu3/hooks/useIsomorphicLayoutEffect.ts +15 -0
  70. package/src/react-menu3/hooks/useItemEffect.ts +22 -0
  71. package/src/react-menu3/hooks/useItemState.ts +63 -0
  72. package/src/react-menu3/hooks/useItems.ts +110 -0
  73. package/src/react-menu3/hooks/useMenuChange.ts +12 -0
  74. package/src/react-menu3/hooks/useMenuState.ts +24 -0
  75. package/src/react-menu3/hooks/useMenuStateAndFocus.ts +15 -0
  76. package/src/react-menu3/index.d.ts +682 -0
  77. package/src/react-menu3/index.ts +11 -0
  78. package/src/react-menu3/positionUtils/getPositionHelpers.ts +76 -0
  79. package/src/react-menu3/positionUtils/index.ts +3 -0
  80. package/src/react-menu3/positionUtils/placeArrowHorizontal.ts +8 -0
  81. package/src/react-menu3/positionUtils/placeArrowVertical.ts +8 -0
  82. package/src/react-menu3/positionUtils/placeLeftorRight.ts +76 -0
  83. package/src/react-menu3/positionUtils/placeToporBottom.ts +77 -0
  84. package/src/react-menu3/positionUtils/positionContextMenu.ts +49 -0
  85. package/src/react-menu3/positionUtils/positionMenu.ts +73 -0
  86. package/src/react-menu3/style-utils/index.ts +80 -0
  87. package/src/react-menu3/style-utils.d.ts +110 -0
  88. package/src/react-menu3/styles/_mixins.scss +11 -0
  89. package/src/react-menu3/styles/_var.scss +20 -0
  90. package/src/react-menu3/styles/core.scss +80 -0
  91. package/src/react-menu3/styles/index.scss +70 -0
  92. package/src/react-menu3/styles/theme-dark.scss +30 -0
  93. package/src/react-menu3/styles/transitions/slide.scss +47 -0
  94. package/src/react-menu3/utils/constants.ts +63 -0
  95. package/src/react-menu3/utils/index.ts +4 -0
  96. package/src/react-menu3/utils/propTypes.ts +61 -0
  97. package/src/react-menu3/utils/utils.ts +71 -0
  98. package/src/react-menu3/utils/withHovering.tsx +22 -0
  99. package/src/stories/components/GridPopoutEditDropDown.stories.tsx +44 -3
  100. package/src/utils/bearing.ts +7 -4
  101. package/src/utils/util.ts +2 -1
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@linzjs/step-ag-grid",
3
3
  "repository": "github:linz/step-ag-grid.git",
4
4
  "license": "MIT",
5
- "version": "1.4.2",
5
+ "version": "1.4.5",
6
6
  "main": "dist/index.js",
7
7
  "typings": "dist/index.d.ts",
8
8
  "module": "dist/step-ag-grid.esm.js",
@@ -14,7 +14,14 @@
14
14
  "access": "public"
15
15
  },
16
16
  "engines": {
17
- "node": ">=10"
17
+ "node": ">=14"
18
+ },
19
+ "peerDependencies": {
20
+ "react": ">=17",
21
+ "react-dom": ">=17",
22
+ "ag-grid-community": ">=27",
23
+ "ag-grid-react": ">=27",
24
+ "@linzjs/lui": ">=17"
18
25
  },
19
26
  "dependencies": {
20
27
  "@linzjs/lui": "^17.18.2",
@@ -24,7 +31,9 @@
24
31
  "lodash-es": "^4.17.21",
25
32
  "react": "^17.0.2",
26
33
  "react-dom": "^17.0.2",
27
- "uuid": "^9.0.0"
34
+ "uuid": "^9.0.0",
35
+ "prop-types": "^15.8.1",
36
+ "react-transition-state": "^1.1.5"
28
37
  },
29
38
  "scripts": {
30
39
  "build": "rollup -c && npm run",
@@ -56,16 +65,15 @@
56
65
  ]
57
66
  },
58
67
  "devDependencies": {
59
- "@rollup/plugin-commonjs": "^21.0.2",
60
- "@rollup/plugin-json": "^4.1.0",
61
- "@rollup/plugin-node-resolve": "^11.2.1",
68
+ "@rollup/plugin-commonjs": "^23.0.2",
69
+ "@rollup/plugin-json": "^5.0.1",
70
+ "@rollup/plugin-node-resolve": "^15.0.1",
62
71
  "@semantic-release/changelog": "^6.0.1",
63
72
  "@semantic-release/git": "^10.0.1",
64
73
  "@storybook/addon-actions": "^6.5.13",
65
74
  "@storybook/addon-essentials": "^6.5.13",
66
75
  "@storybook/addon-interactions": "^6.5.13",
67
76
  "@storybook/addon-links": "^6.5.13",
68
- "storybook-addon-mock": "^2.4.1",
69
77
  "@storybook/jest": "^0.0.10",
70
78
  "@storybook/react": "6.5.13",
71
79
  "@storybook/test-runner": "^0.9.0",
@@ -77,8 +85,8 @@
77
85
  "@types/jest": "^29.2.0",
78
86
  "@types/lodash-es": "^4.17.6",
79
87
  "@types/node": "^18.11.7",
80
- "@types/react": "^18.0.23",
81
- "@types/react-dom": "^18.0.7",
88
+ "@types/react": "^17.0.52",
89
+ "@types/react-dom": "^17.0.18",
82
90
  "@types/uuid": "^8.3.4",
83
91
  "@typescript-eslint/parser": "^5.41.0",
84
92
  "babel-jest": "^26.6.3",
@@ -108,6 +116,7 @@
108
116
  "sass": "^1.55.0",
109
117
  "sass-loader": "10.1.1",
110
118
  "semantic-release": "^19.0.5",
119
+ "storybook-addon-mock": "^2.4.1",
111
120
  "style-loader": "^2.0.0",
112
121
  "tsconfig-paths-webpack-plugin": "^3.5.2",
113
122
  "typescript": "^4.8.4"
@@ -0,0 +1,4 @@
1
+ .GridLoadableCell-container {
2
+ display: flex;
3
+ align-items: center;
4
+ }
@@ -1,9 +1,13 @@
1
+ import "./GridLoadableCell.scss";
2
+
1
3
  import { LuiMiniSpinner } from "@linzjs/lui";
4
+ import clsx from "clsx";
2
5
 
3
6
  export const GridLoadableCell = (props: {
4
7
  isLoading: boolean;
5
8
  dataTestId?: string;
6
9
  children: JSX.Element | string;
10
+ className?: string;
7
11
  }): JSX.Element => {
8
12
  // console.log(`Rendering LoadableCell - loading: ${props.isLoading}`);
9
13
  if (props.isLoading) {
@@ -15,7 +19,7 @@ export const GridLoadableCell = (props: {
15
19
  }
16
20
  // only add test id into ONE of the columns in a grid. this way each row will have one unique id :)
17
21
  return (
18
- <div data-testid={props.dataTestId} style={{ display: "flex", alignItems: "center" }}>
22
+ <div data-testid={props.dataTestId} className={clsx("GridLoadableCell-container", props.className)}>
19
23
  {props.children}
20
24
  </div>
21
25
  );
@@ -1,11 +1,9 @@
1
1
  import { ICellEditorParams } from "ag-grid-community";
2
2
  import { useCallback, useContext, useEffect, useRef, useState } from "react";
3
3
  import { GridContext } from "@contexts/GridContext";
4
- import { ControlledMenu } from "@szhsin/react-menu";
5
4
  import { GridFormProps } from "./GridCell";
6
- import { hasParentClass } from "@utils/util";
7
5
  import { GridBaseRow } from "./Grid";
8
- import { isEmpty } from "lodash-es";
6
+ import { ControlledMenu } from "../react-menu3";
9
7
 
10
8
  export const useGridPopoverHook = <RowType extends GridBaseRow>(
11
9
  props: GridFormProps<RowType>,
@@ -33,51 +31,6 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(
33
31
  [save, stopEditing, updateValue],
34
32
  );
35
33
 
36
- const clickIsWithinMenu = useCallback((ev: MouseEvent) => {
37
- return hasParentClass("szh-menu--state-open", ev.target as Node);
38
- }, []);
39
-
40
- const handleScreenMouseDown = useCallback(
41
- (ev: MouseEvent) => {
42
- if (!clickIsWithinMenu(ev)) {
43
- ev.preventDefault();
44
- ev.stopPropagation();
45
- // There's an issue in React17
46
- // the cell doesn't refresh during update if save is invoked from a native event
47
- // This doesn't happen in React18
48
- // To work around it, I invoke the save by clicking on an invisible button in the dropdown
49
- saveButtonRef.current?.click();
50
- }
51
- },
52
- [clickIsWithinMenu],
53
- );
54
-
55
- const handleScreenMouseEvent = useCallback(
56
- (ev: MouseEvent) => {
57
- if (!clickIsWithinMenu(ev)) {
58
- ev.preventDefault();
59
- ev.stopPropagation();
60
- }
61
- },
62
- [clickIsWithinMenu],
63
- );
64
-
65
- useEffect(() => {
66
- if (isOpen) {
67
- document.addEventListener("mousedown", handleScreenMouseDown, true);
68
- document.addEventListener("mouseup", handleScreenMouseEvent, true);
69
- document.addEventListener("click", handleScreenMouseEvent, true);
70
- document.addEventListener("dblclick", handleScreenMouseEvent, true);
71
- return () => {
72
- document.removeEventListener("mousedown", handleScreenMouseDown, true);
73
- document.removeEventListener("mouseup", handleScreenMouseEvent, true);
74
- document.removeEventListener("click", handleScreenMouseEvent, true);
75
- document.removeEventListener("dblclick", handleScreenMouseEvent, true);
76
- };
77
- }
78
- return () => {};
79
- }, [handleScreenMouseDown, handleScreenMouseEvent, isOpen]);
80
-
81
34
  const popoverWrapper = useCallback(
82
35
  (children: JSX.Element) => {
83
36
  return (
@@ -85,11 +38,12 @@ export const useGridPopoverHook = <RowType extends GridBaseRow>(
85
38
  {anchorRef.current && (
86
39
  <ControlledMenu
87
40
  state={isOpen ? "open" : "closed"}
88
- portal={isEmpty(document.querySelectorAll(".PopoutWindowContainer"))}
41
+ portal={true}
89
42
  unmountOnClose={true}
90
43
  anchorRef={anchorRef}
44
+ saveButtonRef={saveButtonRef}
91
45
  menuClassName={"lui-menu"}
92
- onClose={(event) => triggerSave(event.reason).then()}
46
+ onClose={(event: { reason: string }) => triggerSave(event.reason).then()}
93
47
  >
94
48
  {saving && (
95
49
  <div
@@ -1,6 +1,6 @@
1
- import "@szhsin/react-menu/dist/index.css";
1
+ import "../../react-menu3/styles/index.scss";
2
2
 
3
- import { MenuItem, MenuDivider, FocusableItem } from "@szhsin/react-menu";
3
+ import { MenuItem, MenuDivider, FocusableItem } from "@react-menu3";
4
4
  import { useCallback, useContext, useEffect, useRef, useState, KeyboardEvent } from "react";
5
5
  import { GridBaseRow } from "../Grid";
6
6
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
@@ -182,7 +182,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(props:
182
182
  <MenuDivider key={`$$divider_${index}`} />
183
183
  ) : filteredValues.includes(item.value) ? null : (
184
184
  <MenuItem
185
- key={`${item.value}`}
185
+ key={`${props.field}-${index}`}
186
186
  disabled={!!item.disabled}
187
187
  title={item.disabled && typeof item.disabled !== "boolean" ? item.disabled : ""}
188
188
  value={item.value}
@@ -1,6 +1,6 @@
1
- import "@szhsin/react-menu/dist/index.css";
1
+ import "../../react-menu3/styles/index.scss";
2
2
 
3
- import { MenuItem, MenuDivider, FocusableItem } from "@szhsin/react-menu";
3
+ import { MenuItem, MenuDivider, FocusableItem } from "@react-menu3";
4
4
  import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import { GridBaseRow } from "../Grid";
6
6
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
@@ -137,7 +137,7 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(prop
137
137
  <>
138
138
  <MenuItem
139
139
  key={`${item.value}`}
140
- onClick={(e) => {
140
+ onClick={(e: { keepOpen: boolean }) => {
141
141
  e.keepOpen = true;
142
142
  // onSelectMenuOption(itemIndex, e.value);
143
143
  return false;
@@ -158,7 +158,7 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(prop
158
158
  </MenuItem>
159
159
  {selectedValues.includes(item.value) && item.subComponent && (
160
160
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
161
- {(ref) =>
161
+ {(ref: any) =>
162
162
  item.subComponent &&
163
163
  item.subComponent(
164
164
  {
@@ -2,7 +2,7 @@ import { GridBaseRow } from "../Grid";
2
2
  import { useCallback, useContext, useEffect, useRef, useState } from "react";
3
3
  import { GridContext } from "@contexts/GridContext";
4
4
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
5
- import { MenuDivider, MenuItem } from "@szhsin/react-menu";
5
+ import { MenuDivider, MenuItem } from "@react-menu3";
6
6
  import { GenericCellEditorParams, GridFormProps } from "../GridCell";
7
7
  import { useGridPopoverHook } from "../GridPopoverHook";
8
8
 
@@ -1,7 +1,11 @@
1
1
  @use 'node_modules/@linzjs/lui/dist/scss/Core' as lui;
2
2
 
3
- .GridPopoutMenu-burger svg {
4
- fill: lui.$sea;
3
+ .GridPopoutMenu-burger {
4
+ cursor: pointer;
5
+
6
+ & svg {
7
+ fill: lui.$sea;
8
+ }
5
9
  }
6
10
 
7
11
  .GridPopoutMenu-burgerDisabled svg {
@@ -1,5 +1,5 @@
1
1
  import "./GridPopoverMenu.scss";
2
- import "@szhsin/react-menu/dist/index.css";
2
+ import "../../react-menu3/styles/index.scss";
3
3
 
4
4
  import { ColDef } from "ag-grid-community";
5
5
  import { GenericMultiEditCellClass } from "../GenericCellClass";
@@ -1,6 +1,6 @@
1
- import { ICellRendererParams } from "ag-grid-community";
2
1
  import { useContext } from "react";
3
- import { UpdatingContext } from "../../contexts/UpdatingContext";
2
+ import { ICellRendererParams } from "ag-grid-community";
3
+ import { UpdatingContext } from "@contexts/UpdatingContext";
4
4
  import { GridLoadableCell } from "../GridLoadableCell";
5
5
  import { LuiIcon } from "@linzjs/lui";
6
6
 
@@ -10,13 +10,11 @@ export const GridRenderPopoutMenuCell = (props: ICellRendererParams) => {
10
10
  const disabled = !props.colDef?.editable;
11
11
 
12
12
  return (
13
- <GridLoadableCell isLoading={isLoading}>
14
- <LuiIcon
15
- name={"ic_more_vert"}
16
- alt={"More actions"}
17
- size={"md"}
18
- className={disabled ? `GridPopoutMenu-burgerDisabled` : `GridPopoutMenu-burger`}
19
- />
13
+ <GridLoadableCell
14
+ isLoading={isLoading}
15
+ className={disabled ? `GridPopoutMenu-burgerDisabled` : `GridPopoutMenu-burger`}
16
+ >
17
+ <LuiIcon name={"ic_more_vert"} alt={"More actions"} size={"md"} />
20
18
  </GridLoadableCell>
21
19
  );
22
20
  };
@@ -0,0 +1,260 @@
1
+ // @ts-nocheck
2
+ import { forwardRef, useRef, useMemo, useCallback, useEffect } from "react";
3
+ import { createPortal } from "react-dom";
4
+ import { string, number, bool, func, object, oneOf, oneOfType, exact } from "prop-types";
5
+ import { MenuList } from "./MenuList";
6
+ import { useBEM } from "../hooks";
7
+ import {
8
+ rootMenuPropTypes,
9
+ menuContainerClass,
10
+ mergeProps,
11
+ safeCall,
12
+ isMenuOpen,
13
+ getTransition,
14
+ values,
15
+ CloseReason,
16
+ Keys,
17
+ MenuStateMap,
18
+ EventHandlersContext,
19
+ SettingsContext,
20
+ ItemSettingsContext,
21
+ } from "../utils";
22
+ import { hasParentClass } from "@utils/util";
23
+
24
+ export const ControlledMenu = forwardRef(function ControlledMenu(
25
+ {
26
+ "aria-label": ariaLabel,
27
+ className,
28
+ containerProps,
29
+ initialMounted,
30
+ unmountOnClose,
31
+ transition,
32
+ transitionTimeout,
33
+ boundingBoxRef,
34
+ boundingBoxPadding,
35
+ reposition = "auto",
36
+ submenuOpenDelay = 300,
37
+ submenuCloseDelay = 150,
38
+ skipOpen,
39
+ viewScroll = "initial",
40
+ portal,
41
+ theming,
42
+ onItemClick,
43
+ onClose,
44
+ saveButtonRef,
45
+ ...restProps
46
+ }: any,
47
+ externalRef,
48
+ ) {
49
+ const containerRef = useRef(null);
50
+ const scrollNodesRef = useRef({});
51
+ const { anchorRef, state } = restProps;
52
+
53
+ const settings = useMemo(
54
+ () => ({
55
+ initialMounted,
56
+ unmountOnClose,
57
+ transition,
58
+ transitionTimeout,
59
+ boundingBoxRef,
60
+ boundingBoxPadding,
61
+ rootMenuRef: containerRef,
62
+ rootAnchorRef: anchorRef,
63
+ scrollNodesRef,
64
+ reposition,
65
+ viewScroll,
66
+ }),
67
+ [
68
+ initialMounted,
69
+ unmountOnClose,
70
+ transition,
71
+ transitionTimeout,
72
+ anchorRef,
73
+ boundingBoxRef,
74
+ boundingBoxPadding,
75
+ reposition,
76
+ viewScroll,
77
+ ],
78
+ );
79
+
80
+ const clickIsWithinMenu = useCallback((ev: MouseEvent) => {
81
+ return hasParentClass("szh-menu--state-open", ev.target as Node);
82
+ }, []);
83
+
84
+ const handleScreenEventForSave = useCallback(
85
+ (ev: MouseEvent) => {
86
+ if (!clickIsWithinMenu(ev)) {
87
+ //!ev.currentTarget.contains(ev.relatedTarget || document.activeElement)) {
88
+ ev.preventDefault();
89
+ ev.stopPropagation();
90
+ // FIXME There's an issue in React17
91
+ // the cell doesn't refresh during update if save is invoked from a native event
92
+ // This doesn't happen in React18
93
+ // To work around it, I invoke the save by clicking on a passed in invisible button ref
94
+ if (saveButtonRef.current) saveButtonRef.current.click();
95
+ else safeCall(onClose, { reason: CloseReason.BLUR });
96
+
97
+ // If a user clicks on the menu button when a menu is open, we need to close the menu.
98
+ // However, a blur event will be fired prior to the click event on menu button,
99
+ // which makes the menu first close and then open again.
100
+ // If this happens, e.relatedTarget is incorrectly set to null instead of the button in Safari and Firefox,
101
+ // and makes it difficult to determine whether onBlur is fired because of clicking on menu button.
102
+ // This is a workaround approach which sets a flag to skip a following click event.
103
+ if (skipOpen) {
104
+ skipOpen.current = true;
105
+ setTimeout(() => (skipOpen.current = false), 300);
106
+ }
107
+ }
108
+ },
109
+ [clickIsWithinMenu, onClose],
110
+ );
111
+
112
+ const handleScreenEventForCancel = useCallback(
113
+ (ev: MouseEvent) => {
114
+ if (!clickIsWithinMenu(ev)) {
115
+ ev.preventDefault();
116
+ ev.stopPropagation();
117
+ }
118
+ },
119
+ [clickIsWithinMenu],
120
+ );
121
+
122
+ useEffect(() => {
123
+ if (isMenuOpen(state)) {
124
+ const thisDocument = anchorRef.current ? anchorRef.current.ownerDocument : document;
125
+ thisDocument.addEventListener("mousedown", handleScreenEventForCancel, true);
126
+ thisDocument.addEventListener("mouseup", handleScreenEventForSave, true);
127
+ thisDocument.addEventListener("click", handleScreenEventForCancel, true);
128
+ thisDocument.addEventListener("dblclick", handleScreenEventForCancel, true);
129
+ return () => {
130
+ thisDocument.removeEventListener("mousedown", handleScreenEventForCancel, true);
131
+ thisDocument.removeEventListener("mouseup", handleScreenEventForSave, true);
132
+ thisDocument.removeEventListener("click", handleScreenEventForCancel, true);
133
+ thisDocument.removeEventListener("dblclick", handleScreenEventForCancel, true);
134
+ };
135
+ }
136
+ return () => {};
137
+ }, [handleScreenEventForSave, handleScreenEventForCancel, state]);
138
+
139
+ const itemSettings = useMemo(
140
+ () => ({
141
+ submenuOpenDelay,
142
+ submenuCloseDelay,
143
+ }),
144
+ [submenuOpenDelay, submenuCloseDelay],
145
+ );
146
+
147
+ const eventHandlers = useMemo(
148
+ () => ({
149
+ handleClick(event, isCheckorRadio) {
150
+ if (!event.stopPropagation) safeCall(onItemClick, event);
151
+
152
+ let keepOpen = event.keepOpen;
153
+ if (keepOpen === undefined) {
154
+ // if event.keepOpen is undefined, the following default behaviour is used
155
+ // According to WAI-ARIA Authoring Practices 1.1
156
+ // Keep menu open when check or radio is invoked by SPACE key
157
+ keepOpen = isCheckorRadio && event.key === Keys.SPACE;
158
+ }
159
+
160
+ if (!keepOpen) {
161
+ safeCall(onClose, {
162
+ value: event.value,
163
+ key: event.key,
164
+ reason: CloseReason.CLICK,
165
+ });
166
+ }
167
+ },
168
+
169
+ handleClose(key) {
170
+ safeCall(onClose, { key, reason: CloseReason.CLICK });
171
+ },
172
+ }),
173
+ [onItemClick, onClose],
174
+ );
175
+
176
+ const onKeyDown = ({ key }) => {
177
+ switch (key) {
178
+ case Keys.ESC:
179
+ safeCall(onClose, { key, reason: CloseReason.CANCEL });
180
+ break;
181
+ }
182
+ };
183
+
184
+ const onBlur = (e) => {
185
+ if (isMenuOpen(state) && !e.currentTarget.contains(e.relatedTarget || document.activeElement)) {
186
+ safeCall(onClose, { reason: CloseReason.BLUR });
187
+
188
+ // If a user clicks on the menu button when a menu is open, we need to close the menu.
189
+ // However, a blur event will be fired prior to the click event on menu button,
190
+ // which makes the menu first close and then open again.
191
+ // If this happen, e.relatedTarget is incorrectly set to null instead of the button in Safari and Firefox,
192
+ // and makes it difficult to determine whether onBlur is fired because of clicking on menu button.
193
+ // This is a workaround approach which sets a flag to skip a following click event.
194
+ if (skipOpen) {
195
+ skipOpen.current = true;
196
+ setTimeout(() => (skipOpen.current = false), 300);
197
+ }
198
+ }
199
+ };
200
+
201
+ const itemTransition = getTransition(transition, "item");
202
+ const modifiers = useMemo(() => ({ theme: theming, itemTransition }), [theming, itemTransition]);
203
+
204
+ const menuList = (
205
+ <div
206
+ {...mergeProps({ onKeyDown, onBlur }, containerProps)}
207
+ className={useBEM({
208
+ block: menuContainerClass,
209
+ modifiers,
210
+ className,
211
+ })}
212
+ style={{ ...containerProps?.style, position: "relative" }}
213
+ ref={containerRef}
214
+ >
215
+ {state && (
216
+ <SettingsContext.Provider value={settings}>
217
+ <ItemSettingsContext.Provider value={itemSettings}>
218
+ <EventHandlersContext.Provider value={eventHandlers}>
219
+ <MenuList
220
+ {...restProps}
221
+ ariaLabel={ariaLabel || "Menu"}
222
+ externalRef={externalRef}
223
+ containerRef={containerRef}
224
+ onClose={onClose}
225
+ />
226
+ </EventHandlersContext.Provider>
227
+ </ItemSettingsContext.Provider>
228
+ </SettingsContext.Provider>
229
+ )}
230
+ </div>
231
+ );
232
+
233
+ if (portal === true && anchorRef.current !== undefined) {
234
+ portal = { target: anchorRef.current.ownerDocument.body };
235
+ }
236
+
237
+ if (portal === true && typeof document !== "undefined") {
238
+ return createPortal(menuList, document.body);
239
+ } else if (portal) {
240
+ return portal.target ? createPortal(menuList, portal.target) : portal.stablePosition ? null : menuList;
241
+ }
242
+ return menuList;
243
+ });
244
+
245
+ ControlledMenu.propTypes /* remove-proptypes */ = {
246
+ ...rootMenuPropTypes,
247
+ state: oneOf(values(MenuStateMap)),
248
+ anchorPoint: exact({
249
+ x: number,
250
+ y: number,
251
+ }),
252
+ anchorRef: object,
253
+ skipOpen: object,
254
+ captureFocus: bool,
255
+ menuItemFocus: exact({
256
+ position: oneOfType([string, number]),
257
+ alwaysUpdate: bool,
258
+ }),
259
+ onClose: func,
260
+ };
@@ -0,0 +1,70 @@
1
+ // @ts-nocheck
2
+ import { useContext, useMemo, useRef } from "react";
3
+ import { bool, func } from "prop-types";
4
+ import { useBEM, useCombinedRef, useItemState } from "../hooks";
5
+ import {
6
+ mergeProps,
7
+ commonProps,
8
+ safeCall,
9
+ menuClass,
10
+ menuItemClass,
11
+ stylePropTypes,
12
+ withHovering,
13
+ EventHandlersContext,
14
+ } from "../utils";
15
+
16
+ export const FocusableItem = withHovering(
17
+ "FocusableItem",
18
+ function FocusableItem({ className, disabled, children, isHovering, itemRef, externalRef, ...restProps }) {
19
+ const isDisabled = !!disabled;
20
+ const ref = useRef(null);
21
+ const { setHover, onPointerLeave, ...restStateProps } = useItemState(itemRef, ref, isHovering, isDisabled);
22
+ const { handleClose } = useContext(EventHandlersContext);
23
+
24
+ const modifiers = useMemo(
25
+ () => ({
26
+ disabled: isDisabled,
27
+ hover: isHovering,
28
+ focusable: true,
29
+ }),
30
+ [isDisabled, isHovering],
31
+ );
32
+
33
+ const renderChildren = useMemo(
34
+ () =>
35
+ safeCall(children, {
36
+ ...modifiers,
37
+ ref,
38
+ closeMenu: handleClose,
39
+ }),
40
+ [children, modifiers, handleClose],
41
+ );
42
+
43
+ const mergedProps = mergeProps(
44
+ {
45
+ ...restStateProps,
46
+ onPointerLeave: (e) => onPointerLeave(e, true),
47
+ onFocus: setHover,
48
+ },
49
+ restProps,
50
+ );
51
+
52
+ return (
53
+ <li
54
+ role="menuitem"
55
+ {...mergedProps}
56
+ {...commonProps(isDisabled)}
57
+ ref={useCombinedRef(externalRef, itemRef)}
58
+ className={useBEM({ block: menuClass, element: menuItemClass, modifiers, className })}
59
+ >
60
+ {renderChildren}
61
+ </li>
62
+ );
63
+ },
64
+ );
65
+
66
+ FocusableItem.propTypes = {
67
+ ...stylePropTypes(),
68
+ disabled: bool,
69
+ children: func,
70
+ };