@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.
- package/lib/components/NewComponents/ContextMenu/TMContextMenu.js +3 -3
- package/lib/components/NewComponents/ContextMenu/hooks.d.ts +1 -1
- package/lib/components/NewComponents/ContextMenu/styles.d.ts +45 -43
- package/lib/components/NewComponents/FloatingMenuBar/TMFloatingMenuBar.js +6 -6
- package/lib/components/NewComponents/FloatingMenuBar/styles.d.ts +80 -73
- package/lib/components/base/Styled.d.ts +92 -76
- package/lib/components/base/Styled.js +7 -5
- package/lib/components/base/TMAccordionNew.js +7 -7
- package/lib/components/base/TMAlert.d.ts +32 -32
- package/lib/components/base/TMAlert.js +6 -8
- package/lib/components/base/TMAreaManager.js +53 -55
- package/lib/components/base/TMButton.d.ts +13 -0
- package/lib/components/base/TMButton.js +139 -7
- package/lib/components/base/TMClosableList.js +3 -3
- package/lib/components/base/TMCustomButton.js +40 -48
- package/lib/components/base/TMDataGrid.d.ts +4 -0
- package/lib/components/base/TMDataGrid.js +292 -12
- package/lib/components/base/TMDataGridExportForm.js +39 -30
- package/lib/components/base/TMDropDownMenu.js +1 -1
- package/lib/components/base/TMEditorBase.d.ts +2 -0
- package/lib/components/base/TMFileManager.js +5 -3
- package/lib/components/base/TMFileManagerDataGridView.js +12 -3
- package/lib/components/base/TMFileManagerThumbnailItems.js +20 -10
- package/lib/components/base/TMFileManagerUtils.d.ts +6 -6
- package/lib/components/base/TMFileManagerUtils.js +37 -1
- package/lib/components/base/TMLayout.d.ts +2 -2
- package/lib/components/base/TMLayout.js +3 -3
- package/lib/components/base/TMList.js +1 -1
- package/lib/components/base/TMListView.js +3 -3
- package/lib/components/base/TMModal.d.ts +11 -0
- package/lib/components/base/TMModal.js +47 -13
- package/lib/components/base/TMPanel.d.ts +6 -0
- package/lib/components/base/TMPanel.js +141 -28
- package/lib/components/base/TMPopUp.js +5 -2
- package/lib/components/base/TMResizableMenu.js +2 -2
- package/lib/components/base/TMRightSidebar.js +1 -1
- package/lib/components/base/TMShowAllOrMaxItemsButton.js +1 -1
- package/lib/components/base/TMSpinner.d.ts +2 -1
- package/lib/components/base/TMSpinner.js +9 -7
- package/lib/components/base/TMTab.js +2 -2
- package/lib/components/base/TMTabControl.d.ts +106 -0
- package/lib/components/base/TMTabControl.js +664 -0
- package/lib/components/base/TMToggleButton.js +2 -2
- package/lib/components/base/TMToolbarCard.js +3 -3
- package/lib/components/base/TMTooltip.d.ts +2 -1
- package/lib/components/base/TMTooltip.js +24 -8
- package/lib/components/base/TMTreeView.d.ts +13 -7
- package/lib/components/base/TMTreeView.js +143 -70
- package/lib/components/base/TMWaitPanel.js +3 -3
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.d.ts +12 -0
- package/lib/components/base/waitPanel/TMWaitPanelConfirm.js +185 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.d.ts +169 -0
- package/lib/components/base/waitPanel/TMWaitPanelManager.js +320 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.d.ts +53 -0
- package/lib/components/base/waitPanel/TMWaitPanelView.js +274 -0
- package/lib/components/base/waitPanel/index.d.ts +1 -0
- package/lib/components/base/waitPanel/index.js +1 -0
- package/lib/components/choosers/TMCultureIDPicker.js +2 -2
- package/lib/components/choosers/TMDataListItemChooser.js +1 -1
- package/lib/components/choosers/TMDataListItemPicker.js +2 -2
- package/lib/components/choosers/TMDcmtTypeChooser.js +5 -4
- package/lib/components/choosers/TMDiskChooser.js +1 -1
- package/lib/components/choosers/TMDistinctValues.d.ts +9 -7
- package/lib/components/choosers/TMDistinctValues.js +157 -141
- package/lib/components/choosers/TMDynDataListItemChooser.js +1 -1
- package/lib/components/choosers/TMImageIDChooser.js +1 -1
- package/lib/components/choosers/TMMetadataChooser.js +3 -3
- package/lib/components/choosers/TMPathChooser.js +17 -34
- package/lib/components/choosers/TMQuickSearchInfo.d.ts +13 -0
- package/lib/components/choosers/TMQuickSearchInfo.js +279 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.d.ts +16 -0
- package/lib/components/choosers/TMQuickSearchSettingsForm.js +94 -0
- package/lib/components/choosers/TMRelationChooser.js +1 -1
- package/lib/components/choosers/TMSelectedValuesSummary.d.ts +13 -0
- package/lib/components/choosers/TMSelectedValuesSummary.js +89 -0
- package/lib/components/choosers/TMUserChooser.js +1 -1
- package/lib/components/editors/TMCheckBox.js +1 -1
- package/lib/components/editors/TMDateBox.d.ts +16 -1
- package/lib/components/editors/TMDateBox.js +90 -22
- package/lib/components/editors/TMDropDown.d.ts +3 -0
- package/lib/components/editors/TMDropDown.js +197 -5
- package/lib/components/editors/TMEditorStyled.d.ts +21 -43
- package/lib/components/editors/TMFormulaEditor.js +1 -1
- package/lib/components/editors/TMHtmlContentDisplay.js +1 -1
- package/lib/components/editors/TMLocalizedTextBox.js +1 -1
- package/lib/components/editors/TMMetadataEditor.d.ts +5 -1
- package/lib/components/editors/TMMetadataEditor.js +27 -23
- package/lib/components/editors/TMMetadataValues.js +6 -6
- package/lib/components/editors/TMRadioButton.js +2 -2
- package/lib/components/editors/TMSummary.js +3 -3
- package/lib/components/editors/TMTextArea.d.ts +1 -0
- package/lib/components/editors/TMTextArea.js +25 -17
- package/lib/components/editors/TMTextBox.d.ts +2 -0
- package/lib/components/editors/TMTextBox.js +32 -21
- package/lib/components/editors/TMTextExpression.js +1 -1
- package/lib/components/features/archive/TMArchive.js +24 -5
- package/lib/components/features/assistant/TMHelpOnLineChat.d.ts +29 -0
- package/lib/components/features/assistant/TMHelpOnLineChat.js +1384 -0
- package/lib/components/features/assistant/TMToppyDraggableHelpCenter.js +4 -4
- package/lib/components/features/assistant/TMToppySpeechBubble.js +1 -1
- package/lib/components/features/assistant/dataset/Designer-Suggested-Questions.json +102 -0
- package/lib/components/features/assistant/dataset/Login-Suggested-Questions.json +32 -0
- package/lib/components/features/assistant/dataset/Surfer-Suggested-Questions.json +102 -0
- package/lib/components/features/documents/TMBatchUpdateForm.js +22 -9
- package/lib/components/features/documents/TMCopyToFolderForm.js +50 -42
- package/lib/components/features/documents/TMDcmtBlog.d.ts +3 -0
- package/lib/components/features/documents/TMDcmtBlog.js +4 -4
- package/lib/components/features/documents/TMDcmtForm.d.ts +7 -0
- package/lib/components/features/documents/TMDcmtForm.js +281 -113
- package/lib/components/features/documents/TMDcmtFormActionButtons.d.ts +11 -0
- package/lib/components/features/documents/TMDcmtFormActionButtons.js +79 -9
- package/lib/components/features/documents/TMDcmtIcon.d.ts +1 -3
- package/lib/components/features/documents/TMDcmtIcon.js +8 -8
- package/lib/components/features/documents/TMDcmtPreview.d.ts +7 -5
- package/lib/components/features/documents/TMDcmtPreview.js +16 -10
- package/lib/components/features/documents/TMDcmtTasks.js +2 -9
- package/lib/components/features/documents/TMDownloadRelationViewerSection.js +1 -1
- package/lib/components/features/documents/TMFileUploader.js +7 -7
- package/lib/components/features/documents/TMMasterDetailDcmts.d.ts +2 -0
- package/lib/components/features/documents/TMMasterDetailDcmts.js +95 -108
- package/lib/components/features/documents/TMMasterInfoFields.d.ts +20 -0
- package/lib/components/features/documents/TMMasterInfoFields.js +185 -0
- package/lib/components/features/documents/TMMergeToPdfForm.d.ts +1 -2
- package/lib/components/features/documents/TMMergeToPdfForm.js +83 -56
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.d.ts +46 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsForm.js +320 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.d.ts +370 -0
- package/lib/components/features/documents/TMMultiMasterDetailDcmtsUtils.js +283 -0
- package/lib/components/features/documents/TMRelationViewer.d.ts +29 -9
- package/lib/components/features/documents/TMRelationViewer.js +310 -208
- package/lib/components/features/documents/copyAndMergeDcmtsShared.d.ts +1 -1
- package/lib/components/features/documents/relationTreeState.d.ts +32 -0
- package/lib/components/features/documents/relationTreeState.js +89 -0
- package/lib/components/features/search/TMMetadataOutputForm.js +1 -1
- package/lib/components/features/search/TMMetadataSorterForm.js +1 -1
- package/lib/components/features/search/TMSavedQuerySelector.js +2 -2
- package/lib/components/features/search/TMSearch.d.ts +1 -1
- package/lib/components/features/search/TMSearch.js +172 -34
- package/lib/components/features/search/TMSearchQueryEditor.js +7 -5
- package/lib/components/features/search/TMSearchQueryPanel.d.ts +14 -4
- package/lib/components/features/search/TMSearchQueryPanel.js +153 -62
- package/lib/components/features/search/TMSearchResult.d.ts +21 -0
- package/lib/components/features/search/TMSearchResult.js +461 -152
- package/lib/components/features/search/TMSignatureInfoContent.js +1 -1
- package/lib/components/features/search/TMTreeSelector.js +130 -83
- package/lib/components/features/search/TMViewHistoryDcmt.js +14 -25
- package/lib/components/features/tasks/TMTaskForm.js +1 -1
- package/lib/components/features/tasks/TMTaskFormUtils.js +3 -3
- package/lib/components/features/tasks/TMTasksAgenda.js +1 -1
- package/lib/components/features/tasks/TMTasksCalendar.js +1 -1
- package/lib/components/features/tasks/TMTasksHeader.js +1 -1
- package/lib/components/features/tasks/TMTasksUtilsView.js +1 -1
- package/lib/components/features/tasks/TMTasksView.js +97 -102
- package/lib/components/features/wg/TMWGsCopyMoveForm.js +92 -92
- package/lib/components/features/workflow/TMWorkflowPopup.d.ts +10 -0
- package/lib/components/features/workflow/TMWorkflowPopup.js +9 -5
- package/lib/components/features/workflow/diagram/ConnectionComponent.js +3 -3
- package/lib/components/features/workflow/diagram/DiagramItemComponent.js +3 -3
- package/lib/components/features/workflow/diagram/DiagramItemForm.js +1 -1
- package/lib/components/features/workflow/diagram/DiagramItemSvgContent.js +1 -1
- package/lib/components/features/workflow/diagram/RecipientList.js +1 -1
- package/lib/components/features/workflow/diagram/WFDiagram.js +4 -4
- package/lib/components/features/workflow/diagram/WorkitemRecipientsEditor.d.ts +3 -1
- package/lib/components/forms/Login/ChangePasswordInputs.d.ts +3 -1
- package/lib/components/forms/Login/Menu.js +1 -1
- package/lib/components/forms/Login/OTPReader.js +1 -1
- package/lib/components/forms/Login/PasswordStrengthChecker.js +1 -1
- package/lib/components/forms/Login/RapidAccessLogin.js +2 -2
- package/lib/components/forms/Login/RecoverPasswordFlow.js +2 -2
- package/lib/components/forms/Login/SelectBox.js +1 -1
- package/lib/components/forms/Login/StepIndicator.js +1 -1
- package/lib/components/forms/Login/TMLoginForm.js +7 -7
- package/lib/components/forms/Login/TextBox.js +1 -1
- package/lib/components/forms/TMApplyForm.js +3 -3
- package/lib/components/forms/TMChooserForm.js +1 -1
- package/lib/components/forms/TMResultDialog.js +1 -1
- package/lib/components/forms/TMSaveForm.js +3 -3
- package/lib/components/grids/TMBlogAttachments.js +14 -3
- package/lib/components/grids/TMBlogHeader.js +5 -1
- package/lib/components/grids/TMBlogsPost.d.ts +2 -0
- package/lib/components/grids/TMBlogsPost.js +122 -99
- package/lib/components/grids/TMBlogsPostUtils.d.ts +1 -0
- package/lib/components/grids/TMBlogsPostUtils.js +15 -4
- package/lib/components/grids/TMRecentsManager.js +4 -4
- package/lib/components/grids/TMValidationItemsList.js +1 -1
- package/lib/components/index.d.ts +6 -0
- package/lib/components/index.js +5 -0
- package/lib/components/layout/panelManager/TMPanelManagerContainer.d.ts +1 -0
- package/lib/components/layout/panelManager/TMPanelManagerContainer.js +25 -15
- package/lib/components/layout/panelManager/TMPanelManagerContext.js +68 -93
- package/lib/components/layout/panelManager/TMPanelManagerToolbar.d.ts +4 -5
- package/lib/components/layout/panelManager/TMPanelManagerToolbar.js +1 -1
- package/lib/components/layout/panelManager/TMPanelWrapper.js +3 -3
- package/lib/components/layout/panelManager/types.d.ts +3 -0
- package/lib/components/layout/panelManager/utils.js +13 -11
- package/lib/components/pages/TMPage.js +30 -32
- package/lib/components/query/TMQueryCountButton.js +1 -1
- package/lib/components/query/TMQueryEditor.d.ts +17 -10
- package/lib/components/query/TMQueryEditor.js +9 -8
- package/lib/components/sidebar/TMCommandsPanel.d.ts +4 -4
- package/lib/components/sidebar/TMCommandsPanel.js +1 -1
- package/lib/components/sidebar/TMHeader.d.ts +1 -0
- package/lib/components/sidebar/TMHeader.js +37 -27
- package/lib/components/sidebar/TMSidebar.js +1 -1
- package/lib/components/sidebar/TMSidebarItem.js +1 -1
- package/lib/components/viewers/TMMidViewer.js +6 -3
- package/lib/components/viewers/TMTidViewer.js +89 -28
- package/lib/components/wizard/TMStepIndicator.js +3 -3
- package/lib/css/tm-sdkui.css +1 -1
- package/lib/devextreme-license.d.ts +1 -0
- package/lib/devextreme-license.js +3 -0
- package/lib/helper/Enum_Localizator.d.ts +4 -4
- package/lib/helper/Enum_Localizator.js +1 -0
- package/lib/helper/Globalization.d.ts +1 -1
- package/lib/helper/SDKUI_Globals.d.ts +110 -0
- package/lib/helper/SDKUI_Globals.js +151 -0
- package/lib/helper/SDKUI_Localizator.d.ts +756 -677
- package/lib/helper/SDKUI_Localizator.js +790 -0
- package/lib/helper/TMAppUpdatedModal.d.ts +2 -0
- package/lib/helper/TMAppUpdatedModal.js +134 -0
- package/lib/helper/TMIcons.d.ts +1 -0
- package/lib/helper/TMIcons.js +4 -3
- package/lib/helper/TMPdfViewer.js +1 -1
- package/lib/helper/TMToppyMessage.js +1 -1
- package/lib/helper/TMUtils.d.ts +51 -4
- package/lib/helper/TMUtils.js +273 -12
- package/lib/helper/ZipManager.d.ts +1 -1
- package/lib/helper/ZipManager.js +2 -1
- package/lib/helper/checkinCheckoutManager.d.ts +1 -1
- package/lib/helper/checkinCheckoutManager.js +1 -1
- package/lib/helper/dataGridSearchHelper.d.ts +28 -0
- package/lib/helper/dataGridSearchHelper.js +51 -0
- package/lib/helper/dcmtsHelper.d.ts +22 -0
- package/lib/helper/dcmtsHelper.js +60 -1
- package/lib/helper/helpers.d.ts +24 -2
- package/lib/helper/helpers.js +59 -3
- package/lib/helper/index.d.ts +2 -0
- package/lib/helper/index.js +2 -0
- package/lib/helper/queryHelper.d.ts +6 -1
- package/lib/helper/queryHelper.js +8 -3
- package/lib/hooks/tmDistinctValuesGridHelper.d.ts +16 -0
- package/lib/hooks/tmDistinctValuesGridHelper.js +31 -0
- package/lib/hooks/useArchiveListForm.d.ts +39 -0
- package/lib/hooks/useArchiveListForm.js +46 -0
- package/lib/hooks/useCaseFlowApprove.d.ts +56 -0
- package/lib/hooks/useCaseFlowApprove.js +157 -0
- package/lib/hooks/useCheckInOutOperations.d.ts +0 -6
- package/lib/hooks/useCheckInOutOperations.js +14 -34
- package/lib/hooks/useDataUserIdItem.d.ts +1 -0
- package/lib/hooks/useDataUserIdItem.js +1 -0
- package/lib/hooks/useDcmtOperations.d.ts +3 -12
- package/lib/hooks/useDcmtOperations.js +187 -179
- package/lib/hooks/useDocumentOperations.d.ts +5 -0
- package/lib/hooks/useDocumentOperations.js +64 -33
- package/lib/hooks/useInputDialog.js +1 -1
- package/lib/hooks/useMultiMasterDetailDcmts.d.ts +98 -0
- package/lib/hooks/useMultiMasterDetailDcmts.js +758 -0
- package/lib/hooks/usePreventFileDrop.js +9 -0
- package/lib/hooks/useQueryParametersDialog.js +1 -1
- package/lib/hooks/useRelatedDocuments.d.ts +1 -0
- package/lib/hooks/useRelatedDocuments.js +22 -27
- package/lib/hooks/useShowDossiersV5.d.ts +12 -0
- package/lib/hooks/useShowDossiersV5.js +37 -0
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.d.ts +18 -0
- package/lib/hooks/useTMDistinctValuesMetadataDisplay.js +103 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.d.ts +49 -0
- package/lib/hooks/useTMDistinctValuesQuickSearch.js +307 -0
- package/lib/hooks/useTMDistinctValuesSelection.d.ts +42 -0
- package/lib/hooks/useTMDistinctValuesSelection.js +103 -0
- package/lib/hooks/useTMDistinctValuesSource.d.ts +29 -0
- package/lib/hooks/useTMDistinctValuesSource.js +105 -0
- package/lib/hooks/useWorkflowApprove.js +33 -17
- package/lib/index.d.ts +2 -0
- package/lib/index.js +5 -2
- package/lib/services/caseflow/CaseFlowService.d.ts +277 -0
- package/lib/services/caseflow/CaseFlowService.js +761 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.d.ts +28 -0
- package/lib/services/caseflow/CaseFlowTemplateCacheService.js +121 -0
- package/lib/services/caseflow/caseFlowNameCache.d.ts +32 -0
- package/lib/services/caseflow/caseFlowNameCache.js +116 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.d.ts +61 -0
- package/lib/services/caseflow/caseFlowWorkItemUtils.js +113 -0
- package/lib/services/index.d.ts +4 -0
- package/lib/services/index.js +4 -0
- package/lib/services/platform_services.d.ts +4 -4
- 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
|
+
};
|