@topconsultnpm/sdkui-react 6.20.0-dev1.5 → 6.20.0-dev1.50

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