@rio-cloud/rio-uikit 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/BarList.d.ts +2 -0
  2. package/BarList.js +2 -0
  3. package/SaveableDateInput.d.ts +2 -0
  4. package/SaveableDateInput.js +2 -0
  5. package/components/activity/Activity.d.ts +2 -2
  6. package/components/assetTree/Tree.d.ts +12 -0
  7. package/components/assetTree/Tree.js +72 -37
  8. package/components/assetTree/TreeNodeContainer.d.ts +1 -1
  9. package/components/assetTree/useTreeExpansion.d.ts +4 -0
  10. package/components/assetTree/useTreeExpansion.js +25 -0
  11. package/components/assetTree/useTreeHeight.d.ts +1 -0
  12. package/components/assetTree/useTreeHeight.js +60 -0
  13. package/components/assetTree/useTreeScrollPosition.d.ts +3 -0
  14. package/components/assetTree/useTreeScrollPosition.js +19 -0
  15. package/components/assetTree/useTreeVirtualization.d.ts +17 -0
  16. package/components/assetTree/useTreeVirtualization.js +71 -0
  17. package/components/barList/BarList.d.ts +97 -0
  18. package/components/barList/BarList.js +42 -0
  19. package/components/barList/useSortedBars.d.ts +2 -0
  20. package/components/barList/useSortedBars.js +14 -0
  21. package/components/charts/PieChart.js +1 -1
  22. package/components/clearableInput/ClearableInput.js +1 -1
  23. package/components/formLabel/FormLabel.d.ts +2 -2
  24. package/components/listMenu/ListMenu.js +3 -1
  25. package/components/map/components/Map.js +20 -5
  26. package/components/map/components/constants.d.ts +2 -0
  27. package/components/map/components/constants.js +3 -0
  28. package/components/map/utils/mapTypes.d.ts +5 -0
  29. package/components/map/utils/rendering.d.ts +5 -2
  30. package/components/map/utils/rendering.js +46 -39
  31. package/components/overlay/OverlayTrigger.js +2 -2
  32. package/components/saveableInput/SaveableDateInput.d.ts +83 -0
  33. package/components/saveableInput/SaveableDateInput.js +122 -0
  34. package/components/smoothScrollbars/SmoothScrollbars.d.ts +1 -0
  35. package/components/smoothScrollbars/SmoothScrollbars.js +2 -2
  36. package/components/statsWidget/StatsWidget.d.ts +2 -2
  37. package/components/statsWidget/StatsWidgets.d.ts +2 -2
  38. package/components/svgImage/SvgImage.d.ts +1 -1
  39. package/components/svgImage/SvgImage.js +1 -1
  40. package/components/table/TableCol.d.ts +1 -1
  41. package/components/table/TableCol.js +2 -2
  42. package/components/table/TableHead.js +2 -2
  43. package/components/tooltip/SimpleTooltip.d.ts +1 -1
  44. package/hooks/useIsFocusWithin.d.ts +33 -0
  45. package/hooks/useIsFocusWithin.js +55 -0
  46. package/hooks/useTableExport.d.ts +49 -0
  47. package/hooks/useTableExport.js +57 -0
  48. package/hooks/useTableSelection.d.ts +15 -0
  49. package/hooks/useTableSelection.js +6 -1
  50. package/lib/es/BarList.d.ts +2 -0
  51. package/lib/es/BarList.js +7 -0
  52. package/lib/es/SaveableDateInput.d.ts +2 -0
  53. package/lib/es/SaveableDateInput.js +7 -0
  54. package/lib/es/components/activity/Activity.d.ts +2 -2
  55. package/lib/es/components/assetTree/Tree.d.ts +12 -0
  56. package/lib/es/components/assetTree/Tree.js +71 -36
  57. package/lib/es/components/assetTree/TreeNodeContainer.d.ts +1 -1
  58. package/lib/es/components/assetTree/useTreeExpansion.d.ts +4 -0
  59. package/lib/es/components/assetTree/useTreeExpansion.js +29 -0
  60. package/lib/es/components/assetTree/useTreeHeight.d.ts +1 -0
  61. package/lib/es/components/assetTree/useTreeHeight.js +64 -0
  62. package/lib/es/components/assetTree/useTreeScrollPosition.d.ts +3 -0
  63. package/lib/es/components/assetTree/useTreeScrollPosition.js +23 -0
  64. package/lib/es/components/assetTree/useTreeVirtualization.d.ts +17 -0
  65. package/lib/es/components/assetTree/useTreeVirtualization.js +76 -0
  66. package/lib/es/components/barList/BarList.d.ts +97 -0
  67. package/lib/es/components/barList/BarList.js +45 -0
  68. package/lib/es/components/barList/useSortedBars.d.ts +2 -0
  69. package/lib/es/components/barList/useSortedBars.js +17 -0
  70. package/lib/es/components/charts/PieChart.js +1 -1
  71. package/lib/es/components/clearableInput/ClearableInput.js +1 -1
  72. package/lib/es/components/formLabel/FormLabel.d.ts +2 -2
  73. package/lib/es/components/listMenu/ListMenu.js +3 -1
  74. package/lib/es/components/map/components/Map.js +19 -4
  75. package/lib/es/components/map/components/constants.d.ts +2 -0
  76. package/lib/es/components/map/components/constants.js +4 -1
  77. package/lib/es/components/map/utils/mapTypes.d.ts +5 -0
  78. package/lib/es/components/map/utils/rendering.d.ts +5 -2
  79. package/lib/es/components/map/utils/rendering.js +46 -39
  80. package/lib/es/components/overlay/OverlayTrigger.js +2 -2
  81. package/lib/es/components/saveableInput/SaveableDateInput.d.ts +83 -0
  82. package/lib/es/components/saveableInput/SaveableDateInput.js +125 -0
  83. package/lib/es/components/smoothScrollbars/SmoothScrollbars.d.ts +1 -0
  84. package/lib/es/components/smoothScrollbars/SmoothScrollbars.js +2 -2
  85. package/lib/es/components/statsWidget/StatsWidget.d.ts +2 -2
  86. package/lib/es/components/statsWidget/StatsWidgets.d.ts +2 -2
  87. package/lib/es/components/svgImage/SvgImage.d.ts +1 -1
  88. package/lib/es/components/svgImage/SvgImage.js +1 -3
  89. package/lib/es/components/table/TableCol.d.ts +1 -1
  90. package/lib/es/components/table/TableCol.js +2 -2
  91. package/lib/es/components/table/TableHead.js +2 -2
  92. package/lib/es/components/tooltip/SimpleTooltip.d.ts +1 -1
  93. package/lib/es/hooks/useIsFocusWithin.d.ts +33 -0
  94. package/lib/es/hooks/useIsFocusWithin.js +57 -0
  95. package/lib/es/hooks/useTableExport.d.ts +49 -0
  96. package/lib/es/hooks/useTableExport.js +59 -0
  97. package/lib/es/hooks/useTableSelection.d.ts +15 -0
  98. package/lib/es/hooks/useTableSelection.js +6 -1
  99. package/lib/es/useIsFocusWithin.d.ts +2 -0
  100. package/lib/es/useIsFocusWithin.js +7 -0
  101. package/lib/es/useTableExport.d.ts +2 -0
  102. package/lib/es/useTableExport.js +7 -0
  103. package/lib/es/version.json +1 -1
  104. package/package.json +18 -7
  105. package/useIsFocusWithin.d.ts +2 -0
  106. package/useIsFocusWithin.js +2 -0
  107. package/useTableExport.d.ts +2 -0
  108. package/useTableExport.js +2 -0
  109. package/version.json +1 -1
package/BarList.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export { default } from './components/barList/BarList';
2
+ export * from './components/barList/BarList';
package/BarList.js ADDED
@@ -0,0 +1,2 @@
1
+ export { default } from './components/barList/BarList';
2
+ export * from './components/barList/BarList';
@@ -0,0 +1,2 @@
1
+ export { default } from './components/saveableInput/SaveableDateInput';
2
+ export * from './components/saveableInput/SaveableDateInput';
@@ -0,0 +1,2 @@
1
+ export { default } from './components/saveableInput/SaveableDateInput';
2
+ export * from './components/saveableInput/SaveableDateInput';
@@ -7,13 +7,13 @@ declare const STATUS_MAP: {
7
7
  readonly RESTING: "resting";
8
8
  readonly WORKING: "working";
9
9
  };
10
- type ActivityStatus = ObjectValues<typeof STATUS_MAP>;
10
+ export type ActivityStatus = ObjectValues<typeof STATUS_MAP>;
11
11
  declare const SIZE_MAP: {
12
12
  readonly SIZE_SM: "sm";
13
13
  readonly SIZE_LG: "lg";
14
14
  readonly SIZE_XL: "xl";
15
15
  };
16
- type ActivitySize = ObjectValues<typeof SIZE_MAP>;
16
+ export type ActivitySize = ObjectValues<typeof SIZE_MAP>;
17
17
  export type ActivityProps = {
18
18
  /**
19
19
  * Defines the type of activity.
@@ -200,6 +200,18 @@ export type TreeProps = {
200
200
  * @default false
201
201
  */
202
202
  disableAnimation?: boolean;
203
+ /**
204
+ * The number of items (including groups and children) used for virtualizing the tree.
205
+ *
206
+ * @default 50
207
+ */
208
+ virtualizeThreshold?: number;
209
+ /**
210
+ * The number of items rendered beyond the visible area of a virtualized tree.
211
+ *
212
+ * @default 5
213
+ */
214
+ overscan?: number;
203
215
  /**
204
216
  * Additional classes added to the wrapping element.
205
217
  */
@@ -1,6 +1,6 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  /* eslint-disable no-use-before-define */
3
- import React, { useEffect, useReducer, useRef, useState } from 'react';
3
+ import React, { useCallback, useEffect, useReducer, useRef } from 'react';
4
4
  import classNames from 'classnames';
5
5
  import isNil from 'lodash/fp/isNil';
6
6
  import isEmpty from 'lodash/fp/isEmpty';
@@ -27,6 +27,11 @@ import TreeRoot from './TreeRoot';
27
27
  import TypeCounter from './TypeCounter';
28
28
  import { containsItemById, debounceFn, filterAssetByType, filterEmptyGroups, filterOutByItemId, getTypeCounts, getFlatItems, getListIds, notEmpty, notEqual, excludeFromList, getMappedItemsToGroups, sortGroupItemsByName, sortGroupsByName, addOrRemoveFromList, } from './treeUtils';
29
29
  import { treeReducer, assetCounted, allCheckedChanged, visibleTypeCountersChanged, searchValueChanged, flatItemsChanged, emptyGroupsChanged, groupedItemsChanged, typeFilterChanged, } from './treeReducer';
30
+ import { useTreeVirtualization } from './useTreeVirtualization';
31
+ import { useTreeExpansion } from './useTreeExpansion';
32
+ import { useTreeHeight } from './useTreeHeight';
33
+ import { useTreeScrollPosition } from './useTreeScrollPosition';
34
+ import SmoothScrollbars from '../../SmoothScrollbars';
30
35
  export { getTypeCounts, getSubTypeCounts } from './treeUtils';
31
36
  const filterProps = omit([
32
37
  'expandedGroups',
@@ -36,8 +41,10 @@ const filterProps = omit([
36
41
  'treeOptions',
37
42
  ]);
38
43
  const customCompare = (prevProps, nextProps) => isEqual(filterProps(prevProps), filterProps(nextProps));
44
+ const VIRTUALIZED_THRESHOLD = 50;
45
+ const DEFAULT_VIRTUALIZED_OVERSCAN = 5;
39
46
  const Tree = React.memo((props) => {
40
- const { groups = [], items = [], selectedGroups = [], selectedItems = [], onSelectionChange = noop, hasMultiselect = true, showRadioButtons = false, hideSearch = false, hideTreeHead, treeHeaderContent, summary, hideSummary = false, search, searchPlaceholder = 'Type here to filter by name', onSearchChange = noop, className, scrollHeight, expandedGroups, onExpandGroupsChange = noop, showEmptyGroups = true, treeOptions = [], treeOptionsTooltip, disableAnimation = false, onTypeFilterChange = noop, ...remainingProps } = props;
47
+ const { groups = [], items = [], selectedGroups = [], selectedItems = [], onSelectionChange = noop, hasMultiselect = true, showRadioButtons = false, hideSearch = false, hideTreeHead, treeHeaderContent, summary, hideSummary = false, search, searchPlaceholder = 'Type here to filter by name', onSearchChange = noop, className, scrollHeight, expandedGroups, onExpandGroupsChange = noop, showEmptyGroups = true, treeOptions = [], treeOptionsTooltip, disableAnimation = false, onTypeFilterChange = noop, virtualizeThreshold = VIRTUALIZED_THRESHOLD, overscan = DEFAULT_VIRTUALIZED_OVERSCAN, ...remainingProps } = props;
41
48
  const [state, dispatch] = useReducer(treeReducer, {
42
49
  groupedItems: [],
43
50
  flatItems: [],
@@ -52,7 +59,26 @@ const Tree = React.memo((props) => {
52
59
  const previousItems = useRef();
53
60
  const previousGroups = useRef();
54
61
  const previousSearchValue = useRef('');
55
- const internalExpandedGroups = useRef(expandedGroups);
62
+ const { internalExpandedGroups, handleToggleNode } = useTreeExpansion(props.expandedGroups, props.onExpandGroupsChange);
63
+ const hasGroups = () => groups && notEmpty(groups);
64
+ const hasInternalSearchValue = () => notEmpty(state.searchValue);
65
+ const hasSearchAndGroups = () => hasInternalSearchValue() && hasGroups();
66
+ const hasNoSearchAndGroups = () => !hasInternalSearchValue() && hasGroups();
67
+ const scrollElementRef = useRef(null);
68
+ const { virtualizedItems, virtualizer } = useTreeVirtualization(state.groupedItems, state.flatItems, hasInternalSearchValue(), hasGroups(), internalExpandedGroups, scrollElementRef, overscan);
69
+ const { scrollToTop } = useTreeScrollPosition(virtualizer, scrollElementRef);
70
+ // Enhance the handleToggleNode function
71
+ const enhancedHandleToggleNode = useCallback((nodeId) => {
72
+ // Execute the original toggle logic
73
+ handleToggleNode(nodeId);
74
+ }, [handleToggleNode]);
75
+ // Create a callback for scroll events that works with the virtualizer
76
+ const handleVirtualizedScroll = useCallback((event) => {
77
+ if (virtualizer && event?.target) {
78
+ const scrollElement = event.target;
79
+ virtualizer.scrollToOffset(scrollElement.scrollTop, { align: 'start' });
80
+ }
81
+ }, [virtualizer]);
56
82
  useEffect(() => {
57
83
  // Update Tree when items or groups have changed
58
84
  if (notEqual(previousItems.current, items) || notEqual(previousGroups.current, groups)) {
@@ -79,12 +105,6 @@ const Tree = React.memo((props) => {
79
105
  useEffect(() => makeTree(groups, items), [state.typeFilter]);
80
106
  // Update tree when empty groups are toggled from outside
81
107
  useEffect(() => makeTree(groups, items), [showEmptyGroups]);
82
- // Update expanded groups from outside
83
- const [previousExpandedGroups, setPreviousExpandedGroups] = useState(expandedGroups);
84
- if (!isEqual(expandedGroups, previousExpandedGroups)) {
85
- internalExpandedGroups.current = expandedGroups;
86
- setPreviousExpandedGroups(expandedGroups);
87
- }
88
108
  // Update "select all" state from outside when groups are selected outside programmatically
89
109
  // without using the "select all" checkbox
90
110
  useEffect(() => {
@@ -115,28 +135,6 @@ const Tree = React.memo((props) => {
115
135
  const unselectedItems = updatedItems.filter(filterOutByItemId(updatedSelectedItems));
116
136
  return isEmpty(unselectedItems);
117
137
  };
118
- const handleToggleNode = (nodeId) => {
119
- const nodeContainer = getNodeContainerDomElementById(nodeId);
120
- if (!internalExpandedGroups?.current || !nodeContainer) {
121
- return;
122
- }
123
- const openGroups = internalExpandedGroups.current;
124
- const newExpandedNodes = openGroups.includes(nodeId)
125
- ? openGroups.filter(item => item !== nodeId)
126
- : [...openGroups, nodeId];
127
- // Performance improvement to skip on render cycle and change "open" class directly
128
- if (openGroups.includes(nodeId)) {
129
- nodeContainer.classList.remove('open');
130
- }
131
- else {
132
- nodeContainer.classList.add('open');
133
- }
134
- internalExpandedGroups.current = newExpandedNodes;
135
- onExpandGroupsChange(newExpandedNodes);
136
- };
137
- const getNodeContainerDomElementById = (nodeId) => {
138
- return treeRef?.current?.querySelector(`.TreeNodeContainer[data-id="${nodeId}"]`);
139
- };
140
138
  const selectAllSearchResultItems = (shouldSelect) => selectAllFlatItems(shouldSelect);
141
139
  const handleSelectAll = (shouldSelect, isStateIndeterminate) => {
142
140
  const shouldSelectAll = shouldSelect && !isStateIndeterminate;
@@ -178,10 +176,13 @@ const Tree = React.memo((props) => {
178
176
  const handleSearchChange = (updatedSearchValue) => {
179
177
  onSearchChange(updatedSearchValue);
180
178
  dispatch(searchValueChanged(updatedSearchValue));
179
+ if (virtualizer) {
180
+ // Every time search changes or is cleared, scroll to the top
181
+ setTimeout(() => {
182
+ scrollToTop();
183
+ }, 10);
184
+ }
181
185
  };
182
- const hasGroups = () => groups && notEmpty(groups);
183
- const hasSearchAndGroups = () => hasInternalSearchValue() && hasGroups();
184
- const hasNoSearchAndGroups = () => !hasInternalSearchValue() && hasGroups();
185
186
  const setFlatItemList = (updatedItems, searchValue) => {
186
187
  const flatItems = getFlatItems(updatedItems, searchValue);
187
188
  dispatch(flatItemsChanged(flatItems));
@@ -218,30 +219,64 @@ const Tree = React.memo((props) => {
218
219
  [otherwise, setFlatItems],
219
220
  ])();
220
221
  };
222
+ const containerHeight = useTreeHeight(treeRef, scrollHeight);
223
+ const renderVirtualizedTree = () => {
224
+ const items = virtualizer.getVirtualItems();
225
+ const isGroupedList = hasGroups() && !hasInternalSearchValue();
226
+ return (_jsx(SmoothScrollbars, { ref: scrollElementRef, className: 'tree-virtual-scrollbar', onScroll: handleVirtualizedScroll, autoHeight: false, slideIn: true, style: {
227
+ height: `${containerHeight}px`,
228
+ }, children: _jsx("div", { className: isGroupedList ? 'grouped-list' : 'flat-list', style: {
229
+ height: `${virtualizer.getTotalSize()}px`,
230
+ position: 'relative',
231
+ }, children: items.map(virtualItem => {
232
+ const item = virtualizedItems[virtualItem.index];
233
+ if (!item) {
234
+ return null;
235
+ }
236
+ const isGroupSelected = selectedGroups.includes(item.id) ||
237
+ (item.type === 'leaf' && selectedGroups.includes(item.groupId));
238
+ return (_jsx("div", { "data-index": virtualItem.index, ref: virtualizer.measureElement, className: `virtualized-tree-item ${item.type === 'group' ? 'group-item' : 'leaf-item'} ${isGroupSelected ? 'checked' : ''}`, style: {
239
+ position: 'absolute',
240
+ top: 0,
241
+ left: 0,
242
+ width: '100%',
243
+ height: `${virtualItem.size}px`,
244
+ transform: `translateY(${virtualItem.start}px)`,
245
+ }, children: item.type === 'group' ? (_jsx(TreeNodeContainer, { groupId: item.id, isOpen: item.isExpanded, disableAnimation: disableAnimation, children: _jsx(TreeNode, { node: item.data, hasMultiselect: hasMultiselect, onToggleNode: enhancedHandleToggleNode, onSelect: handleGroupSelection, isSelected: isGroupSelected, isIndeterminate: !isGroupSelected &&
246
+ item.data.items.some(groupItem => selectedItems.includes(groupItem.id)) }) }, item.id)) : (
247
+ // Render individual leaf item with proper styling
248
+ _jsx(TreeNodeContainer, { isOpen: true, disableAnimation: disableAnimation, children: _jsx(TreeLeafList, { leafList: [item.data], hasMultiselect: hasMultiselect, showRadioButtons: showRadioButtons, selectedItems: selectedItems, selectedGroups: selectedGroups, onSelectionChange: respondSelection }) }, item.id)) }, `${item.type}-${item.id}-${virtualItem.index}`));
249
+ }) }) }));
250
+ };
221
251
  const renderTree = () => {
222
252
  const { groupedItems } = state;
223
253
  if (isEmpty(groupedItems)) {
224
254
  return _jsx(TreeNothingFound, {});
225
255
  }
256
+ if (virtualizedItems.length > virtualizeThreshold) {
257
+ return renderVirtualizedTree();
258
+ }
226
259
  const result = map((group) => {
227
260
  const groupId = group.id;
228
261
  const groupItems = group.items;
229
- const isOpen = internalExpandedGroups.current?.includes(groupId) ?? false;
262
+ const isOpen = internalExpandedGroups?.includes(groupId) ?? false;
230
263
  const numSelectedGroupItems = filter(containsItemById(selectedItems))(groupItems).length;
231
264
  const isGroupSelected = selectedGroups.includes(groupId);
232
265
  const isStateIndeterminate = !isGroupSelected && numSelectedGroupItems > 0;
233
- return (_jsxs(TreeNodeContainer, { groupId: groupId, isOpen: isOpen, disableAnimation: disableAnimation, children: [_jsx(TreeNode, { node: group, hasMultiselect: hasMultiselect, onToggleNode: handleToggleNode, onSelect: handleGroupSelection, isSelected: isGroupSelected, isIndeterminate: isStateIndeterminate }), _jsx(TreeLeafList, { leafList: groupItems, hasMultiselect: hasMultiselect, showRadioButtons: showRadioButtons, selectedItems: selectedItems, selectedGroups: selectedGroups, onSelectionChange: respondSelection })] }, groupId));
266
+ return (_jsxs(TreeNodeContainer, { groupId: groupId, isOpen: isOpen, disableAnimation: disableAnimation, children: [_jsx(TreeNode, { node: group, hasMultiselect: hasMultiselect, onToggleNode: handleToggleNode, onSelect: handleGroupSelection, isSelected: isGroupSelected, isIndeterminate: isStateIndeterminate }), isOpen && (_jsx(TreeLeafList, { leafList: groupItems, hasMultiselect: hasMultiselect, showRadioButtons: showRadioButtons, selectedItems: selectedItems, selectedGroups: selectedGroups, onSelectionChange: respondSelection }))] }, groupId));
234
267
  })(groupedItems);
235
268
  return result;
236
269
  };
237
270
  const renderFlatList = () => {
238
271
  const { flatItems } = state;
239
272
  const hasLeafs = isEmpty(flatItems);
273
+ if (virtualizedItems.length > virtualizeThreshold) {
274
+ return renderVirtualizedTree();
275
+ }
240
276
  const getLeafs = () => (_jsx(TreeLeafList, { leafList: flatItems, hasMultiselect: hasMultiselect, showRadioButtons: showRadioButtons, selectedItems: selectedItems, selectedGroups: selectedGroups, onSelectionChange: respondSelection }));
241
277
  return (_jsx(TreeNodeContainer, { disableAnimation: disableAnimation, isOpen: true, children: hasLeafs ? _jsx(TreeNothingFound, {}) : getLeafs() }));
242
278
  };
243
279
  const hasExternalGroups = notEmpty(groups);
244
- const hasInternalSearchValue = () => notEmpty(state.searchValue);
245
280
  const hasSelectedAllItems = () => isEqual(size(selectedItems), size(state.flatItems));
246
281
  const hasPartiallySelectedItems = () => notEmpty(selectedItems) && !hasSelectedAllItems();
247
282
  const hasSelectedAllGroups = () => {
@@ -1,4 +1,4 @@
1
- import { type PropsWithChildren } from 'react';
1
+ import type { PropsWithChildren } from 'react';
2
2
  export type TreeNodeContainerProps = {
3
3
  isOpen: boolean;
4
4
  groupId?: string;
@@ -0,0 +1,4 @@
1
+ export declare const useTreeExpansion: (expandedGroups?: string[], onExpandGroupsChange?: (groups: string[]) => void) => {
2
+ internalExpandedGroups: string[] | undefined;
3
+ handleToggleNode: (nodeId: string) => void;
4
+ };
@@ -0,0 +1,25 @@
1
+ import { useState } from 'react';
2
+ import { isEqual } from 'lodash/fp';
3
+ export const useTreeExpansion = (expandedGroups, onExpandGroupsChange) => {
4
+ const [internalExpandedGroups, setInternalExpandedGroups] = useState(expandedGroups);
5
+ const [previousExpandedGroups, setPreviousExpandedGroups] = useState(expandedGroups);
6
+ // Sync with external prop changes and update expanded groups from outside in case
7
+ if (!isEqual(expandedGroups, previousExpandedGroups)) {
8
+ setInternalExpandedGroups(expandedGroups);
9
+ setPreviousExpandedGroups(expandedGroups);
10
+ }
11
+ const handleToggleNode = (nodeId) => {
12
+ if (!internalExpandedGroups) {
13
+ return;
14
+ }
15
+ const newExpandedNodes = internalExpandedGroups.includes(nodeId)
16
+ ? internalExpandedGroups.filter(item => item !== nodeId)
17
+ : [...internalExpandedGroups, nodeId];
18
+ setInternalExpandedGroups(newExpandedNodes);
19
+ onExpandGroupsChange?.(newExpandedNodes);
20
+ };
21
+ return {
22
+ internalExpandedGroups,
23
+ handleToggleNode,
24
+ };
25
+ };
@@ -0,0 +1 @@
1
+ export declare const useTreeHeight: (treeRef: React.RefObject<HTMLDivElement>, scrollHeight?: number) => number;
@@ -0,0 +1,60 @@
1
+ import { useEffect, useState } from 'react';
2
+ const DEFAULT_TREE_ROOT_HEIGHT = 300;
3
+ export const useTreeHeight = (treeRef, scrollHeight) => {
4
+ const [containerHeight, setContainerHeight] = useState(350);
5
+ // Enhanced height calculation with multiple fallback strategies
6
+ useEffect(() => {
7
+ const calculateHeight = () => {
8
+ if (!treeRef.current) {
9
+ return DEFAULT_TREE_ROOT_HEIGHT;
10
+ }
11
+ const parentElement = treeRef.current;
12
+ const treeHeaderElement = parentElement.querySelector('.TreeHeader');
13
+ if (scrollHeight) {
14
+ // Use scrollHeight prop if provided
15
+ return scrollHeight;
16
+ }
17
+ const treeRoot = treeRef.current.querySelector('.TreeRoot');
18
+ return treeRoot?.clientHeight ?? DEFAULT_TREE_ROOT_HEIGHT;
19
+ };
20
+ const updateHeight = () => {
21
+ const newHeight = calculateHeight();
22
+ setContainerHeight(newHeight);
23
+ };
24
+ // Debounce height updates to avoid excessive recalculations
25
+ let timeoutId;
26
+ const debouncedUpdate = () => {
27
+ clearTimeout(timeoutId);
28
+ timeoutId = setTimeout(updateHeight, 16); // ~60fps
29
+ };
30
+ // Initial calculation with small delay to ensure DOM is ready
31
+ const initialTimeout = setTimeout(updateHeight, 50);
32
+ // Set up observers
33
+ const resizeObserver = new ResizeObserver(debouncedUpdate);
34
+ const mutationObserver = new MutationObserver(debouncedUpdate);
35
+ if (treeRef.current) {
36
+ // Watch parent for size changes
37
+ resizeObserver.observe(treeRef.current);
38
+ if (treeRef.current.parentElement) {
39
+ resizeObserver.observe(treeRef.current.parentElement);
40
+ }
41
+ // Watch for DOM changes that might affect layout
42
+ mutationObserver.observe(treeRef.current, {
43
+ childList: true,
44
+ subtree: true,
45
+ attributes: true,
46
+ attributeFilter: ['style', 'class'],
47
+ });
48
+ }
49
+ // Window resize listener
50
+ window.addEventListener('resize', debouncedUpdate);
51
+ return () => {
52
+ clearTimeout(initialTimeout);
53
+ clearTimeout(timeoutId);
54
+ resizeObserver.disconnect();
55
+ mutationObserver.disconnect();
56
+ window.removeEventListener('resize', debouncedUpdate);
57
+ };
58
+ }, [scrollHeight, treeRef]); // Re-run if scrollHeight prop changes or treeRef changes
59
+ return containerHeight;
60
+ };
@@ -0,0 +1,3 @@
1
+ export declare const useTreeScrollPosition: (virtualizer: any, scrollElementRef: React.RefObject<any>) => {
2
+ scrollToTop: () => void;
3
+ };
@@ -0,0 +1,19 @@
1
+ import { useCallback, useRef } from 'react';
2
+ export const useTreeScrollPosition = (virtualizer, scrollElementRef) => {
3
+ const scrollOffsetRef = useRef(0);
4
+ const scrollToTop = useCallback(() => {
5
+ if (scrollElementRef.current) {
6
+ requestAnimationFrame(() => {
7
+ // Reset scroll position to top
8
+ scrollElementRef.current.scrollTop(0);
9
+ // Also reset the stored scroll offset
10
+ scrollOffsetRef.current = 0;
11
+ // Force virtualizer to sync
12
+ if (virtualizer) {
13
+ virtualizer.scrollToOffset(0);
14
+ }
15
+ });
16
+ }
17
+ }, [scrollElementRef, virtualizer]);
18
+ return { scrollToTop };
19
+ };
@@ -0,0 +1,17 @@
1
+ import type { TreeItem, GroupedItem } from './Tree';
2
+ type VirtualizedItem = {
3
+ type: 'group';
4
+ data: GroupedItem;
5
+ id: string;
6
+ isExpanded: boolean;
7
+ } | {
8
+ type: 'leaf';
9
+ data: TreeItem;
10
+ id: string;
11
+ groupId: string;
12
+ };
13
+ export declare const useTreeVirtualization: (groupedItems: GroupedItem[], flatItems: TreeItem[], hasSearchValue: boolean, hasGroups: boolean, internalExpandedGroups: string[] | undefined, scrollElementRef: React.RefObject<HTMLDivElement>, overscan: number) => {
14
+ virtualizedItems: VirtualizedItem[];
15
+ virtualizer: import("@tanstack/virtual-core").Virtualizer<any, Element>;
16
+ };
17
+ export {};
@@ -0,0 +1,71 @@
1
+ import { useMemo } from 'react';
2
+ import map from 'lodash/fp/map';
3
+ import { useVirtualizer } from '@tanstack/react-virtual';
4
+ export const useTreeVirtualization = (groupedItems, flatItems, hasSearchValue, hasGroups, internalExpandedGroups, scrollElementRef, overscan) => {
5
+ const virtualizedItems = useMemo(() => {
6
+ // When there's a search value, always show flat items (no groups)
7
+ if (hasSearchValue) {
8
+ return flatItems.map((item) => ({
9
+ type: 'leaf',
10
+ data: item,
11
+ id: item.id,
12
+ }));
13
+ }
14
+ // Virtualize grouped list, when there's no search value and we have groups
15
+ if (hasGroups) {
16
+ const flatList = [];
17
+ map((group) => {
18
+ const isExpanded = internalExpandedGroups?.includes(group.id) ?? false;
19
+ // Add the group header
20
+ flatList.push({
21
+ type: 'group',
22
+ data: group,
23
+ id: group.id,
24
+ isExpanded,
25
+ });
26
+ // Add expanded items
27
+ if (isExpanded) {
28
+ group.items.forEach((item) => {
29
+ flatList.push({
30
+ type: 'leaf',
31
+ data: item,
32
+ id: item.id,
33
+ groupId: group.id,
34
+ });
35
+ });
36
+ }
37
+ })(groupedItems);
38
+ return flatList;
39
+ }
40
+ // Virtualize flat list when no groups are provided initially
41
+ return flatItems.map((item) => ({
42
+ type: 'leaf',
43
+ data: item,
44
+ id: item.id,
45
+ }));
46
+ }, [groupedItems, flatItems, hasSearchValue, hasGroups, internalExpandedGroups]);
47
+ const virtualizer = useVirtualizer({
48
+ count: virtualizedItems.length,
49
+ // getScrollElement: () => scrollElementRef.current,
50
+ getScrollElement: () => {
51
+ // For SmoothScrollbars, we need to get the actual scroll container
52
+ if (scrollElementRef.current) {
53
+ // SmoothScrollbars exposes the scroll container through its view
54
+ const scrollbarsInstance = scrollElementRef.current;
55
+ // Access the internal scroll container
56
+ // This depends on how react-custom-scrollbars-2 exposes its internals
57
+ return scrollbarsInstance?.view;
58
+ }
59
+ return null;
60
+ },
61
+ estimateSize: () => 41, // typical single line height without line-break
62
+ measureElement: element => {
63
+ // This will measure the actual rendered height for more accuracy
64
+ return element?.children[0].clientHeight ?? 41;
65
+ },
66
+ overscan, // Render 10 extra items above and below visible area
67
+ // Add this to enable more aggressive remeasurement
68
+ lanes: 1,
69
+ });
70
+ return { virtualizedItems, virtualizer };
71
+ };
@@ -0,0 +1,97 @@
1
+ import React from 'react';
2
+ import { type SortDirectionType } from '../../utils/SortUtils';
3
+ export type BarListRow<T> = T & {
4
+ key?: string;
5
+ href?: string;
6
+ value: number;
7
+ name: string;
8
+ color?: string;
9
+ barColor?: string;
10
+ background?: string;
11
+ };
12
+ /**
13
+ * Props for the BarList component.
14
+ *
15
+ * @template T - The type of the custom data associated with each bar row.
16
+ */
17
+ type BarListProps<T = unknown> = React.HTMLAttributes<HTMLDivElement> & {
18
+ /**
19
+ * Array of bar row data to be rendered.
20
+ */
21
+ data: BarListRow<T>[];
22
+ /**
23
+ * Optional function to format the numeric value displayed on the right side of each bar.
24
+ * Can return a string or a React element.
25
+ *
26
+ * @example
27
+ * valueFormatter={(value) => `${value}%`}
28
+ */
29
+ valueFormatter?: (value: number) => string | React.ReactElement;
30
+ /**
31
+ * A reference value used to calculate relative widths.
32
+ * If not provided, the maximum value in `data` will be used.
33
+ *
34
+ * @default max(data.value)
35
+ */
36
+ referenceValue?: number;
37
+ /**
38
+ * Whether to animate the bar width transitions using Framer Motion.
39
+ *
40
+ * @default false
41
+ */
42
+ showAnimation?: boolean;
43
+ /**
44
+ * Callback fired when a bar is clicked.
45
+ *
46
+ * @param payload - The full data object of the clicked bar.
47
+ */
48
+ onSelectRow?: (payload: BarListRow<T>) => void;
49
+ /**
50
+ * The sort order for the bars. Options are 'asc', 'desc', or 'none'.
51
+ *
52
+ * @default 'none'
53
+ */
54
+ sortOrder?: SortDirectionType | 'none';
55
+ /**
56
+ * Height of each bar row in pixels.
57
+ *
58
+ * @default 32
59
+ */
60
+ rowHeight?: number;
61
+ /**
62
+ * Opacity applied to non-hovered bars (between 0 and 1).
63
+ *
64
+ * @default 0.5
65
+ */
66
+ opacity?: number;
67
+ /**
68
+ * CSS color string used for the active/progress portion of each bar.
69
+ *
70
+ * @default 'bg-highlight-light'
71
+ */
72
+ barColor?: string;
73
+ /**
74
+ * CSS color string used for the text labels.
75
+ *
76
+ * @default 'text-color-darker'
77
+ */
78
+ labelColor?: string;
79
+ /**
80
+ * CSS color string used for the bar background.
81
+ *
82
+ * @default 'bg-transparent'
83
+ */
84
+ background?: string;
85
+ /**
86
+ * Additional className added to the wrapper element.
87
+ */
88
+ className?: string;
89
+ };
90
+ declare const BarListInner: {
91
+ <T>(props: BarListProps<T>, forwardedRef: React.ForwardedRef<HTMLDivElement>): import("react/jsx-runtime").JSX.Element;
92
+ displayName: string;
93
+ };
94
+ declare const BarList: <T>(props: BarListProps<T> & {
95
+ ref?: React.ForwardedRef<HTMLDivElement>;
96
+ }) => ReturnType<typeof BarListInner>;
97
+ export default BarList;
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React, { useMemo } from 'react';
3
+ import classNames from 'classnames';
4
+ import { motion } from 'framer-motion';
5
+ import { SortDirection } from '../../utils/SortUtils';
6
+ import { useSortedBars } from './useSortedBars';
7
+ // Animation variants
8
+ const containerVariants = {
9
+ animate: {
10
+ transition: {
11
+ staggerChildren: 0.1,
12
+ },
13
+ },
14
+ };
15
+ const barVariants = {
16
+ initial: { width: 0 },
17
+ animate: (width) => ({
18
+ width: `${width}%`,
19
+ transition: { duration: 0.8, ease: 'easeOut' },
20
+ }),
21
+ };
22
+ const DEFAULT_ROW_HEIGHT = 25;
23
+ const DEFAULT_OPACITY = 0.5;
24
+ const BarListInner = (props, forwardedRef) => {
25
+ const { data = [], valueFormatter = value => value.toString(), showAnimation = false, onSelectRow, referenceValue, sortOrder = SortDirection.DESCENDING, barColor = 'bg-highlight-light', labelColor = 'text-color-darker', background = 'bg-transparent', rowHeight = DEFAULT_ROW_HEIGHT, opacity = DEFAULT_OPACITY, className = '', ...remainingProps } = props;
26
+ const sortedData = useSortedBars(data, sortOrder, item => item.value);
27
+ const [hoveredIndex, setHoveredIndex] = React.useState(null);
28
+ const widths = useMemo(() => {
29
+ const base = referenceValue ?? Math.max(...sortedData.map(item => item.value), 0);
30
+ return sortedData.map(item => (base === 0 || item.value === 0 ? 0 : Math.max((item.value / base) * 100, 2)));
31
+ }, [sortedData, referenceValue]);
32
+ return (_jsxs("div", { ...remainingProps, ref: forwardedRef, className: classNames('display-flex justify-content-between gap-15', className), children: [_jsx(motion.div, { variants: showAnimation ? containerVariants : undefined, initial: 'initial', animate: 'animate', className: 'width-100pct space-y-5', children: sortedData.map((item, index) => {
33
+ const width = widths[index];
34
+ const itemLabelColor = item.color ?? labelColor;
35
+ const itemBarColor = item.barColor ?? barColor;
36
+ const itemBackground = item.background ?? background;
37
+ return (_jsxs("div", { onMouseEnter: () => setHoveredIndex(index), onMouseLeave: () => setHoveredIndex(null), onClick: () => onSelectRow?.(item), className: classNames('position-relative width-100pct rounded-small', 'transition-all transition-ease-in-out transition-duration-01', itemBackground, onSelectRow && 'cursor-pointer', hoveredIndex === index && 'bg-lightest'), children: [_jsx(motion.div, { custom: width, variants: showAnimation ? barVariants : undefined, className: classNames('display-flex align-items-center rounded-small', 'transition-all transition-ease-in-out transition-duration-01', itemBarColor), style: { height: `${rowHeight}px`, opacity: hoveredIndex === index ? 1 : opacity }, role: 'presentation' }), _jsx("div", { className: 'position-absolute left-0 top-50pct translate-y-50pct display-flex width-100pct padding-left-10', children: item.href ? (_jsx("a", { href: item.href, className: itemLabelColor, target: '_blank', rel: 'noreferrer', onClick: event => event.stopPropagation(), children: item.name })) : (_jsx("div", { className: itemLabelColor, children: item.name })) })] }, item.key ?? item.name));
38
+ }) }), _jsx("div", { className: 'space-y-5', children: sortedData.map(item => (_jsx("div", { className: classNames('display-flex align-items-center justify-content-end'), style: { height: `${rowHeight}px` }, children: _jsx("div", { className: 'text-color-darkest', children: valueFormatter(item.value) }) }, item.key ?? item.name))) })] }));
39
+ };
40
+ BarListInner.displayName = 'BarList';
41
+ const BarList = React.forwardRef(BarListInner);
42
+ export default BarList;
@@ -0,0 +1,2 @@
1
+ import { type SortDirectionType } from '../../utils/SortUtils';
2
+ export declare function useSortedBars<T>(data: T[], sortOrder: SortDirectionType | 'none', getValue: (item: T) => number): T[];
@@ -0,0 +1,14 @@
1
+ import { useMemo } from 'react';
2
+ import { SortDirection } from '../../utils/SortUtils';
3
+ export function useSortedBars(data, sortOrder, getValue) {
4
+ return useMemo(() => {
5
+ if (sortOrder === 'none') {
6
+ return data;
7
+ }
8
+ return [...data].sort((a, b) => {
9
+ const aVal = getValue(a);
10
+ const bVal = getValue(b);
11
+ return sortOrder === SortDirection.ASCENDING ? aVal - bVal : bVal - aVal;
12
+ });
13
+ }, [data, sortOrder, getValue]);
14
+ }
@@ -14,7 +14,7 @@ const renderCustomInnerLabel = (dataUnit) => ({ cx, cy, midAngle, innerRadius, o
14
14
  };
15
15
  const PieChart = (props) => {
16
16
  const { width, height, innerRadius, outerRadius, data = [], dataKey = 'value', dataUnit = '', nameKey = 'name', color, filled = false, labels = true, innerLabels = false, paddingAngle = 3, legend = _jsx(RechartsLegend, {}), tooltip = true, pieOptions, containerOptions, ...remainingProps } = props;
17
- // biome-ignore lint/suspicious/noExplicitAny:
17
+ // biome-ignore lint/suspicious/noExplicitAny: unknown type
18
18
  const renderLabels = (entry) => `${isFunction(dataKey) ? dataKey(entry) : entry[dataKey]} ${dataUnit}`;
19
19
  const pieLabel = labels && (innerLabels ? renderCustomInnerLabel(dataUnit) : renderLabels);
20
20
  const tooltipProps = isObject(tooltip) ? tooltip.props : {};
@@ -136,7 +136,7 @@ const ClearableInput = forwardRef((props, ref) => {
136
136
  maxLength: hasMask ? undefined : maxLength,
137
137
  tabIndex,
138
138
  };
139
- const input = hasMask ? (_jsx(IMaskInput, { ...inputProps, ref: mergedInternalMaskRef, mask: mask, placeholderChar: maskPlaceholder, onAccept: handleAccept, lazy: !shouldShowMask(), overwrite: true })) : (_jsx("input", { ...inputProps, ref: inputRef || ref }));
139
+ const input = hasMask ? (_jsx(IMaskInput, { ...inputProps, inputRef: mergedInternalMaskRef, mask: mask, placeholderChar: maskPlaceholder, onAccept: handleAccept, lazy: !shouldShowMask(), overwrite: true })) : (_jsx("input", { ...inputProps, ref: inputRef || ref }));
140
140
  return (_jsxs("div", { className: classes, children: [children && isFunction(children) ? children(inputProps) : input, _jsx("span", { className: clearButtonClassNames, onClick: clearInputValue, children: _jsx("span", { className: 'clearButtonIcon rioglyph rioglyph-remove-sign' }) })] }));
141
141
  });
142
142
  export default ClearableInput;