@topconsultnpm/sdkui-react 6.21.0-dev4.15 → 6.21.0-dev4.17

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.
@@ -11,25 +11,28 @@ export interface ITMTreeItem {
11
11
  currentPage?: number;
12
12
  totalItemsCount?: number;
13
13
  }
14
- interface ITMTreeViewProps<T extends ITMTreeItem> {
15
- dataSource?: T[];
16
- focusedItem?: T | null;
17
- selectedItems?: T[];
14
+ interface ITMTreeViewProps {
15
+ dataSource?: ITMTreeItem[];
16
+ focusedItem?: ITMTreeItem | null;
17
+ selectedItems?: ITMTreeItem[];
18
18
  allowMultipleSelection?: boolean;
19
- calculateItemsForNode?: (node: T) => Promise<T[] | undefined>;
20
- itemRender: (item: T | null) => JSX.Element;
21
- onFocusedItemChanged?: (item: T | null) => void;
22
- onSelectionChanged?: (selectedItems: T[]) => void;
23
- onNodeUpdate?: (updatedNode: T) => void;
24
- onDataChanged?: (items: T[]) => void;
25
- shouldDelayFocusOnEvent?: (node: T, event: React.MouseEvent) => boolean;
26
- onItemContextMenu?: (item: T, e: React.MouseEvent) => void;
19
+ calculateItemsForNode?: (node: ITMTreeItem) => Promise<ITMTreeItem[] | undefined>;
20
+ itemRender: (item: ITMTreeItem | null) => JSX.Element;
21
+ onFocusedItemChanged?: (item: ITMTreeItem | null) => void;
22
+ onSelectionChanged?: (selectedItems: ITMTreeItem[]) => void;
23
+ onNodeUpdate?: (updatedNode: ITMTreeItem) => void;
24
+ onDataChanged?: (items: ITMTreeItem[]) => void;
25
+ shouldDelayFocusOnEvent?: (node: ITMTreeItem, event: React.MouseEvent) => boolean;
26
+ onItemContextMenu?: (item: ITMTreeItem, e: React.MouseEvent) => void;
27
27
  autoSelectChildren?: boolean;
28
28
  itemsPerPage?: number;
29
29
  showLoadMoreButton?: boolean;
30
+ enableVirtualization?: boolean;
30
31
  }
31
- declare const TMTreeView: <T extends ITMTreeItem>({ dataSource, focusedItem, selectedItems, allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent, onItemContextMenu, autoSelectChildren, itemsPerPage, showLoadMoreButton }: ITMTreeViewProps<T>) => import("react/jsx-runtime").JSX.Element;
32
+ declare const TMTreeView: ({ dataSource, focusedItem, selectedItems, allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent, onItemContextMenu, autoSelectChildren, itemsPerPage, showLoadMoreButton, enableVirtualization }: ITMTreeViewProps) => import("react/jsx-runtime").JSX.Element;
32
33
  export default TMTreeView;
34
+ export declare const StyledTreeContainer: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
35
+ export declare const StyledItemContent: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never> & Partial<Pick<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, never>>> & string;
33
36
  export declare const StyledTreeNode: import("styled-components/dist/types").IStyledComponentBase<"web", import("styled-components").FastOmit<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$isSelected"> & {
34
37
  $isSelected?: boolean;
35
38
  }, never> & Partial<Pick<import("styled-components").FastOmit<React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement>, "$isSelected"> & {
@@ -1,9 +1,147 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useRef } from 'react';
2
+ import React, { useCallback, useEffect, useMemo, useRef, memo } from 'react';
3
+ import { List } from 'react-window';
3
4
  import styled from 'styled-components';
4
5
  import { IconArrowLeft, IconArrowRight, IconChevronDown, IconChevronRight, SDKUI_Localizator } from '../../helper';
5
6
  import TMButton from './TMButton';
6
- const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent, onItemContextMenu, autoSelectChildren = true, itemsPerPage = 100, showLoadMoreButton = true }) => {
7
+ // Componente riga virtualizzata memoizzato
8
+ const VirtualRowComponent = memo(({ ariaAttributes, index, style, flattenedItems, focusedItemKey, selectedItemKeys, allowMultipleSelection, hasVisibleItems, handleNodeToggle, handleNodeClick, handleCheckboxChange, hasPartialChildSelection, itemRender, onItemContextMenu }) => {
9
+ const item = flattenedItems[index];
10
+ if (!item)
11
+ return null;
12
+ const { node, depth } = item;
13
+ const isSelected = node.key === focusedItemKey;
14
+ return (_jsx("div", { ...ariaAttributes, "data-node-key": node.key, style: {
15
+ ...style,
16
+ paddingLeft: depth * 20,
17
+ display: 'flex',
18
+ alignItems: 'center',
19
+ width: 'fit-content',
20
+ minWidth: '100%',
21
+ paddingRight: 10,
22
+ boxSizing: 'border-box'
23
+ }, children: _jsxs(StyledTreeNode, { "$isSelected": isSelected, children: [_jsx("div", { style: {
24
+ display: 'flex',
25
+ alignItems: 'center',
26
+ justifyContent: 'center',
27
+ minHeight: '18px',
28
+ minWidth: '18px',
29
+ maxHeight: '18px',
30
+ maxWidth: '18px',
31
+ flexShrink: 0
32
+ }, onClick: (e) => { handleNodeToggle(node.key, e.ctrlKey); }, children: hasVisibleItems(node)
33
+ ? node.expanded
34
+ ? _jsx(IconChevronDown, { cursor: 'pointer', fontSize: 14 })
35
+ : _jsx(IconChevronRight, { cursor: 'pointer', fontSize: 14 })
36
+ : _jsx("div", { style: { height: '18px', width: '18px' } }) }), allowMultipleSelection && (_jsx("input", { type: "checkbox", checked: selectedItemKeys.has(node.key), onChange: (e) => handleCheckboxChange(node, e.target.checked), onClick: (e) => e.stopPropagation(), style: { flexShrink: 0 }, ref: input => {
37
+ if (input) {
38
+ // Imposta lo stato visuale "trattino" sulla checkbox quando il container
39
+ // ha alcuni figli selezionati ma non tutti. Necessario usare ref perché
40
+ // "indeterminate" è una proprietà DOM, non un attributo HTML.
41
+ input.indeterminate = hasPartialChildSelection(node);
42
+ }
43
+ } })), _jsx(StyledItemContent, { onClick: (e) => { handleNodeClick(node, e); }, onContextMenu: (e) => {
44
+ if (onItemContextMenu) {
45
+ e.preventDefault();
46
+ onItemContextMenu(node, e);
47
+ }
48
+ }, children: itemRender(node) })] }) }));
49
+ }, (prevProps, nextProps) => {
50
+ // Custom comparison: re-render solo se i dati rilevanti per questa riga cambiano
51
+ const prevItem = prevProps.flattenedItems[prevProps.index];
52
+ const nextItem = nextProps.flattenedItems[nextProps.index];
53
+ if (!prevItem || !nextItem)
54
+ return prevItem === nextItem;
55
+ const prevNode = prevItem.node;
56
+ const nextNode = nextItem.node;
57
+ // Controlla se questa riga specifica è cambiata
58
+ return (prevNode.key === nextNode.key &&
59
+ prevNode.expanded === nextNode.expanded &&
60
+ prevItem.depth === nextItem.depth &&
61
+ (prevNode.key === prevProps.focusedItemKey) === (nextNode.key === nextProps.focusedItemKey) &&
62
+ prevProps.selectedItemKeys.has(prevNode.key) === nextProps.selectedItemKeys.has(nextNode.key) &&
63
+ prevProps.allowMultipleSelection === nextProps.allowMultipleSelection);
64
+ });
65
+ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMultipleSelection, onDataChanged, calculateItemsForNode, itemRender, onNodeUpdate, onFocusedItemChanged, onSelectionChanged, shouldDelayFocusOnEvent, onItemContextMenu, autoSelectChildren = true, itemsPerPage = 100, showLoadMoreButton = true, enableVirtualization = false }) => {
66
+ // Altezza fissa per ogni riga nella virtualizzazione
67
+ const VIRTUAL_ROW_HEIGHT = 26;
68
+ const listRef = useRef(null);
69
+ const containerRef = useRef(null);
70
+ const [containerSize, setContainerSize] = React.useState({ width: 0, height: 0 });
71
+ // Ref per selectedItems - permette alle callback memoizzate di accedere sempre al valore più recente
72
+ const selectedItemsRef = useRef(selectedItems);
73
+ selectedItemsRef.current = selectedItems;
74
+ // Ref per handleCheckboxChange - permette all'useEffect di accedere alla funzione prima che sia definita
75
+ const handleCheckboxChangeRef = useRef(() => { });
76
+ // Filtra gli items da mostrare in base alla paginazione (definito prima per essere usato da flattenTreeWithDepth)
77
+ const getVisibleItems = useCallback((node) => {
78
+ if (!node.items)
79
+ return [];
80
+ const totalItems = node.items.length;
81
+ // Se non c'è paginazione attiva o gli items sono pochi, mostra tutti
82
+ if (totalItems <= itemsPerPage || !showLoadMoreButton) {
83
+ return node.items;
84
+ }
85
+ // Altrimenti mostra solo gli items della pagina corrente
86
+ const currentPage = node.currentPage ?? 0;
87
+ const startIndex = currentPage * itemsPerPage;
88
+ const endIndex = startIndex + itemsPerPage;
89
+ return node.items.slice(startIndex, endIndex);
90
+ }, [itemsPerPage, showLoadMoreButton]);
91
+ // Misura le dimensioni del container per react-window
92
+ useEffect(() => {
93
+ if (!enableVirtualization || !containerRef.current)
94
+ return;
95
+ const resizeObserver = new ResizeObserver((entries) => {
96
+ for (const entry of entries) {
97
+ const { width, height } = entry.contentRect;
98
+ setContainerSize({ width, height });
99
+ }
100
+ });
101
+ resizeObserver.observe(containerRef.current);
102
+ return () => resizeObserver.disconnect();
103
+ }, [enableVirtualization]);
104
+ // Flatten tree con informazioni di profondità per la virtualizzazione
105
+ const flattenTreeWithDepth = useCallback((nodes, depth = 0, parentKey) => {
106
+ let flatList = [];
107
+ nodes.forEach(node => {
108
+ if (!node.hidden) {
109
+ flatList.push({ node, depth, parentKey });
110
+ if (node.expanded && node.items) {
111
+ const visibleItems = getVisibleItems(node);
112
+ flatList = flatList.concat(flattenTreeWithDepth(visibleItems, depth + 1, node.key));
113
+ }
114
+ }
115
+ });
116
+ return flatList;
117
+ }, [getVisibleItems]);
118
+ // Lista piatta per la virtualizzazione
119
+ const flattenedItems = useMemo(() => {
120
+ if (!enableVirtualization)
121
+ return [];
122
+ return flattenTreeWithDepth(dataSource);
123
+ }, [enableVirtualization, dataSource, flattenTreeWithDepth]);
124
+ // Scroll verso l'elemento focalizzato quando cambia (navigazione con frecce)
125
+ useEffect(() => {
126
+ if (!focusedItem?.key)
127
+ return;
128
+ if (enableVirtualization) {
129
+ // Lista virtualizzata: usa scrollToRow per garantire che l'elemento sia visibile
130
+ const index = flattenedItems.findIndex(item => item.node.key === focusedItem.key);
131
+ if (index >= 0 && listRef.current) {
132
+ listRef.current.scrollToRow({ index, align: 'smart' });
133
+ }
134
+ }
135
+ else {
136
+ // Lista standard: usa scrollIntoView
137
+ if (containerRef.current) {
138
+ const element = containerRef.current.querySelector(`[data-node-key="${focusedItem.key}"]`);
139
+ if (element) {
140
+ element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
141
+ }
142
+ }
143
+ }
144
+ }, [focusedItem?.key, enableVirtualization, flattenedItems]);
7
145
  useEffect(() => {
8
146
  const handleKeyDown = (event) => {
9
147
  if (!focusedItem)
@@ -33,9 +171,14 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
33
171
  handled = true;
34
172
  }
35
173
  break;
36
- // case ' ':
37
- // allowMultipleSelection && handleCheckboxChange(focusedItem, !selectedItems.some(item => item.key === focusedItem.key));
38
- // break;
174
+ case ' ':
175
+ case 'Spacebar': // IE/Edge legacy
176
+ if (allowMultipleSelection) {
177
+ const isCurrentlySelected = selectedItemsRef.current.some(item => item.key === focusedItem.key);
178
+ handleCheckboxChangeRef.current(focusedItem, !isCurrentlySelected);
179
+ handled = true;
180
+ }
181
+ break;
39
182
  // case '*':
40
183
  // handleExpandAllNodes();
41
184
  // break;
@@ -57,7 +200,7 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
57
200
  return () => {
58
201
  window.removeEventListener('keydown', handleKeyDown, true);
59
202
  };
60
- }, [focusedItem, dataSource, onFocusedItemChanged]);
203
+ }, [focusedItem, dataSource, onFocusedItemChanged, allowMultipleSelection]);
61
204
  const findNextItem = (nodes, currentItem) => {
62
205
  const flatList = flattenTree(nodes);
63
206
  const currentIndex = flatList.findIndex(item => item.key === currentItem.key);
@@ -162,7 +305,9 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
162
305
  onFocusedItemChanged?.(node); // Chiama onFocusedItemChanged immediatamente
163
306
  }
164
307
  }, [onFocusedItemChanged, calculateItemsForNode, onNodeUpdate]);
165
- const handleCheckboxChange = (node, checked) => {
308
+ const handleCheckboxChange = useCallback((node, checked) => {
309
+ // Usa la ref per accedere sempre al valore più recente di selectedItems
310
+ const currentSelectedItems = selectedItemsRef.current;
166
311
  // Funzione helper per trovare il parent di un nodo e verificare se tutti i suoi figli sono selezionati
167
312
  const findAndCheckParents = (items, targetNode, currentSelection) => {
168
313
  const parentsToAdd = [];
@@ -199,11 +344,14 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
199
344
  if (node.isContainer && autoSelectChildren) {
200
345
  // Quando selezioni un container, aggiungi il container stesso + tutti i figli (se autoSelectChildren è true)
201
346
  const allChildren = flattenTree(node.items || []);
202
- newSelectedItems = [...selectedItems, node, ...allChildren];
347
+ // Rimuovi eventuali duplicati: filtra gli elementi già presenti nella selezione
348
+ const childrenKeys = new Set(allChildren.map(child => child.key));
349
+ const filteredSelectedItems = currentSelectedItems.filter(item => !childrenKeys.has(item.key) && item.key !== node.key);
350
+ newSelectedItems = [...filteredSelectedItems, node, ...allChildren];
203
351
  }
204
- else if (!selectedItems.some(item => item.key === node.key)) {
352
+ else if (!currentSelectedItems.some(item => item.key === node.key)) {
205
353
  // Quando selezioni un figlio o un container con autoSelectChildren=false, aggiungi solo il nodo
206
- newSelectedItems = [...selectedItems, node];
354
+ newSelectedItems = [...currentSelectedItems, node];
207
355
  // Verifica se selezionando questo figlio sono ora selezionati tutti i figli del parent (solo se autoSelectChildren è true)
208
356
  if (autoSelectChildren) {
209
357
  const parentsToAdd = findAndCheckParents(dataSource, node, newSelectedItems);
@@ -211,17 +359,17 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
211
359
  }
212
360
  }
213
361
  else {
214
- newSelectedItems = selectedItems;
362
+ newSelectedItems = currentSelectedItems;
215
363
  }
216
364
  }
217
365
  else if (node.isContainer && autoSelectChildren) {
218
366
  // Quando deselezioni un container, rimuovi il container stesso + tutti i figli (solo se autoSelectChildren è true)
219
367
  const childKeys = flattenTree(node.items || []).map(item => item.key);
220
- newSelectedItems = selectedItems.filter(item => item.key !== node.key && !childKeys.includes(item.key));
368
+ newSelectedItems = currentSelectedItems.filter(item => item.key !== node.key && !childKeys.includes(item.key));
221
369
  }
222
370
  else {
223
371
  // Quando deselezioni un figlio o un container con autoSelectChildren=false, rimuovi solo il nodo
224
- newSelectedItems = selectedItems.filter(item => item.key !== node.key);
372
+ newSelectedItems = currentSelectedItems.filter(item => item.key !== node.key);
225
373
  // Se il figlio apparteneva a un parent che era selezionato, rimuovi anche il parent (solo se autoSelectChildren è true)
226
374
  if (autoSelectChildren && !node.isContainer) {
227
375
  const removeParentContainers = (items) => {
@@ -243,28 +391,10 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
243
391
  }
244
392
  }
245
393
  onSelectionChanged?.(newSelectedItems);
246
- };
247
- // const handleExpandAllNodes = () => {
248
- // const expandAll = (nodes: T[]): T[] => {
249
- // return nodes.map(node => ({
250
- // ...node,
251
- // expanded: true,
252
- // items: node.items ? expandAll(node.items as T[]) : node.items
253
- // }));
254
- // };
255
- // setTreeData(prevData => expandAll(prevData));
256
- // };
257
- // const handleCollapseAllNodes = () => {
258
- // const collapseAll = (nodes: T[]): T[] => {
259
- // return nodes.map(node => ({
260
- // ...node,
261
- // expanded: false,
262
- // items: node.items ? collapseAll(node.items as T[]) : node.items
263
- // }));
264
- // };
265
- // setTreeData(prevData => collapseAll(prevData));
266
- // };
267
- const isIndeterminate = (node) => {
394
+ }, [autoSelectChildren, dataSource, onSelectionChanged]);
395
+ // Aggiorna la ref con l'ultima versione di handleCheckboxChange
396
+ handleCheckboxChangeRef.current = handleCheckboxChange;
397
+ const hasPartialChildSelection = (node) => {
268
398
  // Lo stato indeterminate ha senso solo quando autoSelectChildren è true
269
399
  if (!autoSelectChildren)
270
400
  return false;
@@ -275,13 +405,6 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
275
405
  const selectedCount = childKeys.filter(key => selectedChildKeys.includes(key)).length;
276
406
  return selectedCount > 0 && selectedCount < childKeys.length;
277
407
  };
278
- const isFullySelected = (node) => {
279
- if (!node.isContainer || !node.items)
280
- return false;
281
- const childKeys = flattenTree(node.items).map(item => item.key);
282
- const selectedChildKeys = selectedItems.map(item => item.key);
283
- return childKeys.every(key => selectedChildKeys.includes(key));
284
- };
285
408
  const hasVisibleItems = (node) => {
286
409
  // If node has explicit isExpandible value, use that
287
410
  if (node.isExpandible !== undefined) {
@@ -312,21 +435,6 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
312
435
  const updatedData = updateNodePage(dataSource);
313
436
  onDataChanged?.(updatedData);
314
437
  }, [dataSource, onDataChanged, onNodeUpdate]);
315
- // Filtra gli items da mostrare in base alla paginazione
316
- const getVisibleItems = useCallback((node) => {
317
- if (!node.items)
318
- return [];
319
- const totalItems = node.items.length;
320
- // Se non c'è paginazione attiva o gli items sono pochi, mostra tutti
321
- if (totalItems <= itemsPerPage || !showLoadMoreButton) {
322
- return node.items;
323
- }
324
- // Altrimenti mostra solo gli items della pagina corrente
325
- const currentPage = node.currentPage ?? 0;
326
- const startIndex = currentPage * itemsPerPage;
327
- const endIndex = startIndex + itemsPerPage;
328
- return node.items.slice(startIndex, endIndex);
329
- }, [itemsPerPage, showLoadMoreButton]);
330
438
  // Verifica se c'è bisogno del paginatore
331
439
  const needsPagination = useCallback((node) => {
332
440
  if (!showLoadMoreButton || !node.items)
@@ -340,7 +448,7 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
340
448
  return Math.ceil(node.items.length / itemsPerPage);
341
449
  }, [itemsPerPage]);
342
450
  const renderTree = useCallback((nodes) => {
343
- return nodes.map(node => !node.hidden && (_jsxs("div", { style: { width: '100%', margin: 0, padding: 0 }, children: [_jsxs(StyledTreeNode, { "$isSelected": node.key === focusedItem?.key, children: [_jsx("div", { style: {
451
+ return nodes.map(node => !node.hidden && (_jsxs("div", { style: { width: 'fit-content', minWidth: '100%', margin: 0, padding: 0 }, children: [_jsxs(StyledTreeNode, { "$isSelected": node.key === focusedItem?.key, "data-node-key": node.key, children: [_jsx("div", { style: {
344
452
  display: 'flex',
345
453
  alignItems: 'center',
346
454
  justifyContent: 'center',
@@ -355,28 +463,86 @@ const TMTreeView = ({ dataSource = [], focusedItem, selectedItems = [], allowMul
355
463
  : _jsx(IconChevronRight, { cursor: 'pointer', fontSize: 14 })
356
464
  : _jsx("div", { style: { height: '18px', width: '18px' } }) }), allowMultipleSelection && (_jsx("input", { type: "checkbox", checked: selectedItems.some(item => item.key === node.key), onChange: (e) => handleCheckboxChange(node, e.target.checked), onClick: (e) => e.stopPropagation(), style: { flexShrink: 0 }, ref: input => {
357
465
  if (input) {
358
- input.indeterminate = isIndeterminate(node);
466
+ // Imposta lo stato visuale "trattino" () sulla checkbox quando il container
467
+ // ha alcuni figli selezionati ma non tutti. Necessario usare ref perché
468
+ // "indeterminate" è una proprietà DOM, non un attributo HTML.
469
+ input.indeterminate = hasPartialChildSelection(node);
359
470
  }
360
- } })), _jsx("div", { style: { display: 'flex', alignItems: 'center', flex: 1, minWidth: 0 }, onClick: (e) => { handleNodeClick(node, e); }, onContextMenu: (e) => {
471
+ } })), _jsx(StyledItemContent, { onClick: (e) => { handleNodeClick(node, e); }, onContextMenu: (e) => {
361
472
  if (onItemContextMenu) {
362
473
  e.preventDefault();
363
474
  onItemContextMenu(node, e);
364
475
  }
365
- }, children: itemRender(node) })] }), node.expanded && node.items && (_jsxs("div", { style: { paddingLeft: 20, width: '100%' }, children: [renderTree(getVisibleItems(node)), needsPagination(node) && (_jsxs(StyledStickyPaginator, { children: [_jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) - 1), showTooltip: false, caption: "\u25C4", icon: _jsx(IconArrowLeft, { color: 'white' }), disabled: (node.currentPage ?? 0) <= 0 }), _jsx("span", { style: { fontSize: '11px', whiteSpace: 'nowrap', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0, color: 'white' }, children: SDKUI_Localizator.PaginationInfo.replaceParams((node.currentPage ?? 0) + 1, getTotalPages(node), node.items?.length ?? 0) }), _jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) + 1), showTooltip: false, caption: "\u25BA", icon: _jsx(IconArrowRight, { color: 'white' }), disabled: (node.currentPage ?? 0) >= getTotalPages(node) - 1 })] }))] }))] }, node.key)));
476
+ }, children: itemRender(node) })] }), node.expanded && node.items && (_jsxs("div", { style: { paddingLeft: 20, width: 'fit-content', minWidth: '100%' }, children: [renderTree(getVisibleItems(node)), needsPagination(node) && (_jsxs(StyledStickyPaginator, { children: [_jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) - 1), showTooltip: false, caption: "\u25C4", icon: _jsx(IconArrowLeft, { color: 'white' }), disabled: (node.currentPage ?? 0) <= 0 }), _jsx("span", { style: { fontSize: '11px', whiteSpace: 'nowrap', fontWeight: 500, overflow: 'hidden', textOverflow: 'ellipsis', minWidth: 0, color: 'white' }, children: SDKUI_Localizator.PaginationInfo.replaceParams((node.currentPage ?? 0) + 1, getTotalPages(node), node.items?.length ?? 0) }), _jsx(TMButton, { btnStyle: 'icon', onClick: () => handlePageChange(node.key, (node.currentPage ?? 0) + 1), showTooltip: false, caption: "\u25BA", icon: _jsx(IconArrowRight, { color: 'white' }), disabled: (node.currentPage ?? 0) >= getTotalPages(node) - 1 })] }))] }))] }, node.key)));
366
477
  }, [handleNodeClick, handleNodeToggle, handleCheckboxChange, focusedItem, selectedItems, allowMultipleSelection, getVisibleItems, needsPagination, handlePageChange, getTotalPages, onItemContextMenu]);
367
- return (_jsx("div", { style: { height: '100%', width: '100%', overflowY: 'auto', overflowX: 'hidden', padding: '0px 5px 2px 2px' }, children: renderTree(dataSource) }));
478
+ // Calcola selectedItemKeys ad ogni render per garantire sincronizzazione con selectedItems
479
+ const selectedItemKeys = new Set(selectedItems.map(s => s.key));
480
+ // Memoizza rowPropsData per evitare re-render della List
481
+ const rowPropsData = useMemo(() => ({
482
+ flattenedItems,
483
+ focusedItemKey: focusedItem?.key,
484
+ selectedItemKeys,
485
+ allowMultipleSelection,
486
+ hasVisibleItems,
487
+ handleNodeToggle,
488
+ handleNodeClick,
489
+ handleCheckboxChange,
490
+ hasPartialChildSelection: hasPartialChildSelection,
491
+ itemRender,
492
+ onItemContextMenu
493
+ }), [
494
+ flattenedItems,
495
+ focusedItem?.key,
496
+ selectedItemKeys,
497
+ allowMultipleSelection,
498
+ hasVisibleItems,
499
+ handleNodeToggle,
500
+ handleNodeClick,
501
+ handleCheckboxChange,
502
+ hasPartialChildSelection,
503
+ itemRender,
504
+ onItemContextMenu
505
+ ]);
506
+ // Render virtualizzato con react-window v2
507
+ if (enableVirtualization) {
508
+ return (_jsx(StyledTreeContainer, { ref: containerRef, children: containerSize.height > 0 && (_jsx(List, { listRef: listRef, rowCount: flattenedItems.length, rowHeight: VIRTUAL_ROW_HEIGHT, rowComponent: VirtualRowComponent, rowProps: rowPropsData, style: { height: containerSize.height, width: '100%' }, overscanCount: 5 })) }));
509
+ }
510
+ // Render standard (non virtualizzato) con scrolling orizzontale
511
+ return (_jsx(StyledTreeContainer, { ref: containerRef, children: _jsx("div", { children: renderTree(dataSource) }) }));
368
512
  };
369
513
  export default TMTreeView;
514
+ // Container principale con scrolling orizzontale
515
+ export const StyledTreeContainer = styled.div `
516
+ height: 100%;
517
+ width: 100%;
518
+ overflow-y: auto;
519
+ overflow-x: auto;
520
+ padding: 0px 0px 2px 2px;
521
+
522
+ /* Contenitore interno che si espande per accomodare nodi annidati */
523
+ & > div {
524
+ width: fit-content;
525
+ min-width: 100%;
526
+ }
527
+ `;
528
+ // Contenuto dell'item - niente ellipsis per permettere scrolling orizzontale del container
529
+ export const StyledItemContent = styled.div `
530
+ display: flex;
531
+ align-items: center;
532
+ flex-shrink: 0;
533
+ white-space: nowrap;
534
+ `;
370
535
  export const StyledTreeNode = styled.div `
371
536
  display: flex;
372
537
  flex-direction: row;
373
- width: 100%;
374
- min-width: 0;
538
+ width: fit-content;
539
+ min-width: 100%;
375
540
  min-height: 22px;
376
541
  max-height: 30px;
377
542
  gap: 5px;
378
543
  align-items: center;
379
544
  padding: 0;
545
+ padding-right: 10px; /* Spazio extra a destra per evitare che il testo tocchi il bordo */
380
546
  margin: 0;
381
547
  background: ${(props) => props.$isSelected ? 'oklch(from var(--dx-color-primary) l c h / .2) !important' : 'transparent'};
382
548
 
@@ -99,7 +99,7 @@ const TMFileUploader = ({ fromDTD, deviceType = DeviceType.DESKTOP, onClose, onF
99
99
  let content = !uploadedFile ?
100
100
  _jsxs("div", { style: { display: 'flex', gap: 10, width: '100%', height: '100%' }, children: [_jsx(HiddenInput, { id: "fileInput", type: "file", onChange: handleInputChange }), _jsxs(UploadContainer, { ref: uploaderRef, tabIndex: 0, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, style: { backgroundColor: dragOver ? '#76b1e6' : 'white' }, onDoubleClick: browseHandler, "$isRequired": isRequired, children: [_jsxs("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 }) }), showScannerIcon && isScannerLicenseConfigured() && onScanRequest && _jsx(TMButton, { btnStyle: 'icon', caption: 'Scanner', color: 'primary', onClick: () => { onScanRequest((file) => { onFileUpload?.(file); }); }, icon: _jsx(IconScanner, { fontSize: 22 }) }), showScannerIcon && isScannerLicenseConfigured() && !onScanRequest && _jsx(TMButton, { btnStyle: 'icon', caption: 'Scanner', color: 'primary', onClick: () => { ShowAlert({ message: SDKUI_Localizator.ScanFeatureUnavailableInThisContext, mode: 'info', duration: 3000, title: 'Scanner' }); }, icon: _jsx(IconScanner, { fontSize: 22 }) })] }), _jsx("p", { style: { fontSize: '1.2rem', fontWeight: 'bold' }, children: deviceType === DeviceType.MOBILE ? SDKUI_Localizator.ClickToBrowseFile : SDKUI_Localizator.DragOrDoubleClickToBrowseFile }), isRequired && _jsxs("p", { style: { fontWeight: 'bold' }, children: [" ", SDKUI_Localizator.RequiredField, " "] })] })] }) :
101
101
  _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 }) :
102
- _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})` })] }) })] });
102
+ _jsx("div", { style: { backgroundColor: '#f6dbdb', padding: '5px 10px', borderRadius: 8, display: 'flex', alignItems: 'center', justifyContent: 'space-between', color: TMColors.error }, children: _jsxs("div", { children: [" ", SDKUI_Localizator.PreviewNotAvailable, fileExt && _jsx("b", { children: ` (*.${fileExt})` })] }) })] });
103
103
  const innerContent = (_jsxs("div", { style: { width: '100%', height: '100%', padding: '2px', display: 'flex', flexDirection: 'column', gap: 10 }, children: [enableDragDropOverlay && _jsx(TMDragDropOverlay, { handleFile: handleFile, refocusAfterFileInput: refocusAfterFileInput }), content] }));
104
104
  const toolbar = useMemo(() => {
105
105
  return (_jsxs(_Fragment, { children: [(isPdfEditorAvailable(fromDTD, fileExt) && openFileUploaderPdfEditor) && (_jsx(TMCommandsContextMenu, { target: "#TMPanel-FileUploader-Commands-Header", menuItems: [
@@ -7,14 +7,14 @@ import { IconMultipleSelection, IconCheckFile, IconDetailDcmts, SDKUI_Localizato
7
7
  import { FormModes, SearchResultContext } from '../../../ts';
8
8
  import { TMColors } from '../../../utils/theme';
9
9
  import ShowAlert from '../../base/TMAlert';
10
- import { DeviceType } from '../../base/TMDeviceProvider';
10
+ import { DeviceType, useDeviceType } from '../../base/TMDeviceProvider';
11
11
  import { TMSaveFormButtonPrevious, TMSaveFormButtonNext } from '../../forms/TMSaveForm';
12
12
  import TMPanelManagerContainer from '../../layout/panelManager/TMPanelManagerContainer';
13
13
  import { TMPanelManagerProvider, useTMPanelManagerContext } from '../../layout/panelManager/TMPanelManagerContext';
14
14
  import TMSearchResult from '../search/TMSearchResult';
15
15
  import TMDcmtForm from './TMDcmtForm';
16
16
  import { TMNothingToShow } from './TMDcmtPreview';
17
- import { Spinner, TMButton } from '../..';
17
+ import { Spinner, TMButton, TMLayoutWaitingContainer } from '../..';
18
18
  import { useDocumentOperations } from '../../../hooks/useDocumentOperations';
19
19
  import TMToppyMessage from '../../../helper/TMToppyMessage';
20
20
  import TMPanel from '../../base/TMPanel';
@@ -29,6 +29,8 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
29
29
  const [isCheckingFirstLoad, setIsCheckingFirstLoad] = useState(true);
30
30
  const [contextMenuVisible, setContextMenuVisible] = useState(false);
31
31
  const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 });
32
+ // Track if TMRelationViewer is loading (used to disable context menu during loading)
33
+ const [isRelationViewerLoading, setIsRelationViewerLoading] = useState(false);
32
34
  const [dtdFocused, setDtdFocused] = useState();
33
35
  const [refreshKey, setRefreshKey] = useState(0);
34
36
  // Separate refresh key for TMFormOrResultWrapper only (doesn't affect tmTreeView)
@@ -215,6 +217,7 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
215
217
  onRefreshAfterAddDcmtToFavs,
216
218
  },
217
219
  });
220
+ const { dcmtOperations: { abortController, showWaitPanel, showPrimary, waitPanelTitle, waitPanelTextPrimary, waitPanelValuePrimary, waitPanelMaxValuePrimary, showSecondary, waitPanelTextSecondary, waitPanelValueSecondary, waitPanelMaxValueSecondary, }, } = features;
218
221
  // Load dtdMaster when inputDcmts changes
219
222
  useEffect(() => {
220
223
  const loadDtdMaster = async () => {
@@ -267,14 +270,14 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
267
270
  const commandsMenuItems = [
268
271
  {
269
272
  icon: _jsx(IconMultipleSelection, { color: allowMultipleSelection ? TMColors.tertiary : TMColors.text_normal }),
270
- name: "Selezione multipla",
273
+ name: allowMultipleSelection ? "Disabilita selezione multipla" : "Abilita selezione multipla",
271
274
  onClick: () => {
272
275
  setAllowMultipleSelection(prev => !prev);
273
276
  }
274
277
  },
275
278
  {
276
- icon: _jsx(IconCheckFile, {}),
277
- name: "Consenti dettagli con 0 documenti",
279
+ icon: _jsx(IconCheckFile, { color: showZeroDcmts ? TMColors.tertiary : TMColors.text_normal }),
280
+ name: showZeroDcmts ? "Nascondi dettagli con 0 documenti" : "Consenti dettagli con 0 documenti",
278
281
  onClick: () => {
279
282
  setShowZeroDcmts(prev => !prev);
280
283
  }
@@ -296,17 +299,22 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
296
299
  _jsx(TMNothingToShow, { text: getTitle(), secondText: SDKUI_Localizator.NoDataToDisplay, icon: isForMaster ? _jsx(IconDetailDcmts, { fontSize: 96, transform: 'scale(-1, 1)' }) : _jsx(IconDetailDcmts, { fontSize: 96 }) })
297
300
  :
298
301
  _jsxs("div", { ref: floatingBarContainerRef, style: { width: "100%", height: "100%" }, onContextMenu: (e) => {
302
+ // Disable context menu when loading
303
+ if (isRelationViewerLoading) {
304
+ e.preventDefault();
305
+ return;
306
+ }
299
307
  // Mostra context menu anche sullo spazio bianco (quando non si clicca su un item)
300
308
  e.preventDefault();
301
309
  setContextMenuPosition({ x: e.clientX, y: e.clientY });
302
310
  setContextMenuVisible(true);
303
311
  }, children: [_jsx(TMRelationViewerWrapper, { refreshKey: refreshKey, inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, showZeroDcmts: showZeroDcmts,
304
312
  // customItemRender={customItemRender}
305
- allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: handleSelectedItemsChanged, onNoRelationsFound: handleNoRelationsFound, onItemContextMenu: onItemContextMenu, focusedItemFormData: focusedItemFormData }), _jsx(TMContextMenu, { items: operationItems, externalControl: {
313
+ allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: handleSelectedItemsChanged, onNoRelationsFound: handleNoRelationsFound, onItemContextMenu: isRelationViewerLoading ? undefined : onItemContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: setIsRelationViewerLoading }), _jsx(TMContextMenu, { items: operationItems, externalControl: {
306
314
  visible: contextMenuVisible,
307
315
  position: contextMenuPosition,
308
316
  onClose: () => setContextMenuVisible(false)
309
- } })] }) }), [inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, allowMultipleSelection, focusedItem, selectedItems, handleFocusedItemChanged, handleSelectedItemsChanged, handleNoRelationsFound, onItemContextMenu, contextMenuVisible, contextMenuPosition, refreshKey, focusedItemFormData]);
317
+ } })] }) }), [inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, allowMultipleSelection, focusedItem, selectedItems, handleFocusedItemChanged, handleSelectedItemsChanged, handleNoRelationsFound, onItemContextMenu, contextMenuVisible, contextMenuPosition, refreshKey, focusedItemFormData, isRelationViewerLoading]);
310
318
  const tmFormOrResult = useMemo(() => _jsx(TMFormOrResultWrapper, { refreshKey: refreshKeyFormOrResult, deviceType: deviceType, focusedItem: focusedItem, onTaskCreateRequest: onTaskCreateRequest, allTasks: allTasks, getAllTasks: getAllTasks, deleteTaskByIdsCallback: deleteTaskByIdsCallback, addTaskCallback: addTaskCallback, editTaskCallback: editTaskCallback, handleNavigateToWGs: handleNavigateToWGs, handleNavigateToDossiers: handleNavigateToDossiers, handleNavigateToReference: handleNavigateToReference, onRefreshAfterAddDcmtToFavs: onRefreshAfterAddDcmtToFavs, editPdfForm: editPdfForm, openS4TViewer: openS4TViewer, onOpenS4TViewerRequest: onOpenS4TViewerRequest, onOpenPdfEditorRequest: onOpenPdfEditorRequest, onRefreshSearchResults: onRefreshAllPanels }), [focusedItem, deviceType, allTasks, handleNavigateToWGs, handleNavigateToDossiers, handleNavigateToReference, editPdfForm, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, onRefreshAfterAddDcmtToFavs, refreshKeyFormOrResult]);
311
319
  const initialPanelDimensions = {
312
320
  'tmTreeView': { width: '50%', height: '100%' },
@@ -384,7 +392,7 @@ const TMMasterDetailDcmts = ({ allTasks = [], getAllTasks, deleteTaskByIdsCallba
384
392
  toolbarOptions: { icon: _jsx(IconSearchCheck, { fontSize: 24 }), visible: false, orderNumber: 2, isActive: allInitialPanelVisibility['tmFormOrResult'] }
385
393
  }
386
394
  ], [tmTreeView, tmFormOrResult, focusedItem?.isDcmt, dtdMaster]);
387
- return (_jsxs("div", { style: { width: '100%', height: '100%', position: 'relative' }, children: [isCheckingFirstLoad && (_jsx(Spinner, { description: SDKUI_Localizator.Loading, flat: true })), _jsxs("div", { style: isCheckingFirstLoad ? { position: 'absolute', width: 0, height: 0, overflow: 'hidden', opacity: 0, pointerEvents: 'none' } : { width: '100%', height: '100%' }, children: [_jsx(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmTreeView', children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true }) }), renderDcmtOperations, renderFloatingBar] })] }));
395
+ return (_jsxs("div", { style: { width: '100%', height: '100%', position: 'relative' }, children: [isCheckingFirstLoad && (_jsx(Spinner, { description: SDKUI_Localizator.Loading, flat: true })), _jsxs("div", { style: isCheckingFirstLoad ? { position: 'absolute', width: 0, height: 0, overflow: 'hidden', opacity: 0, pointerEvents: 'none' } : { width: '100%', height: '100%' }, children: [_jsx(TMLayoutWaitingContainer, { direction: 'vertical', showWaitPanel: showWaitPanel, showWaitPanelPrimary: showPrimary, showWaitPanelSecondary: showSecondary, waitPanelTitle: waitPanelTitle, waitPanelTextPrimary: waitPanelTextPrimary, waitPanelValuePrimary: waitPanelValuePrimary, waitPanelMaxValuePrimary: waitPanelMaxValuePrimary, waitPanelTextSecondary: waitPanelTextSecondary, waitPanelValueSecondary: waitPanelValueSecondary, waitPanelMaxValueSecondary: waitPanelMaxValueSecondary, isCancelable: true, abortController: abortController, children: _jsx(TMPanelManagerProvider, { panels: initialPanels, initialVisibility: allInitialPanelVisibility, defaultDimensions: initialPanelDimensions, initialDimensions: initialPanelDimensions, initialMobilePanelId: 'tmTreeView', children: _jsx(TMPanelManagerContainer, { panels: initialPanels, direction: "horizontal", showToolbar: true }) }) }), renderDcmtOperations, renderFloatingBar] })] }));
388
396
  };
389
397
  export default TMMasterDetailDcmts;
390
398
  /**
@@ -393,8 +401,37 @@ export default TMMasterDetailDcmts;
393
401
  * - Panel visibility toggling
394
402
  * - Focus delay handling
395
403
  */
396
- const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, customItemRender, allowMultipleSelection, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onNoRelationsFound, onItemContextMenu, focusedItemFormData }) => {
404
+ const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurrentDcmtIndicator, showZeroDcmts, customItemRender, allowMultipleSelection, focusedItem, selectedItems, onFocusedItemChanged, onSelectedItemsChanged, onNoRelationsFound, onItemContextMenu, focusedItemFormData, onLoadingStateChanged }) => {
397
405
  const { setPanelVisibilityById, setToolbarButtonVisibility } = useTMPanelManagerContext();
406
+ // Monitor device type changes to restore panel visibility when switching from mobile to desktop
407
+ const deviceType = useDeviceType();
408
+ const prevDeviceTypeRef = useRef(deviceType);
409
+ // Restore panel visibility when switching from mobile to desktop
410
+ useEffect(() => {
411
+ const prevDeviceType = prevDeviceTypeRef.current;
412
+ prevDeviceTypeRef.current = deviceType;
413
+ // When switching from mobile to desktop, restore panel visibility based on current focused item
414
+ if (prevDeviceType === DeviceType.MOBILE && deviceType !== DeviceType.MOBILE) {
415
+ // Small delay to let the panel manager complete its internal state update
416
+ setTimeout(() => {
417
+ // Force tmFormOrResult panel to be visible
418
+ setPanelVisibilityById('tmFormOrResult', true);
419
+ // Restore child panel visibility based on focused item
420
+ if (focusedItem?.isDcmt) {
421
+ setPanelVisibilityById('tmSearchResult', false);
422
+ setPanelVisibilityById('tmDcmtForm', true);
423
+ setToolbarButtonVisibility('tmSearchResult', false);
424
+ setToolbarButtonVisibility('tmDcmtForm', true);
425
+ }
426
+ else {
427
+ setPanelVisibilityById('tmSearchResult', true);
428
+ setPanelVisibilityById('tmDcmtForm', false);
429
+ setToolbarButtonVisibility('tmSearchResult', true);
430
+ setToolbarButtonVisibility('tmDcmtForm', false);
431
+ }
432
+ }, 50);
433
+ }
434
+ }, [deviceType, focusedItem, setPanelVisibilityById, setToolbarButtonVisibility]);
398
435
  // Handle focused item changes with panel visibility management
399
436
  const handleFocusedItemChanged = useCallback((item) => {
400
437
  onFocusedItemChanged?.(item);
@@ -424,7 +461,7 @@ const TMRelationViewerWrapper = ({ refreshKey, inputDcmts, isForMaster, showCurr
424
461
  onItemContextMenu?.(item, e);
425
462
  }, 100);
426
463
  }, [onItemContextMenu, handleFocusedItemChanged]);
427
- return (_jsx(TMRelationViewer, { inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, initialShowZeroDcmts: showZeroDcmts, customItemRender: customItemRender, allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: onSelectedItemsChanged, maxDepthLevel: 1, invertMasterNavigation: false, showExpandAllButton: true, onNoRelationsFound: onNoRelationsFound, onItemContextMenu: onContextMenu, focusedItemFormData: focusedItemFormData }, refreshKey));
464
+ return (_jsx(TMRelationViewer, { inputDcmts: inputDcmts, isForMaster: isForMaster, showCurrentDcmtIndicator: showCurrentDcmtIndicator, initialShowZeroDcmts: showZeroDcmts, customItemRender: customItemRender, allowMultipleSelection: allowMultipleSelection, focusedItem: focusedItem, selectedItems: selectedItems, onFocusedItemChanged: handleFocusedItemChanged, onSelectedItemsChanged: onSelectedItemsChanged, maxDepthLevel: 1, invertMasterNavigation: false, showExpandAllButton: true, onNoRelationsFound: onNoRelationsFound, onItemContextMenu: onContextMenu, focusedItemFormData: focusedItemFormData, onLoadingStateChanged: onLoadingStateChanged }, refreshKey));
428
465
  };
429
466
  const TMFormOrResultWrapper = ({ refreshKey, deviceType, focusedItem, onTaskCreateRequest, allTasks = [], getAllTasks, deleteTaskByIdsCallback, addTaskCallback, editTaskCallback, handleNavigateToWGs, handleNavigateToDossiers, onRefreshAfterAddDcmtToFavs, editPdfForm, openS4TViewer, onOpenS4TViewerRequest, onOpenPdfEditorRequest, onRefreshSearchAsyncDatagrid, onRefreshSearchResults, handleNavigateToReference, fetchRemoteCertificates }) => {
430
467
  const { setPanelVisibilityById } = useTMPanelManagerContext();
@@ -73,7 +73,10 @@ export interface TMRelationViewerProps {
73
73
  * Value rendering respects DataDomain (uses TMDataListItemViewer for lists, TMDataUserIdItemViewer for users, etc.)
74
74
  */
75
75
  showMetadataNames?: boolean;
76
- /** Maximum depth level for recursive loading (default: 2) */
76
+ /**
77
+ * Maximum depth level for recursive loading (default: 2).
78
+ * Use 0 for unlimited depth (expand as much as possible).
79
+ */
77
80
  maxDepthLevel?: number;
78
81
  /**
79
82
  * If true (default), when isForMaster=true shows: detail doc → master docs as children (inverted navigation)
@@ -123,6 +126,11 @@ export interface TMRelationViewerProps {
123
126
  * (root container + first document + first correlation folder).
124
127
  */
125
128
  defaultExpandAll?: boolean;
129
+ /**
130
+ * Callback invoked when loading state changes.
131
+ * Useful to disable interactions (like context menu) during loading.
132
+ */
133
+ onLoadingStateChanged?: (isLoading: boolean) => void;
126
134
  }
127
135
  /**
128
136
  * Check if document type has detail relations
@@ -140,5 +148,6 @@ export declare const getDisplayValueByColumn: (col: DataColumnDescriptor | undef
140
148
  * Convert SearchResultDescriptor to structured data source with metadata
141
149
  */
142
150
  export declare const searchResultToDataSource: (searchResult: SearchResultDescriptor | undefined, hideSysMetadata?: boolean) => Promise<any[]>;
151
+ export declare const DEFAULT_RELATION_EXPAND_LEVEL = 4;
143
152
  declare const TMRelationViewer: React.FC<TMRelationViewerProps>;
144
153
  export default TMRelationViewer;