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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/lib/assets/Toppy-help-center.png +0 -0
  2. package/lib/assets/headergradient.svg +87 -0
  3. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +322 -30
  4. package/lib/components/NewComponents/ContextMenu/hooks.d.ts +8 -1
  5. package/lib/components/NewComponents/ContextMenu/hooks.js +80 -8
  6. package/lib/components/NewComponents/ContextMenu/index.d.ts +3 -0
  7. package/lib/components/NewComponents/ContextMenu/index.js +2 -0
  8. package/lib/components/NewComponents/ContextMenu/styles.d.ts +9 -1
  9. package/lib/components/NewComponents/ContextMenu/styles.js +146 -47
  10. package/lib/components/NewComponents/ContextMenu/types.d.ts +22 -3
  11. package/lib/components/NewComponents/ContextMenu/useLongPress.d.ts +21 -0
  12. package/lib/components/NewComponents/ContextMenu/useLongPress.js +112 -0
  13. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +620 -125
  14. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +25 -5
  15. package/lib/components/NewComponents/FloatingMenuBar/styles.js +215 -59
  16. package/lib/components/NewComponents/FloatingMenuBar/types.d.ts +12 -3
  17. package/lib/components/base/TMAccordionNew.js +35 -14
  18. package/lib/components/base/TMButton.js +6 -0
  19. package/lib/components/base/TMClosableList.js +4 -0
  20. package/lib/components/base/TMCustomButton.js +61 -17
  21. package/lib/components/base/TMDataGrid.d.ts +7 -4
  22. package/lib/components/base/TMDataGrid.js +153 -11
  23. package/lib/components/base/TMDropDownMenu.js +21 -18
  24. package/lib/components/base/TMFileManager.d.ts +4 -3
  25. package/lib/components/base/TMFileManager.js +32 -24
  26. package/lib/components/base/TMFileManagerDataGridView.d.ts +3 -2
  27. package/lib/components/base/TMFileManagerDataGridView.js +1 -11
  28. package/lib/components/base/TMFileManagerThumbnailItems.d.ts +7 -1
  29. package/lib/components/base/TMFileManagerThumbnailItems.js +5 -2
  30. package/lib/components/base/TMFileManagerThumbnailsView.d.ts +17 -4
  31. package/lib/components/base/TMFileManagerThumbnailsView.js +18 -6
  32. package/lib/components/base/TMFileManagerUtils.d.ts +0 -12
  33. package/lib/components/base/TMListView.js +33 -15
  34. package/lib/components/base/TMPanel.d.ts +1 -1
  35. package/lib/components/base/TMPanel.js +4 -2
  36. package/lib/components/base/TMPopUp.js +6 -0
  37. package/lib/components/base/TMToolbarCard.js +2 -0
  38. package/lib/components/base/TMTreeView.d.ts +2 -1
  39. package/lib/components/base/TMTreeView.js +33 -26
  40. package/lib/components/choosers/TMDataListItemChooser.d.ts +2 -0
  41. package/lib/components/choosers/TMDataListItemChooser.js +8 -2
  42. package/lib/components/choosers/TMDcmtTypeChooser.d.ts +1 -0
  43. package/lib/components/choosers/TMDcmtTypeChooser.js +11 -3
  44. package/lib/components/choosers/TMDistinctValues.js +2 -2
  45. package/lib/components/choosers/TMDynDataListItemChooser.d.ts +2 -0
  46. package/lib/components/choosers/TMDynDataListItemChooser.js +8 -2
  47. package/lib/components/choosers/TMInvoiceRetrieveFormats.js +1 -1
  48. package/lib/components/choosers/TMMetadataChooser.d.ts +2 -0
  49. package/lib/components/choosers/TMMetadataChooser.js +19 -4
  50. package/lib/components/choosers/TMOrderRetrieveFormats.js +1 -1
  51. package/lib/components/choosers/TMUserChooser.d.ts +2 -5
  52. package/lib/components/choosers/TMUserChooser.js +33 -47
  53. package/lib/components/editors/TMCheckBox.js +2 -0
  54. package/lib/components/editors/TMDateBox.js +18 -9
  55. package/lib/components/editors/TMEditorStyled.js +7 -0
  56. package/lib/components/editors/TMLocalizedTextBox.d.ts +3 -1
  57. package/lib/components/editors/TMLocalizedTextBox.js +16 -14
  58. package/lib/components/editors/TMMetadataEditor.d.ts +1 -0
  59. package/lib/components/editors/TMMetadataEditor.js +4 -4
  60. package/lib/components/editors/TMMetadataTextBox.d.ts +9 -0
  61. package/lib/components/editors/TMMetadataTextBox.js +92 -0
  62. package/lib/components/editors/TMMetadataValues.d.ts +2 -0
  63. package/lib/components/editors/TMMetadataValues.js +26 -8
  64. package/lib/components/editors/TMRadioButton.js +2 -0
  65. package/lib/components/editors/TMTextArea.js +18 -30
  66. package/lib/components/editors/TMTextBox.d.ts +1 -1
  67. package/lib/components/editors/TMTextBox.js +29 -4
  68. package/lib/components/editors/TMTextExpression.js +6 -91
  69. package/lib/components/features/archive/TMArchive.js +2 -2
  70. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.d.ts +15 -0
  71. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +462 -0
  72. package/lib/components/features/assistant/TMToppySpeechBubble.d.ts +11 -0
  73. package/lib/components/features/assistant/TMToppySpeechBubble.js +126 -0
  74. package/lib/components/features/documents/TMDcmtBlog.js +1 -1
  75. package/lib/components/features/documents/TMDcmtForm.d.ts +14 -2
  76. package/lib/components/features/documents/TMDcmtForm.js +576 -292
  77. package/lib/components/features/documents/TMDcmtPreview.js +42 -155
  78. package/lib/components/features/documents/TMDcmtTasks.js +9 -9
  79. package/lib/components/features/documents/TMMasterDetailDcmts.js +38 -53
  80. package/lib/components/features/documents/TMRelationViewer.d.ts +1 -1
  81. package/lib/components/features/documents/TMRelationViewer.js +2 -2
  82. package/lib/components/features/search/TMDcmtCheckoutInfoForm.d.ts +8 -0
  83. package/lib/components/features/search/{TMSearchResultCheckoutInfoForm.js → TMDcmtCheckoutInfoForm.js} +2 -2
  84. package/lib/components/features/search/TMSavedQuerySelector.js +72 -67
  85. package/lib/components/features/search/TMSearch.d.ts +3 -0
  86. package/lib/components/features/search/TMSearch.js +50 -11
  87. package/lib/components/features/search/TMSearchQueryEditor.d.ts +1 -0
  88. package/lib/components/features/search/TMSearchQueryEditor.js +10 -10
  89. package/lib/components/features/search/TMSearchQueryPanel.d.ts +1 -0
  90. package/lib/components/features/search/TMSearchQueryPanel.js +40 -25
  91. package/lib/components/features/search/TMSearchResult.d.ts +3 -0
  92. package/lib/components/features/search/TMSearchResult.js +370 -252
  93. package/lib/components/features/search/TMSearchResultsMenuItems.d.ts +3 -3
  94. package/lib/components/features/search/TMSearchResultsMenuItems.js +227 -171
  95. package/lib/components/features/search/TMSignSettingsForm.js +1 -1
  96. package/lib/components/features/search/TMSignatureInfoContent.d.ts +6 -0
  97. package/lib/components/features/search/TMSignatureInfoContent.js +140 -0
  98. package/lib/components/features/search/TMViewHistoryDcmt.js +47 -52
  99. package/lib/components/features/tasks/TMTaskForm.js +75 -25
  100. package/lib/components/features/tasks/TMTasksAgenda.d.ts +3 -1
  101. package/lib/components/features/tasks/TMTasksAgenda.js +48 -9
  102. package/lib/components/features/tasks/TMTasksCalendar.d.ts +2 -0
  103. package/lib/components/features/tasks/TMTasksCalendar.js +19 -7
  104. package/lib/components/features/tasks/TMTasksUtils.d.ts +2 -2
  105. package/lib/components/features/tasks/TMTasksUtils.js +57 -37
  106. package/lib/components/features/tasks/TMTasksView.js +28 -19
  107. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +33 -2
  108. package/lib/components/features/workflow/TMWorkflowPopup.js +140 -34
  109. package/lib/components/features/workflow/diagram/DiagramItemComponent.d.ts +2 -0
  110. package/lib/components/features/workflow/diagram/DiagramItemComponent.js +14 -7
  111. package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
  112. package/lib/components/features/workflow/diagram/RecipientList.js +3 -2
  113. package/lib/components/features/workflow/diagram/WFDiagram.d.ts +4 -0
  114. package/lib/components/features/workflow/diagram/WFDiagram.js +164 -13
  115. package/lib/components/forms/Login/LoginValidatorService.d.ts +2 -0
  116. package/lib/components/forms/Login/LoginValidatorService.js +7 -2
  117. package/lib/components/forms/Login/TMLoginForm.js +35 -7
  118. package/lib/components/forms/TMChooserForm.js +1 -1
  119. package/lib/components/grids/TMBlogsPost.js +56 -31
  120. package/lib/components/grids/TMRecentsManager.js +20 -10
  121. package/lib/components/grids/TMValidationItemsList.js +6 -0
  122. package/lib/components/index.d.ts +6 -3
  123. package/lib/components/index.js +6 -3
  124. package/lib/components/layout/panelManager/TMPanelManagerContext.js +13 -5
  125. package/lib/components/query/TMQueryEditor.d.ts +6 -1
  126. package/lib/components/query/TMQueryEditor.js +105 -101
  127. package/lib/components/settings/SettingsAppearance.d.ts +2 -1
  128. package/lib/components/settings/SettingsAppearance.js +99 -30
  129. package/lib/components/sidebar/TMHeader.js +11 -7
  130. package/lib/components/sidebar/TMSidebar.d.ts +0 -1
  131. package/lib/components/sidebar/TMSidebar.js +16 -44
  132. package/lib/components/sidebar/TMSidebarItem.js +36 -17
  133. package/lib/components/viewers/TMDataListItemViewer.d.ts +2 -1
  134. package/lib/components/viewers/TMDataListItemViewer.js +35 -71
  135. package/lib/components/viewers/TMDataUserIdItemViewer.d.ts +8 -0
  136. package/lib/components/viewers/TMDataUserIdItemViewer.js +39 -0
  137. package/lib/css/tm-sdkui.css +1 -1
  138. package/lib/helper/SDKUI_Globals.d.ts +22 -0
  139. package/lib/helper/SDKUI_Globals.js +10 -1
  140. package/lib/helper/SDKUI_Localizator.d.ts +21 -3
  141. package/lib/helper/SDKUI_Localizator.js +196 -10
  142. package/lib/helper/TMCommandsContextMenu.d.ts +4 -2
  143. package/lib/helper/TMCommandsContextMenu.js +15 -4
  144. package/lib/helper/TMIcons.d.ts +4 -0
  145. package/lib/helper/TMIcons.js +13 -3
  146. package/lib/helper/TMPdfViewer.d.ts +8 -0
  147. package/lib/helper/TMPdfViewer.js +373 -0
  148. package/lib/helper/TMToppyMessage.js +4 -0
  149. package/lib/helper/checkinCheckoutManager.d.ts +31 -1
  150. package/lib/helper/checkinCheckoutManager.js +112 -30
  151. package/lib/helper/devextremeCustomMessages.d.ts +30 -0
  152. package/lib/helper/devextremeCustomMessages.js +30 -0
  153. package/lib/helper/helpers.d.ts +30 -2
  154. package/lib/helper/helpers.js +132 -4
  155. package/lib/helper/index.d.ts +2 -0
  156. package/lib/helper/index.js +2 -0
  157. package/lib/helper/queryHelper.d.ts +2 -2
  158. package/lib/helper/queryHelper.js +80 -24
  159. package/lib/helper/workItemsHelper.d.ts +6 -0
  160. package/lib/helper/workItemsHelper.js +230 -0
  161. package/lib/hooks/useCheckInOutOperations.d.ts +28 -0
  162. package/lib/hooks/useCheckInOutOperations.js +223 -0
  163. package/lib/hooks/useDataListItem.d.ts +12 -0
  164. package/lib/hooks/useDataListItem.js +132 -0
  165. package/lib/hooks/useDataUserIdItem.d.ts +10 -0
  166. package/lib/hooks/useDataUserIdItem.js +96 -0
  167. package/lib/hooks/useFloatingBarPinnedItems.d.ts +11 -0
  168. package/lib/hooks/useFloatingBarPinnedItems.js +54 -0
  169. package/lib/hooks/useMetadataExpression.d.ts +19 -0
  170. package/lib/hooks/useMetadataExpression.js +99 -0
  171. package/lib/hooks/useSettingsFeedback.d.ts +11 -0
  172. package/lib/hooks/useSettingsFeedback.js +38 -0
  173. package/lib/hooks/useWorkflowApprove.d.ts +4 -0
  174. package/lib/hooks/useWorkflowApprove.js +14 -1
  175. package/lib/index.d.ts +1 -0
  176. package/lib/index.js +3 -2
  177. package/lib/services/platform_services.d.ts +3 -3
  178. package/lib/ts/types.d.ts +61 -1
  179. package/lib/utils/theme.d.ts +1 -1
  180. package/lib/utils/theme.js +1 -1
  181. package/package.json +6 -5
  182. package/lib/components/NewComponents/Notification/Notification.d.ts +0 -4
  183. package/lib/components/NewComponents/Notification/Notification.js +0 -60
  184. package/lib/components/NewComponents/Notification/NotificationContainer.d.ts +0 -8
  185. package/lib/components/NewComponents/Notification/NotificationContainer.js +0 -33
  186. package/lib/components/NewComponents/Notification/index.d.ts +0 -2
  187. package/lib/components/NewComponents/Notification/index.js +0 -2
  188. package/lib/components/NewComponents/Notification/styles.d.ts +0 -21
  189. package/lib/components/NewComponents/Notification/styles.js +0 -180
  190. package/lib/components/NewComponents/Notification/types.d.ts +0 -18
  191. package/lib/components/NewComponents/Notification/types.js +0 -1
  192. package/lib/components/base/TMContextMenu.d.ts +0 -25
  193. package/lib/components/base/TMContextMenu.js +0 -109
  194. package/lib/components/base/TMContextMenuOLD.d.ts +0 -26
  195. package/lib/components/base/TMContextMenuOLD.js +0 -56
  196. package/lib/components/base/TMFloatingToolbar.d.ts +0 -9
  197. package/lib/components/base/TMFloatingToolbar.js +0 -101
  198. package/lib/components/features/assistant/ToppyDraggableHelpCenter.d.ts +0 -30
  199. package/lib/components/features/assistant/ToppyDraggableHelpCenter.js +0 -482
  200. package/lib/components/features/assistant/ToppySpeechBubble.d.ts +0 -9
  201. package/lib/components/features/assistant/ToppySpeechBubble.js +0 -117
  202. package/lib/components/features/search/TMSearchResultCheckoutInfoForm.d.ts +0 -8
@@ -1,70 +1,201 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useState, useRef, useEffect, useCallback } from 'react';
3
3
  import { ContextMenu } from '../ContextMenu';
4
- import Notification from '../Notification';
4
+ import ShowAlert from '../../base/TMAlert';
5
5
  import TMTooltip from '../../base/TMTooltip';
6
6
  import * as S from './styles';
7
- import { IconApply, IconMenuKebab, IconMenuVertical, IconSettings, IconStar } from '../../../helper';
7
+ import { IconDelete, IconMenuVertical, IconPin, IconRotate, IconSeparator, SDKUI_Globals } 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" }) }));
8
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" }) }));
9
- const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = 'floatingMenuBar-config', isConstrained = false, defaultPosition = { x: 100, y: 100 }, maxItems = 8, }) => {
10
+ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 1, y: 90 }, defaultOrientation = 'horizontal', maxItems = 100, defaultPinnedItems = [], fixedItems = [], enableConfigMode = true, bgColor = undefined, hasContextMenu = true, pinnedItemIds: externalPinnedItemIds, onPinChange, }) => {
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: defaultOrientation,
31
+ savedItemIds: defaultPinnedItems,
32
+ position: defaultPosition,
33
+ });
34
+ const resetFloatingBarSettings = () => {
35
+ // Reset the floatingMenuBar settings in SDKUI_Globals to empty
36
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
37
+ orientation: undefined,
38
+ itemIds: undefined,
39
+ position: undefined,
40
+ };
41
+ };
10
42
  const loadConfig = () => {
43
+ // If config mode is disabled, use default position and orientation
44
+ if (!enableConfigMode) {
45
+ return {
46
+ orientation: defaultOrientation,
47
+ savedItemIds: defaultPinnedItems,
48
+ position: defaultPosition,
49
+ };
50
+ }
11
51
  try {
12
- const saved = localStorage.getItem(storageKey);
13
- if (saved) {
14
- const config = JSON.parse(saved);
15
- return {
16
- orientation: config.orientation || 'horizontal',
17
- pinnedItemIds: new Set(config.pinnedItemIds || []),
18
- savedItemIds: config.itemIds || [],
52
+ const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
53
+ // If localStorage is empty (first time) or itemIds is empty array, use props as defaults
54
+ if (!settings || !settings.position || !settings.itemIds || settings.itemIds.length === 0) {
55
+ return getDefaultConfig();
56
+ }
57
+ // Validate position
58
+ const hasValidPosition = settings.position &&
59
+ typeof settings.position.x === 'number' &&
60
+ typeof settings.position.y === 'number' &&
61
+ !Number.isNaN(settings.position.x) &&
62
+ !Number.isNaN(settings.position.y) &&
63
+ Number.isFinite(settings.position.x) &&
64
+ Number.isFinite(settings.position.y);
65
+ if (!hasValidPosition) {
66
+ console.warn('FloatingMenuBar: Invalid position, resetting to defaults');
67
+ resetFloatingBarSettings();
68
+ return getDefaultConfig();
69
+ }
70
+ // Ensure position is within reasonable viewport bounds
71
+ const maxX = globalThis.window?.innerWidth ? globalThis.window.innerWidth - 50 : 1000;
72
+ const maxY = globalThis.window?.innerHeight ? globalThis.window.innerHeight - 50 : 800;
73
+ if (settings.position.x < 0 || settings.position.x > maxX ||
74
+ settings.position.y < 0 || settings.position.y > maxY) {
75
+ console.warn('FloatingMenuBar: Position out of bounds, resetting to defaults');
76
+ resetFloatingBarSettings();
77
+ return getDefaultConfig();
78
+ }
79
+ // Validate orientation
80
+ const validOrientation = (settings.orientation === 'horizontal' || settings.orientation === 'vertical')
81
+ ? settings.orientation
82
+ : 'horizontal';
83
+ // Validate itemIds
84
+ const validItemIds = Array.isArray(settings.itemIds) ? settings.itemIds : [];
85
+ if (validItemIds.length > 0) {
86
+ // Check if any ID looks like the old format (contains language-specific text or is too long)
87
+ const hasOldFormatIds = validItemIds.some(id => typeof id === 'string' &&
88
+ !id.startsWith('separator-') &&
89
+ (id.length > 20 || id.includes(' ')));
90
+ if (hasOldFormatIds) {
91
+ console.warn('FloatingMenuBar: Detected old name-based configuration, resetting to defaults');
92
+ resetFloatingBarSettings();
93
+ return getDefaultConfig();
94
+ }
95
+ }
96
+ // Migrate old pixel-based position to percentage-based
97
+ let finalPosition = settings.position;
98
+ if (isPixelFormat(settings.position) || settings.positionFormat === 'pixels') {
99
+ console.log('FloatingMenuBar: Migrating pixel-based position to percentage-based');
100
+ finalPosition = migrateToPercentage(settings.position);
101
+ // Save migrated position immediately
102
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
103
+ orientation: validOrientation,
104
+ itemIds: validItemIds,
105
+ position: finalPosition,
106
+ positionFormat: 'percentage',
19
107
  };
20
108
  }
109
+ return {
110
+ orientation: validOrientation,
111
+ savedItemIds: validItemIds,
112
+ position: finalPosition,
113
+ };
21
114
  }
22
115
  catch (error) {
23
116
  console.error('Failed to load FloatingMenuBar config:', error);
117
+ // Reset to defaults on any error
118
+ try {
119
+ resetFloatingBarSettings();
120
+ }
121
+ catch (e) {
122
+ console.error('Failed to reset FloatingMenuBar settings:', e);
123
+ }
124
+ return getDefaultConfig();
24
125
  }
25
- return {
26
- orientation: 'horizontal',
27
- pinnedItemIds: new Set(),
28
- savedItemIds: [],
29
- };
30
126
  };
31
127
  const initialConfig = loadConfig();
32
128
  const [state, setState] = useState({
33
- position: defaultPosition,
129
+ position: initialConfig.position, // Stored as percentage
34
130
  isDragging: false,
35
- isConfigMode: false,
36
131
  orientation: initialConfig.orientation,
132
+ verticalDirection: 'down',
37
133
  items: [],
38
134
  draggedItemIndex: null,
39
135
  });
40
136
  const floatingRef = useRef(null);
41
137
  const dragOffset = useRef({ x: 0, y: 0 });
42
- const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
43
138
  const [dragOverIndex, setDragOverIndex] = useState(null);
44
- const [showMaxItemsNotification, setShowMaxItemsNotification] = useState(false);
45
- // Use refs to track item IDs without causing re-renders
139
+ const [pixelPosition, setPixelPosition] = useState({ x: 0, y: 0 }); // Calculated pixel position
140
+ const [isOrientationChanging, setIsOrientationChanging] = useState(false); // Hide bar during orientation transition
141
+ const containerSizeRef = useRef({ width: 0, height: 0 });
142
+ const positionBeforeOrientationChange = useRef(null);
46
143
  const floatingBarItemIds = useRef(new Set());
47
144
  const floatingBarItemNames = useRef(new Set());
48
- // Update refs when items change, but don't trigger re-renders
145
+ const isSyncingFromExternal = useRef(false);
146
+ const isLocalChange = useRef(false);
147
+ const dragStartPosition = useRef(null);
148
+ const isItemsInitialized = useRef(false);
49
149
  useEffect(() => {
50
150
  floatingBarItemIds.current = new Set(state.items.map(i => i.id));
51
151
  floatingBarItemNames.current = new Set(state.items.map(i => i.name));
52
152
  }, [state.items]);
53
- // Convert menu items to flat list with pinned status
153
+ // Calculate pixel position from percentage when container size or position changes
154
+ useEffect(() => {
155
+ const updatePixelPosition = () => {
156
+ if (!containerRef.current || !floatingRef.current)
157
+ return;
158
+ const container = containerRef.current.getBoundingClientRect();
159
+ const floating = floatingRef.current.getBoundingClientRect();
160
+ const containerWidth = isConstrained ? container.width : window.innerWidth;
161
+ const containerHeight = isConstrained ? container.height : window.innerHeight;
162
+ containerSizeRef.current = { width: containerWidth, height: containerHeight };
163
+ let newX = percentToPixels(state.position.x, containerWidth);
164
+ let newY = percentToPixels(state.position.y, containerHeight);
165
+ newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
166
+ newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
167
+ setPixelPosition({ x: newX, y: newY });
168
+ };
169
+ updatePixelPosition();
170
+ const resizeObserver = new ResizeObserver(() => {
171
+ updatePixelPosition();
172
+ });
173
+ if (containerRef.current) {
174
+ resizeObserver.observe(containerRef.current);
175
+ }
176
+ if (!isConstrained) {
177
+ window.addEventListener('resize', updatePixelPosition);
178
+ }
179
+ return () => {
180
+ resizeObserver.disconnect();
181
+ if (!isConstrained) {
182
+ window.removeEventListener('resize', updatePixelPosition);
183
+ }
184
+ };
185
+ }, [state.position, isConstrained]);
54
186
  const flattenMenuItems = useCallback((items, parentPath = '') => {
55
187
  const result = [];
56
188
  items.forEach((item, index) => {
57
- const itemId = `${parentPath}${item.name}-${index}`;
58
- // Only add items that have onClick (final actions, not submenu parents)
189
+ const itemId = item.id || `${parentPath}${item.name}-${index}`;
59
190
  if (item.onClick && !item.submenu) {
191
+ const isPinned = state.items.some(i => i.id === itemId);
60
192
  result.push({
61
193
  id: itemId,
62
194
  name: item.name,
63
- icon: item.icon || _jsx(IconStar, {}),
195
+ icon: item.icon || _jsx(IconPin, {}),
64
196
  onClick: item.onClick,
65
197
  disabled: item.disabled,
66
- isPinned: pinnedItemIds.has(itemId),
67
- originalMenuItem: item,
198
+ isPinned: isPinned,
68
199
  });
69
200
  }
70
201
  // Recursively process submenus
@@ -73,56 +204,126 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
73
204
  }
74
205
  });
75
206
  return result;
76
- }, [pinnedItemIds]);
77
- // Restore items on mount from savedItemIds
207
+ }, [state.items]);
208
+ // Restore items on mount (and when contextMenuItems become available) from savedItemIds
78
209
  useEffect(() => {
79
- if (contextMenuItems.length > 0) {
80
- const flatItems = flattenMenuItems(contextMenuItems);
210
+ // For enableConfigMode=false: always sync with fixedItems
211
+ if (!enableConfigMode) {
212
+ if (fixedItems.length > 0) {
213
+ setState(s => ({ ...s, items: fixedItems }));
214
+ }
215
+ return;
216
+ }
217
+ // For enableConfigMode=true: only initialize once
218
+ if (isItemsInitialized.current)
219
+ return;
220
+ // Wait until contextMenuItems is populated
221
+ if (contextMenuItems.length === 0)
222
+ return;
223
+ const flatItems = flattenMenuItems(contextMenuItems);
224
+ if (flatItems.length === 0)
225
+ return;
226
+ let itemsToSet = [];
227
+ if (initialConfig.savedItemIds.length > 0) {
81
228
  // Restore items in the saved order from localStorage
82
229
  const restoredItems = initialConfig.savedItemIds
230
+ .map((id) => {
231
+ if (id.startsWith('separator-')) {
232
+ return {
233
+ id,
234
+ name: 'Separator',
235
+ icon: _jsx(Separator, {}),
236
+ onClick: () => { },
237
+ isSeparator: true,
238
+ };
239
+ }
240
+ return flatItems.find(item => item.id === id);
241
+ })
242
+ .filter((item) => item !== undefined);
243
+ itemsToSet = restoredItems;
244
+ }
245
+ // If still empty, try defaultPinnedItems
246
+ if (itemsToSet.length === 0 && defaultPinnedItems.length > 0) {
247
+ const resolvedDefaultItems = defaultPinnedItems
83
248
  .map((id) => flatItems.find(item => item.id === id))
84
249
  .filter((item) => item !== undefined);
85
- if (restoredItems.length > 0) {
86
- setState(s => ({ ...s, items: restoredItems }));
250
+ itemsToSet = resolvedDefaultItems;
251
+ }
252
+ if (itemsToSet.length > 0) {
253
+ isItemsInitialized.current = true;
254
+ setState(s => ({ ...s, items: itemsToSet }));
255
+ }
256
+ // eslint-disable-next-line react-hooks/exhaustive-deps
257
+ }, [contextMenuItems, fixedItems, enableConfigMode]);
258
+ // Sync with external pinnedItemIds when they change (from parent component)
259
+ useEffect(() => {
260
+ // Skip sync if a local change was just made (e.g. right-click remove/add separator)
261
+ if (isLocalChange.current) {
262
+ isLocalChange.current = false;
263
+ return;
264
+ }
265
+ if (externalPinnedItemIds === undefined || !enableConfigMode)
266
+ return;
267
+ // Skip sync if external is empty but we have initialized items (first login case)
268
+ // The parent hook starts with [] but we've initialized with defaults
269
+ if (externalPinnedItemIds.length === 0 && state.items.length > 0)
270
+ return;
271
+ const flatItems = flattenMenuItems(contextMenuItems);
272
+ // Build items from external pinned IDs
273
+ const itemsFromExternal = externalPinnedItemIds
274
+ .map((id) => {
275
+ if (id.startsWith('separator-')) {
276
+ return {
277
+ id,
278
+ name: 'Separator',
279
+ icon: _jsx(Separator, {}),
280
+ onClick: () => { },
281
+ isSeparator: true,
282
+ };
87
283
  }
284
+ return flatItems.find(item => item.id === id);
285
+ })
286
+ .filter((item) => item !== undefined);
287
+ // Only update if different
288
+ const currentIds = state.items.map(i => i.id).join(',');
289
+ const newIds = itemsFromExternal.map(i => i.id).join(',');
290
+ if (currentIds !== newIds) {
291
+ isSyncingFromExternal.current = true; // Mark that we're syncing from external
292
+ setState(s => ({ ...s, items: itemsFromExternal }));
88
293
  }
89
- }, []); // Only run once on mount
294
+ }, [externalPinnedItemIds, contextMenuItems, enableConfigMode, flattenMenuItems]);
90
295
  const togglePin = useCallback((item) => {
91
296
  setState(s => {
92
- const isInFloatingBar = s.items.some(i => i.id === item.id || i.name === item.name);
297
+ const isInFloatingBar = s.items.some(i => i.id === item.id);
93
298
  if (isInFloatingBar) {
94
299
  // Remove from floating bar
95
- const newItems = s.items.filter(i => i.id !== item.id && i.name !== item.name);
300
+ const newItems = s.items.filter(i => i.id !== item.id);
96
301
  return { ...s, items: newItems };
97
302
  }
98
303
  else {
99
304
  // Add to floating bar
100
305
  if (s.items.length >= maxItems) {
101
- setShowMaxItemsNotification(true);
102
- setTimeout(() => setShowMaxItemsNotification(false), 3000);
306
+ ShowAlert({
307
+ mode: 'warning',
308
+ title: 'Limite Massimo Raggiunto',
309
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
310
+ duration: 4000,
311
+ });
103
312
  return s;
104
313
  }
105
314
  return { ...s, items: [...s.items, item] };
106
315
  }
107
316
  });
108
- // Update pinned IDs for context menu items
109
- setPinnedItemIds(prev => {
110
- const newSet = new Set(prev);
111
- if (newSet.has(item.id)) {
112
- newSet.delete(item.id);
113
- }
114
- else {
115
- newSet.add(item.id);
116
- }
117
- return newSet;
118
- });
119
317
  }, [maxItems]);
120
318
  // Get current item state (disabled and onClick) from contextMenuItems
121
- const getCurrentItemState = useCallback((itemName) => {
319
+ const getCurrentItemState = useCallback((itemId) => {
122
320
  const findInItems = (items) => {
123
- for (const item of items) {
124
- if (item.name === itemName)
321
+ for (let i = 0; i < items.length; i++) {
322
+ const item = items[i];
323
+ // Match by ID if the item has one
324
+ if (item.id === itemId)
125
325
  return item;
326
+ // Check in submenu
126
327
  if (item.submenu) {
127
328
  const found = findInItems(item.submenu);
128
329
  if (found)
@@ -136,58 +337,92 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
136
337
  disabled: foundItem?.disabled,
137
338
  onClick: foundItem?.onClick
138
339
  };
139
- }, [contextMenuItems]); // Enhanced context menu items with pin functionality
140
- const enhancedContextMenuItems = useCallback(() => {
340
+ }, [contextMenuItems]);
341
+ // Remove trailing separators from items array
342
+ const removeTrailingSeparators = useCallback((items) => {
343
+ const result = [...items];
344
+ while (result.length > 0 && result.at(-1)?.isSeparator) {
345
+ result.pop();
346
+ }
347
+ return result;
348
+ }, []);
349
+ // Create a new separator item
350
+ const createSeparator = useCallback(() => {
351
+ const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
352
+ return {
353
+ id: separatorId,
354
+ name: 'Separator',
355
+ icon: _jsx(Separator, {}),
356
+ onClick: () => { },
357
+ isSeparator: true,
358
+ };
359
+ }, []);
360
+ // Add separator to items
361
+ const addSeparator = useCallback(() => {
362
+ if (state.items.length >= maxItems) {
363
+ ShowAlert({
364
+ mode: 'warning',
365
+ title: 'Limite Massimo Raggiunto',
366
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
367
+ duration: 4000,
368
+ });
369
+ return;
370
+ }
371
+ const separator = createSeparator();
372
+ setState(s => ({ ...s, items: [...s.items, separator] }));
373
+ }, [state.items.length, maxItems, createSeparator]);
374
+ const getContextMenuItemsWithPinIcons = useCallback(() => {
141
375
  const flatItems = flattenMenuItems(contextMenuItems);
142
- // Calculate current pinned items directly from state.items (not refs)
143
376
  const currentItemIds = new Set(state.items.map(i => i.id));
144
- const currentItemNames = new Set(state.items.map(i => i.name));
145
- const enhanceItems = (items) => {
377
+ const addPinIcons = (items) => {
146
378
  return items.map(item => {
147
- const flatItem = flatItems.find(fi => fi.name === item.name);
148
- const itemId = flatItem?.id || '';
149
- // Check if item is in the floating bar using current state
150
- const isInFloatingBar = currentItemIds.has(itemId) || currentItemNames.has(item.name);
151
- const enhanced = {
379
+ const flatItem = flatItems.find(fi => fi.id === item.id);
380
+ const itemId = flatItem?.id || item.id || '';
381
+ const isPinned = currentItemIds.has(itemId);
382
+ const itemWithPin = {
152
383
  ...item,
153
- rightIcon: item.onClick && !item.submenu ? (isInFloatingBar ? _jsx(IconStar, { color: "#FFD700" }) : _jsx(IconStar, {})) : undefined,
154
- onRightIconClick: item.onClick && !item.submenu ? () => {
155
- if (flatItem) {
156
- togglePin(flatItem);
157
- }
384
+ rightIconProps: flatItem ? {
385
+ icon: _jsx(IconPin, {}),
386
+ activeColor: 'red',
387
+ inactiveColor: 'black',
388
+ isActive: isPinned,
389
+ onClick: () => togglePin(flatItem),
158
390
  } : undefined,
159
391
  };
160
392
  if (item.submenu) {
161
- enhanced.submenu = enhanceItems(item.submenu);
393
+ itemWithPin.submenu = addPinIcons(item.submenu);
162
394
  }
163
- return enhanced;
395
+ return itemWithPin;
164
396
  });
165
397
  };
166
- return enhanceItems(contextMenuItems);
398
+ return addPinIcons(contextMenuItems);
167
399
  }, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
168
400
  const handleMouseDown = (e) => {
169
- if (state.isConfigMode)
170
- return;
171
401
  const containerRect = containerRef.current?.getBoundingClientRect();
172
402
  if (containerRect) {
173
403
  // Calculate drag offset based on positioning mode
174
404
  if (isConstrained) {
175
405
  // For absolute positioning, offset is relative to container
176
406
  dragOffset.current = {
177
- x: e.clientX - containerRect.left - state.position.x,
178
- y: e.clientY - containerRect.top - state.position.y,
407
+ x: e.clientX - containerRect.left - pixelPosition.x,
408
+ y: e.clientY - containerRect.top - pixelPosition.y,
179
409
  };
180
410
  }
181
411
  else {
182
412
  // For fixed positioning, offset is relative to viewport
183
413
  dragOffset.current = {
184
- x: e.clientX - state.position.x,
185
- y: e.clientY - state.position.y,
414
+ x: e.clientX - pixelPosition.x,
415
+ y: e.clientY - pixelPosition.y,
186
416
  };
187
417
  }
188
418
  }
419
+ // Save starting position to detect if user actually moved the bar
420
+ dragStartPosition.current = { ...pixelPosition };
189
421
  setState(s => ({ ...s, isDragging: true }));
190
422
  };
423
+ const handleGripDoubleClick = () => {
424
+ toggleOrientation();
425
+ };
191
426
  const handleMouseMove = useCallback((e) => {
192
427
  if (!state.isDragging || !containerRef.current || !floatingRef.current)
193
428
  return;
@@ -210,50 +445,230 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
210
445
  newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
211
446
  newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
212
447
  }
213
- setState(s => ({
214
- ...s,
215
- position: { x: newX, y: newY },
216
- }));
448
+ // Update pixel position directly during drag
449
+ setPixelPosition({ x: newX, y: newY });
217
450
  }, [state.isDragging, containerRef, isConstrained]);
218
451
  const handleMouseUp = useCallback(() => {
219
- setState(s => ({ ...s, isDragging: false }));
220
- }, []);
452
+ if (state.isDragging && containerSizeRef.current.width > 0) {
453
+ // Convert final pixel position to percentage before updating state
454
+ const percentagePosition = {
455
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
456
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
457
+ };
458
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
459
+ if (dragStartPosition.current) {
460
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
461
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
462
+ if (dx > 5 || dy > 5) {
463
+ positionBeforeOrientationChange.current = null;
464
+ }
465
+ }
466
+ dragStartPosition.current = null;
467
+ setState(s => ({
468
+ ...s,
469
+ isDragging: false,
470
+ position: percentagePosition,
471
+ }));
472
+ }
473
+ else {
474
+ setState(s => ({ ...s, isDragging: false }));
475
+ }
476
+ }, [state.isDragging, pixelPosition]);
477
+ // Touch event handlers for tablet support
478
+ const handleTouchStart = (e) => {
479
+ const touch = e.touches[0];
480
+ const containerRect = containerRef.current?.getBoundingClientRect();
481
+ if (containerRect) {
482
+ if (isConstrained) {
483
+ dragOffset.current = {
484
+ x: touch.clientX - containerRect.left - pixelPosition.x,
485
+ y: touch.clientY - containerRect.top - pixelPosition.y,
486
+ };
487
+ }
488
+ else {
489
+ dragOffset.current = {
490
+ x: touch.clientX - pixelPosition.x,
491
+ y: touch.clientY - pixelPosition.y,
492
+ };
493
+ }
494
+ }
495
+ // Save starting position to detect if user actually moved the bar
496
+ dragStartPosition.current = { ...pixelPosition };
497
+ setState(s => ({ ...s, isDragging: true }));
498
+ };
499
+ const handleTouchMove = useCallback((e) => {
500
+ if (!state.isDragging || !containerRef.current || !floatingRef.current)
501
+ return;
502
+ const touch = e.touches[0];
503
+ const container = containerRef.current.getBoundingClientRect();
504
+ const floating = floatingRef.current.getBoundingClientRect();
505
+ let newX, newY;
506
+ if (isConstrained) {
507
+ newX = touch.clientX - container.left - dragOffset.current.x;
508
+ newY = touch.clientY - container.top - dragOffset.current.y;
509
+ newX = Math.max(0, Math.min(newX, container.width - floating.width));
510
+ newY = Math.max(0, Math.min(newY, container.height - floating.height));
511
+ }
512
+ else {
513
+ newX = touch.clientX - dragOffset.current.x;
514
+ newY = touch.clientY - dragOffset.current.y;
515
+ newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
516
+ newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
517
+ }
518
+ setPixelPosition({ x: newX, y: newY });
519
+ }, [state.isDragging, containerRef, isConstrained]);
520
+ const handleTouchEnd = useCallback(() => {
521
+ if (state.isDragging && containerSizeRef.current.width > 0) {
522
+ const percentagePosition = {
523
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
524
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
525
+ };
526
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
527
+ if (dragStartPosition.current) {
528
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
529
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
530
+ if (dx > 5 || dy > 5) {
531
+ positionBeforeOrientationChange.current = null;
532
+ }
533
+ }
534
+ dragStartPosition.current = null;
535
+ setState(s => ({
536
+ ...s,
537
+ isDragging: false,
538
+ position: percentagePosition,
539
+ }));
540
+ }
541
+ else {
542
+ setState(s => ({ ...s, isDragging: false }));
543
+ }
544
+ }, [state.isDragging, pixelPosition]);
221
545
  useEffect(() => {
222
546
  if (state.isDragging) {
223
547
  document.addEventListener('mousemove', handleMouseMove);
224
548
  document.addEventListener('mouseup', handleMouseUp);
549
+ document.addEventListener('touchmove', handleTouchMove);
550
+ document.addEventListener('touchend', handleTouchEnd);
225
551
  return () => {
226
552
  document.removeEventListener('mousemove', handleMouseMove);
227
553
  document.removeEventListener('mouseup', handleMouseUp);
554
+ document.removeEventListener('touchmove', handleTouchMove);
555
+ document.removeEventListener('touchend', handleTouchEnd);
228
556
  };
229
557
  }
230
558
  return undefined;
231
- }, [state.isDragging, handleMouseMove, handleMouseUp]);
232
- // Save to localStorage whenever config changes (excluding position)
559
+ }, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
560
+ // Save to SDKUI_Globals.userSettings
233
561
  useEffect(() => {
562
+ if (!enableConfigMode)
563
+ return; // Don't save if config mode is disabled
564
+ const pinnedIds = state.items.map(item => item.id);
234
565
  try {
235
- const config = {
566
+ // Replace the entire object to trigger the Proxy
567
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
236
568
  orientation: state.orientation,
237
- pinnedItemIds: Array.from(pinnedItemIds),
238
- itemIds: state.items.map(item => item.id), // Save only IDs, not functions
569
+ itemIds: pinnedIds,
570
+ position: state.position,
571
+ positionFormat: 'percentage',
239
572
  };
240
- localStorage.setItem(storageKey, JSON.stringify(config));
241
573
  }
242
574
  catch (error) {
243
575
  console.error('Failed to save FloatingMenuBar config:', error);
244
576
  }
245
- }, [state.orientation, state.items, pinnedItemIds, storageKey]);
246
- const toggleConfigMode = () => {
247
- setState(s => ({ ...s, isConfigMode: !s.isConfigMode }));
248
- };
577
+ // Notify parent about pin changes only if not syncing from external
578
+ // This prevents infinite loop: external change -> sync -> save -> onPinChange -> external change
579
+ if (!isSyncingFromExternal.current) {
580
+ onPinChange?.(pinnedIds);
581
+ }
582
+ isSyncingFromExternal.current = false; // Reset the flag
583
+ }, [state.orientation, state.items, state.position, enableConfigMode, onPinChange]);
249
584
  const toggleOrientation = () => {
250
585
  const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
251
- // First, change the orientation
586
+ // When switching from vertical back to horizontal, restore the original position
587
+ // Use visibility hiding only for this case to prevent flicker during position restoration
588
+ if (state.orientation === 'vertical' && newOrientation === 'horizontal') {
589
+ if (positionBeforeOrientationChange.current) {
590
+ setIsOrientationChanging(true); // Hide only when restoring position
591
+ const savedPosition = positionBeforeOrientationChange.current.position;
592
+ const savedPixelPosition = positionBeforeOrientationChange.current.pixelPosition;
593
+ setPixelPosition(savedPixelPosition);
594
+ setState(s => ({
595
+ ...s,
596
+ orientation: newOrientation,
597
+ verticalDirection: 'down',
598
+ position: savedPosition,
599
+ }));
600
+ positionBeforeOrientationChange.current = null;
601
+ // Show the bar after the state has been applied
602
+ requestAnimationFrame(() => {
603
+ setIsOrientationChanging(false);
604
+ });
605
+ return;
606
+ }
607
+ }
608
+ // When switching to vertical, save current position and check if we need to expand upward
609
+ // Use opacity hiding (doesn't affect focus unlike visibility:hidden)
610
+ if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
611
+ setIsOrientationChanging(true);
612
+ // Save the current position before changing orientation
613
+ positionBeforeOrientationChange.current = {
614
+ position: { ...state.position },
615
+ pixelPosition: { ...pixelPosition },
616
+ };
617
+ if (floatingRef.current) {
618
+ const floating = floatingRef.current.getBoundingClientRect();
619
+ const containerHeight = isConstrained && containerRef.current
620
+ ? containerRef.current.getBoundingClientRect().height
621
+ : window.innerHeight;
622
+ // Estimate vertical height (horizontal width becomes vertical height)
623
+ const estimatedVerticalHeight = floating.width;
624
+ if (estimatedVerticalHeight > containerHeight - 70) {
625
+ ShowAlert({
626
+ mode: 'warning',
627
+ title: 'Troppi elementi',
628
+ message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
629
+ duration: 4000,
630
+ });
631
+ positionBeforeOrientationChange.current = null; // Clear saved position since we're not changing
632
+ setIsOrientationChanging(false); // Show bar again since we're not changing
633
+ return;
634
+ }
635
+ // Check if we're in the bottom part of the screen and don't have enough space below
636
+ const spaceBelow = containerHeight - floating.bottom;
637
+ const spaceAbove = floating.top;
638
+ const needsVerticalSpace = estimatedVerticalHeight - floating.height; // Additional space needed
639
+ // If not enough space below but enough space above, expand upward
640
+ if (spaceBelow < needsVerticalSpace && spaceAbove >= needsVerticalSpace) {
641
+ // Calculate the new Y position so the bottom of the bar stays at the same place
642
+ const currentBottom = pixelPosition.y + floating.height;
643
+ const newPixelY = currentBottom - estimatedVerticalHeight;
644
+ setState(s => ({
645
+ ...s,
646
+ orientation: newOrientation,
647
+ verticalDirection: 'up',
648
+ }));
649
+ // Update pixel position and percentage after orientation change
650
+ requestAnimationFrame(() => {
651
+ requestAnimationFrame(() => {
652
+ const newY = Math.max(0, newPixelY);
653
+ setPixelPosition(prev => ({ ...prev, y: newY }));
654
+ const newPercentY = pixelsToPercent(newY, containerHeight);
655
+ setState(s => ({
656
+ ...s,
657
+ position: { ...s.position, y: newPercentY },
658
+ }));
659
+ setIsOrientationChanging(false);
660
+ });
661
+ });
662
+ return;
663
+ }
664
+ }
665
+ }
666
+ // Default case: just change orientation without special positioning
252
667
  setState(s => ({
253
668
  ...s,
254
669
  orientation: newOrientation,
670
+ verticalDirection: 'down',
255
671
  }));
256
- // Then, after DOM updates, adjust position to stay in bounds
257
672
  requestAnimationFrame(() => {
258
673
  requestAnimationFrame(() => {
259
674
  if (containerRef.current && floatingRef.current) {
@@ -278,31 +693,97 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
278
693
  }));
279
694
  }
280
695
  }
696
+ setIsOrientationChanging(false);
281
697
  });
282
698
  });
283
699
  };
284
700
  const removeItem = (itemId) => {
701
+ isLocalChange.current = true;
285
702
  setState(s => ({
286
703
  ...s,
287
704
  items: s.items.filter(item => item.id !== itemId),
288
705
  }));
289
- // Also remove from pinned items if it was pinned
290
- setPinnedItemIds(prev => {
291
- const newSet = new Set(prev);
292
- newSet.delete(itemId);
293
- return newSet;
294
- });
295
706
  };
707
+ const getItemRightClickMenuItems = useCallback((item, index) => {
708
+ const hasSeparatorOnRight = index < state.items.length - 1 && state.items[index + 1]?.isSeparator;
709
+ const hasSeparatorOnLeft = index > 0 && state.items[index - 1]?.isSeparator;
710
+ return [
711
+ {
712
+ name: 'Rimuovi',
713
+ icon: _jsx(IconDelete, { fontSize: 16 }),
714
+ onClick: () => removeItem(item.id),
715
+ },
716
+ {
717
+ name: 'Aggiungi separatore a sinistra',
718
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
719
+ disabled: hasSeparatorOnLeft,
720
+ onClick: () => {
721
+ isLocalChange.current = true;
722
+ const separator = createSeparator();
723
+ setState(s => {
724
+ const newItems = [...s.items];
725
+ newItems.splice(index, 0, separator);
726
+ return { ...s, items: newItems };
727
+ });
728
+ },
729
+ },
730
+ {
731
+ name: 'Aggiungi separatore a destra',
732
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
733
+ disabled: hasSeparatorOnRight,
734
+ onClick: () => {
735
+ isLocalChange.current = true;
736
+ const separator = createSeparator();
737
+ setState(s => {
738
+ const newItems = [...s.items];
739
+ newItems.splice(index + 1, 0, separator);
740
+ return { ...s, items: newItems };
741
+ });
742
+ },
743
+ },
744
+ {
745
+ beginGroup: true,
746
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
747
+ icon: _jsx(IconRotate, { fontSize: 16 }),
748
+ onClick: toggleOrientation,
749
+ },
750
+ ];
751
+ }, [state.items, state.orientation, removeItem, createSeparator, toggleOrientation]);
752
+ const getSeparatorRightClickMenuItems = useCallback((index) => {
753
+ return [
754
+ {
755
+ name: 'Rimuovi',
756
+ icon: _jsx(IconDelete, { fontSize: 16 }),
757
+ onClick: () => removeItem(state.items[index].id),
758
+ },
759
+ {
760
+ name: 'Aggiungi separatore a sinistra',
761
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
762
+ disabled: true,
763
+ },
764
+ {
765
+ name: 'Aggiungi separatore a destra',
766
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
767
+ disabled: true,
768
+ },
769
+ {
770
+ beginGroup: true,
771
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
772
+ icon: _jsx(IconRotate, { fontSize: 16 }),
773
+ onClick: toggleOrientation,
774
+ },
775
+ ];
776
+ }, [state.items, state.orientation, removeItem, toggleOrientation]);
296
777
  // Drag and drop for reordering
297
778
  const handleDragStart = (e, index) => {
298
- if (!state.isConfigMode)
779
+ if (!enableConfigMode)
299
780
  return;
300
781
  e.dataTransfer.effectAllowed = 'move';
301
782
  e.dataTransfer.setData('text/plain', index.toString());
302
783
  setState(s => ({ ...s, draggedItemIndex: index }));
303
784
  };
304
785
  const handleDragEnter = (e, index) => {
305
- if (!state.isConfigMode)
786
+ if (!enableConfigMode)
306
787
  return;
307
788
  e.preventDefault();
308
789
  if (state.draggedItemIndex !== index) {
@@ -310,13 +791,13 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
310
791
  }
311
792
  };
312
793
  const handleDragOver = (e) => {
313
- if (!state.isConfigMode)
794
+ if (!enableConfigMode)
314
795
  return;
315
796
  e.preventDefault();
316
797
  e.dataTransfer.dropEffect = 'move';
317
798
  };
318
799
  const handleDragLeave = (_e, index) => {
319
- if (!state.isConfigMode)
800
+ if (!enableConfigMode)
320
801
  return;
321
802
  // Only clear if we're actually leaving this specific item
322
803
  if (dragOverIndex === index) {
@@ -324,7 +805,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
324
805
  }
325
806
  };
326
807
  const handleDrop = (e, dropIndex) => {
327
- if (!state.isConfigMode || state.draggedItemIndex === null)
808
+ if (!enableConfigMode || state.draggedItemIndex === null)
328
809
  return;
329
810
  e.preventDefault();
330
811
  e.stopPropagation();
@@ -333,6 +814,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
333
814
  setDragOverIndex(null);
334
815
  return;
335
816
  }
817
+ isLocalChange.current = true;
336
818
  const newItems = [...state.items];
337
819
  const [draggedItem] = newItems.splice(dragIndex, 1);
338
820
  newItems.splice(dropIndex, 0, draggedItem);
@@ -347,24 +829,37 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
347
829
  setState(s => ({ ...s, draggedItemIndex: null }));
348
830
  setDragOverIndex(null);
349
831
  };
350
- return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x": state.position.x, "$y": state.position.y, "$orientation": state.orientation, "$isDragging": state.isDragging, "$isConfigMode": state.isConfigMode, "$isConstrained": isConstrained, children: [_jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, children: _jsx(IconDraggableDots, {}) }), _jsx(S.ConfigButton, { onClick: toggleConfigMode, "$isActive": state.isConfigMode, children: state.isConfigMode ? _jsx(IconApply, {}) : _jsx(IconSettings, {}) }), state.items.map((item, index) => {
351
- // Get current state (disabled and onClick) from contextMenuItems
352
- const currentState = getCurrentItemState(item.name);
353
- const isDisabled = currentState.disabled || false;
354
- const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
355
- 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: [state.isConfigMode ? (_jsx(S.MenuButton, { onClick: () => {
356
- if (state.isConfigMode || isDisabled)
357
- return;
358
- if (currentOnClick) {
359
- currentOnClick();
360
- }
361
- }, disabled: isDisabled && !state.isConfigMode, children: item.icon })) : (_jsx(TMTooltip, { content: item.name, position: "top", children: _jsx(S.MenuButton, { onClick: () => {
362
- if (state.isConfigMode || isDisabled)
363
- return;
364
- if (currentOnClick) {
365
- currentOnClick();
366
- }
367
- }, disabled: isDisabled, children: item.icon }) })), state.isConfigMode && (_jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" }))] }, item.id));
368
- }), !state.isConfigMode && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: enhancedContextMenuItems(), trigger: "left", children: _jsx(S.MenuButton, { children: _jsx(IconMenuVertical, {}) }) }, Array.from(pinnedItemIds).join(','))), !state.isConfigMode && (_jsx(S.OrientationToggle, { "$orientation": state.orientation, onClick: toggleOrientation, children: _jsx(IconMenuKebab, {}) }))] }), showMaxItemsNotification && (_jsx("div", { style: { position: 'fixed', top: '20px', left: '50%', transform: 'translateX(-50%)', zIndex: 100001 }, children: _jsx(Notification, { title: "Maximum Items Reached", message: `You have reached the maximum number of pinned items (${maxItems}). Please unpin an item before adding a new one.`, mode: "warning", position: "top-center", duration: 4000, closable: true, stopOnMouseEnter: true, hasProgress: true, onClose: () => setShowMaxItemsNotification(false) }) }))] }));
832
+ return (_jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": false, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: undefined, children: [_jsx(ContextMenu, { items: [
833
+ {
834
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
835
+ icon: _jsx(IconRotate, { fontSize: 16, style: { transform: state.orientation === 'horizontal' ? 'rotate(90deg)' : 'rotate(0deg)' } }),
836
+ onClick: toggleOrientation,
837
+ },
838
+ ], trigger: "right", children: _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) => {
839
+ // Handle separator items
840
+ if (item.isSeparator) {
841
+ return (_jsx(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: enableConfigMode, 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: enableConfigMode ? (_jsx(ContextMenu, { items: getSeparatorRightClickMenuItems(index), trigger: "right", children: _jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": false }) })) : (_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": false })) }, item.id));
842
+ }
843
+ // Get current state (disabled and onClick) from contextMenuItems
844
+ const currentState = getCurrentItemState(item.id);
845
+ // Prefer currentState.disabled if contextMenuItems has items, otherwise use item.disabled
846
+ const isDisabled = (contextMenuItems.length > 0 && currentState.disabled !== undefined)
847
+ ? currentState.disabled === true
848
+ : item.disabled === true;
849
+ const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
850
+ return (_jsx(S.DraggableItem, { "$isDragging": state.draggedItemIndex === index, "$isDragOver": dragOverIndex === index && state.draggedItemIndex !== index, draggable: enableConfigMode, 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: enableConfigMode ? (_jsx(ContextMenu, { items: getItemRightClickMenuItems(item, index), trigger: "right", children: item.staticItem ? (_jsx("div", { children: item.staticItem })) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
851
+ if (isDisabled)
852
+ return;
853
+ if (currentOnClick) {
854
+ currentOnClick();
855
+ }
856
+ }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })) })) : (item.staticItem ? (_jsx("div", { children: item.staticItem })) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => {
857
+ if (isDisabled)
858
+ return;
859
+ if (currentOnClick) {
860
+ currentOnClick();
861
+ }
862
+ }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) }))) }, item.id));
863
+ }), enableConfigMode && hasContextMenu && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: false, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) }))] }));
369
864
  };
370
865
  export default TMFloatingMenuBar;