@topconsultnpm/sdkui-react 6.20.0-dev1.30 → 6.20.0-dev1.32
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 +27 -9
- package/lib/components/NewComponents/ContextMenu/styles.d.ts +1 -0
- package/lib/components/NewComponents/ContextMenu/styles.js +15 -20
- package/lib/components/NewComponents/ContextMenu/types.d.ts +9 -0
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +65 -37
- package/lib/components/NewComponents/FloatingMenuBar/styles.js +0 -1
- package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +0 -1
- package/lib/components/base/TMCustomButton.js +20 -12
- package/lib/components/base/TMDataGrid.d.ts +3 -0
- package/lib/components/base/TMDataGrid.js +85 -5
- package/lib/components/features/search/TMSearchResult.js +10 -24
- package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +2 -2
- package/lib/components/features/search/TMSearchResultsMenuItems.js +139 -139
- package/lib/helper/SDKUI_Globals.d.ts +0 -1
- package/lib/helper/SDKUI_Globals.js +0 -1
- package/package.json +1 -1
|
@@ -2,7 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
import { useState, useRef, useEffect } from 'react';
|
|
3
3
|
import * as S from './styles';
|
|
4
4
|
import { useIsMobile, useMenuPosition } from './hooks';
|
|
5
|
-
|
|
5
|
+
import { IconArrowLeft } from '../../../helper';
|
|
6
|
+
const TMContextMenu = ({ items, trigger = 'right', children, externalControl }) => {
|
|
6
7
|
const [menuState, setMenuState] = useState({
|
|
7
8
|
visible: false,
|
|
8
9
|
position: { x: 0, y: 0 },
|
|
@@ -16,14 +17,31 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
16
17
|
const submenuTimeoutRef = useRef(null);
|
|
17
18
|
const { openLeft, openUp, isCalculated } = useMenuPosition(menuRef, menuState.position);
|
|
18
19
|
const handleClose = () => {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
if (externalControl) {
|
|
21
|
+
externalControl.onClose();
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
setMenuState(prev => ({
|
|
25
|
+
...prev,
|
|
26
|
+
visible: false,
|
|
27
|
+
submenuStack: [items],
|
|
28
|
+
parentNames: [],
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
25
31
|
setHoveredSubmenus([]);
|
|
26
32
|
};
|
|
33
|
+
// Sync with external control when provided
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
if (externalControl) {
|
|
36
|
+
setMenuState(prev => ({
|
|
37
|
+
...prev,
|
|
38
|
+
visible: externalControl.visible,
|
|
39
|
+
position: externalControl.position,
|
|
40
|
+
submenuStack: [items],
|
|
41
|
+
parentNames: [],
|
|
42
|
+
}));
|
|
43
|
+
}
|
|
44
|
+
}, [externalControl, items]);
|
|
27
45
|
useEffect(() => {
|
|
28
46
|
if (!menuState.visible)
|
|
29
47
|
return;
|
|
@@ -178,10 +196,10 @@ const TMContextMenu = ({ items, trigger = 'right', children }) => {
|
|
|
178
196
|
};
|
|
179
197
|
const currentMenu = menuState.submenuStack.at(-1) || items;
|
|
180
198
|
const currentParentName = menuState.parentNames.at(-1) || '';
|
|
181
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onKeyDown: (e) => {
|
|
199
|
+
return (_jsxs(_Fragment, { children: [!externalControl && children && (_jsx("div", { ref: triggerRef, onContextMenu: handleContextMenu, onClick: handleClick, onKeyDown: (e) => {
|
|
182
200
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
183
201
|
handleClick(e);
|
|
184
202
|
}
|
|
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, "$isPositioned": isCalculated, children: [isMobile && menuState.parentNames.length > 0 && (_jsxs(S.MobileMenuHeader, { children: [_jsx(S.BackButton, { onClick: handleBack, "aria-label": "Go back", children:
|
|
203
|
+
}, 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, "$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}`)))] }))] }));
|
|
186
204
|
};
|
|
187
205
|
export default TMContextMenu;
|
|
@@ -4,6 +4,7 @@ export declare const MenuContainer: import("styled-components/dist/types").IStyl
|
|
|
4
4
|
$openLeft: boolean;
|
|
5
5
|
$openUp: boolean;
|
|
6
6
|
$isPositioned: boolean;
|
|
7
|
+
$externalControl?: boolean;
|
|
7
8
|
}>> & string;
|
|
8
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>, {
|
|
9
10
|
$disabled?: boolean;
|
|
@@ -54,6 +54,16 @@ export const MenuContainer = styled.div `
|
|
|
54
54
|
[data-theme='dark'] & *:not(svg):not(.right-icon-btn):not(.right-icon-btn *) {
|
|
55
55
|
color: #e0e0e0 !important;
|
|
56
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
|
+
`}
|
|
57
67
|
`;
|
|
58
68
|
export const MenuItem = styled.div `
|
|
59
69
|
display: flex;
|
|
@@ -123,8 +133,8 @@ export const MenuItem = styled.div `
|
|
|
123
133
|
}
|
|
124
134
|
|
|
125
135
|
@media (max-width: 768px) {
|
|
126
|
-
padding:
|
|
127
|
-
font-size:
|
|
136
|
+
padding: 4px 10px;
|
|
137
|
+
font-size: 12px;
|
|
128
138
|
}
|
|
129
139
|
`;
|
|
130
140
|
export const MenuItemContent = styled.div `
|
|
@@ -266,7 +276,7 @@ export const Submenu = styled.div `
|
|
|
266
276
|
export const MobileMenuHeader = styled.div `
|
|
267
277
|
display: flex;
|
|
268
278
|
align-items: center;
|
|
269
|
-
padding:
|
|
279
|
+
padding: 4px 8px;
|
|
270
280
|
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
|
|
271
281
|
margin-bottom: 8px;
|
|
272
282
|
gap: 12px;
|
|
@@ -282,33 +292,18 @@ export const BackButton = styled.button `
|
|
|
282
292
|
display: flex;
|
|
283
293
|
align-items: center;
|
|
284
294
|
justify-content: center;
|
|
285
|
-
background: #0066cc;
|
|
286
|
-
color: white;
|
|
287
295
|
border: none;
|
|
288
296
|
border-radius: 8px;
|
|
289
297
|
width: 32px;
|
|
290
298
|
height: 32px;
|
|
291
299
|
cursor: pointer;
|
|
292
|
-
font-size: 18px;
|
|
293
300
|
transition: all 0.15s ease;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
background: #0052a3;
|
|
297
|
-
transform: scale(1.05);
|
|
298
|
-
}
|
|
301
|
+
font-size: 16px;
|
|
302
|
+
transform: translateY(-2px);
|
|
299
303
|
|
|
300
304
|
&:active {
|
|
301
305
|
transform: scale(0.95);
|
|
302
306
|
}
|
|
303
|
-
|
|
304
|
-
[data-theme='dark'] & {
|
|
305
|
-
background: #4db8ff;
|
|
306
|
-
color: #1a1a1a;
|
|
307
|
-
|
|
308
|
-
&:hover {
|
|
309
|
-
background: #66c2ff;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
307
|
`;
|
|
313
308
|
export const HeaderTitle = styled.h3 `
|
|
314
309
|
margin: 0;
|
|
@@ -9,11 +9,20 @@ export interface TMContextMenuItemProps {
|
|
|
9
9
|
onRightIconClick?: () => void;
|
|
10
10
|
beginGroup?: boolean;
|
|
11
11
|
tooltip?: string;
|
|
12
|
+
operationType?: 'singleRow' | 'multiRow';
|
|
12
13
|
}
|
|
13
14
|
export interface TMContextMenuProps {
|
|
14
15
|
items: TMContextMenuItemProps[];
|
|
15
16
|
trigger?: 'right' | 'left';
|
|
16
17
|
children?: React.ReactNode;
|
|
18
|
+
externalControl?: {
|
|
19
|
+
visible: boolean;
|
|
20
|
+
position: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
onClose: () => void;
|
|
25
|
+
};
|
|
17
26
|
}
|
|
18
27
|
export interface Position {
|
|
19
28
|
x: number;
|
|
@@ -6,30 +6,75 @@ import TMTooltip from '../../base/TMTooltip';
|
|
|
6
6
|
import * as S from './styles';
|
|
7
7
|
import { IconApply, IconMenuKebab, IconMenuVertical, IconPencil, IconPin, SDKUI_Globals } from '../../../helper';
|
|
8
8
|
const IconDraggableDots = (props) => (_jsx("svg", { fontSize: 18, viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M9 3a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm10-18a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0zm0 9a2 2 0 11-4 0 2 2 0 014 0z" }) }));
|
|
9
|
-
const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [],
|
|
10
|
-
|
|
9
|
+
const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) => {
|
|
10
|
+
const getDefaultConfig = () => ({
|
|
11
|
+
orientation: 'horizontal',
|
|
12
|
+
savedItemIds: [],
|
|
13
|
+
position: defaultPosition,
|
|
14
|
+
});
|
|
15
|
+
const resetFloatingBarSettings = () => {
|
|
16
|
+
// Reset the floatingMenuBar settings in SDKUI_Globals to trigger save to localStorage
|
|
17
|
+
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
18
|
+
orientation: 'horizontal',
|
|
19
|
+
itemIds: [],
|
|
20
|
+
position: defaultPosition,
|
|
21
|
+
};
|
|
22
|
+
};
|
|
11
23
|
const loadConfig = () => {
|
|
12
24
|
try {
|
|
13
25
|
const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
|
|
26
|
+
// Validate that settings object exists and has required properties with correct types
|
|
27
|
+
if (!settings || typeof settings !== 'object') {
|
|
28
|
+
console.warn('FloatingMenuBar: Invalid settings object, resetting to defaults');
|
|
29
|
+
resetFloatingBarSettings();
|
|
30
|
+
return getDefaultConfig();
|
|
31
|
+
}
|
|
32
|
+
// Validate position
|
|
33
|
+
const hasValidPosition = settings.position &&
|
|
34
|
+
typeof settings.position.x === 'number' &&
|
|
35
|
+
typeof settings.position.y === 'number' &&
|
|
36
|
+
!isNaN(settings.position.x) &&
|
|
37
|
+
!isNaN(settings.position.y) &&
|
|
38
|
+
isFinite(settings.position.x) &&
|
|
39
|
+
isFinite(settings.position.y);
|
|
40
|
+
if (!hasValidPosition) {
|
|
41
|
+
console.warn('FloatingMenuBar: Invalid position, resetting to defaults');
|
|
42
|
+
resetFloatingBarSettings();
|
|
43
|
+
return getDefaultConfig();
|
|
44
|
+
}
|
|
45
|
+
// Ensure position is within reasonable viewport bounds
|
|
46
|
+
const maxX = globalThis.window?.innerWidth ? globalThis.window.innerWidth - 50 : 1000;
|
|
47
|
+
const maxY = globalThis.window?.innerHeight ? globalThis.window.innerHeight - 50 : 800;
|
|
48
|
+
if (settings.position.x < 0 || settings.position.x > maxX ||
|
|
49
|
+
settings.position.y < 0 || settings.position.y > maxY) {
|
|
50
|
+
console.warn('FloatingMenuBar: Position out of bounds, resetting to defaults');
|
|
51
|
+
resetFloatingBarSettings();
|
|
52
|
+
return getDefaultConfig();
|
|
53
|
+
}
|
|
54
|
+
// Validate orientation
|
|
55
|
+
const validOrientation = (settings.orientation === 'horizontal' || settings.orientation === 'vertical')
|
|
56
|
+
? settings.orientation
|
|
57
|
+
: 'horizontal';
|
|
58
|
+
// Validate itemIds
|
|
59
|
+
const validItemIds = Array.isArray(settings.itemIds) ? settings.itemIds : [];
|
|
14
60
|
// Check if position was actually saved (not just the default class value)
|
|
15
|
-
const hasSavedPosition = settings.position
|
|
16
|
-
(settings.position.x !== 100 || settings.position.y !== 100 ||
|
|
17
|
-
(settings.itemIds && settings.itemIds.length > 0));
|
|
61
|
+
const hasSavedPosition = settings.position.x !== 100 || settings.position.y !== 100 || validItemIds.length > 0;
|
|
18
62
|
return {
|
|
19
|
-
orientation:
|
|
20
|
-
|
|
21
|
-
savedItemIds: settings.itemIds || [],
|
|
63
|
+
orientation: validOrientation,
|
|
64
|
+
savedItemIds: validItemIds,
|
|
22
65
|
position: hasSavedPosition ? settings.position : defaultPosition,
|
|
23
66
|
};
|
|
24
67
|
}
|
|
25
68
|
catch (error) {
|
|
26
69
|
console.error('Failed to load FloatingMenuBar config:', error);
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
70
|
+
// Reset to defaults on any error
|
|
71
|
+
try {
|
|
72
|
+
resetFloatingBarSettings();
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
console.error('Failed to reset FloatingMenuBar settings:', e);
|
|
76
|
+
}
|
|
77
|
+
return getDefaultConfig();
|
|
33
78
|
}
|
|
34
79
|
};
|
|
35
80
|
const initialConfig = loadConfig();
|
|
@@ -43,7 +88,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
43
88
|
});
|
|
44
89
|
const floatingRef = useRef(null);
|
|
45
90
|
const dragOffset = useRef({ x: 0, y: 0 });
|
|
46
|
-
const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
|
|
47
91
|
const [dragOverIndex, setDragOverIndex] = useState(null);
|
|
48
92
|
// Use refs to track item IDs without causing re-renders
|
|
49
93
|
const floatingBarItemIds = useRef(new Set());
|
|
@@ -60,13 +104,15 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
60
104
|
const itemId = `${parentPath}${item.name}-${index}`;
|
|
61
105
|
// Only add items that have onClick (final actions, not submenu parents)
|
|
62
106
|
if (item.onClick && !item.submenu) {
|
|
107
|
+
// Check if item is currently in the floating bar
|
|
108
|
+
const isPinned = state.items.some(i => i.id === itemId || i.name === item.name);
|
|
63
109
|
result.push({
|
|
64
110
|
id: itemId,
|
|
65
111
|
name: item.name,
|
|
66
112
|
icon: item.icon || _jsx(IconPin, {}),
|
|
67
113
|
onClick: item.onClick,
|
|
68
114
|
disabled: item.disabled,
|
|
69
|
-
isPinned:
|
|
115
|
+
isPinned: isPinned,
|
|
70
116
|
originalMenuItem: item,
|
|
71
117
|
});
|
|
72
118
|
}
|
|
@@ -76,7 +122,7 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
76
122
|
}
|
|
77
123
|
});
|
|
78
124
|
return result;
|
|
79
|
-
}, [
|
|
125
|
+
}, [state.items]);
|
|
80
126
|
// Restore items on mount from savedItemIds
|
|
81
127
|
useEffect(() => {
|
|
82
128
|
if (contextMenuItems.length > 0) {
|
|
@@ -112,17 +158,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
112
158
|
return { ...s, items: [...s.items, item] };
|
|
113
159
|
}
|
|
114
160
|
});
|
|
115
|
-
// Update pinned IDs for context menu items
|
|
116
|
-
setPinnedItemIds(prev => {
|
|
117
|
-
const newSet = new Set(prev);
|
|
118
|
-
if (newSet.has(item.id)) {
|
|
119
|
-
newSet.delete(item.id);
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
newSet.add(item.id);
|
|
123
|
-
}
|
|
124
|
-
return newSet;
|
|
125
|
-
});
|
|
126
161
|
}, [maxItems]);
|
|
127
162
|
// Get current item state (disabled and onClick) from contextMenuItems
|
|
128
163
|
const getCurrentItemState = useCallback((itemName) => {
|
|
@@ -242,7 +277,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
242
277
|
// Replace the entire object to trigger the Proxy
|
|
243
278
|
SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
|
|
244
279
|
orientation: state.orientation,
|
|
245
|
-
pinnedItemIds: Array.from(pinnedItemIds),
|
|
246
280
|
itemIds: state.items.map(item => item.id),
|
|
247
281
|
position: state.position,
|
|
248
282
|
};
|
|
@@ -250,7 +284,7 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
250
284
|
catch (error) {
|
|
251
285
|
console.error('Failed to save FloatingMenuBar config:', error);
|
|
252
286
|
}
|
|
253
|
-
}, [state.orientation, state.items, state.position
|
|
287
|
+
}, [state.orientation, state.items, state.position]);
|
|
254
288
|
const toggleConfigMode = () => {
|
|
255
289
|
setState(s => ({ ...s, isConfigMode: !s.isConfigMode }));
|
|
256
290
|
};
|
|
@@ -294,12 +328,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
294
328
|
...s,
|
|
295
329
|
items: s.items.filter(item => item.id !== itemId),
|
|
296
330
|
}));
|
|
297
|
-
// Also remove from pinned items if it was pinned
|
|
298
|
-
setPinnedItemIds(prev => {
|
|
299
|
-
const newSet = new Set(prev);
|
|
300
|
-
newSet.delete(itemId);
|
|
301
|
-
return newSet;
|
|
302
|
-
});
|
|
303
331
|
};
|
|
304
332
|
// Drag and drop for reordering
|
|
305
333
|
const handleDragStart = (e, index) => {
|
|
@@ -373,6 +401,6 @@ isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) =>
|
|
|
373
401
|
currentOnClick();
|
|
374
402
|
}
|
|
375
403
|
}, disabled: isDisabled, children: item.icon }) })), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
|
|
376
|
-
}), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: enhancedContextMenuItems(), trigger: "left", children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) },
|
|
404
|
+
}), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: enhancedContextMenuItems(), trigger: "left", children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) }, state.items.map(i => i.id).join(','))), _jsx(S.ConfigButton, { onClick: toggleConfigMode, "$isActive": state.isConfigMode, children: state.isConfigMode ? _jsx(IconApply, {}) : _jsx(IconPencil, {}) }), !state.isConfigMode && (_jsx(S.OrientationToggle, { "$orientation": state.orientation, onClick: toggleOrientation, children: _jsx(IconMenuKebab, {}) }))] })] }));
|
|
377
405
|
};
|
|
378
406
|
export default TMFloatingMenuBar;
|
|
@@ -11,7 +11,6 @@ export interface TMFloatingMenuItem {
|
|
|
11
11
|
export interface TMFloatingMenuBarProps {
|
|
12
12
|
containerRef: React.RefObject<HTMLElement | null>;
|
|
13
13
|
contextMenuItems?: TMContextMenuItemProps[];
|
|
14
|
-
storageKey?: string;
|
|
15
14
|
isConstrained?: boolean;
|
|
16
15
|
defaultPosition?: Position;
|
|
17
16
|
maxItems?: number;
|
|
@@ -52,12 +52,12 @@ const TMCustomButton = (props) => {
|
|
|
52
52
|
const RunOnce = button.mode === "RunOnce";
|
|
53
53
|
const [loading, setLoading] = useState(true);
|
|
54
54
|
const [error, setError] = useState(false);
|
|
55
|
-
const
|
|
55
|
+
const itemsToProcess = useMemo(() => selectedItems && selectedItems.length > 0 ? selectedItems : [attributes], [selectedItems, attributes]);
|
|
56
56
|
// Stati per il wait panel
|
|
57
|
-
const [showWaitPanel, setShowWaitPanel] = useState(
|
|
58
|
-
const [waitPanelText, setWaitPanelText] = useState(SDKUI_Localizator.CustomButtonActions.replaceParams(1,
|
|
57
|
+
const [showWaitPanel, setShowWaitPanel] = useState(itemsToProcess.length > 0 && !RunOnce);
|
|
58
|
+
const [waitPanelText, setWaitPanelText] = useState(SDKUI_Localizator.CustomButtonActions.replaceParams(1, itemsToProcess.length));
|
|
59
59
|
const [waitPanelValue, setWaitPanelValue] = useState(0);
|
|
60
|
-
const [waitPanelMaxValue, setWaitPanelMaxValue] = useState(
|
|
60
|
+
const [waitPanelMaxValue, setWaitPanelMaxValue] = useState(itemsToProcess.length);
|
|
61
61
|
const [abortController, setAbortController] = useState(undefined);
|
|
62
62
|
// Aggiungi timestamp all'URL per evitare cache
|
|
63
63
|
const iframeUrl = useMemo(() => {
|
|
@@ -84,21 +84,29 @@ const TMCustomButton = (props) => {
|
|
|
84
84
|
setError(true);
|
|
85
85
|
};
|
|
86
86
|
const executeSequentially = async (controller) => {
|
|
87
|
-
if (!
|
|
87
|
+
if (!itemsToProcess)
|
|
88
88
|
return;
|
|
89
|
-
for (const [index, item] of
|
|
89
|
+
for (const [index, item] of itemsToProcess.entries()) {
|
|
90
90
|
if (controller.signal.aborted)
|
|
91
91
|
break;
|
|
92
|
-
setWaitPanelText(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1,
|
|
92
|
+
setWaitPanelText(SDKUI_Localizator.CustomButtonActions.replaceParams(index + 1, itemsToProcess.length));
|
|
93
93
|
setWaitPanelValue(index);
|
|
94
94
|
// Attendi che l'iframe sia pronto e invia il messaggio
|
|
95
95
|
await new Promise((resolve) => {
|
|
96
96
|
const checkIframe = setInterval(() => {
|
|
97
97
|
if (iframeRef.current?.contentWindow) {
|
|
98
98
|
clearInterval(checkIframe);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if (selectedItems && selectedItems.length > 0) {
|
|
100
|
+
//devo convertire item in formData
|
|
101
|
+
const processedItem = getSelectedItem(args, formData, item);
|
|
102
|
+
postMessageIframe(processedItem);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
postMessageIframe(item);
|
|
106
|
+
}
|
|
107
|
+
//imposta 100% se sono all'ultimo item
|
|
108
|
+
if (index === itemsToProcess.length - 1)
|
|
109
|
+
setWaitPanelValue(index + 1);
|
|
102
110
|
// Attendi prima di passare al prossimo
|
|
103
111
|
setTimeout(() => {
|
|
104
112
|
setWaitPanelValue(index + 1);
|
|
@@ -120,7 +128,7 @@ const TMCustomButton = (props) => {
|
|
|
120
128
|
useEffect(() => {
|
|
121
129
|
if (loading || error)
|
|
122
130
|
return;
|
|
123
|
-
if (!RunOnce &&
|
|
131
|
+
if (!RunOnce && itemsToProcess.length > 0) {
|
|
124
132
|
// esegui per ogni item selezionato
|
|
125
133
|
const controller = new AbortController();
|
|
126
134
|
controller.signal.addEventListener('abort', () => {
|
|
@@ -128,7 +136,7 @@ const TMCustomButton = (props) => {
|
|
|
128
136
|
onClose?.();
|
|
129
137
|
});
|
|
130
138
|
setAbortController(controller);
|
|
131
|
-
setWaitPanelMaxValue(
|
|
139
|
+
setWaitPanelMaxValue(itemsToProcess.length);
|
|
132
140
|
executeSequentially(controller);
|
|
133
141
|
}
|
|
134
142
|
else {
|
|
@@ -2,6 +2,7 @@ import React from 'react';
|
|
|
2
2
|
import { IColumnProps, IDataGridOptions, IMasterDetailProps } from 'devextreme-react/data-grid';
|
|
3
3
|
import dxDataGrid from 'devextreme/ui/data_grid';
|
|
4
4
|
import { ITMCounterContainerProps } from './TMCounterContainer';
|
|
5
|
+
import { TMContextMenuItemProps } from '../NewComponents/ContextMenu/types';
|
|
5
6
|
export interface TMDataGridContextMenuItem {
|
|
6
7
|
text: string;
|
|
7
8
|
icon: string;
|
|
@@ -51,6 +52,8 @@ export interface TMDataGridProps<T> extends IDataGridOptions {
|
|
|
51
52
|
masterDetail?: IMasterDetailProps;
|
|
52
53
|
/** On Has Filters Change */
|
|
53
54
|
onHasFiltersChange?: (hasFilters: boolean) => void;
|
|
55
|
+
/** Custom context menu items - when provided, replaces DevExtreme's native context menu with TMContextMenu */
|
|
56
|
+
customContextMenuItems?: TMContextMenuItemProps[];
|
|
54
57
|
}
|
|
55
58
|
declare const TMDataGrid: React.ForwardRefExoticComponent<TMDataGridProps<unknown> & React.RefAttributes<dxDataGrid<any, any>>>;
|
|
56
59
|
export default TMDataGrid;
|
|
@@ -4,6 +4,7 @@ import DataGrid, { Column, HeaderFilter, Selection, Scrolling, LoadPanel, Search
|
|
|
4
4
|
import DataSource from 'devextreme/data/data_source';
|
|
5
5
|
import { IconAll, IconSelected, IconVisible, SDKUI_Globals, SDKUI_Localizator } from '../../helper';
|
|
6
6
|
import TMCounterContainer, { CounterItemKey } from './TMCounterContainer';
|
|
7
|
+
import TMContextMenu from '../NewComponents/ContextMenu/TMContextMenu';
|
|
7
8
|
;
|
|
8
9
|
export var TMDataGridPageSize;
|
|
9
10
|
(function (TMDataGridPageSize) {
|
|
@@ -16,7 +17,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
16
17
|
// main properties
|
|
17
18
|
keyExpr = 'id', dataSource, focusedRowEnabled = true, hoverStateEnabled = true, focusedRowKey, selectedRowKeys = [],
|
|
18
19
|
// custom options
|
|
19
|
-
dataColumns = [], pageSize = TMDataGridPageSize.Large, showHeaderFilter = true, showFilterPanel = true, showHeaderColumnChooser = false, showLoadPanel = true, showSearchPanel = true, searchPanelToolbarPosition = 'before', searchPanelFocusStarting = false, counterConfig = { show: false, items: new Map() }, onHasFiltersChange,
|
|
20
|
+
dataColumns = [], pageSize = TMDataGridPageSize.Large, showHeaderFilter = true, showFilterPanel = true, showHeaderColumnChooser = false, showLoadPanel = true, showSearchPanel = true, searchPanelToolbarPosition = 'before', searchPanelFocusStarting = false, counterConfig = { show: false, items: new Map() }, onHasFiltersChange, customContextMenuItems,
|
|
20
21
|
// events and callbacks
|
|
21
22
|
onSelectionChanged, onFocusedRowChanged, onRowDblClick, onRowClick, onCellClick, onCellDblClick, onOptionChanged, onContentReady, onContextMenuPreparing, onInitialized, onEditorPreparing, onCellPrepared, onRowPrepared, onRowUpdating, onRowExpanded, onRowCollapsed, onRowUpdated, onSaved, onEditCanceled, onEditingStart, onEditingChange, customizeColumns, onKeyDown, scrolling = { mode: 'standard', useNative: SDKUI_Globals.userSettings?.themeSettings.gridSettings.useNativeScrollbar === 1 }, paging = { enabled: true, pageSize: pageSize }, pager = { visible: true, showInfo: true, showNavigationButtons: true }, selection = { mode: 'multiple', showCheckBoxesMode: "always", selectAllMode: "allPages" }, sorting, summary, stateStoring, grouping, groupPanel, filterRow, headerFilter, editing, rowDragging, masterDetail,
|
|
22
23
|
// other properties
|
|
@@ -29,10 +30,42 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
29
30
|
const [totalRecordCount, setTotalRecordCount] = useState(0);
|
|
30
31
|
const [visibleItemsCount, setVisibleItemsCount] = useState(0);
|
|
31
32
|
const [hasFilters, setHasFilters] = useState(false);
|
|
33
|
+
// Custom context menu states
|
|
34
|
+
const [customContextMenuVisible, setCustomContextMenuVisible] = useState(false);
|
|
35
|
+
const [customContextMenuPosition, setCustomContextMenuPosition] = useState({ x: 0, y: 0 });
|
|
36
|
+
const [customContextMenuRowKey, setCustomContextMenuRowKey] = useState(undefined);
|
|
37
|
+
const gridContainerRef = useRef(null);
|
|
32
38
|
useEffect(() => {
|
|
33
39
|
const count = getRecordCount(dataSource);
|
|
34
40
|
setTotalRecordCount(count);
|
|
35
41
|
}, [dataSource]);
|
|
42
|
+
// Handle custom context menu (only when customContextMenuItems is provided)
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (!customContextMenuItems || !gridContainerRef.current)
|
|
45
|
+
return;
|
|
46
|
+
const gridContainer = gridContainerRef.current;
|
|
47
|
+
const handleContextMenu = (e) => {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
e.stopPropagation();
|
|
50
|
+
// Get the clicked row
|
|
51
|
+
const target = e.target;
|
|
52
|
+
const rowElement = target.closest('.dx-data-row');
|
|
53
|
+
if (rowElement && internalRef.current) {
|
|
54
|
+
const rowIndex = Array.from(rowElement.parentElement?.children || []).indexOf(rowElement);
|
|
55
|
+
const rowKey = internalRef.current.instance().getKeyByRowIndex(rowIndex);
|
|
56
|
+
// Change focused row
|
|
57
|
+
internalRef.current.instance().option('focusedRowKey', rowKey);
|
|
58
|
+
// Show custom context menu
|
|
59
|
+
setCustomContextMenuVisible(true);
|
|
60
|
+
setCustomContextMenuPosition({ x: e.clientX, y: e.clientY });
|
|
61
|
+
setCustomContextMenuRowKey(rowKey);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
gridContainer.addEventListener('contextmenu', handleContextMenu);
|
|
65
|
+
return () => {
|
|
66
|
+
gridContainer.removeEventListener('contextmenu', handleContextMenu);
|
|
67
|
+
};
|
|
68
|
+
}, [customContextMenuItems]);
|
|
36
69
|
// Creating a ref to store the timestamp of the last selection change
|
|
37
70
|
const lastSelectionChangeTime = useRef(Date.now());
|
|
38
71
|
useEffect(() => {
|
|
@@ -101,6 +134,8 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
101
134
|
}
|
|
102
135
|
return {
|
|
103
136
|
...item,
|
|
137
|
+
// Ensure icon is not null/undefined to prevent DevExtreme errors
|
|
138
|
+
icon: item.icon || '',
|
|
104
139
|
disabled: disabled || disabledCalculation, // An item is disabled if it's explicitly set to `true` or if the calculation above determines so
|
|
105
140
|
// Define the behavior when the menu item is clicked
|
|
106
141
|
onClick: () => {
|
|
@@ -132,10 +167,51 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
132
167
|
};
|
|
133
168
|
});
|
|
134
169
|
}, [focusedRowEnabled, focusedRowKey, selectedRowKeys]);
|
|
170
|
+
// Process custom context menu items (for TMContextMenuItemProps)
|
|
171
|
+
const processCustomContextMenuItems = useCallback((items, rowID) => {
|
|
172
|
+
return items.map(item => {
|
|
173
|
+
let disabled = item.disabled ?? false;
|
|
174
|
+
let disabledCalculation = false;
|
|
175
|
+
const id = focusedRowEnabled ? focusedRowKey : rowID;
|
|
176
|
+
if (item.operationType === 'singleRow') {
|
|
177
|
+
disabledCalculation = selectedRowKeys.length > 1 || id === undefined;
|
|
178
|
+
}
|
|
179
|
+
if (item.operationType === 'multiRow') {
|
|
180
|
+
disabledCalculation = selectedRowKeys.length === 0 && id === undefined;
|
|
181
|
+
}
|
|
182
|
+
const originalOnClick = item.onClick;
|
|
183
|
+
return {
|
|
184
|
+
...item,
|
|
185
|
+
disabled: disabled || disabledCalculation,
|
|
186
|
+
onClick: originalOnClick ? () => {
|
|
187
|
+
if (item.operationType === 'singleRow' && id !== undefined) {
|
|
188
|
+
originalOnClick(id);
|
|
189
|
+
}
|
|
190
|
+
else if (item.operationType === 'multiRow' && id !== undefined) {
|
|
191
|
+
if (selectedRowKeys.length > 0) {
|
|
192
|
+
originalOnClick(selectedRowKeys);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
originalOnClick([id]);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
originalOnClick();
|
|
200
|
+
}
|
|
201
|
+
} : undefined,
|
|
202
|
+
submenu: item.submenu ? processCustomContextMenuItems(item.submenu, id) : undefined,
|
|
203
|
+
};
|
|
204
|
+
});
|
|
205
|
+
}, [focusedRowEnabled, focusedRowKey, selectedRowKeys]);
|
|
135
206
|
// Handle context menu preparation
|
|
136
207
|
const onContextMenuPreparingCallback = useCallback((e) => {
|
|
137
208
|
if (e === undefined)
|
|
138
209
|
return;
|
|
210
|
+
// If custom context menu is enabled, completely disable DevExtreme's native context menu
|
|
211
|
+
if (customContextMenuItems && e.target === 'content') {
|
|
212
|
+
e.items = undefined;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
139
215
|
if (onContextMenuPreparing)
|
|
140
216
|
onContextMenuPreparing(e);
|
|
141
217
|
if (e.target === 'content') {
|
|
@@ -157,7 +233,7 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
157
233
|
}
|
|
158
234
|
});
|
|
159
235
|
}
|
|
160
|
-
}, [updateContextMenuItems, onContextMenuPreparing, showHeaderColumnChooser]);
|
|
236
|
+
}, [updateContextMenuItems, onContextMenuPreparing, showHeaderColumnChooser, customContextMenuItems]);
|
|
161
237
|
// Handle toolbar preparation, especially for the search panel
|
|
162
238
|
const onToolbarPreparingCallback = useCallback((e) => {
|
|
163
239
|
if (e === undefined || e.toolbarOptions === undefined || e.toolbarOptions.items === undefined)
|
|
@@ -226,15 +302,19 @@ const TMDataGrid = React.forwardRef((props, ref) => {
|
|
|
226
302
|
// Propaga l'evento originale
|
|
227
303
|
onOptionChanged?.(e);
|
|
228
304
|
}, [onOptionChanged, onHasFiltersChange]);
|
|
229
|
-
return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsx("div", { style: { width: "100%", height: counterConfig.show ? "calc(100% - 25px)" : "100%" }, children: _jsxs(DataGrid, { ref: internalRef, id: id, className: `tm-datagrid ${hasFilters ? 'has-filters' : ''}`,
|
|
305
|
+
return _jsxs("div", { style: { width: "100%", height: "100%" }, children: [_jsx("div", { ref: gridContainerRef, style: { width: "100%", height: counterConfig.show ? "calc(100% - 25px)" : "100%" }, children: _jsxs(DataGrid, { ref: internalRef, id: id, className: `tm-datagrid ${hasFilters ? 'has-filters' : ''}`,
|
|
230
306
|
// main properties
|
|
231
307
|
keyExpr: keyExpr, dataSource: dataSource, selectedRowKeys: selectedRowKeys, focusedRowEnabled: focusedRowEnabled, hoverStateEnabled: hoverStateEnabled,
|
|
232
308
|
// events and callbacks
|
|
233
|
-
onSelectionChanged: onSelectionChangedCallback, onRowDblClick: onRowDblClickCallback, onRowPrepared: onRowPrepared, onContextMenuPreparing: onContextMenuPreparingCallback, onToolbarPreparing: onToolbarPreparingCallback, onFocusedRowChanged: onFocusedRowChanged, onRowClick: onRowClick, onCellClick: onCellClick, onCellDblClick: onCellDblClick, onOptionChanged: onOptionChangedCallback, onContentReady: onContentReadyCallback, onInitialized: onInitialized, customizeColumns: customizeColumns, onEditorPreparing: onEditorPreparing, onCellPrepared: onCellPrepared, onRowUpdating: onRowUpdating, onRowExpanded: onRowExpanded, onRowCollapsed: onRowCollapsed, onRowUpdated: onRowUpdated, onSaved: onSaved, onEditCanceled: onEditCanceled, onEditingStart: onEditingStart, onEditingChange: onEditingChange, onKeyDown: onKeyDown,
|
|
309
|
+
onSelectionChanged: onSelectionChangedCallback, onRowDblClick: onRowDblClickCallback, onRowPrepared: onRowPrepared, onContextMenuPreparing: customContextMenuItems ? undefined : onContextMenuPreparingCallback, onToolbarPreparing: onToolbarPreparingCallback, onFocusedRowChanged: onFocusedRowChanged, onRowClick: onRowClick, onCellClick: onCellClick, onCellDblClick: onCellDblClick, onOptionChanged: onOptionChangedCallback, onContentReady: onContentReadyCallback, onInitialized: onInitialized, customizeColumns: customizeColumns, onEditorPreparing: onEditorPreparing, onCellPrepared: onCellPrepared, onRowUpdating: onRowUpdating, onRowExpanded: onRowExpanded, onRowCollapsed: onRowCollapsed, onRowUpdated: onRowUpdated, onSaved: onSaved, onEditCanceled: onEditCanceled, onEditingStart: onEditingStart, onEditingChange: onEditingChange, onKeyDown: onKeyDown,
|
|
234
310
|
// other properties
|
|
235
311
|
disabled: disabled, autoNavigateToFocusedRow: autoNavigateToFocusedRow, focusedRowKey: focusedRowKey, columnHidingEnabled: columnHidingEnabled, columnResizingMode: columnResizingMode, columnAutoWidth: columnAutoWidth, allowColumnResizing: allowColumnResizing, allowColumnReordering: allowColumnReordering, showBorders: showBorders, showRowLines: showRowLines, showColumnLines: showColumnLines, showColumnHeaders: showColumnHeaders, rowAlternationEnabled: rowAlternationEnabled, wordWrapEnabled: wordWrapEnabled, noDataText: noDataText,
|
|
236
312
|
// styles
|
|
237
|
-
width: width, height: height, style: { userSelect: 'none' }, children: [dataColumns.map((column, index) => (_jsx(Column, { ...column }, column.caption + index.toString()))), sorting && _jsx(Sorting, { ...sorting }), selection && _jsx(Selection, { ...selection }), scrolling && _jsx(Scrolling, { ...scrolling }), summary && _jsx(Summary, { ...summary }), showHeaderFilter && _jsx(HeaderFilter, { visible: true, ...headerFilter }), rowDragging && _jsx(RowDragging, { ...rowDragging }), filterRow && _jsx(FilterRow, { ...filterRow }), showFilterPanel && _jsx(FilterPanel, { visible: true }), showHeaderColumnChooser && _jsxs(ColumnChooser, { height: "400px", enabled: !showHeaderColumnChooser, mode: "select", children: [_jsx(Position, { my: "center", at: "center", of: window }), _jsx(ColumnChooserSearch, { enabled: true }), _jsx(ColumnChooserSelection, { allowSelectAll: false, selectByClick: true, recursive: true })] }), stateStoring && _jsx(StateStoring, { ...stateStoring }), groupPanel && _jsx(GroupPanel, { ...groupPanel }), _jsx(Grouping, { contextMenuEnabled: true, ...grouping }), _jsx(LoadPanel, { enabled: showLoadPanel }), _jsx(SearchPanel, { visible: showSearchPanel, searchVisibleColumnsOnly: true, highlightSearchText: true }), editing && _jsx(Editing, { ...editing }), paging && _jsx(Paging, { ...paging }), pager && _jsx(Pager, { ...pager, visible: totalRecordCount > pageSize }), masterDetail && _jsx(MasterDetail, { ...masterDetail })] }) }), counterConfig.show && _jsx("div", { style: { width: "100%", height: "25px", display: "flex", alignItems: "center", gap: "15px", backgroundColor: "#e0e0e0" }, children: _jsx(TMCounterContainer, { items: counterValues, bgColorContainer: counterConfig.bgColorContainer, bgColorItem: counterConfig.bgColorItem, hoverColorItem: counterConfig.hoverColorItem, textColorItem: counterConfig.textColorItem }) })
|
|
313
|
+
width: width, height: height, style: { userSelect: 'none' }, children: [dataColumns.map((column, index) => (_jsx(Column, { ...column }, column.caption + index.toString()))), sorting && _jsx(Sorting, { ...sorting }), selection && _jsx(Selection, { ...selection }), scrolling && _jsx(Scrolling, { ...scrolling }), summary && _jsx(Summary, { ...summary }), showHeaderFilter && _jsx(HeaderFilter, { visible: true, ...headerFilter }), rowDragging && _jsx(RowDragging, { ...rowDragging }), filterRow && _jsx(FilterRow, { ...filterRow }), showFilterPanel && _jsx(FilterPanel, { visible: true }), showHeaderColumnChooser && _jsxs(ColumnChooser, { height: "400px", enabled: !showHeaderColumnChooser, mode: "select", children: [_jsx(Position, { my: "center", at: "center", of: window }), _jsx(ColumnChooserSearch, { enabled: true }), _jsx(ColumnChooserSelection, { allowSelectAll: false, selectByClick: true, recursive: true })] }), stateStoring && _jsx(StateStoring, { ...stateStoring }), groupPanel && _jsx(GroupPanel, { ...groupPanel }), _jsx(Grouping, { contextMenuEnabled: true, ...grouping }), _jsx(LoadPanel, { enabled: showLoadPanel }), _jsx(SearchPanel, { visible: showSearchPanel, searchVisibleColumnsOnly: true, highlightSearchText: true }), editing && _jsx(Editing, { ...editing }), paging && _jsx(Paging, { ...paging }), pager && _jsx(Pager, { ...pager, visible: totalRecordCount > pageSize }), masterDetail && _jsx(MasterDetail, { ...masterDetail })] }) }), counterConfig.show && _jsx("div", { style: { width: "100%", height: "25px", display: "flex", alignItems: "center", gap: "15px", backgroundColor: "#e0e0e0" }, children: _jsx(TMCounterContainer, { items: counterValues, bgColorContainer: counterConfig.bgColorContainer, bgColorItem: counterConfig.bgColorItem, hoverColorItem: counterConfig.hoverColorItem, textColorItem: counterConfig.textColorItem }) }), customContextMenuItems && (_jsx(TMContextMenu, { items: processCustomContextMenuItems(customContextMenuItems, customContextMenuRowKey), externalControl: {
|
|
314
|
+
visible: customContextMenuVisible,
|
|
315
|
+
position: customContextMenuPosition,
|
|
316
|
+
onClose: () => setCustomContextMenuVisible(false)
|
|
317
|
+
} }))] });
|
|
238
318
|
});
|
|
239
319
|
export default TMDataGrid;
|
|
240
320
|
const getRecordCount = (dataSource) => {
|