@topconsultnpm/sdkui-react 6.22.0-t1 → 6.22.0-t3

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 (286) hide show
  1. package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +3 -3
  2. package/lib/components/NewComponents/ContextMenu/hooks.d.ts +1 -1
  3. package/lib/components/NewComponents/ContextMenu/styles.d.ts +45 -43
  4. package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +6 -6
  5. package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +80 -73
  6. package/lib/components/base/Styled.d.ts +92 -76
  7. package/lib/components/base/Styled.js +7 -5
  8. package/lib/components/base/TMAccordionNew.js +7 -7
  9. package/lib/components/base/TMAlert.d.ts +32 -32
  10. package/lib/components/base/TMAlert.js +6 -8
  11. package/lib/components/base/TMAreaManager.js +53 -55
  12. package/lib/components/base/TMButton.d.ts +13 -0
  13. package/lib/components/base/TMButton.js +139 -7
  14. package/lib/components/base/TMClosableList.js +3 -3
  15. package/lib/components/base/TMCustomButton.js +40 -48
  16. package/lib/components/base/TMDataGrid.d.ts +4 -0
  17. package/lib/components/base/TMDataGrid.js +292 -12
  18. package/lib/components/base/TMDataGridExportForm.js +39 -30
  19. package/lib/components/base/TMDropDownMenu.js +1 -1
  20. package/lib/components/base/TMEditorBase.d.ts +2 -0
  21. package/lib/components/base/TMFileManager.js +5 -3
  22. package/lib/components/base/TMFileManagerDataGridView.js +12 -3
  23. package/lib/components/base/TMFileManagerThumbnailItems.js +20 -10
  24. package/lib/components/base/TMFileManagerUtils.d.ts +6 -6
  25. package/lib/components/base/TMFileManagerUtils.js +37 -1
  26. package/lib/components/base/TMLayout.d.ts +2 -2
  27. package/lib/components/base/TMLayout.js +3 -3
  28. package/lib/components/base/TMList.js +1 -1
  29. package/lib/components/base/TMListView.js +3 -3
  30. package/lib/components/base/TMModal.d.ts +11 -0
  31. package/lib/components/base/TMModal.js +47 -13
  32. package/lib/components/base/TMPanel.d.ts +6 -0
  33. package/lib/components/base/TMPanel.js +141 -28
  34. package/lib/components/base/TMPopUp.js +5 -2
  35. package/lib/components/base/TMResizableMenu.js +2 -2
  36. package/lib/components/base/TMRightSidebar.js +1 -1
  37. package/lib/components/base/TMShowAllOrMaxItemsButton.js +1 -1
  38. package/lib/components/base/TMSpinner.d.ts +2 -1
  39. package/lib/components/base/TMSpinner.js +9 -7
  40. package/lib/components/base/TMTab.js +2 -2
  41. package/lib/components/base/TMTabControl.d.ts +106 -0
  42. package/lib/components/base/TMTabControl.js +664 -0
  43. package/lib/components/base/TMToggleButton.js +2 -2
  44. package/lib/components/base/TMToolbarCard.js +3 -3
  45. package/lib/components/base/TMTooltip.d.ts +2 -1
  46. package/lib/components/base/TMTooltip.js +24 -8
  47. package/lib/components/base/TMTreeView.d.ts +13 -7
  48. package/lib/components/base/TMTreeView.js +143 -70
  49. package/lib/components/base/TMWaitPanel.js +3 -3
  50. package/lib/components/base/waitPanel/TMWaitPanelConfirm.d.ts +12 -0
  51. package/lib/components/base/waitPanel/TMWaitPanelConfirm.js +185 -0
  52. package/lib/components/base/waitPanel/TMWaitPanelManager.d.ts +169 -0
  53. package/lib/components/base/waitPanel/TMWaitPanelManager.js +320 -0
  54. package/lib/components/base/waitPanel/TMWaitPanelView.d.ts +53 -0
  55. package/lib/components/base/waitPanel/TMWaitPanelView.js +274 -0
  56. package/lib/components/base/waitPanel/index.d.ts +1 -0
  57. package/lib/components/base/waitPanel/index.js +1 -0
  58. package/lib/components/choosers/TMCultureIDPicker.js +2 -2
  59. package/lib/components/choosers/TMDataListItemChooser.js +1 -1
  60. package/lib/components/choosers/TMDataListItemPicker.js +2 -2
  61. package/lib/components/choosers/TMDcmtTypeChooser.js +5 -4
  62. package/lib/components/choosers/TMDiskChooser.js +1 -1
  63. package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
  64. package/lib/components/choosers/TMDistinctValues.js +157 -141
  65. package/lib/components/choosers/TMDynDataListItemChooser.js +1 -1
  66. package/lib/components/choosers/TMImageIDChooser.js +1 -1
  67. package/lib/components/choosers/TMMetadataChooser.js +3 -3
  68. package/lib/components/choosers/TMPathChooser.js +17 -34
  69. package/lib/components/choosers/TMQuickSearchInfo.d.ts +13 -0
  70. package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
  71. package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
  72. package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
  73. package/lib/components/choosers/TMRelationChooser.js +1 -1
  74. package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
  75. package/lib/components/choosers/TMSelectedValuesSummary.js +89 -0
  76. package/lib/components/choosers/TMUserChooser.js +1 -1
  77. package/lib/components/editors/TMCheckBox.js +1 -1
  78. package/lib/components/editors/TMDateBox.d.ts +16 -1
  79. package/lib/components/editors/TMDateBox.js +90 -22
  80. package/lib/components/editors/TMDropDown.d.ts +3 -0
  81. package/lib/components/editors/TMDropDown.js +197 -5
  82. package/lib/components/editors/TMEditorStyled.d.ts +21 -43
  83. package/lib/components/editors/TMFormulaEditor.js +1 -1
  84. package/lib/components/editors/TMHtmlContentDisplay.js +1 -1
  85. package/lib/components/editors/TMLocalizedTextBox.js +1 -1
  86. package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
  87. package/lib/components/editors/TMMetadataEditor.js +27 -23
  88. package/lib/components/editors/TMMetadataValues.js +6 -6
  89. package/lib/components/editors/TMRadioButton.js +2 -2
  90. package/lib/components/editors/TMSummary.js +3 -3
  91. package/lib/components/editors/TMTextArea.d.ts +1 -0
  92. package/lib/components/editors/TMTextArea.js +25 -17
  93. package/lib/components/editors/TMTextBox.d.ts +2 -0
  94. package/lib/components/editors/TMTextBox.js +32 -21
  95. package/lib/components/editors/TMTextExpression.js +1 -1
  96. package/lib/components/features/archive/TMArchive.js +24 -5
  97. package/lib/components/features/assistant/TMHelpOnLineChat.d.ts +29 -0
  98. package/lib/components/features/assistant/TMHelpOnLineChat.js +1384 -0
  99. package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +4 -4
  100. package/lib/components/features/assistant/TMToppySpeechBubble.js +1 -1
  101. package/lib/components/features/assistant/dataset/Designer-Suggested-Questions.json +102 -0
  102. package/lib/components/features/assistant/dataset/Login-Suggested-Questions.json +32 -0
  103. package/lib/components/features/assistant/dataset/Surfer-Suggested-Questions.json +102 -0
  104. package/lib/components/features/documents/TMBatchUpdateForm.js +22 -9
  105. package/lib/components/features/documents/TMCopyToFolderForm.js +50 -42
  106. package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
  107. package/lib/components/features/documents/TMDcmtBlog.js +4 -4
  108. package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
  109. package/lib/components/features/documents/TMDcmtForm.js +281 -113
  110. package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
  111. package/lib/components/features/documents/TMDcmtFormActionButtons.js +79 -9
  112. package/lib/components/features/documents/TMDcmtIcon.d.ts +1 -3
  113. package/lib/components/features/documents/TMDcmtIcon.js +8 -8
  114. package/lib/components/features/documents/TMDcmtPreview.d.ts +7 -5
  115. package/lib/components/features/documents/TMDcmtPreview.js +16 -10
  116. package/lib/components/features/documents/TMDcmtTasks.js +2 -9
  117. package/lib/components/features/documents/TMDownloadRelationViewerSection.js +1 -1
  118. package/lib/components/features/documents/TMFileUploader.js +7 -7
  119. package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
  120. package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -108
  121. package/lib/components/features/documents/TMMasterInfoFields.d.ts +20 -0
  122. package/lib/components/features/documents/TMMasterInfoFields.js +185 -0
  123. package/lib/components/features/documents/TMMergeToPdfForm.d.ts +1 -2
  124. package/lib/components/features/documents/TMMergeToPdfForm.js +83 -56
  125. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +46 -0
  126. package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +320 -0
  127. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +370 -0
  128. package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +283 -0
  129. package/lib/components/features/documents/TMRelationViewer.d.ts +29 -9
  130. package/lib/components/features/documents/TMRelationViewer.js +310 -208
  131. package/lib/components/features/documents/copyAndMergeDcmtsShared.d.ts +1 -1
  132. package/lib/components/features/documents/relationTreeState.d.ts +32 -0
  133. package/lib/components/features/documents/relationTreeState.js +89 -0
  134. package/lib/components/features/search/TMMetadataOutputForm.js +1 -1
  135. package/lib/components/features/search/TMMetadataSorterForm.js +1 -1
  136. package/lib/components/features/search/TMSavedQuerySelector.js +2 -2
  137. package/lib/components/features/search/TMSearch.d.ts +1 -1
  138. package/lib/components/features/search/TMSearch.js +172 -34
  139. package/lib/components/features/search/TMSearchQueryEditor.js +7 -5
  140. package/lib/components/features/search/TMSearchQueryPanel.d.ts +14 -4
  141. package/lib/components/features/search/TMSearchQueryPanel.js +153 -62
  142. package/lib/components/features/search/TMSearchResult.d.ts +21 -0
  143. package/lib/components/features/search/TMSearchResult.js +461 -152
  144. package/lib/components/features/search/TMSignatureInfoContent.js +1 -1
  145. package/lib/components/features/search/TMTreeSelector.js +130 -83
  146. package/lib/components/features/search/TMViewHistoryDcmt.js +14 -25
  147. package/lib/components/features/tasks/TMTaskForm.js +1 -1
  148. package/lib/components/features/tasks/TMTaskFormUtils.js +3 -3
  149. package/lib/components/features/tasks/TMTasksAgenda.js +1 -1
  150. package/lib/components/features/tasks/TMTasksCalendar.js +1 -1
  151. package/lib/components/features/tasks/TMTasksHeader.js +1 -1
  152. package/lib/components/features/tasks/TMTasksUtilsView.js +1 -1
  153. package/lib/components/features/tasks/TMTasksView.js +97 -102
  154. package/lib/components/features/wg/TMWGsCopyMoveForm.js +92 -92
  155. package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
  156. package/lib/components/features/workflow/TMWorkflowPopup.js +9 -5
  157. package/lib/components/features/workflow/diagram/ConnectionComponent.js +3 -3
  158. package/lib/components/features/workflow/diagram/DiagramItemComponent.js +3 -3
  159. package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
  160. package/lib/components/features/workflow/diagram/DiagramItemSvgContent.js +1 -1
  161. package/lib/components/features/workflow/diagram/RecipientList.js +1 -1
  162. package/lib/components/features/workflow/diagram/WFDiagram.js +4 -4
  163. package/lib/components/features/workflow/diagram/WorkitemRecipientsEditor.d.ts +3 -1
  164. package/lib/components/forms/Login/ChangePasswordInputs.d.ts +3 -1
  165. package/lib/components/forms/Login/Menu.js +1 -1
  166. package/lib/components/forms/Login/OTPReader.js +1 -1
  167. package/lib/components/forms/Login/PasswordStrengthChecker.js +1 -1
  168. package/lib/components/forms/Login/RapidAccessLogin.js +2 -2
  169. package/lib/components/forms/Login/RecoverPasswordFlow.js +2 -2
  170. package/lib/components/forms/Login/SelectBox.js +1 -1
  171. package/lib/components/forms/Login/StepIndicator.js +1 -1
  172. package/lib/components/forms/Login/TMLoginForm.js +7 -7
  173. package/lib/components/forms/Login/TextBox.js +1 -1
  174. package/lib/components/forms/TMApplyForm.js +3 -3
  175. package/lib/components/forms/TMChooserForm.js +1 -1
  176. package/lib/components/forms/TMResultDialog.js +1 -1
  177. package/lib/components/forms/TMSaveForm.js +3 -3
  178. package/lib/components/grids/TMBlogAttachments.js +14 -3
  179. package/lib/components/grids/TMBlogHeader.js +5 -1
  180. package/lib/components/grids/TMBlogsPost.d.ts +2 -0
  181. package/lib/components/grids/TMBlogsPost.js +122 -99
  182. package/lib/components/grids/TMBlogsPostUtils.d.ts +1 -0
  183. package/lib/components/grids/TMBlogsPostUtils.js +15 -4
  184. package/lib/components/grids/TMRecentsManager.js +4 -4
  185. package/lib/components/grids/TMValidationItemsList.js +1 -1
  186. package/lib/components/index.d.ts +6 -0
  187. package/lib/components/index.js +5 -0
  188. package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
  189. package/lib/components/layout/panelManager/TMPanelManagerContainer.js +25 -15
  190. package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
  191. package/lib/components/layout/panelManager/TMPanelManagerToolbar.d.ts +4 -5
  192. package/lib/components/layout/panelManager/TMPanelManagerToolbar.js +1 -1
  193. package/lib/components/layout/panelManager/TMPanelWrapper.js +3 -3
  194. package/lib/components/layout/panelManager/types.d.ts +3 -0
  195. package/lib/components/layout/panelManager/utils.js +13 -11
  196. package/lib/components/pages/TMPage.js +30 -32
  197. package/lib/components/query/TMQueryCountButton.js +1 -1
  198. package/lib/components/query/TMQueryEditor.d.ts +17 -10
  199. package/lib/components/query/TMQueryEditor.js +9 -8
  200. package/lib/components/sidebar/TMCommandsPanel.d.ts +4 -4
  201. package/lib/components/sidebar/TMCommandsPanel.js +1 -1
  202. package/lib/components/sidebar/TMHeader.d.ts +1 -0
  203. package/lib/components/sidebar/TMHeader.js +37 -27
  204. package/lib/components/sidebar/TMSidebar.js +1 -1
  205. package/lib/components/sidebar/TMSidebarItem.js +1 -1
  206. package/lib/components/viewers/TMMidViewer.js +6 -3
  207. package/lib/components/viewers/TMTidViewer.js +89 -28
  208. package/lib/components/wizard/TMStepIndicator.js +3 -3
  209. package/lib/css/tm-sdkui.css +1 -1
  210. package/lib/devextreme-license.d.ts +1 -0
  211. package/lib/devextreme-license.js +3 -0
  212. package/lib/helper/Enum_Localizator.d.ts +4 -4
  213. package/lib/helper/Enum_Localizator.js +1 -0
  214. package/lib/helper/Globalization.d.ts +1 -1
  215. package/lib/helper/SDKUI_Globals.d.ts +110 -0
  216. package/lib/helper/SDKUI_Globals.js +151 -0
  217. package/lib/helper/SDKUI_Localizator.d.ts +756 -677
  218. package/lib/helper/SDKUI_Localizator.js +790 -0
  219. package/lib/helper/TMAppUpdatedModal.d.ts +2 -0
  220. package/lib/helper/TMAppUpdatedModal.js +134 -0
  221. package/lib/helper/TMIcons.d.ts +1 -0
  222. package/lib/helper/TMIcons.js +4 -3
  223. package/lib/helper/TMPdfViewer.js +1 -1
  224. package/lib/helper/TMToppyMessage.js +1 -1
  225. package/lib/helper/TMUtils.d.ts +51 -4
  226. package/lib/helper/TMUtils.js +273 -12
  227. package/lib/helper/ZipManager.d.ts +1 -1
  228. package/lib/helper/ZipManager.js +2 -1
  229. package/lib/helper/checkinCheckoutManager.d.ts +1 -1
  230. package/lib/helper/checkinCheckoutManager.js +1 -1
  231. package/lib/helper/dataGridSearchHelper.d.ts +28 -0
  232. package/lib/helper/dataGridSearchHelper.js +51 -0
  233. package/lib/helper/dcmtsHelper.d.ts +22 -0
  234. package/lib/helper/dcmtsHelper.js +60 -1
  235. package/lib/helper/helpers.d.ts +24 -2
  236. package/lib/helper/helpers.js +59 -3
  237. package/lib/helper/index.d.ts +2 -0
  238. package/lib/helper/index.js +2 -0
  239. package/lib/helper/queryHelper.d.ts +6 -1
  240. package/lib/helper/queryHelper.js +8 -3
  241. package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
  242. package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
  243. package/lib/hooks/useArchiveListForm.d.ts +39 -0
  244. package/lib/hooks/useArchiveListForm.js +46 -0
  245. package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
  246. package/lib/hooks/useCaseFlowApprove.js +157 -0
  247. package/lib/hooks/useCheckInOutOperations.d.ts +0 -6
  248. package/lib/hooks/useCheckInOutOperations.js +14 -34
  249. package/lib/hooks/useDataUserIdItem.d.ts +1 -0
  250. package/lib/hooks/useDataUserIdItem.js +1 -0
  251. package/lib/hooks/useDcmtOperations.d.ts +3 -12
  252. package/lib/hooks/useDcmtOperations.js +187 -179
  253. package/lib/hooks/useDocumentOperations.d.ts +5 -0
  254. package/lib/hooks/useDocumentOperations.js +64 -33
  255. package/lib/hooks/useInputDialog.js +1 -1
  256. package/lib/hooks/useMultiMasterDetailDcmts.d.ts +98 -0
  257. package/lib/hooks/useMultiMasterDetailDcmts.js +758 -0
  258. package/lib/hooks/usePreventFileDrop.js +9 -0
  259. package/lib/hooks/useQueryParametersDialog.js +1 -1
  260. package/lib/hooks/useRelatedDocuments.d.ts +1 -0
  261. package/lib/hooks/useRelatedDocuments.js +22 -27
  262. package/lib/hooks/useShowDossiersV5.d.ts +12 -0
  263. package/lib/hooks/useShowDossiersV5.js +37 -0
  264. package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
  265. package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
  266. package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
  267. package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
  268. package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
  269. package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
  270. package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
  271. package/lib/hooks/useTMDistinctValuesSource.js +105 -0
  272. package/lib/hooks/useWorkflowApprove.js +33 -17
  273. package/lib/index.d.ts +2 -0
  274. package/lib/index.js +5 -2
  275. package/lib/services/caseflow/CaseFlowService.d.ts +277 -0
  276. package/lib/services/caseflow/CaseFlowService.js +761 -0
  277. package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
  278. package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
  279. package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
  280. package/lib/services/caseflow/caseFlowNameCache.js +116 -0
  281. package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
  282. package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
  283. package/lib/services/index.d.ts +4 -0
  284. package/lib/services/index.js +4 -0
  285. package/lib/services/platform_services.d.ts +4 -4
  286. package/package.json +16 -13
@@ -0,0 +1,664 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
3
+ import styled, { css, keyframes } from 'styled-components';
4
+ import TMContextMenu from '../NewComponents/ContextMenu/TMContextMenu';
5
+ import TMTooltip from './TMTooltip';
6
+ import { ButtonNames, TMMessageBoxManager } from './TMPopUp';
7
+ import { AppModules, SDK_Globals } from '@topconsultnpm/sdk-ts';
8
+ import { getAppModuleButtonColors, getAppModuleGradient, IconAdd, IconCloseOutline, IconEdit, IconLeft, IconRight, SDKUI_Localizator } from '../../helper';
9
+ // =============================================
10
+ // TMTabControl: gestore di tab generico (barra + contenuto), senza DevExtreme.
11
+ //
12
+ // <TMTabControl hideBarWhenSingle onTabClose={...} onTabRename={...} onTabReorder={...}>
13
+ // <TMTabPanel id='main' label='Ricerca' closable={false}>{ ...contenuto... }</TMTabPanel>
14
+ // </TMTabControl>
15
+ //
16
+ // Comportamenti chiave:
17
+ // hideBarWhenSingle -> con un solo tab la barra non si disegna: si vede solo il contenuto.
18
+ // I tab non attivi restano sempre montati e solo spostati fuori schermo: nulla si ricarica ne perde stato.
19
+ // onTabClose -> mostra la 'x' sui tab e la voce Chiudi nel menu contestuale.
20
+ // onTabRename -> doppio clic (o voce Rinomina) per modificare il titolo in linea.
21
+ // onTabReorder -> tab spostabili con il trascinamento; il singolo tab si blocca con movable={false}.
22
+ // tabMenuItems -> voci del chiamante nel menu del tasto destro sul tab, prima di Rinomina e Chiudi.
23
+ // contentAnimation -> effetto del contenuto al cambio tab: 'fade' (default), 'slide', 'zoom' o 'none'.
24
+ // swipeEnabled -> su touch si cambia tab trascinando il contenuto di lato.
25
+ // toolbar -> contenuto del chiamante in coda alla barra, a destra dei tab (azioni di pagina).
26
+ //
27
+ // Da tastiera, con il focus sulla barra: frecce destra/giu al tab successivo, sinistra/su al precedente,
28
+ // Home ed End al primo e all'ultimo. I tab disabilitati vengono saltati.
29
+ //
30
+ // Il contenuto e sempre montato nell'ordine di prima apparizione dei tab: spostarli cambia solo la
31
+ // barra, non il DOM del contenuto, cosi nulla viene rimontato ne perde stato o scroll.
32
+ // =============================================
33
+ const ACCENT = '#0F6CBD'; // Bordo del campo di rinomina e segnaposto di rilascio
34
+ const TEXT_ACTIVE = '#1B2B44'; // Titolo del tab attivo: il blu scuro dei testi dell'applicazione
35
+ const TAB_BG = '#FFFFFF'; // Linguetta attiva
36
+ const TAB_RADIUS = 10;
37
+ const TAB_CORNERS = `${TAB_RADIUS}px ${TAB_RADIUS}px 0 0`; // Linguetta arrotondata solo in alto, appoggiata al contenuto
38
+ const SWIPE_MIN_DISTANCE = 60; // Trascinamento minimo in px perche lo swipe valga come cambio tab
39
+ const TAB_DRAG_MIME = 'application/x-tm-tab'; // Formato dei dati trascinati: riconosciuto solo dai tab
40
+ /** Colore del modulo in uso: Surfer blu #2559a5, Designer bordeaux #482234, Orchestrator verde #1d6f42. */
41
+ const moduleColor = () => getAppModuleButtonColors(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER).base;
42
+ // Un tema per voce: sono funzioni perche module e moduleFilled leggono il colore del modulo al momento dell'uso.
43
+ const VARIANTS = {
44
+ // Barra senza fondo: solo il tab attivo e una scheda, schiarita verso il basso fino a fondersi col contenuto
45
+ card: () => {
46
+ const accent = moduleColor();
47
+ return {
48
+ stripBg: 'transparent',
49
+ tabBg: `linear-gradient(180deg, ${accent}1F 0%, ${accent}0A 55%, ${TAB_BG} 100%)`,
50
+ idleTabBg: 'transparent',
51
+ tabRadius: TAB_CORNERS,
52
+ tabGap: '2px',
53
+ textColor: accent,
54
+ idleTextColor: `${accent}CC`,
55
+ stripPadding: '0 2px',
56
+ divider: '#D6DEEA',
57
+ // Nessuna ombra: il tab attivo si riconosce dal fondo sfumato e dal titolo nel colore del modulo
58
+ tabShadow: 'none',
59
+ joins: false,
60
+ tabHover: `${accent}12`,
61
+ buttonHover: `${accent}1A`,
62
+ buttonActive: `${accent}29`
63
+ };
64
+ },
65
+ // Le tinte del modulo in uso (Surfer blu, Designer bordeaux, Orchestrator verde) su tab bianco
66
+ module: () => {
67
+ const accent = moduleColor();
68
+ return {
69
+ stripBg: `${accent}14`,
70
+ tabBg: TAB_BG,
71
+ idleTabBg: 'transparent',
72
+ tabRadius: TAB_CORNERS,
73
+ tabGap: '0',
74
+ textColor: accent,
75
+ idleTextColor: `${accent}CC`,
76
+ stripPadding: `0 ${TAB_RADIUS}px`,
77
+ divider: `${accent}33`,
78
+ tabShadow: 'none',
79
+ joins: true,
80
+ tabHover: 'rgba(255, 255, 255, 0.55)',
81
+ buttonHover: 'rgba(255, 255, 255, 0.75)',
82
+ buttonActive: 'rgba(255, 255, 255, 0.95)'
83
+ };
84
+ },
85
+ // Come module, ma il tab attivo e riempito dal gradiente del modulo e il titolo e bianco
86
+ moduleFilled: () => ({
87
+ ...VARIANTS.module(),
88
+ tabBg: getAppModuleGradient(SDK_Globals.tmSession?.SessionDescr?.appModuleID ?? AppModules.SURFER),
89
+ textColor: '#FFFFFF',
90
+ // Un raccordo non si puo dipingere con un gradiente: il tab resta staccato dal contenuto
91
+ joins: false
92
+ })
93
+ };
94
+ /** Contenitore della barra: frecce ai lati, striscia dei tab in mezzo ed eventuale toolbar in coda. */
95
+ const TabsHeader = styled.div `
96
+ display: flex;
97
+ align-items: flex-end;
98
+ width: 100%;
99
+ flex-shrink: 0;
100
+ padding: 4px 4px 0 4px;
101
+ background: ${({ $v }) => $v.stripBg};
102
+ `;
103
+ /** Striscia scorrevole dei tab: la scrollbar resta nascosta, si scorre con le frecce. */
104
+ const TabsStrip = styled.div `
105
+ display: flex;
106
+ align-items: flex-end;
107
+ flex: 1;
108
+ min-width: 0;
109
+ gap: ${({ $v }) => $v.tabGap};
110
+ padding: ${({ $v }) => $v.stripPadding};
111
+ overflow-x: auto;
112
+ scroll-behavior: smooth;
113
+ scrollbar-width: none;
114
+ -ms-overflow-style: none;
115
+ &::-webkit-scrollbar { display: none; }
116
+ `;
117
+ /** Freccia di scorrimento: compare solo se i tab non entrano nella barra. */
118
+ const NavButton = styled.button `
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ width: 26px;
123
+ height: 28px;
124
+ margin-bottom: 3px;
125
+ flex-shrink: 0;
126
+ padding: 0;
127
+ border: none;
128
+ border-radius: 50%;
129
+ background: transparent;
130
+ color: ${({ $v }) => $v.idleTextColor};
131
+ cursor: pointer;
132
+ transition: background 0.18s ease-out, color 0.18s ease-out;
133
+ &:hover { background: ${({ $v }) => $v.buttonHover}; color: ${TEXT_ACTIVE}; }
134
+ &:focus, &:focus-visible { outline: none; }
135
+ `;
136
+ /** Pulsante '+' subito dopo l'ultimo tab, come il nuovo tab del browser. */
137
+ const AddButton = styled.button `
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: center;
141
+ width: 28px;
142
+ height: 28px;
143
+ margin: 0 0 3px 4px;
144
+ flex-shrink: 0;
145
+ padding: 0;
146
+ border: none;
147
+ border-radius: 50%;
148
+ background: transparent;
149
+ color: ${({ $v }) => $v.idleTextColor};
150
+ cursor: pointer;
151
+ transition: background 0.18s ease-out, color 0.18s ease-out;
152
+ &:hover { background: ${({ $v }) => $v.buttonHover}; color: ${TEXT_ACTIVE}; }
153
+ &:active { background: ${({ $v }) => $v.buttonActive}; }
154
+ &:focus, &:focus-visible { outline: none; }
155
+ `;
156
+ /** Contenuto libero del chiamante in coda alla barra: azioni di pagina allineate a destra dei tab.
157
+ E' position: relative cosi un menu a tendina del chiamante puo posizionarsi rispetto a quest'area. */
158
+ const TabsToolbar = styled.div `
159
+ display: flex;
160
+ align-items: center;
161
+ flex-shrink: 0;
162
+ margin-bottom: 3px;
163
+ padding-left: 8px;
164
+ position: relative;
165
+ `;
166
+ /** Area del contenuto: fa da riferimento ai pannelli nascosti, che restano montati ma non impaginati. */
167
+ const ContentArea = styled.div `
168
+ position: relative;
169
+ display: flex;
170
+ flex-direction: column;
171
+ flex: 1;
172
+ min-height: 0;
173
+ `;
174
+ const ANIM_FADE = keyframes `
175
+ from { opacity: 0; transform: translate3d(0, 10px, 0); }
176
+ 50% { opacity: 1; }
177
+ to { opacity: 1; transform: translate3d(0, 0, 0); }
178
+ `;
179
+ // Entra appena piu piccolo e leggermente piu in basso: sembra posarsi al suo posto invece di comparire
180
+ const ANIM_ZOOM = keyframes `
181
+ from { opacity: 0; transform: translate3d(0, 6px, 0) scale3d(0.985, 0.985, 1); }
182
+ 50% { opacity: 1; }
183
+ to { opacity: 1; transform: translate3d(0, 0, 0) scale3d(1, 1, 1); }
184
+ `;
185
+ // Lo scorrimento segue il verso dello spostamento: da destra andando avanti, da sinistra tornando indietro
186
+ const ANIM_SLIDE_FORWARD = keyframes `
187
+ from { opacity: 0; transform: translate3d(24px, 0, 0); }
188
+ 50% { opacity: 1; }
189
+ to { opacity: 1; transform: translate3d(0, 0, 0); }
190
+ `;
191
+ const ANIM_SLIDE_BACK = keyframes `
192
+ from { opacity: 0; transform: translate3d(-24px, 0, 0); }
193
+ 50% { opacity: 1; }
194
+ to { opacity: 1; transform: translate3d(0, 0, 0); }
195
+ `;
196
+ const pickAnimation = (animation, forward) => {
197
+ switch (animation) {
198
+ case 'fade': return ANIM_FADE;
199
+ case 'zoom': return ANIM_ZOOM;
200
+ case 'slide': return forward ? ANIM_SLIDE_FORWARD : ANIM_SLIDE_BACK;
201
+ default: return undefined;
202
+ }
203
+ };
204
+ /** Contenitore di un singolo tab: l'animazione parte da sola perche il pannello inattivo non ne ha nessuna. */
205
+ const ContentPane = styled.div `
206
+ ${({ $active, $anim, $duration, $phase }) => $active && $anim && $phase !== 'done' && css `
207
+ /* Livello grafico dedicato creato subito, mentre il pannello e ancora trasparente: quando il
208
+ movimento parte non resta nulla da ridisegnare e l'effetto scorre senza perdere fotogrammi */
209
+ will-change: opacity, transform;
210
+ backface-visibility: hidden;
211
+ transform: translateZ(0);
212
+ opacity: 0;
213
+
214
+ /* Decelerazione lunga e continua: parte deciso e si posa, senza strappi ne rimbalzi */
215
+ ${$phase === 'run' && css `animation: ${$anim} ${$duration}ms cubic-bezier(0.32, 0.72, 0, 1) both;`}
216
+
217
+ /* Chi ha chiesto meno animazioni nel sistema vede il cambio tab immediato */
218
+ @media (prefers-reduced-motion: reduce) { animation: none; opacity: 1; transform: none; }
219
+ `}
220
+ `;
221
+ const TabSeparator = styled.div `
222
+ width: 1px;
223
+ height: 18px;
224
+ margin-bottom: 8px;
225
+ flex-shrink: 0;
226
+ background: ${({ $v }) => $v.divider};
227
+ `;
228
+ const DropMark = styled.div `
229
+ position: absolute;
230
+ top: 2px;
231
+ bottom: 0;
232
+ width: 3px;
233
+ border-radius: 2px;
234
+ background: ${ACCENT};
235
+ left: ${({ $side }) => ($side === 'left' ? '-2px' : 'auto')};
236
+ right: ${({ $side }) => ($side === 'right' ? '-2px' : 'auto')};
237
+ `;
238
+ const TabItem = styled.div `
239
+ position: relative;
240
+ display: inline-flex;
241
+ align-items: center;
242
+ gap: 2px;
243
+ height: 32px;
244
+ padding: 0 6px 0 12px;
245
+ max-width: 330px;
246
+ user-select: none;
247
+ border-radius: ${({ $v }) => $v.tabRadius};
248
+ background: ${({ $active, $v }) => ($active ? $v.tabBg : $v.idleTabBg)};
249
+ /* Stile card: ombra verso l'alto, sotto il tab poggia sul contenuto */
250
+ box-shadow: ${({ $active, $v }) => ($active ? $v.tabShadow : 'none')};
251
+ opacity: ${({ $dragging }) => ($dragging ? 0.4 : 1)};
252
+ z-index: ${({ $active }) => ($active ? 2 : 1)};
253
+ transition: background 0.18s ease-out, box-shadow 0.18s ease-out, opacity 0.18s ease-out;
254
+ &:hover { background: ${({ $active, $v }) => ($active ? $v.tabBg : $v.tabHover)}; }
255
+ &::before, &::after {
256
+ content: '';
257
+ position: absolute;
258
+ bottom: 0;
259
+ width: ${TAB_RADIUS}px;
260
+ height: ${TAB_RADIUS}px;
261
+ display: ${({ $active, $v }) => ($active && $v.joins ? 'block' : 'none')};
262
+ }
263
+ &::before {
264
+ left: -${TAB_RADIUS}px;
265
+ background: ${({ $v }) => $v.joins ? `radial-gradient(circle at 0 0, transparent ${TAB_RADIUS - 0.5}px, ${$v.tabBg} ${TAB_RADIUS}px)` : 'none'};
266
+ }
267
+ &::after {
268
+ right: -${TAB_RADIUS}px;
269
+ background: ${({ $v }) => $v.joins ? `radial-gradient(circle at ${TAB_RADIUS}px 0, transparent ${TAB_RADIUS - 0.5}px, ${$v.tabBg} ${TAB_RADIUS}px)` : 'none'};
270
+ }
271
+ `;
272
+ const TabButton = styled.button `
273
+ display: inline-flex;
274
+ align-items: center;
275
+ gap: 6px;
276
+ max-width: 290px;
277
+ height: 100%;
278
+ padding: 0 8px 0 0;
279
+ border: none;
280
+ background: none;
281
+ font-family: inherit;
282
+ font-size: 13px;
283
+ font-weight: ${({ $active }) => ($active ? 600 : 500)};
284
+ /* !important perche contenitori come l'intestazione del TMPanel impongono il proprio colore
285
+ a tutti i discendenti con '*, & { color: ... !important }': senza, il titolo lo eredita da li */
286
+ color: ${({ $active, $textColor, $idleColor }) => ($active ? $textColor : $idleColor)} !important;
287
+ white-space: nowrap;
288
+ overflow: hidden;
289
+ text-overflow: ellipsis;
290
+ cursor: pointer;
291
+ &:disabled { color: #BDBDBD !important; cursor: default; }
292
+ &:focus, &:focus-visible { outline: none; }
293
+ `;
294
+ /** Pulsante 'x' per chiudere il tab: stesso colore del titolo accanto. */
295
+ const CloseButton = styled.button `
296
+ display: flex;
297
+ align-items: center;
298
+ justify-content: center;
299
+ width: 18px;
300
+ height: 18px;
301
+ flex-shrink: 0;
302
+ padding: 0;
303
+ border: none;
304
+ border-radius: 4px;
305
+ background: none;
306
+ color: ${({ $color }) => $color} !important;
307
+ font-size: 14px;
308
+ line-height: 1;
309
+ font-family: inherit;
310
+ cursor: pointer;
311
+
312
+ &:hover { background: rgba(0, 0, 0, 0.15); }
313
+ &:focus, &:focus-visible { outline: none; }
314
+ `;
315
+ /** Casella di testo mostrata al posto del titolo durante la rinomina. */
316
+ const RenameInput = styled.input `
317
+ margin: 4px 2px 4px 10px;
318
+ padding: 2px 4px;
319
+ width: 130px;
320
+ font-family: inherit;
321
+ font-size: 13px;
322
+ font-weight: 600;
323
+ color: ${TEXT_ACTIVE};
324
+ background: ${TAB_BG};
325
+ border: 1px solid ${ACCENT};
326
+ border-radius: 4px;
327
+ outline: none;
328
+ `;
329
+ /** Badge numerico a pillola accanto al titolo (es. conteggio errori). */
330
+ const TabBadge = styled.span `
331
+ background: ${({ $color }) => $color};
332
+ color: #fff !important;
333
+ font-size: 11px;
334
+ font-weight: 700;
335
+ line-height: 16px;
336
+ min-width: 16px;
337
+ padding: 0 5px;
338
+ border-radius: 999em;
339
+ text-align: center;
340
+ `;
341
+ const MAX_LABEL_CHARS = 30;
342
+ const LABEL_COUNTER = /\s*\(\d+\)$/;
343
+ /**
344
+ * Titolo tagliato ai primi MAX_LABEL_CHARS caratteri del nome, con i puntini di sospensione e il
345
+ * numero progressivo in coda (es. 'NomeLunghissimoLunghi... (2)'). undefined se il titolo ci sta gia.
346
+ */
347
+ const shortenLabel = (label) => {
348
+ const counter = label.match(LABEL_COUNTER)?.[0] ?? '';
349
+ const name = label.slice(0, label.length - counter.length);
350
+ return name.length > MAX_LABEL_CHARS ? `${name.slice(0, MAX_LABEL_CHARS).trimEnd()}…${counter}` : undefined;
351
+ };
352
+ /** Contenitore dichiarativo di un tab: figlio diretto di TMTabControl, non renderizza nulla di suo. */
353
+ export const TMTabPanel = ({ children }) => _jsx(_Fragment, { children: children });
354
+ const TMTabControl = (props) => {
355
+ const { selectedId, defaultSelectedId, onSelectedIdChange, hideBarWhenSingle = false, onTabClose, closableWhenSingle = false, onTabRename, onTabReorder, onTabAdd, tabMenuItems, addCaption = SDKUI_Localizator.AddTab, renameCaption = SDKUI_Localizator.Rename, closeCaption = SDKUI_Localizator.Close, closeOtherCaption = SDKUI_Localizator.CloseOtherTabs, variant = 'module', textColor, idleTextColor, contentAnimation = 'none', animationDuration = 300, swipeEnabled = true, contentStyle, style, toolbar, children } = props;
356
+ const v = useMemo(() => VARIANTS[variant](), [variant]);
357
+ const titleColor = textColor ?? v.textColor;
358
+ const titleIdleColor = idleTextColor ?? v.idleTextColor;
359
+ // Legge i TMTabPanel figli scartando quelli senza id o nascosti.
360
+ const panels = useMemo(() => React.Children.toArray(children)
361
+ .filter((c) => React.isValidElement(c) && c.props?.id != null)
362
+ .filter(c => c.props.visible !== false), [children]);
363
+ const [internalId, setInternalId] = useState(defaultSelectedId);
364
+ const [editingId, setEditingId] = useState(undefined); // Tab con il titolo in rinomina
365
+ const [dragId, setDragId] = useState(undefined); // Tab che si sta trascinando
366
+ const [dropId, setDropId] = useState(undefined); // Tab sopra cui si rilascerebbe
367
+ const [menu, setMenu] = useState({ visible: false, position: { x: 0, y: 0 } });
368
+ const [arrows, setArrows] = useState({ left: false, right: false });
369
+ const stripRef = useRef(null);
370
+ // Ordine di prima apparizione dei tab: il contenuto resta montato sempre nella stessa posizione
371
+ // del DOM, cosi spostare o rinominare un tab non rimonta e non ricarica nulla.
372
+ const mountOrder = useRef(new Map());
373
+ const mountSeq = useRef(0);
374
+ panels.forEach(p => { if (!mountOrder.current.has(p.props.id))
375
+ mountOrder.current.set(p.props.id, ++mountSeq.current); });
376
+ const mountedPanels = useMemo(() => [...panels].sort((a, b) => (mountOrder.current.get(a.props.id) ?? 0) - (mountOrder.current.get(b.props.id) ?? 0)), [panels]);
377
+ // Il tab attivo: prop in modalita controllata, altrimenti stato interno, altrimenti il primo.
378
+ const activeId = selectedId ?? internalId ?? panels[0]?.props.id;
379
+ const activePanel = panels.find(p => p.props.id === activeId);
380
+ // Con un solo tab e hideBarWhenSingle la barra sparisce: resta solo il contenuto.
381
+ const showBar = !(hideBarWhenSingle && panels.length <= 1);
382
+ // Se il tab attivo sparisce (chiuso o nascosto) ripiega sul primo disponibile.
383
+ useEffect(() => {
384
+ if (activePanel || panels.length === 0)
385
+ return;
386
+ const fallbackId = panels[0].props.id;
387
+ if (selectedId === undefined)
388
+ setInternalId(fallbackId);
389
+ onSelectedIdChange?.(fallbackId);
390
+ }, [activePanel, panels, selectedId, onSelectedIdChange]);
391
+ // Le frecce compaiono solo quando i tab non entrano tutti nella barra.
392
+ const refreshArrows = useCallback(() => {
393
+ const el = stripRef.current;
394
+ if (!el)
395
+ return;
396
+ const maxScroll = el.scrollWidth - el.clientWidth;
397
+ const next = { left: el.scrollLeft > 1, right: el.scrollLeft < maxScroll - 1 };
398
+ setArrows(prev => (prev.left === next.left && prev.right === next.right) ? prev : next);
399
+ }, []);
400
+ // Firma dei titoli: rinominando un tab cambia la larghezza della striscia e le frecce vanno ricalcolate
401
+ const labelsSignature = panels.map(p => typeof p.props.label === 'string' ? p.props.label : '').join('|');
402
+ useEffect(() => {
403
+ const el = stripRef.current;
404
+ if (!el)
405
+ return;
406
+ refreshArrows();
407
+ const observer = new ResizeObserver(refreshArrows);
408
+ observer.observe(el);
409
+ return () => observer.disconnect();
410
+ }, [refreshArrows, panels.length, labelsSignature, showBar]);
411
+ // Cambiando tab quello attivo viene portato a vista se era fuori dalla parte visibile.
412
+ useEffect(() => {
413
+ if (!activeId)
414
+ return;
415
+ stripRef.current?.querySelector(`[data-tab-id="${activeId}"]`)?.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'smooth' });
416
+ }, [activeId]);
417
+ // Verso dell'ultimo cambio tab: serve allo 'slide', che entra dal lato da cui si arriva.
418
+ const activeIndex = panels.findIndex(p => p.props.id === activeId);
419
+ const prevIndexRef = useRef(activeIndex);
420
+ const goingForward = activeIndex >= prevIndexRef.current;
421
+ useEffect(() => { prevIndexRef.current = activeIndex; }, [activeIndex]);
422
+ const animation = useMemo(() => pickAnimation(contentAnimation, goingForward), [contentAnimation, goingForward]);
423
+ const [animPhase, setAnimPhase] = useState('done');
424
+ useLayoutEffect(() => {
425
+ if (!animation) {
426
+ setAnimPhase('done');
427
+ return;
428
+ }
429
+ setAnimPhase('hold');
430
+ let end = 0;
431
+ // Il fotogramma successivo arriva a disegno del contenuto gia fatto: da li in poi resta solo da muovere
432
+ const frame = requestAnimationFrame(() => {
433
+ setAnimPhase('run');
434
+ // A effetto finito si toglie il livello grafico dedicato: i testi tornano nitidi come prima
435
+ end = window.setTimeout(() => setAnimPhase('done'), animationDuration + 60);
436
+ });
437
+ return () => { cancelAnimationFrame(frame); clearTimeout(end); };
438
+ }, [activeId, animation, animationDuration]);
439
+ const scrollTabs = (direction) => {
440
+ const el = stripRef.current;
441
+ if (el)
442
+ el.scrollBy({ left: direction * Math.max(120, el.clientWidth * 0.6), behavior: 'smooth' });
443
+ };
444
+ const handleSelect = (id) => {
445
+ if (selectedId === undefined)
446
+ setInternalId(id); // Aggiorna lo stato solo se non controllato
447
+ onSelectedIdChange?.(id);
448
+ };
449
+ // Primo tab abilitato partendo da 'from' e muovendosi di 'delta': i disabilitati si saltano.
450
+ const nextEnabledId = (from, delta) => {
451
+ for (let i = from + delta; i >= 0 && i < panels.length; i += delta) {
452
+ if (!panels[i].props.disabled)
453
+ return panels[i].props.id;
454
+ }
455
+ return undefined;
456
+ };
457
+ const focusTab = (id) => stripRef.current?.querySelector(`[data-tab-button="${id}"]`)?.focus();
458
+ // Frecce come nei tab DevExpress: destra e giu al successivo, sinistra e su al precedente, Home ed End ai capi.
459
+ const handleBarKeyDown = (e) => {
460
+ if (editingId)
461
+ return; // Durante la rinomina i tasti appartengono all'input
462
+ const current = panels.findIndex(p => p.props.id === activeId);
463
+ let target;
464
+ switch (e.key) {
465
+ case 'ArrowRight':
466
+ case 'ArrowDown':
467
+ target = nextEnabledId(current, 1);
468
+ break;
469
+ case 'ArrowLeft':
470
+ case 'ArrowUp':
471
+ target = nextEnabledId(current, -1);
472
+ break;
473
+ case 'Home':
474
+ target = nextEnabledId(-1, 1);
475
+ break;
476
+ case 'End':
477
+ target = nextEnabledId(panels.length, -1);
478
+ break;
479
+ default: return;
480
+ }
481
+ e.preventDefault(); // Le frecce non devono scorrere la pagina ne la striscia dei tab
482
+ if (!target)
483
+ return;
484
+ handleSelect(target);
485
+ focusTab(target); // Il focus segue la selezione: la freccia dopo parte dal nuovo tab
486
+ };
487
+ // Swipe touch sul contenuto: trascinando verso sinistra si va al tab successivo, verso destra al precedente.
488
+ const swipeStart = useRef(undefined);
489
+ const handleTouchStart = (e) => {
490
+ if (!swipeEnabled || e.touches.length !== 1) {
491
+ swipeStart.current = undefined;
492
+ return;
493
+ }
494
+ swipeStart.current = { x: e.touches[0].clientX, y: e.touches[0].clientY };
495
+ };
496
+ const handleTouchEnd = (e) => {
497
+ const start = swipeStart.current;
498
+ swipeStart.current = undefined;
499
+ if (!start)
500
+ return;
501
+ const dx = e.changedTouches[0].clientX - start.x;
502
+ const dy = e.changedTouches[0].clientY - start.y;
503
+ if (Math.abs(dx) < SWIPE_MIN_DISTANCE || Math.abs(dx) < Math.abs(dy) * 2)
504
+ return;
505
+ const target = nextEnabledId(panels.findIndex(p => p.props.id === activeId), dx < 0 ? 1 : -1);
506
+ if (target)
507
+ handleSelect(target);
508
+ };
509
+ // Chiude la rinomina salvando il nuovo titolo solo se non vuoto.
510
+ const commitRename = (id, value) => {
511
+ const label = value.trim();
512
+ if (label)
513
+ onTabRename?.(id, label);
514
+ setEditingId(undefined);
515
+ };
516
+ const closeMenu = () => setMenu(prev => ({ ...prev, visible: false, id: undefined }));
517
+ // Voci del menu di un tab: prima quelle del chiamante, poi quelle che il tab consente fra
518
+ // Rinomina, Chiudi, Chiudi gli altri e Aggiungi.
519
+ const buildMenuItems = useCallback((id) => {
520
+ const panel = panels.find(p => p.props.id === id);
521
+ if (!panel)
522
+ return [];
523
+ const custom = tabMenuItems?.(panel.props.id) ?? [];
524
+ const items = [...custom];
525
+ const ownFirst = items.length; // Il primo comando del controllo apre un gruppo sotto le voci del chiamante
526
+ // Se il chiamante ha gia una voce con lo stesso testo (tipicamente Chiudi) vince la sua: il controllo non la ripete
527
+ const customNames = new Set(custom.filter(i => i.visible !== false).map(i => i.name));
528
+ const pushOwn = (item, beginGroup) => {
529
+ if (customNames.has(item.name))
530
+ return;
531
+ items.push({ ...item, beginGroup: beginGroup ?? (items.length === ownFirst && ownFirst > 0) });
532
+ };
533
+ if (onTabRename && panel.props.renamable !== false && typeof panel.props.label === 'string') {
534
+ // L'input si apre dopo la chiusura del menu, altrimenti perde subito il focus
535
+ pushOwn({ name: renameCaption, icon: _jsx(IconEdit, { fontSize: 18 }), onClick: () => { closeMenu(); setTimeout(() => setEditingId(panel.props.id), 0); } });
536
+ }
537
+ // Come per la 'x': l'ultimo tab rimasto non si chiude, salvo closableWhenSingle
538
+ if (onTabClose && panel.props.closable !== false && (closableWhenSingle || panels.length > 1)) {
539
+ pushOwn({ name: closeCaption, icon: _jsx(IconCloseOutline, { fontSize: 18 }), onClick: () => { closeMenu(); onTabClose(panel.props.id); } });
540
+ }
541
+ // Chiude in blocco gli altri tab chiudibili, ma prima chiede conferma dicendo quanti sono
542
+ const others = onTabClose ? panels.filter(p => p.props.id !== panel.props.id && p.props.closable !== false) : [];
543
+ if (onTabClose && others.length > 0) {
544
+ pushOwn({
545
+ name: closeOtherCaption, icon: _jsx(IconCloseOutline, { fontSize: 18 }), onClick: () => {
546
+ closeMenu();
547
+ TMMessageBoxManager.show({
548
+ title: closeOtherCaption,
549
+ message: SDKUI_Localizator.CloseTabs_ConfirmForN.replaceParams(others.length),
550
+ buttons: [ButtonNames.YES, ButtonNames.NO],
551
+ onButtonClick: (e) => { if (e === ButtonNames.YES)
552
+ others.forEach(t => onTabClose(t.props.id)); }
553
+ });
554
+ }
555
+ });
556
+ }
557
+ if (onTabAdd) {
558
+ pushOwn({ name: addCaption, icon: _jsx(IconAdd, { fontSize: 18 }), onClick: () => { closeMenu(); onTabAdd(); } }, items.length > 0);
559
+ }
560
+ return items;
561
+ }, [panels, tabMenuItems, onTabRename, onTabClose, closableWhenSingle, onTabAdd, renameCaption, closeCaption, closeOtherCaption, addCaption]);
562
+ // Tasto destro sul tab: niente menu del browser, si apre quello con Rinomina e Chiudi.
563
+ const openMenu = (e, id) => {
564
+ e.preventDefault();
565
+ e.stopPropagation();
566
+ // Senza voci non si apre niente: lasciare lo stato aperto farebbe comparire il menu
567
+ // a sorpresa appena le voci tornano disponibili, es. all'apertura di un altro tab.
568
+ if (buildMenuItems(id).length === 0)
569
+ return;
570
+ setMenu({ visible: true, position: { x: e.clientX, y: e.clientY }, id });
571
+ };
572
+ const menuItems = useMemo(() => buildMenuItems(menu.id), [buildMenuItems, menu.id]);
573
+ return (_jsxs("div", { style: { display: 'flex', flexDirection: 'column', minHeight: 0, ...style }, children: [showBar &&
574
+ _jsxs(TabsHeader, { $v: v, onContextMenu: (e) => e.preventDefault(), children: [arrows.left && _jsx(NavButton, { $v: v, type: 'button', tabIndex: -1, onClick: () => scrollTabs(-1), children: _jsx(IconLeft, { fontSize: 18 }) }), _jsxs(TabsStrip, { $v: v, ref: stripRef, role: 'tablist', onScroll: refreshArrows, onKeyDown: handleBarKeyDown, children: [panels.map((p, index) => {
575
+ const isActive = p.props.id === activeId;
576
+ // Con un solo tab la 'x' sparisce: non deve essere possibile restare senza contenuto,
577
+ // a meno che closableWhenSingle non consenta di chiudere anche l'ultimo tab.
578
+ const canClose = !!onTabClose && p.props.closable !== false && (closableWhenSingle || panels.length > 1);
579
+ const canRename = !!onTabRename && p.props.renamable !== false && typeof p.props.label === 'string';
580
+ const isDropTarget = !!dragId && dropId === p.props.id && dragId !== p.props.id;
581
+ const draggedIndex = panels.findIndex(t => t.props.id === dragId);
582
+ // Separatore solo tra due tab entrambi inattivi, come nel browser
583
+ const showSeparator = index > 0 && !isActive && panels[index - 1].props.id !== activeId;
584
+ // Titoli lunghi (es. certi tipi documento): si accorciano e il nome intero va nel tooltip
585
+ const labelText = typeof p.props.label === 'string' ? p.props.label : undefined;
586
+ const shortLabel = labelText ? shortenLabel(labelText) : undefined;
587
+ const tabButton = (_jsxs(TabButton, { type: 'button', role: 'tab', "data-tab-button": p.props.id, "aria-selected": isActive,
588
+ // Con il Tab si entra e si esce dalla barra in un colpo solo: dentro si gira con le frecce
589
+ tabIndex: isActive ? 0 : -1,
590
+ // Con il titolo accorciato il nome per esteso lo mostra gia il tooltip
591
+ title: canRename && !shortLabel ? 'Doppio clic per rinominare' : undefined, $active: isActive, $textColor: titleColor, $idleColor: titleIdleColor, disabled: p.props.disabled, onClick: () => handleSelect(p.props.id), onDoubleClick: () => canRename && setEditingId(p.props.id), children: [shortLabel ?? p.props.label, (p.props.badge ?? 0) > 0 && _jsx(TabBadge, { $color: p.props.badgeColor ?? '#E53935', children: p.props.badge })] }));
592
+ return (_jsxs(React.Fragment, { children: [showSeparator && _jsx(TabSeparator, { $v: v }), _jsxs(TabItem, { "data-tab-id": p.props.id, $v: v, $active: isActive, $dragging: dragId === p.props.id, draggable: !!onTabReorder && p.props.movable !== false && editingId !== p.props.id,
593
+ // Tipo MIME privato invece di 'text/plain': il tab non e rilasciabile fuori dalla barra
594
+ // (barra indirizzi, campi di ricerca, altre applicazioni) scrivendoci dentro il suo id
595
+ onDragStart: (e) => { setDragId(p.props.id); e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData(TAB_DRAG_MIME, p.props.id); }, onDragOver: (e) => { if (!dragId || dragId === p.props.id || p.props.movable === false)
596
+ return; e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setDropId(p.props.id); }, onDragLeave: () => setDropId(prev => prev === p.props.id ? undefined : prev), onDrop: (e) => { e.preventDefault(); if (dragId && dragId !== p.props.id && p.props.movable !== false)
597
+ onTabReorder?.(dragId, p.props.id); setDragId(undefined); setDropId(undefined); }, onDragEnd: () => { setDragId(undefined); setDropId(undefined); }, onContextMenu: (e) => openMenu(e, p.props.id), children: [editingId === p.props.id
598
+ // In rinomina: Invio conferma, Esc annulla, uscendo dal campo si conferma
599
+ ? _jsx(RenameInput, { autoFocus: true, defaultValue: String(p.props.label), onFocus: (e) => e.currentTarget.select(), onBlur: (e) => commitRename(p.props.id, e.currentTarget.value), onKeyDown: (e) => {
600
+ if (e.key === 'Enter')
601
+ commitRename(p.props.id, e.currentTarget.value);
602
+ else if (e.key === 'Escape')
603
+ setEditingId(undefined);
604
+ } })
605
+ : shortLabel
606
+ // width: auto annulla il max-content del tooltip, che impedirebbe al titolo di restringersi
607
+ ? _jsx(TMTooltip, { content: labelText, parentStyle: { display: 'flex', minWidth: 0 }, childStyle: { width: 'auto', minWidth: 0 }, children: tabButton })
608
+ : tabButton, canClose && _jsx(CloseButton, { $color: isActive ? v.textColor : v.idleTextColor, type: 'button', tabIndex: isActive ? 0 : -1, title: closeCaption, onClick: () => onTabClose?.(p.props.id), children: "\u00D7" }), isDropTarget && _jsx(DropMark, { $side: draggedIndex > index ? 'left' : 'right' })] })] }, p.props.id));
609
+ }), onTabAdd && _jsx(AddButton, { $v: v, type: 'button', title: addCaption, onClick: onTabAdd, children: _jsx(IconAdd, { fontSize: 18 }) })] }), arrows.right && _jsx(NavButton, { $v: v, type: 'button', tabIndex: -1, onClick: () => scrollTabs(1), children: _jsx(IconRight, { fontSize: 18 }) }), toolbar && _jsx(TabsToolbar, { children: toolbar })] }), menu.visible && menuItems.length > 0 &&
610
+ _jsx(TMContextMenu, { items: menuItems, externalControl: { visible: menu.visible, position: menu.position, onClose: closeMenu } }), _jsx(ContentArea, { onTouchStart: handleTouchStart, onTouchEnd: handleTouchEnd, children: mountedPanels.map(p => {
611
+ const isActive = p.props.id === activeId;
612
+ return (_jsx(ContentPane, { role: 'tabpanel', "aria-hidden": !isActive, $active: isActive, $anim: animation, $duration: animationDuration, $phase: animPhase,
613
+ // Il tab inattivo resta a grandezza piena ma fuori campo: griglie e pannelli non si rimisurano a zero
614
+ style: isActive ? contentStyle : { ...contentStyle, position: 'absolute', top: 0, left: '-9999px', width: '100%', height: '100%', visibility: 'hidden' }, children: p.props.children }, p.props.id));
615
+ }) })] }));
616
+ };
617
+ export default TMTabControl;
618
+ export const useTMTabs = ({ label, newLabel }) => {
619
+ const [items, setItems] = useState([{ id: 'tab-1', label }]);
620
+ const [activeId, setActiveId] = useState('tab-1');
621
+ const seq = useRef(1); // Contatore degli id generati
622
+ const itemsRef = useRef(items); // Lista corrente leggibile dalle callback senza rigenerarle
623
+ itemsRef.current = items;
624
+ const labelRef = useRef({ label, newLabel });
625
+ labelRef.current = { label, newLabel };
626
+ const add = useCallback((customLabel) => {
627
+ const index = ++seq.current;
628
+ const id = `tab-${index}`;
629
+ const { label, newLabel } = labelRef.current;
630
+ setItems(prev => [...prev, { id, label: customLabel ?? (newLabel ? newLabel(index) : `${label} ${index}`) }]);
631
+ setActiveId(id);
632
+ return id;
633
+ }, []);
634
+ const close = useCallback((id) => {
635
+ const prev = itemsRef.current;
636
+ const index = prev.findIndex(t => t.id === id);
637
+ if (index < 0)
638
+ return;
639
+ const next = prev.filter(t => t.id !== id);
640
+ itemsRef.current = next; // aggiorno subito: cosi piu chiusure nello stesso tick non si sovrascrivono
641
+ setItems(next);
642
+ // Chiudendo il tab attivo si passa al precedente, come fa il browser
643
+ setActiveId(curr => curr === id ? (next[Math.max(0, index - 1)]?.id ?? curr) : curr);
644
+ }, []);
645
+ const rename = useCallback((id, label) => setItems(prev => prev.map(t => t.id === id ? { ...t, label } : t)), []);
646
+ const move = useCallback((draggedId, targetId) => setItems(prev => {
647
+ const from = prev.findIndex(t => t.id === draggedId);
648
+ const to = prev.findIndex(t => t.id === targetId);
649
+ if (from < 0 || to < 0 || from === to)
650
+ return prev;
651
+ const next = [...prev];
652
+ next.splice(to, 0, next.splice(from, 1)[0]);
653
+ return next;
654
+ }), []);
655
+ const controlProps = useMemo(() => ({
656
+ selectedId: activeId,
657
+ onSelectedIdChange: setActiveId,
658
+ onTabClose: close,
659
+ onTabRename: rename,
660
+ onTabReorder: move,
661
+ onTabAdd: () => { add(); }
662
+ }), [activeId, add, close, rename, move]);
663
+ return { items, activeId, setActiveId, add, close, rename, move, controlProps };
664
+ };