@topconsultnpm/sdkui-react-beta 6.14.29 → 6.14.30

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.
@@ -3,7 +3,7 @@ import { Colors, ITMEditorBase } from './TMEditorBase';
3
3
  interface ITMDropDown extends ITMEditorBase {
4
4
  dataSource?: any[];
5
5
  color?: Colors;
6
- value?: string | number | undefined;
6
+ value?: string | number;
7
7
  disabled?: boolean;
8
8
  onValueChanged?: (e: React.ChangeEvent<HTMLSelectElement>) => void;
9
9
  }
@@ -1,3 +1,7 @@
1
+ /** Gestione TMPanel "attivo / non attivo" secondo una pattern ibrido (Controlled/Uncontrolled Component):
2
+ 1) Modalità Non Controllata (Uncontrolled): Se non gli viene passata la prop "isActive", il pannello gestisce lo stato "attivo" internamente. Per l'uso singolo ("plug-and-play").
3
+ 2) Modalità Controllata (Controlled): Se il genitore passa la prop "isActive", il pannello cede il controllo e si affida completamente al genitore. Ideale per layout con più pannelli che necessitano di coordinamento.
4
+ */
1
5
  import React from 'react';
2
6
  export interface ITMPanelRef {
3
7
  focusPanel: () => void;
@@ -15,11 +19,14 @@ export interface ITMPanelProps {
15
19
  toolbar?: any;
16
20
  padding?: string;
17
21
  isVisible?: boolean;
22
+ panelID?: string;
23
+ isActive?: boolean;
24
+ onActivate?: (panelID: string) => void;
25
+ onActiveChanged?: (isActive: boolean) => void;
18
26
  onBack?: () => void;
19
27
  onClose?: () => void;
20
28
  onHeaderDoubleClick?: () => void;
21
29
  onMaximize?: (isMaximized: boolean) => void;
22
- onActiveChanged?: (isActive: boolean) => void;
23
30
  }
24
31
  declare const TMPanel: React.ForwardRefExoticComponent<ITMPanelProps & React.RefAttributes<ITMPanelRef>>;
25
32
  export default TMPanel;
@@ -1,4 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /** Gestione TMPanel "attivo / non attivo" secondo una pattern ibrido (Controlled/Uncontrolled Component):
3
+ 1) Modalità Non Controllata (Uncontrolled): Se non gli viene passata la prop "isActive", il pannello gestisce lo stato "attivo" internamente. Per l'uso singolo ("plug-and-play").
4
+ 2) Modalità Controllata (Controlled): Se il genitore passa la prop "isActive", il pannello cede il controllo e si affida completamente al genitore. Ideale per layout con più pannelli che necessitano di coordinamento.
5
+ */
2
6
  import { useCallback, useRef, useState, forwardRef, useImperativeHandle } from 'react';
3
7
  import styled from 'styled-components';
4
8
  import { IconArrowLeft, IconClearButton, IconWindowMaximize, IconWindowMinimize, isPositiveNumber, SDKUI_Globals, SDKUI_Localizator } from '../../helper';
@@ -65,11 +69,18 @@ const StyledPanelContent = styled.div `
65
69
  outline: none;
66
70
  }
67
71
  `;
68
- const TMPanel = forwardRef(({ allowMaximize = true, color, backgroundColor, backgroundColorContainer, children, showHeader = true, title, totalItems, displayedItemsCount, toolbar, padding = '5px', isVisible = true, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged }, ref) => {
69
- const [isPanelActive, setIsPanelActive] = useState(false);
72
+ const TMPanel = forwardRef(({ allowMaximize = true, color, backgroundColor, backgroundColorContainer, children, showHeader = true, title, totalItems, displayedItemsCount, toolbar, padding = '5px', isVisible = true, panelID = 'tmpanel', isActive, // Questa prop determinerà la modalità
73
+ onActivate, onBack, onClose, onHeaderDoubleClick, onMaximize, onActiveChanged }, ref) => {
70
74
  const [isMaximized, setIsMaximized] = useState(false);
75
+ // INTERNAL STATUS for UNCONTROLLED mode
76
+ const [internalIsActive, setInternalIsActive] = useState(false);
71
77
  const panelRef = useRef(null);
72
- const pendingFocusCheck = useRef(null); // Per un timer
78
+ // If `isActive` prop is passed (it is not undefined), the component is checked.
79
+ const isControlled = isActive !== undefined;
80
+ // Use parent state if checked, otherwise use internal state.
81
+ const currentIsActive = isControlled ? isActive : internalIsActive;
82
+ // Focus/blur management logic for UNCONTROLLED mode
83
+ const pendingFocusCheck = useRef(null);
73
84
  // Espone i metodi imperativi tramite useImperativeHandle
74
85
  useImperativeHandle(ref, () => ({
75
86
  focusPanel: () => {
@@ -77,41 +88,38 @@ const TMPanel = forwardRef(({ allowMaximize = true, color, backgroundColor, back
77
88
  panelRef.current?.focus();
78
89
  },
79
90
  }));
80
- const setActiveStatus = useCallback((isActive) => {
81
- if (isPanelActive !== isActive) { // Solo se lo stato cambia
82
- setIsPanelActive(isActive);
83
- onActiveChanged?.(isActive);
84
- }
85
- }, [isPanelActive, onActiveChanged]);
86
- const handleFocus = useCallback(() => {
87
- // Se c'è un check di pending blur, annullalo
91
+ const handleFocusUncontrolled = useCallback(() => {
88
92
  if (pendingFocusCheck.current) {
89
93
  clearTimeout(pendingFocusCheck.current);
90
94
  pendingFocusCheck.current = null;
91
95
  }
92
- setActiveStatus(true);
93
- }, [setActiveStatus]);
94
- const handleBlur = useCallback((event) => {
95
- // Pulisci qualsiasi check precedente (importante se ci sono blur rapidi)
96
+ if (!internalIsActive) {
97
+ setInternalIsActive(true);
98
+ onActiveChanged?.(true);
99
+ }
100
+ }, [internalIsActive, onActiveChanged]);
101
+ const handleBlurUncontrolled = useCallback(() => {
96
102
  if (pendingFocusCheck.current) {
97
103
  clearTimeout(pendingFocusCheck.current);
98
104
  }
99
- // Programma un controllo dopo un brevissimo ritardo
100
- // Questo permette al browser e a DevExtreme di stabilizzare il focus
101
105
  pendingFocusCheck.current = setTimeout(() => {
102
- const currentActiveElement = document.activeElement;
103
- // Se l'elemento attivo corrente è all'interno del pannello,
104
- // non fare nulla (il focus è interno)
105
- if (panelRef.current?.contains(currentActiveElement)) {
106
- setActiveStatus(true); // Assicurati che sia attivo
107
- }
108
- else {
109
- // Il focus è realmente uscito
110
- setActiveStatus(false);
106
+ if (panelRef.current && !panelRef.current.contains(document.activeElement)) {
107
+ setInternalIsActive(false);
108
+ onActiveChanged?.(false);
111
109
  }
112
- pendingFocusCheck.current = null; // Resetta il timer
113
- }, 50); // Piccolo ritardo, 50ms è un buon punto di partenza
114
- }, [setActiveStatus]);
110
+ }, 50);
111
+ }, [onActiveChanged]);
112
+ const handleActivation = () => {
113
+ if (isControlled) {
114
+ // In modalità controllata, notifica il genitore.
115
+ // Il genitore sarà responsabile di aggiornare la prop `isActive`.
116
+ onActivate?.(panelID);
117
+ }
118
+ else {
119
+ // In modalità non controllata, gestisci internamente.
120
+ handleFocusUncontrolled();
121
+ }
122
+ };
115
123
  // handler for external maximize management
116
124
  const handleMaximize = () => {
117
125
  setIsMaximized(prevState => {
@@ -122,10 +130,10 @@ const TMPanel = forwardRef(({ allowMaximize = true, color, backgroundColor, back
122
130
  return newValue;
123
131
  });
124
132
  };
125
- return (_jsxs(StyledPanelContainer, { className: "tmpanel-container", ref: panelRef, "$isMaximized": onMaximize ? false : isMaximized, style: {
133
+ return (_jsxs(StyledPanelContainer, { ref: panelRef, "$isMaximized": onMaximize ? false : isMaximized, style: {
126
134
  visibility: isVisible ? 'visible' : 'hidden',
127
- }, tabIndex: -1, onFocus: handleFocus, onBlur: handleBlur, onClick: handleFocus, children: [showHeader &&
128
- _jsx(StyledPanelHeader, { "$backgroundColor": backgroundColor, "$color": color, "$isActive": isPanelActive, onDoubleClick: () => {
135
+ }, tabIndex: -1, onFocus: !isControlled ? handleFocusUncontrolled : undefined, onBlur: !isControlled ? handleBlurUncontrolled : undefined, onClick: handleActivation, children: [showHeader &&
136
+ _jsx(StyledPanelHeader, { "$backgroundColor": backgroundColor, "$color": color, "$isActive": currentIsActive, onDoubleClick: () => {
129
137
  if (onHeaderDoubleClick)
130
138
  onHeaderDoubleClick();
131
139
  else
@@ -22,7 +22,7 @@ const TMFileUploader = ({ deviceType = DeviceType.DESKTOP, onClose, onFileUpload
22
22
  usePreventFileDrop([uploaderRef]);
23
23
  useEffect(() => {
24
24
  if (!defaultBlob) {
25
- clearFile();
25
+ clearFile(false);
26
26
  return;
27
27
  }
28
28
  ;
@@ -51,6 +51,7 @@ const TMFileUploader = ({ deviceType = DeviceType.DESKTOP, onClose, onFileUpload
51
51
  const file = e.dataTransfer.files[0];
52
52
  if (file)
53
53
  handleFile(file);
54
+ refocusAfterFileInput();
54
55
  };
55
56
  // Funzione per rifocalizzare il pannello dopo la selezione del file
56
57
  const refocusAfterFileInput = useCallback(() => {
@@ -63,7 +64,7 @@ const TMFileUploader = ({ deviceType = DeviceType.DESKTOP, onClose, onFileUpload
63
64
  // Important: Rifocalizza dopo che il file input ha finito
64
65
  refocusAfterFileInput();
65
66
  }, [onFileUpload, refocusAfterFileInput]);
66
- const clearFile = useCallback(() => {
67
+ const clearFile = useCallback((refocus) => {
67
68
  setUploadedFile(null);
68
69
  setFileName('');
69
70
  setFileSize(undefined);
@@ -73,17 +74,18 @@ const TMFileUploader = ({ deviceType = DeviceType.DESKTOP, onClose, onFileUpload
73
74
  if (fileInput) {
74
75
  fileInput.value = '';
75
76
  }
76
- // Rifocalizza dopo la cancellazione del file
77
- refocusAfterFileInput();
77
+ //Rifocalizza dopo la cancellazione del file
78
+ if (refocus)
79
+ refocusAfterFileInput();
78
80
  }, [onFileUpload, refocusAfterFileInput]);
79
81
  const browseHandler = useCallback(() => {
80
82
  document.getElementById('fileInput')?.click();
81
83
  }, []);
82
84
  let content = !uploadedFile ?
83
85
  _jsx("div", { style: { display: 'flex', gap: 10, width: '100%', height: '100%' }, children: _jsxs(UploadContainer, { ref: uploaderRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, style: { backgroundColor: dragOver ? '#76b1e6' : 'white' }, onDoubleClick: browseHandler, "$isRequired": isRequired, children: [_jsx("div", { style: { display: 'flex', gap: '10px', flexDirection: 'column', position: 'absolute', right: 5, top: 5 }, children: _jsx(TMButton, { btnStyle: 'icon', caption: 'Sfoglia', color: isRequired && !uploadedFile ? 'error' : 'primary', onClick: browseHandler, icon: _jsx(IconFolderOpen, { fontSize: 22 }) }) }), _jsx("p", { style: { fontSize: '1.2rem', fontWeight: 'bold' }, children: deviceType === DeviceType.MOBILE ? 'Clicca per sfogliare il tuo file' : 'Trascina il tuo file qui o fai doppio click per sfogliarlo' }), isRequired && _jsxs("p", { style: { fontWeight: 'bold' }, children: [" ", SDKUI_Localizator.RequiredField, " "] })] }) }) :
84
- _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, width: '100%', height: '100%' }, children: [_jsxs("div", { style: { backgroundColor: 'white', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.primaryColor }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 5 }, children: [_jsx("p", { children: "File name:" }), _jsxs("div", { style: { fontWeight: 'bold' }, children: [fileName, " ", _jsxs("span", { children: [" ", ` (${formatBytes(fileSize)})`, " "] })] })] }), uploadedFile && _jsx(TMButton, { btnStyle: 'icon', color: 'error', caption: 'Pulisci', onClick: clearFile, icon: _jsx(IconClear, { fontSize: 22 }) })] }), extensionHandler(fileExt) === FileExtensionHandler.READY_TO_SHOW ? _jsx(TMFileViewer, { fileBlob: uploadedFile, isResizingActive: isResizingActive }) :
86
+ _jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 10, width: '100%', height: '100%' }, children: [_jsxs("div", { style: { backgroundColor: 'white', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.primaryColor }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 5 }, children: [_jsx("p", { children: "File name:" }), _jsxs("div", { style: { fontWeight: 'bold' }, children: [fileName, " ", _jsxs("span", { children: [" ", ` (${formatBytes(fileSize)})`, " "] })] })] }), uploadedFile && _jsx(TMButton, { btnStyle: 'icon', color: 'error', caption: 'Pulisci', onClick: () => clearFile(true), icon: _jsx(IconClear, { fontSize: 22 }) })] }), extensionHandler(fileExt) === FileExtensionHandler.READY_TO_SHOW ? _jsx(TMFileViewer, { fileBlob: uploadedFile, isResizingActive: isResizingActive }) :
85
87
  _jsx("div", { style: { backgroundColor: '#f6dbdb', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.error }, children: _jsxs("div", { children: [" ", 'Anteprima non disponibile.', fileExt && _jsx("b", { children: ` (*.${fileExt})` })] }) })] });
86
- return (_jsx(TMPanel, { ref: fileUploaderPanelRef, title: SDKUI_Localizator.FileUpload, onBack: deviceType === DeviceType.MOBILE ? () => onClose?.() : undefined, toolbar: deviceType !== DeviceType.MOBILE ? _jsx(StyledHeaderIcon, { onClick: onClose, "$color": 'white', children: _jsx(TMTooltip, { content: SDKUI_Localizator.Close, children: _jsx(IconCloseOutline, {}) }) }) : undefined, children: _jsxs("div", { style: { width: '100%', height: '100%', padding: '2px', display: 'flex', flexDirection: 'column', gap: 10 }, children: [_jsx(HiddenInput, { id: "fileInput", type: "file", onChange: handleInputChange }), content] }) }));
88
+ return (_jsx(TMPanel, { ref: fileUploaderPanelRef, panelID: 'file-uploader-panel', title: SDKUI_Localizator.FileUpload, onBack: deviceType === DeviceType.MOBILE ? () => onClose?.() : undefined, toolbar: deviceType !== DeviceType.MOBILE ? _jsx(StyledHeaderIcon, { onClick: onClose, "$color": 'white', children: _jsx(TMTooltip, { content: SDKUI_Localizator.Close, children: _jsx(IconCloseOutline, {}) }) }) : undefined, children: _jsxs("div", { style: { width: '100%', height: '100%', padding: '2px', display: 'flex', flexDirection: 'column', gap: 10 }, children: [_jsx(HiddenInput, { id: "fileInput", type: "file", onChange: handleInputChange }), content] }) }));
87
89
  };
88
90
  const UploadContainer = styled.div `
89
91
  position: relative;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@topconsultnpm/sdkui-react-beta",
3
- "version": "6.14.29",
3
+ "version": "6.14.30",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "test": "echo \"Error: no test specified\" && exit 1",