@topconsultnpm/sdkui-react 6.22.0-dev3.3 → 6.22.0-dev3.5

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