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

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 +803 -109
  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 -2
  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,204 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, 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 { IconAdd, IconCloseOutline, IconDelete, IconMenuVertical, IconPin, IconRotate, IconSave, IconSeparator, IconSettings, IconUndo, SDKUI_Globals, SDKUI_Localizator } from '../../../helper';
8
+ import { ButtonNames, TMMessageBoxManager } from '../../base/TMPopUp';
9
+ 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
10
  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, }) => {
11
+ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], isConstrained = false, defaultPosition = { x: 1, y: 90 }, defaultOrientation = 'horizontal', maxItems = 100, contextMenuDefaultPinnedIds = [], defaultItems = [], disbaleConfigMode = false, bgColor = undefined, hasContextMenu = true, pinnedItemIds: externalPinnedItemIds, onPinChange, }) => {
12
+ const percentToPixels = (percent, containerSize) => {
13
+ return (percent / 100) * containerSize;
14
+ };
15
+ const pixelsToPercent = (pixels, containerSize) => {
16
+ return (pixels / containerSize) * 100;
17
+ };
18
+ const isPixelFormat = (pos) => {
19
+ return pos.x > 100 || pos.y > 100;
20
+ };
21
+ const migrateToPercentage = (pixelPos) => {
22
+ const container = containerRef.current?.getBoundingClientRect();
23
+ const containerWidth = isConstrained && container ? container.width : window.innerWidth;
24
+ const containerHeight = isConstrained && container ? container.height : window.innerHeight;
25
+ return {
26
+ x: pixelsToPercent(pixelPos.x, containerWidth),
27
+ y: pixelsToPercent(pixelPos.y, containerHeight),
28
+ };
29
+ };
30
+ const getDefaultConfig = () => ({
31
+ orientation: defaultOrientation,
32
+ savedItemIds: contextMenuDefaultPinnedIds,
33
+ position: defaultPosition,
34
+ });
35
+ const resetFloatingBarSettings = () => {
36
+ // Reset the floatingMenuBar settings in SDKUI_Globals to empty
37
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
38
+ orientation: undefined,
39
+ itemIds: undefined,
40
+ position: undefined,
41
+ };
42
+ };
10
43
  const loadConfig = () => {
44
+ // If config mode is disabled, use default position and orientation
45
+ if (disbaleConfigMode) {
46
+ return {
47
+ orientation: defaultOrientation,
48
+ savedItemIds: contextMenuDefaultPinnedIds,
49
+ position: defaultPosition,
50
+ };
51
+ }
11
52
  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 || [],
53
+ const settings = SDKUI_Globals.userSettings.searchSettings.floatingMenuBar;
54
+ // If localStorage is empty (first time), use props as defaults
55
+ if (!settings || !settings.position || !settings.itemIds) {
56
+ return getDefaultConfig();
57
+ }
58
+ // Validate position
59
+ const hasValidPosition = settings.position &&
60
+ typeof settings.position.x === 'number' &&
61
+ typeof settings.position.y === 'number' &&
62
+ !Number.isNaN(settings.position.x) &&
63
+ !Number.isNaN(settings.position.y) &&
64
+ Number.isFinite(settings.position.x) &&
65
+ Number.isFinite(settings.position.y);
66
+ if (!hasValidPosition) {
67
+ console.warn('FloatingMenuBar: Invalid position, resetting to defaults');
68
+ resetFloatingBarSettings();
69
+ return getDefaultConfig();
70
+ }
71
+ // Ensure position is within reasonable viewport bounds
72
+ const maxX = globalThis.window?.innerWidth ? globalThis.window.innerWidth - 50 : 1000;
73
+ const maxY = globalThis.window?.innerHeight ? globalThis.window.innerHeight - 50 : 800;
74
+ if (settings.position.x < 0 || settings.position.x > maxX ||
75
+ settings.position.y < 0 || settings.position.y > maxY) {
76
+ console.warn('FloatingMenuBar: Position out of bounds, resetting to defaults');
77
+ resetFloatingBarSettings();
78
+ return getDefaultConfig();
79
+ }
80
+ // Validate orientation
81
+ const validOrientation = (settings.orientation === 'horizontal' || settings.orientation === 'vertical')
82
+ ? settings.orientation
83
+ : 'horizontal';
84
+ // Validate itemIds
85
+ const validItemIds = Array.isArray(settings.itemIds) ? settings.itemIds : [];
86
+ if (validItemIds.length > 0) {
87
+ // Check if any ID looks like the old format (contains language-specific text or is too long)
88
+ const hasOldFormatIds = validItemIds.some(id => typeof id === 'string' &&
89
+ !id.startsWith('separator-') &&
90
+ (id.length > 20 || id.includes(' ')));
91
+ if (hasOldFormatIds) {
92
+ console.warn('FloatingMenuBar: Detected old name-based configuration, resetting to defaults');
93
+ resetFloatingBarSettings();
94
+ return getDefaultConfig();
95
+ }
96
+ }
97
+ // Migrate old pixel-based position to percentage-based
98
+ let finalPosition = settings.position;
99
+ if (isPixelFormat(settings.position) || settings.positionFormat === 'pixels') {
100
+ console.log('FloatingMenuBar: Migrating pixel-based position to percentage-based');
101
+ finalPosition = migrateToPercentage(settings.position);
102
+ // Save migrated position immediately
103
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
104
+ orientation: validOrientation,
105
+ itemIds: validItemIds,
106
+ position: finalPosition,
107
+ positionFormat: 'percentage',
19
108
  };
20
109
  }
110
+ return {
111
+ orientation: validOrientation,
112
+ savedItemIds: validItemIds,
113
+ position: finalPosition,
114
+ };
21
115
  }
22
116
  catch (error) {
23
117
  console.error('Failed to load FloatingMenuBar config:', error);
118
+ // Reset to defaults on any error
119
+ try {
120
+ resetFloatingBarSettings();
121
+ }
122
+ catch (e) {
123
+ console.error('Failed to reset FloatingMenuBar settings:', e);
124
+ }
125
+ return getDefaultConfig();
24
126
  }
25
- return {
26
- orientation: 'horizontal',
27
- pinnedItemIds: new Set(),
28
- savedItemIds: [],
29
- };
30
127
  };
31
128
  const initialConfig = loadConfig();
32
129
  const [state, setState] = useState({
33
- position: defaultPosition,
130
+ position: initialConfig.position, // Stored as percentage
34
131
  isDragging: false,
35
132
  isConfigMode: false,
36
133
  orientation: initialConfig.orientation,
134
+ verticalDirection: 'down',
37
135
  items: [],
38
136
  draggedItemIndex: null,
39
137
  });
40
138
  const floatingRef = useRef(null);
41
139
  const dragOffset = useRef({ x: 0, y: 0 });
42
- const [pinnedItemIds, setPinnedItemIds] = useState(initialConfig.pinnedItemIds);
43
140
  const [dragOverIndex, setDragOverIndex] = useState(null);
44
- const [showMaxItemsNotification, setShowMaxItemsNotification] = useState(false);
45
- // Use refs to track item IDs without causing re-renders
141
+ const [pixelPosition, setPixelPosition] = useState({ x: 0, y: 0 }); // Calculated pixel position
142
+ const [isOrientationChanging, setIsOrientationChanging] = useState(false); // Hide bar during orientation transition
143
+ const containerSizeRef = useRef({ width: 0, height: 0 });
144
+ const stateSnapshot = useRef(null);
145
+ const positionBeforeOrientationChange = useRef(null);
46
146
  const floatingBarItemIds = useRef(new Set());
47
147
  const floatingBarItemNames = useRef(new Set());
48
- // Update refs when items change, but don't trigger re-renders
148
+ const isSyncingFromExternal = useRef(false);
149
+ const isExitingConfigMode = useRef(false);
150
+ const isLocalChange = useRef(false);
151
+ const dragStartPosition = useRef(null);
49
152
  useEffect(() => {
50
153
  floatingBarItemIds.current = new Set(state.items.map(i => i.id));
51
154
  floatingBarItemNames.current = new Set(state.items.map(i => i.name));
52
155
  }, [state.items]);
53
- // Convert menu items to flat list with pinned status
156
+ // Calculate pixel position from percentage when container size or position changes
157
+ useEffect(() => {
158
+ const updatePixelPosition = () => {
159
+ if (!containerRef.current || !floatingRef.current)
160
+ return;
161
+ const container = containerRef.current.getBoundingClientRect();
162
+ const floating = floatingRef.current.getBoundingClientRect();
163
+ const containerWidth = isConstrained ? container.width : window.innerWidth;
164
+ const containerHeight = isConstrained ? container.height : window.innerHeight;
165
+ containerSizeRef.current = { width: containerWidth, height: containerHeight };
166
+ let newX = percentToPixels(state.position.x, containerWidth);
167
+ let newY = percentToPixels(state.position.y, containerHeight);
168
+ newX = Math.max(0, Math.min(newX, containerWidth - floating.width));
169
+ newY = Math.max(0, Math.min(newY, containerHeight - floating.height));
170
+ setPixelPosition({ x: newX, y: newY });
171
+ };
172
+ updatePixelPosition();
173
+ const resizeObserver = new ResizeObserver(() => {
174
+ updatePixelPosition();
175
+ });
176
+ if (containerRef.current) {
177
+ resizeObserver.observe(containerRef.current);
178
+ }
179
+ if (!isConstrained) {
180
+ window.addEventListener('resize', updatePixelPosition);
181
+ }
182
+ return () => {
183
+ resizeObserver.disconnect();
184
+ if (!isConstrained) {
185
+ window.removeEventListener('resize', updatePixelPosition);
186
+ }
187
+ };
188
+ }, [state.position, isConstrained]);
54
189
  const flattenMenuItems = useCallback((items, parentPath = '') => {
55
190
  const result = [];
56
191
  items.forEach((item, index) => {
57
- const itemId = `${parentPath}${item.name}-${index}`;
58
- // Only add items that have onClick (final actions, not submenu parents)
192
+ const itemId = item.id || `${parentPath}${item.name}-${index}`;
59
193
  if (item.onClick && !item.submenu) {
194
+ const isPinned = state.items.some(i => i.id === itemId);
60
195
  result.push({
61
196
  id: itemId,
62
197
  name: item.name,
63
- icon: item.icon || _jsx(IconStar, {}),
198
+ icon: item.icon || _jsx(IconPin, {}),
64
199
  onClick: item.onClick,
65
200
  disabled: item.disabled,
66
- isPinned: pinnedItemIds.has(itemId),
67
- originalMenuItem: item,
201
+ isPinned: isPinned,
68
202
  });
69
203
  }
70
204
  // Recursively process submenus
@@ -73,56 +207,120 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
73
207
  }
74
208
  });
75
209
  return result;
76
- }, [pinnedItemIds]);
210
+ }, [state.items]);
77
211
  // Restore items on mount from savedItemIds
78
212
  useEffect(() => {
79
- if (contextMenuItems.length > 0) {
213
+ if (contextMenuItems.length > 0 || initialConfig.savedItemIds.length > 0 || defaultItems.length > 0) {
80
214
  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 }));
215
+ let itemsToSet = [];
216
+ // If disbaleConfigMode is true and defaultItems provided, use only defaultItems
217
+ if (disbaleConfigMode && defaultItems.length > 0) {
218
+ itemsToSet = defaultItems;
219
+ }
220
+ else if (!disbaleConfigMode && initialConfig.savedItemIds.length > 0) {
221
+ // Restore items in the saved order from localStorage (only if config mode is enabled)
222
+ const restoredItems = initialConfig.savedItemIds
223
+ .map((id) => {
224
+ if (id.startsWith('separator-')) {
225
+ return {
226
+ id,
227
+ name: 'Separator',
228
+ icon: _jsx(Separator, {}),
229
+ onClick: () => { },
230
+ isSeparator: true,
231
+ };
232
+ }
233
+ return flatItems.find(item => item.id === id);
234
+ })
235
+ .filter((item) => item !== undefined);
236
+ itemsToSet = restoredItems;
237
+ }
238
+ else if (contextMenuDefaultPinnedIds.length > 0) {
239
+ // First time: Use contextMenuDefaultPinnedIds from props to find items by ID
240
+ const defaultPinnedItems = contextMenuDefaultPinnedIds
241
+ .map((id) => flatItems.find(item => item.id === id))
242
+ .filter((item) => item !== undefined);
243
+ itemsToSet = defaultPinnedItems;
244
+ }
245
+ else if (defaultItems.length > 0) {
246
+ // Use defaultItems as fallback
247
+ itemsToSet = defaultItems;
248
+ }
249
+ if (itemsToSet.length > 0) {
250
+ setState(s => ({ ...s, items: itemsToSet }));
87
251
  }
88
252
  }
89
- }, []); // Only run once on mount
253
+ }, disbaleConfigMode ? [defaultItems] : []); // Update when defaultItems change if config mode is disabled
254
+ // Sync with external pinnedItemIds when they change (from parent component)
255
+ useEffect(() => {
256
+ // Skip sync if exiting config mode - let save effect update external state first
257
+ if (isExitingConfigMode.current) {
258
+ isExitingConfigMode.current = false;
259
+ return;
260
+ }
261
+ // Skip sync if a local change was just made (e.g. right-click remove/add separator)
262
+ if (isLocalChange.current) {
263
+ isLocalChange.current = false;
264
+ return;
265
+ }
266
+ if (externalPinnedItemIds === undefined || disbaleConfigMode || state.isConfigMode)
267
+ return;
268
+ const flatItems = flattenMenuItems(contextMenuItems);
269
+ // Build items from external pinned IDs
270
+ const itemsFromExternal = externalPinnedItemIds
271
+ .map((id) => {
272
+ if (id.startsWith('separator-')) {
273
+ return {
274
+ id,
275
+ name: 'Separator',
276
+ icon: _jsx(Separator, {}),
277
+ onClick: () => { },
278
+ isSeparator: true,
279
+ };
280
+ }
281
+ return flatItems.find(item => item.id === id);
282
+ })
283
+ .filter((item) => item !== undefined);
284
+ // Only update if different
285
+ const currentIds = state.items.map(i => i.id).join(',');
286
+ const newIds = itemsFromExternal.map(i => i.id).join(',');
287
+ if (currentIds !== newIds) {
288
+ isSyncingFromExternal.current = true; // Mark that we're syncing from external
289
+ setState(s => ({ ...s, items: itemsFromExternal }));
290
+ }
291
+ }, [externalPinnedItemIds, contextMenuItems, disbaleConfigMode, state.isConfigMode, flattenMenuItems]);
90
292
  const togglePin = useCallback((item) => {
91
293
  setState(s => {
92
- const isInFloatingBar = s.items.some(i => i.id === item.id || i.name === item.name);
294
+ const isInFloatingBar = s.items.some(i => i.id === item.id);
93
295
  if (isInFloatingBar) {
94
296
  // Remove from floating bar
95
- const newItems = s.items.filter(i => i.id !== item.id && i.name !== item.name);
297
+ const newItems = s.items.filter(i => i.id !== item.id);
96
298
  return { ...s, items: newItems };
97
299
  }
98
300
  else {
99
301
  // Add to floating bar
100
302
  if (s.items.length >= maxItems) {
101
- setShowMaxItemsNotification(true);
102
- setTimeout(() => setShowMaxItemsNotification(false), 3000);
303
+ ShowAlert({
304
+ mode: 'warning',
305
+ title: 'Limite Massimo Raggiunto',
306
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
307
+ duration: 4000,
308
+ });
103
309
  return s;
104
310
  }
105
311
  return { ...s, items: [...s.items, item] };
106
312
  }
107
313
  });
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
314
  }, [maxItems]);
120
315
  // Get current item state (disabled and onClick) from contextMenuItems
121
- const getCurrentItemState = useCallback((itemName) => {
316
+ const getCurrentItemState = useCallback((itemId) => {
122
317
  const findInItems = (items) => {
123
- for (const item of items) {
124
- if (item.name === itemName)
318
+ for (let i = 0; i < items.length; i++) {
319
+ const item = items[i];
320
+ // Match by ID if the item has one
321
+ if (item.id === itemId)
125
322
  return item;
323
+ // Check in submenu
126
324
  if (item.submenu) {
127
325
  const found = findInItems(item.submenu);
128
326
  if (found)
@@ -136,34 +334,101 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
136
334
  disabled: foundItem?.disabled,
137
335
  onClick: foundItem?.onClick
138
336
  };
139
- }, [contextMenuItems]); // Enhanced context menu items with pin functionality
140
- const enhancedContextMenuItems = useCallback(() => {
337
+ }, [contextMenuItems]);
338
+ // Remove trailing separators from items array
339
+ const removeTrailingSeparators = useCallback((items) => {
340
+ const result = [...items];
341
+ while (result.length > 0 && result.at(-1)?.isSeparator) {
342
+ result.pop();
343
+ }
344
+ return result;
345
+ }, []);
346
+ // Create a new separator item
347
+ const createSeparator = useCallback(() => {
348
+ const separatorId = `separator-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`;
349
+ return {
350
+ id: separatorId,
351
+ name: 'Separator',
352
+ icon: _jsx(Separator, {}),
353
+ onClick: () => { },
354
+ isSeparator: true,
355
+ };
356
+ }, []);
357
+ // Add separator to items
358
+ const addSeparator = useCallback(() => {
359
+ if (state.items.length >= maxItems) {
360
+ ShowAlert({
361
+ mode: 'warning',
362
+ title: 'Limite Massimo Raggiunto',
363
+ message: `Hai raggiunto il massimo di ${maxItems} elementi. Rimuovine uno prima di aggiungerne altri.`,
364
+ duration: 4000,
365
+ });
366
+ return;
367
+ }
368
+ const separator = createSeparator();
369
+ setState(s => ({ ...s, items: [...s.items, separator] }));
370
+ }, [state.items.length, maxItems, createSeparator]);
371
+ const getPinContextMenuItems = useCallback(() => {
141
372
  const flatItems = flattenMenuItems(contextMenuItems);
142
- // Calculate current pinned items directly from state.items (not refs)
143
373
  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) => {
374
+ const createPinItems = (items) => {
146
375
  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 = {
376
+ const flatItem = flatItems.find(fi => fi.id === item.id);
377
+ const itemId = flatItem?.id || item.id || '';
378
+ const isAlreadyPinned = currentItemIds.has(itemId);
379
+ const pinItem = {
152
380
  ...item,
153
- rightIcon: item.onClick && !item.submenu ? (isInFloatingBar ? _jsx(IconStar, { color: "#FFD700" }) : _jsx(IconStar, {})) : undefined,
154
- onRightIconClick: item.onClick && !item.submenu ? () => {
155
- if (flatItem) {
381
+ // Remove rightIconProps in config mode - clicking the item itself pins it
382
+ rightIconProps: undefined,
383
+ onClick: item.onClick && !item.submenu ? () => {
384
+ if (flatItem && !isAlreadyPinned) {
156
385
  togglePin(flatItem);
157
386
  }
158
387
  } : undefined,
388
+ disabled: isAlreadyPinned,
159
389
  };
160
390
  if (item.submenu) {
161
- enhanced.submenu = enhanceItems(item.submenu);
391
+ pinItem.submenu = createPinItems(item.submenu);
162
392
  }
163
- return enhanced;
393
+ return pinItem;
164
394
  });
165
395
  };
166
- return enhanceItems(contextMenuItems);
396
+ const pinItems = createPinItems(contextMenuItems);
397
+ // Add separator option at the end
398
+ pinItems.push({
399
+ id: 'add-separator',
400
+ name: SDKUI_Localizator.Add + ' separatore',
401
+ icon: _jsx(IconSeparator, {}),
402
+ onClick: addSeparator,
403
+ beginGroup: true
404
+ });
405
+ return pinItems;
406
+ }, [contextMenuItems, flattenMenuItems, togglePin, state.items, addSeparator]);
407
+ const getContextMenuItemsWithPinIcons = useCallback(() => {
408
+ const flatItems = flattenMenuItems(contextMenuItems);
409
+ const currentItemIds = new Set(state.items.map(i => i.id));
410
+ const addPinIcons = (items) => {
411
+ return items.map(item => {
412
+ const flatItem = flatItems.find(fi => fi.id === item.id);
413
+ const itemId = flatItem?.id || item.id || '';
414
+ const isPinned = currentItemIds.has(itemId);
415
+ const itemWithPin = {
416
+ ...item,
417
+ rightIconProps: flatItem ? {
418
+ icon: _jsx(IconPin, {}),
419
+ activeColor: 'red',
420
+ inactiveColor: 'black',
421
+ isActive: isPinned,
422
+ onClick: () => togglePin(flatItem),
423
+ } : undefined,
424
+ };
425
+ if (item.submenu) {
426
+ itemWithPin.submenu = addPinIcons(item.submenu);
427
+ }
428
+ return itemWithPin;
429
+ });
430
+ };
431
+ return addPinIcons(contextMenuItems);
167
432
  }, [contextMenuItems, flattenMenuItems, togglePin, state.items]);
168
433
  const handleMouseDown = (e) => {
169
434
  if (state.isConfigMode)
@@ -174,20 +439,27 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
174
439
  if (isConstrained) {
175
440
  // For absolute positioning, offset is relative to container
176
441
  dragOffset.current = {
177
- x: e.clientX - containerRect.left - state.position.x,
178
- y: e.clientY - containerRect.top - state.position.y,
442
+ x: e.clientX - containerRect.left - pixelPosition.x,
443
+ y: e.clientY - containerRect.top - pixelPosition.y,
179
444
  };
180
445
  }
181
446
  else {
182
447
  // For fixed positioning, offset is relative to viewport
183
448
  dragOffset.current = {
184
- x: e.clientX - state.position.x,
185
- y: e.clientY - state.position.y,
449
+ x: e.clientX - pixelPosition.x,
450
+ y: e.clientY - pixelPosition.y,
186
451
  };
187
452
  }
188
453
  }
454
+ // Save starting position to detect if user actually moved the bar
455
+ dragStartPosition.current = { ...pixelPosition };
189
456
  setState(s => ({ ...s, isDragging: true }));
190
457
  };
458
+ const handleGripDoubleClick = () => {
459
+ if (state.isConfigMode)
460
+ return;
461
+ toggleOrientation();
462
+ };
191
463
  const handleMouseMove = useCallback((e) => {
192
464
  if (!state.isDragging || !containerRef.current || !floatingRef.current)
193
465
  return;
@@ -210,50 +482,366 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
210
482
  newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
211
483
  newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
212
484
  }
213
- setState(s => ({
214
- ...s,
215
- position: { x: newX, y: newY },
216
- }));
485
+ // Update pixel position directly during drag
486
+ setPixelPosition({ x: newX, y: newY });
217
487
  }, [state.isDragging, containerRef, isConstrained]);
218
488
  const handleMouseUp = useCallback(() => {
219
- setState(s => ({ ...s, isDragging: false }));
220
- }, []);
489
+ if (state.isDragging && containerSizeRef.current.width > 0) {
490
+ // Convert final pixel position to percentage before updating state
491
+ const percentagePosition = {
492
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
493
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
494
+ };
495
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
496
+ if (dragStartPosition.current) {
497
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
498
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
499
+ if (dx > 5 || dy > 5) {
500
+ positionBeforeOrientationChange.current = null;
501
+ }
502
+ }
503
+ dragStartPosition.current = null;
504
+ setState(s => ({
505
+ ...s,
506
+ isDragging: false,
507
+ position: percentagePosition,
508
+ }));
509
+ }
510
+ else {
511
+ setState(s => ({ ...s, isDragging: false }));
512
+ }
513
+ }, [state.isDragging, pixelPosition]);
514
+ // Touch event handlers for tablet support
515
+ const handleTouchStart = (e) => {
516
+ if (state.isConfigMode)
517
+ return;
518
+ const touch = e.touches[0];
519
+ const containerRect = containerRef.current?.getBoundingClientRect();
520
+ if (containerRect) {
521
+ if (isConstrained) {
522
+ dragOffset.current = {
523
+ x: touch.clientX - containerRect.left - pixelPosition.x,
524
+ y: touch.clientY - containerRect.top - pixelPosition.y,
525
+ };
526
+ }
527
+ else {
528
+ dragOffset.current = {
529
+ x: touch.clientX - pixelPosition.x,
530
+ y: touch.clientY - pixelPosition.y,
531
+ };
532
+ }
533
+ }
534
+ // Save starting position to detect if user actually moved the bar
535
+ dragStartPosition.current = { ...pixelPosition };
536
+ setState(s => ({ ...s, isDragging: true }));
537
+ };
538
+ const handleTouchMove = useCallback((e) => {
539
+ if (!state.isDragging || !containerRef.current || !floatingRef.current)
540
+ return;
541
+ const touch = e.touches[0];
542
+ const container = containerRef.current.getBoundingClientRect();
543
+ const floating = floatingRef.current.getBoundingClientRect();
544
+ let newX, newY;
545
+ if (isConstrained) {
546
+ newX = touch.clientX - container.left - dragOffset.current.x;
547
+ newY = touch.clientY - container.top - dragOffset.current.y;
548
+ newX = Math.max(0, Math.min(newX, container.width - floating.width));
549
+ newY = Math.max(0, Math.min(newY, container.height - floating.height));
550
+ }
551
+ else {
552
+ newX = touch.clientX - dragOffset.current.x;
553
+ newY = touch.clientY - dragOffset.current.y;
554
+ newX = Math.max(0, Math.min(newX, window.innerWidth - floating.width));
555
+ newY = Math.max(0, Math.min(newY, window.innerHeight - floating.height));
556
+ }
557
+ setPixelPosition({ x: newX, y: newY });
558
+ }, [state.isDragging, containerRef, isConstrained]);
559
+ const handleTouchEnd = useCallback(() => {
560
+ if (state.isDragging && containerSizeRef.current.width > 0) {
561
+ const percentagePosition = {
562
+ x: pixelsToPercent(pixelPosition.x, containerSizeRef.current.width),
563
+ y: pixelsToPercent(pixelPosition.y, containerSizeRef.current.height),
564
+ };
565
+ // Only clear saved position if user actually moved the bar significantly (more than 5 pixels)
566
+ if (dragStartPosition.current) {
567
+ const dx = Math.abs(pixelPosition.x - dragStartPosition.current.x);
568
+ const dy = Math.abs(pixelPosition.y - dragStartPosition.current.y);
569
+ if (dx > 5 || dy > 5) {
570
+ positionBeforeOrientationChange.current = null;
571
+ }
572
+ }
573
+ dragStartPosition.current = null;
574
+ setState(s => ({
575
+ ...s,
576
+ isDragging: false,
577
+ position: percentagePosition,
578
+ }));
579
+ }
580
+ else {
581
+ setState(s => ({ ...s, isDragging: false }));
582
+ }
583
+ }, [state.isDragging, pixelPosition]);
221
584
  useEffect(() => {
222
585
  if (state.isDragging) {
223
586
  document.addEventListener('mousemove', handleMouseMove);
224
587
  document.addEventListener('mouseup', handleMouseUp);
588
+ document.addEventListener('touchmove', handleTouchMove);
589
+ document.addEventListener('touchend', handleTouchEnd);
225
590
  return () => {
226
591
  document.removeEventListener('mousemove', handleMouseMove);
227
592
  document.removeEventListener('mouseup', handleMouseUp);
593
+ document.removeEventListener('touchmove', handleTouchMove);
594
+ document.removeEventListener('touchend', handleTouchEnd);
228
595
  };
229
596
  }
230
597
  return undefined;
231
- }, [state.isDragging, handleMouseMove, handleMouseUp]);
232
- // Save to localStorage whenever config changes (excluding position)
598
+ }, [state.isDragging, handleMouseMove, handleMouseUp, handleTouchMove, handleTouchEnd]);
599
+ // Save to SDKUI_Globals.userSettings only when NOT in config mode (when applying changes)
233
600
  useEffect(() => {
601
+ if (state.isConfigMode)
602
+ return; // Don't save during edit mode
603
+ if (disbaleConfigMode)
604
+ return; // Don't save if config mode is disabled
605
+ const pinnedIds = state.items.map(item => item.id);
234
606
  try {
235
- const config = {
607
+ // Replace the entire object to trigger the Proxy
608
+ SDKUI_Globals.userSettings.searchSettings.floatingMenuBar = {
236
609
  orientation: state.orientation,
237
- pinnedItemIds: Array.from(pinnedItemIds),
238
- itemIds: state.items.map(item => item.id), // Save only IDs, not functions
610
+ itemIds: pinnedIds,
611
+ position: state.position,
612
+ positionFormat: 'percentage',
239
613
  };
240
- localStorage.setItem(storageKey, JSON.stringify(config));
241
614
  }
242
615
  catch (error) {
243
616
  console.error('Failed to save FloatingMenuBar config:', error);
244
617
  }
245
- }, [state.orientation, state.items, pinnedItemIds, storageKey]);
618
+ // Notify parent about pin changes only if not syncing from external
619
+ // This prevents infinite loop: external change -> sync -> save -> onPinChange -> external change
620
+ if (!isSyncingFromExternal.current) {
621
+ onPinChange?.(pinnedIds);
622
+ }
623
+ isSyncingFromExternal.current = false; // Reset the flag
624
+ }, [state.orientation, state.items, state.position, state.isConfigMode, disbaleConfigMode, onPinChange]);
246
625
  const toggleConfigMode = () => {
247
- setState(s => ({ ...s, isConfigMode: !s.isConfigMode }));
626
+ setState(s => {
627
+ if (!s.isConfigMode) {
628
+ stateSnapshot.current = {
629
+ items: [...s.items],
630
+ orientation: s.orientation,
631
+ verticalDirection: s.verticalDirection,
632
+ position: { ...s.position },
633
+ };
634
+ return { ...s, isConfigMode: true };
635
+ }
636
+ else {
637
+ // Exiting edit mode (applying changes) - clean up trailing separators and clear snapshot
638
+ stateSnapshot.current = null;
639
+ isExitingConfigMode.current = true; // Prevent sync effect from overwriting changes
640
+ const cleanedItems = removeTrailingSeparators(s.items);
641
+ return { ...s, isConfigMode: false, items: cleanedItems };
642
+ }
643
+ });
644
+ };
645
+ // Auto-reposition when entering edit mode to ensure Apply/Undo buttons are visible
646
+ useEffect(() => {
647
+ if (!state.isConfigMode || !floatingRef.current)
648
+ return;
649
+ // Use double requestAnimationFrame to ensure the DOM has fully updated with new buttons
650
+ requestAnimationFrame(() => {
651
+ requestAnimationFrame(() => {
652
+ if (!floatingRef.current)
653
+ return;
654
+ const floating = floatingRef.current.getBoundingClientRect();
655
+ const containerWidth = isConstrained && containerRef.current
656
+ ? containerRef.current.getBoundingClientRect().width
657
+ : window.innerWidth;
658
+ const containerHeight = isConstrained && containerRef.current
659
+ ? containerRef.current.getBoundingClientRect().height
660
+ : window.innerHeight;
661
+ // Use current pixel position
662
+ let newPixelX = pixelPosition.x;
663
+ let newPixelY = pixelPosition.y;
664
+ let needsUpdate = false;
665
+ // Check horizontal overflow
666
+ if (newPixelX + floating.width > containerWidth) {
667
+ newPixelX = Math.max(0, containerWidth - floating.width);
668
+ needsUpdate = true;
669
+ }
670
+ // Check vertical overflow
671
+ if (newPixelY + floating.height > containerHeight) {
672
+ newPixelY = Math.max(0, containerHeight - floating.height);
673
+ needsUpdate = true;
674
+ }
675
+ if (needsUpdate) {
676
+ // Update pixel position immediately
677
+ setPixelPosition({ x: newPixelX, y: newPixelY });
678
+ // Convert to percentage for state
679
+ const newPercentagePosition = {
680
+ x: pixelsToPercent(newPixelX, containerWidth),
681
+ y: pixelsToPercent(newPixelY, containerHeight),
682
+ };
683
+ setState(s => ({
684
+ ...s,
685
+ position: newPercentagePosition,
686
+ }));
687
+ // Update snapshot position to the corrected position so Undo restores to visible position
688
+ if (stateSnapshot.current) {
689
+ stateSnapshot.current.position = newPercentagePosition;
690
+ }
691
+ }
692
+ });
693
+ });
694
+ }, [state.isConfigMode, state.orientation, isConstrained, state.items, pixelPosition.x, pixelPosition.y]);
695
+ const handleUndo = () => {
696
+ if (stateSnapshot.current) {
697
+ setState(s => ({
698
+ ...s,
699
+ items: [...stateSnapshot.current.items],
700
+ orientation: stateSnapshot.current.orientation,
701
+ verticalDirection: stateSnapshot.current.verticalDirection,
702
+ position: { ...stateSnapshot.current.position },
703
+ isConfigMode: true,
704
+ }));
705
+ }
706
+ };
707
+ // Check if current state has changed from snapshot
708
+ const hasChanges = () => {
709
+ if (!stateSnapshot.current)
710
+ return false;
711
+ // Check if items have changed (different IDs or different order)
712
+ const currentIds = state.items.map(i => i.id).join(',');
713
+ const snapshotIds = stateSnapshot.current.items.map(i => i.id).join(',');
714
+ return currentIds !== snapshotIds;
715
+ };
716
+ const handleClose = () => {
717
+ // If all items removed, exit without asking and restore last items
718
+ if (state.items.length === 0 && stateSnapshot.current) {
719
+ setState(s => ({
720
+ ...s,
721
+ items: [...stateSnapshot.current.items],
722
+ orientation: stateSnapshot.current.orientation,
723
+ verticalDirection: stateSnapshot.current.verticalDirection,
724
+ position: { ...stateSnapshot.current.position },
725
+ isConfigMode: false,
726
+ }));
727
+ stateSnapshot.current = null;
728
+ return;
729
+ }
730
+ // If no changes, simply exit config mode
731
+ if (!hasChanges()) {
732
+ stateSnapshot.current = null;
733
+ const cleanedItems = removeTrailingSeparators(state.items);
734
+ setState(s => ({ ...s, isConfigMode: false, items: cleanedItems }));
735
+ return;
736
+ }
737
+ // If there are changes, ask for confirmation
738
+ TMMessageBoxManager.show({
739
+ message: 'Perderai le tue modifiche, sei sicuro?',
740
+ buttons: [ButtonNames.YES, ButtonNames.NO],
741
+ onButtonClick: (buttonName) => {
742
+ if (buttonName === ButtonNames.YES && stateSnapshot.current) {
743
+ // Restore snapshot and exit config mode
744
+ setState(s => ({
745
+ ...s,
746
+ items: [...stateSnapshot.current.items],
747
+ orientation: stateSnapshot.current.orientation,
748
+ verticalDirection: stateSnapshot.current.verticalDirection,
749
+ position: { ...stateSnapshot.current.position },
750
+ isConfigMode: false,
751
+ }));
752
+ stateSnapshot.current = null;
753
+ }
754
+ },
755
+ });
248
756
  };
249
757
  const toggleOrientation = () => {
250
758
  const newOrientation = state.orientation === 'horizontal' ? 'vertical' : 'horizontal';
251
- // First, change the orientation
759
+ // When switching from vertical back to horizontal, restore the original position
760
+ // Use visibility hiding only for this case to prevent flicker during position restoration
761
+ if (state.orientation === 'vertical' && newOrientation === 'horizontal') {
762
+ if (positionBeforeOrientationChange.current) {
763
+ setIsOrientationChanging(true); // Hide only when restoring position
764
+ const savedPosition = positionBeforeOrientationChange.current.position;
765
+ const savedPixelPosition = positionBeforeOrientationChange.current.pixelPosition;
766
+ setPixelPosition(savedPixelPosition);
767
+ setState(s => ({
768
+ ...s,
769
+ orientation: newOrientation,
770
+ verticalDirection: 'down',
771
+ position: savedPosition,
772
+ }));
773
+ positionBeforeOrientationChange.current = null;
774
+ // Show the bar after the state has been applied
775
+ requestAnimationFrame(() => {
776
+ setIsOrientationChanging(false);
777
+ });
778
+ return;
779
+ }
780
+ }
781
+ // When switching to vertical, save current position and check if we need to expand upward
782
+ // Use opacity hiding (doesn't affect focus unlike visibility:hidden)
783
+ if (state.orientation === 'horizontal' && newOrientation === 'vertical') {
784
+ setIsOrientationChanging(true);
785
+ // Save the current position before changing orientation
786
+ positionBeforeOrientationChange.current = {
787
+ position: { ...state.position },
788
+ pixelPosition: { ...pixelPosition },
789
+ };
790
+ if (floatingRef.current) {
791
+ const floating = floatingRef.current.getBoundingClientRect();
792
+ const containerHeight = isConstrained && containerRef.current
793
+ ? containerRef.current.getBoundingClientRect().height
794
+ : window.innerHeight;
795
+ // Estimate vertical height (horizontal width becomes vertical height)
796
+ const estimatedVerticalHeight = floating.width;
797
+ if (estimatedVerticalHeight > containerHeight - 70) {
798
+ ShowAlert({
799
+ mode: 'warning',
800
+ title: 'Troppi elementi',
801
+ message: 'Ci sono troppi elementi nella barra mobile per la modalità verticale.',
802
+ duration: 4000,
803
+ });
804
+ positionBeforeOrientationChange.current = null; // Clear saved position since we're not changing
805
+ setIsOrientationChanging(false); // Show bar again since we're not changing
806
+ return;
807
+ }
808
+ // Check if we're in the bottom part of the screen and don't have enough space below
809
+ const spaceBelow = containerHeight - floating.bottom;
810
+ const spaceAbove = floating.top;
811
+ const needsVerticalSpace = estimatedVerticalHeight - floating.height; // Additional space needed
812
+ // If not enough space below but enough space above, expand upward
813
+ if (spaceBelow < needsVerticalSpace && spaceAbove >= needsVerticalSpace) {
814
+ // Calculate the new Y position so the bottom of the bar stays at the same place
815
+ const currentBottom = pixelPosition.y + floating.height;
816
+ const newPixelY = currentBottom - estimatedVerticalHeight;
817
+ setState(s => ({
818
+ ...s,
819
+ orientation: newOrientation,
820
+ verticalDirection: 'up',
821
+ }));
822
+ // Update pixel position and percentage after orientation change
823
+ requestAnimationFrame(() => {
824
+ requestAnimationFrame(() => {
825
+ const newY = Math.max(0, newPixelY);
826
+ setPixelPosition(prev => ({ ...prev, y: newY }));
827
+ const newPercentY = pixelsToPercent(newY, containerHeight);
828
+ setState(s => ({
829
+ ...s,
830
+ position: { ...s.position, y: newPercentY },
831
+ }));
832
+ setIsOrientationChanging(false);
833
+ });
834
+ });
835
+ return;
836
+ }
837
+ }
838
+ }
839
+ // Default case: just change orientation without special positioning
252
840
  setState(s => ({
253
841
  ...s,
254
842
  orientation: newOrientation,
843
+ verticalDirection: 'down',
255
844
  }));
256
- // Then, after DOM updates, adjust position to stay in bounds
257
845
  requestAnimationFrame(() => {
258
846
  requestAnimationFrame(() => {
259
847
  if (containerRef.current && floatingRef.current) {
@@ -278,21 +866,105 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
278
866
  }));
279
867
  }
280
868
  }
869
+ setIsOrientationChanging(false);
281
870
  });
282
871
  });
283
872
  };
284
873
  const removeItem = (itemId) => {
874
+ isLocalChange.current = true;
285
875
  setState(s => ({
286
876
  ...s,
287
877
  items: s.items.filter(item => item.id !== itemId),
288
878
  }));
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
879
  };
880
+ const getItemRightClickMenuItems = useCallback((item, index) => {
881
+ const hasSeparatorOnRight = index < state.items.length - 1 && state.items[index + 1]?.isSeparator;
882
+ const hasSeparatorOnLeft = index > 0 && state.items[index - 1]?.isSeparator;
883
+ return [
884
+ {
885
+ name: 'Rimuovi',
886
+ icon: _jsx(IconDelete, { fontSize: 16 }),
887
+ onClick: () => removeItem(item.id),
888
+ },
889
+ {
890
+ name: 'Aggiungi separatore a destra',
891
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
892
+ disabled: hasSeparatorOnRight,
893
+ onClick: () => {
894
+ isLocalChange.current = true;
895
+ const separator = createSeparator();
896
+ setState(s => {
897
+ const newItems = [...s.items];
898
+ newItems.splice(index + 1, 0, separator);
899
+ return { ...s, items: newItems };
900
+ });
901
+ },
902
+ },
903
+ {
904
+ name: 'Aggiungi separatore a sinistra',
905
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
906
+ disabled: hasSeparatorOnLeft,
907
+ onClick: () => {
908
+ isLocalChange.current = true;
909
+ const separator = createSeparator();
910
+ setState(s => {
911
+ const newItems = [...s.items];
912
+ newItems.splice(index, 0, separator);
913
+ return { ...s, items: newItems };
914
+ });
915
+ },
916
+ },
917
+ ...(!disbaleConfigMode ? [{
918
+ beginGroup: true,
919
+ name: SDKUI_Localizator.Configure,
920
+ icon: _jsx(IconSettings, { fontSize: 16 }),
921
+ onClick: () => {
922
+ if (!state.isConfigMode) {
923
+ toggleConfigMode();
924
+ }
925
+ },
926
+ }] : []),
927
+ {
928
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
929
+ icon: _jsx(IconRotate, { fontSize: 16 }),
930
+ onClick: toggleOrientation,
931
+ },
932
+ ];
933
+ }, [state.items, state.isConfigMode, state.orientation, removeItem, createSeparator, toggleConfigMode, toggleOrientation, disbaleConfigMode]);
934
+ const getSeparatorRightClickMenuItems = useCallback((index) => {
935
+ return [
936
+ {
937
+ name: 'Rimuovi',
938
+ icon: _jsx(IconDelete, { fontSize: 16 }),
939
+ onClick: () => removeItem(state.items[index].id),
940
+ },
941
+ {
942
+ name: 'Aggiungi separatore a destra',
943
+ icon: _jsx(IconSeparator, { style: { transform: 'rotate(-90deg)' }, fontSize: 16 }),
944
+ disabled: true,
945
+ },
946
+ {
947
+ name: 'Aggiungi separatore a sinistra',
948
+ icon: _jsx(IconSeparator, { fontSize: 16 }),
949
+ disabled: true,
950
+ },
951
+ ...(!disbaleConfigMode ? [{
952
+ beginGroup: true,
953
+ name: SDKUI_Localizator.Configure,
954
+ icon: _jsx(IconSettings, { fontSize: 16 }),
955
+ onClick: () => {
956
+ if (!state.isConfigMode) {
957
+ toggleConfigMode();
958
+ }
959
+ },
960
+ }] : []),
961
+ {
962
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
963
+ icon: _jsx(IconRotate, { fontSize: 16 }),
964
+ onClick: toggleOrientation,
965
+ },
966
+ ];
967
+ }, [state.items, state.isConfigMode, state.orientation, removeItem, toggleConfigMode, toggleOrientation, disbaleConfigMode]);
296
968
  // Drag and drop for reordering
297
969
  const handleDragStart = (e, index) => {
298
970
  if (!state.isConfigMode)
@@ -347,24 +1019,46 @@ const TMFloatingMenuBar = ({ containerRef, contextMenuItems = [], storageKey = '
347
1019
  setState(s => ({ ...s, draggedItemIndex: null }));
348
1020
  setDragOverIndex(null);
349
1021
  };
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) => {
1022
+ return (_jsxs(_Fragment, { children: [_jsx(S.Overlay, { "$visible": state.isConfigMode }), _jsxs(S.FloatingContainer, { ref: floatingRef, "$x": pixelPosition.x, "$y": pixelPosition.y, "$orientation": state.orientation, "$verticalDirection": state.verticalDirection, "$isDragging": state.isDragging, "$isConfigMode": state.isConfigMode, "$isConstrained": isConstrained, "$isHidden": isOrientationChanging, "$bgColor": bgColor, onContextMenu: state.isConfigMode ? (e) => e.preventDefault() : undefined, children: [!state.isConfigMode ? (_jsx(ContextMenu, { items: [
1023
+ ...(!disbaleConfigMode ? [{
1024
+ name: SDKUI_Localizator.Configure,
1025
+ icon: _jsx(IconSettings, { fontSize: 16 }),
1026
+ onClick: () => {
1027
+ if (!state.isConfigMode) {
1028
+ toggleConfigMode();
1029
+ }
1030
+ },
1031
+ }] : []),
1032
+ {
1033
+ name: state.orientation === 'horizontal' ? 'Floating bar verticale' : 'Floating bar orizzontale',
1034
+ icon: _jsx(IconRotate, { fontSize: 16, style: { transform: state.orientation === 'horizontal' ? 'rotate(90deg)' : 'rotate(0deg)' } }),
1035
+ onClick: toggleOrientation,
1036
+ },
1037
+ ], trigger: "right", children: _jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) }) })) : (_jsx(S.GripHandle, { "$orientation": state.orientation, onMouseDown: handleMouseDown, onTouchStart: handleTouchStart, onDoubleClick: handleGripDoubleClick, children: _jsx(IconDraggableDots, {}) })), _jsx(S.Separator, { "$orientation": state.orientation }), state.items.map((item, index) => {
1038
+ // Handle separator items
1039
+ if (item.isSeparator) {
1040
+ return (_jsx(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 ? (_jsxs(_Fragment, { children: [_jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }), _jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" })] })) : (_jsx(ContextMenu, { items: getSeparatorRightClickMenuItems(index), trigger: "right", children: _jsx(S.ItemSeparator, { "$orientation": state.orientation, "$isConfigMode": state.isConfigMode }) })) }, item.id));
1041
+ }
351
1042
  // Get current state (disabled and onClick) from contextMenuItems
352
- const currentState = getCurrentItemState(item.name);
353
- const isDisabled = currentState.disabled || false;
1043
+ const currentState = getCurrentItemState(item.id);
1044
+ // Prefer currentState.disabled if contextMenuItems has items, otherwise use item.disabled
1045
+ const isDisabled = (contextMenuItems.length > 0 && currentState.disabled !== undefined)
1046
+ ? currentState.disabled === true
1047
+ : item.disabled === true;
354
1048
  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)
1049
+ return (_jsx(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 ? (
1050
+ // Config mode: show remove button, no right-click menu
1051
+ _jsxs(_Fragment, { children: [item.staticItem ? (item.staticItem) : (_jsx(TMTooltip, { content: item.name, position: "bottom", children: _jsx(S.MenuButton, { onClick: () => { }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })), _jsx(S.RemoveButton, { onClick: () => removeItem(item.id), children: "\u00D7" })] })) : (
1052
+ // Normal mode: wrap in right-click context menu
1053
+ _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: () => {
1054
+ if (isDisabled)
363
1055
  return;
364
1056
  if (currentOnClick) {
365
1057
  currentOnClick();
366
1058
  }
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) }) }))] }));
1059
+ }, disabled: isDisabled, "$isActive": item.isToggle, children: item.icon }) })) })) }, item.id));
1060
+ }), !state.isConfigMode && !disbaleConfigMode && hasContextMenu && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getContextMenuItemsWithPinIcons(), trigger: "left", keepOpenOnClick: false, children: _jsx(S.ContextMenuButton, { children: _jsx(IconMenuVertical, {}) }) })), state.isConfigMode && state.items.length < maxItems && contextMenuItems.length > 0 && (_jsx(ContextMenu, { items: getPinContextMenuItems(), trigger: "left", keepOpenOnClick: true, children: _jsx(TMTooltip, { content: SDKUI_Localizator.Add, children: _jsx(S.AddButton, { children: _jsx(IconAdd, {}) }) }) })), state.isConfigMode && (_jsxs(_Fragment, { children: [_jsx(S.Separator, { "$orientation": state.orientation }), _jsxs(S.ButtonGroup, { "$orientation": state.orientation, children: [_jsx(TMTooltip, { content: SDKUI_Localizator.Undo, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.UndoButton, { onClick: handleUndo, disabled: !hasChanges(), children: _jsx(IconUndo, { fontSize: 18 }) }) }), _jsx(TMTooltip, { content: state.items.length === 0
1061
+ ? 'Devi aggiungere almeno un item'
1062
+ : SDKUI_Localizator.Save, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.ApplyButton, { onClick: toggleConfigMode, disabled: state.items.length === 0 || !hasChanges(), children: _jsx(IconSave, { fontSize: 20 }) }) }), _jsx(TMTooltip, { content: SDKUI_Localizator.Close, position: state.orientation === 'horizontal' ? 'right' : 'top', children: _jsx(S.CloseButton, { onClick: handleClose, children: _jsx(IconCloseOutline, { fontSize: 20 }) }) })] })] }))] })] }));
369
1063
  };
370
1064
  export default TMFloatingMenuBar;