@linzjs/step-ag-grid 1.4.4 → 1.4.6

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 (102) hide show
  1. package/dist/index.js +1631 -3040
  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 +9 -0
  9. package/dist/src/react-menu3/components/Menu.d.ts +16 -0
  10. package/dist/src/react-menu3/components/MenuButton.d.ts +8 -0
  11. package/dist/src/react-menu3/components/MenuDivider.d.ts +3 -0
  12. package/dist/src/react-menu3/components/MenuGroup.d.ts +12 -0
  13. package/dist/src/react-menu3/components/MenuHeader.d.ts +4 -0
  14. package/dist/src/react-menu3/components/MenuItem.d.ts +59 -0
  15. package/dist/src/react-menu3/components/MenuList.d.ts +57 -0
  16. package/dist/src/react-menu3/components/MenuRadioGroup.d.ts +44 -0
  17. package/dist/src/react-menu3/components/SubMenu.d.ts +110 -0
  18. package/dist/src/react-menu3/hooks/index.d.ts +9 -0
  19. package/dist/src/react-menu3/hooks/useBEM.d.ts +10 -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 +7 -0
  24. package/dist/src/react-menu3/hooks/useItems.d.ts +7 -0
  25. package/dist/src/react-menu3/hooks/useMenuChange.d.ts +2 -0
  26. package/dist/src/react-menu3/hooks/useMenuState.d.ts +14 -0
  27. package/dist/src/react-menu3/hooks/useMenuStateAndFocus.d.ts +2 -0
  28. package/dist/src/react-menu3/index.d.ts +402 -0
  29. package/dist/src/react-menu3/positionUtils/getPositionHelpers.d.ts +11 -0
  30. package/dist/src/react-menu3/positionUtils/index.d.ts +3 -0
  31. package/dist/src/react-menu3/positionUtils/placeArrowHorizontal.d.ts +8 -0
  32. package/dist/src/react-menu3/positionUtils/placeArrowVertical.d.ts +8 -0
  33. package/dist/src/react-menu3/positionUtils/placeLeftorRight.d.ts +19 -0
  34. package/dist/src/react-menu3/positionUtils/placeToporBottom.d.ts +19 -0
  35. package/dist/src/react-menu3/positionUtils/positionContextMenu.d.ts +13 -0
  36. package/dist/src/react-menu3/positionUtils/positionMenu.d.ts +26 -0
  37. package/dist/src/react-menu3/style-utils/index.d.ts +11 -0
  38. package/dist/src/react-menu3/utils/constants.d.ts +92 -0
  39. package/dist/src/react-menu3/utils/index.d.ts +4 -0
  40. package/dist/src/react-menu3/utils/propTypes.d.ts +56 -0
  41. package/dist/src/react-menu3/utils/utils.d.ts +36 -0
  42. package/dist/src/react-menu3/utils/withHovering.d.ts +7 -0
  43. package/dist/src/stories/components/ReactMenu.stories.d.ts +8 -0
  44. package/dist/step-ag-grid.esm.js +1543 -2951
  45. package/dist/step-ag-grid.esm.js.map +1 -1
  46. package/package.json +22 -13
  47. package/src/components/GridLoadableCell.scss +4 -0
  48. package/src/components/GridLoadableCell.tsx +5 -1
  49. package/src/components/GridPopoverHook.tsx +4 -50
  50. package/src/components/gridForm/GridFormDropDown.tsx +3 -3
  51. package/src/components/gridForm/GridFormMultiSelect.tsx +12 -6
  52. package/src/components/gridForm/GridFormPopoutMenu.tsx +1 -1
  53. package/src/components/gridPopoverEdit/GridPopoverMenu.scss +6 -2
  54. package/src/components/gridPopoverEdit/GridPopoverMenu.tsx +1 -1
  55. package/src/components/gridRender/GridRenderPopoutMenuCell.tsx +7 -9
  56. package/src/react-menu3/components/ControlledMenu.tsx +261 -0
  57. package/src/react-menu3/components/FocusableItem.tsx +75 -0
  58. package/src/react-menu3/components/Menu.tsx +108 -0
  59. package/src/react-menu3/components/MenuButton.tsx +34 -0
  60. package/src/react-menu3/components/MenuDivider.tsx +17 -0
  61. package/src/react-menu3/components/MenuGroup.tsx +47 -0
  62. package/src/react-menu3/components/MenuHeader.tsx +17 -0
  63. package/src/react-menu3/components/MenuItem.tsx +183 -0
  64. package/src/react-menu3/components/MenuList.tsx +502 -0
  65. package/src/react-menu3/components/MenuRadioGroup.tsx +72 -0
  66. package/src/react-menu3/components/SubMenu.tsx +349 -0
  67. package/src/react-menu3/hooks/index.ts +9 -0
  68. package/src/react-menu3/hooks/useBEM.ts +33 -0
  69. package/src/react-menu3/hooks/useCombinedRef.ts +24 -0
  70. package/src/react-menu3/hooks/useIsomorphicLayoutEffect.ts +15 -0
  71. package/src/react-menu3/hooks/useItemEffect.ts +23 -0
  72. package/src/react-menu3/hooks/useItemState.ts +67 -0
  73. package/src/react-menu3/hooks/useItems.ts +111 -0
  74. package/src/react-menu3/hooks/useMenuChange.ts +12 -0
  75. package/src/react-menu3/hooks/useMenuState.ts +42 -0
  76. package/src/react-menu3/hooks/useMenuStateAndFocus.ts +17 -0
  77. package/src/react-menu3/index.d.ts +683 -0
  78. package/src/react-menu3/index.ts +442 -0
  79. package/src/react-menu3/positionUtils/getPositionHelpers.ts +83 -0
  80. package/src/react-menu3/positionUtils/index.ts +3 -0
  81. package/src/react-menu3/positionUtils/placeArrowHorizontal.ts +15 -0
  82. package/src/react-menu3/positionUtils/placeArrowVertical.ts +15 -0
  83. package/src/react-menu3/positionUtils/placeLeftorRight.ts +92 -0
  84. package/src/react-menu3/positionUtils/placeToporBottom.ts +93 -0
  85. package/src/react-menu3/positionUtils/positionContextMenu.ts +61 -0
  86. package/src/react-menu3/positionUtils/positionMenu.ts +80 -0
  87. package/src/react-menu3/style-utils/index.ts +83 -0
  88. package/src/react-menu3/style-utils.d.ts +110 -0
  89. package/src/react-menu3/styles/_mixins.scss +11 -0
  90. package/src/react-menu3/styles/_var.scss +20 -0
  91. package/src/react-menu3/styles/core.scss +80 -0
  92. package/src/react-menu3/styles/index.scss +70 -0
  93. package/src/react-menu3/styles/theme-dark.scss +30 -0
  94. package/src/react-menu3/styles/transitions/slide.scss +47 -0
  95. package/src/react-menu3/utils/constants.ts +116 -0
  96. package/src/react-menu3/utils/index.ts +4 -0
  97. package/src/react-menu3/utils/propTypes.ts +63 -0
  98. package/src/react-menu3/utils/utils.ts +88 -0
  99. package/src/react-menu3/utils/withHovering.tsx +27 -0
  100. package/src/stories/components/GridPopoutEditDropDown.stories.tsx +1 -3
  101. package/src/stories/components/ReactMenu.stories.tsx +37 -0
  102. 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.4",
5
+ "version": "1.4.6",
6
6
  "main": "dist/index.js",
7
7
  "typings": "dist/index.d.ts",
8
8
  "module": "dist/step-ag-grid.esm.js",
@@ -14,16 +14,25 @@
14
14
  "access": "public"
15
15
  },
16
16
  "engines": {
17
- "node": ">=10"
17
+ "node": ">=14"
18
+ },
19
+ "peerDependencies": {
20
+ "@linzjs/lui": ">=17",
21
+ "ag-grid-community": ">=27",
22
+ "ag-grid-react": ">=27",
23
+ "react": ">=17",
24
+ "react-dom": ">=17"
18
25
  },
19
26
  "dependencies": {
20
- "@linzjs/lui": "^17.18.2",
21
- "ag-grid-community": "^27.3.0",
22
- "ag-grid-react": "^27.3.0",
27
+ "@linzjs/lui": ">=17",
28
+ "ag-grid-community": ">=27",
29
+ "ag-grid-react": ">=27",
23
30
  "debounce-promise": "^3.1.2",
24
31
  "lodash-es": "^4.17.21",
25
- "react": "^17.0.2",
26
- "react-dom": "^17.0.2",
32
+ "prop-types": "^15.8.1",
33
+ "react": ">=17",
34
+ "react-dom": ">=17",
35
+ "react-transition-state": "^1.1.5",
27
36
  "uuid": "^9.0.0"
28
37
  },
29
38
  "scripts": {
@@ -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";
@@ -153,7 +153,7 @@ export const GridFormDropDown = <RowType extends GridBaseRow, ValueType>(props:
153
153
  <>
154
154
  {formProps.filtered && (
155
155
  <>
156
- <FocusableItem className={"filter-item"} index={-1}>
156
+ <FocusableItem className={"filter-item"}>
157
157
  {({ ref }: any) => (
158
158
  <div style={{ display: "flex", width: "100%" }}>
159
159
  <input
@@ -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, RadioChangeEvent } from "@react-menu3";
4
4
  import { useCallback, useEffect, useRef, useState } from "react";
5
5
  import { GridBaseRow } from "../Grid";
6
6
  import { ComponentLoadingWrapper } from "../ComponentLoadingWrapper";
@@ -58,7 +58,7 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(prop
58
58
  },
59
59
  [formProps, selectedValues],
60
60
  );
61
- const { popoverWrapper } = useGridPopoverHook(props, save);
61
+ const { popoverWrapper, triggerSave } = useGridPopoverHook(props, save);
62
62
 
63
63
  // Load up options list if it's async function
64
64
  useEffect(() => {
@@ -137,11 +137,17 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(prop
137
137
  <>
138
138
  <MenuItem
139
139
  key={`${item.value}`}
140
- onClick={(e) => {
140
+ onClick={(e: RadioChangeEvent) => {
141
+ // FIXME Matt Event type guessed here
141
142
  e.keepOpen = true;
142
- // onSelectMenuOption(itemIndex, e.value);
143
+ if (selectedValues.includes(e.value)) {
144
+ setSelectedValues(selectedValues.filter((value) => value != item.value));
145
+ } else {
146
+ setSelectedValues([...selectedValues, item.value as string]);
147
+ }
143
148
  return false;
144
149
  }}
150
+ onKeyDown={async (e) => e.key === "Enter" && triggerSave().then()}
145
151
  >
146
152
  <LuiCheckboxInput
147
153
  isChecked={selectedValues.includes(item.value)}
@@ -158,7 +164,7 @@ export const GridFormMultiSelect = <RowType extends GridBaseRow, ValueType>(prop
158
164
  </MenuItem>
159
165
  {selectedValues.includes(item.value) && item.subComponent && (
160
166
  <FocusableItem className={"LuiDeprecatedForms"} key={`${item.value}_subcomponent`}>
161
- {(ref) =>
167
+ {(ref: any) =>
162
168
  item.subComponent &&
163
169
  item.subComponent(
164
170
  {
@@ -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,261 @@
1
+ // @ts-nocheck
2
+ /* eslint-disable */
3
+ import { forwardRef, useRef, useMemo, useCallback, useEffect } from "react";
4
+ import { createPortal } from "react-dom";
5
+ import { string, number, bool, func, object, oneOf, oneOfType, exact } from "prop-types";
6
+ import { MenuList } from "./MenuList";
7
+ import { useBEM } from "../hooks";
8
+ import {
9
+ rootMenuPropTypes,
10
+ menuContainerClass,
11
+ mergeProps,
12
+ safeCall,
13
+ isMenuOpen,
14
+ getTransition,
15
+ values,
16
+ CloseReason,
17
+ Keys,
18
+ MenuStateMap,
19
+ EventHandlersContext,
20
+ SettingsContext,
21
+ ItemSettingsContext,
22
+ } from "../utils";
23
+ import { hasParentClass } from "@utils/util";
24
+
25
+ export const ControlledMenu = forwardRef(function ControlledMenu(
26
+ {
27
+ "aria-label": ariaLabel,
28
+ className,
29
+ containerProps,
30
+ initialMounted,
31
+ unmountOnClose,
32
+ transition,
33
+ transitionTimeout,
34
+ boundingBoxRef,
35
+ boundingBoxPadding,
36
+ reposition = "auto",
37
+ submenuOpenDelay = 300,
38
+ submenuCloseDelay = 150,
39
+ skipOpen,
40
+ viewScroll = "initial",
41
+ portal,
42
+ theming,
43
+ onItemClick,
44
+ onClose,
45
+ saveButtonRef,
46
+ ...restProps
47
+ }: any,
48
+ externalRef,
49
+ ) {
50
+ const containerRef = useRef(null);
51
+ const scrollNodesRef = useRef<{anchors?:HTMLDivElement[]}>({});
52
+ const { anchorRef, state } = restProps;
53
+
54
+ const settings = useMemo(
55
+ () => ({
56
+ initialMounted,
57
+ unmountOnClose,
58
+ transition,
59
+ transitionTimeout,
60
+ boundingBoxRef,
61
+ boundingBoxPadding,
62
+ rootMenuRef: containerRef,
63
+ rootAnchorRef: anchorRef,
64
+ scrollNodesRef,
65
+ reposition,
66
+ viewScroll,
67
+ }),
68
+ [
69
+ initialMounted,
70
+ unmountOnClose,
71
+ transition,
72
+ transitionTimeout,
73
+ anchorRef,
74
+ boundingBoxRef,
75
+ boundingBoxPadding,
76
+ reposition,
77
+ viewScroll,
78
+ ],
79
+ );
80
+
81
+ const clickIsWithinMenu = useCallback((ev: MouseEvent) => {
82
+ return hasParentClass("szh-menu--state-open", ev.target as Node);
83
+ }, []);
84
+
85
+ const handleScreenEventForSave = useCallback(
86
+ (ev: MouseEvent) => {
87
+ if (!clickIsWithinMenu(ev)) {
88
+ //!ev.currentTarget.contains(ev.relatedTarget || document.activeElement)) {
89
+ ev.preventDefault();
90
+ ev.stopPropagation();
91
+ // FIXME There's an issue in React17
92
+ // the cell doesn't refresh during update if save is invoked from a native event
93
+ // This doesn't happen in React18
94
+ // To work around it, I invoke the save by clicking on a passed in invisible button ref
95
+ if (saveButtonRef.current) saveButtonRef.current.click();
96
+ else safeCall(onClose, { reason: CloseReason.BLUR });
97
+
98
+ // If a user clicks on the menu button when a menu is open, we need to close the menu.
99
+ // However, a blur event will be fired prior to the click event on menu button,
100
+ // which makes the menu first close and then open again.
101
+ // If this happens, e.relatedTarget is incorrectly set to null instead of the button in Safari and Firefox,
102
+ // and makes it difficult to determine whether onBlur is fired because of clicking on menu button.
103
+ // This is a workaround approach which sets a flag to skip a following click event.
104
+ if (skipOpen) {
105
+ skipOpen.current = true;
106
+ setTimeout(() => (skipOpen.current = false), 300);
107
+ }
108
+ }
109
+ },
110
+ [clickIsWithinMenu, onClose],
111
+ );
112
+
113
+ const handleScreenEventForCancel = useCallback(
114
+ (ev: MouseEvent) => {
115
+ if (!clickIsWithinMenu(ev)) {
116
+ ev.preventDefault();
117
+ ev.stopPropagation();
118
+ }
119
+ },
120
+ [clickIsWithinMenu],
121
+ );
122
+
123
+ useEffect(() => {
124
+ if (isMenuOpen(state)) {
125
+ const thisDocument = anchorRef.current ? anchorRef.current.ownerDocument : document;
126
+ thisDocument.addEventListener("mousedown", handleScreenEventForCancel, true);
127
+ thisDocument.addEventListener("mouseup", handleScreenEventForSave, true);
128
+ thisDocument.addEventListener("click", handleScreenEventForCancel, true);
129
+ thisDocument.addEventListener("dblclick", handleScreenEventForCancel, true);
130
+ return () => {
131
+ thisDocument.removeEventListener("mousedown", handleScreenEventForCancel, true);
132
+ thisDocument.removeEventListener("mouseup", handleScreenEventForSave, true);
133
+ thisDocument.removeEventListener("click", handleScreenEventForCancel, true);
134
+ thisDocument.removeEventListener("dblclick", handleScreenEventForCancel, true);
135
+ };
136
+ }
137
+ return () => {};
138
+ }, [handleScreenEventForSave, handleScreenEventForCancel, state]);
139
+
140
+ const itemSettings = useMemo(
141
+ () => ({
142
+ submenuOpenDelay,
143
+ submenuCloseDelay,
144
+ }),
145
+ [submenuOpenDelay, submenuCloseDelay],
146
+ );
147
+
148
+ const eventHandlers = useMemo(
149
+ () => ({
150
+ handleClick(event, isCheckorRadio) {
151
+ if (!event.stopPropagation) safeCall(onItemClick, event);
152
+
153
+ let keepOpen = event.keepOpen;
154
+ if (keepOpen === undefined) {
155
+ // if event.keepOpen is undefined, the following default behaviour is used
156
+ // According to WAI-ARIA Authoring Practices 1.1
157
+ // Keep menu open when check or radio is invoked by SPACE key
158
+ keepOpen = isCheckorRadio && event.key === Keys.SPACE;
159
+ }
160
+
161
+ if (!keepOpen) {
162
+ safeCall(onClose, {
163
+ value: event.value,
164
+ key: event.key,
165
+ reason: CloseReason.CLICK,
166
+ });
167
+ }
168
+ },
169
+
170
+ handleClose(key) {
171
+ safeCall(onClose, { key, reason: CloseReason.CLICK });
172
+ },
173
+ }),
174
+ [onItemClick, onClose],
175
+ );
176
+
177
+ const onKeyDown = ({ key }) => {
178
+ switch (key) {
179
+ case Keys.ESC:
180
+ safeCall(onClose, { key, reason: CloseReason.CANCEL });
181
+ break;
182
+ }
183
+ };
184
+
185
+ const onBlur = (e) => {
186
+ if (isMenuOpen(state) && !e.currentTarget.contains(e.relatedTarget || document.activeElement)) {
187
+ safeCall(onClose, { reason: CloseReason.BLUR });
188
+
189
+ // If a user clicks on the menu button when a menu is open, we need to close the menu.
190
+ // However, a blur event will be fired prior to the click event on menu button,
191
+ // which makes the menu first close and then open again.
192
+ // If this happen, e.relatedTarget is incorrectly set to null instead of the button in Safari and Firefox,
193
+ // and makes it difficult to determine whether onBlur is fired because of clicking on menu button.
194
+ // This is a workaround approach which sets a flag to skip a following click event.
195
+ if (skipOpen) {
196
+ skipOpen.current = true;
197
+ setTimeout(() => (skipOpen.current = false), 300);
198
+ }
199
+ }
200
+ };
201
+
202
+ const itemTransition = getTransition(transition, "item");
203
+ const modifiers = useMemo(() => ({ theme: theming, itemTransition }), [theming, itemTransition]);
204
+
205
+ const menuList = (
206
+ <div
207
+ {...mergeProps({ onKeyDown, onBlur }, containerProps)}
208
+ className={useBEM({
209
+ block: menuContainerClass,
210
+ modifiers,
211
+ className,
212
+ })}
213
+ style={{ ...containerProps?.style, position: "relative" }}
214
+ ref={containerRef}
215
+ >
216
+ {state && (
217
+ <SettingsContext.Provider value={settings}>
218
+ <ItemSettingsContext.Provider value={itemSettings}>
219
+ <EventHandlersContext.Provider value={eventHandlers}>
220
+ <MenuList
221
+ {...restProps}
222
+ ariaLabel={ariaLabel || "Menu"}
223
+ externalRef={externalRef}
224
+ containerRef={containerRef}
225
+ onClose={onClose}
226
+ />
227
+ </EventHandlersContext.Provider>
228
+ </ItemSettingsContext.Provider>
229
+ </SettingsContext.Provider>
230
+ )}
231
+ </div>
232
+ );
233
+
234
+ if (portal === true && anchorRef.current !== undefined) {
235
+ portal = { target: anchorRef.current.ownerDocument.body };
236
+ }
237
+
238
+ if (portal === true && typeof document !== "undefined") {
239
+ return createPortal(menuList, document.body);
240
+ } else if (portal) {
241
+ return portal.target ? createPortal(menuList, portal.target) : portal.stablePosition ? null : menuList;
242
+ }
243
+ return menuList;
244
+ });
245
+
246
+ ControlledMenu.propTypes /* remove-proptypes */ = {
247
+ ...rootMenuPropTypes,
248
+ state: oneOf(values(MenuStateMap)),
249
+ anchorPoint: exact({
250
+ x: number,
251
+ y: number,
252
+ }),
253
+ anchorRef: object,
254
+ skipOpen: object,
255
+ captureFocus: bool,
256
+ menuItemFocus: exact({
257
+ position: oneOfType([string, number]),
258
+ alwaysUpdate: bool,
259
+ }),
260
+ onClose: func,
261
+ };
@@ -0,0 +1,75 @@
1
+ import { LegacyRef, useContext, useMemo, useRef } from "react";
2
+ import { useBEM, useCombinedRef, useItemState } from "../hooks";
3
+ import {
4
+ mergeProps,
5
+ commonProps,
6
+ safeCall,
7
+ menuClass,
8
+ menuItemClass,
9
+ withHovering,
10
+ EventHandlersContext,
11
+ } from "../utils";
12
+ import { BaseProps } from "../index";
13
+ import { withHoveringResultProps } from "../utils/withHovering";
14
+
15
+ export interface FocusableItemProps extends BaseProps, withHoveringResultProps {
16
+ disabled?: boolean;
17
+ children: (ref: LegacyRef<any>) => JSX.Element;
18
+ }
19
+
20
+ export const FocusableItemFr = ({
21
+ className,
22
+ disabled,
23
+ children,
24
+ isHovering,
25
+ menuItemRef,
26
+ externalRef,
27
+ ...restProps
28
+ }: FocusableItemProps) => {
29
+ const isDisabled = !!disabled;
30
+ const ref = useRef(null);
31
+ const { setHover, onPointerLeave, ...restStateProps } = useItemState(menuItemRef, ref, isHovering, isDisabled);
32
+ const { handleClose } = useContext(EventHandlersContext);
33
+
34
+ const modifiers = useMemo(
35
+ () => ({
36
+ disabled: isDisabled,
37
+ hover: isHovering,
38
+ focusable: true,
39
+ }),
40
+ [isDisabled, isHovering],
41
+ );
42
+
43
+ const renderChildren = useMemo(
44
+ () =>
45
+ safeCall(children, {
46
+ ...modifiers,
47
+ ref,
48
+ closeMenu: handleClose,
49
+ }),
50
+ [children, modifiers, handleClose],
51
+ );
52
+
53
+ const mergedProps = mergeProps(
54
+ {
55
+ ...restStateProps,
56
+ onPointerLeave: (e: PointerEvent) => onPointerLeave(e, true),
57
+ onFocus: setHover,
58
+ },
59
+ restProps,
60
+ );
61
+
62
+ return (
63
+ <li
64
+ role="menuitem"
65
+ {...mergedProps}
66
+ {...commonProps(isDisabled)}
67
+ ref={useCombinedRef(externalRef, menuItemRef)}
68
+ className={useBEM({ block: menuClass, element: menuItemClass, modifiers, className })}
69
+ >
70
+ {renderChildren}
71
+ </li>
72
+ );
73
+ };
74
+
75
+ export const FocusableItem = withHovering("FocusableItem", FocusableItemFr) as any as typeof FocusableItemFr;