@wallarm-org/design-system 0.52.0-rc-feature-shell.1 → 0.52.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/dist/components/NavRail/NavRail.d.ts +0 -1
- package/dist/components/NavRail/NavRail.js +2 -2
- package/dist/components/NavRail/classes.js +1 -1
- package/dist/components/Table/TableBody/TableBody.js +6 -1
- package/dist/components/Table/TableBody/TableBodyVirtualizedContainer.js +7 -2
- package/dist/components/Table/TableBody/TableBodyVirtualizedCore.js +6 -1
- package/dist/components/Table/TableBody/TableBodyVirtualizedWindow.js +7 -2
- package/dist/components/Table/TableBody/lib/measureRowElement.d.ts +10 -0
- package/dist/components/Table/TableBody/lib/measureRowElement.js +7 -0
- package/dist/components/Table/TableBody/useResetVirtualizerOnDataChange.d.ts +3 -3
- package/dist/components/Table/TableBody/useResetVirtualizerOnDataChange.js +8 -6
- package/dist/components/Table/TableContext/TableProvider.js +9 -3
- package/dist/components/Table/TableContext/types.d.ts +4 -1
- package/dist/components/Table/TableInner/TableInnerContainer.js +10 -4
- package/dist/components/Table/TableInner/TableInnerWindow.js +10 -4
- package/dist/components/Table/TableLoadingState.d.ts +12 -1
- package/dist/components/Table/TableLoadingState.js +4 -3
- package/dist/components/Table/hooks/index.d.ts +1 -1
- package/dist/components/Table/hooks/index.js +2 -2
- package/dist/components/Table/hooks/infiniteScroll/index.d.ts +1 -0
- package/dist/components/Table/hooks/infiniteScroll/index.js +2 -0
- package/dist/components/Table/hooks/infiniteScroll/useInfiniteScroll.d.ts +19 -0
- package/dist/components/Table/hooks/infiniteScroll/useInfiniteScroll.js +36 -0
- package/dist/components/Table/hooks/infiniteScroll/useInitialAnchor.d.ts +16 -0
- package/dist/components/Table/hooks/infiniteScroll/useInitialAnchor.js +28 -0
- package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.d.ts +27 -0
- package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.js +70 -0
- package/dist/components/Table/hooks/infiniteScroll/useScrollEdge.d.ts +20 -0
- package/dist/components/Table/hooks/{useEndReached.js → infiniteScroll/useScrollEdge.js} +15 -11
- package/dist/components/Table/lib/constants.d.ts +5 -0
- package/dist/components/Table/lib/constants.js +4 -1
- package/dist/components/Table/lib/detectDataChange.d.ts +4 -0
- package/dist/components/Table/lib/detectDataChange.js +7 -0
- package/dist/components/Table/lib/getRowKey.d.ts +4 -0
- package/dist/components/Table/lib/getRowKey.js +2 -0
- package/dist/components/Table/lib/index.d.ts +3 -1
- package/dist/components/Table/lib/index.js +4 -2
- package/dist/components/Table/mocks.d.ts +14 -0
- package/dist/components/Table/mocks.js +61 -1
- package/dist/components/Table/types.d.ts +17 -1
- package/dist/components/TopHeader/TopHeader.d.ts +0 -1
- package/dist/components/TopHeader/TopHeader.js +2 -2
- package/dist/hooks/useOverflowItems.d.ts +6 -0
- package/dist/hooks/useOverflowItems.js +44 -14
- package/dist/hooks/useOverflowItems.scheduler.d.ts +16 -0
- package/dist/hooks/useOverflowItems.scheduler.js +25 -0
- package/dist/metadata/components.json +22 -12
- package/package.json +1 -1
- package/dist/components/Table/hooks/useEndReached.d.ts +0 -19
|
@@ -6,7 +6,7 @@ import { cn } from "../../utils/cn.js";
|
|
|
6
6
|
import { TestIdProvider } from "../../utils/testId.js";
|
|
7
7
|
import { navRailVariants } from "./classes.js";
|
|
8
8
|
import { NavRailContextProvider } from "./NavRailContext.js";
|
|
9
|
-
const NavRail = ({ ref, collapsed = false,
|
|
9
|
+
const NavRail = ({ ref, collapsed = false, className, children, 'data-testid': testId, ...props })=>{
|
|
10
10
|
const internalRef = useRef(null);
|
|
11
11
|
const focusPanel = useCallback(()=>{
|
|
12
12
|
const panel = document.querySelector('[data-slot="nav-panel"]');
|
|
@@ -47,7 +47,7 @@ const NavRail = ({ ref, collapsed = false, appeared, className, children, 'data-
|
|
|
47
47
|
"data-testid": testId,
|
|
48
48
|
className: cn(navRailVariants({
|
|
49
49
|
collapsed
|
|
50
|
-
}),
|
|
50
|
+
}), className),
|
|
51
51
|
children: children
|
|
52
52
|
})
|
|
53
53
|
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { cva } from "class-variance-authority";
|
|
2
|
-
const navRailVariants = cva('flex h-full shrink-0 flex-col overflow-hidden px-8 pt-6 pb-12 transition-[width] duration-200 ease-in-out
|
|
2
|
+
const navRailVariants = cva('flex h-full shrink-0 flex-col overflow-hidden px-8 pt-6 pb-12 transition-[width] duration-200 ease-in-out', {
|
|
3
3
|
variants: {
|
|
4
4
|
collapsed: {
|
|
5
5
|
true: 'w-[48px]',
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTestId } from "../../../utils/testId.js";
|
|
3
|
+
import { TABLE_PREPEND_SKELETON_ROWS } from "../lib/index.js";
|
|
3
4
|
import { TBody } from "../primitives/index.js";
|
|
4
5
|
import { useTableContext } from "../TableContext/index.js";
|
|
5
6
|
import { TableLoadingState } from "../TableLoadingState.js";
|
|
@@ -7,7 +8,7 @@ import { TableRow } from "../TableRow.js";
|
|
|
7
8
|
import { TableBodyVirtualizedContainer } from "./TableBodyVirtualizedContainer.js";
|
|
8
9
|
import { TableBodyVirtualizedWindow } from "./TableBodyVirtualizedWindow.js";
|
|
9
10
|
const TableBody = ()=>{
|
|
10
|
-
const { table, isLoading, virtualized, tbodyRef, virtualizerRef } = useTableContext();
|
|
11
|
+
const { table, isLoading, isLoadingPrevious, virtualized, tbodyRef, virtualizerRef } = useTableContext();
|
|
11
12
|
const testId = useTestId('body');
|
|
12
13
|
const rows = table.getRowModel().rows;
|
|
13
14
|
const hasData = rows.length > 0;
|
|
@@ -20,6 +21,10 @@ const TableBody = ()=>{
|
|
|
20
21
|
ref: tbodyRef,
|
|
21
22
|
"data-testid": testId,
|
|
22
23
|
children: [
|
|
24
|
+
isLoadingPrevious && /*#__PURE__*/ jsx(TableLoadingState, {
|
|
25
|
+
position: "start",
|
|
26
|
+
count: TABLE_PREPEND_SKELETON_ROWS
|
|
27
|
+
}),
|
|
23
28
|
rows.map((row)=>/*#__PURE__*/ jsx(TableRow, {
|
|
24
29
|
row: row
|
|
25
30
|
}, row.id)),
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect } from "react";
|
|
3
3
|
import { useVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
-
import { TABLE_VIRTUALIZATION_OVERSCAN } from "../lib/index.js";
|
|
4
|
+
import { TABLE_VIRTUALIZATION_OVERSCAN, getRowKey } from "../lib/index.js";
|
|
5
5
|
import { useTableContext } from "../TableContext/index.js";
|
|
6
|
+
import { measureRowElement } from "./lib/measureRowElement.js";
|
|
6
7
|
import { TableBodyVirtualizedCore } from "./TableBodyVirtualizedCore.js";
|
|
7
8
|
import { useResetVirtualizerOnDataChange } from "./useResetVirtualizerOnDataChange.js";
|
|
8
9
|
import { useSmoothScrollOnSort } from "./useSmoothScrollOnSort.js";
|
|
@@ -15,7 +16,11 @@ const TableBodyVirtualizedContainer = ()=>{
|
|
|
15
16
|
count: table.getRowModel().rows.length,
|
|
16
17
|
getScrollElement,
|
|
17
18
|
estimateSize: estimateRowHeight ?? (()=>40),
|
|
18
|
-
overscan: overscan ?? TABLE_VIRTUALIZATION_OVERSCAN
|
|
19
|
+
overscan: overscan ?? TABLE_VIRTUALIZATION_OVERSCAN,
|
|
20
|
+
getItemKey: useCallback((index)=>getRowKey(table.getRowModel().rows, index), [
|
|
21
|
+
table
|
|
22
|
+
]),
|
|
23
|
+
measureElement: measureRowElement
|
|
19
24
|
});
|
|
20
25
|
virtualizerRef.current = virtualizer;
|
|
21
26
|
useEffect(()=>()=>{
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useTestId } from "../../../utils/testId.js";
|
|
3
|
+
import { TABLE_PREPEND_SKELETON_ROWS } from "../lib/index.js";
|
|
3
4
|
import { TBody, Td, Tr } from "../primitives/index.js";
|
|
4
5
|
import { useTableContext } from "../TableContext/index.js";
|
|
5
6
|
import { TableLoadingState } from "../TableLoadingState.js";
|
|
6
7
|
import { TableRow } from "../TableRow.js";
|
|
7
8
|
const TableBodyVirtualizedCore = ({ tbodyRef, virtualizer })=>{
|
|
8
|
-
const { table, isLoading } = useTableContext();
|
|
9
|
+
const { table, isLoading, isLoadingPrevious } = useTableContext();
|
|
9
10
|
const testId = useTestId('body');
|
|
10
11
|
const virtualRows = virtualizer.getVirtualItems();
|
|
11
12
|
const totalSize = virtualizer.getTotalSize();
|
|
@@ -17,6 +18,10 @@ const TableBodyVirtualizedCore = ({ tbodyRef, virtualizer })=>{
|
|
|
17
18
|
ref: tbodyRef,
|
|
18
19
|
"data-testid": testId,
|
|
19
20
|
children: [
|
|
21
|
+
isLoadingPrevious && /*#__PURE__*/ jsx(TableLoadingState, {
|
|
22
|
+
position: "start",
|
|
23
|
+
count: TABLE_PREPEND_SKELETON_ROWS
|
|
24
|
+
}),
|
|
20
25
|
virtualRows.length > 0 && /*#__PURE__*/ jsx(Tr, {
|
|
21
26
|
children: /*#__PURE__*/ jsx(Td, {
|
|
22
27
|
style: {
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect } from "react";
|
|
3
3
|
import { useWindowVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
-
import { TABLE_VIRTUALIZATION_OVERSCAN } from "../lib/index.js";
|
|
4
|
+
import { TABLE_VIRTUALIZATION_OVERSCAN, getRowKey } from "../lib/index.js";
|
|
5
5
|
import { useTableContext } from "../TableContext/index.js";
|
|
6
6
|
import { getDocumentOffsetTop } from "./lib/getDocumentOffsetTop.js";
|
|
7
|
+
import { measureRowElement } from "./lib/measureRowElement.js";
|
|
7
8
|
import { TableBodyVirtualizedCore } from "./TableBodyVirtualizedCore.js";
|
|
8
9
|
import { useResetVirtualizerOnDataChange } from "./useResetVirtualizerOnDataChange.js";
|
|
9
10
|
import { useSmoothScrollOnSort } from "./useSmoothScrollOnSort.js";
|
|
@@ -13,7 +14,11 @@ const TableBodyVirtualizedWindow = ()=>{
|
|
|
13
14
|
count: table.getRowModel().rows.length,
|
|
14
15
|
estimateSize: estimateRowHeight ?? (()=>40),
|
|
15
16
|
overscan: overscan ?? TABLE_VIRTUALIZATION_OVERSCAN,
|
|
16
|
-
scrollMargin: tbodyRef.current ? getDocumentOffsetTop(tbodyRef.current) : 0
|
|
17
|
+
scrollMargin: tbodyRef.current ? getDocumentOffsetTop(tbodyRef.current) : 0,
|
|
18
|
+
getItemKey: useCallback((index)=>getRowKey(table.getRowModel().rows, index), [
|
|
19
|
+
table
|
|
20
|
+
]),
|
|
21
|
+
measureElement: measureRowElement
|
|
17
22
|
});
|
|
18
23
|
virtualizerRef.current = virtualizer;
|
|
19
24
|
useEffect(()=>()=>{
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Virtualizer } from '@tanstack/react-virtual';
|
|
2
|
+
/**
|
|
3
|
+
* Row-measurement for the table virtualizers. TanStack's default reads
|
|
4
|
+
* `offsetHeight` when called without a ResizeObserver entry — i.e. on row
|
|
5
|
+
* mount mid-commit, forcing a reflow per new row while scrolling. That read is
|
|
6
|
+
* redundant: `observe()` delivers an initial `borderBoxSize` entry the same
|
|
7
|
+
* frame, post-layout. So entry-less we return the assumed size (cache/estimate)
|
|
8
|
+
* — a no-op — and let the observer entry supply the real one.
|
|
9
|
+
*/
|
|
10
|
+
export declare const measureRowElement: <TScrollElement extends Element | Window>(element: Element, entry: ResizeObserverEntry | undefined, instance: Virtualizer<TScrollElement, Element>) => number;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { measureElement } from "@tanstack/react-virtual";
|
|
2
|
+
const measureRowElement = (element, entry, instance)=>{
|
|
3
|
+
if (entry) return measureElement(element, entry, instance);
|
|
4
|
+
const index = instance.indexFromElement(element);
|
|
5
|
+
return instance.measurementsCache[index]?.size ?? instance.options.estimateSize(index);
|
|
6
|
+
};
|
|
7
|
+
export { measureRowElement };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Table } from '@tanstack/react-table';
|
|
2
2
|
import type { Virtualizer } from '@tanstack/react-virtual';
|
|
3
3
|
/**
|
|
4
|
-
* Reset cached measurements
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Reset cached measurements only on a full dataset replacement. On a prepend
|
|
5
|
+
* (infinite scroll up) measurements are kept — usePrependScrollAnchor handles
|
|
6
|
+
* the position — so the virtualizer does not re-measure and jump.
|
|
7
7
|
*/
|
|
8
8
|
export declare const useResetVirtualizerOnDataChange: (table: Table<unknown>, virtualizer: Virtualizer<Element, Element> | Virtualizer<Window, Element> | Virtualizer<HTMLElement, Element>) => void;
|
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
|
+
import { detectDataChange } from "../lib/index.js";
|
|
2
3
|
const useResetVirtualizerOnDataChange = (table, virtualizer)=>{
|
|
3
|
-
const
|
|
4
|
-
const firstRowId = rows[0]?.id;
|
|
4
|
+
const firstRowId = table.getRowModel().rows[0]?.id;
|
|
5
5
|
const prevFirstRowIdRef = useRef(firstRowId);
|
|
6
6
|
useEffect(()=>{
|
|
7
|
-
if (prevFirstRowIdRef.current
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
if (prevFirstRowIdRef.current === firstRowId) return;
|
|
8
|
+
const rows = table.getRowModel().rows;
|
|
9
|
+
const change = detectDataChange(prevFirstRowIdRef.current, rows);
|
|
10
|
+
prevFirstRowIdRef.current = firstRowId;
|
|
11
|
+
if ('replace' === change) virtualizer.measure();
|
|
11
12
|
}, [
|
|
12
13
|
firstRowId,
|
|
14
|
+
table,
|
|
13
15
|
virtualizer
|
|
14
16
|
]);
|
|
15
17
|
};
|
|
@@ -8,7 +8,7 @@ import { useTableState } from "../hooks/index.js";
|
|
|
8
8
|
import { TABLE_EXPAND_COLUMN_ID, TABLE_MIN_COLUMN_WIDTH, TABLE_SELECT_COLUMN_ID, TABLE_SKELETON_ROWS, TABLE_VIRTUALIZATION_OVERSCAN, createExpandColumn, createSelectionColumn } from "../lib/index.js";
|
|
9
9
|
import { TableContext } from "./TableContext.js";
|
|
10
10
|
const TableProvider = (props)=>{
|
|
11
|
-
const { data, columns, isLoading = false, skeletonCount = TABLE_SKELETON_ROWS, children, getRowId, sorting: sortingProp, onSortingChange, manualSorting = false, rowSelection: rowSelectionProp, onRowSelectionChange, columnSizing: columnSizingProp, onColumnSizingChange, columnPinning: columnPinningProp, onColumnPinningChange, columnOrder: columnOrderProp, onColumnOrderChange, grouping: groupingProp, onGroupingChange, expanded: expandedProp, onExpandedChange, renderGroupRow, getSubRows, renderExpandedRow, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, defaultColumnVisibility, defaultColumnOrder, virtualized, estimateRowHeight, overscan = TABLE_VIRTUALIZATION_OVERSCAN, onEndReached, onEndReachedThreshold, onMasterCellClick, activeRowId: activeRowIdProp } = props;
|
|
11
|
+
const { data, columns, isLoading = false, isLoadingPrevious = false, skeletonCount = TABLE_SKELETON_ROWS, children, getRowId, sorting: sortingProp, onSortingChange, manualSorting = false, rowSelection: rowSelectionProp, onRowSelectionChange, columnSizing: columnSizingProp, onColumnSizingChange, columnPinning: columnPinningProp, onColumnPinningChange, columnOrder: columnOrderProp, onColumnOrderChange, grouping: groupingProp, onGroupingChange, expanded: expandedProp, onExpandedChange, renderGroupRow, getSubRows, renderExpandedRow, columnVisibility: columnVisibilityProp, onColumnVisibilityChange, defaultColumnVisibility, defaultColumnOrder, virtualized, estimateRowHeight, overscan = TABLE_VIRTUALIZATION_OVERSCAN, onEndReached, onEndReachedThreshold, onStartReached, onStartReachedThreshold, initialScrollToRowId, onMasterCellClick, activeRowId: activeRowIdProp } = props;
|
|
12
12
|
const masterCellActiveRowId = activeRowIdProp ?? null;
|
|
13
13
|
const sortingEnabled = !!onSortingChange;
|
|
14
14
|
const selectionEnabled = !!onRowSelectionChange;
|
|
@@ -182,13 +182,13 @@ const TableProvider = (props)=>{
|
|
|
182
182
|
});
|
|
183
183
|
const allLeafColumns = table.getAllLeafColumns();
|
|
184
184
|
const lastSelectedRowIndexRef = useRef(null);
|
|
185
|
-
const theadRef = useRef(null);
|
|
186
185
|
const containerRef = useRef(null);
|
|
187
186
|
const tbodyRef = useRef(null);
|
|
188
187
|
const virtualizerRef = useRef(null);
|
|
189
188
|
const contextValue = useMemo(()=>({
|
|
190
189
|
table,
|
|
191
190
|
isLoading,
|
|
191
|
+
isLoadingPrevious,
|
|
192
192
|
skeletonCount,
|
|
193
193
|
sortingEnabled,
|
|
194
194
|
selectionEnabled,
|
|
@@ -209,17 +209,20 @@ const TableProvider = (props)=>{
|
|
|
209
209
|
alwaysPinnedLeft,
|
|
210
210
|
masterColumnId,
|
|
211
211
|
lastSelectedRowIndexRef,
|
|
212
|
-
theadRef,
|
|
213
212
|
containerRef,
|
|
214
213
|
tbodyRef,
|
|
215
214
|
virtualizerRef,
|
|
216
215
|
onEndReached,
|
|
217
216
|
onEndReachedThreshold,
|
|
217
|
+
onStartReached,
|
|
218
|
+
onStartReachedThreshold,
|
|
219
|
+
initialScrollToRowId,
|
|
218
220
|
onMasterCellClick,
|
|
219
221
|
activeRowId: masterCellActiveRowId
|
|
220
222
|
}), [
|
|
221
223
|
table,
|
|
222
224
|
isLoading,
|
|
225
|
+
isLoadingPrevious,
|
|
223
226
|
skeletonCount,
|
|
224
227
|
sortingEnabled,
|
|
225
228
|
selectionEnabled,
|
|
@@ -241,6 +244,9 @@ const TableProvider = (props)=>{
|
|
|
241
244
|
masterColumnId,
|
|
242
245
|
onEndReached,
|
|
243
246
|
onEndReachedThreshold,
|
|
247
|
+
onStartReached,
|
|
248
|
+
onStartReachedThreshold,
|
|
249
|
+
initialScrollToRowId,
|
|
244
250
|
masterCellActiveRowId,
|
|
245
251
|
onMasterCellClick
|
|
246
252
|
]);
|
|
@@ -11,6 +11,7 @@ export type TableVirtualizerInstance = Virtualizer<Window, Element> | Virtualize
|
|
|
11
11
|
export interface TableContextValue<T> {
|
|
12
12
|
table: TanStackTable<T>;
|
|
13
13
|
isLoading: boolean;
|
|
14
|
+
isLoadingPrevious: boolean;
|
|
14
15
|
skeletonCount: number;
|
|
15
16
|
sortingEnabled: boolean;
|
|
16
17
|
selectionEnabled: boolean;
|
|
@@ -31,12 +32,14 @@ export interface TableContextValue<T> {
|
|
|
31
32
|
lastSelectedRowIndexRef: RefObject<number | null>;
|
|
32
33
|
alwaysPinnedLeft: string[];
|
|
33
34
|
masterColumnId: string | null;
|
|
34
|
-
theadRef: RefObject<HTMLTableSectionElement | null>;
|
|
35
35
|
containerRef: RefObject<HTMLDivElement | null>;
|
|
36
36
|
tbodyRef: RefObject<HTMLTableSectionElement | null>;
|
|
37
37
|
virtualizerRef: RefObject<TableVirtualizerInstance | null>;
|
|
38
38
|
onEndReached?: () => void;
|
|
39
39
|
onEndReachedThreshold?: number;
|
|
40
|
+
onStartReached?: () => void;
|
|
41
|
+
onStartReachedThreshold?: number;
|
|
42
|
+
initialScrollToRowId?: string;
|
|
40
43
|
onMasterCellClick?: (rowId: string) => void;
|
|
41
44
|
activeRowId: string | null;
|
|
42
45
|
}
|
|
@@ -4,7 +4,7 @@ import { cn } from "../../../utils/cn.js";
|
|
|
4
4
|
import { useTestId } from "../../../utils/testId.js";
|
|
5
5
|
import { ScrollArea, ScrollAreaCorner, ScrollAreaScrollbar, ScrollAreaViewport } from "../../ScrollArea/index.js";
|
|
6
6
|
import { tableContainerVariants } from "../classes.js";
|
|
7
|
-
import {
|
|
7
|
+
import { useInfiniteScroll } from "../hooks/index.js";
|
|
8
8
|
import { useContainerWidth } from "../lib/index.js";
|
|
9
9
|
import { TableBody } from "../TableBody/index.js";
|
|
10
10
|
import { TableColGroup } from "../TableColGroup.js";
|
|
@@ -12,15 +12,21 @@ import { useTableContext } from "../TableContext/index.js";
|
|
|
12
12
|
import { TableHead } from "../TableHead.js";
|
|
13
13
|
import { TableSettingsMenu } from "../TableSettingsMenu/index.js";
|
|
14
14
|
const TableInnerContainer = ({ isEmpty, virtualized, showSettings, ariaLabel, children })=>{
|
|
15
|
-
const { containerRef, table, onEndReached, onEndReachedThreshold } = useTableContext();
|
|
15
|
+
const { containerRef, table, virtualizerRef, tbodyRef, onEndReached, onEndReachedThreshold, onStartReached, onStartReachedThreshold, initialScrollToRowId } = useTableContext();
|
|
16
16
|
const testId = useTestId('container');
|
|
17
17
|
const scrollRootRef = useRef(null);
|
|
18
18
|
const containerWidth = useContainerWidth(containerRef);
|
|
19
|
-
|
|
19
|
+
useInfiniteScroll({
|
|
20
20
|
mode: 'container',
|
|
21
21
|
scrollRef: containerRef,
|
|
22
|
+
table,
|
|
23
|
+
virtualizerRef,
|
|
24
|
+
tbodyRef,
|
|
22
25
|
onEndReached,
|
|
23
|
-
|
|
26
|
+
onEndReachedThreshold,
|
|
27
|
+
onStartReached,
|
|
28
|
+
onStartReachedThreshold,
|
|
29
|
+
initialScrollToRowId
|
|
24
30
|
});
|
|
25
31
|
useEffect(()=>{
|
|
26
32
|
const viewport = containerRef.current;
|
|
@@ -2,7 +2,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
import { useEffect, useRef } from "react";
|
|
3
3
|
import { useTestId } from "../../../utils/testId.js";
|
|
4
4
|
import { ScrollArea, ScrollAreaScrollbar, ScrollAreaViewport } from "../../ScrollArea/index.js";
|
|
5
|
-
import {
|
|
5
|
+
import { useInfiniteScroll } from "../hooks/index.js";
|
|
6
6
|
import { useContainerWidth } from "../lib/index.js";
|
|
7
7
|
import { TableBody } from "../TableBody/index.js";
|
|
8
8
|
import { TableColGroup } from "../TableColGroup.js";
|
|
@@ -10,15 +10,21 @@ import { useTableContext } from "../TableContext/index.js";
|
|
|
10
10
|
import { TableHead } from "../TableHead.js";
|
|
11
11
|
import { TableSettingsMenu } from "../TableSettingsMenu/index.js";
|
|
12
12
|
const TableInnerWindow = ({ isEmpty, showSettings, ariaLabel, children })=>{
|
|
13
|
-
const { table, onEndReached, onEndReachedThreshold } = useTableContext();
|
|
13
|
+
const { table, virtualizerRef, tbodyRef, onEndReached, onEndReachedThreshold, onStartReached, onStartReachedThreshold, initialScrollToRowId } = useTableContext();
|
|
14
14
|
const testId = useTestId('window');
|
|
15
15
|
const rootRef = useRef(null);
|
|
16
16
|
const scrollRef = useRef(null);
|
|
17
17
|
const containerWidth = useContainerWidth(rootRef);
|
|
18
|
-
|
|
18
|
+
useInfiniteScroll({
|
|
19
19
|
mode: 'window',
|
|
20
|
+
table,
|
|
21
|
+
virtualizerRef,
|
|
22
|
+
tbodyRef,
|
|
20
23
|
onEndReached,
|
|
21
|
-
|
|
24
|
+
onEndReachedThreshold,
|
|
25
|
+
onStartReached,
|
|
26
|
+
onStartReachedThreshold,
|
|
27
|
+
initialScrollToRowId
|
|
22
28
|
});
|
|
23
29
|
useEffect(()=>{
|
|
24
30
|
const scrollEl = scrollRef.current;
|
|
@@ -1,2 +1,13 @@
|
|
|
1
1
|
import type { FC } from 'react';
|
|
2
|
-
|
|
2
|
+
interface TableLoadingStateProps {
|
|
3
|
+
/**
|
|
4
|
+
* Edge the skeletons sit at. 'end' (default) is the bottom loader; 'start'
|
|
5
|
+
* is the prepend loader above the rows — own test id + a data attribute the
|
|
6
|
+
* scroll compensation measures.
|
|
7
|
+
*/
|
|
8
|
+
position?: 'start' | 'end';
|
|
9
|
+
/** Number of skeleton rows; defaults to the table-level skeletonCount. */
|
|
10
|
+
count?: number;
|
|
11
|
+
}
|
|
12
|
+
export declare const TableLoadingState: FC<TableLoadingStateProps>;
|
|
13
|
+
export {};
|
|
@@ -3,17 +3,18 @@ import { useTestId } from "../../utils/testId.js";
|
|
|
3
3
|
import { Skeleton } from "../Skeleton/index.js";
|
|
4
4
|
import { Td, Tr } from "./primitives/index.js";
|
|
5
5
|
import { useTableContext } from "./TableContext/index.js";
|
|
6
|
-
const TableLoadingState = ()=>{
|
|
6
|
+
const TableLoadingState = ({ position = 'end', count })=>{
|
|
7
7
|
const { table, skeletonCount } = useTableContext();
|
|
8
|
-
const testId = useTestId('loading');
|
|
8
|
+
const testId = useTestId('start' === position ? 'loading-start' : 'loading');
|
|
9
9
|
const columns = table.getVisibleLeafColumns();
|
|
10
10
|
return /*#__PURE__*/ jsx(Fragment, {
|
|
11
11
|
children: Array.from({
|
|
12
|
-
length: skeletonCount
|
|
12
|
+
length: count ?? skeletonCount
|
|
13
13
|
}, (_, rowIdx)=>{
|
|
14
14
|
const key = `skeleton-${rowIdx}`;
|
|
15
15
|
return /*#__PURE__*/ jsx(Tr, {
|
|
16
16
|
"data-testid": 0 === rowIdx ? testId : void 0,
|
|
17
|
+
"data-loading-position": position,
|
|
17
18
|
children: columns.map((column)=>/*#__PURE__*/ jsx(Td, {
|
|
18
19
|
className: "px-16 py-8 border-b border-r border-border-primary-light",
|
|
19
20
|
style: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { useInfiniteScroll } from './infiniteScroll';
|
|
2
2
|
export { useHorizontalScrollState } from './useHorizontalScrollState';
|
|
3
3
|
export { useMasterCell } from './useMasterCell';
|
|
4
4
|
export { useTableState } from './useTableState';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useInfiniteScroll } from "./infiniteScroll/index.js";
|
|
2
2
|
import { useHorizontalScrollState } from "./useHorizontalScrollState.js";
|
|
3
3
|
import { useMasterCell } from "./useMasterCell.js";
|
|
4
4
|
import { useTableState } from "./useTableState.js";
|
|
5
|
-
export {
|
|
5
|
+
export { useHorizontalScrollState, useInfiniteScroll, useMasterCell, useTableState };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { useInfiniteScroll } from './useInfiniteScroll';
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import type { Table } from '@tanstack/react-table';
|
|
3
|
+
import type { TableVirtualizerInstance } from '../../TableContext/types';
|
|
4
|
+
interface UseInfiniteScrollOptions<T> {
|
|
5
|
+
mode: 'container' | 'window';
|
|
6
|
+
/** Scroll element ref — required for `container` mode */
|
|
7
|
+
scrollRef?: RefObject<HTMLElement | null>;
|
|
8
|
+
table: Table<T>;
|
|
9
|
+
virtualizerRef: RefObject<TableVirtualizerInstance | null>;
|
|
10
|
+
tbodyRef?: RefObject<HTMLTableSectionElement | null>;
|
|
11
|
+
onStartReached?: () => void;
|
|
12
|
+
onStartReachedThreshold?: number;
|
|
13
|
+
onEndReached?: () => void;
|
|
14
|
+
onEndReachedThreshold?: number;
|
|
15
|
+
initialScrollToRowId?: string;
|
|
16
|
+
}
|
|
17
|
+
/** Single entry point for bidirectional infinite scroll behavior. */
|
|
18
|
+
export declare const useInfiniteScroll: <T>({ mode, scrollRef, table, virtualizerRef, tbodyRef, onStartReached, onStartReachedThreshold, onEndReached, onEndReachedThreshold, initialScrollToRowId, }: UseInfiniteScrollOptions<T>) => void;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { TABLE_END_REACHED_THRESHOLD, TABLE_START_REACHED_THRESHOLD } from "../../lib/index.js";
|
|
2
|
+
import { useInitialAnchor } from "./useInitialAnchor.js";
|
|
3
|
+
import { usePrependScrollAnchor } from "./usePrependScrollAnchor.js";
|
|
4
|
+
import { useScrollEdge } from "./useScrollEdge.js";
|
|
5
|
+
const useInfiniteScroll = ({ mode, scrollRef, table, virtualizerRef, tbodyRef, onStartReached, onStartReachedThreshold, onEndReached, onEndReachedThreshold, initialScrollToRowId })=>{
|
|
6
|
+
const rows = table.getRowModel().rows;
|
|
7
|
+
const ready = useInitialAnchor({
|
|
8
|
+
initialScrollToRowId,
|
|
9
|
+
rows,
|
|
10
|
+
virtualizerRef
|
|
11
|
+
});
|
|
12
|
+
usePrependScrollAnchor({
|
|
13
|
+
mode,
|
|
14
|
+
scrollRef,
|
|
15
|
+
rows,
|
|
16
|
+
virtualizerRef,
|
|
17
|
+
tbodyRef
|
|
18
|
+
});
|
|
19
|
+
useScrollEdge({
|
|
20
|
+
edge: 'start',
|
|
21
|
+
mode,
|
|
22
|
+
scrollRef,
|
|
23
|
+
onReached: onStartReached,
|
|
24
|
+
threshold: onStartReachedThreshold ?? TABLE_START_REACHED_THRESHOLD,
|
|
25
|
+
enabled: ready
|
|
26
|
+
});
|
|
27
|
+
useScrollEdge({
|
|
28
|
+
edge: 'end',
|
|
29
|
+
mode,
|
|
30
|
+
scrollRef,
|
|
31
|
+
onReached: onEndReached,
|
|
32
|
+
threshold: onEndReachedThreshold ?? TABLE_END_REACHED_THRESHOLD,
|
|
33
|
+
enabled: ready
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
export { useInfiniteScroll };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { TableVirtualizerInstance } from '../../TableContext/types';
|
|
3
|
+
interface UseInitialAnchorOptions {
|
|
4
|
+
initialScrollToRowId?: string;
|
|
5
|
+
rows: {
|
|
6
|
+
id: string;
|
|
7
|
+
}[];
|
|
8
|
+
virtualizerRef: RefObject<TableVirtualizerInstance | null>;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Scrolls to the anchor row once on mount and returns `ready`, which gates the
|
|
12
|
+
* edge detectors until the initial scroll has settled. Without this, a table
|
|
13
|
+
* mounted at scrollTop 0 would fire `onStartReached` immediately.
|
|
14
|
+
*/
|
|
15
|
+
export declare const useInitialAnchor: ({ initialScrollToRowId, rows, virtualizerRef, }: UseInitialAnchorOptions) => boolean;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from "react";
|
|
2
|
+
const useInitialAnchor = ({ initialScrollToRowId, rows, virtualizerRef })=>{
|
|
3
|
+
const [ready, setReady] = useState(!initialScrollToRowId);
|
|
4
|
+
const doneRef = useRef(false);
|
|
5
|
+
useEffect(()=>{
|
|
6
|
+
if (doneRef.current || !initialScrollToRowId) return;
|
|
7
|
+
const virtualizer = virtualizerRef.current;
|
|
8
|
+
if (!virtualizer) {
|
|
9
|
+
doneRef.current = true;
|
|
10
|
+
setReady(true);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const index = rows.findIndex((r)=>r.id === initialScrollToRowId);
|
|
14
|
+
if (index < 0) return;
|
|
15
|
+
virtualizer.scrollToIndex(index, {
|
|
16
|
+
align: 'center'
|
|
17
|
+
});
|
|
18
|
+
doneRef.current = true;
|
|
19
|
+
const raf = requestAnimationFrame(()=>setReady(true));
|
|
20
|
+
return ()=>cancelAnimationFrame(raf);
|
|
21
|
+
}, [
|
|
22
|
+
initialScrollToRowId,
|
|
23
|
+
rows,
|
|
24
|
+
virtualizerRef
|
|
25
|
+
]);
|
|
26
|
+
return ready;
|
|
27
|
+
};
|
|
28
|
+
export { useInitialAnchor };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
import type { TableVirtualizerInstance } from '../../TableContext/types';
|
|
3
|
+
interface UsePrependScrollAnchorOptions {
|
|
4
|
+
mode: 'container' | 'window';
|
|
5
|
+
scrollRef?: RefObject<HTMLElement | null>;
|
|
6
|
+
rows: {
|
|
7
|
+
id: string;
|
|
8
|
+
}[];
|
|
9
|
+
/** Preferred delta source: virtual-list offsets are immune to unrelated layout growth. */
|
|
10
|
+
virtualizerRef?: RefObject<TableVirtualizerInstance | null>;
|
|
11
|
+
/** Scopes the start-loader measurement to this table's body. */
|
|
12
|
+
tbodyRef?: RefObject<HTMLTableSectionElement | null>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Keeps the viewport stable across a prepend, in a layout effect (pre-paint, no
|
|
16
|
+
* flicker). Delta = how far the previously-first row moved down the virtual list
|
|
17
|
+
* (`start` offset) — immune to unrelated page-height changes, unlike a
|
|
18
|
+
* scrollHeight diff, which matters in `window` mode. Falls back to scrollHeight
|
|
19
|
+
* when there's no virtualizer.
|
|
20
|
+
*
|
|
21
|
+
* Start-edge skeletons (`isLoadingPrevious`) sit above the rows but outside the
|
|
22
|
+
* virtual list, so the offset delta can't see them; their height is tracked per
|
|
23
|
+
* commit (hence no dep array) and its collapse joins the delta when the prepend
|
|
24
|
+
* lands in the same commit.
|
|
25
|
+
*/
|
|
26
|
+
export declare const usePrependScrollAnchor: ({ mode, scrollRef, rows, virtualizerRef, tbodyRef, }: UsePrependScrollAnchorOptions) => void;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "react";
|
|
2
|
+
import { detectDataChange } from "../../lib/index.js";
|
|
3
|
+
const usePrependScrollAnchor = ({ mode, scrollRef, rows, virtualizerRef, tbodyRef })=>{
|
|
4
|
+
const prevFirstRowIdRef = useRef(void 0);
|
|
5
|
+
const prevFirstRowStartRef = useRef(null);
|
|
6
|
+
const prevScrollHeightRef = useRef(null);
|
|
7
|
+
const prevLoaderRef = useRef({
|
|
8
|
+
count: 0,
|
|
9
|
+
height: 0
|
|
10
|
+
});
|
|
11
|
+
const seededRef = useRef(false);
|
|
12
|
+
useLayoutEffect(()=>{
|
|
13
|
+
const getScrollHeight = ()=>'window' === mode ? document.documentElement.scrollHeight : scrollRef?.current?.scrollHeight ?? 0;
|
|
14
|
+
const getStartLoaderHeight = ()=>{
|
|
15
|
+
const loaderRows = tbodyRef?.current?.querySelectorAll('tr[data-loading-position="start"]');
|
|
16
|
+
const count = loaderRows?.length ?? 0;
|
|
17
|
+
if (count !== prevLoaderRef.current.count) {
|
|
18
|
+
let height = 0;
|
|
19
|
+
loaderRows?.forEach((row)=>{
|
|
20
|
+
height += row.offsetHeight;
|
|
21
|
+
});
|
|
22
|
+
prevLoaderRef.current = {
|
|
23
|
+
count,
|
|
24
|
+
height
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return prevLoaderRef.current.height;
|
|
28
|
+
};
|
|
29
|
+
const getFirstRowStart = ()=>{
|
|
30
|
+
const start = virtualizerRef?.current?.measurementsCache[0]?.start;
|
|
31
|
+
return 'number' == typeof start ? start : null;
|
|
32
|
+
};
|
|
33
|
+
const trackScrollHeight = !virtualizerRef?.current;
|
|
34
|
+
if (!seededRef.current) {
|
|
35
|
+
seededRef.current = true;
|
|
36
|
+
prevScrollHeightRef.current = trackScrollHeight ? getScrollHeight() : null;
|
|
37
|
+
prevFirstRowStartRef.current = getFirstRowStart();
|
|
38
|
+
prevFirstRowIdRef.current = rows[0]?.id;
|
|
39
|
+
getStartLoaderHeight();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const prevLoaderHeight = prevLoaderRef.current.height;
|
|
43
|
+
if ('prepend' === detectDataChange(prevFirstRowIdRef.current, rows)) {
|
|
44
|
+
const prevId = prevFirstRowIdRef.current;
|
|
45
|
+
const index = rows.findIndex((row)=>row.id === prevId);
|
|
46
|
+
const newStart = virtualizerRef?.current?.measurementsCache[index]?.start;
|
|
47
|
+
const useVirtualDelta = index >= 0 && 'number' == typeof newStart && null !== prevFirstRowStartRef.current;
|
|
48
|
+
if (useVirtualDelta) {
|
|
49
|
+
const rowDelta = newStart - prevFirstRowStartRef.current;
|
|
50
|
+
const loaderDelta = getStartLoaderHeight() - prevLoaderHeight;
|
|
51
|
+
const delta = rowDelta + loaderDelta;
|
|
52
|
+
if (0 !== delta) {
|
|
53
|
+
if ('window' === mode) window.scrollBy(0, delta);
|
|
54
|
+
else if (scrollRef?.current) scrollRef.current.scrollTop += delta;
|
|
55
|
+
}
|
|
56
|
+
} else if (null !== prevScrollHeightRef.current) {
|
|
57
|
+
const delta = getScrollHeight() - prevScrollHeightRef.current;
|
|
58
|
+
if (delta > 0) {
|
|
59
|
+
if ('window' === mode) window.scrollBy(0, delta);
|
|
60
|
+
else if (scrollRef?.current) scrollRef.current.scrollTop += delta;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
prevFirstRowIdRef.current = rows[0]?.id;
|
|
65
|
+
prevFirstRowStartRef.current = getFirstRowStart();
|
|
66
|
+
prevScrollHeightRef.current = trackScrollHeight ? getScrollHeight() : null;
|
|
67
|
+
getStartLoaderHeight();
|
|
68
|
+
});
|
|
69
|
+
};
|
|
70
|
+
export { usePrependScrollAnchor };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type RefObject } from 'react';
|
|
2
|
+
type ScrollMode = 'container' | 'window';
|
|
3
|
+
type ScrollEdge = 'start' | 'end';
|
|
4
|
+
interface UseScrollEdgeOptions {
|
|
5
|
+
edge: ScrollEdge;
|
|
6
|
+
mode: ScrollMode;
|
|
7
|
+
/** Scroll element ref — required for `container` mode */
|
|
8
|
+
scrollRef?: RefObject<HTMLElement | null>;
|
|
9
|
+
onReached?: () => void;
|
|
10
|
+
threshold: number;
|
|
11
|
+
/** When false, suppresses firing (e.g. while the initial anchor scroll settles) */
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Fires `onReached` once when the user scrolls within `threshold` px of the
|
|
16
|
+
* given edge. Re-arms after scrolling back past the threshold. A cooldown
|
|
17
|
+
* guard prevents rapid re-fires when prepended/appended rows grow the content.
|
|
18
|
+
*/
|
|
19
|
+
export declare const useScrollEdge: ({ edge, mode, scrollRef, onReached, threshold, enabled, }: UseScrollEdgeOptions) => void;
|
|
20
|
+
export {};
|