@trackunit/react-components 2.1.64 → 2.2.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.
- package/index.cjs.js +42 -37
- package/index.esm.js +42 -37
- package/package.json +7 -7
- package/src/components/List/List.d.ts +1 -1
- package/src/components/List/ListLoadingIndicator.d.ts +4 -4
- package/src/components/List/useList.d.ts +8 -1
- package/src/hooks/useBidirectionalScroll.d.ts +11 -2
- package/src/hooks/useInfiniteScroll.d.ts +3 -3
package/index.cjs.js
CHANGED
|
@@ -5733,14 +5733,17 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
|
|
|
5733
5733
|
*/
|
|
5734
5734
|
const List = ({ children, className, "data-testid": dataTestId, style, ref,
|
|
5735
5735
|
// UseListResult properties
|
|
5736
|
-
containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
|
|
5736
|
+
containerRef, listRef, listContainerRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
|
|
5737
5737
|
// Unused but part of UseListResult interface (can be used from parent)
|
|
5738
5738
|
scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
|
|
5739
5739
|
const mergedContainerRef = useMergeRefs([containerRef, ref]);
|
|
5740
|
+
// The virtualizer owns the `<ul>` height + item positions via directDomUpdates,
|
|
5741
|
+
// so its container callback must be attached to the `<ul>` (alongside listRef).
|
|
5742
|
+
const mergedListRef = useMergeRefs([listRef, listContainerRef]);
|
|
5740
5743
|
return (jsxRuntime.jsx("div", { className: cvaListContainer({
|
|
5741
5744
|
withTopSeparator: topSeparatorOnScroll && !isAtTop,
|
|
5742
5745
|
className,
|
|
5743
|
-
}), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsxRuntime.jsx("ul", { className: cvaList(), ref:
|
|
5746
|
+
}), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsxRuntime.jsx("ul", { className: cvaList(), ref: mergedListRef, children: rows.map(row => {
|
|
5744
5747
|
// Generate list item props with clean separator styling
|
|
5745
5748
|
const listItemProps = getListItemProps(row, {
|
|
5746
5749
|
className: cvaListItem$1({
|
|
@@ -5794,7 +5797,7 @@ const DEFAULT_ROW_HEIGHT$1 = 50;
|
|
|
5794
5797
|
* @param props.overscan - Optional number of items to render outside the visible area.
|
|
5795
5798
|
* @param props.onChange - Optional callback when virtualizer changes.
|
|
5796
5799
|
* @param props.skip - Whether to skip automatic loading of more data (default: false).
|
|
5797
|
-
* @returns {
|
|
5800
|
+
* @returns {ReactVirtualizer} The virtualizer instance with all its properties and methods, including `containerRef` for direct DOM positioning.
|
|
5798
5801
|
* @description
|
|
5799
5802
|
* This hook is used to implement infinite scrolling in a table. It uses TanStack Virtual's
|
|
5800
5803
|
* built-in capabilities for virtualization and automatically loads more data when scrolling
|
|
@@ -5808,12 +5811,14 @@ const DEFAULT_ROW_HEIGHT$1 = 50;
|
|
|
5808
5811
|
* });
|
|
5809
5812
|
*/
|
|
5810
5813
|
const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, }) => {
|
|
5811
|
-
"use no memo"; //! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
5812
5814
|
const handleChange = react.useCallback((virtualizer) => {
|
|
5813
5815
|
onChange?.(virtualizer);
|
|
5814
5816
|
}, [onChange]);
|
|
5815
5817
|
const handleEstimateSize = react.useCallback((index) => estimateSize?.(index) ?? DEFAULT_ROW_HEIGHT$1, [estimateSize]);
|
|
5816
|
-
|
|
5818
|
+
// eslint-plugin-react-hooks hardcodes `useVirtualizer` as a known-incompatible
|
|
5819
|
+
// API (its returned functions can't be memoized), so this warning is emitted
|
|
5820
|
+
// regardless of the installed version. The React Compiler already auto-skips
|
|
5821
|
+
// memoizing this hook, so the behaviour is safe.
|
|
5817
5822
|
// eslint-disable-next-line react-hooks/incompatible-library
|
|
5818
5823
|
const rowVirtualizer = reactVirtual.useVirtualizer({
|
|
5819
5824
|
count,
|
|
@@ -5824,6 +5829,12 @@ const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize,
|
|
|
5824
5829
|
// This option enables wrapping ResizeObserver measurements in requestAnimationFrame for smoother updates and reduced layout thrashing.
|
|
5825
5830
|
// It helps prevent the "ResizeObserver loop completed with undelivered notifications" error by ensuring that measurements align with the rendering cycle
|
|
5826
5831
|
useAnimationFrameWithResizeObserver: true,
|
|
5832
|
+
// Let the virtualizer write item positions and container size straight to
|
|
5833
|
+
// the DOM, only re-rendering when the visible index range changes. This
|
|
5834
|
+
// removes the imperative transform/height races that made scrolling jumpy.
|
|
5835
|
+
directDomUpdates: true,
|
|
5836
|
+
directDomUpdatesMode: "transform",
|
|
5837
|
+
useFlushSync: false,
|
|
5827
5838
|
});
|
|
5828
5839
|
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
5829
5840
|
// Auto-load more data based on scroll position and content height
|
|
@@ -5883,7 +5894,7 @@ const DEFAULT_LOADING_INDICATOR_CONFIG = {
|
|
|
5883
5894
|
*
|
|
5884
5895
|
* return (
|
|
5885
5896
|
* <div ref={list.containerRef}>
|
|
5886
|
-
* <ul
|
|
5897
|
+
* <ul ref={list.listContainerRef} className="relative">
|
|
5887
5898
|
* {list.rows.map(row => {
|
|
5888
5899
|
* const props = list.getListItemProps(row, { className: 'list-item' });
|
|
5889
5900
|
* return <li {...props}>{row.item?.title}</li>;
|
|
@@ -5896,7 +5907,6 @@ const DEFAULT_LOADING_INDICATOR_CONFIG = {
|
|
|
5896
5907
|
const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAULT_LOADING_INDICATOR_CONFIG, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, separator = "line", topSeparatorOnScroll = false, }) => {
|
|
5897
5908
|
const containerRef = react.useRef(null);
|
|
5898
5909
|
const listRef = react.useRef(null);
|
|
5899
|
-
const rowRefsMap = react.useRef(new Map());
|
|
5900
5910
|
const virtualizerRef = react.useRef(null);
|
|
5901
5911
|
const hasMeasuredOnMount = react.useRef(false);
|
|
5902
5912
|
// Resolve loading indicator once to avoid unnecessary re-renders
|
|
@@ -5988,19 +5998,10 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
5988
5998
|
count: totalRowCount,
|
|
5989
5999
|
estimateSize,
|
|
5990
6000
|
overscan,
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
// Apply transforms to all virtual items
|
|
5996
|
-
instance.getVirtualItems().forEach(virtualRow => {
|
|
5997
|
-
const rowRef = rowRefsMap.current.get(virtualRow.index);
|
|
5998
|
-
if (rowRef) {
|
|
5999
|
-
rowRef.style.transform = `translateY(${virtualRow.start}px)`;
|
|
6000
|
-
}
|
|
6001
|
-
});
|
|
6002
|
-
onChange?.(instance);
|
|
6003
|
-
},
|
|
6001
|
+
// Item positions and the `<ul>` size are written directly to the DOM by
|
|
6002
|
+
// the virtualizer (directDomUpdates), so this callback only forwards the
|
|
6003
|
+
// change to consumers.
|
|
6004
|
+
onChange,
|
|
6004
6005
|
});
|
|
6005
6006
|
// Keep ref updated with latest virtualizer instance
|
|
6006
6007
|
react.useEffect(() => {
|
|
@@ -6024,9 +6025,9 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6024
6025
|
}
|
|
6025
6026
|
previousCountRef.current = currentCount;
|
|
6026
6027
|
}, [virtualizer, pagination?.pageInfo?.count]);
|
|
6028
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
6027
6029
|
// Transform virtual items into typed rows
|
|
6028
6030
|
const rows = react.useMemo(() => {
|
|
6029
|
-
const virtualItems = virtualizer.getVirtualItems();
|
|
6030
6031
|
return virtualItems.map((virtualRow) => {
|
|
6031
6032
|
const { index } = virtualRow;
|
|
6032
6033
|
// Determine row type
|
|
@@ -6056,8 +6057,7 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6056
6057
|
dataIndex,
|
|
6057
6058
|
};
|
|
6058
6059
|
});
|
|
6059
|
-
|
|
6060
|
-
}, [virtualizer, totalDataRows, hasHeader, dataStartIndex, getItem]);
|
|
6060
|
+
}, [virtualItems, totalDataRows, hasHeader, dataStartIndex, getItem]);
|
|
6061
6061
|
// Helper to create list item props
|
|
6062
6062
|
const getListItemProps = react.useCallback((row, options) => {
|
|
6063
6063
|
const { type, item, dataIndex } = row;
|
|
@@ -6072,17 +6072,9 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6072
6072
|
className: options.className,
|
|
6073
6073
|
"data-index": row.virtualRow.index,
|
|
6074
6074
|
onClick,
|
|
6075
|
-
|
|
6076
|
-
|
|
6077
|
-
|
|
6078
|
-
rowRefsMap.current.set(row.virtualRow.index, el);
|
|
6079
|
-
// Apply transform immediately when element is added to map
|
|
6080
|
-
const virtualRow = virtualizer.getVirtualItems().find(vr => vr.index === row.virtualRow.index);
|
|
6081
|
-
if (virtualRow) {
|
|
6082
|
-
el.style.transform = `translateY(${virtualRow.start}px)`;
|
|
6083
|
-
}
|
|
6084
|
-
}
|
|
6085
|
-
},
|
|
6075
|
+
// The virtualizer positions rows itself (directDomUpdates); the ref
|
|
6076
|
+
// only needs to register the element for dynamic height measurement.
|
|
6077
|
+
ref: virtualizer.measureElement,
|
|
6086
6078
|
tabIndex: -1,
|
|
6087
6079
|
};
|
|
6088
6080
|
}, [onRowClick, virtualizer]);
|
|
@@ -6096,7 +6088,11 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6096
6088
|
}, [totalSize]);
|
|
6097
6089
|
return react.useMemo(() => ({
|
|
6098
6090
|
...virtualizer,
|
|
6091
|
+
// `containerRef` (scroll element) intentionally overrides the spread
|
|
6092
|
+
// `virtualizer.containerRef`; the latter is re-exposed as
|
|
6093
|
+
// `listContainerRef` for the inner `<ul>` size container.
|
|
6099
6094
|
containerRef,
|
|
6095
|
+
listContainerRef: virtualizer.containerRef,
|
|
6100
6096
|
listRef,
|
|
6101
6097
|
rows,
|
|
6102
6098
|
getListItemProps,
|
|
@@ -12124,8 +12120,7 @@ const DEFAULT_ROW_HEIGHT = 50;
|
|
|
12124
12120
|
* scroll position at both ends and triggers the appropriate pagination
|
|
12125
12121
|
* direction.
|
|
12126
12122
|
*/
|
|
12127
|
-
const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, onTopItemChange, }) => {
|
|
12128
|
-
"use no memo"; //! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
12123
|
+
const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, onTopItemChange, getItemKey, }) => {
|
|
12129
12124
|
const onTopItemChangeRef = react.useRef(onTopItemChange);
|
|
12130
12125
|
react.useEffect(() => {
|
|
12131
12126
|
onTopItemChangeRef.current = onTopItemChange;
|
|
@@ -12141,7 +12136,10 @@ const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateS
|
|
|
12141
12136
|
}
|
|
12142
12137
|
}, [onChange]);
|
|
12143
12138
|
const handleEstimateSize = react.useCallback((index) => estimateSize?.(index) ?? DEFAULT_ROW_HEIGHT, [estimateSize]);
|
|
12144
|
-
|
|
12139
|
+
// eslint-plugin-react-hooks hardcodes `useVirtualizer` as a known-incompatible
|
|
12140
|
+
// API (its returned functions can't be memoized), so this warning is emitted
|
|
12141
|
+
// regardless of the installed version. The React Compiler already auto-skips
|
|
12142
|
+
// memoizing this hook, so the behaviour is safe.
|
|
12145
12143
|
// eslint-disable-next-line react-hooks/incompatible-library
|
|
12146
12144
|
const rowVirtualizer = reactVirtual.useVirtualizer({
|
|
12147
12145
|
count,
|
|
@@ -12149,7 +12147,14 @@ const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateS
|
|
|
12149
12147
|
estimateSize: handleEstimateSize,
|
|
12150
12148
|
overscan: overscan ?? OVERSCAN,
|
|
12151
12149
|
onChange: handleChange,
|
|
12150
|
+
getItemKey,
|
|
12152
12151
|
useAnimationFrameWithResizeObserver: true,
|
|
12152
|
+
// Let the virtualizer write item positions and container size straight to
|
|
12153
|
+
// the DOM, only re-rendering when the visible index range changes. This
|
|
12154
|
+
// removes the imperative transform/height races that made scrolling jumpy.
|
|
12155
|
+
directDomUpdates: true,
|
|
12156
|
+
directDomUpdatesMode: "transform",
|
|
12157
|
+
useFlushSync: false,
|
|
12153
12158
|
});
|
|
12154
12159
|
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
12155
12160
|
// Track count before backward fetch so we can offset scroll position
|
package/index.esm.js
CHANGED
|
@@ -5731,14 +5731,17 @@ const ListLoadingIndicator = ({ type, hasThumbnail, thumbnailShape, hasDescripti
|
|
|
5731
5731
|
*/
|
|
5732
5732
|
const List = ({ children, className, "data-testid": dataTestId, style, ref,
|
|
5733
5733
|
// UseListResult properties
|
|
5734
|
-
containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
|
|
5734
|
+
containerRef, listRef, listContainerRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer,
|
|
5735
5735
|
// Unused but part of UseListResult interface (can be used from parent)
|
|
5736
5736
|
scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }) => {
|
|
5737
5737
|
const mergedContainerRef = useMergeRefs([containerRef, ref]);
|
|
5738
|
+
// The virtualizer owns the `<ul>` height + item positions via directDomUpdates,
|
|
5739
|
+
// so its container callback must be attached to the `<ul>` (alongside listRef).
|
|
5740
|
+
const mergedListRef = useMergeRefs([listRef, listContainerRef]);
|
|
5738
5741
|
return (jsx("div", { className: cvaListContainer({
|
|
5739
5742
|
withTopSeparator: topSeparatorOnScroll && !isAtTop,
|
|
5740
5743
|
className,
|
|
5741
|
-
}), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsx("ul", { className: cvaList(), ref:
|
|
5744
|
+
}), "data-is-scrolling": isScrolling, "data-testid": dataTestId, ref: mergedContainerRef, style: style, children: jsx("ul", { className: cvaList(), ref: mergedListRef, children: rows.map(row => {
|
|
5742
5745
|
// Generate list item props with clean separator styling
|
|
5743
5746
|
const listItemProps = getListItemProps(row, {
|
|
5744
5747
|
className: cvaListItem$1({
|
|
@@ -5792,7 +5795,7 @@ const DEFAULT_ROW_HEIGHT$1 = 50;
|
|
|
5792
5795
|
* @param props.overscan - Optional number of items to render outside the visible area.
|
|
5793
5796
|
* @param props.onChange - Optional callback when virtualizer changes.
|
|
5794
5797
|
* @param props.skip - Whether to skip automatic loading of more data (default: false).
|
|
5795
|
-
* @returns {
|
|
5798
|
+
* @returns {ReactVirtualizer} The virtualizer instance with all its properties and methods, including `containerRef` for direct DOM positioning.
|
|
5796
5799
|
* @description
|
|
5797
5800
|
* This hook is used to implement infinite scrolling in a table. It uses TanStack Virtual's
|
|
5798
5801
|
* built-in capabilities for virtualization and automatically loads more data when scrolling
|
|
@@ -5806,12 +5809,14 @@ const DEFAULT_ROW_HEIGHT$1 = 50;
|
|
|
5806
5809
|
* });
|
|
5807
5810
|
*/
|
|
5808
5811
|
const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, }) => {
|
|
5809
|
-
"use no memo"; //! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
5810
5812
|
const handleChange = useCallback((virtualizer) => {
|
|
5811
5813
|
onChange?.(virtualizer);
|
|
5812
5814
|
}, [onChange]);
|
|
5813
5815
|
const handleEstimateSize = useCallback((index) => estimateSize?.(index) ?? DEFAULT_ROW_HEIGHT$1, [estimateSize]);
|
|
5814
|
-
|
|
5816
|
+
// eslint-plugin-react-hooks hardcodes `useVirtualizer` as a known-incompatible
|
|
5817
|
+
// API (its returned functions can't be memoized), so this warning is emitted
|
|
5818
|
+
// regardless of the installed version. The React Compiler already auto-skips
|
|
5819
|
+
// memoizing this hook, so the behaviour is safe.
|
|
5815
5820
|
// eslint-disable-next-line react-hooks/incompatible-library
|
|
5816
5821
|
const rowVirtualizer = useVirtualizer({
|
|
5817
5822
|
count,
|
|
@@ -5822,6 +5827,12 @@ const useInfiniteScroll = ({ pagination, scrollElementRef, count, estimateSize,
|
|
|
5822
5827
|
// This option enables wrapping ResizeObserver measurements in requestAnimationFrame for smoother updates and reduced layout thrashing.
|
|
5823
5828
|
// It helps prevent the "ResizeObserver loop completed with undelivered notifications" error by ensuring that measurements align with the rendering cycle
|
|
5824
5829
|
useAnimationFrameWithResizeObserver: true,
|
|
5830
|
+
// Let the virtualizer write item positions and container size straight to
|
|
5831
|
+
// the DOM, only re-rendering when the visible index range changes. This
|
|
5832
|
+
// removes the imperative transform/height races that made scrolling jumpy.
|
|
5833
|
+
directDomUpdates: true,
|
|
5834
|
+
directDomUpdatesMode: "transform",
|
|
5835
|
+
useFlushSync: false,
|
|
5825
5836
|
});
|
|
5826
5837
|
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
5827
5838
|
// Auto-load more data based on scroll position and content height
|
|
@@ -5881,7 +5892,7 @@ const DEFAULT_LOADING_INDICATOR_CONFIG = {
|
|
|
5881
5892
|
*
|
|
5882
5893
|
* return (
|
|
5883
5894
|
* <div ref={list.containerRef}>
|
|
5884
|
-
* <ul
|
|
5895
|
+
* <ul ref={list.listContainerRef} className="relative">
|
|
5885
5896
|
* {list.rows.map(row => {
|
|
5886
5897
|
* const props = list.getListItemProps(row, { className: 'list-item' });
|
|
5887
5898
|
* return <li {...props}>{row.item?.title}</li>;
|
|
@@ -5894,7 +5905,6 @@ const DEFAULT_LOADING_INDICATOR_CONFIG = {
|
|
|
5894
5905
|
const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAULT_LOADING_INDICATOR_CONFIG, onRowClick, onChange, estimateItemSize, estimateHeaderSize, overscan, separator = "line", topSeparatorOnScroll = false, }) => {
|
|
5895
5906
|
const containerRef = useRef(null);
|
|
5896
5907
|
const listRef = useRef(null);
|
|
5897
|
-
const rowRefsMap = useRef(new Map());
|
|
5898
5908
|
const virtualizerRef = useRef(null);
|
|
5899
5909
|
const hasMeasuredOnMount = useRef(false);
|
|
5900
5910
|
// Resolve loading indicator once to avoid unnecessary re-renders
|
|
@@ -5986,19 +5996,10 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
5986
5996
|
count: totalRowCount,
|
|
5987
5997
|
estimateSize,
|
|
5988
5998
|
overscan,
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
// Apply transforms to all virtual items
|
|
5994
|
-
instance.getVirtualItems().forEach(virtualRow => {
|
|
5995
|
-
const rowRef = rowRefsMap.current.get(virtualRow.index);
|
|
5996
|
-
if (rowRef) {
|
|
5997
|
-
rowRef.style.transform = `translateY(${virtualRow.start}px)`;
|
|
5998
|
-
}
|
|
5999
|
-
});
|
|
6000
|
-
onChange?.(instance);
|
|
6001
|
-
},
|
|
5999
|
+
// Item positions and the `<ul>` size are written directly to the DOM by
|
|
6000
|
+
// the virtualizer (directDomUpdates), so this callback only forwards the
|
|
6001
|
+
// change to consumers.
|
|
6002
|
+
onChange,
|
|
6002
6003
|
});
|
|
6003
6004
|
// Keep ref updated with latest virtualizer instance
|
|
6004
6005
|
useEffect(() => {
|
|
@@ -6022,9 +6023,9 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6022
6023
|
}
|
|
6023
6024
|
previousCountRef.current = currentCount;
|
|
6024
6025
|
}, [virtualizer, pagination?.pageInfo?.count]);
|
|
6026
|
+
const virtualItems = virtualizer.getVirtualItems();
|
|
6025
6027
|
// Transform virtual items into typed rows
|
|
6026
6028
|
const rows = useMemo(() => {
|
|
6027
|
-
const virtualItems = virtualizer.getVirtualItems();
|
|
6028
6029
|
return virtualItems.map((virtualRow) => {
|
|
6029
6030
|
const { index } = virtualRow;
|
|
6030
6031
|
// Determine row type
|
|
@@ -6054,8 +6055,7 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6054
6055
|
dataIndex,
|
|
6055
6056
|
};
|
|
6056
6057
|
});
|
|
6057
|
-
|
|
6058
|
-
}, [virtualizer, totalDataRows, hasHeader, dataStartIndex, getItem]);
|
|
6058
|
+
}, [virtualItems, totalDataRows, hasHeader, dataStartIndex, getItem]);
|
|
6059
6059
|
// Helper to create list item props
|
|
6060
6060
|
const getListItemProps = useCallback((row, options) => {
|
|
6061
6061
|
const { type, item, dataIndex } = row;
|
|
@@ -6070,17 +6070,9 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6070
6070
|
className: options.className,
|
|
6071
6071
|
"data-index": row.virtualRow.index,
|
|
6072
6072
|
onClick,
|
|
6073
|
-
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
rowRefsMap.current.set(row.virtualRow.index, el);
|
|
6077
|
-
// Apply transform immediately when element is added to map
|
|
6078
|
-
const virtualRow = virtualizer.getVirtualItems().find(vr => vr.index === row.virtualRow.index);
|
|
6079
|
-
if (virtualRow) {
|
|
6080
|
-
el.style.transform = `translateY(${virtualRow.start}px)`;
|
|
6081
|
-
}
|
|
6082
|
-
}
|
|
6083
|
-
},
|
|
6073
|
+
// The virtualizer positions rows itself (directDomUpdates); the ref
|
|
6074
|
+
// only needs to register the element for dynamic height measurement.
|
|
6075
|
+
ref: virtualizer.measureElement,
|
|
6084
6076
|
tabIndex: -1,
|
|
6085
6077
|
};
|
|
6086
6078
|
}, [onRowClick, virtualizer]);
|
|
@@ -6094,7 +6086,11 @@ const useList = ({ count, pagination, header, getItem, loadingIndicator = DEFAUL
|
|
|
6094
6086
|
}, [totalSize]);
|
|
6095
6087
|
return useMemo(() => ({
|
|
6096
6088
|
...virtualizer,
|
|
6089
|
+
// `containerRef` (scroll element) intentionally overrides the spread
|
|
6090
|
+
// `virtualizer.containerRef`; the latter is re-exposed as
|
|
6091
|
+
// `listContainerRef` for the inner `<ul>` size container.
|
|
6097
6092
|
containerRef,
|
|
6093
|
+
listContainerRef: virtualizer.containerRef,
|
|
6098
6094
|
listRef,
|
|
6099
6095
|
rows,
|
|
6100
6096
|
getListItemProps,
|
|
@@ -12122,8 +12118,7 @@ const DEFAULT_ROW_HEIGHT = 50;
|
|
|
12122
12118
|
* scroll position at both ends and triggers the appropriate pagination
|
|
12123
12119
|
* direction.
|
|
12124
12120
|
*/
|
|
12125
|
-
const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, onTopItemChange, }) => {
|
|
12126
|
-
"use no memo"; //! TODO: remove this once Tanstack Virtual is updated to support React Compiler
|
|
12121
|
+
const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip = false, onTopItemChange, getItemKey, }) => {
|
|
12127
12122
|
const onTopItemChangeRef = useRef(onTopItemChange);
|
|
12128
12123
|
useEffect(() => {
|
|
12129
12124
|
onTopItemChangeRef.current = onTopItemChange;
|
|
@@ -12139,7 +12134,10 @@ const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateS
|
|
|
12139
12134
|
}
|
|
12140
12135
|
}, [onChange]);
|
|
12141
12136
|
const handleEstimateSize = useCallback((index) => estimateSize?.(index) ?? DEFAULT_ROW_HEIGHT, [estimateSize]);
|
|
12142
|
-
|
|
12137
|
+
// eslint-plugin-react-hooks hardcodes `useVirtualizer` as a known-incompatible
|
|
12138
|
+
// API (its returned functions can't be memoized), so this warning is emitted
|
|
12139
|
+
// regardless of the installed version. The React Compiler already auto-skips
|
|
12140
|
+
// memoizing this hook, so the behaviour is safe.
|
|
12143
12141
|
// eslint-disable-next-line react-hooks/incompatible-library
|
|
12144
12142
|
const rowVirtualizer = useVirtualizer({
|
|
12145
12143
|
count,
|
|
@@ -12147,7 +12145,14 @@ const useBidirectionalScroll = ({ pagination, scrollElementRef, count, estimateS
|
|
|
12147
12145
|
estimateSize: handleEstimateSize,
|
|
12148
12146
|
overscan: overscan ?? OVERSCAN,
|
|
12149
12147
|
onChange: handleChange,
|
|
12148
|
+
getItemKey,
|
|
12150
12149
|
useAnimationFrameWithResizeObserver: true,
|
|
12150
|
+
// Let the virtualizer write item positions and container size straight to
|
|
12151
|
+
// the DOM, only re-rendering when the visible index range changes. This
|
|
12152
|
+
// removes the imperative transform/height races that made scrolling jumpy.
|
|
12153
|
+
directDomUpdates: true,
|
|
12154
|
+
directDomUpdatesMode: "transform",
|
|
12155
|
+
useFlushSync: false,
|
|
12151
12156
|
});
|
|
12152
12157
|
const virtualItems = rowVirtualizer.getVirtualItems();
|
|
12153
12158
|
// Track count before backward fetch so we can offset scroll position
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-components",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"migrations": "./migrations.json",
|
|
@@ -14,17 +14,17 @@
|
|
|
14
14
|
"@floating-ui/react": "^0.26.25",
|
|
15
15
|
"string-ts": "^2.0.0",
|
|
16
16
|
"tailwind-merge": "^2.0.0",
|
|
17
|
-
"@trackunit/ui-design-tokens": "1.13.
|
|
18
|
-
"@trackunit/css-class-variance-utilities": "1.13.
|
|
19
|
-
"@trackunit/shared-utils": "1.15.
|
|
20
|
-
"@trackunit/ui-icons": "1.13.
|
|
17
|
+
"@trackunit/ui-design-tokens": "1.13.58",
|
|
18
|
+
"@trackunit/css-class-variance-utilities": "1.13.59",
|
|
19
|
+
"@trackunit/shared-utils": "1.15.62",
|
|
20
|
+
"@trackunit/ui-icons": "1.13.60",
|
|
21
21
|
"es-toolkit": "^1.39.10",
|
|
22
|
-
"@tanstack/react-virtual": "3.
|
|
22
|
+
"@tanstack/react-virtual": "^3.14.3",
|
|
23
23
|
"dequal": "^2.0.3",
|
|
24
24
|
"fflate": "^0.8.2",
|
|
25
25
|
"superjson": "^2.2.6",
|
|
26
26
|
"zod": "^3.25.76",
|
|
27
|
-
"@trackunit/i18n-library-translation": "2.0
|
|
27
|
+
"@trackunit/i18n-library-translation": "2.1.0"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": "^19.0.0",
|
|
@@ -93,4 +93,4 @@ export interface ListProps<TItem = unknown> extends CommonProps, Styleable, Refa
|
|
|
93
93
|
* );
|
|
94
94
|
* ```
|
|
95
95
|
*/
|
|
96
|
-
export declare const List: <TItem = unknown>({ children, className, "data-testid": dataTestId, style, ref, containerRef, listRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer, scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }: ListProps<TItem>) => ReactElement;
|
|
96
|
+
export declare const List: <TItem = unknown>({ children, className, "data-testid": dataTestId, style, ref, containerRef, listRef, listContainerRef, rows, getListItemProps, header, loadingIndicator, shouldShowLoaderAtIndex, count, isScrolling, separator, topSeparatorOnScroll, isAtTop, contentFillsContainer, scrollOffset: _scrollOffset, getTotalSize: _getTotalSize, getVirtualItems: _getVirtualItems, scrollToOffset: _scrollToOffset, scrollToIndex: _scrollToIndex, measure: _measure, }: ListProps<TItem>) => ReactElement;
|
|
@@ -28,13 +28,13 @@ type CustomLoadingIndicator = {
|
|
|
28
28
|
/**
|
|
29
29
|
* Number of loading indicators to show on initial load
|
|
30
30
|
*
|
|
31
|
-
* @default
|
|
31
|
+
* @default 10
|
|
32
32
|
*/
|
|
33
33
|
initialLoadingCount?: number;
|
|
34
34
|
/**
|
|
35
35
|
* Number of loading indicators to show when loading more items during scroll
|
|
36
36
|
*
|
|
37
|
-
* @default
|
|
37
|
+
* @default 3
|
|
38
38
|
*/
|
|
39
39
|
scrollLoadingCount?: number;
|
|
40
40
|
hasThumbnail?: never;
|
|
@@ -54,13 +54,13 @@ type SkeletonLoadingIndicator = {
|
|
|
54
54
|
/**
|
|
55
55
|
* Number of loading indicators to show on initial load
|
|
56
56
|
*
|
|
57
|
-
* @default
|
|
57
|
+
* @default 10
|
|
58
58
|
*/
|
|
59
59
|
initialLoadingCount?: number;
|
|
60
60
|
/**
|
|
61
61
|
* Number of loading indicators to show when loading more items during scroll
|
|
62
62
|
*
|
|
63
|
-
* @default
|
|
63
|
+
* @default 3
|
|
64
64
|
*/
|
|
65
65
|
scrollLoadingCount?: number;
|
|
66
66
|
};
|
|
@@ -132,6 +132,13 @@ export interface UseListResult<TItem> extends Pick<Virtualizer<HTMLDivElement, H
|
|
|
132
132
|
containerRef: RefObject<HTMLDivElement | null>;
|
|
133
133
|
/** Reference to attach to the list */
|
|
134
134
|
listRef: RefObject<HTMLUListElement | null>;
|
|
135
|
+
/**
|
|
136
|
+
* Callback ref for the inner size container (`<ul>`). The virtualizer writes
|
|
137
|
+
* the container's height and item positions directly to the DOM
|
|
138
|
+
* (directDomUpdates), so this MUST be attached to the `<ul>` and the `<ul>`
|
|
139
|
+
* must not set its own height.
|
|
140
|
+
*/
|
|
141
|
+
listContainerRef: (node: HTMLElement | null) => void;
|
|
135
142
|
/** Array of row data to render */
|
|
136
143
|
readonly rows: ReadonlyArray<ListRow<TItem>>;
|
|
137
144
|
/** Helper to create list item props for a given row */
|
|
@@ -178,7 +185,7 @@ export interface UseListResult<TItem> extends Pick<Virtualizer<HTMLDivElement, H
|
|
|
178
185
|
*
|
|
179
186
|
* return (
|
|
180
187
|
* <div ref={list.containerRef}>
|
|
181
|
-
* <ul
|
|
188
|
+
* <ul ref={list.listContainerRef} className="relative">
|
|
182
189
|
* {list.rows.map(row => {
|
|
183
190
|
* const props = list.getListItemProps(row, { className: 'list-item' });
|
|
184
191
|
* return <li {...props}>{row.item?.title}</li>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Virtualizer } from "@tanstack/react-virtual";
|
|
1
|
+
import { type ReactVirtualizer, type Virtualizer } from "@tanstack/react-virtual";
|
|
2
2
|
import { type RefObject } from "react";
|
|
3
3
|
import type { RelayPagination } from "./useRelayPagination";
|
|
4
4
|
interface BidirectionalScrollProps<TScrollElement extends Element, TItemElement extends Element> {
|
|
@@ -10,6 +10,15 @@ interface BidirectionalScrollProps<TScrollElement extends Element, TItemElement
|
|
|
10
10
|
readonly onChange?: (virtualizer: Virtualizer<TScrollElement, TItemElement>) => void;
|
|
11
11
|
readonly skip?: boolean;
|
|
12
12
|
readonly onTopItemChange?: (index: number) => void;
|
|
13
|
+
/**
|
|
14
|
+
* Derives a stable per-item key from the index. Must agree with the React
|
|
15
|
+
* `key` used by the consumer's rendered items, otherwise the virtualizer's
|
|
16
|
+
* `elementsCache` (keyed by this value) and React's reconciliation (keyed
|
|
17
|
+
* by the rendered `key`) can disagree when indexes shift without a remount
|
|
18
|
+
* (e.g. sorting, filtering, or prepending pages), causing `directDomUpdates`
|
|
19
|
+
* to position rows incorrectly. Defaults to the item index.
|
|
20
|
+
*/
|
|
21
|
+
readonly getItemKey?: (index: number) => string | number;
|
|
13
22
|
}
|
|
14
23
|
/**
|
|
15
24
|
* Bidirectional infinite scroll hook that supports both forward (nextPage)
|
|
@@ -19,5 +28,5 @@ interface BidirectionalScrollProps<TScrollElement extends Element, TItemElement
|
|
|
19
28
|
* scroll position at both ends and triggers the appropriate pagination
|
|
20
29
|
* direction.
|
|
21
30
|
*/
|
|
22
|
-
export declare const useBidirectionalScroll: <TScrollElement extends Element, TItemElement extends Element>({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip, onTopItemChange, }: BidirectionalScrollProps<TScrollElement, TItemElement>) =>
|
|
31
|
+
export declare const useBidirectionalScroll: <TScrollElement extends Element, TItemElement extends Element>({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip, onTopItemChange, getItemKey, }: BidirectionalScrollProps<TScrollElement, TItemElement>) => ReactVirtualizer<TScrollElement, TItemElement>;
|
|
23
32
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Virtualizer } from "@tanstack/react-virtual";
|
|
1
|
+
import { type ReactVirtualizer, type Virtualizer } from "@tanstack/react-virtual";
|
|
2
2
|
import { RefObject } from "react";
|
|
3
3
|
import { RelayPagination } from "./useRelayPagination";
|
|
4
4
|
interface InfiniteScrollProps<TScrollElement extends Element, TItemElement extends Element> {
|
|
@@ -21,7 +21,7 @@ interface InfiniteScrollProps<TScrollElement extends Element, TItemElement exten
|
|
|
21
21
|
* @param props.overscan - Optional number of items to render outside the visible area.
|
|
22
22
|
* @param props.onChange - Optional callback when virtualizer changes.
|
|
23
23
|
* @param props.skip - Whether to skip automatic loading of more data (default: false).
|
|
24
|
-
* @returns {
|
|
24
|
+
* @returns {ReactVirtualizer} The virtualizer instance with all its properties and methods, including `containerRef` for direct DOM positioning.
|
|
25
25
|
* @description
|
|
26
26
|
* This hook is used to implement infinite scrolling in a table. It uses TanStack Virtual's
|
|
27
27
|
* built-in capabilities for virtualization and automatically loads more data when scrolling
|
|
@@ -34,5 +34,5 @@ interface InfiniteScrollProps<TScrollElement extends Element, TItemElement exten
|
|
|
34
34
|
* estimateSize: () => 35,
|
|
35
35
|
* });
|
|
36
36
|
*/
|
|
37
|
-
export declare const useInfiniteScroll: <TScrollElement extends Element, TItemElement extends Element>({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip, }: InfiniteScrollProps<TScrollElement, TItemElement>) =>
|
|
37
|
+
export declare const useInfiniteScroll: <TScrollElement extends Element, TItemElement extends Element>({ pagination, scrollElementRef, count, estimateSize, overscan, onChange, skip, }: InfiniteScrollProps<TScrollElement, TItemElement>) => ReactVirtualizer<TScrollElement, TItemElement>;
|
|
38
38
|
export {};
|