@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
|
|
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,
|
|
69
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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, {
|
|
133
|
+
return (_jsxs(StyledPanelContainer, { ref: panelRef, "$isMaximized": onMaximize ? false : isMaximized, style: {
|
|
126
134
|
visibility: isVisible ? 'visible' : 'hidden',
|
|
127
|
-
}, tabIndex: -1, onFocus:
|
|
128
|
-
_jsx(StyledPanelHeader, { "$backgroundColor": backgroundColor, "$color": color, "$isActive":
|
|
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
|
-
//
|
|
77
|
-
|
|
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;
|