@topconsultnpm/sdkui-react 6.20.0-dev1.12 → 6.20.0-dev1.121

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 +159 -37
  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 +610 -127
  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 +3 -1
  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 +1 -0
  63. package/lib/components/editors/TMMetadataValues.js +25 -7
  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 +459 -208
  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 +210 -250
  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 +2 -2
  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 +139 -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 +3 -1
  126. package/lib/components/query/TMQueryEditor.js +102 -100
  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 +20 -1
  141. package/lib/helper/SDKUI_Localizator.js +197 -1
  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 +131 -3
  155. package/lib/helper/index.d.ts +2 -0
  156. package/lib/helper/index.js +2 -0
  157. package/lib/helper/queryHelper.d.ts +1 -1
  158. package/lib/helper/queryHelper.js +33 -3
  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 +7 -4
  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,200 @@
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, contextMenuDefaultPinnedIds = [], defaultItems = [], 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: contextMenuDefaultPinnedIds,
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: contextMenuDefaultPinnedIds,
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), use props as defaults
54
+ if (!settings || !settings.position || !settings.itemIds) {
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);
49
148
  useEffect(() => {
50
149
  floatingBarItemIds.current = new Set(state.items.map(i => i.id));
51
150
  floatingBarItemNames.current = new Set(state.items.map(i => i.name));
52
151
  }, [state.items]);
53
- // Convert menu items to flat list with pinned status
152
+ // Calculate pixel position from percentage when container size or position changes
153
+ useEffect(() => {
154
+ const updatePixelPosition = () => {
155
+ if (!containerRef.current || !floatingRef.current)
156
+ return;
157
+ const container = containerRef.current.getBoundingClientRect();
158
+ const floating = floatingRef.current.getBoundingClientRect();
159
+ const containerWidth = isConstrained ? container.width : window.innerWidth;
160
+ const containerHeight = isConstrained ? container.height : window.innerHeight;
161
+ containerSizeRef.current = { width: containerWidth, height: containerHeight };
162
+ let newX = percentToPixels(state.position.x, containerWidth);
163
+ let newY = percentToPixels(state.position.y, containerHeight);
164
+ newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
165
+ newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
166
+ setPixelPosition({ x: newX, y: newY });
167
+ };
168
+ updatePixelPosition();
169
+ const resizeObserver = new ResizeObserver(() => {
170
+ updatePixelPosition();
171
+ });
172
+ if (containerRef.current) {
173
+ resizeObserver.observe(containerRef.current);
174
+ }
175
+ if (!isConstrained) {
176
+ window.addEventListener('resize', updatePixelPosition);
177
+ }
178
+ return () => {
179
+ resizeObserver.disconnect();
180
+ if (!isConstrained) {
181
+ window.removeEventListener('resize', updatePixelPosition);
182
+ }
183
+ };
184
+ }, [state.position, isConstrained]);
54
185
  const flattenMenuItems = useCallback((items, parentPath = '') => {
55
186
  const result = [];
56
187
  items.forEach((item, index) => {
57
- const itemId = `${parentPath}${item.name}-${index}`;
58
- // Only add items that have onClick (final actions, not submenu parents)
188
+ const itemId = item.id || `${parentPath}${item.name}-${index}`;
59
189
  if (item.onClick && !item.submenu) {
190
+ const isPinned = state.items.some(i => i.id === itemId);
60
191
  result.push({
61
192
  id: itemId,
62
193
  name: item.name,
63
- icon: item.icon || _jsx(IconStar, {}),
194
+ icon: item.icon || _jsx(IconPin, {}),
64
195
  onClick: item.onClick,
65
196
  disabled: item.disabled,
66
- isPinned: pinnedItemIds.has(itemId),
67
- originalMenuItem: item,
197
+ isPinned: isPinned,
68
198
  });
69
199
  }
70
200
  // Recursively process submenus
@@ -73,56 +203,115 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
73
203
  }
74
204
  });
75
205
  return result;
76
- }, [pinnedItemIds]);
206
+ }, [state.items]);
77
207
  // Restore items on mount from savedItemIds
78
208
  useEffect(() => {
79
- if (contextMenuItems.length > 0) {
209
+ if (contextMenuItems.length > 0 || initialConfig.savedItemIds.length > 0 || defaultItems.length > 0) {
80
210
  const flatItems = flattenMenuItems(contextMenuItems);
81
- // Restore items in the saved order from localStorage
82
- const restoredItems = initialConfig.savedItemIds
83
- .map((id) => flatItems.find(item => item.id === id))
84
- .filter((item) => item !== undefined);
85
- if (restoredItems.length > 0) {
86
- setState(s => ({ ...s, items: restoredItems }));
211
+ let itemsToSet = [];
212
+ // If enableConfigMode is false and defaultItems provided, use only defaultItems
213
+ if (!enableConfigMode && defaultItems.length > 0) {
214
+ itemsToSet = defaultItems;
215
+ }
216
+ else if (enableConfigMode && initialConfig.savedItemIds.length > 0) {
217
+ // Restore items in the saved order from localStorage (only if config mode is enabled)
218
+ const restoredItems = initialConfig.savedItemIds
219
+ .map((id) => {
220
+ if (id.startsWith('separator-')) {
221
+ return {
222
+ id,
223
+ name: 'Separator',
224
+ icon: _jsx(Separator, {}),
225
+ onClick: () => { },
226
+ isSeparator: true,
227
+ };
228
+ }
229
+ return flatItems.find(item => item.id === id);
230
+ })
231
+ .filter((item) => item !== undefined);
232
+ itemsToSet = restoredItems;
233
+ }
234
+ else if (contextMenuDefaultPinnedIds.length > 0) {
235
+ // First time: Use contextMenuDefaultPinnedIds from props to find items by ID
236
+ const defaultPinnedItems = contextMenuDefaultPinnedIds
237
+ .map((id) => flatItems.find(item => item.id === id))
238
+ .filter((item) => item !== undefined);
239
+ itemsToSet = defaultPinnedItems;
240
+ }
241
+ else if (defaultItems.length > 0) {
242
+ // Use defaultItems as fallback
243
+ itemsToSet = defaultItems;
244
+ }
245
+ if (itemsToSet.length > 0) {
246
+ setState(s => ({ ...s, items: itemsToSet }));
87
247
  }
88
248
  }
89
- }, []); // Only run once on mount
249
+ }, enableConfigMode ? [] : [defaultItems]); // Update when defaultItems change if config mode is disabled
250
+ // Sync with external pinnedItemIds when they change (from parent component)
251
+ useEffect(() => {
252
+ // Skip sync if a local change was just made (e.g. right-click remove/add separator)
253
+ if (isLocalChange.current) {
254
+ isLocalChange.current = false;
255
+ return;
256
+ }
257
+ if (externalPinnedItemIds === undefined || !enableConfigMode)
258
+ return;
259
+ const flatItems = flattenMenuItems(contextMenuItems);
260
+ // Build items from external pinned IDs
261
+ const itemsFromExternal = externalPinnedItemIds
262
+ .map((id) => {
263
+ if (id.startsWith('separator-')) {
264
+ return {
265
+ id,
266
+ name: 'Separator',
267
+ icon: _jsx(Separator, {}),
268
+ onClick: () => { },
269
+ isSeparator: true,
270
+ };
271
+ }
272
+ return flatItems.find(item => item.id === id);
273
+ })
274
+ .filter((item) => item !== undefined);
275
+ // Only update if different
276
+ const currentIds = state.items.map(i => i.id).join(',');
277
+ const newIds = itemsFromExternal.map(i => i.id).join(',');
278
+ if (currentIds !== newIds) {
279
+ isSyncingFromExternal.current = true; // Mark that we're syncing from external
280
+ setState(s => ({ ...s, items: itemsFromExternal }));
281
+ }
282
+ }, [externalPinnedItemIds, contextMenuItems, enableConfigMode, flattenMenuItems]);
90
283
  const togglePin = useCallback((item) => {
91
284
  setState(s => {
92
- const isInFloatingBar = s.items.some(i => i.id === item.id || i.name === item.name);
285
+ const isInFloatingBar = s.items.some(i => i.id === item.id);
93
286
  if (isInFloatingBar) {
94
287
  // Remove from floating bar
95
- const newItems = s.items.filter(i => i.id !== item.id && i.name !== item.name);
288
+ const newItems = s.items.filter(i => i.id !== item.id);
96
289
  return { ...s, items: newItems };
97
290
  }
98
291
  else {
99
292
  // Add to floating bar
100
293
  if (s.items.length >= maxItems) {
101
- setShowMaxItemsNotification(true);
102
- setTimeout(() => setShowMaxItemsNotification(false), 3000);
294
+ ShowAlert({
295
+ mode: 'warning',
296
+ title: 'Limite Massimo Raggiunto',
297
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
298
+ duration: 4000,
299
+ });
103
300
  return s;
104
301
  }
105
302
  return { ...s, items: [...s.items, item] };
106
303
  }
107
304
  });
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
305
  }, [maxItems]);
120
306
  // Get current item state (disabled and onClick) from contextMenuItems
121
- const getCurrentItemState = useCallback((itemName) => {
307
+ const getCurrentItemState = useCallback((itemId) => {
122
308
  const findInItems = (items) => {
123
- for (const item of items) {
124
- if (item.name === itemName)
309
+ for (let i = 0; i < items.length; i++) {
310
+ const item = items[i];
311
+ // Match by ID if the item has one
312
+ if (item.id === itemId)
125
313
  return item;
314
+ // Check in submenu
126
315
  if (item.submenu) {
127
316
  const found = findInItems(item.submenu);
128
317
  if (found)
@@ -136,58 +325,92 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
136
325
  disabled: foundItem?.disabled,
137
326
  onClick: foundItem?.onClick
138
327
  };
139
- }, [contextMenuItems]); // Enhanced context menu items with pin functionality
140
- const enhancedContextMenuItems = useCallback(() => {
328
+ }, [contextMenuItems]);
329
+ // Remove trailing separators from items array
330
+ const removeTrailingSeparators = useCallback((items) => {
331
+ const result = [...items];
332
+ while (result.length > 0 && result.at(-1)?.isSeparator) {
333
+ result.pop();
334
+ }
335
+ return result;
336
+ }, []);
337
+ // Create a new separator item
338
+ const createSeparator = useCallback(() => {
339
+ const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
340
+ return {
341
+ id: separatorId,
342
+ name: 'Separator',
343
+ icon: _jsx(Separator, {}),
344
+ onClick: () => { },
345
+ isSeparator: true,
346
+ };
347
+ }, []);
348
+ // Add separator to items
349
+ const addSeparator = useCallback(() => {
350
+ if (state.items.length >= maxItems) {
351
+ ShowAlert({
352
+ mode: 'warning',
353
+ title: 'Limite Massimo Raggiunto',
354
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
355
+ duration: 4000,
356
+ });
357
+ return;
358
+ }
359
+ const separator = createSeparator();
360
+ setState(s => ({ ...s, items: [...s.items, separator] }));
361
+ }, [state.items.length, maxItems, createSeparator]);
362
+ const getContextMenuItemsWithPinIcons = useCallback(() => {
141
363
  const flatItems = flattenMenuItems(contextMenuItems);
142
- // Calculate current pinned items directly from state.items (not refs)
143
364
  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) => {
365
+ const addPinIcons = (items) => {
146
366
  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 = {
367
+ const flatItem = flatItems.find(fi => fi.id === item.id);
368
+ const itemId = flatItem?.id || item.id || '';
369
+ const isPinned = currentItemIds.has(itemId);
370
+ const itemWithPin = {
152
371
  ...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
- }
372
+ rightIconProps: flatItem ? {
373
+ icon: _jsx(IconPin, {}),
374
+ activeColor: 'red',
375
+ inactiveColor: 'black',
376
+ isActive: isPinned,
377
+ onClick: () => togglePin(flatItem),
158
378
  } : undefined,
159
379
  };
160
380
  if (item.submenu) {
161
- enhanced.submenu = enhanceItems(item.submenu);
381
+ itemWithPin.submenu = addPinIcons(item.submenu);
162
382
  }
163
- return enhanced;
383
+ return itemWithPin;
164
384
  });
165
385
  };
166
- return enhanceItems(contextMenuItems);
386
+ return addPinIcons(contextMenuItems);
167
387
  }, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
168
388
  const handleMouseDown = (e) => {
169
- if (state.isConfigMode)
170
- return;
171
389
  const containerRect = containerRef.current?.getBoundingClientRect();
172
390
  if (containerRect) {
173
391
  // Calculate drag offset based on positioning mode
174
392
  if (isConstrained) {
175
393
  // For absolute positioning, offset is relative to container
176
394
  dragOffset.current = {
177
- x: e.clientX - containerRect.left - state.position.x,
178
- y: e.clientY - containerRect.top - state.position.y,
395
+ x: e.clientX - containerRect.left - pixelPosition.x,
396
+ y: e.clientY - containerRect.top - pixelPosition.y,
179
397
  };
180
398
  }
181
399
  else {
182
400
  // For fixed positioning, offset is relative to viewport
183
401
  dragOffset.current = {
184
- x: e.clientX - state.position.x,
185
- y: e.clientY - state.position.y,
402
+ x: e.clientX - pixelPosition.x,
403
+ y: e.clientY - pixelPosition.y,
186
404
  };
187
405
  }
188
406
  }
407
+ // Save starting position to detect if user actually moved the bar
408
+ dragStartPosition.current = { ...pixelPosition };
189
409
  setState(s => ({ ...s, isDragging: true }));
190
410
  };
411
+ const handleGripDoubleClick = () => {
412
+ toggleOrientation();
413
+ };
191
414
  const handleMouseMove = useCallback((e) => {
192
415
  if (!state.isDragging || !containerRef.current || !floatingRef.current)
193
416
  return;
@@ -210,50 +433,230 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
210
433
  newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
211
434
  newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
212
435
  }
213
- setState(s => ({
214
- ...s,
215
- position: { x: newX, y: newY },
216
- }));
436
+ // Update pixel position directly during drag
437
+ setPixelPosition({ x: newX, y: newY });
217
438
  }, [state.isDragging, containerRef, isConstrained]);
218
439
  const handleMouseUp = useCallback(() => {
219
- setState(s => ({ ...s, isDragging: false }));
220
- }, []);
440
+ if (state.isDragging && containerSizeRef.current.width > 0) {
441
+ // Convert final pixel position to percentage before updating state
442
+ const percentagePosition = {
443
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
444
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
445
+ };
446
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
447
+ if (dragStartPosition.current) {
448
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
449
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
450
+ if (dx > 5 || dy > 5) {
451
+ positionBeforeOrientationChange.current = null;
452
+ }
453
+ }
454
+ dragStartPosition.current = null;
455
+ setState(s => ({
456
+ ...s,
457
+ isDragging: false,
458
+ position: percentagePosition,
459
+ }));
460
+ }
461
+ else {
462
+ setState(s => ({ ...s, isDragging: false }));
463
+ }
464
+ }, [state.isDragging, pixelPosition]);
465
+ // Touch event handlers for tablet support
466
+ const handleTouchStart = (e) => {
467
+ const touch = e.touches[0];
468
+ const containerRect = containerRef.current?.getBoundingClientRect();
469
+ if (containerRect) {
470
+ if (isConstrained) {
471
+ dragOffset.current = {
472
+ x: touch.clientX - containerRect.left - pixelPosition.x,
473
+ y: touch.clientY - containerRect.top - pixelPosition.y,
474
+ };
475
+ }
476
+ else {
477
+ dragOffset.current = {
478
+ x: touch.clientX - pixelPosition.x,
479
+ y: touch.clientY - pixelPosition.y,
480
+ };
481
+ }
482
+ }
483
+ // Save starting position to detect if user actually moved the bar
484
+ dragStartPosition.current = { ...pixelPosition };
485
+ setState(s => ({ ...s, isDragging: true }));
486
+ };
487
+ const handleTouchMove = useCallback((e) => {
488
+ if (!state.isDragging || !containerRef.current || !floatingRef.current)
489
+ return;
490
+ const touch = e.touches[0];
491
+ const container = containerRef.current.getBoundingClientRect();
492
+ const floating = floatingRef.current.getBoundingClientRect();
493
+ let newX, newY;
494
+ if (isConstrained) {
495
+ newX = touch.clientX - container.left - dragOffset.current.x;
496
+ newY = touch.clientY - container.top - dragOffset.current.y;
497
+ newX = Math.max(0, Math.min(newX, container.width - floating.width));
498
+ newY = Math.max(0, Math.min(newY, container.height - floating.height));
499
+ }
500
+ else {
501
+ newX = touch.clientX - dragOffset.current.x;
502
+ newY = touch.clientY - dragOffset.current.y;
503
+ newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
504
+ newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
505
+ }
506
+ setPixelPosition({ x: newX, y: newY });
507
+ }, [state.isDragging, containerRef, isConstrained]);
508
+ const handleTouchEnd = useCallback(() => {
509
+ if (state.isDragging && containerSizeRef.current.width > 0) {
510
+ const percentagePosition = {
511
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
512
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
513
+ };
514
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
515
+ if (dragStartPosition.current) {
516
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
517
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
518
+ if (dx > 5 || dy > 5) {
519
+ positionBeforeOrientationChange.current = null;
520
+ }
521
+ }
522
+ dragStartPosition.current = null;
523
+ setState(s => ({
524
+ ...s,
525
+ isDragging: false,
526
+ position: percentagePosition,
527
+ }));
528
+ }
529
+ else {
530
+ setState(s => ({ ...s, isDragging: false }));
531
+ }
532
+ }, [state.isDragging, pixelPosition]);
221
533
  useEffect(() => {
222
534
  if (state.isDragging) {
223
535
  document.addEventListener('mousemove', handleMouseMove);
224
536
  document.addEventListener('mouseup', handleMouseUp);
537
+ document.addEventListener('touchmove', handleTouchMove);
538
+ document.addEventListener('touchend', handleTouchEnd);
225
539
  return () => {
226
540
  document.removeEventListener('mousemove', handleMouseMove);
227
541
  document.removeEventListener('mouseup', handleMouseUp);
542
+ document.removeEventListener('touchmove', handleTouchMove);
543
+ document.removeEventListener('touchend', handleTouchEnd);
228
544
  };
229
545
  }
230
546
  return undefined;
231
- }, [state.isDragging, handleMouseMove, handleMouseUp]);
232
- // Save to localStorage whenever config changes (excluding position)
547
+ }, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
548
+ // Save to SDKUI_Globals.userSettings
233
549
  useEffect(() => {
550
+ if (!enableConfigMode)
551
+ return; // Don't save if config mode is disabled
552
+ const pinnedIds = state.items.map(item => item.id);
234
553
  try {
235
- const config = {
554
+ // Replace the entire object to trigger the Proxy
555
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
236
556
  orientation: state.orientation,
237
- pinnedItemIds: Array.from(pinnedItemIds),
238
- itemIds: state.items.map(item => item.id), // Save only IDs, not functions
557
+ itemIds: pinnedIds,
558
+ position: state.position,
559
+ positionFormat: 'percentage',
239
560
  };
240
- localStorage.setItem(storageKey, JSON.stringify(config));
241
561
  }
242
562
  catch (error) {
243
563
  console.error('Failed to save FloatingMenuBar config:', error);
244
564
  }
245
- }, [state.orientation, state.items, pinnedItemIds, storageKey]);
246
- const toggleConfigMode = () => {
247
- setState(s => ({ ...s, isConfigMode: !s.isConfigMode }));
248
- };
565
+ // Notify parent about pin changes only if not syncing from external
566
+ // This prevents infinite loop: external change -> sync -> save -> onPinChange -> external change
567
+ if (!isSyncingFromExternal.current) {
568
+ onPinChange?.(pinnedIds);
569
+ }
570
+ isSyncingFromExternal.current = false; // Reset the flag
571
+ }, [state.orientation, state.items, state.position, enableConfigMode, onPinChange]);
249
572
  const toggleOrientation = () => {
250
573
  const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
251
- // First, change the orientation
574
+ // When switching from vertical back to horizontal, restore the original position
575
+ // Use visibility hiding only for this case to prevent flicker during position restoration
576
+ if (state.orientation === 'vertical' && newOrientation === 'horizontal') {
577
+ if (positionBeforeOrientationChange.current) {
578
+ setIsOrientationChanging(true); // Hide only when restoring position
579
+ const savedPosition = positionBeforeOrientationChange.current.position;
580
+ const savedPixelPosition = positionBeforeOrientationChange.current.pixelPosition;
581
+ setPixelPosition(savedPixelPosition);
582
+ setState(s => ({
583
+ ...s,
584
+ orientation: newOrientation,
585
+ verticalDirection: 'down',
586
+ position: savedPosition,
587
+ }));
588
+ positionBeforeOrientationChange.current = null;
589
+ // Show the bar after the state has been applied
590
+ requestAnimationFrame(() => {
591
+ setIsOrientationChanging(false);
592
+ });
593
+ return;
594
+ }
595
+ }
596
+ // When switching to vertical, save current position and check if we need to expand upward
597
+ // Use opacity hiding (doesn't affect focus unlike visibility:hidden)
598
+ if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
599
+ setIsOrientationChanging(true);
600
+ // Save the current position before changing orientation
601
+ positionBeforeOrientationChange.current = {
602
+ position: { ...state.position },
603
+ pixelPosition: { ...pixelPosition },
604
+ };
605
+ if (floatingRef.current) {
606
+ const floating = floatingRef.current.getBoundingClientRect();
607
+ const containerHeight = isConstrained && containerRef.current
608
+ ? containerRef.current.getBoundingClientRect().height
609
+ : window.innerHeight;
610
+ // Estimate vertical height (horizontal width becomes vertical height)
611
+ const estimatedVerticalHeight = floating.width;
612
+ if (estimatedVerticalHeight > containerHeight - 70) {
613
+ ShowAlert({
614
+ mode: 'warning',
615
+ title: 'Troppi elementi',
616
+ message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
617
+ duration: 4000,
618
+ });
619
+ positionBeforeOrientationChange.current = null; // Clear saved position since we're not changing
620
+ setIsOrientationChanging(false); // Show bar again since we're not changing
621
+ return;
622
+ }
623
+ // Check if we're in the bottom part of the screen and don't have enough space below
624
+ const spaceBelow = containerHeight - floating.bottom;
625
+ const spaceAbove = floating.top;
626
+ const needsVerticalSpace = estimatedVerticalHeight - floating.height; // Additional space needed
627
+ // If not enough space below but enough space above, expand upward
628
+ if (spaceBelow < needsVerticalSpace && spaceAbove >= needsVerticalSpace) {
629
+ // Calculate the new Y position so the bottom of the bar stays at the same place
630
+ const currentBottom = pixelPosition.y + floating.height;
631
+ const newPixelY = currentBottom - estimatedVerticalHeight;
632
+ setState(s => ({
633
+ ...s,
634
+ orientation: newOrientation,
635
+ verticalDirection: 'up',
636
+ }));
637
+ // Update pixel position and percentage after orientation change
638
+ requestAnimationFrame(() => {
639
+ requestAnimationFrame(() => {
640
+ const newY = Math.max(0, newPixelY);
641
+ setPixelPosition(prev => ({ ...prev, y: newY }));
642
+ const newPercentY = pixelsToPercent(newY, containerHeight);
643
+ setState(s => ({
644
+ ...s,
645
+ position: { ...s.position, y: newPercentY },
646
+ }));
647
+ setIsOrientationChanging(false);
648
+ });
649
+ });
650
+ return;
651
+ }
652
+ }
653
+ }
654
+ // Default case: just change orientation without special positioning
252
655
  setState(s => ({
253
656
  ...s,
254
657
  orientation: newOrientation,
658
+ verticalDirection: 'down',
255
659
  }));
256
- // Then, after DOM updates, adjust position to stay in bounds
257
660
  requestAnimationFrame(() => {
258
661
  requestAnimationFrame(() => {
259
662
  if (containerRef.current && floatingRef.current) {
@@ -278,31 +681,97 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
278
681
  }));
279
682
  }
280
683
  }
684
+ setIsOrientationChanging(false);
281
685
  });
282
686
  });
283
687
  };
284
688
  const removeItem = (itemId) => {
689
+ isLocalChange.current = true;
285
690
  setState(s => ({
286
691
  ...s,
287
692
  items: s.items.filter(item => item.id !== itemId),
288
693
  }));
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
694
  };
695
+ const getItemRightClickMenuItems = useCallback((item, index) => {
696
+ const hasSeparatorOnRight = index < state.items.length - 1 && state.items[index + 1]?.isSeparator;
697
+ const hasSeparatorOnLeft = index > 0 && state.items[index - 1]?.isSeparator;
698
+ return [
699
+ {
700
+ name: 'Rimuovi',
701
+ icon: _jsx(IconDelete, { fontSize: 16 }),
702
+ onClick: () => removeItem(item.id),
703
+ },
704
+ {
705
+ name: 'Aggiungi separatore a sinistra',
706
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
707
+ disabled: hasSeparatorOnLeft,
708
+ onClick: () => {
709
+ isLocalChange.current = true;
710
+ const separator = createSeparator();
711
+ setState(s => {
712
+ const newItems = [...s.items];
713
+ newItems.splice(index, 0, separator);
714
+ return { ...s, items: newItems };
715
+ });
716
+ },
717
+ },
718
+ {
719
+ name: 'Aggiungi separatore a destra',
720
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
721
+ disabled: hasSeparatorOnRight,
722
+ onClick: () => {
723
+ isLocalChange.current = true;
724
+ const separator = createSeparator();
725
+ setState(s => {
726
+ const newItems = [...s.items];
727
+ newItems.splice(index + 1, 0, separator);
728
+ return { ...s, items: newItems };
729
+ });
730
+ },
731
+ },
732
+ {
733
+ beginGroup: true,
734
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
735
+ icon: _jsx(IconRotate, { fontSize: 16 }),
736
+ onClick: toggleOrientation,
737
+ },
738
+ ];
739
+ }, [state.items, state.orientation, removeItem, createSeparator, toggleOrientation]);
740
+ const getSeparatorRightClickMenuItems = useCallback((index) => {
741
+ return [
742
+ {
743
+ name: 'Rimuovi',
744
+ icon: _jsx(IconDelete, { fontSize: 16 }),
745
+ onClick: () => removeItem(state.items[index].id),
746
+ },
747
+ {
748
+ name: 'Aggiungi separatore a sinistra',
749
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
750
+ disabled: true,
751
+ },
752
+ {
753
+ name: 'Aggiungi separatore a destra',
754
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
755
+ disabled: true,
756
+ },
757
+ {
758
+ beginGroup: true,
759
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
760
+ icon: _jsx(IconRotate, { fontSize: 16 }),
761
+ onClick: toggleOrientation,
762
+ },
763
+ ];
764
+ }, [state.items, state.orientation, removeItem, toggleOrientation]);
296
765
  // Drag and drop for reordering
297
766
  const handleDragStart = (e, index) => {
298
- if (!state.isConfigMode)
767
+ if (!enableConfigMode)
299
768
  return;
300
769
  e.dataTransfer.effectAllowed = 'move';
301
770
  e.dataTransfer.setData('text/plain', index.toString());
302
771
  setState(s => ({ ...s, draggedItemIndex: index }));
303
772
  };
304
773
  const handleDragEnter = (e, index) => {
305
- if (!state.isConfigMode)
774
+ if (!enableConfigMode)
306
775
  return;
307
776
  e.preventDefault();
308
777
  if (state.draggedItemIndex !== index) {
@@ -310,13 +779,13 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
310
779
  }
311
780
  };
312
781
  const handleDragOver = (e) => {
313
- if (!state.isConfigMode)
782
+ if (!enableConfigMode)
314
783
  return;
315
784
  e.preventDefault();
316
785
  e.dataTransfer.dropEffect = 'move';
317
786
  };
318
787
  const handleDragLeave = (_e, index) => {
319
- if (!state.isConfigMode)
788
+ if (!enableConfigMode)
320
789
  return;
321
790
  // Only clear if we're actually leaving this specific item
322
791
  if (dragOverIndex === index) {
@@ -324,7 +793,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
324
793
  }
325
794
  };
326
795
  const handleDrop = (e, dropIndex) => {
327
- if (!state.isConfigMode || state.draggedItemIndex === null)
796
+ if (!enableConfigMode || state.draggedItemIndex === null)
328
797
  return;
329
798
  e.preventDefault();
330
799
  e.stopPropagation();
@@ -333,6 +802,7 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
333
802
  setDragOverIndex(null);
334
803
  return;
335
804
  }
805
+ isLocalChange.current = true;
336
806
  const newItems = [...state.items];
337
807
  const [draggedItem] = newItems.splice(dragIndex, 1);
338
808
  newItems.splice(dropIndex, 0, draggedItem);
@@ -347,24 +817,37 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
347
817
  setState(s => ({ ...s, draggedItemIndex: null }));
348
818
  setDragOverIndex(null);
349
819
  };
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) }) }))] }));
820
+ 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: [
821
+ {
822
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
823
+ icon: _jsx(IconRotate, { fontSize: 16, style: { transform: state.orientation === 'horizontal' ? 'rotate(90deg)' : 'rotate(0deg)' } }),
824
+ onClick: toggleOrientation,
825
+ },
826
+ ], 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) => {
827
+ // Handle separator items
828
+ if (item.isSeparator) {
829
+ 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));
830
+ }
831
+ // Get current state (disabled and onClick) from contextMenuItems
832
+ const currentState = getCurrentItemState(item.id);
833
+ // Prefer currentState.disabled if contextMenuItems has items, otherwise use item.disabled
834
+ const isDisabled = (contextMenuItems.length > 0 && currentState.disabled !== undefined)
835
+ ? currentState.disabled === true
836
+ : item.disabled === true;
837
+ const currentOnClick = currentState.onClick || item.onClick; // Fallback to stored onClick if not found
838
+ 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: () => {
839
+ if (isDisabled)
840
+ return;
841
+ if (currentOnClick) {
842
+ currentOnClick();
843
+ }
844
+ }, 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: () => {
845
+ if (isDisabled)
846
+ return;
847
+ if (currentOnClick) {
848
+ currentOnClick();
849
+ }
850
+ }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) }))) }, item.id));
851
+ }), enableConfigMode && hasContextMenu && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: false, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) }))] }));
369
852
  };
370
853
  export default TMFloatingMenuBar;