@topconsultnpm/sdkui-react 6.20.0-dev1.7 → 6.20.0-dev1.71
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.
- package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +258 -17
- package/lib/components/NewComponents/ContextMenu/hooks.d.ts +5 -0
- package/lib/components/NewComponents/ContextMenu/hooks.js +38 -4
- package/lib/components/NewComponents/ContextMenu/index.d.ts +3 -0
- package/lib/components/NewComponents/ContextMenu/index.js +2 -0
- package/lib/components/NewComponents/ContextMenu/styles.d.ts +5 -1
- package/lib/components/NewComponents/ContextMenu/styles.js +59 -31
- package/lib/components/NewComponents/ContextMenu/types.d.ts +13 -0
- package/lib/components/NewComponents/ContextMenu/useLongPress.d.ts +21 -0
- package/lib/components/NewComponents/ContextMenu/useLongPress.js +112 -0
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +517 -100
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +19 -5
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +206 -54
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +1 -2
- package/lib/components/base/TMAccordionNew.js +35 -14
- package/lib/components/base/TMCustomButton.js +61 -17
- package/lib/components/base/TMDataGrid.d.ts +7 -4
- package/lib/components/base/TMDataGrid.js +142 -11
- package/lib/components/base/TMDropDownMenu.js +19 -18
- package/lib/components/base/TMPanel.js +1 -1
- package/lib/components/choosers/TMInvoiceRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMMetadataChooser.js +8 -1
- package/lib/components/choosers/TMOrderRetrieveFormats.js +1 -1
- package/lib/components/choosers/TMUserChooser.d.ts +0 -5
- package/lib/components/choosers/TMUserChooser.js +25 -45
- package/lib/components/editors/TMMetadataValues.js +23 -5
- package/lib/components/editors/TMTextBox.js +6 -3
- package/lib/components/features/documents/TMDcmtForm.d.ts +13 -1
- package/lib/components/features/documents/TMDcmtForm.js +386 -194
- package/lib/components/features/documents/TMDcmtPreview.js +41 -105
- package/lib/components/features/documents/TMMasterDetailDcmts.js +37 -52
- package/lib/components/features/documents/TMRelationViewer.d.ts +1 -1
- package/lib/components/features/documents/TMRelationViewer.js +2 -2
- package/lib/components/features/search/TMDcmtCheckoutInfoForm.d.ts +8 -0
- package/lib/components/features/search/{TMSearchResultCheckoutInfoForm.js → TMDcmtCheckoutInfoForm.js} +5 -10
- package/lib/components/features/search/TMSavedQuerySelector.js +72 -67
- package/lib/components/features/search/TMSearch.js +41 -9
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -0
- package/lib/components/features/search/TMSearchQueryPanel.js +19 -18
- package/lib/components/features/search/TMSearchResult.js +118 -242
- package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +3 -3
- package/lib/components/features/search/TMSearchResultsMenuItems.js +205 -169
- package/lib/components/features/search/TMSignSettingsForm.js +1 -1
- package/lib/components/features/search/TMSignatureInfoContent.d.ts +6 -0
- package/lib/components/features/search/TMSignatureInfoContent.js +140 -0
- package/lib/components/features/search/TMViewHistoryDcmt.js +1 -1
- package/lib/components/features/tasks/TMTaskForm.js +20 -1
- package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -2
- package/lib/components/features/tasks/TMTasksUtils.js +62 -52
- package/lib/components/features/tasks/TMTasksView.js +6 -6
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +33 -2
- package/lib/components/features/workflow/TMWorkflowPopup.js +134 -24
- package/lib/components/features/workflow/diagram/DiagramItemComponent.d.ts +1 -0
- package/lib/components/features/workflow/diagram/DiagramItemComponent.js +2 -3
- package/lib/components/features/workflow/diagram/RecipientList.js +3 -2
- package/lib/components/features/workflow/diagram/WFDiagram.d.ts +2 -0
- package/lib/components/features/workflow/diagram/WFDiagram.js +21 -4
- package/lib/components/forms/Login/LoginValidatorService.d.ts +2 -0
- package/lib/components/forms/Login/LoginValidatorService.js +7 -2
- package/lib/components/forms/Login/TMLoginForm.js +34 -6
- package/lib/components/forms/TMChooserForm.js +1 -1
- package/lib/components/grids/TMBlogsPost.js +55 -30
- package/lib/components/grids/TMRecentsManager.js +20 -10
- package/lib/components/index.d.ts +4 -0
- package/lib/components/index.js +4 -0
- package/lib/components/settings/SettingsAppearance.js +92 -29
- package/lib/components/viewers/TMDataListItemViewer.d.ts +2 -1
- package/lib/components/viewers/TMDataListItemViewer.js +35 -71
- package/lib/components/viewers/TMDataUserIdItemViewer.d.ts +8 -0
- package/lib/components/viewers/TMDataUserIdItemViewer.js +39 -0
- package/lib/css/tm-sdkui.css +1 -1
- package/lib/helper/SDKUI_Globals.d.ts +19 -0
- package/lib/helper/SDKUI_Globals.js +11 -0
- package/lib/helper/SDKUI_Localizator.d.ts +15 -1
- package/lib/helper/SDKUI_Localizator.js +147 -1
- package/lib/helper/TMIcons.d.ts +2 -0
- package/lib/helper/TMIcons.js +6 -0
- package/lib/helper/TMPdfViewer.d.ts +8 -0
- package/lib/helper/TMPdfViewer.js +373 -0
- package/lib/helper/checkinCheckoutManager.d.ts +32 -2
- package/lib/helper/checkinCheckoutManager.js +115 -38
- package/lib/helper/devextremeCustomMessages.d.ts +30 -0
- package/lib/helper/devextremeCustomMessages.js +30 -0
- package/lib/helper/helpers.d.ts +2 -1
- package/lib/helper/helpers.js +14 -3
- package/lib/helper/index.d.ts +1 -0
- package/lib/helper/index.js +1 -0
- package/lib/helper/queryHelper.d.ts +1 -1
- package/lib/helper/queryHelper.js +33 -3
- package/lib/hooks/useCheckInOutOperations.d.ts +28 -0
- package/lib/hooks/useCheckInOutOperations.js +223 -0
- package/lib/hooks/useDataListItem.d.ts +12 -0
- package/lib/hooks/useDataListItem.js +131 -0
- package/lib/hooks/useDataUserIdItem.d.ts +10 -0
- package/lib/hooks/useDataUserIdItem.js +96 -0
- package/lib/hooks/useSettingsFeedback.d.ts +11 -0
- package/lib/hooks/useSettingsFeedback.js +38 -0
- package/lib/hooks/useWorkflowApprove.d.ts +4 -0
- package/lib/hooks/useWorkflowApprove.js +14 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/ts/types.d.ts +58 -1
- package/lib/utils/theme.d.ts +1 -1
- package/lib/utils/theme.js +1 -1
- package/package.json +5 -2
- package/lib/components/NewComponents/Notification/Notification.d.ts +0 -4
- package/lib/components/NewComponents/Notification/Notification.js +0 -60
- package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +0 -8
- package/lib/components/NewComponents/Notification/NotificationContainer.js +0 -33
- package/lib/components/NewComponents/Notification/index.d.ts +0 -2
- package/lib/components/NewComponents/Notification/index.js +0 -2
- package/lib/components/NewComponents/Notification/styles.d.ts +0 -21
- package/lib/components/NewComponents/Notification/styles.js +0 -180
- package/lib/components/NewComponents/Notification/types.d.ts +0 -18
- package/lib/components/NewComponents/Notification/types.js +0 -1
- package/lib/components/features/search/TMSearchResultCheckoutInfoForm.d.ts +0 -8
|
@@ -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
|
-
|
|
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 },
|
|
@@ -11,23 +13,208 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
11
13
|
});
|
|
12
14
|
const [hoveredSubmenus, setHoveredSubmenus] = useState([]);
|
|
13
15
|
const isMobile = useIsMobile();
|
|
16
|
+
const isIOS = useIsIOS();
|
|
14
17
|
const menuRef = useRef(null);
|
|
15
18
|
const triggerRef = useRef(null);
|
|
16
19
|
const submenuTimeoutRef = useRef(null);
|
|
17
|
-
const
|
|
20
|
+
const longPressTimeoutRef = useRef(null);
|
|
21
|
+
const touchStartPos = useRef(null);
|
|
22
|
+
const { openLeft, openUp, isCalculated } = useMenuPosition(menuRef, menuState.position);
|
|
18
23
|
const handleClose = () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
24
|
+
if (externalControl) {
|
|
25
|
+
externalControl.onClose();
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
setMenuState(prev => ({
|
|
29
|
+
...prev,
|
|
30
|
+
visible: false,
|
|
31
|
+
submenuStack: [items],
|
|
32
|
+
parentNames: [],
|
|
33
|
+
}));
|
|
34
|
+
}
|
|
25
35
|
setHoveredSubmenus([]);
|
|
26
36
|
};
|
|
37
|
+
// Sync with external control when provided
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (externalControl) {
|
|
40
|
+
setMenuState(prev => ({
|
|
41
|
+
...prev,
|
|
42
|
+
visible: externalControl.visible,
|
|
43
|
+
position: externalControl.position,
|
|
44
|
+
submenuStack: [items],
|
|
45
|
+
parentNames: [],
|
|
46
|
+
}));
|
|
47
|
+
}
|
|
48
|
+
}, [externalControl, items]);
|
|
49
|
+
// iOS long-press support: attach touch listeners to target elements
|
|
50
|
+
// On long-press, dispatch synthetic contextmenu event to trigger existing handlers
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!target || !isIOS)
|
|
53
|
+
return;
|
|
54
|
+
const elements = document.querySelectorAll(target);
|
|
55
|
+
if (elements.length === 0)
|
|
56
|
+
return;
|
|
57
|
+
const touchStateMap = new WeakMap();
|
|
58
|
+
const handleTouchStart = (e) => {
|
|
59
|
+
const touchEvent = e;
|
|
60
|
+
const element = e.currentTarget;
|
|
61
|
+
const touch = touchEvent.touches[0];
|
|
62
|
+
// Prevent text selection during long press
|
|
63
|
+
e.preventDefault();
|
|
64
|
+
let state = touchStateMap.get(element);
|
|
65
|
+
if (!state) {
|
|
66
|
+
state = { timeout: null, startX: 0, startY: 0, longPressTriggered: false };
|
|
67
|
+
touchStateMap.set(element, state);
|
|
68
|
+
}
|
|
69
|
+
state.startX = touch.clientX;
|
|
70
|
+
state.startY = touch.clientY;
|
|
71
|
+
state.longPressTriggered = false;
|
|
72
|
+
if (state.timeout)
|
|
73
|
+
clearTimeout(state.timeout);
|
|
74
|
+
state.timeout = setTimeout(() => {
|
|
75
|
+
if (state)
|
|
76
|
+
state.longPressTriggered = true;
|
|
77
|
+
// Haptic feedback
|
|
78
|
+
if ('vibrate' in navigator)
|
|
79
|
+
navigator.vibrate(50);
|
|
80
|
+
const syntheticEvent = new MouseEvent('contextmenu', {
|
|
81
|
+
bubbles: true,
|
|
82
|
+
cancelable: true,
|
|
83
|
+
clientX: touch.clientX,
|
|
84
|
+
clientY: touch.clientY,
|
|
85
|
+
});
|
|
86
|
+
element.dispatchEvent(syntheticEvent);
|
|
87
|
+
if (state)
|
|
88
|
+
state.timeout = null;
|
|
89
|
+
}, 500);
|
|
90
|
+
};
|
|
91
|
+
const handleTouchMove = (e) => {
|
|
92
|
+
const touchEvent = e;
|
|
93
|
+
const element = e.currentTarget;
|
|
94
|
+
const state = touchStateMap.get(element);
|
|
95
|
+
if (!state?.timeout)
|
|
96
|
+
return;
|
|
97
|
+
const touch = touchEvent.touches[0];
|
|
98
|
+
const dx = Math.abs(touch.clientX - state.startX);
|
|
99
|
+
const dy = Math.abs(touch.clientY - state.startY);
|
|
100
|
+
if (dx > 10 || dy > 10) {
|
|
101
|
+
clearTimeout(state.timeout);
|
|
102
|
+
state.timeout = null;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const handleTouchEnd = (e) => {
|
|
106
|
+
const element = e.currentTarget;
|
|
107
|
+
const state = touchStateMap.get(element);
|
|
108
|
+
if (state?.timeout) {
|
|
109
|
+
clearTimeout(state.timeout);
|
|
110
|
+
state.timeout = null;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
// Prevent click event after long-press was triggered
|
|
114
|
+
const handleClick = (e) => {
|
|
115
|
+
const element = e.currentTarget;
|
|
116
|
+
const state = touchStateMap.get(element);
|
|
117
|
+
if (state?.longPressTriggered) {
|
|
118
|
+
e.preventDefault();
|
|
119
|
+
e.stopPropagation();
|
|
120
|
+
e.stopImmediatePropagation();
|
|
121
|
+
state.longPressTriggered = false;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
// Prevent default iOS context menu
|
|
125
|
+
const handleContextMenu = (e) => {
|
|
126
|
+
e.preventDefault();
|
|
127
|
+
e.stopPropagation();
|
|
128
|
+
return false;
|
|
129
|
+
};
|
|
130
|
+
// Attach listeners to all matching elements
|
|
131
|
+
elements.forEach(element => {
|
|
132
|
+
const el = element;
|
|
133
|
+
// Prevent iOS native callout and text selection
|
|
134
|
+
const style = el.style;
|
|
135
|
+
style.webkitTouchCallout = 'none';
|
|
136
|
+
style.webkitUserSelect = 'none';
|
|
137
|
+
style.userSelect = 'none';
|
|
138
|
+
el.addEventListener('touchstart', handleTouchStart, { passive: true });
|
|
139
|
+
el.addEventListener('touchmove', handleTouchMove, { passive: true });
|
|
140
|
+
el.addEventListener('touchend', handleTouchEnd);
|
|
141
|
+
el.addEventListener('touchcancel', handleTouchEnd);
|
|
142
|
+
el.addEventListener('contextmenu', handleContextMenu);
|
|
143
|
+
el.addEventListener('click', handleClick, { capture: true });
|
|
144
|
+
});
|
|
145
|
+
return () => {
|
|
146
|
+
elements.forEach(element => {
|
|
147
|
+
const el = element;
|
|
148
|
+
const style = el.style;
|
|
149
|
+
style.webkitTouchCallout = '';
|
|
150
|
+
style.webkitUserSelect = '';
|
|
151
|
+
style.userSelect = '';
|
|
152
|
+
el.removeEventListener('touchstart', handleTouchStart);
|
|
153
|
+
el.removeEventListener('touchmove', handleTouchMove);
|
|
154
|
+
el.removeEventListener('touchend', handleTouchEnd);
|
|
155
|
+
el.removeEventListener('touchcancel', handleTouchEnd);
|
|
156
|
+
el.removeEventListener('contextmenu', handleContextMenu);
|
|
157
|
+
el.removeEventListener('click', handleClick, { capture: true });
|
|
158
|
+
});
|
|
159
|
+
};
|
|
160
|
+
}, [target, isIOS]);
|
|
161
|
+
// Update items when they change while menu is visible (for keepOpenOnClick behavior)
|
|
162
|
+
useEffect(() => {
|
|
163
|
+
if (!keepOpenOnClick)
|
|
164
|
+
return;
|
|
165
|
+
if (!externalControl && menuState.visible) {
|
|
166
|
+
setMenuState(prev => ({
|
|
167
|
+
...prev,
|
|
168
|
+
submenuStack: [items],
|
|
169
|
+
}));
|
|
170
|
+
// Update hoveredSubmenus with fresh items while keeping them open
|
|
171
|
+
setHoveredSubmenus(prev => {
|
|
172
|
+
if (prev.length === 0)
|
|
173
|
+
return prev;
|
|
174
|
+
// Rebuild hoveredSubmenus with updated items from the new items structure
|
|
175
|
+
return prev.map(submenu => {
|
|
176
|
+
// Find the matching submenu in the new items structure
|
|
177
|
+
const findSubmenuInItems = (searchItems) => {
|
|
178
|
+
for (const item of searchItems) {
|
|
179
|
+
if (item.submenu && item.submenu.length > 0) {
|
|
180
|
+
// Check if this submenu matches (compare first item name as identifier)
|
|
181
|
+
if (submenu.items.length > 0 && item.submenu.length > 0 &&
|
|
182
|
+
item.submenu[0].name === submenu.items[0].name) {
|
|
183
|
+
return item.submenu;
|
|
184
|
+
}
|
|
185
|
+
// Recursively search in nested submenus
|
|
186
|
+
const found = findSubmenuInItems(item.submenu);
|
|
187
|
+
if (found)
|
|
188
|
+
return found;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
};
|
|
193
|
+
const updatedItems = findSubmenuInItems(items);
|
|
194
|
+
return {
|
|
195
|
+
...submenu,
|
|
196
|
+
items: updatedItems || submenu.items, // Use updated items if found, otherwise keep old
|
|
197
|
+
};
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}, [items, menuState.visible, externalControl, keepOpenOnClick]);
|
|
202
|
+
// Track when the menu was opened to prevent immediate close on iOS
|
|
203
|
+
const menuOpenedAtRef = useRef(0);
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (menuState.visible) {
|
|
206
|
+
menuOpenedAtRef.current = Date.now();
|
|
207
|
+
}
|
|
208
|
+
}, [menuState.visible]);
|
|
27
209
|
useEffect(() => {
|
|
28
210
|
if (!menuState.visible)
|
|
29
211
|
return;
|
|
30
212
|
const handleClickOutside = (event) => {
|
|
213
|
+
// On iOS, prevent closing immediately after opening (within 300ms)
|
|
214
|
+
// This handles the case where touchend from long-press triggers touchstart listener
|
|
215
|
+
if (Date.now() - menuOpenedAtRef.current < 300) {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
31
218
|
const target = event.target;
|
|
32
219
|
// Check if click is inside main menu
|
|
33
220
|
if (menuRef.current?.contains(target)) {
|
|
@@ -72,27 +259,73 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
72
259
|
});
|
|
73
260
|
}
|
|
74
261
|
};
|
|
262
|
+
// iOS-specific touch handlers for long press
|
|
263
|
+
const handleTouchStart = (e) => {
|
|
264
|
+
if (!isIOS || trigger !== 'right')
|
|
265
|
+
return;
|
|
266
|
+
const touch = e.touches[0];
|
|
267
|
+
touchStartPos.current = { x: touch.clientX, y: touch.clientY };
|
|
268
|
+
if (longPressTimeoutRef.current) {
|
|
269
|
+
clearTimeout(longPressTimeoutRef.current);
|
|
270
|
+
}
|
|
271
|
+
longPressTimeoutRef.current = setTimeout(() => {
|
|
272
|
+
if (touchStartPos.current) {
|
|
273
|
+
if ('vibrate' in navigator) {
|
|
274
|
+
navigator.vibrate(50);
|
|
275
|
+
}
|
|
276
|
+
setMenuState({
|
|
277
|
+
visible: true,
|
|
278
|
+
position: { x: touchStartPos.current.x, y: touchStartPos.current.y },
|
|
279
|
+
submenuStack: [items],
|
|
280
|
+
parentNames: [],
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
}, 500);
|
|
284
|
+
};
|
|
285
|
+
const handleTouchMove = (e) => {
|
|
286
|
+
if (!isIOS || trigger !== 'right' || !touchStartPos.current)
|
|
287
|
+
return;
|
|
288
|
+
const touch = e.touches[0];
|
|
289
|
+
const moveThreshold = 10; // pixels
|
|
290
|
+
// If finger moved too much, cancel long press
|
|
291
|
+
const deltaX = Math.abs(touch.clientX - touchStartPos.current.x);
|
|
292
|
+
const deltaY = Math.abs(touch.clientY - touchStartPos.current.y);
|
|
293
|
+
if (deltaX > moveThreshold || deltaY > moveThreshold) {
|
|
294
|
+
if (longPressTimeoutRef.current) {
|
|
295
|
+
clearTimeout(longPressTimeoutRef.current);
|
|
296
|
+
longPressTimeoutRef.current = null;
|
|
297
|
+
}
|
|
298
|
+
touchStartPos.current = null;
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
const handleTouchEnd = () => {
|
|
302
|
+
if (!isIOS || trigger !== 'right')
|
|
303
|
+
return;
|
|
304
|
+
if (longPressTimeoutRef.current) {
|
|
305
|
+
clearTimeout(longPressTimeoutRef.current);
|
|
306
|
+
longPressTimeoutRef.current = null;
|
|
307
|
+
}
|
|
308
|
+
touchStartPos.current = null;
|
|
309
|
+
};
|
|
75
310
|
const handleItemClick = (item) => {
|
|
76
311
|
if (item.disabled)
|
|
77
312
|
return;
|
|
78
|
-
// Always execute onClick if present
|
|
79
313
|
if (item.onClick) {
|
|
80
314
|
item.onClick();
|
|
81
315
|
}
|
|
82
316
|
if (item.submenu && item.submenu.length > 0) {
|
|
83
317
|
if (isMobile) {
|
|
84
|
-
// Mobile: Push submenu to stack
|
|
85
318
|
setMenuState(prev => ({
|
|
86
319
|
...prev,
|
|
87
320
|
submenuStack: [...prev.submenuStack, item.submenu],
|
|
88
321
|
parentNames: [...prev.parentNames, item.name],
|
|
89
322
|
}));
|
|
90
323
|
}
|
|
91
|
-
// Desktop: Submenus are handled by hover, don't close menu
|
|
92
324
|
}
|
|
93
325
|
else {
|
|
94
|
-
|
|
95
|
-
|
|
326
|
+
if (item.onClick || !keepOpenOnClick) {
|
|
327
|
+
handleClose();
|
|
328
|
+
}
|
|
96
329
|
}
|
|
97
330
|
};
|
|
98
331
|
const handleBack = () => {
|
|
@@ -155,6 +388,9 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
155
388
|
if (submenuTimeoutRef.current) {
|
|
156
389
|
clearTimeout(submenuTimeoutRef.current);
|
|
157
390
|
}
|
|
391
|
+
if (longPressTimeoutRef.current) {
|
|
392
|
+
clearTimeout(longPressTimeoutRef.current);
|
|
393
|
+
}
|
|
158
394
|
};
|
|
159
395
|
}, []);
|
|
160
396
|
const renderMenuItems = (menuItems, depth = 0) => {
|
|
@@ -172,16 +408,21 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
172
408
|
e.stopPropagation();
|
|
173
409
|
// if (item.disabled) return;
|
|
174
410
|
item.onRightIconClick?.();
|
|
411
|
+
handleClose();
|
|
175
412
|
};
|
|
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));
|
|
413
|
+
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), title: item.tooltip, 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));
|
|
177
414
|
});
|
|
178
415
|
};
|
|
179
416
|
const currentMenu = menuState.submenuStack.at(-1) || items;
|
|
180
417
|
const currentParentName = menuState.parentNames.at(-1) || '';
|
|
181
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onKeyDown: (e) => {
|
|
418
|
+
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
419
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
183
420
|
handleClick(e);
|
|
184
421
|
}
|
|
185
|
-
}, role: "button", tabIndex: 0, style: {
|
|
422
|
+
}, role: "button", tabIndex: 0, style: {
|
|
423
|
+
display: 'inline-block',
|
|
424
|
+
WebkitTouchCallout: isIOS ? 'none' : undefined,
|
|
425
|
+
WebkitUserSelect: isIOS ? 'none' : undefined,
|
|
426
|
+
}, 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, 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
427
|
};
|
|
187
428
|
export default TMContextMenu;
|
|
@@ -1,3 +1,4 @@
|
|
|
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 {
|
|
@@ -5,7 +6,11 @@ interface Position {
|
|
|
5
6
|
y: number;
|
|
6
7
|
}
|
|
7
8
|
export declare const useMenuPosition: (menuRef: React.RefObject<HTMLDivElement | null>, position: Position) => {
|
|
9
|
+
isCalculated: boolean;
|
|
8
10
|
openLeft: boolean;
|
|
9
11
|
openUp: boolean;
|
|
10
12
|
};
|
|
13
|
+
export declare const getContextMenuTarget: <T extends {
|
|
14
|
+
id: string;
|
|
15
|
+
}>(event: React.MouseEvent | undefined, focusedItem: T | undefined, selectedItem: T | undefined, dataSource: T[], idPrefix: string, isMobile: boolean) => T | undefined;
|
|
11
16
|
export {};
|
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import { useState, useEffect, useRef } from 'react';
|
|
1
|
+
import { useState, useEffect, useLayoutEffect, useRef } from 'react';
|
|
2
|
+
export const useIsIOS = () => {
|
|
3
|
+
const [isIOS, setIsIOS] = useState(false);
|
|
4
|
+
useEffect(() => {
|
|
5
|
+
const iOS = /iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
|
6
|
+
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);
|
|
7
|
+
setIsIOS(iOS);
|
|
8
|
+
}, []);
|
|
9
|
+
return isIOS;
|
|
10
|
+
};
|
|
2
11
|
export const useIsMobile = () => {
|
|
3
12
|
const [isMobile, setIsMobile] = useState(false);
|
|
4
13
|
useEffect(() => {
|
|
@@ -31,9 +40,12 @@ export const useClickOutside = (callback) => {
|
|
|
31
40
|
};
|
|
32
41
|
export const useMenuPosition = (menuRef, position) => {
|
|
33
42
|
const [adjustedPosition, setAdjustedPosition] = useState({ openLeft: false, openUp: false });
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
const [isCalculated, setIsCalculated] = useState(false);
|
|
44
|
+
useLayoutEffect(() => {
|
|
45
|
+
if (!menuRef.current) {
|
|
46
|
+
setIsCalculated(false);
|
|
36
47
|
return;
|
|
48
|
+
}
|
|
37
49
|
const menuRect = menuRef.current.getBoundingClientRect();
|
|
38
50
|
const viewportWidth = window.innerWidth;
|
|
39
51
|
const viewportHeight = window.innerHeight;
|
|
@@ -43,6 +55,28 @@ export const useMenuPosition = (menuRef, position) => {
|
|
|
43
55
|
openLeft: spaceRight < menuRect.width + 20,
|
|
44
56
|
openUp: spaceBottom < menuRect.height + 20,
|
|
45
57
|
});
|
|
58
|
+
setIsCalculated(true);
|
|
46
59
|
}, [position, menuRef]);
|
|
47
|
-
return adjustedPosition;
|
|
60
|
+
return { ...adjustedPosition, isCalculated };
|
|
61
|
+
};
|
|
62
|
+
export const getContextMenuTarget = (event, focusedItem, selectedItem, dataSource, idPrefix, isMobile) => {
|
|
63
|
+
if (!event)
|
|
64
|
+
return undefined;
|
|
65
|
+
let targetItem = focusedItem ?? selectedItem;
|
|
66
|
+
if (!focusedItem && isMobile) {
|
|
67
|
+
// Find the actual item that was long-pressed by traversing up from the event target
|
|
68
|
+
let element = event.target;
|
|
69
|
+
while (element && element !== event.currentTarget) {
|
|
70
|
+
if (element.id && element.id.startsWith(`${idPrefix}-`)) {
|
|
71
|
+
const itemId = element.id.replace(`${idPrefix}-`, '');
|
|
72
|
+
const foundItem = dataSource.find(item => item.id === itemId);
|
|
73
|
+
if (foundItem) {
|
|
74
|
+
targetItem = foundItem;
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
element = element.parentElement;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return targetItem;
|
|
48
82
|
};
|
|
@@ -1,2 +1,5 @@
|
|
|
1
1
|
export { default as ContextMenu } from './TMContextMenu';
|
|
2
2
|
export type { TMContextMenuItemProps, TMContextMenuProps } from './types';
|
|
3
|
+
export { useLongPress, triggerContextMenuEvent } from './useLongPress';
|
|
4
|
+
export type { UseLongPressOptions } from './useLongPress';
|
|
5
|
+
export { useIsIOS } from './hooks';
|
|
@@ -3,6 +3,8 @@ export declare const MenuContainer: import("styled-components/dist/types").IStyl
|
|
|
3
3
|
$y: number;
|
|
4
4
|
$openLeft: boolean;
|
|
5
5
|
$openUp: boolean;
|
|
6
|
+
$isPositioned: boolean;
|
|
7
|
+
$externalControl?: boolean;
|
|
6
8
|
}>> & string;
|
|
7
9
|
export declare const MenuItem: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, {
|
|
8
10
|
$disabled?: boolean;
|
|
@@ -12,7 +14,9 @@ export declare const MenuItem: import("styled-components/dist/types").IStyledCom
|
|
|
12
14
|
export declare const MenuItemContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>> & string;
|
|
13
15
|
export declare const IconWrapper: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>> & string;
|
|
14
16
|
export declare const MenuItemName: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, never>> & string;
|
|
15
|
-
export declare const RightIconButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>,
|
|
17
|
+
export declare const RightIconButton: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, Omit<import("react").DetailedHTMLProps<import("react").ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>, "ref"> & {
|
|
18
|
+
ref?: ((instance: HTMLButtonElement | null) => void | import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES[keyof import("react").DO_NOT_USE_OR_YOU_WILL_BE_FIRED_CALLBACK_REF_RETURN_VALUES]) | import("react").RefObject<HTMLButtonElement> | null | undefined;
|
|
19
|
+
}>, never>, never>> & string;
|
|
16
20
|
export declare const SubmenuIndicator: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components/dist/types").Substitute<import("react").DetailedHTMLProps<import("react").HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>, {
|
|
17
21
|
$isMobile?: boolean;
|
|
18
22
|
}>> & string;
|
|
@@ -25,7 +25,7 @@ export const MenuContainer = styled.div `
|
|
|
25
25
|
right: ${props => props.$openLeft ? `${window.innerWidth - props.$x}px` : 'auto'};
|
|
26
26
|
top: ${props => props.$openUp ? 'auto' : `${props.$y}px`};
|
|
27
27
|
bottom: ${props => props.$openUp ? `${window.innerHeight - props.$y}px` : 'auto'};
|
|
28
|
-
z-index:
|
|
28
|
+
z-index: 10100;
|
|
29
29
|
background: #ffffff;
|
|
30
30
|
border-radius: 12px;
|
|
31
31
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12),
|
|
@@ -36,6 +36,13 @@ export const MenuContainer = styled.div `
|
|
|
36
36
|
animation: ${fadeIn} 0.15s ease-out;
|
|
37
37
|
backdrop-filter: blur(10px);
|
|
38
38
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
39
|
+
opacity: ${props => props.$isPositioned ? 1 : 0};
|
|
40
|
+
transition: opacity 0.05s ease-in;
|
|
41
|
+
|
|
42
|
+
/* Reset color inheritance from parent with !important to override panel header styles */
|
|
43
|
+
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
44
|
+
color: #1a1a1a !important;
|
|
45
|
+
}
|
|
39
46
|
|
|
40
47
|
[data-theme='dark'] & {
|
|
41
48
|
background: #2a2a2a;
|
|
@@ -43,14 +50,27 @@ export const MenuContainer = styled.div `
|
|
|
43
50
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
|
|
44
51
|
0 2px 8px rgba(0, 0, 0, 0.3);
|
|
45
52
|
}
|
|
53
|
+
|
|
54
|
+
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
55
|
+
color: #e0e0e0 !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
${props => props.$externalControl && `
|
|
59
|
+
@media (max-width: 768px) {
|
|
60
|
+
left: 100px !important;
|
|
61
|
+
right: 100px !important;
|
|
62
|
+
max-width: calc(100vw - 200px);
|
|
63
|
+
width: auto;
|
|
64
|
+
min-width: auto;
|
|
65
|
+
}
|
|
66
|
+
`}
|
|
46
67
|
`;
|
|
47
68
|
export const MenuItem = styled.div `
|
|
48
69
|
display: flex;
|
|
49
70
|
align-items: center;
|
|
50
71
|
justify-content: space-between;
|
|
51
72
|
padding: 4px 12px;
|
|
52
|
-
cursor: ${props => props.$disabled ? '
|
|
53
|
-
opacity: ${props => props.$disabled ? 0.4 : 1};
|
|
73
|
+
cursor: ${props => props.$disabled ? 'default' : 'pointer'};
|
|
54
74
|
transition: all 0.15s ease;
|
|
55
75
|
position: relative;
|
|
56
76
|
user-select: none;
|
|
@@ -63,12 +83,26 @@ export const MenuItem = styled.div `
|
|
|
63
83
|
padding-top: 8px;
|
|
64
84
|
`}
|
|
65
85
|
|
|
86
|
+
/* Apply opacity only to direct children except right-icon-btn */
|
|
87
|
+
& > *:not(.right-icon-btn) {
|
|
88
|
+
opacity: ${props => props.$disabled ? 0.4 : 1};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* Right icon button hidden by default, shown on hover */
|
|
92
|
+
& .right-icon-btn {
|
|
93
|
+
cursor: pointer !important;
|
|
94
|
+
}
|
|
95
|
+
|
|
66
96
|
&:hover {
|
|
67
97
|
${props => !props.$disabled && `
|
|
68
98
|
background: linear-gradient(90deg, #f0f7ff 0%, #e6f2ff 100%);
|
|
69
99
|
color: #0066cc;
|
|
70
|
-
padding-left: 14px;
|
|
71
100
|
`}
|
|
101
|
+
|
|
102
|
+
/* Show right icon on hover */
|
|
103
|
+
& .right-icon-btn {
|
|
104
|
+
opacity: 1 !important;
|
|
105
|
+
}
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
&:active {
|
|
@@ -99,8 +133,8 @@ export const MenuItem = styled.div `
|
|
|
99
133
|
}
|
|
100
134
|
|
|
101
135
|
@media (max-width: 768px) {
|
|
102
|
-
padding:
|
|
103
|
-
font-size:
|
|
136
|
+
padding: 4px 10px;
|
|
137
|
+
font-size: 12px;
|
|
104
138
|
}
|
|
105
139
|
`;
|
|
106
140
|
export const MenuItemContent = styled.div `
|
|
@@ -124,23 +158,23 @@ export const MenuItemName = styled.span `
|
|
|
124
158
|
overflow-wrap: break-word;
|
|
125
159
|
line-height: 1.4;
|
|
126
160
|
`;
|
|
127
|
-
export const RightIconButton = styled.button
|
|
161
|
+
export const RightIconButton = styled.button.attrs({
|
|
162
|
+
className: 'right-icon-btn'
|
|
163
|
+
}) `
|
|
128
164
|
display: flex;
|
|
129
165
|
align-items: center;
|
|
130
166
|
justify-content: center;
|
|
131
167
|
background: transparent;
|
|
132
168
|
border: none;
|
|
133
|
-
cursor: pointer;
|
|
169
|
+
cursor: pointer !important;
|
|
134
170
|
padding: 4px 8px;
|
|
135
171
|
margin-left: 8px;
|
|
136
172
|
border-radius: 6px;
|
|
137
|
-
color: inherit;
|
|
138
173
|
font-size: 14px;
|
|
139
|
-
opacity: 0
|
|
140
|
-
transition:
|
|
174
|
+
opacity: 0 !important;
|
|
175
|
+
transition: opacity 0.15s ease, background 0.15s ease, transform 0.15s ease;
|
|
141
176
|
|
|
142
177
|
&:hover {
|
|
143
|
-
opacity: 1;
|
|
144
178
|
background: rgba(0, 0, 0, 0.05);
|
|
145
179
|
transform: scale(1.1);
|
|
146
180
|
}
|
|
@@ -189,7 +223,7 @@ export const Submenu = styled.div `
|
|
|
189
223
|
// If openUp is true, anchor to bottom and grow upward
|
|
190
224
|
return props.$openUp ? `${globalThis.innerHeight - props.$parentRect.bottom - 8}px` : 'auto';
|
|
191
225
|
}};
|
|
192
|
-
z-index:
|
|
226
|
+
z-index: 10101;
|
|
193
227
|
background: #ffffff;
|
|
194
228
|
border-radius: 12px;
|
|
195
229
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12),
|
|
@@ -223,17 +257,26 @@ export const Submenu = styled.div `
|
|
|
223
257
|
background: transparent;
|
|
224
258
|
}
|
|
225
259
|
|
|
260
|
+
/* Reset color inheritance from parent with !important to override panel header styles */
|
|
261
|
+
& *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
262
|
+
color: #1a1a1a !important;
|
|
263
|
+
}
|
|
264
|
+
|
|
226
265
|
[data-theme='dark'] & {
|
|
227
266
|
background: #2a2a2a;
|
|
228
267
|
border-color: rgba(255, 255, 255, 0.1);
|
|
229
268
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4),
|
|
230
269
|
0 2px 8px rgba(0, 0, 0, 0.3);
|
|
231
270
|
}
|
|
271
|
+
|
|
272
|
+
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
273
|
+
color: #e0e0e0 !important;
|
|
274
|
+
}
|
|
232
275
|
`;
|
|
233
276
|
export const MobileMenuHeader = styled.div `
|
|
234
277
|
display: flex;
|
|
235
278
|
align-items: center;
|
|
236
|
-
padding:
|
|
279
|
+
padding: 4px 8px;
|
|
237
280
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
238
281
|
margin-bottom: 8px;
|
|
239
282
|
gap: 12px;
|
|
@@ -249,33 +292,18 @@ export const BackButton = styled.button `
|
|
|
249
292
|
display: flex;
|
|
250
293
|
align-items: center;
|
|
251
294
|
justify-content: center;
|
|
252
|
-
background: #0066cc;
|
|
253
|
-
color: white;
|
|
254
295
|
border: none;
|
|
255
296
|
border-radius: 8px;
|
|
256
297
|
width: 32px;
|
|
257
298
|
height: 32px;
|
|
258
299
|
cursor: pointer;
|
|
259
|
-
font-size: 18px;
|
|
260
300
|
transition: all 0.15s ease;
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
background: #0052a3;
|
|
264
|
-
transform: scale(1.05);
|
|
265
|
-
}
|
|
301
|
+
font-size: 16px;
|
|
302
|
+
transform: translateY(-2px);
|
|
266
303
|
|
|
267
304
|
&:active {
|
|
268
305
|
transform: scale(0.95);
|
|
269
306
|
}
|
|
270
|
-
|
|
271
|
-
[data-theme='dark'] & {
|
|
272
|
-
background: #4db8ff;
|
|
273
|
-
color: #1a1a1a;
|
|
274
|
-
|
|
275
|
-
&:hover {
|
|
276
|
-
background: #66c2ff;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
307
|
`;
|
|
280
308
|
export const HeaderTitle = styled.h3 `
|
|
281
309
|
margin: 0;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export interface TMContextMenuItemProps {
|
|
2
|
+
id?: string;
|
|
2
3
|
name: string;
|
|
3
4
|
icon?: React.ReactNode;
|
|
4
5
|
disabled?: boolean;
|
|
@@ -8,11 +9,23 @@ export interface TMContextMenuItemProps {
|
|
|
8
9
|
rightIcon?: React.ReactNode;
|
|
9
10
|
onRightIconClick?: () => void;
|
|
10
11
|
beginGroup?: boolean;
|
|
12
|
+
tooltip?: string;
|
|
13
|
+
operationType?: 'singleRow' | 'multiRow';
|
|
11
14
|
}
|
|
12
15
|
export interface TMContextMenuProps {
|
|
13
16
|
items: TMContextMenuItemProps[];
|
|
14
17
|
trigger?: 'right' | 'left';
|
|
15
18
|
children?: React.ReactNode;
|
|
19
|
+
target?: string;
|
|
20
|
+
externalControl?: {
|
|
21
|
+
visible: boolean;
|
|
22
|
+
position: {
|
|
23
|
+
x: number;
|
|
24
|
+
y: number;
|
|
25
|
+
};
|
|
26
|
+
onClose: () => void;
|
|
27
|
+
};
|
|
28
|
+
keepOpenOnClick?: boolean;
|
|
16
29
|
}
|
|
17
30
|
export interface Position {
|
|
18
31
|
x: number;
|