@dmsi/wedgekit-react 0.0.185 → 0.0.187

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.
@@ -19,6 +19,10 @@ function useSubMenuSystem(mobilePositionTo) {
19
19
  const subMenuRefs = useRef({});
20
20
  const hoverTimeoutRef = useRef(null);
21
21
  const closeTimeoutRef = useRef(null);
22
+ const mouseStopTimeoutRef = useRef(null);
23
+ const isMouseMovingRef = useRef(false);
24
+ const pendingOpenActionRef = useRef(null);
25
+ const pendingCloseActionRef = useRef(null);
22
26
  const isMobile = useMatchesMobile();
23
27
  const toggleMenu = (menuId, level) => {
24
28
  if (closeTimeoutRef.current) {
@@ -58,36 +62,67 @@ function useSubMenuSystem(mobilePositionTo) {
58
62
  return newActiveMenus;
59
63
  });
60
64
  };
65
+ const executePendingActions = useCallback(() => {
66
+ if (pendingCloseActionRef.current) {
67
+ pendingCloseActionRef.current();
68
+ pendingCloseActionRef.current = null;
69
+ }
70
+ if (pendingOpenActionRef.current) {
71
+ pendingOpenActionRef.current();
72
+ pendingOpenActionRef.current = null;
73
+ }
74
+ }, []);
61
75
  const openMenuWithDelay = (menuId, level, delay = 150) => {
62
76
  if (isMobile) {
63
77
  return;
64
78
  }
65
- if (hoverTimeoutRef.current) {
66
- clearTimeout(hoverTimeoutRef.current);
79
+ pendingOpenActionRef.current = () => {
80
+ if (hoverTimeoutRef.current) {
81
+ clearTimeout(hoverTimeoutRef.current);
82
+ }
83
+ hoverTimeoutRef.current = setTimeout(() => {
84
+ openMenu(menuId, level);
85
+ }, delay);
86
+ };
87
+ if (!isMouseMovingRef.current) {
88
+ executePendingActions();
67
89
  }
68
- hoverTimeoutRef.current = setTimeout(() => {
69
- openMenu(menuId, level);
70
- }, delay);
71
90
  };
72
91
  const closeMenuWithDelay = (level, delay = 500) => {
73
92
  if (isMobile) {
74
93
  return;
75
94
  }
76
- if (hoverTimeoutRef.current) {
77
- clearTimeout(hoverTimeoutRef.current);
78
- hoverTimeoutRef.current = null;
95
+ pendingCloseActionRef.current = () => {
96
+ if (hoverTimeoutRef.current) {
97
+ clearTimeout(hoverTimeoutRef.current);
98
+ hoverTimeoutRef.current = null;
99
+ }
100
+ closeTimeoutRef.current = setTimeout(() => {
101
+ closeSubMenuLevel(level);
102
+ }, delay);
103
+ };
104
+ if (!isMouseMovingRef.current) {
105
+ executePendingActions();
106
+ }
107
+ };
108
+ const handleMouseMove = () => {
109
+ isMouseMovingRef.current = true;
110
+ if (mouseStopTimeoutRef.current) {
111
+ clearTimeout(mouseStopTimeoutRef.current);
79
112
  }
80
- closeTimeoutRef.current = setTimeout(() => {
81
- closeSubMenuLevel(level);
82
- }, delay);
113
+ mouseStopTimeoutRef.current = setTimeout(() => {
114
+ isMouseMovingRef.current = false;
115
+ executePendingActions();
116
+ }, 200);
83
117
  };
84
118
  const cancelCloseTimeout = () => {
85
119
  if (isMobile) {
86
120
  return;
87
121
  }
88
- if (closeTimeoutRef.current) {
89
- clearTimeout(closeTimeoutRef.current);
90
- closeTimeoutRef.current = null;
122
+ if (mouseStopTimeoutRef.current) {
123
+ clearTimeout(mouseStopTimeoutRef.current);
124
+ mouseStopTimeoutRef.current = null;
125
+ isMouseMovingRef.current = false;
91
126
  }
92
127
  };
93
128
  const closeSubMenuLevel = (level) => {
@@ -203,6 +238,7 @@ function useSubMenuSystem(mobilePositionTo) {
203
238
  onSubMenuHover: openMenuWithDelay,
204
239
  onSubMenuLeave: closeMenuWithDelay,
205
240
  onSubMenuEnter: cancelCloseTimeout,
241
+ onMouseMove: handleMouseMove,
206
242
  toggleMenu,
207
243
  mobilePositionTo,
208
244
  activeMenu,
@@ -217,9 +253,9 @@ function useMenuPosition(elementRef, position = "bottom", options) {
217
253
  left: 0,
218
254
  minWidth: 0
219
255
  });
220
- const isMobile = useMatchesMobile();
256
+ const isMobile = options == null ? void 0 : options.isMobile;
221
257
  const updatePosition = useCallback(() => {
222
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
258
+ var _a, _b, _c;
223
259
  if (!(elementRef == null ? void 0 : elementRef.current)) return;
224
260
  const triggerRect = elementRef.current.getBoundingClientRect();
225
261
  const menuRect = (_b = (_a = options == null ? void 0 : options.menuRef) == null ? void 0 : _a.current) == null ? void 0 : _b.getBoundingClientRect();
@@ -238,15 +274,27 @@ function useMenuPosition(elementRef, position = "bottom", options) {
238
274
  } else if (position === "bottom-right") {
239
275
  left = triggerRect.right + window.scrollX - menuWidth;
240
276
  } else if (position === "right") {
241
- left = triggerRect.right + window.scrollX;
277
+ if (isMobile) {
278
+ left = triggerRect.left + window.scrollX;
279
+ } else {
280
+ left = triggerRect.right + window.scrollX;
281
+ }
282
+ }
283
+ const overflowsRightViewport = left + menuWidth > viewportWidth;
284
+ if (overflowsRightViewport) {
285
+ const newLeft = triggerRect.left - menuWidth;
286
+ const overflowsLeftViewport = newLeft < 0;
287
+ if (overflowsLeftViewport) {
288
+ left = (viewportWidth - menuWidth) / 2;
289
+ } else {
290
+ left = newLeft;
291
+ }
242
292
  }
243
- const overflowsLeftViewport = left + menuWidth > viewportWidth;
244
- if (overflowsLeftViewport) {
245
- left = triggerRect.left - menuWidth;
293
+ if (isMobile && position === "right") {
294
+ top = triggerRect.top + triggerRect.height + topOffset;
246
295
  }
247
- if (isMobile) {
248
- left = triggerRect.left + menuWidth > viewportWidth ? Math.max(viewportWidth - menuWidth, 0) - 8 : triggerRect.left;
249
- top = ((_f = (_e = (_d = elementRef.current.parentElement) == null ? void 0 : _d.getBoundingClientRect()) == null ? void 0 : _e.top) != null ? _f : 0) + ((_i = (_h = (_g = elementRef.current.parentElement) == null ? void 0 : _g.getBoundingClientRect()) == null ? void 0 : _h.height) != null ? _i : 0) + topOffset;
296
+ if (isMobile && menuWidth > viewportWidth) {
297
+ left = 0;
250
298
  }
251
299
  setMenuPosition({
252
300
  top,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useMenuPosition
3
- } from "./chunk-5GUW4DUY.js";
3
+ } from "./chunk-AEDQLVKH.js";
4
4
  import {
5
5
  useMatchesMobile
6
6
  } from "./chunk-WNQ53SVY.js";
@@ -73,7 +73,8 @@ var Menu = (_a) => {
73
73
  isOpen: show,
74
74
  setIsOpen: setShow,
75
75
  menuRef: internalRef,
76
- topOffset
76
+ topOffset,
77
+ isMobile: !!(isMobile && mobilePositionTo)
77
78
  }
78
79
  );
79
80
  useEffect(() => {
@@ -146,6 +147,7 @@ var Menu = (_a) => {
146
147
  "shadow-4 rounded-base bg-background-grouped-primary-normal overflow-x-hidden overflow-y-auto flex flex-col outline-0",
147
148
  "fixed",
148
149
  "z-50",
150
+ "max-w-screen",
149
151
  mobileHide && "opacity-0 pointer-events-none",
150
152
  className
151
153
  )
@@ -47,7 +47,8 @@ var MenuOption = ({
47
47
  activeMenu,
48
48
  mobilePositionTo,
49
49
  highlightMatchingText = false,
50
- menuValue
50
+ menuValue,
51
+ onMouseMove
51
52
  }) => {
52
53
  const uniqueId = useId();
53
54
  const internalRef = useRef(null);
@@ -64,6 +65,11 @@ var MenuOption = ({
64
65
  onSubMenuLeave(subMenuLevel);
65
66
  }
66
67
  };
68
+ const handleMouseMove = () => {
69
+ if (subMenu && onMouseMove && !disabled) {
70
+ onMouseMove();
71
+ }
72
+ };
67
73
  const handleSubMenuEnter = () => {
68
74
  if (onSubMenuEnter) {
69
75
  onSubMenuEnter();
@@ -122,7 +128,8 @@ var MenuOption = ({
122
128
  }
123
129
  },
124
130
  onMouseEnter: handleMouseEnter,
125
- onMouseLeave: handleMouseLeave
131
+ onMouseLeave: handleMouseLeave,
132
+ onMouseMove: handleMouseMove
126
133
  }, additionalAttributes), {
127
134
  tabIndex: -1,
128
135
  role: "menuitem",
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  Menu
3
- } from "./chunk-UBU6IJML.js";
3
+ } from "./chunk-ESTHTAR5.js";
4
4
  import {
5
5
  useSubMenuSystem
6
- } from "./chunk-5GUW4DUY.js";
6
+ } from "./chunk-AEDQLVKH.js";
7
7
  import {
8
8
  MenuOption
9
- } from "./chunk-FFU6FB3K.js";
9
+ } from "./chunk-OZKCUKBS.js";
10
10
  import {
11
11
  Search
12
12
  } from "./chunk-PMBEIP24.js";
@@ -907,6 +907,10 @@ function useSubMenuSystem(mobilePositionTo) {
907
907
  const subMenuRefs = (0, import_react7.useRef)({});
908
908
  const hoverTimeoutRef = (0, import_react7.useRef)(null);
909
909
  const closeTimeoutRef = (0, import_react7.useRef)(null);
910
+ const mouseStopTimeoutRef = (0, import_react7.useRef)(null);
911
+ const isMouseMovingRef = (0, import_react7.useRef)(false);
912
+ const pendingOpenActionRef = (0, import_react7.useRef)(null);
913
+ const pendingCloseActionRef = (0, import_react7.useRef)(null);
910
914
  const isMobile = useMatchesMobile();
911
915
  const toggleMenu = (menuId, level) => {
912
916
  if (closeTimeoutRef.current) {
@@ -946,36 +950,67 @@ function useSubMenuSystem(mobilePositionTo) {
946
950
  return newActiveMenus;
947
951
  });
948
952
  };
953
+ const executePendingActions = (0, import_react7.useCallback)(() => {
954
+ if (pendingCloseActionRef.current) {
955
+ pendingCloseActionRef.current();
956
+ pendingCloseActionRef.current = null;
957
+ }
958
+ if (pendingOpenActionRef.current) {
959
+ pendingOpenActionRef.current();
960
+ pendingOpenActionRef.current = null;
961
+ }
962
+ }, []);
949
963
  const openMenuWithDelay = (menuId, level, delay = 150) => {
950
964
  if (isMobile) {
951
965
  return;
952
966
  }
953
- if (hoverTimeoutRef.current) {
954
- clearTimeout(hoverTimeoutRef.current);
967
+ pendingOpenActionRef.current = () => {
968
+ if (hoverTimeoutRef.current) {
969
+ clearTimeout(hoverTimeoutRef.current);
970
+ }
971
+ hoverTimeoutRef.current = setTimeout(() => {
972
+ openMenu(menuId, level);
973
+ }, delay);
974
+ };
975
+ if (!isMouseMovingRef.current) {
976
+ executePendingActions();
955
977
  }
956
- hoverTimeoutRef.current = setTimeout(() => {
957
- openMenu(menuId, level);
958
- }, delay);
959
978
  };
960
979
  const closeMenuWithDelay = (level, delay = 500) => {
961
980
  if (isMobile) {
962
981
  return;
963
982
  }
964
- if (hoverTimeoutRef.current) {
965
- clearTimeout(hoverTimeoutRef.current);
966
- hoverTimeoutRef.current = null;
983
+ pendingCloseActionRef.current = () => {
984
+ if (hoverTimeoutRef.current) {
985
+ clearTimeout(hoverTimeoutRef.current);
986
+ hoverTimeoutRef.current = null;
987
+ }
988
+ closeTimeoutRef.current = setTimeout(() => {
989
+ closeSubMenuLevel(level);
990
+ }, delay);
991
+ };
992
+ if (!isMouseMovingRef.current) {
993
+ executePendingActions();
967
994
  }
968
- closeTimeoutRef.current = setTimeout(() => {
969
- closeSubMenuLevel(level);
970
- }, delay);
995
+ };
996
+ const handleMouseMove = () => {
997
+ isMouseMovingRef.current = true;
998
+ if (mouseStopTimeoutRef.current) {
999
+ clearTimeout(mouseStopTimeoutRef.current);
1000
+ }
1001
+ mouseStopTimeoutRef.current = setTimeout(() => {
1002
+ isMouseMovingRef.current = false;
1003
+ executePendingActions();
1004
+ }, 200);
971
1005
  };
972
1006
  const cancelCloseTimeout = () => {
973
1007
  if (isMobile) {
974
1008
  return;
975
1009
  }
976
- if (closeTimeoutRef.current) {
977
- clearTimeout(closeTimeoutRef.current);
978
- closeTimeoutRef.current = null;
1010
+ if (mouseStopTimeoutRef.current) {
1011
+ clearTimeout(mouseStopTimeoutRef.current);
1012
+ mouseStopTimeoutRef.current = null;
1013
+ isMouseMovingRef.current = false;
979
1014
  }
980
1015
  };
981
1016
  const closeSubMenuLevel = (level) => {
@@ -1091,6 +1126,7 @@ function useSubMenuSystem(mobilePositionTo) {
1091
1126
  onSubMenuHover: openMenuWithDelay,
1092
1127
  onSubMenuLeave: closeMenuWithDelay,
1093
1128
  onSubMenuEnter: cancelCloseTimeout,
1129
+ onMouseMove: handleMouseMove,
1094
1130
  toggleMenu,
1095
1131
  mobilePositionTo,
1096
1132
  activeMenu,
@@ -1105,9 +1141,9 @@ function useMenuPosition(elementRef, position = "bottom", options) {
1105
1141
  left: 0,
1106
1142
  minWidth: 0
1107
1143
  });
1108
- const isMobile = useMatchesMobile();
1144
+ const isMobile = options == null ? void 0 : options.isMobile;
1109
1145
  const updatePosition = (0, import_react7.useCallback)(() => {
1110
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1146
+ var _a, _b, _c;
1111
1147
  if (!(elementRef == null ? void 0 : elementRef.current)) return;
1112
1148
  const triggerRect = elementRef.current.getBoundingClientRect();
1113
1149
  const menuRect = (_b = (_a = options == null ? void 0 : options.menuRef) == null ? void 0 : _a.current) == null ? void 0 : _b.getBoundingClientRect();
@@ -1126,15 +1162,27 @@ function useMenuPosition(elementRef, position = "bottom", options) {
1126
1162
  } else if (position === "bottom-right") {
1127
1163
  left = triggerRect.right + window.scrollX - menuWidth;
1128
1164
  } else if (position === "right") {
1129
- left = triggerRect.right + window.scrollX;
1165
+ if (isMobile) {
1166
+ left = triggerRect.left + window.scrollX;
1167
+ } else {
1168
+ left = triggerRect.right + window.scrollX;
1169
+ }
1170
+ }
1171
+ const overflowsRightViewport = left + menuWidth > viewportWidth;
1172
+ if (overflowsRightViewport) {
1173
+ const newLeft = triggerRect.left - menuWidth;
1174
+ const overflowsLeftViewport = newLeft < 0;
1175
+ if (overflowsLeftViewport) {
1176
+ left = (viewportWidth - menuWidth) / 2;
1177
+ } else {
1178
+ left = newLeft;
1179
+ }
1130
1180
  }
1131
- const overflowsLeftViewport = left + menuWidth > viewportWidth;
1132
- if (overflowsLeftViewport) {
1133
- left = triggerRect.left - menuWidth;
1181
+ if (isMobile && position === "right") {
1182
+ top = triggerRect.top + triggerRect.height + topOffset;
1134
1183
  }
1135
- if (isMobile) {
1136
- left = triggerRect.left + menuWidth > viewportWidth ? Math.max(viewportWidth - menuWidth, 0) - 8 : triggerRect.left;
1137
- top = ((_f = (_e = (_d = elementRef.current.parentElement) == null ? void 0 : _d.getBoundingClientRect()) == null ? void 0 : _e.top) != null ? _f : 0) + ((_i = (_h = (_g = elementRef.current.parentElement) == null ? void 0 : _g.getBoundingClientRect()) == null ? void 0 : _h.height) != null ? _i : 0) + topOffset;
1184
+ if (isMobile && menuWidth > viewportWidth) {
1185
+ left = 0;
1138
1186
  }
1139
1187
  setMenuPosition({
1140
1188
  top,
@@ -1261,7 +1309,8 @@ var Menu = (_a) => {
1261
1309
  isOpen: show,
1262
1310
  setIsOpen: setShow,
1263
1311
  menuRef: internalRef,
1264
- topOffset
1312
+ topOffset,
1313
+ isMobile: !!(isMobile && mobilePositionTo)
1265
1314
  }
1266
1315
  );
1267
1316
  (0, import_react8.useEffect)(() => {
@@ -1334,6 +1383,7 @@ var Menu = (_a) => {
1334
1383
  "shadow-4 rounded-base bg-background-grouped-primary-normal overflow-x-hidden overflow-y-auto flex flex-col outline-0",
1335
1384
  "fixed",
1336
1385
  "z-50",
1386
+ "max-w-screen",
1337
1387
  mobileHide && "opacity-0 pointer-events-none",
1338
1388
  className
1339
1389
  )
@@ -1540,7 +1590,8 @@ var MenuOption = ({
1540
1590
  activeMenu,
1541
1591
  mobilePositionTo,
1542
1592
  highlightMatchingText = false,
1543
- menuValue
1593
+ menuValue,
1594
+ onMouseMove
1544
1595
  }) => {
1545
1596
  const uniqueId = (0, import_react9.useId)();
1546
1597
  const internalRef = (0, import_react9.useRef)(null);
@@ -1557,6 +1608,11 @@ var MenuOption = ({
1557
1608
  onSubMenuLeave(subMenuLevel);
1558
1609
  }
1559
1610
  };
1611
+ const handleMouseMove = () => {
1612
+ if (subMenu && onMouseMove && !disabled) {
1613
+ onMouseMove();
1614
+ }
1615
+ };
1560
1616
  const handleSubMenuEnter = () => {
1561
1617
  if (onSubMenuEnter) {
1562
1618
  onSubMenuEnter();
@@ -1615,7 +1671,8 @@ var MenuOption = ({
1615
1671
  }
1616
1672
  },
1617
1673
  onMouseEnter: handleMouseEnter,
1618
- onMouseLeave: handleMouseLeave
1674
+ onMouseLeave: handleMouseLeave,
1675
+ onMouseMove: handleMouseMove
1619
1676
  }, additionalAttributes), {
1620
1677
  tabIndex: -1,
1621
1678
  role: "menuitem",
@@ -4,10 +4,10 @@ import {
4
4
  DataGridCell,
5
5
  DragAlongCell,
6
6
  DraggableCellHeader
7
- } from "../chunk-Z2KZO4J3.js";
8
- import "../chunk-UBU6IJML.js";
9
- import "../chunk-5GUW4DUY.js";
10
- import "../chunk-FFU6FB3K.js";
7
+ } from "../chunk-SDQPZQTI.js";
8
+ import "../chunk-ESTHTAR5.js";
9
+ import "../chunk-AEDQLVKH.js";
10
+ import "../chunk-OZKCUKBS.js";
11
11
  import "../chunk-WNQ53SVY.js";
12
12
  import "../chunk-6LN6QT6M.js";
13
13
  import "../chunk-PMBEIP24.js";
@@ -98,9 +98,9 @@ function useMenuPosition(elementRef, position = "bottom", options) {
98
98
  left: 0,
99
99
  minWidth: 0
100
100
  });
101
- const isMobile = useMatchesMobile();
101
+ const isMobile = options == null ? void 0 : options.isMobile;
102
102
  const updatePosition = (0, import_react4.useCallback)(() => {
103
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
103
+ var _a, _b, _c;
104
104
  if (!(elementRef == null ? void 0 : elementRef.current)) return;
105
105
  const triggerRect = elementRef.current.getBoundingClientRect();
106
106
  const menuRect = (_b = (_a = options == null ? void 0 : options.menuRef) == null ? void 0 : _a.current) == null ? void 0 : _b.getBoundingClientRect();
@@ -119,15 +119,27 @@ function useMenuPosition(elementRef, position = "bottom", options) {
119
119
  } else if (position === "bottom-right") {
120
120
  left = triggerRect.right + window.scrollX - menuWidth;
121
121
  } else if (position === "right") {
122
- left = triggerRect.right + window.scrollX;
122
+ if (isMobile) {
123
+ left = triggerRect.left + window.scrollX;
124
+ } else {
125
+ left = triggerRect.right + window.scrollX;
126
+ }
123
127
  }
124
- const overflowsLeftViewport = left + menuWidth > viewportWidth;
125
- if (overflowsLeftViewport) {
126
- left = triggerRect.left - menuWidth;
128
+ const overflowsRightViewport = left + menuWidth > viewportWidth;
129
+ if (overflowsRightViewport) {
130
+ const newLeft = triggerRect.left - menuWidth;
131
+ const overflowsLeftViewport = newLeft < 0;
132
+ if (overflowsLeftViewport) {
133
+ left = (viewportWidth - menuWidth) / 2;
134
+ } else {
135
+ left = newLeft;
136
+ }
137
+ }
138
+ if (isMobile && position === "right") {
139
+ top = triggerRect.top + triggerRect.height + topOffset;
127
140
  }
128
- if (isMobile) {
129
- left = triggerRect.left + menuWidth > viewportWidth ? Math.max(viewportWidth - menuWidth, 0) - 8 : triggerRect.left;
130
- top = ((_f = (_e = (_d = elementRef.current.parentElement) == null ? void 0 : _d.getBoundingClientRect()) == null ? void 0 : _e.top) != null ? _f : 0) + ((_i = (_h = (_g = elementRef.current.parentElement) == null ? void 0 : _g.getBoundingClientRect()) == null ? void 0 : _h.height) != null ? _i : 0) + topOffset;
141
+ if (isMobile && menuWidth > viewportWidth) {
142
+ left = 0;
131
143
  }
132
144
  setMenuPosition({
133
145
  top,
@@ -254,7 +266,8 @@ var Menu = (_a) => {
254
266
  isOpen: show,
255
267
  setIsOpen: setShow,
256
268
  menuRef: internalRef,
257
- topOffset
269
+ topOffset,
270
+ isMobile: !!(isMobile && mobilePositionTo)
258
271
  }
259
272
  );
260
273
  (0, import_react5.useEffect)(() => {
@@ -327,6 +340,7 @@ var Menu = (_a) => {
327
340
  "shadow-4 rounded-base bg-background-grouped-primary-normal overflow-x-hidden overflow-y-auto flex flex-col outline-0",
328
341
  "fixed",
329
342
  "z-50",
343
+ "max-w-screen",
330
344
  mobileHide && "opacity-0 pointer-events-none",
331
345
  className
332
346
  )
@@ -1,8 +1,8 @@
1
1
  "use client";
2
2
  import {
3
3
  Menu
4
- } from "../chunk-UBU6IJML.js";
5
- import "../chunk-5GUW4DUY.js";
4
+ } from "../chunk-ESTHTAR5.js";
5
+ import "../chunk-AEDQLVKH.js";
6
6
  import "../chunk-WNQ53SVY.js";
7
7
  import "../chunk-6LN6QT6M.js";
8
8
  import "../chunk-ORMEWXMH.js";
@@ -370,7 +370,8 @@ var MenuOption = ({
370
370
  activeMenu,
371
371
  mobilePositionTo,
372
372
  highlightMatchingText = false,
373
- menuValue
373
+ menuValue,
374
+ onMouseMove
374
375
  }) => {
375
376
  const uniqueId = (0, import_react4.useId)();
376
377
  const internalRef = (0, import_react4.useRef)(null);
@@ -387,6 +388,11 @@ var MenuOption = ({
387
388
  onSubMenuLeave(subMenuLevel);
388
389
  }
389
390
  };
391
+ const handleMouseMove = () => {
392
+ if (subMenu && onMouseMove && !disabled) {
393
+ onMouseMove();
394
+ }
395
+ };
390
396
  const handleSubMenuEnter = () => {
391
397
  if (onSubMenuEnter) {
392
398
  onSubMenuEnter();
@@ -445,7 +451,8 @@ var MenuOption = ({
445
451
  }
446
452
  },
447
453
  onMouseEnter: handleMouseEnter,
448
- onMouseLeave: handleMouseLeave
454
+ onMouseLeave: handleMouseLeave,
455
+ onMouseMove: handleMouseMove
449
456
  }, additionalAttributes), {
450
457
  tabIndex: -1,
451
458
  role: "menuitem",
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  MenuOption
4
- } from "../chunk-FFU6FB3K.js";
4
+ } from "../chunk-OZKCUKBS.js";
5
5
  import "../chunk-WNQ53SVY.js";
6
6
  import "../chunk-JWCT72WR.js";
7
7
  import "../chunk-HVI3CL7Y.js";
@@ -399,7 +399,8 @@ var MenuOption = ({
399
399
  activeMenu,
400
400
  mobilePositionTo,
401
401
  highlightMatchingText = false,
402
- menuValue
402
+ menuValue,
403
+ onMouseMove
403
404
  }) => {
404
405
  const uniqueId = (0, import_react4.useId)();
405
406
  const internalRef = (0, import_react4.useRef)(null);
@@ -416,6 +417,11 @@ var MenuOption = ({
416
417
  onSubMenuLeave(subMenuLevel);
417
418
  }
418
419
  };
420
+ const handleMouseMove = () => {
421
+ if (subMenu && onMouseMove && !disabled) {
422
+ onMouseMove();
423
+ }
424
+ };
419
425
  const handleSubMenuEnter = () => {
420
426
  if (onSubMenuEnter) {
421
427
  onSubMenuEnter();
@@ -474,7 +480,8 @@ var MenuOption = ({
474
480
  }
475
481
  },
476
482
  onMouseEnter: handleMouseEnter,
477
- onMouseLeave: handleMouseLeave
483
+ onMouseLeave: handleMouseLeave,
484
+ onMouseMove: handleMouseMove
478
485
  }, additionalAttributes), {
479
486
  tabIndex: -1,
480
487
  role: "menuitem",
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
  import {
3
3
  MenuOption
4
- } from "../chunk-FFU6FB3K.js";
4
+ } from "../chunk-OZKCUKBS.js";
5
5
  import {
6
6
  useKeydown
7
7
  } from "../chunk-WNQ53SVY.js";
@@ -949,6 +949,10 @@ function useSubMenuSystem(mobilePositionTo) {
949
949
  const subMenuRefs = (0, import_react7.useRef)({});
950
950
  const hoverTimeoutRef = (0, import_react7.useRef)(null);
951
951
  const closeTimeoutRef = (0, import_react7.useRef)(null);
952
+ const mouseStopTimeoutRef = (0, import_react7.useRef)(null);
953
+ const isMouseMovingRef = (0, import_react7.useRef)(false);
954
+ const pendingOpenActionRef = (0, import_react7.useRef)(null);
955
+ const pendingCloseActionRef = (0, import_react7.useRef)(null);
952
956
  const isMobile = useMatchesMobile();
953
957
  const toggleMenu = (menuId, level) => {
954
958
  if (closeTimeoutRef.current) {
@@ -988,36 +992,67 @@ function useSubMenuSystem(mobilePositionTo) {
988
992
  return newActiveMenus;
989
993
  });
990
994
  };
995
+ const executePendingActions = (0, import_react7.useCallback)(() => {
996
+ if (pendingCloseActionRef.current) {
997
+ pendingCloseActionRef.current();
998
+ pendingCloseActionRef.current = null;
999
+ }
1000
+ if (pendingOpenActionRef.current) {
1001
+ pendingOpenActionRef.current();
1002
+ pendingOpenActionRef.current = null;
1003
+ }
1004
+ }, []);
991
1005
  const openMenuWithDelay = (menuId, level, delay = 150) => {
992
1006
  if (isMobile) {
993
1007
  return;
994
1008
  }
995
- if (hoverTimeoutRef.current) {
996
- clearTimeout(hoverTimeoutRef.current);
1009
+ pendingOpenActionRef.current = () => {
1010
+ if (hoverTimeoutRef.current) {
1011
+ clearTimeout(hoverTimeoutRef.current);
1012
+ }
1013
+ hoverTimeoutRef.current = setTimeout(() => {
1014
+ openMenu(menuId, level);
1015
+ }, delay);
1016
+ };
1017
+ if (!isMouseMovingRef.current) {
1018
+ executePendingActions();
997
1019
  }
998
- hoverTimeoutRef.current = setTimeout(() => {
999
- openMenu(menuId, level);
1000
- }, delay);
1001
1020
  };
1002
1021
  const closeMenuWithDelay = (level, delay = 500) => {
1003
1022
  if (isMobile) {
1004
1023
  return;
1005
1024
  }
1006
- if (hoverTimeoutRef.current) {
1007
- clearTimeout(hoverTimeoutRef.current);
1008
- hoverTimeoutRef.current = null;
1025
+ pendingCloseActionRef.current = () => {
1026
+ if (hoverTimeoutRef.current) {
1027
+ clearTimeout(hoverTimeoutRef.current);
1028
+ hoverTimeoutRef.current = null;
1029
+ }
1030
+ closeTimeoutRef.current = setTimeout(() => {
1031
+ closeSubMenuLevel(level);
1032
+ }, delay);
1033
+ };
1034
+ if (!isMouseMovingRef.current) {
1035
+ executePendingActions();
1009
1036
  }
1010
- closeTimeoutRef.current = setTimeout(() => {
1011
- closeSubMenuLevel(level);
1012
- }, delay);
1037
+ };
1038
+ const handleMouseMove = () => {
1039
+ isMouseMovingRef.current = true;
1040
+ if (mouseStopTimeoutRef.current) {
1041
+ clearTimeout(mouseStopTimeoutRef.current);
1042
+ }
1043
+ mouseStopTimeoutRef.current = setTimeout(() => {
1044
+ isMouseMovingRef.current = false;
1045
+ executePendingActions();
1046
+ }, 200);
1013
1047
  };
1014
1048
  const cancelCloseTimeout = () => {
1015
1049
  if (isMobile) {
1016
1050
  return;
1017
1051
  }
1018
- if (closeTimeoutRef.current) {
1019
- clearTimeout(closeTimeoutRef.current);
1020
- closeTimeoutRef.current = null;
1052
+ if (mouseStopTimeoutRef.current) {
1053
+ clearTimeout(mouseStopTimeoutRef.current);
1054
+ mouseStopTimeoutRef.current = null;
1055
+ isMouseMovingRef.current = false;
1021
1056
  }
1022
1057
  };
1023
1058
  const closeSubMenuLevel = (level) => {
@@ -1133,6 +1168,7 @@ function useSubMenuSystem(mobilePositionTo) {
1133
1168
  onSubMenuHover: openMenuWithDelay,
1134
1169
  onSubMenuLeave: closeMenuWithDelay,
1135
1170
  onSubMenuEnter: cancelCloseTimeout,
1171
+ onMouseMove: handleMouseMove,
1136
1172
  toggleMenu,
1137
1173
  mobilePositionTo,
1138
1174
  activeMenu,
@@ -1147,9 +1183,9 @@ function useMenuPosition(elementRef, position = "bottom", options) {
1147
1183
  left: 0,
1148
1184
  minWidth: 0
1149
1185
  });
1150
- const isMobile = useMatchesMobile();
1186
+ const isMobile = options == null ? void 0 : options.isMobile;
1151
1187
  const updatePosition = (0, import_react7.useCallback)(() => {
1152
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
1188
+ var _a, _b, _c;
1153
1189
  if (!(elementRef == null ? void 0 : elementRef.current)) return;
1154
1190
  const triggerRect = elementRef.current.getBoundingClientRect();
1155
1191
  const menuRect = (_b = (_a = options == null ? void 0 : options.menuRef) == null ? void 0 : _a.current) == null ? void 0 : _b.getBoundingClientRect();
@@ -1168,15 +1204,27 @@ function useMenuPosition(elementRef, position = "bottom", options) {
1168
1204
  } else if (position === "bottom-right") {
1169
1205
  left = triggerRect.right + window.scrollX - menuWidth;
1170
1206
  } else if (position === "right") {
1171
- left = triggerRect.right + window.scrollX;
1207
+ if (isMobile) {
1208
+ left = triggerRect.left + window.scrollX;
1209
+ } else {
1210
+ left = triggerRect.right + window.scrollX;
1211
+ }
1212
+ }
1213
+ const overflowsRightViewport = left + menuWidth > viewportWidth;
1214
+ if (overflowsRightViewport) {
1215
+ const newLeft = triggerRect.left - menuWidth;
1216
+ const overflowsLeftViewport = newLeft < 0;
1217
+ if (overflowsLeftViewport) {
1218
+ left = (viewportWidth - menuWidth) / 2;
1219
+ } else {
1220
+ left = newLeft;
1221
+ }
1172
1222
  }
1173
- const overflowsLeftViewport = left + menuWidth > viewportWidth;
1174
- if (overflowsLeftViewport) {
1175
- left = triggerRect.left - menuWidth;
1223
+ if (isMobile && position === "right") {
1224
+ top = triggerRect.top + triggerRect.height + topOffset;
1176
1225
  }
1177
- if (isMobile) {
1178
- left = triggerRect.left + menuWidth > viewportWidth ? Math.max(viewportWidth - menuWidth, 0) - 8 : triggerRect.left;
1179
- top = ((_f = (_e = (_d = elementRef.current.parentElement) == null ? void 0 : _d.getBoundingClientRect()) == null ? void 0 : _e.top) != null ? _f : 0) + ((_i = (_h = (_g = elementRef.current.parentElement) == null ? void 0 : _g.getBoundingClientRect()) == null ? void 0 : _h.height) != null ? _i : 0) + topOffset;
1226
+ if (isMobile && menuWidth > viewportWidth) {
1227
+ left = 0;
1180
1228
  }
1181
1229
  setMenuPosition({
1182
1230
  top,
@@ -1303,7 +1351,8 @@ var Menu = (_a) => {
1303
1351
  isOpen: show,
1304
1352
  setIsOpen: setShow,
1305
1353
  menuRef: internalRef,
1306
- topOffset
1354
+ topOffset,
1355
+ isMobile: !!(isMobile && mobilePositionTo)
1307
1356
  }
1308
1357
  );
1309
1358
  (0, import_react8.useEffect)(() => {
@@ -1376,6 +1425,7 @@ var Menu = (_a) => {
1376
1425
  "shadow-4 rounded-base bg-background-grouped-primary-normal overflow-x-hidden overflow-y-auto flex flex-col outline-0",
1377
1426
  "fixed",
1378
1427
  "z-50",
1428
+ "max-w-screen",
1379
1429
  mobileHide && "opacity-0 pointer-events-none",
1380
1430
  className
1381
1431
  )
@@ -1582,7 +1632,8 @@ var MenuOption = ({
1582
1632
  activeMenu,
1583
1633
  mobilePositionTo,
1584
1634
  highlightMatchingText = false,
1585
- menuValue
1635
+ menuValue,
1636
+ onMouseMove
1586
1637
  }) => {
1587
1638
  const uniqueId = (0, import_react9.useId)();
1588
1639
  const internalRef = (0, import_react9.useRef)(null);
@@ -1599,6 +1650,11 @@ var MenuOption = ({
1599
1650
  onSubMenuLeave(subMenuLevel);
1600
1651
  }
1601
1652
  };
1653
+ const handleMouseMove = () => {
1654
+ if (subMenu && onMouseMove && !disabled) {
1655
+ onMouseMove();
1656
+ }
1657
+ };
1602
1658
  const handleSubMenuEnter = () => {
1603
1659
  if (onSubMenuEnter) {
1604
1660
  onSubMenuEnter();
@@ -1657,7 +1713,8 @@ var MenuOption = ({
1657
1713
  }
1658
1714
  },
1659
1715
  onMouseEnter: handleMouseEnter,
1660
- onMouseLeave: handleMouseLeave
1716
+ onMouseLeave: handleMouseLeave,
1717
+ onMouseMove: handleMouseMove
1661
1718
  }, additionalAttributes), {
1662
1719
  tabIndex: -1,
1663
1720
  role: "menuitem",
@@ -12,14 +12,14 @@ import {
12
12
  DataGridCell,
13
13
  DragAlongCell,
14
14
  DraggableCellHeader
15
- } from "../chunk-Z2KZO4J3.js";
15
+ } from "../chunk-SDQPZQTI.js";
16
16
  import {
17
17
  Menu
18
- } from "../chunk-UBU6IJML.js";
19
- import "../chunk-5GUW4DUY.js";
18
+ } from "../chunk-ESTHTAR5.js";
19
+ import "../chunk-AEDQLVKH.js";
20
20
  import {
21
21
  MenuOption
22
- } from "../chunk-FFU6FB3K.js";
22
+ } from "../chunk-OZKCUKBS.js";
23
23
  import {
24
24
  useInfiniteScroll
25
25
  } from "../chunk-WNQ53SVY.js";
@@ -75,6 +75,10 @@ function useSubMenuSystem(mobilePositionTo) {
75
75
  const subMenuRefs = (0, import_react4.useRef)({});
76
76
  const hoverTimeoutRef = (0, import_react4.useRef)(null);
77
77
  const closeTimeoutRef = (0, import_react4.useRef)(null);
78
+ const mouseStopTimeoutRef = (0, import_react4.useRef)(null);
79
+ const isMouseMovingRef = (0, import_react4.useRef)(false);
80
+ const pendingOpenActionRef = (0, import_react4.useRef)(null);
81
+ const pendingCloseActionRef = (0, import_react4.useRef)(null);
78
82
  const isMobile = useMatchesMobile();
79
83
  const toggleMenu = (menuId, level) => {
80
84
  if (closeTimeoutRef.current) {
@@ -114,36 +118,67 @@ function useSubMenuSystem(mobilePositionTo) {
114
118
  return newActiveMenus;
115
119
  });
116
120
  };
121
+ const executePendingActions = (0, import_react4.useCallback)(() => {
122
+ if (pendingCloseActionRef.current) {
123
+ pendingCloseActionRef.current();
124
+ pendingCloseActionRef.current = null;
125
+ }
126
+ if (pendingOpenActionRef.current) {
127
+ pendingOpenActionRef.current();
128
+ pendingOpenActionRef.current = null;
129
+ }
130
+ }, []);
117
131
  const openMenuWithDelay = (menuId, level, delay = 150) => {
118
132
  if (isMobile) {
119
133
  return;
120
134
  }
121
- if (hoverTimeoutRef.current) {
122
- clearTimeout(hoverTimeoutRef.current);
135
+ pendingOpenActionRef.current = () => {
136
+ if (hoverTimeoutRef.current) {
137
+ clearTimeout(hoverTimeoutRef.current);
138
+ }
139
+ hoverTimeoutRef.current = setTimeout(() => {
140
+ openMenu(menuId, level);
141
+ }, delay);
142
+ };
143
+ if (!isMouseMovingRef.current) {
144
+ executePendingActions();
123
145
  }
124
- hoverTimeoutRef.current = setTimeout(() => {
125
- openMenu(menuId, level);
126
- }, delay);
127
146
  };
128
147
  const closeMenuWithDelay = (level, delay = 500) => {
129
148
  if (isMobile) {
130
149
  return;
131
150
  }
132
- if (hoverTimeoutRef.current) {
133
- clearTimeout(hoverTimeoutRef.current);
134
- hoverTimeoutRef.current = null;
151
+ pendingCloseActionRef.current = () => {
152
+ if (hoverTimeoutRef.current) {
153
+ clearTimeout(hoverTimeoutRef.current);
154
+ hoverTimeoutRef.current = null;
155
+ }
156
+ closeTimeoutRef.current = setTimeout(() => {
157
+ closeSubMenuLevel(level);
158
+ }, delay);
159
+ };
160
+ if (!isMouseMovingRef.current) {
161
+ executePendingActions();
162
+ }
163
+ };
164
+ const handleMouseMove = () => {
165
+ isMouseMovingRef.current = true;
166
+ if (mouseStopTimeoutRef.current) {
167
+ clearTimeout(mouseStopTimeoutRef.current);
135
168
  }
136
- closeTimeoutRef.current = setTimeout(() => {
137
- closeSubMenuLevel(level);
138
- }, delay);
169
+ mouseStopTimeoutRef.current = setTimeout(() => {
170
+ isMouseMovingRef.current = false;
171
+ executePendingActions();
172
+ }, 200);
139
173
  };
140
174
  const cancelCloseTimeout = () => {
141
175
  if (isMobile) {
142
176
  return;
143
177
  }
144
- if (closeTimeoutRef.current) {
145
- clearTimeout(closeTimeoutRef.current);
146
- closeTimeoutRef.current = null;
178
+ if (mouseStopTimeoutRef.current) {
179
+ clearTimeout(mouseStopTimeoutRef.current);
180
+ mouseStopTimeoutRef.current = null;
181
+ isMouseMovingRef.current = false;
147
182
  }
148
183
  };
149
184
  const closeSubMenuLevel = (level) => {
@@ -259,6 +294,7 @@ function useSubMenuSystem(mobilePositionTo) {
259
294
  onSubMenuHover: openMenuWithDelay,
260
295
  onSubMenuLeave: closeMenuWithDelay,
261
296
  onSubMenuEnter: cancelCloseTimeout,
297
+ onMouseMove: handleMouseMove,
262
298
  toggleMenu,
263
299
  mobilePositionTo,
264
300
  activeMenu,
@@ -273,9 +309,9 @@ function useMenuPosition(elementRef, position = "bottom", options) {
273
309
  left: 0,
274
310
  minWidth: 0
275
311
  });
276
- const isMobile = useMatchesMobile();
312
+ const isMobile = options == null ? void 0 : options.isMobile;
277
313
  const updatePosition = (0, import_react4.useCallback)(() => {
278
- var _a, _b, _c, _d, _e, _f, _g, _h, _i;
314
+ var _a, _b, _c;
279
315
  if (!(elementRef == null ? void 0 : elementRef.current)) return;
280
316
  const triggerRect = elementRef.current.getBoundingClientRect();
281
317
  const menuRect = (_b = (_a = options == null ? void 0 : options.menuRef) == null ? void 0 : _a.current) == null ? void 0 : _b.getBoundingClientRect();
@@ -294,15 +330,27 @@ function useMenuPosition(elementRef, position = "bottom", options) {
294
330
  } else if (position === "bottom-right") {
295
331
  left = triggerRect.right + window.scrollX - menuWidth;
296
332
  } else if (position === "right") {
297
- left = triggerRect.right + window.scrollX;
333
+ if (isMobile) {
334
+ left = triggerRect.left + window.scrollX;
335
+ } else {
336
+ left = triggerRect.right + window.scrollX;
337
+ }
338
+ }
339
+ const overflowsRightViewport = left + menuWidth > viewportWidth;
340
+ if (overflowsRightViewport) {
341
+ const newLeft = triggerRect.left - menuWidth;
342
+ const overflowsLeftViewport = newLeft < 0;
343
+ if (overflowsLeftViewport) {
344
+ left = (viewportWidth - menuWidth) / 2;
345
+ } else {
346
+ left = newLeft;
347
+ }
298
348
  }
299
- const overflowsLeftViewport = left + menuWidth > viewportWidth;
300
- if (overflowsLeftViewport) {
301
- left = triggerRect.left - menuWidth;
349
+ if (isMobile && position === "right") {
350
+ top = triggerRect.top + triggerRect.height + topOffset;
302
351
  }
303
- if (isMobile) {
304
- left = triggerRect.left + menuWidth > viewportWidth ? Math.max(viewportWidth - menuWidth, 0) - 8 : triggerRect.left;
305
- top = ((_f = (_e = (_d = elementRef.current.parentElement) == null ? void 0 : _d.getBoundingClientRect()) == null ? void 0 : _e.top) != null ? _f : 0) + ((_i = (_h = (_g = elementRef.current.parentElement) == null ? void 0 : _g.getBoundingClientRect()) == null ? void 0 : _h.height) != null ? _i : 0) + topOffset;
352
+ if (isMobile && menuWidth > viewportWidth) {
353
+ left = 0;
306
354
  }
307
355
  setMenuPosition({
308
356
  top,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  useMenuPosition,
4
4
  useSubMenuSystem
5
- } from "../chunk-5GUW4DUY.js";
5
+ } from "../chunk-AEDQLVKH.js";
6
6
  import "../chunk-WNQ53SVY.js";
7
7
  import "../chunk-ORMEWXMH.js";
8
8
  export {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@dmsi/wedgekit-react",
3
3
  "private": false,
4
- "version": "0.0.185",
4
+ "version": "0.0.187",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "build": "tsup",
@@ -67,6 +67,7 @@ export const Menu = ({
67
67
  setIsOpen: setShow,
68
68
  menuRef: internalRef,
69
69
  topOffset,
70
+ isMobile: !!(isMobile && mobilePositionTo)
70
71
  },
71
72
  );
72
73
 
@@ -158,6 +159,7 @@ export const Menu = ({
158
159
  "shadow-4 rounded-base bg-background-grouped-primary-normal overflow-x-hidden overflow-y-auto flex flex-col outline-0",
159
160
  "fixed",
160
161
  "z-50",
162
+ "max-w-screen",
161
163
  mobileHide && "opacity-0 pointer-events-none",
162
164
  className,
163
165
  )}
@@ -38,6 +38,7 @@ type BaseProps = PropsWithChildren<{
38
38
  onSubMenuHover: (menuId: string, level: number) => void;
39
39
  onSubMenuLeave: (level: number) => void;
40
40
  onSubMenuEnter: () => void;
41
+ onMouseMove: () => void;
41
42
  toggleMenu: (id: string, level: number) => void;
42
43
  activeMenu: string;
43
44
  closeSubMenuLevel: (level: number) => void;
@@ -48,6 +49,7 @@ type BaseProps = PropsWithChildren<{
48
49
  onSubMenuHover?: (menuId: string, level: number) => void;
49
50
  onSubMenuLeave?: (level: number) => void;
50
51
  onSubMenuEnter?: () => void;
52
+ onMouseMove?: () => void;
51
53
  toggleMenu?: (id: string, level: number) => void;
52
54
  closeSubMenuLevel?: (level: number) => void;
53
55
  activeMenu?: string;
@@ -98,6 +100,7 @@ export const MenuOption = ({
98
100
  mobilePositionTo,
99
101
  highlightMatchingText = false,
100
102
  menuValue,
103
+ onMouseMove
101
104
  }: MenuOptionProps) => {
102
105
  const uniqueId = useId();
103
106
  const internalRef = useRef(null);
@@ -117,6 +120,12 @@ export const MenuOption = ({
117
120
  }
118
121
  };
119
122
 
123
+ const handleMouseMove = () => {
124
+ if (subMenu && onMouseMove && !disabled) {
125
+ onMouseMove();
126
+ }
127
+ }
128
+
120
129
  const handleSubMenuEnter = () => {
121
130
  if (onSubMenuEnter) {
122
131
  onSubMenuEnter();
@@ -209,6 +218,7 @@ export const MenuOption = ({
209
218
  }}
210
219
  onMouseEnter={handleMouseEnter}
211
220
  onMouseLeave={handleMouseLeave}
221
+ onMouseMove={handleMouseMove}
212
222
  {...additionalAttributes}
213
223
  tabIndex={-1}
214
224
  role="menuitem"
@@ -23,6 +23,10 @@ export function useSubMenuSystem(
23
23
  const subMenuRefs = useRef<{ [id: string]: HTMLElement | null }>({});
24
24
  const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
25
25
  const closeTimeoutRef = useRef<NodeJS.Timeout | null>(null);
26
+ const mouseStopTimeoutRef = useRef<NodeJS.Timeout | null>(null);
27
+ const isMouseMovingRef = useRef(false);
28
+ const pendingOpenActionRef = useRef<(() => void) | null>(null);
29
+ const pendingCloseActionRef = useRef<(() => void) | null>(null);
26
30
  const isMobile = useMatchesMobile();
27
31
 
28
32
  const toggleMenu = (menuId: string, level: number) => {
@@ -72,18 +76,36 @@ export function useSubMenuSystem(
72
76
  });
73
77
  };
74
78
 
79
+ const executePendingActions = useCallback(() => {
80
+ if (pendingCloseActionRef.current) {
81
+ pendingCloseActionRef.current();
82
+ pendingCloseActionRef.current = null;
83
+ }
84
+
85
+ if (pendingOpenActionRef.current) {
86
+ pendingOpenActionRef.current();
87
+ pendingOpenActionRef.current = null;
88
+ }
89
+ }, []);
90
+
75
91
  const openMenuWithDelay = (menuId: string, level: number, delay = 150) => {
76
92
  if (isMobile) {
77
93
  return;
78
94
  }
79
95
 
80
- if (hoverTimeoutRef.current) {
81
- clearTimeout(hoverTimeoutRef.current);
96
+ pendingOpenActionRef.current = () => {
97
+ if (hoverTimeoutRef.current) {
98
+ clearTimeout(hoverTimeoutRef.current);
99
+ }
100
+
101
+ hoverTimeoutRef.current = setTimeout(() => {
102
+ openMenu(menuId, level);
103
+ }, delay);
82
104
  }
83
105
 
84
- hoverTimeoutRef.current = setTimeout(() => {
85
- openMenu(menuId, level);
86
- }, delay);
106
+ if (!isMouseMovingRef.current) {
107
+ executePendingActions();
108
+ }
87
109
  };
88
110
 
89
111
  const closeMenuWithDelay = (level: number, delay = 500) => {
@@ -91,14 +113,33 @@ export function useSubMenuSystem(
91
113
  return;
92
114
  }
93
115
 
94
- if (hoverTimeoutRef.current) {
95
- clearTimeout(hoverTimeoutRef.current);
96
- hoverTimeoutRef.current = null;
116
+ pendingCloseActionRef.current = () => {
117
+ if (hoverTimeoutRef.current) {
118
+ clearTimeout(hoverTimeoutRef.current);
119
+ hoverTimeoutRef.current = null;
120
+ }
121
+
122
+ closeTimeoutRef.current = setTimeout(() => {
123
+ closeSubMenuLevel(level);
124
+ }, delay);
125
+ }
126
+
127
+ if (!isMouseMovingRef.current) {
128
+ executePendingActions();
97
129
  }
130
+ };
98
131
 
99
- closeTimeoutRef.current = setTimeout(() => {
100
- closeSubMenuLevel(level);
101
- }, delay);
132
+ const handleMouseMove = () => {
133
+ isMouseMovingRef.current = true;
134
+
135
+ if (mouseStopTimeoutRef.current) {
136
+ clearTimeout(mouseStopTimeoutRef.current);
137
+ }
138
+
139
+ mouseStopTimeoutRef.current = setTimeout(() => {
140
+ isMouseMovingRef.current = false;
141
+ executePendingActions();
142
+ }, 200);
102
143
  };
103
144
 
104
145
  const cancelCloseTimeout = () => {
@@ -106,9 +147,10 @@ export function useSubMenuSystem(
106
147
  return;
107
148
  }
108
149
 
109
- if (closeTimeoutRef.current) {
110
- clearTimeout(closeTimeoutRef.current);
111
- closeTimeoutRef.current = null;
150
+ if (mouseStopTimeoutRef.current) {
151
+ clearTimeout(mouseStopTimeoutRef.current);
152
+ mouseStopTimeoutRef.current = null;
153
+ isMouseMovingRef.current = false;
112
154
  }
113
155
  };
114
156
 
@@ -250,6 +292,7 @@ export function useSubMenuSystem(
250
292
  onSubMenuHover: openMenuWithDelay,
251
293
  onSubMenuLeave: closeMenuWithDelay,
252
294
  onSubMenuEnter: cancelCloseTimeout,
295
+ onMouseMove: handleMouseMove,
253
296
  toggleMenu,
254
297
  mobilePositionTo,
255
298
  activeMenu,
@@ -268,6 +311,7 @@ export function useMenuPosition(
268
311
  menuRef?: RefObject<HTMLElement | null>;
269
312
  additionalRefs?: RefObject<HTMLElement | null>[];
270
313
  topOffset?: number | null;
314
+ isMobile?: boolean;
271
315
  },
272
316
  ) {
273
317
  const [menuPosition, setMenuPosition] = useState<MenuPosition>({
@@ -276,7 +320,7 @@ export function useMenuPosition(
276
320
  minWidth: 0,
277
321
  });
278
322
 
279
- const isMobile = useMatchesMobile();
323
+ const isMobile = options?.isMobile;
280
324
 
281
325
  const updatePosition = useCallback(() => {
282
326
  if (!elementRef?.current) return;
@@ -302,25 +346,32 @@ export function useMenuPosition(
302
346
  } else if (position === "bottom-right") {
303
347
  left = triggerRect.right + window.scrollX - menuWidth;
304
348
  } else if (position === "right") {
305
- left = triggerRect.right + window.scrollX;
349
+ if (isMobile) {
350
+ left = triggerRect.left + window.scrollX
351
+ } else {
352
+ left = triggerRect.right + window.scrollX;
353
+ }
306
354
  }
307
355
 
308
- const overflowsLeftViewport = left + menuWidth > viewportWidth;
356
+ const overflowsRightViewport = left + menuWidth > viewportWidth;
357
+
358
+ if (overflowsRightViewport) {
359
+ const newLeft = triggerRect.left - menuWidth;
360
+ const overflowsLeftViewport = newLeft < 0;
361
+
362
+ if (overflowsLeftViewport) {
363
+ left = (viewportWidth - menuWidth) / 2;
364
+ } else {
365
+ left = newLeft;
366
+ }
367
+ }
309
368
 
310
- if (overflowsLeftViewport) {
311
- left = triggerRect.left - menuWidth;
369
+ if (isMobile && position === "right") {
370
+ top = triggerRect.top + triggerRect.height + topOffset;
312
371
  }
313
372
 
314
- if (isMobile) {
315
- left =
316
- triggerRect.left + menuWidth > viewportWidth
317
- ? Math.max(viewportWidth - menuWidth, 0) - 8
318
- : triggerRect.left;
319
- top =
320
- (elementRef.current.parentElement?.getBoundingClientRect()?.top ?? 0) +
321
- (elementRef.current.parentElement?.getBoundingClientRect()?.height ??
322
- 0) +
323
- topOffset;
373
+ if (isMobile && menuWidth > viewportWidth) {
374
+ left = 0;
324
375
  }
325
376
 
326
377
  setMenuPosition({