@topconsultnpm/sdkui-react 6.20.0-dev1.6 → 6.20.0-dev1.60

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 (84) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.d.ts +4 -0
  2. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +416 -0
  3. package/lib/components/NewComponents/ContextMenu/hooks.d.ts +13 -0
  4. package/lib/components/NewComponents/ContextMenu/hooks.js +61 -0
  5. package/lib/components/NewComponents/ContextMenu/index.d.ts +5 -0
  6. package/lib/components/NewComponents/ContextMenu/index.js +3 -0
  7. package/lib/components/NewComponents/ContextMenu/styles.d.ts +31 -0
  8. package/lib/components/NewComponents/ContextMenu/styles.js +336 -0
  9. package/lib/components/NewComponents/ContextMenu/types.d.ts +39 -0
  10. package/lib/components/NewComponents/ContextMenu/types.js +1 -0
  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.d.ts +4 -0
  14. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +745 -0
  15. package/lib/components/NewComponents/FloatingMenuBar/index.d.ts +2 -0
  16. package/lib/components/NewComponents/FloatingMenuBar/index.js +2 -0
  17. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +51 -0
  18. package/lib/components/NewComponents/FloatingMenuBar/styles.js +385 -0
  19. package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +29 -0
  20. package/lib/components/NewComponents/FloatingMenuBar/types.js +1 -0
  21. package/lib/components/base/TMAccordionNew.js +35 -14
  22. package/lib/components/base/TMCustomButton.js +61 -17
  23. package/lib/components/base/TMDataGrid.d.ts +7 -4
  24. package/lib/components/base/TMDataGrid.js +142 -11
  25. package/lib/components/choosers/TMMetadataChooser.js +8 -1
  26. package/lib/components/editors/TMMetadataValues.js +23 -5
  27. package/lib/components/editors/TMTextBox.js +6 -3
  28. package/lib/components/features/documents/TMDcmtForm.d.ts +13 -1
  29. package/lib/components/features/documents/TMDcmtForm.js +386 -194
  30. package/lib/components/features/documents/TMDcmtPreview.js +40 -69
  31. package/lib/components/features/documents/TMMasterDetailDcmts.js +37 -52
  32. package/lib/components/features/search/TMDcmtCheckoutInfoForm.d.ts +8 -0
  33. package/lib/components/features/search/{TMSearchResultCheckoutInfoForm.js → TMDcmtCheckoutInfoForm.js} +5 -10
  34. package/lib/components/features/search/TMSavedQuerySelector.js +72 -67
  35. package/lib/components/features/search/TMSearch.js +30 -5
  36. package/lib/components/features/search/TMSearchQueryPanel.js +13 -12
  37. package/lib/components/features/search/TMSearchResult.js +57 -216
  38. package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +3 -3
  39. package/lib/components/features/search/TMSearchResultsMenuItems.js +205 -169
  40. package/lib/components/features/search/TMSignSettingsForm.js +1 -1
  41. package/lib/components/features/search/TMSignatureInfoContent.d.ts +6 -0
  42. package/lib/components/features/search/TMSignatureInfoContent.js +140 -0
  43. package/lib/components/features/search/TMViewHistoryDcmt.js +1 -1
  44. package/lib/components/features/tasks/TMTaskForm.js +20 -1
  45. package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -2
  46. package/lib/components/features/tasks/TMTasksUtils.js +62 -52
  47. package/lib/components/features/tasks/TMTasksView.js +6 -6
  48. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +32 -2
  49. package/lib/components/features/workflow/TMWorkflowPopup.js +112 -14
  50. package/lib/components/features/workflow/diagram/WFDiagram.js +2 -2
  51. package/lib/components/forms/Login/LoginValidatorService.d.ts +2 -0
  52. package/lib/components/forms/Login/LoginValidatorService.js +7 -2
  53. package/lib/components/forms/Login/TMLoginForm.js +34 -6
  54. package/lib/components/forms/TMChooserForm.js +1 -1
  55. package/lib/components/grids/TMBlogsPost.js +55 -30
  56. package/lib/components/index.d.ts +2 -0
  57. package/lib/components/index.js +2 -0
  58. package/lib/components/viewers/TMDataListItemViewer.d.ts +2 -1
  59. package/lib/components/viewers/TMDataListItemViewer.js +12 -11
  60. package/lib/css/tm-sdkui.css +1 -1
  61. package/lib/helper/SDKUI_Globals.d.ts +17 -0
  62. package/lib/helper/SDKUI_Globals.js +9 -0
  63. package/lib/helper/SDKUI_Localizator.d.ts +9 -1
  64. package/lib/helper/SDKUI_Localizator.js +87 -1
  65. package/lib/helper/TMIcons.d.ts +2 -0
  66. package/lib/helper/TMIcons.js +6 -0
  67. package/lib/helper/TMPdfViewer.d.ts +8 -0
  68. package/lib/helper/TMPdfViewer.js +368 -0
  69. package/lib/helper/checkinCheckoutManager.d.ts +32 -2
  70. package/lib/helper/checkinCheckoutManager.js +115 -38
  71. package/lib/helper/devextremeCustomMessages.d.ts +30 -0
  72. package/lib/helper/devextremeCustomMessages.js +30 -0
  73. package/lib/helper/helpers.d.ts +2 -1
  74. package/lib/helper/helpers.js +14 -3
  75. package/lib/helper/index.d.ts +1 -0
  76. package/lib/helper/index.js +1 -0
  77. package/lib/helper/queryHelper.js +29 -0
  78. package/lib/hooks/useCheckInOutOperations.d.ts +28 -0
  79. package/lib/hooks/useCheckInOutOperations.js +223 -0
  80. package/lib/hooks/useWorkflowApprove.d.ts +4 -0
  81. package/lib/hooks/useWorkflowApprove.js +14 -1
  82. package/lib/ts/types.d.ts +56 -1
  83. package/package.json +5 -2
  84. package/lib/components/features/search/TMSearchResultCheckoutInfoForm.d.ts +0 -8
@@ -0,0 +1,745 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useRef, useEffect, useCallback } from 'react';
3
+ import { ContextMenu } from '../ContextMenu';
4
+ import ShowAlert from '../../base/TMAlert';
5
+ import TMTooltip from '../../base/TMTooltip';
6
+ import * as S from './styles';
7
+ import { IconAdd, IconApply, IconMenuVertical, IconPin, IconSeparator, IconUndo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
8
+ const Separator = (props) => (_jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", height: "1em", width: "1em", ...props, children: _jsx("path", { d: "M12 2v20", stroke: "currentColor", strokeWidth: "3", strokeLinecap: "round" }) }));
9
+ 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" }) }));
10
+ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 100, }) => {
11
+ const percentToPixels = (percent, containerSize) => {
12
+ return (percent / 100) * containerSize;
13
+ };
14
+ const pixelsToPercent = (pixels, containerSize) => {
15
+ return (pixels / containerSize) * 100;
16
+ };
17
+ const isPixelFormat = (pos) => {
18
+ return pos.x > 100 || pos.y > 100;
19
+ };
20
+ const migrateToPercentage = (pixelPos) => {
21
+ const container = containerRef.current?.getBoundingClientRect();
22
+ const containerWidth = isConstrained && container ? container.width : window.innerWidth;
23
+ const containerHeight = isConstrained && container ? container.height : window.innerHeight;
24
+ return {
25
+ x: pixelsToPercent(pixelPos.x, containerWidth),
26
+ y: pixelsToPercent(pixelPos.y, containerHeight),
27
+ };
28
+ };
29
+ const getDefaultConfig = () => ({
30
+ orientation: 'horizontal',
31
+ savedItemIds: [],
32
+ position: defaultPosition,
33
+ });
34
+ const resetFloatingBarSettings = () => {
35
+ // Reset the floatingMenuBar settings in SDKUI_Globals to trigger save to localStorage
36
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
37
+ orientation: 'horizontal',
38
+ itemIds: [],
39
+ position: defaultPosition,
40
+ };
41
+ };
42
+ const loadConfig = () => {
43
+ try {
44
+ const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
45
+ // Validate that settings object exists and has required properties with correct types
46
+ if (!settings || typeof settings !== 'object') {
47
+ console.warn('FloatingMenuBar: Invalid settings object, resetting to defaults');
48
+ resetFloatingBarSettings();
49
+ return getDefaultConfig();
50
+ }
51
+ // Validate position
52
+ const hasValidPosition = settings.position &&
53
+ typeof settings.position.x === 'number' &&
54
+ typeof settings.position.y === 'number' &&
55
+ !Number.isNaN(settings.position.x) &&
56
+ !Number.isNaN(settings.position.y) &&
57
+ Number.isFinite(settings.position.x) &&
58
+ Number.isFinite(settings.position.y);
59
+ if (!hasValidPosition) {
60
+ console.warn('FloatingMenuBar: Invalid position, resetting to defaults');
61
+ resetFloatingBarSettings();
62
+ return getDefaultConfig();
63
+ }
64
+ // Ensure position is within reasonable viewport bounds
65
+ const maxX = globalThis.window?.innerWidth ? globalThis.window.innerWidth - 50 : 1000;
66
+ const maxY = globalThis.window?.innerHeight ? globalThis.window.innerHeight - 50 : 800;
67
+ if (settings.position.x < 0 || settings.position.x > maxX ||
68
+ settings.position.y < 0 || settings.position.y > maxY) {
69
+ console.warn('FloatingMenuBar: Position out of bounds, resetting to defaults');
70
+ resetFloatingBarSettings();
71
+ return getDefaultConfig();
72
+ }
73
+ // Validate orientation
74
+ const validOrientation = (settings.orientation === 'horizontal' || settings.orientation === 'vertical')
75
+ ? settings.orientation
76
+ : 'horizontal';
77
+ // Validate itemIds
78
+ const validItemIds = Array.isArray(settings.itemIds) ? settings.itemIds : [];
79
+ if (validItemIds.length > 0) {
80
+ // Check if any ID looks like the old format (contains language-specific text or is too long)
81
+ const hasOldFormatIds = validItemIds.some(id => typeof id === 'string' &&
82
+ !id.startsWith('separator-') &&
83
+ (id.length > 20 || id.includes(' ')));
84
+ if (hasOldFormatIds) {
85
+ console.warn('FloatingMenuBar: Detected old name-based configuration, resetting to defaults');
86
+ resetFloatingBarSettings();
87
+ return getDefaultConfig();
88
+ }
89
+ }
90
+ // Migrate old pixel-based position to percentage-based
91
+ let finalPosition = settings.position;
92
+ if (isPixelFormat(settings.position) || settings.positionFormat === 'pixels') {
93
+ console.log('FloatingMenuBar: Migrating pixel-based position to percentage-based');
94
+ finalPosition = migrateToPercentage(settings.position);
95
+ // Save migrated position immediately
96
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
97
+ orientation: validOrientation,
98
+ itemIds: validItemIds,
99
+ position: finalPosition,
100
+ positionFormat: 'percentage',
101
+ };
102
+ }
103
+ return {
104
+ orientation: validOrientation,
105
+ savedItemIds: validItemIds,
106
+ position: finalPosition,
107
+ };
108
+ }
109
+ catch (error) {
110
+ console.error('Failed to load FloatingMenuBar config:', error);
111
+ // Reset to defaults on any error
112
+ try {
113
+ resetFloatingBarSettings();
114
+ }
115
+ catch (e) {
116
+ console.error('Failed to reset FloatingMenuBar settings:', e);
117
+ }
118
+ return getDefaultConfig();
119
+ }
120
+ };
121
+ const initialConfig = loadConfig();
122
+ const [state, setState] = useState({
123
+ position: initialConfig.position, // Stored as percentage
124
+ isDragging: false,
125
+ isConfigMode: false,
126
+ orientation: initialConfig.orientation,
127
+ items: [],
128
+ draggedItemIndex: null,
129
+ });
130
+ const floatingRef = useRef(null);
131
+ const dragOffset = useRef({ x: 0, y: 0 });
132
+ const [dragOverIndex, setDragOverIndex] = useState(null);
133
+ const [pixelPosition, setPixelPosition] = useState({ x: 0, y: 0 }); // Calculated pixel position
134
+ const containerSizeRef = useRef({ width: 0, height: 0 });
135
+ const stateSnapshot = useRef(null);
136
+ const floatingBarItemIds = useRef(new Set());
137
+ const floatingBarItemNames = useRef(new Set());
138
+ useEffect(() => {
139
+ floatingBarItemIds.current = new Set(state.items.map(i => i.id));
140
+ floatingBarItemNames.current = new Set(state.items.map(i => i.name));
141
+ }, [state.items]);
142
+ // Calculate pixel position from percentage when container size or position changes
143
+ useEffect(() => {
144
+ const updatePixelPosition = () => {
145
+ if (!containerRef.current || !floatingRef.current)
146
+ return;
147
+ const container = containerRef.current.getBoundingClientRect();
148
+ const floating = floatingRef.current.getBoundingClientRect();
149
+ const containerWidth = isConstrained ? container.width : window.innerWidth;
150
+ const containerHeight = isConstrained ? container.height : window.innerHeight;
151
+ containerSizeRef.current = { width: containerWidth, height: containerHeight };
152
+ let newX = percentToPixels(state.position.x, containerWidth);
153
+ let newY = percentToPixels(state.position.y, containerHeight);
154
+ newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
155
+ newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
156
+ setPixelPosition({ x: newX, y: newY });
157
+ };
158
+ updatePixelPosition();
159
+ const resizeObserver = new ResizeObserver(() => {
160
+ updatePixelPosition();
161
+ });
162
+ if (containerRef.current) {
163
+ resizeObserver.observe(containerRef.current);
164
+ }
165
+ if (!isConstrained) {
166
+ window.addEventListener('resize', updatePixelPosition);
167
+ }
168
+ return () => {
169
+ resizeObserver.disconnect();
170
+ if (!isConstrained) {
171
+ window.removeEventListener('resize', updatePixelPosition);
172
+ }
173
+ };
174
+ }, [state.position, isConstrained]);
175
+ const flattenMenuItems = useCallback((items, parentPath = '') => {
176
+ const result = [];
177
+ items.forEach((item, index) => {
178
+ const itemId = item.id || `${parentPath}${item.name}-${index}`;
179
+ if (item.onClick && !item.submenu) {
180
+ const isPinned = state.items.some(i => i.id === itemId);
181
+ result.push({
182
+ id: itemId,
183
+ name: item.name,
184
+ icon: item.icon || _jsx(IconPin, {}),
185
+ onClick: item.onClick,
186
+ disabled: item.disabled,
187
+ isPinned: isPinned,
188
+ });
189
+ }
190
+ // Recursively process submenus
191
+ if (item.submenu) {
192
+ result.push(...flattenMenuItems(item.submenu, `${itemId}/`));
193
+ }
194
+ });
195
+ return result;
196
+ }, [state.items]);
197
+ // Restore items on mount from savedItemIds
198
+ useEffect(() => {
199
+ if (contextMenuItems.length > 0 || initialConfig.savedItemIds.length > 0) {
200
+ const flatItems = flattenMenuItems(contextMenuItems);
201
+ // Restore items in the saved order from localStorage
202
+ const restoredItems = initialConfig.savedItemIds
203
+ .map((id) => {
204
+ if (id.startsWith('separator-')) {
205
+ return {
206
+ id,
207
+ name: 'Separator',
208
+ icon: _jsx(Separator, {}),
209
+ onClick: () => { },
210
+ isSeparator: true,
211
+ };
212
+ }
213
+ return flatItems.find(item => item.id === id);
214
+ })
215
+ .filter((item) => item !== undefined);
216
+ if (restoredItems.length > 0) {
217
+ setState(s => ({ ...s, items: restoredItems }));
218
+ }
219
+ }
220
+ }, []); // Only run once on mount
221
+ const togglePin = useCallback((item) => {
222
+ setState(s => {
223
+ const isInFloatingBar = s.items.some(i => i.id === item.id);
224
+ if (isInFloatingBar) {
225
+ // Remove from floating bar
226
+ const newItems = s.items.filter(i => i.id !== item.id);
227
+ return { ...s, items: newItems };
228
+ }
229
+ else {
230
+ // Add to floating bar
231
+ if (s.items.length >= maxItems) {
232
+ ShowAlert({
233
+ mode: 'warning',
234
+ title: 'Limite Massimo Raggiunto',
235
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
236
+ duration: 4000,
237
+ });
238
+ return s;
239
+ }
240
+ return { ...s, items: [...s.items, item] };
241
+ }
242
+ });
243
+ }, [maxItems]);
244
+ // Get current item state (disabled and onClick) from contextMenuItems
245
+ const getCurrentItemState = useCallback((itemId) => {
246
+ const findInItems = (items) => {
247
+ for (let i = 0; i < items.length; i++) {
248
+ const item = items[i];
249
+ // Match by ID if the item has one
250
+ if (item.id === itemId)
251
+ return item;
252
+ // Check in submenu
253
+ if (item.submenu) {
254
+ const found = findInItems(item.submenu);
255
+ if (found)
256
+ return found;
257
+ }
258
+ }
259
+ return undefined;
260
+ };
261
+ const foundItem = findInItems(contextMenuItems);
262
+ return {
263
+ disabled: foundItem?.disabled,
264
+ onClick: foundItem?.onClick
265
+ };
266
+ }, [contextMenuItems]);
267
+ // Remove trailing separators from items array
268
+ const removeTrailingSeparators = useCallback((items) => {
269
+ const result = [...items];
270
+ while (result.length > 0 && result.at(-1)?.isSeparator) {
271
+ result.pop();
272
+ }
273
+ return result;
274
+ }, []);
275
+ // Create a new separator item
276
+ const createSeparator = useCallback(() => {
277
+ const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
278
+ return {
279
+ id: separatorId,
280
+ name: 'Separator',
281
+ icon: _jsx(Separator, {}),
282
+ onClick: () => { },
283
+ isSeparator: true,
284
+ };
285
+ }, []);
286
+ // Add separator to items
287
+ const addSeparator = useCallback(() => {
288
+ if (state.items.length >= maxItems) {
289
+ ShowAlert({
290
+ mode: 'warning',
291
+ title: 'Limite Massimo Raggiunto',
292
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
293
+ duration: 4000,
294
+ });
295
+ return;
296
+ }
297
+ const separator = createSeparator();
298
+ setState(s => ({ ...s, items: [...s.items, separator] }));
299
+ }, [state.items.length, maxItems, createSeparator]);
300
+ const getPinContextMenuItems = useCallback(() => {
301
+ const flatItems = flattenMenuItems(contextMenuItems);
302
+ const currentItemIds = new Set(state.items.map(i => i.id));
303
+ const createPinItems = (items) => {
304
+ return items.map(item => {
305
+ const flatItem = flatItems.find(fi => fi.id === item.id);
306
+ const itemId = flatItem?.id || item.id || '';
307
+ const isAlreadyPinned = currentItemIds.has(itemId);
308
+ const pinItem = {
309
+ ...item,
310
+ onClick: item.onClick && !item.submenu ? () => {
311
+ if (flatItem && !isAlreadyPinned) {
312
+ togglePin(flatItem);
313
+ }
314
+ } : undefined,
315
+ disabled: isAlreadyPinned,
316
+ };
317
+ if (item.submenu) {
318
+ pinItem.submenu = createPinItems(item.submenu);
319
+ }
320
+ return pinItem;
321
+ });
322
+ };
323
+ const pinItems = createPinItems(contextMenuItems);
324
+ // Add separator option at the end
325
+ pinItems.push({
326
+ id: 'add-separator',
327
+ name: SDKUI_Localizator.Add + ' separatore',
328
+ icon: _jsx(IconSeparator, {}),
329
+ onClick: addSeparator,
330
+ beginGroup: true
331
+ });
332
+ return pinItems;
333
+ }, [contextMenuItems, flattenMenuItems, togglePin, state.items, addSeparator]);
334
+ const getContextMenuItemsWithPinIcons = useCallback(() => {
335
+ const flatItems = flattenMenuItems(contextMenuItems);
336
+ const currentItemIds = new Set(state.items.map(i => i.id));
337
+ const addPinIcons = (items) => {
338
+ return items.map(item => {
339
+ const flatItem = flatItems.find(fi => fi.id === item.id);
340
+ const itemId = flatItem?.id || item.id || '';
341
+ const isPinned = currentItemIds.has(itemId);
342
+ const itemWithPin = {
343
+ ...item,
344
+ rightIcon: flatItem ? _jsx(IconPin, { color: isPinned ? 'red' : 'black' }) : undefined,
345
+ onRightIconClick: flatItem ? () => {
346
+ togglePin(flatItem);
347
+ } : undefined,
348
+ };
349
+ if (item.submenu) {
350
+ itemWithPin.submenu = addPinIcons(item.submenu);
351
+ }
352
+ return itemWithPin;
353
+ });
354
+ };
355
+ return addPinIcons(contextMenuItems);
356
+ }, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
357
+ const handleMouseDown = (e) => {
358
+ if (state.isConfigMode)
359
+ return;
360
+ const containerRect = containerRef.current?.getBoundingClientRect();
361
+ if (containerRect) {
362
+ // Calculate drag offset based on positioning mode
363
+ if (isConstrained) {
364
+ // For absolute positioning, offset is relative to container
365
+ dragOffset.current = {
366
+ x: e.clientX - containerRect.left - pixelPosition.x,
367
+ y: e.clientY - containerRect.top - pixelPosition.y,
368
+ };
369
+ }
370
+ else {
371
+ // For fixed positioning, offset is relative to viewport
372
+ dragOffset.current = {
373
+ x: e.clientX - pixelPosition.x,
374
+ y: e.clientY - pixelPosition.y,
375
+ };
376
+ }
377
+ }
378
+ setState(s => ({ ...s, isDragging: true }));
379
+ };
380
+ const handleGripDoubleClick = () => {
381
+ if (state.isConfigMode)
382
+ return;
383
+ toggleOrientation();
384
+ };
385
+ const handleMouseMove = useCallback((e) => {
386
+ if (!state.isDragging || !containerRef.current || !floatingRef.current)
387
+ return;
388
+ const container = containerRef.current.getBoundingClientRect();
389
+ const floating = floatingRef.current.getBoundingClientRect();
390
+ let newX, newY;
391
+ if (isConstrained) {
392
+ // For constrained (absolute positioning), use container coordinates
393
+ newX = e.clientX - container.left - dragOffset.current.x;
394
+ newY = e.clientY - container.top - dragOffset.current.y;
395
+ // Constrain to container bounds
396
+ newX = Math.max(0, Math.min(newX, container.width - floating.width));
397
+ newY = Math.max(0, Math.min(newY, container.height - floating.height));
398
+ }
399
+ else {
400
+ // For unconstrained (fixed positioning), use viewport coordinates
401
+ newX = e.clientX - dragOffset.current.x;
402
+ newY = e.clientY - dragOffset.current.y;
403
+ // Constrain to viewport bounds
404
+ newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
405
+ newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
406
+ }
407
+ // Update pixel position directly during drag
408
+ setPixelPosition({ x: newX, y: newY });
409
+ }, [state.isDragging, containerRef, isConstrained]);
410
+ const handleMouseUp = useCallback(() => {
411
+ if (state.isDragging && containerSizeRef.current.width > 0) {
412
+ // Convert final pixel position to percentage before updating state
413
+ const percentagePosition = {
414
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
415
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
416
+ };
417
+ setState(s => ({
418
+ ...s,
419
+ isDragging: false,
420
+ position: percentagePosition,
421
+ }));
422
+ }
423
+ else {
424
+ setState(s => ({ ...s, isDragging: false }));
425
+ }
426
+ }, [state.isDragging, pixelPosition]);
427
+ // Touch event handlers for tablet support
428
+ const handleTouchStart = (e) => {
429
+ if (state.isConfigMode)
430
+ return;
431
+ const touch = e.touches[0];
432
+ const containerRect = containerRef.current?.getBoundingClientRect();
433
+ if (containerRect) {
434
+ if (isConstrained) {
435
+ dragOffset.current = {
436
+ x: touch.clientX - containerRect.left - pixelPosition.x,
437
+ y: touch.clientY - containerRect.top - pixelPosition.y,
438
+ };
439
+ }
440
+ else {
441
+ dragOffset.current = {
442
+ x: touch.clientX - pixelPosition.x,
443
+ y: touch.clientY - pixelPosition.y,
444
+ };
445
+ }
446
+ }
447
+ setState(s => ({ ...s, isDragging: true }));
448
+ };
449
+ const handleTouchMove = useCallback((e) => {
450
+ if (!state.isDragging || !containerRef.current || !floatingRef.current)
451
+ return;
452
+ const touch = e.touches[0];
453
+ const container = containerRef.current.getBoundingClientRect();
454
+ const floating = floatingRef.current.getBoundingClientRect();
455
+ let newX, newY;
456
+ if (isConstrained) {
457
+ newX = touch.clientX - container.left - dragOffset.current.x;
458
+ newY = touch.clientY - container.top - dragOffset.current.y;
459
+ newX = Math.max(0, Math.min(newX, container.width - floating.width));
460
+ newY = Math.max(0, Math.min(newY, container.height - floating.height));
461
+ }
462
+ else {
463
+ newX = touch.clientX - dragOffset.current.x;
464
+ newY = touch.clientY - dragOffset.current.y;
465
+ newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
466
+ newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
467
+ }
468
+ setPixelPosition({ x: newX, y: newY });
469
+ }, [state.isDragging, containerRef, isConstrained]);
470
+ const handleTouchEnd = useCallback(() => {
471
+ if (state.isDragging && containerSizeRef.current.width > 0) {
472
+ const percentagePosition = {
473
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
474
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
475
+ };
476
+ setState(s => ({
477
+ ...s,
478
+ isDragging: false,
479
+ position: percentagePosition,
480
+ }));
481
+ }
482
+ else {
483
+ setState(s => ({ ...s, isDragging: false }));
484
+ }
485
+ }, [state.isDragging, pixelPosition]);
486
+ useEffect(() => {
487
+ if (state.isDragging) {
488
+ document.addEventListener('mousemove', handleMouseMove);
489
+ document.addEventListener('mouseup', handleMouseUp);
490
+ document.addEventListener('touchmove', handleTouchMove);
491
+ document.addEventListener('touchend', handleTouchEnd);
492
+ return () => {
493
+ document.removeEventListener('mousemove', handleMouseMove);
494
+ document.removeEventListener('mouseup', handleMouseUp);
495
+ document.removeEventListener('touchmove', handleTouchMove);
496
+ document.removeEventListener('touchend', handleTouchEnd);
497
+ };
498
+ }
499
+ return undefined;
500
+ }, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
501
+ // Save to SDKUI_Globals.userSettings only when NOT in config mode (when applying changes)
502
+ useEffect(() => {
503
+ if (state.isConfigMode)
504
+ return; // Don't save during edit mode
505
+ try {
506
+ // Replace the entire object to trigger the Proxy
507
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
508
+ orientation: state.orientation,
509
+ itemIds: state.items.map(item => item.id),
510
+ position: state.position,
511
+ positionFormat: 'percentage',
512
+ };
513
+ }
514
+ catch (error) {
515
+ console.error('Failed to save FloatingMenuBar config:', error);
516
+ }
517
+ }, [state.orientation, state.items, state.position, state.isConfigMode]);
518
+ const toggleConfigMode = () => {
519
+ setState(s => {
520
+ if (!s.isConfigMode) {
521
+ stateSnapshot.current = {
522
+ items: [...s.items],
523
+ orientation: s.orientation,
524
+ position: { ...s.position },
525
+ };
526
+ return { ...s, isConfigMode: true };
527
+ }
528
+ else {
529
+ // Exiting edit mode (applying changes) - clean up trailing separators and clear snapshot
530
+ stateSnapshot.current = null;
531
+ const cleanedItems = removeTrailingSeparators(s.items);
532
+ return { ...s, isConfigMode: false, items: cleanedItems };
533
+ }
534
+ });
535
+ };
536
+ // Auto-reposition when entering edit mode to ensure Apply/Undo buttons are visible
537
+ useEffect(() => {
538
+ if (!state.isConfigMode || !floatingRef.current)
539
+ return;
540
+ // Use double requestAnimationFrame to ensure the DOM has fully updated with new buttons
541
+ requestAnimationFrame(() => {
542
+ requestAnimationFrame(() => {
543
+ if (!floatingRef.current)
544
+ return;
545
+ const floating = floatingRef.current.getBoundingClientRect();
546
+ const containerWidth = isConstrained && containerRef.current
547
+ ? containerRef.current.getBoundingClientRect().width
548
+ : window.innerWidth;
549
+ const containerHeight = isConstrained && containerRef.current
550
+ ? containerRef.current.getBoundingClientRect().height
551
+ : window.innerHeight;
552
+ // Use current pixel position
553
+ let newPixelX = pixelPosition.x;
554
+ let newPixelY = pixelPosition.y;
555
+ let needsUpdate = false;
556
+ // Check horizontal overflow
557
+ if (newPixelX + floating.width > containerWidth) {
558
+ newPixelX = Math.max(0, containerWidth - floating.width);
559
+ needsUpdate = true;
560
+ }
561
+ // Check vertical overflow
562
+ if (newPixelY + floating.height > containerHeight) {
563
+ newPixelY = Math.max(0, containerHeight - floating.height);
564
+ needsUpdate = true;
565
+ }
566
+ if (needsUpdate) {
567
+ // Update pixel position immediately
568
+ setPixelPosition({ x: newPixelX, y: newPixelY });
569
+ // Convert to percentage for state
570
+ const newPercentagePosition = {
571
+ x: pixelsToPercent(newPixelX, containerWidth),
572
+ y: pixelsToPercent(newPixelY, containerHeight),
573
+ };
574
+ setState(s => ({
575
+ ...s,
576
+ position: newPercentagePosition,
577
+ }));
578
+ // Update snapshot position to the corrected position so Undo restores to visible position
579
+ if (stateSnapshot.current) {
580
+ stateSnapshot.current.position = newPercentagePosition;
581
+ }
582
+ }
583
+ });
584
+ });
585
+ }, [state.isConfigMode, state.orientation, isConstrained, state.items, pixelPosition.x, pixelPosition.y]);
586
+ const handleUndo = () => {
587
+ if (stateSnapshot.current) {
588
+ setState(s => ({
589
+ ...s,
590
+ items: [...stateSnapshot.current.items],
591
+ orientation: stateSnapshot.current.orientation,
592
+ position: { ...stateSnapshot.current.position },
593
+ isConfigMode: true,
594
+ }));
595
+ }
596
+ };
597
+ // Check if current state has changed from snapshot
598
+ const hasChanges = () => {
599
+ if (!stateSnapshot.current)
600
+ return false;
601
+ // Check if items have changed (different IDs or different order)
602
+ const currentIds = state.items.map(i => i.id).join(',');
603
+ const snapshotIds = stateSnapshot.current.items.map(i => i.id).join(',');
604
+ return currentIds !== snapshotIds;
605
+ };
606
+ const toggleOrientation = () => {
607
+ const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
608
+ if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
609
+ if (floatingRef.current) {
610
+ const floating = floatingRef.current.getBoundingClientRect();
611
+ const screenHeight = window.innerHeight;
612
+ if (floating.width > screenHeight - 70) {
613
+ ShowAlert({
614
+ mode: 'warning',
615
+ title: 'Troppi elementi',
616
+ message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
617
+ duration: 4000,
618
+ });
619
+ return;
620
+ }
621
+ }
622
+ }
623
+ setState(s => ({
624
+ ...s,
625
+ orientation: newOrientation,
626
+ }));
627
+ requestAnimationFrame(() => {
628
+ requestAnimationFrame(() => {
629
+ if (containerRef.current && floatingRef.current) {
630
+ const container = containerRef.current.getBoundingClientRect();
631
+ const floating = floatingRef.current.getBoundingClientRect();
632
+ let newX, newY;
633
+ if (isConstrained) {
634
+ // For constrained mode, use container bounds
635
+ newX = Math.max(0, Math.min(state.position.x, container.width - floating.width));
636
+ newY = Math.max(0, Math.min(state.position.y, container.height - floating.height));
637
+ }
638
+ else {
639
+ // For unconstrained mode, use viewport bounds
640
+ newX = Math.max(0, Math.min(state.position.x, window.innerWidth - floating.width));
641
+ newY = Math.max(0, Math.min(state.position.y, window.innerHeight - floating.height));
642
+ }
643
+ // Only update position if it changed (to avoid unnecessary re-render)
644
+ if (newX !== state.position.x || newY !== state.position.y) {
645
+ setState(s => ({
646
+ ...s,
647
+ position: { x: newX, y: newY },
648
+ }));
649
+ }
650
+ }
651
+ });
652
+ });
653
+ };
654
+ const removeItem = (itemId) => {
655
+ setState(s => ({
656
+ ...s,
657
+ items: s.items.filter(item => item.id !== itemId),
658
+ }));
659
+ };
660
+ // Drag and drop for reordering
661
+ const handleDragStart = (e, index) => {
662
+ if (!state.isConfigMode)
663
+ return;
664
+ e.dataTransfer.effectAllowed = 'move';
665
+ e.dataTransfer.setData('text/plain', index.toString());
666
+ setState(s => ({ ...s, draggedItemIndex: index }));
667
+ };
668
+ const handleDragEnter = (e, index) => {
669
+ if (!state.isConfigMode)
670
+ return;
671
+ e.preventDefault();
672
+ if (state.draggedItemIndex !== index) {
673
+ setDragOverIndex(index);
674
+ }
675
+ };
676
+ const handleDragOver = (e) => {
677
+ if (!state.isConfigMode)
678
+ return;
679
+ e.preventDefault();
680
+ e.dataTransfer.dropEffect = 'move';
681
+ };
682
+ const handleDragLeave = (_e, index) => {
683
+ if (!state.isConfigMode)
684
+ return;
685
+ // Only clear if we're actually leaving this specific item
686
+ if (dragOverIndex === index) {
687
+ setDragOverIndex(null);
688
+ }
689
+ };
690
+ const handleDrop = (e, dropIndex) => {
691
+ if (!state.isConfigMode || state.draggedItemIndex === null)
692
+ return;
693
+ e.preventDefault();
694
+ e.stopPropagation();
695
+ const dragIndex = state.draggedItemIndex;
696
+ if (dragIndex === dropIndex) {
697
+ setDragOverIndex(null);
698
+ return;
699
+ }
700
+ const newItems = [...state.items];
701
+ const [draggedItem] = newItems.splice(dragIndex, 1);
702
+ newItems.splice(dropIndex, 0, draggedItem);
703
+ setState(s => ({
704
+ ...s,
705
+ items: newItems,
706
+ draggedItemIndex: null,
707
+ }));
708
+ setDragOverIndex(null);
709
+ };
710
+ const handleDragEnd = () => {
711
+ setState(s => ({ ...s, draggedItemIndex: null }));
712
+ setDragOverIndex(null);
713
+ };
714
+ return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$isDragging": state.isDragging, "$isConfigMode": state.isConfigMode, "$isConstrained": isConstrained, onContextMenu: (e) => e.preventDefault(), children: [!state.isConfigMode ? (_jsx(ContextMenu, { items: [
715
+ {
716
+ name: SDKUI_Localizator.Configure,
717
+ onClick: () => {
718
+ if (!state.isConfigMode) {
719
+ toggleConfigMode();
720
+ }
721
+ },
722
+ },
723
+ {
724
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
725
+ onClick: toggleOrientation,
726
+ },
727
+ ], trigger: "right", children: _jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) }) })) : (_jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) })), _jsx(S.Separator, { "$orientation": state.orientation }), state.items.map((item, index) => {
728
+ // Handle separator items
729
+ if (item.isSeparator) {
730
+ return (_jsxs(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: [_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
731
+ }
732
+ // Get current state (disabled and onClick) from contextMenuItems
733
+ const currentState = getCurrentItemState(item.id);
734
+ const isDisabled = currentState.disabled || false;
735
+ const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
736
+ return (_jsxs(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: state.isConfigMode, onDragStart: (e) => handleDragStart(e, index), onDragEnter: (e) => handleDragEnter(e, index), onDragOver: handleDragOver, onDragLeave: (e) => handleDragLeave(e, index), onDrop: (e) => handleDrop(e, index), onDragEnd: handleDragEnd, children: [_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
737
+ if (state.isConfigMode || isDisabled)
738
+ return;
739
+ if (currentOnClick) {
740
+ currentOnClick();
741
+ }
742
+ }, disabled: state.isConfigMode ? isDisabled && !state.isConfigMode : isDisabled, children: item.icon }) }), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
743
+ }), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: true, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) })), state.isConfigMode && state.items.length < maxItems && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getPinContextMenuItems(), trigger: "left", keepOpenOnClick: true, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Add, children: _jsx(S.AddButton, { children: _jsx(IconAdd, {}) }) }) })), state.isConfigMode && (_jsxs(_Fragment, { children: [_jsx(S.Separator, { "$orientation": state.orientation }), _jsxs(S.ButtonGroup, { "$orientation": state.orientation, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Undo, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.UndoButton, { onClick: handleUndo, disabled: !hasChanges(), children: _jsx(IconUndo, { fontSize: 18 }) }) }), _jsx(TMTooltip, { content: state.items.length === 0 ? 'Devi aggiungere almeno un item' : SDKUI_Localizator.ApplyAndClose, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.ApplyButton, { onClick: toggleConfigMode, disabled: state.items.length === 0, children: _jsx(IconApply, { fontSize: 20 }) }) })] })] }))] })] }));
744
+ };
745
+ export default TMFloatingMenuBar;