@topconsultnpm/sdkui-react 6.20.0-dev1.13 → 6.20.0-dev1.131

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 (202) hide show
  1. package/lib/assets/Toppy-help-center.png +0 -0
  2. package/lib/assets/headergradient.svg +87 -0
  3. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +322 -30
  4. package/lib/components/NewComponents/ContextMenu/hooks.d.ts +8 -1
  5. package/lib/components/NewComponents/ContextMenu/hooks.js +80 -8
  6. package/lib/components/NewComponents/ContextMenu/index.d.ts +3 -0
  7. package/lib/components/NewComponents/ContextMenu/index.js +2 -0
  8. package/lib/components/NewComponents/ContextMenu/styles.d.ts +9 -1
  9. package/lib/components/NewComponents/ContextMenu/styles.js +146 -47
  10. package/lib/components/NewComponents/ContextMenu/types.d.ts +22 -3
  11. package/lib/components/NewComponents/ContextMenu/useLongPress.d.ts +21 -0
  12. package/lib/components/NewComponents/ContextMenu/useLongPress.js +112 -0
  13. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +620 -125
  14. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +25 -5
  15. package/lib/components/NewComponents/FloatingMenuBar/styles.js +215 -59
  16. package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +12 -3
  17. package/lib/components/base/TMAccordionNew.js +35 -14
  18. package/lib/components/base/TMButton.js +6 -0
  19. package/lib/components/base/TMClosableList.js +4 -0
  20. package/lib/components/base/TMCustomButton.js +61 -17
  21. package/lib/components/base/TMDataGrid.d.ts +7 -4
  22. package/lib/components/base/TMDataGrid.js +153 -11
  23. package/lib/components/base/TMDropDownMenu.js +21 -18
  24. package/lib/components/base/TMFileManager.d.ts +4 -3
  25. package/lib/components/base/TMFileManager.js +32 -24
  26. package/lib/components/base/TMFileManagerDataGridView.d.ts +3 -2
  27. package/lib/components/base/TMFileManagerDataGridView.js +1 -11
  28. package/lib/components/base/TMFileManagerThumbnailItems.d.ts +7 -1
  29. package/lib/components/base/TMFileManagerThumbnailItems.js +5 -2
  30. package/lib/components/base/TMFileManagerThumbnailsView.d.ts +17 -4
  31. package/lib/components/base/TMFileManagerThumbnailsView.js +18 -6
  32. package/lib/components/base/TMFileManagerUtils.d.ts +0 -12
  33. package/lib/components/base/TMListView.js +33 -15
  34. package/lib/components/base/TMPanel.d.ts +1 -1
  35. package/lib/components/base/TMPanel.js +4 -2
  36. package/lib/components/base/TMPopUp.js +6 -0
  37. package/lib/components/base/TMToolbarCard.js +2 -0
  38. package/lib/components/base/TMTreeView.d.ts +2 -1
  39. package/lib/components/base/TMTreeView.js +33 -26
  40. package/lib/components/choosers/TMDataListItemChooser.d.ts +2 -0
  41. package/lib/components/choosers/TMDataListItemChooser.js +8 -2
  42. package/lib/components/choosers/TMDcmtTypeChooser.d.ts +1 -0
  43. package/lib/components/choosers/TMDcmtTypeChooser.js +11 -3
  44. package/lib/components/choosers/TMDistinctValues.js +2 -2
  45. package/lib/components/choosers/TMDynDataListItemChooser.d.ts +2 -0
  46. package/lib/components/choosers/TMDynDataListItemChooser.js +8 -2
  47. package/lib/components/choosers/TMInvoiceRetrieveFormats.js +1 -1
  48. package/lib/components/choosers/TMMetadataChooser.d.ts +2 -0
  49. package/lib/components/choosers/TMMetadataChooser.js +19 -4
  50. package/lib/components/choosers/TMOrderRetrieveFormats.js +1 -1
  51. package/lib/components/choosers/TMUserChooser.d.ts +2 -5
  52. package/lib/components/choosers/TMUserChooser.js +33 -47
  53. package/lib/components/editors/TMCheckBox.js +2 -0
  54. package/lib/components/editors/TMDateBox.js +18 -9
  55. package/lib/components/editors/TMEditorStyled.js +7 -0
  56. package/lib/components/editors/TMLocalizedTextBox.d.ts +3 -1
  57. package/lib/components/editors/TMLocalizedTextBox.js +16 -14
  58. package/lib/components/editors/TMMetadataEditor.d.ts +1 -0
  59. package/lib/components/editors/TMMetadataEditor.js +4 -4
  60. package/lib/components/editors/TMMetadataTextBox.d.ts +9 -0
  61. package/lib/components/editors/TMMetadataTextBox.js +92 -0
  62. package/lib/components/editors/TMMetadataValues.d.ts +2 -0
  63. package/lib/components/editors/TMMetadataValues.js +26 -8
  64. package/lib/components/editors/TMRadioButton.js +2 -0
  65. package/lib/components/editors/TMTextArea.js +18 -30
  66. package/lib/components/editors/TMTextBox.d.ts +1 -1
  67. package/lib/components/editors/TMTextBox.js +29 -4
  68. package/lib/components/editors/TMTextExpression.js +6 -91
  69. package/lib/components/features/archive/TMArchive.js +2 -2
  70. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.d.ts +15 -0
  71. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +462 -0
  72. package/lib/components/features/assistant/TMToppySpeechBubble.d.ts +11 -0
  73. package/lib/components/features/assistant/TMToppySpeechBubble.js +126 -0
  74. package/lib/components/features/documents/TMDcmtBlog.js +1 -1
  75. package/lib/components/features/documents/TMDcmtForm.d.ts +14 -2
  76. package/lib/components/features/documents/TMDcmtForm.js +576 -292
  77. package/lib/components/features/documents/TMDcmtPreview.js +42 -155
  78. package/lib/components/features/documents/TMDcmtTasks.js +9 -9
  79. package/lib/components/features/documents/TMMasterDetailDcmts.js +38 -53
  80. package/lib/components/features/documents/TMRelationViewer.d.ts +1 -1
  81. package/lib/components/features/documents/TMRelationViewer.js +2 -2
  82. package/lib/components/features/search/TMDcmtCheckoutInfoForm.d.ts +8 -0
  83. package/lib/components/features/search/{TMSearchResultCheckoutInfoForm.js → TMDcmtCheckoutInfoForm.js} +2 -2
  84. package/lib/components/features/search/TMSavedQuerySelector.js +72 -67
  85. package/lib/components/features/search/TMSearch.d.ts +3 -0
  86. package/lib/components/features/search/TMSearch.js +50 -11
  87. package/lib/components/features/search/TMSearchQueryEditor.d.ts +1 -0
  88. package/lib/components/features/search/TMSearchQueryEditor.js +10 -10
  89. package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -0
  90. package/lib/components/features/search/TMSearchQueryPanel.js +40 -25
  91. package/lib/components/features/search/TMSearchResult.d.ts +3 -0
  92. package/lib/components/features/search/TMSearchResult.js +370 -252
  93. package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +3 -3
  94. package/lib/components/features/search/TMSearchResultsMenuItems.js +227 -171
  95. package/lib/components/features/search/TMSignSettingsForm.js +1 -1
  96. package/lib/components/features/search/TMSignatureInfoContent.d.ts +6 -0
  97. package/lib/components/features/search/TMSignatureInfoContent.js +140 -0
  98. package/lib/components/features/search/TMViewHistoryDcmt.js +47 -52
  99. package/lib/components/features/tasks/TMTaskForm.js +75 -25
  100. package/lib/components/features/tasks/TMTasksAgenda.d.ts +3 -1
  101. package/lib/components/features/tasks/TMTasksAgenda.js +48 -9
  102. package/lib/components/features/tasks/TMTasksCalendar.d.ts +2 -0
  103. package/lib/components/features/tasks/TMTasksCalendar.js +19 -7
  104. package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -2
  105. package/lib/components/features/tasks/TMTasksUtils.js +57 -37
  106. package/lib/components/features/tasks/TMTasksView.js +28 -19
  107. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +33 -2
  108. package/lib/components/features/workflow/TMWorkflowPopup.js +140 -34
  109. package/lib/components/features/workflow/diagram/DiagramItemComponent.d.ts +2 -0
  110. package/lib/components/features/workflow/diagram/DiagramItemComponent.js +14 -7
  111. package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
  112. package/lib/components/features/workflow/diagram/RecipientList.js +3 -2
  113. package/lib/components/features/workflow/diagram/WFDiagram.d.ts +4 -0
  114. package/lib/components/features/workflow/diagram/WFDiagram.js +164 -13
  115. package/lib/components/forms/Login/LoginValidatorService.d.ts +2 -0
  116. package/lib/components/forms/Login/LoginValidatorService.js +7 -2
  117. package/lib/components/forms/Login/TMLoginForm.js +35 -7
  118. package/lib/components/forms/TMChooserForm.js +1 -1
  119. package/lib/components/grids/TMBlogsPost.js +56 -31
  120. package/lib/components/grids/TMRecentsManager.js +20 -10
  121. package/lib/components/grids/TMValidationItemsList.js +6 -0
  122. package/lib/components/index.d.ts +6 -3
  123. package/lib/components/index.js +6 -3
  124. package/lib/components/layout/panelManager/TMPanelManagerContext.js +13 -5
  125. package/lib/components/query/TMQueryEditor.d.ts +6 -1
  126. package/lib/components/query/TMQueryEditor.js +105 -101
  127. package/lib/components/settings/SettingsAppearance.d.ts +2 -1
  128. package/lib/components/settings/SettingsAppearance.js +99 -30
  129. package/lib/components/sidebar/TMHeader.js +11 -7
  130. package/lib/components/sidebar/TMSidebar.d.ts +0 -1
  131. package/lib/components/sidebar/TMSidebar.js +16 -44
  132. package/lib/components/sidebar/TMSidebarItem.js +36 -17
  133. package/lib/components/viewers/TMDataListItemViewer.d.ts +2 -1
  134. package/lib/components/viewers/TMDataListItemViewer.js +35 -71
  135. package/lib/components/viewers/TMDataUserIdItemViewer.d.ts +8 -0
  136. package/lib/components/viewers/TMDataUserIdItemViewer.js +39 -0
  137. package/lib/css/tm-sdkui.css +1 -1
  138. package/lib/helper/SDKUI_Globals.d.ts +22 -0
  139. package/lib/helper/SDKUI_Globals.js +10 -1
  140. package/lib/helper/SDKUI_Localizator.d.ts +21 -3
  141. package/lib/helper/SDKUI_Localizator.js +196 -10
  142. package/lib/helper/TMCommandsContextMenu.d.ts +4 -2
  143. package/lib/helper/TMCommandsContextMenu.js +15 -4
  144. package/lib/helper/TMIcons.d.ts +4 -0
  145. package/lib/helper/TMIcons.js +13 -3
  146. package/lib/helper/TMPdfViewer.d.ts +8 -0
  147. package/lib/helper/TMPdfViewer.js +373 -0
  148. package/lib/helper/TMToppyMessage.js +4 -0
  149. package/lib/helper/checkinCheckoutManager.d.ts +31 -1
  150. package/lib/helper/checkinCheckoutManager.js +112 -30
  151. package/lib/helper/devextremeCustomMessages.d.ts +30 -0
  152. package/lib/helper/devextremeCustomMessages.js +30 -0
  153. package/lib/helper/helpers.d.ts +30 -2
  154. package/lib/helper/helpers.js +132 -4
  155. package/lib/helper/index.d.ts +2 -0
  156. package/lib/helper/index.js +2 -0
  157. package/lib/helper/queryHelper.d.ts +2 -2
  158. package/lib/helper/queryHelper.js +80 -24
  159. package/lib/helper/workItemsHelper.d.ts +6 -0
  160. package/lib/helper/workItemsHelper.js +230 -0
  161. package/lib/hooks/useCheckInOutOperations.d.ts +28 -0
  162. package/lib/hooks/useCheckInOutOperations.js +223 -0
  163. package/lib/hooks/useDataListItem.d.ts +12 -0
  164. package/lib/hooks/useDataListItem.js +132 -0
  165. package/lib/hooks/useDataUserIdItem.d.ts +10 -0
  166. package/lib/hooks/useDataUserIdItem.js +96 -0
  167. package/lib/hooks/useFloatingBarPinnedItems.d.ts +11 -0
  168. package/lib/hooks/useFloatingBarPinnedItems.js +54 -0
  169. package/lib/hooks/useMetadataExpression.d.ts +19 -0
  170. package/lib/hooks/useMetadataExpression.js +99 -0
  171. package/lib/hooks/useSettingsFeedback.d.ts +11 -0
  172. package/lib/hooks/useSettingsFeedback.js +38 -0
  173. package/lib/hooks/useWorkflowApprove.d.ts +4 -0
  174. package/lib/hooks/useWorkflowApprove.js +14 -1
  175. package/lib/index.d.ts +1 -0
  176. package/lib/index.js +3 -2
  177. package/lib/services/platform_services.d.ts +3 -3
  178. package/lib/ts/types.d.ts +61 -1
  179. package/lib/utils/theme.d.ts +1 -1
  180. package/lib/utils/theme.js +1 -1
  181. package/package.json +8 -6
  182. package/lib/components/NewComponents/Notification/Notification.d.ts +0 -4
  183. package/lib/components/NewComponents/Notification/Notification.js +0 -60
  184. package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +0 -8
  185. package/lib/components/NewComponents/Notification/NotificationContainer.js +0 -33
  186. package/lib/components/NewComponents/Notification/index.d.ts +0 -2
  187. package/lib/components/NewComponents/Notification/index.js +0 -2
  188. package/lib/components/NewComponents/Notification/styles.d.ts +0 -21
  189. package/lib/components/NewComponents/Notification/styles.js +0 -180
  190. package/lib/components/NewComponents/Notification/types.d.ts +0 -18
  191. package/lib/components/NewComponents/Notification/types.js +0 -1
  192. package/lib/components/base/TMContextMenu.d.ts +0 -25
  193. package/lib/components/base/TMContextMenu.js +0 -109
  194. package/lib/components/base/TMContextMenuOLD.d.ts +0 -26
  195. package/lib/components/base/TMContextMenuOLD.js +0 -56
  196. package/lib/components/base/TMFloatingToolbar.d.ts +0 -9
  197. package/lib/components/base/TMFloatingToolbar.js +0 -101
  198. package/lib/components/features/assistant/ToppyDraggableHelpCenter.d.ts +0 -30
  199. package/lib/components/features/assistant/ToppyDraggableHelpCenter.js +0 -482
  200. package/lib/components/features/assistant/ToppySpeechBubble.d.ts +0 -9
  201. package/lib/components/features/assistant/ToppySpeechBubble.js +0 -117
  202. package/lib/components/features/search/TMSearchResultCheckoutInfoForm.d.ts +0 -8
Binary file
@@ -0,0 +1,87 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <svg id="Livello_2" data-name="Livello 2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1920 505.45">
3
+ <defs>
4
+ <style>
5
+ .cls-1 {
6
+ fill: url(#Sfumatura_senza_nome_6);
7
+ }
8
+
9
+ .cls-1, .cls-2, .cls-3, .cls-4 {
10
+ opacity: .5;
11
+ }
12
+
13
+ .cls-5 {
14
+ clip-path: url(#clippath-2);
15
+ }
16
+
17
+ .cls-5, .cls-6, .cls-7, .cls-8, .cls-9 {
18
+ fill: none;
19
+ }
20
+
21
+ .cls-2 {
22
+ fill: url(#Sfumatura_senza_nome_2);
23
+ }
24
+
25
+ .cls-6 {
26
+ clip-path: url(#clippath-3);
27
+ }
28
+
29
+ .cls-8 {
30
+ clip-path: url(#clippath);
31
+ }
32
+
33
+ .cls-3 {
34
+ fill: url(#Sfumatura_senza_nome_3-2);
35
+ }
36
+
37
+ .cls-9 {
38
+ clip-path: url(#clippath-1);
39
+ }
40
+
41
+ .cls-4 {
42
+ fill: url(#Sfumatura_senza_nome_3);
43
+ }
44
+ </style>
45
+ <clipPath id="clippath">
46
+ <rect class="cls-7" x="1237.53" y="0" width="682.47" height="326.62"/>
47
+ </clipPath>
48
+ <radialGradient id="Sfumatura_senza_nome_3" data-name="Sfumatura senza nome 3" cx="1285.93" cy="3418.23" fx="1285.93" fy="3418.23" r="50" gradientTransform="translate(-6983.46 -9577.73) scale(6.82 2.8)" gradientUnits="userSpaceOnUse">
49
+ <stop offset="0" stop-color="#2459a4"/>
50
+ <stop offset="1" stop-color="#fff" stop-opacity="0"/>
51
+ </radialGradient>
52
+ <clipPath id="clippath-1">
53
+ <rect class="cls-7" x="0" y="0" width="682.47" height="326.62" transform="translate(682.47 326.62) rotate(-180)"/>
54
+ </clipPath>
55
+ <radialGradient id="Sfumatura_senza_nome_6" data-name="Sfumatura senza nome 6" cx="1673.12" cy="3418.23" fx="1673.12" fy="3418.23" r="50" gradientTransform="translate(11545.93 -9577.73) rotate(-180) scale(6.82 -2.8)" gradientUnits="userSpaceOnUse">
56
+ <stop offset="0" stop-color="#2459a4"/>
57
+ <stop offset="1" stop-color="#fff" stop-opacity="0"/>
58
+ </radialGradient>
59
+ <clipPath id="clippath-2">
60
+ <rect class="cls-7" x="0" y="0" width="1055.7" height="297.5"/>
61
+ </clipPath>
62
+ <radialGradient id="Sfumatura_senza_nome_3-2" data-name="Sfumatura senza nome 3" cx="955.6" cy="3436.6" fx="955.6" fy="3436.6" r="50" gradientTransform="translate(-9560.44 -8770.57) scale(10.56 2.55)" xlink:href="#Sfumatura_senza_nome_3"/>
63
+ <clipPath id="clippath-3">
64
+ <rect class="cls-7" x="297.09" y="0" width="1055.7" height="505.45"/>
65
+ </clipPath>
66
+ <radialGradient id="Sfumatura_senza_nome_2" data-name="Sfumatura senza nome 2" cx="983.74" cy="3351.83" fx="983.74" fy="3351.83" r="50" gradientTransform="translate(-9560.44 -14533.91) scale(10.56 4.33)" gradientUnits="userSpaceOnUse">
67
+ <stop offset="0" stop-color="#2459a4"/>
68
+ <stop offset="1" stop-color="#fff"/>
69
+ </radialGradient>
70
+ </defs>
71
+ <g id="Livello_1-2" data-name="Livello 1">
72
+ <g>
73
+ <g class="cls-8">
74
+ <ellipse class="cls-4" cx="1792.58" cy="-7.96" rx="341.23" ry="139.98"/>
75
+ </g>
76
+ <g class="cls-9">
77
+ <ellipse class="cls-1" cx="127.42" cy="-7.96" rx="341.23" ry="139.98"/>
78
+ </g>
79
+ <g class="cls-5">
80
+ <ellipse class="cls-3" cx="527.85" cy="-7.25" rx="527.85" ry="127.5"/>
81
+ </g>
82
+ <g class="cls-6">
83
+ <ellipse class="cls-2" cx="824.94" cy="-12.32" rx="527.85" ry="216.62"/>
84
+ </g>
85
+ </g>
86
+ </g>
87
+ </svg>
@@ -1,8 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect } from 'react';
3
+ import { createPortal } from 'react-dom';
3
4
  import * as S from './styles';
4
- import { useIsMobile, useMenuPosition } from './hooks';
5
- const TMContextMenu = ({ items, trigger = 'right', children }) => {
5
+ import { useIsMobile, useMenuPosition, useIsIOS } from './hooks';
6
+ import { IconArrowLeft } from '../../../helper';
7
+ const TMContextMenu = ({ items, trigger = 'right', children, target, externalControl, keepOpenOnClick = false }) => {
6
8
  const [menuState, setMenuState] = useState({
7
9
  visible: false,
8
10
  position: { x: 0, y: 0 },
@@ -10,24 +12,217 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
10
12
  parentNames: [],
11
13
  });
12
14
  const [hoveredSubmenus, setHoveredSubmenus] = useState([]);
15
+ // Tracks items whose rightIconProps have been clicked, so the icon color flips immediately
16
+ const [toggledItemIds, setToggledItemIds] = useState(new Set());
13
17
  const isMobile = useIsMobile();
18
+ const isIOS = useIsIOS();
14
19
  const menuRef = useRef(null);
15
20
  const triggerRef = useRef(null);
16
21
  const submenuTimeoutRef = useRef(null);
17
- const { openLeft, openUp } = useMenuPosition(menuRef, menuState.position);
22
+ const longPressTimeoutRef = useRef(null);
23
+ const touchStartPos = useRef(null);
24
+ // Get current menu to pass items count to positioning hook
25
+ const currentMenu = menuState.submenuStack.at(-1) || items;
26
+ const { openLeft, openUp, isCalculated, needsScroll, maxHeight } = useMenuPosition(menuRef, menuState.position, currentMenu.length);
18
27
  const handleClose = () => {
19
- setMenuState(prev => ({
20
- ...prev,
21
- visible: false,
22
- submenuStack: [items],
23
- parentNames: [],
24
- }));
28
+ if (externalControl) {
29
+ externalControl.onClose();
30
+ }
31
+ else {
32
+ setMenuState(prev => ({
33
+ ...prev,
34
+ visible: false,
35
+ submenuStack: [items],
36
+ parentNames: [],
37
+ }));
38
+ }
25
39
  setHoveredSubmenus([]);
40
+ setToggledItemIds(new Set());
26
41
  };
42
+ // Sync with external control when provided
43
+ useEffect(() => {
44
+ if (externalControl) {
45
+ setMenuState(prev => ({
46
+ ...prev,
47
+ visible: externalControl.visible,
48
+ position: externalControl.position,
49
+ submenuStack: [items],
50
+ parentNames: [],
51
+ }));
52
+ }
53
+ }, [externalControl, items]);
54
+ // iOS long-press support: attach touch listeners to target elements
55
+ // On long-press, dispatch synthetic contextmenu event to trigger existing handlers
56
+ useEffect(() => {
57
+ if (!target || !isIOS)
58
+ return;
59
+ const elements = document.querySelectorAll(target);
60
+ if (elements.length === 0)
61
+ return;
62
+ const touchStateMap = new WeakMap();
63
+ const handleTouchStart = (e) => {
64
+ const touchEvent = e;
65
+ const element = e.currentTarget;
66
+ const touch = touchEvent.touches[0];
67
+ let state = touchStateMap.get(element);
68
+ if (!state) {
69
+ state = { timeout: null, startX: 0, startY: 0, longPressTriggered: false };
70
+ touchStateMap.set(element, state);
71
+ }
72
+ state.startX = touch.clientX;
73
+ state.startY = touch.clientY;
74
+ state.longPressTriggered = false;
75
+ if (state.timeout)
76
+ clearTimeout(state.timeout);
77
+ state.timeout = setTimeout(() => {
78
+ if (state)
79
+ state.longPressTriggered = true;
80
+ // Haptic feedback
81
+ if ('vibrate' in navigator)
82
+ navigator.vibrate(50);
83
+ const syntheticEvent = new MouseEvent('contextmenu', {
84
+ bubbles: true,
85
+ cancelable: true,
86
+ clientX: touch.clientX,
87
+ clientY: touch.clientY,
88
+ });
89
+ // Mark this as our synthetic event
90
+ syntheticEvent.isSynthetic = true;
91
+ element.dispatchEvent(syntheticEvent);
92
+ if (state)
93
+ state.timeout = null;
94
+ }, 500);
95
+ };
96
+ const handleTouchMove = (e) => {
97
+ const touchEvent = e;
98
+ const element = e.currentTarget;
99
+ const state = touchStateMap.get(element);
100
+ if (!state?.timeout)
101
+ return;
102
+ const touch = touchEvent.touches[0];
103
+ const dx = Math.abs(touch.clientX - state.startX);
104
+ const dy = Math.abs(touch.clientY - state.startY);
105
+ if (dx > 10 || dy > 10) {
106
+ clearTimeout(state.timeout);
107
+ state.timeout = null;
108
+ }
109
+ };
110
+ const handleTouchEnd = (e) => {
111
+ const element = e.currentTarget;
112
+ const state = touchStateMap.get(element);
113
+ if (state?.timeout) {
114
+ clearTimeout(state.timeout);
115
+ state.timeout = null;
116
+ }
117
+ };
118
+ // Prevent click event after long-press was triggered
119
+ const handleClick = (e) => {
120
+ const element = e.currentTarget;
121
+ const state = touchStateMap.get(element);
122
+ if (state?.longPressTriggered) {
123
+ e.preventDefault();
124
+ e.stopPropagation();
125
+ e.stopImmediatePropagation();
126
+ state.longPressTriggered = false;
127
+ }
128
+ };
129
+ // Prevent default iOS context menu (but allow our synthetic events)
130
+ const handleContextMenu = (e) => {
131
+ // Don't prevent our own synthetic contextmenu events
132
+ if (e.isSynthetic)
133
+ return;
134
+ e.preventDefault();
135
+ e.stopPropagation();
136
+ return false;
137
+ };
138
+ // Attach listeners to all matching elements
139
+ elements.forEach(element => {
140
+ const el = element;
141
+ // Prevent iOS native callout and text selection - must be set before touch starts
142
+ const style = el.style;
143
+ style.webkitTouchCallout = 'none';
144
+ style.webkitUserSelect = 'none';
145
+ el.addEventListener('touchstart', handleTouchStart, { passive: true });
146
+ el.addEventListener('touchmove', handleTouchMove, { passive: true });
147
+ el.addEventListener('touchend', handleTouchEnd);
148
+ el.addEventListener('touchcancel', handleTouchEnd);
149
+ el.addEventListener('contextmenu', handleContextMenu);
150
+ el.addEventListener('click', handleClick, { capture: true });
151
+ });
152
+ return () => {
153
+ elements.forEach(element => {
154
+ const el = element;
155
+ // Restore webkit properties
156
+ const style = el.style;
157
+ style.webkitTouchCallout = '';
158
+ style.webkitUserSelect = '';
159
+ el.removeEventListener('touchstart', handleTouchStart);
160
+ el.removeEventListener('touchmove', handleTouchMove);
161
+ el.removeEventListener('touchend', handleTouchEnd);
162
+ el.removeEventListener('touchcancel', handleTouchEnd);
163
+ el.removeEventListener('contextmenu', handleContextMenu);
164
+ el.removeEventListener('click', handleClick, { capture: true });
165
+ });
166
+ };
167
+ }, [target, isIOS]);
168
+ // Update items when they change while menu is visible (for keepOpenOnClick behavior)
169
+ useEffect(() => {
170
+ if (!keepOpenOnClick)
171
+ return;
172
+ if (!externalControl && menuState.visible) {
173
+ setMenuState(prev => ({
174
+ ...prev,
175
+ submenuStack: [items],
176
+ }));
177
+ // Update hoveredSubmenus with fresh items while keeping them open
178
+ setHoveredSubmenus(prev => {
179
+ if (prev.length === 0)
180
+ return prev;
181
+ // Rebuild hoveredSubmenus with updated items from the new items structure
182
+ return prev.map(submenu => {
183
+ // Find the matching submenu in the new items structure
184
+ const findSubmenuInItems = (searchItems) => {
185
+ for (const item of searchItems) {
186
+ if (item.submenu && item.submenu.length > 0) {
187
+ // Check if this submenu matches (compare first item name as identifier)
188
+ if (submenu.items.length > 0 && item.submenu.length > 0 &&
189
+ item.submenu[0].name === submenu.items[0].name) {
190
+ return item.submenu;
191
+ }
192
+ // Recursively search in nested submenus
193
+ const found = findSubmenuInItems(item.submenu);
194
+ if (found)
195
+ return found;
196
+ }
197
+ }
198
+ return null;
199
+ };
200
+ const updatedItems = findSubmenuInItems(items);
201
+ return {
202
+ ...submenu,
203
+ items: updatedItems || submenu.items, // Use updated items if found, otherwise keep old
204
+ };
205
+ });
206
+ });
207
+ }
208
+ }, [items, menuState.visible, externalControl, keepOpenOnClick]);
209
+ // Track when the menu was opened to prevent immediate close on iOS
210
+ const menuOpenedAtRef = useRef(0);
211
+ useEffect(() => {
212
+ if (menuState.visible) {
213
+ menuOpenedAtRef.current = Date.now();
214
+ }
215
+ }, [menuState.visible]);
27
216
  useEffect(() => {
28
217
  if (!menuState.visible)
29
218
  return;
30
219
  const handleClickOutside = (event) => {
220
+ // On iOS, prevent closing immediately after opening (within 500ms for iOS, 300ms for others)
221
+ // This handles the case where touchend from long-press triggers touchstart listener
222
+ const delay = isIOS ? 500 : 300;
223
+ if (Date.now() - menuOpenedAtRef.current < delay) {
224
+ return;
225
+ }
31
226
  const target = event.target;
32
227
  // Check if click is inside main menu
33
228
  if (menuRef.current?.contains(target)) {
@@ -45,20 +240,35 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
45
240
  };
46
241
  document.addEventListener('mousedown', handleClickOutside);
47
242
  document.addEventListener('touchstart', handleClickOutside);
243
+ document.addEventListener('contextmenu', handleClickOutside); // Close on right-click outside
48
244
  return () => {
49
245
  document.removeEventListener('mousedown', handleClickOutside);
50
246
  document.removeEventListener('touchstart', handleClickOutside);
247
+ document.removeEventListener('contextmenu', handleClickOutside);
51
248
  };
52
249
  }, [menuState.visible]);
53
250
  const handleContextMenu = (e) => {
54
251
  if (trigger === 'right') {
55
252
  e.preventDefault();
56
- setMenuState({
57
- visible: true,
58
- position: { x: e.clientX, y: e.clientY },
59
- submenuStack: [items],
60
- parentNames: [],
253
+ // Close any other open context menus by dispatching a synthetic contextmenu event to document
254
+ // This triggers the document listener in other ContextMenu instances to close them
255
+ const syntheticEvent = new MouseEvent('contextmenu', {
256
+ bubbles: true,
257
+ cancelable: true,
258
+ clientX: -1000, // Position outside viewport so it doesn't interfere
259
+ clientY: -1000,
61
260
  });
261
+ document.dispatchEvent(syntheticEvent);
262
+ e.stopPropagation(); // Prevent event from bubbling after closing others
263
+ // Small delay to ensure other menus close first
264
+ setTimeout(() => {
265
+ setMenuState({
266
+ visible: true,
267
+ position: { x: e.clientX, y: e.clientY },
268
+ submenuStack: [items],
269
+ parentNames: [],
270
+ });
271
+ }, 0);
62
272
  }
63
273
  };
64
274
  const handleClick = (e) => {
@@ -72,27 +282,73 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
72
282
  });
73
283
  }
74
284
  };
285
+ // iOS-specific touch handlers for long press
286
+ const handleTouchStart = (e) => {
287
+ if (!isIOS || trigger !== 'right')
288
+ return;
289
+ const touch = e.touches[0];
290
+ touchStartPos.current = { x: touch.clientX, y: touch.clientY };
291
+ if (longPressTimeoutRef.current) {
292
+ clearTimeout(longPressTimeoutRef.current);
293
+ }
294
+ longPressTimeoutRef.current = setTimeout(() => {
295
+ if (touchStartPos.current) {
296
+ if ('vibrate' in navigator) {
297
+ navigator.vibrate(50);
298
+ }
299
+ setMenuState({
300
+ visible: true,
301
+ position: { x: touchStartPos.current.x, y: touchStartPos.current.y },
302
+ submenuStack: [items],
303
+ parentNames: [],
304
+ });
305
+ }
306
+ }, 500);
307
+ };
308
+ const handleTouchMove = (e) => {
309
+ if (!isIOS || trigger !== 'right' || !touchStartPos.current)
310
+ return;
311
+ const touch = e.touches[0];
312
+ const moveThreshold = 10; // pixels
313
+ // If finger moved too much, cancel long press
314
+ const deltaX = Math.abs(touch.clientX - touchStartPos.current.x);
315
+ const deltaY = Math.abs(touch.clientY - touchStartPos.current.y);
316
+ if (deltaX > moveThreshold || deltaY > moveThreshold) {
317
+ if (longPressTimeoutRef.current) {
318
+ clearTimeout(longPressTimeoutRef.current);
319
+ longPressTimeoutRef.current = null;
320
+ }
321
+ touchStartPos.current = null;
322
+ }
323
+ };
324
+ const handleTouchEnd = () => {
325
+ if (!isIOS || trigger !== 'right')
326
+ return;
327
+ if (longPressTimeoutRef.current) {
328
+ clearTimeout(longPressTimeoutRef.current);
329
+ longPressTimeoutRef.current = null;
330
+ }
331
+ touchStartPos.current = null;
332
+ };
75
333
  const handleItemClick = (item) => {
76
334
  if (item.disabled)
77
335
  return;
78
- // Always execute onClick if present
79
336
  if (item.onClick) {
80
337
  item.onClick();
81
338
  }
82
339
  if (item.submenu && item.submenu.length > 0) {
83
340
  if (isMobile) {
84
- // Mobile: Push submenu to stack
85
341
  setMenuState(prev => ({
86
342
  ...prev,
87
343
  submenuStack: [...prev.submenuStack, item.submenu],
88
344
  parentNames: [...prev.parentNames, item.name],
89
345
  }));
90
346
  }
91
- // Desktop: Submenus are handled by hover, don't close menu
92
347
  }
93
348
  else {
94
- // No submenu: close menu after executing action
95
- handleClose();
349
+ if (!keepOpenOnClick) {
350
+ handleClose();
351
+ }
96
352
  }
97
353
  };
98
354
  const handleBack = () => {
@@ -103,20 +359,24 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
103
359
  }));
104
360
  };
105
361
  const handleMouseEnter = (item, event, depth = 0) => {
106
- if (isMobile || !item.submenu || item.submenu.length === 0)
362
+ if (isMobile || !item.submenu || item.submenu.length === 0 || item.disabled)
107
363
  return;
108
364
  if (submenuTimeoutRef.current) {
109
365
  clearTimeout(submenuTimeoutRef.current);
110
366
  submenuTimeoutRef.current = null;
111
367
  }
112
368
  const rect = event.currentTarget.getBoundingClientRect();
113
- // Calculate if submenu should open upward based on available space
114
- // Estimate submenu height: ~35px per item (accounting for smaller padding) + container padding
369
+ // Calculate submenu height dynamically
115
370
  const estimatedSubmenuHeight = (item.submenu.length * 35) + 8;
116
371
  const spaceBelow = window.innerHeight - rect.top;
117
372
  const spaceAbove = rect.bottom;
118
- // Open upward only if there's not enough space below AND there's more space above
373
+ const padding = 8;
374
+ // Determine if submenu should open upward
119
375
  const shouldOpenUp = spaceBelow < estimatedSubmenuHeight && spaceAbove > spaceBelow;
376
+ // Calculate if submenu needs scroll and max-height
377
+ const availableSpace = shouldOpenUp ? spaceAbove : spaceBelow;
378
+ const needsScroll = estimatedSubmenuHeight > availableSpace - padding;
379
+ const maxHeight = needsScroll ? availableSpace - padding : undefined;
120
380
  // Remove all submenus at this depth and deeper
121
381
  setHoveredSubmenus(prev => {
122
382
  const filtered = prev.filter(sub => sub.depth < depth);
@@ -129,6 +389,8 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
129
389
  parentRect: rect,
130
390
  depth: depth,
131
391
  openUp: shouldOpenUp,
392
+ needsScroll,
393
+ maxHeight,
132
394
  }
133
395
  ];
134
396
  });
@@ -155,6 +417,9 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
155
417
  if (submenuTimeoutRef.current) {
156
418
  clearTimeout(submenuTimeoutRef.current);
157
419
  }
420
+ if (longPressTimeoutRef.current) {
421
+ clearTimeout(longPressTimeoutRef.current);
422
+ }
158
423
  };
159
424
  }, []);
160
425
  const renderMenuItems = (menuItems, depth = 0) => {
@@ -168,20 +433,47 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
168
433
  e.stopPropagation();
169
434
  handleItemClick(item);
170
435
  };
171
- const handleRightIconClick = (e) => {
436
+ const handleRightIconPropsClick = (e) => {
437
+ e.preventDefault();
172
438
  e.stopPropagation();
173
- // if (item.disabled) return;
174
- item.onRightIconClick?.();
439
+ e.nativeEvent.stopImmediatePropagation();
440
+ item.rightIconProps?.onClick?.();
441
+ if (item.id) {
442
+ setToggledItemIds(prev => {
443
+ const next = new Set(prev);
444
+ if (next.has(item.id))
445
+ next.delete(item.id);
446
+ else
447
+ next.add(item.id);
448
+ return next;
449
+ });
450
+ }
175
451
  };
176
- return (_jsxs(S.MenuItem, { "$disabled": item.disabled, "$hasSubmenu": !!item.submenu && item.submenu.length > 0, "$beginGroup": item.beginGroup, onMouseDown: handleClick, onMouseEnter: (e) => !isMobile && handleMouseEnter(item, e, depth + 1), onMouseLeave: () => !isMobile && handleMouseLeave(depth + 1), children: [_jsxs(S.MenuItemContent, { children: [item.icon && _jsx(S.IconWrapper, { children: item.icon }), _jsx(S.MenuItemName, { children: item.name })] }), item.rightIcon && item.onRightIconClick && (_jsx(S.RightIconButton, { onClick: handleRightIconClick, onMouseDown: (e) => e.stopPropagation(), "aria-label": `Action for ${item.name}`, children: item.rightIcon })), item.submenu && item.submenu.length > 0 && (_jsx(S.SubmenuIndicator, { "$isMobile": isMobile, children: isMobile ? '›' : '▸' }))] }, itemKey));
452
+ // rightIconProps takes precedence over rightIcon
453
+ let rightIconElement = null;
454
+ let rightIconClickHandler = null;
455
+ if (item.rightIconProps) {
456
+ const rip = item.rightIconProps;
457
+ const wasToggled = item.id ? toggledItemIds.has(item.id) : false;
458
+ const isActive = wasToggled ? !rip.isActive : rip.isActive;
459
+ const color = isActive ? (rip.activeColor || '#d32f2f') : (rip.inactiveColor || '#626262');
460
+ rightIconElement = _jsx("span", { style: { color, display: 'flex', alignItems: 'center' }, children: rip.icon });
461
+ if (rip.onClick) {
462
+ rightIconClickHandler = handleRightIconPropsClick;
463
+ }
464
+ }
465
+ return (_jsxs(S.MenuItem, { "$disabled": item.disabled, "$hasSubmenu": !!item.submenu && item.submenu.length > 0, "$beginGroup": item.beginGroup, "data-disabled": item.disabled ? "true" : undefined, onMouseDown: handleClick, onMouseEnter: (e) => !isMobile && handleMouseEnter(item, e, depth + 1), onMouseLeave: () => !isMobile && handleMouseLeave(depth + 1), title: item.tooltip, children: [_jsxs(S.MenuItemContent, { children: [item.icon && _jsx(S.IconWrapper, { children: item.icon }), _jsx(S.MenuItemName, { children: item.name })] }), rightIconElement && rightIconClickHandler ? (_jsx(S.RightIconButton, { onClick: rightIconClickHandler, onMouseDown: (e) => e.stopPropagation(), "aria-label": `Action for ${item.name}`, children: rightIconElement })) : rightIconElement ? (_jsx(S.IconWrapper, { children: rightIconElement })) : null, item.submenu && item.submenu.length > 0 && (_jsx(S.SubmenuIndicator, { "$isMobile": isMobile, children: isMobile ? '›' : '▸' }))] }, itemKey));
177
466
  });
178
467
  };
179
- const currentMenu = menuState.submenuStack.at(-1) || items;
180
468
  const currentParentName = menuState.parentNames.at(-1) || '';
181
- return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onKeyDown: (e) => {
469
+ return (_jsxs(_Fragment, { children: [!externalControl && children && (_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onTouchStart: handleTouchStart, onTouchMove: handleTouchMove, onTouchEnd: handleTouchEnd, onTouchCancel: handleTouchEnd, onKeyDown: (e) => {
182
470
  if (e.key === 'Enter' || e.key === ' ') {
183
471
  handleClick(e);
184
472
  }
185
- }, role: "button", tabIndex: 0, style: { display: 'inline-block' }, children: children }), menuState.visible && (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { onClick: handleClose }), _jsxs(S.MenuContainer, { ref: menuRef, "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, children: [isMobile && menuState.parentNames.length > 0 && (_jsxs(S.MobileMenuHeader, { children: [_jsx(S.BackButton, { onClick: handleBack, "aria-label": "Go back", children: "\u2190" }), _jsx(S.HeaderTitle, { children: currentParentName })] })), renderMenuItems(currentMenu, 0)] }), !isMobile && hoveredSubmenus.map((submenu, idx) => (_jsx(S.Submenu, { "$parentRect": submenu.parentRect, "$openUp": submenu.openUp, "data-submenu": "true", onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: () => handleMouseLeave(submenu.depth), children: renderMenuItems(submenu.items, submenu.depth) }, `submenu-${submenu.depth}-${idx}`)))] }))] }));
473
+ }, role: "button", tabIndex: 0, style: {
474
+ // display: 'inline-block',
475
+ WebkitTouchCallout: isIOS ? 'none' : undefined,
476
+ WebkitUserSelect: isIOS ? 'none' : undefined,
477
+ }, children: children })), menuState.visible && createPortal(_jsxs(_Fragment, { children: [_jsxs(S.MenuContainer, { ref: menuRef, "$x": menuState.position.x, "$y": menuState.position.y, "$openLeft": openLeft, "$openUp": openUp, "$isPositioned": isCalculated, "$externalControl": !!externalControl, "$needsScroll": needsScroll, "$maxHeight": maxHeight, children: [isMobile && menuState.parentNames.length > 0 && (_jsxs(S.MobileMenuHeader, { children: [_jsx(S.BackButton, { onClick: handleBack, "aria-label": "Go back", children: _jsx(IconArrowLeft, {}) }), _jsx(S.HeaderTitle, { children: currentParentName })] })), renderMenuItems(currentMenu, 0)] }), !isMobile && hoveredSubmenus.map((submenu, idx) => (_jsx(S.Submenu, { "$parentRect": submenu.parentRect, "$openUp": submenu.openUp, "data-submenu": "true", onMouseEnter: handleSubmenuMouseEnter, onMouseLeave: () => handleMouseLeave(submenu.depth), children: renderMenuItems(submenu.items, submenu.depth) }, `submenu-${submenu.depth}-${idx}`)))] }), document.body)] }));
186
478
  };
187
479
  export default TMContextMenu;
@@ -1,11 +1,18 @@
1
+ export declare const useIsIOS: () => boolean;
1
2
  export declare const useIsMobile: () => boolean;
2
3
  export declare const useClickOutside: (callback: () => void) => import("react").RefObject<HTMLDivElement>;
3
4
  interface Position {
4
5
  x: number;
5
6
  y: number;
6
7
  }
7
- export declare const useMenuPosition: (menuRef: React.RefObject<HTMLDivElement | null>, position: Position) => {
8
+ export declare const useMenuPosition: (menuRef: React.RefObject<HTMLDivElement | null>, position: Position, itemsCount?: number) => {
9
+ isCalculated: boolean;
8
10
  openLeft: boolean;
9
11
  openUp: boolean;
12
+ needsScroll: boolean;
13
+ maxHeight: number | undefined;
10
14
  };
15
+ export declare const getContextMenuTarget: <T extends {
16
+ id: string;
17
+ }>(event: React.MouseEvent | undefined, focusedItem: T | undefined, selectedItem: T | undefined, dataSource: T[], idPrefix: string, isMobile: boolean) => T | undefined;
11
18
  export {};