@wallarm-org/design-system 0.51.2 → 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/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/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 -2
- package/package.json +1 -1
- package/dist/components/Table/hooks/useEndReached.d.ts +0 -19
|
@@ -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 {};
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
|
-
import {
|
|
3
|
-
const
|
|
4
|
-
const useEndReached = ({ mode, scrollRef, onEndReached, threshold = TABLE_END_REACHED_THRESHOLD })=>{
|
|
2
|
+
import { SCROLL_EDGE_COOLDOWN_MS } from "../../lib/index.js";
|
|
3
|
+
const useScrollEdge = ({ edge, mode, scrollRef, onReached, threshold, enabled = true })=>{
|
|
5
4
|
const firedRef = useRef(false);
|
|
6
5
|
const lastFiredAtRef = useRef(0);
|
|
7
|
-
const
|
|
6
|
+
const onReachedRef = useRef(onReached);
|
|
8
7
|
useEffect(()=>{
|
|
9
|
-
|
|
8
|
+
onReachedRef.current = onReached;
|
|
9
|
+
});
|
|
10
|
+
const enabledRef = useRef(enabled);
|
|
11
|
+
useEffect(()=>{
|
|
12
|
+
enabledRef.current = enabled;
|
|
10
13
|
});
|
|
11
14
|
useEffect(()=>{
|
|
12
15
|
const check = ()=>{
|
|
13
|
-
const callback =
|
|
14
|
-
if (!callback) return;
|
|
16
|
+
const callback = onReachedRef.current;
|
|
17
|
+
if (!callback || !enabledRef.current) return;
|
|
15
18
|
let scrollTop;
|
|
16
19
|
let clientHeight;
|
|
17
20
|
let scrollHeight;
|
|
@@ -26,10 +29,10 @@ const useEndReached = ({ mode, scrollRef, onEndReached, threshold = TABLE_END_RE
|
|
|
26
29
|
clientHeight = el.clientHeight;
|
|
27
30
|
scrollHeight = el.scrollHeight;
|
|
28
31
|
}
|
|
29
|
-
const
|
|
30
|
-
if (
|
|
32
|
+
const distance = 'start' === edge ? scrollTop : scrollHeight - scrollTop - clientHeight;
|
|
33
|
+
if (distance <= threshold) {
|
|
31
34
|
const now = Date.now();
|
|
32
|
-
if (!firedRef.current && now - lastFiredAtRef.current >=
|
|
35
|
+
if (!firedRef.current && now - lastFiredAtRef.current >= SCROLL_EDGE_COOLDOWN_MS) {
|
|
33
36
|
firedRef.current = true;
|
|
34
37
|
lastFiredAtRef.current = now;
|
|
35
38
|
callback();
|
|
@@ -46,9 +49,10 @@ const useEndReached = ({ mode, scrollRef, onEndReached, threshold = TABLE_END_RE
|
|
|
46
49
|
target.removeEventListener('scroll', check);
|
|
47
50
|
};
|
|
48
51
|
}, [
|
|
52
|
+
edge,
|
|
49
53
|
mode,
|
|
50
54
|
scrollRef,
|
|
51
55
|
threshold
|
|
52
56
|
]);
|
|
53
57
|
};
|
|
54
|
-
export {
|
|
58
|
+
export { useScrollEdge };
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export declare const TABLE_SKELETON_ROWS = 12;
|
|
2
|
+
/** Short on purpose: the prepend loader's height pushes visible rows down. */
|
|
3
|
+
export declare const TABLE_PREPEND_SKELETON_ROWS = 6;
|
|
2
4
|
export declare const TABLE_VIRTUALIZATION_OVERSCAN = 6;
|
|
3
5
|
export declare const TABLE_MIN_COLUMN_WIDTH = 96;
|
|
4
6
|
export declare const TABLE_SELECT_COLUMN_ID = "_selection";
|
|
@@ -6,6 +8,9 @@ export declare const TABLE_SELECT_COLUMN_WIDTH = 33;
|
|
|
6
8
|
export declare const TABLE_EXPAND_COLUMN_ID = "_expand";
|
|
7
9
|
export declare const TABLE_EXPAND_COLUMN_WIDTH = 33;
|
|
8
10
|
export declare const TABLE_END_REACHED_THRESHOLD = 200;
|
|
11
|
+
export declare const TABLE_START_REACHED_THRESHOLD = 200;
|
|
12
|
+
/** Minimum time (ms) between successive edge-reached callbacks. */
|
|
13
|
+
export declare const SCROLL_EDGE_COOLDOWN_MS = 200;
|
|
9
14
|
/** Resolve text-align class from column meta */
|
|
10
15
|
export declare const getAlignClass: (meta?: {
|
|
11
16
|
align?: string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const TABLE_SKELETON_ROWS = 12;
|
|
2
|
+
const TABLE_PREPEND_SKELETON_ROWS = 6;
|
|
2
3
|
const TABLE_VIRTUALIZATION_OVERSCAN = 6;
|
|
3
4
|
const TABLE_MIN_COLUMN_WIDTH = 96;
|
|
4
5
|
const TABLE_SELECT_COLUMN_ID = '_selection';
|
|
@@ -6,6 +7,8 @@ const TABLE_SELECT_COLUMN_WIDTH = 33;
|
|
|
6
7
|
const TABLE_EXPAND_COLUMN_ID = '_expand';
|
|
7
8
|
const TABLE_EXPAND_COLUMN_WIDTH = 33;
|
|
8
9
|
const TABLE_END_REACHED_THRESHOLD = 200;
|
|
10
|
+
const TABLE_START_REACHED_THRESHOLD = 200;
|
|
11
|
+
const SCROLL_EDGE_COOLDOWN_MS = 200;
|
|
9
12
|
const RIGHT_ALIGNED_SORT_TYPES = new Set([
|
|
10
13
|
'number',
|
|
11
14
|
'score',
|
|
@@ -56,4 +59,4 @@ const SORT_LABELS = {
|
|
|
56
59
|
'Smallest on top'
|
|
57
60
|
]
|
|
58
61
|
};
|
|
59
|
-
export { SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_VIRTUALIZATION_OVERSCAN, getAlignClass, getExpandBorderClass };
|
|
62
|
+
export { SCROLL_EDGE_COOLDOWN_MS, SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_PREPEND_SKELETON_ROWS, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_START_REACHED_THRESHOLD, TABLE_VIRTUALIZATION_OVERSCAN, getAlignClass, getExpandBorderClass };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const detectDataChange = (prevFirstRowId, rows)=>{
|
|
2
|
+
const currentFirstId = rows[0]?.id;
|
|
3
|
+
if (void 0 === prevFirstRowId) return 'none';
|
|
4
|
+
if (currentFirstId === prevFirstRowId) return 'none';
|
|
5
|
+
return rows.some((r)=>r.id === prevFirstRowId) ? 'prepend' : 'replace';
|
|
6
|
+
};
|
|
7
|
+
export { detectDataChange };
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
export { getAlignClass, getExpandBorderClass, SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_VIRTUALIZATION_OVERSCAN, } from './constants';
|
|
1
|
+
export { getAlignClass, getExpandBorderClass, SCROLL_EDGE_COOLDOWN_MS, SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_PREPEND_SKELETON_ROWS, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_START_REACHED_THRESHOLD, TABLE_VIRTUALIZATION_OVERSCAN, } from './constants';
|
|
2
2
|
export { createExpandColumn } from './createExpandColumn';
|
|
3
3
|
export { createSelectionColumn } from './createSelectionColumn';
|
|
4
4
|
export { createTableColumnHelper } from './createTableColumnHelper';
|
|
5
|
+
export { detectDataChange } from './detectDataChange';
|
|
5
6
|
export { getDndStyles } from './getDndStyles';
|
|
6
7
|
export { getPinningStyles } from './getPinningStyles';
|
|
8
|
+
export { getRowKey } from './getRowKey';
|
|
7
9
|
export { isLastPinnedLeft } from './isLastPinnedLeft';
|
|
8
10
|
export { useColumnDnd } from './useColumnDnd';
|
|
9
11
|
export { useContainerWidth } from './useContainerWidth';
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
-
import { SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_VIRTUALIZATION_OVERSCAN, getAlignClass, getExpandBorderClass } from "./constants.js";
|
|
1
|
+
import { SCROLL_EDGE_COOLDOWN_MS, SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_PREPEND_SKELETON_ROWS, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_START_REACHED_THRESHOLD, TABLE_VIRTUALIZATION_OVERSCAN, getAlignClass, getExpandBorderClass } from "./constants.js";
|
|
2
2
|
import { createExpandColumn } from "./createExpandColumn.js";
|
|
3
3
|
import { createSelectionColumn } from "./createSelectionColumn.js";
|
|
4
4
|
import { createTableColumnHelper } from "./createTableColumnHelper.js";
|
|
5
|
+
import { detectDataChange } from "./detectDataChange.js";
|
|
5
6
|
import { getDndStyles } from "./getDndStyles.js";
|
|
6
7
|
import { getPinningStyles } from "./getPinningStyles.js";
|
|
8
|
+
import { getRowKey } from "./getRowKey.js";
|
|
7
9
|
import { isLastPinnedLeft } from "./isLastPinnedLeft.js";
|
|
8
10
|
import { useColumnDnd } from "./useColumnDnd.js";
|
|
9
11
|
import { useContainerWidth } from "./useContainerWidth.js";
|
|
10
|
-
export { SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_VIRTUALIZATION_OVERSCAN, createExpandColumn, createSelectionColumn, createTableColumnHelper, getAlignClass, getDndStyles, getExpandBorderClass, getPinningStyles, isLastPinnedLeft, useColumnDnd, useContainerWidth };
|
|
12
|
+
export { SCROLL_EDGE_COOLDOWN_MS, SORT_LABELS, TABLE_END_REACHED_THRESHOLD, TABLE_EXPAND_COLUMN_ID, TABLE_EXPAND_COLUMN_WIDTH, TABLE_MIN_COLUMN_WIDTH, TABLE_PREPEND_SKELETON_ROWS, TABLE_SELECT_COLUMN_ID, TABLE_SELECT_COLUMN_WIDTH, TABLE_SKELETON_ROWS, TABLE_START_REACHED_THRESHOLD, TABLE_VIRTUALIZATION_OVERSCAN, createExpandColumn, createSelectionColumn, createTableColumnHelper, detectDataChange, getAlignClass, getDndStyles, getExpandBorderClass, getPinningStyles, getRowKey, isLastPinnedLeft, useColumnDnd, useContainerWidth };
|
|
@@ -59,3 +59,17 @@ export declare const useInfiniteData: () => {
|
|
|
59
59
|
totalItems: number;
|
|
60
60
|
fetchNextPage: () => void;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Mock for bidirectional infinite scroll: opens a window around an anchor row
|
|
64
|
+
* and exposes cursor-style fetchers for both directions.
|
|
65
|
+
*/
|
|
66
|
+
export declare const useBidirectionalData: () => {
|
|
67
|
+
data: SecurityEvent[];
|
|
68
|
+
anchorId: string | undefined;
|
|
69
|
+
isFetchingPrev: boolean;
|
|
70
|
+
isFetchingNext: boolean;
|
|
71
|
+
hasPrev: boolean;
|
|
72
|
+
hasNext: boolean;
|
|
73
|
+
fetchPrevPage: () => void;
|
|
74
|
+
fetchNextPage: () => void;
|
|
75
|
+
};
|
|
@@ -953,6 +953,9 @@ const fullFeaturedColumns = headerColumns.map((col)=>{
|
|
|
953
953
|
});
|
|
954
954
|
const INFINITE_PAGE_SIZE = 50;
|
|
955
955
|
const INFINITE_MAX_ITEMS = 500;
|
|
956
|
+
const BIDIRECTIONAL_TOTAL = 500;
|
|
957
|
+
const BIDIRECTIONAL_PAGE_SIZE = 50;
|
|
958
|
+
const BIDIRECTIONAL_INITIAL_ANCHOR_INDEX = 250;
|
|
956
959
|
const useInfiniteData = ()=>{
|
|
957
960
|
const allData = useMemo(()=>createLargeSecurityEvents(INFINITE_MAX_ITEMS), []);
|
|
958
961
|
const [data, setData] = useState(()=>allData.slice(0, INFINITE_PAGE_SIZE));
|
|
@@ -978,4 +981,61 @@ const useInfiniteData = ()=>{
|
|
|
978
981
|
fetchNextPage
|
|
979
982
|
};
|
|
980
983
|
};
|
|
981
|
-
|
|
984
|
+
const useBidirectionalData = ()=>{
|
|
985
|
+
const allData = useMemo(()=>createLargeSecurityEvents(BIDIRECTIONAL_TOTAL), []);
|
|
986
|
+
const initialStart = Math.max(0, BIDIRECTIONAL_INITIAL_ANCHOR_INDEX - BIDIRECTIONAL_PAGE_SIZE);
|
|
987
|
+
const initialEnd = Math.min(allData.length, BIDIRECTIONAL_INITIAL_ANCHOR_INDEX + BIDIRECTIONAL_PAGE_SIZE);
|
|
988
|
+
const [range, setRange] = useState({
|
|
989
|
+
start: initialStart,
|
|
990
|
+
end: initialEnd
|
|
991
|
+
});
|
|
992
|
+
const [isFetchingPrev, setIsFetchingPrev] = useState(false);
|
|
993
|
+
const [isFetchingNext, setIsFetchingNext] = useState(false);
|
|
994
|
+
const data = useMemo(()=>allData.slice(range.start, range.end), [
|
|
995
|
+
allData,
|
|
996
|
+
range
|
|
997
|
+
]);
|
|
998
|
+
const anchorId = allData[BIDIRECTIONAL_INITIAL_ANCHOR_INDEX]?.id;
|
|
999
|
+
const hasPrev = range.start > 0;
|
|
1000
|
+
const hasNext = range.end < allData.length;
|
|
1001
|
+
const fetchPrevPage = useCallback(()=>{
|
|
1002
|
+
if (isFetchingPrev || range.start <= 0) return;
|
|
1003
|
+
setIsFetchingPrev(true);
|
|
1004
|
+
setTimeout(()=>{
|
|
1005
|
+
setRange((prev)=>({
|
|
1006
|
+
...prev,
|
|
1007
|
+
start: Math.max(0, prev.start - BIDIRECTIONAL_PAGE_SIZE)
|
|
1008
|
+
}));
|
|
1009
|
+
setIsFetchingPrev(false);
|
|
1010
|
+
}, 600);
|
|
1011
|
+
}, [
|
|
1012
|
+
isFetchingPrev,
|
|
1013
|
+
range.start
|
|
1014
|
+
]);
|
|
1015
|
+
const fetchNextPage = useCallback(()=>{
|
|
1016
|
+
if (isFetchingNext || range.end >= allData.length) return;
|
|
1017
|
+
setIsFetchingNext(true);
|
|
1018
|
+
setTimeout(()=>{
|
|
1019
|
+
setRange((prev)=>({
|
|
1020
|
+
...prev,
|
|
1021
|
+
end: Math.min(allData.length, prev.end + BIDIRECTIONAL_PAGE_SIZE)
|
|
1022
|
+
}));
|
|
1023
|
+
setIsFetchingNext(false);
|
|
1024
|
+
}, 600);
|
|
1025
|
+
}, [
|
|
1026
|
+
isFetchingNext,
|
|
1027
|
+
range.end,
|
|
1028
|
+
allData.length
|
|
1029
|
+
]);
|
|
1030
|
+
return {
|
|
1031
|
+
data,
|
|
1032
|
+
anchorId,
|
|
1033
|
+
isFetchingPrev,
|
|
1034
|
+
isFetchingNext,
|
|
1035
|
+
hasPrev,
|
|
1036
|
+
hasNext,
|
|
1037
|
+
fetchPrevPage,
|
|
1038
|
+
fetchNextPage
|
|
1039
|
+
};
|
|
1040
|
+
};
|
|
1041
|
+
export { createLargeGroupedData, createLargeSecurityEvents, fullFeaturedColumns, groupedHeaderData, headerColumnHelper, headerColumnIds, headerColumns, multiplySecurityEvents, renderSecurityPreviewContent, renderSecurityPreviewHeader, securityColumnHelper, securityColumnIds, securityColumns, securityEvents, useBidirectionalData, useInfiniteData };
|
|
@@ -143,6 +143,12 @@ export interface TableProps<T> extends TestableProps {
|
|
|
143
143
|
columns: TableColumnDef<T>[];
|
|
144
144
|
/** Show skeleton rows */
|
|
145
145
|
isLoading?: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* Show skeleton rows above the first row while a previous page is being
|
|
148
|
+
* fetched (bidirectional infinite scroll — the start-edge counterpart of
|
|
149
|
+
* `isLoading`). Pair with `onStartReached`.
|
|
150
|
+
*/
|
|
151
|
+
isLoadingPrevious?: boolean;
|
|
146
152
|
/** Number of skeleton rows to display when loading (default: 6) */
|
|
147
153
|
skeletonCount?: number;
|
|
148
154
|
/** Slot for TableActionBar, TableEmptyState, and other compound components */
|
|
@@ -188,10 +194,20 @@ export interface TableProps<T> extends TestableProps {
|
|
|
188
194
|
virtualized?: TableVirtualized;
|
|
189
195
|
estimateRowHeight?: (index: number) => number;
|
|
190
196
|
overscan?: number;
|
|
191
|
-
/** Callback fired when the user scrolls near the end of the table */
|
|
197
|
+
/** Callback fired when the user scrolls near the end (bottom) of the table */
|
|
192
198
|
onEndReached?: () => void;
|
|
193
199
|
/** Distance from the bottom (in px) to trigger onEndReached (default: 200) */
|
|
194
200
|
onEndReachedThreshold?: number;
|
|
201
|
+
/** Callback fired when the user scrolls near the start (top) of the table */
|
|
202
|
+
onStartReached?: () => void;
|
|
203
|
+
/** Distance from the top (in px) to trigger onStartReached (default: 200) */
|
|
204
|
+
onStartReachedThreshold?: number;
|
|
205
|
+
/**
|
|
206
|
+
* Row id to anchor the initial scroll position to. The table scrolls this row
|
|
207
|
+
* into view on mount and arms the edge detectors only after that initial
|
|
208
|
+
* scroll settles. Use for deep-linking into the middle of a dataset.
|
|
209
|
+
*/
|
|
210
|
+
initialScrollToRowId?: string;
|
|
195
211
|
/** Callback fired when the master cell is clicked. Receives the row ID. */
|
|
196
212
|
onMasterCellClick?: (rowId: string) => void;
|
|
197
213
|
/** ID of the currently active (highlighted) row, or null. Controls row highlighting via data-preview-active attribute. */
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { type ReactElement, type RefObject } from 'react';
|
|
2
2
|
export interface UseOverflowItemsOptions<T> {
|
|
3
|
+
/**
|
|
4
|
+
* Invariant: a given `items` array identity must always render to the same
|
|
5
|
+
* widths. Measurements are cached by array identity regardless of the
|
|
6
|
+
* renderers/reserveSpace, so changing those to alter widths needs a fresh
|
|
7
|
+
* array.
|
|
8
|
+
*/
|
|
3
9
|
items: T[];
|
|
4
10
|
renderItem: (item: T) => ReactElement;
|
|
5
11
|
renderMeasurementItem?: (item: T) => ReactElement;
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import { calculateVisibleCount } from "./useOverflowItems.helpers.js";
|
|
4
|
+
import { scheduleOverflowMeasurement } from "./useOverflowItems.scheduler.js";
|
|
5
|
+
const crossMountCache = new WeakMap();
|
|
4
6
|
function useOverflowItems({ items, renderItem, renderMeasurementItem, overflowRenderer, reserveSpace = 60 }) {
|
|
5
7
|
const containerRef = useRef(null);
|
|
6
|
-
const [visibleCount, setVisibleCount] = useState(
|
|
8
|
+
const [visibleCount, setVisibleCount] = useState(()=>{
|
|
9
|
+
const cached = items.length > 0 ? crossMountCache.get(items) : void 0;
|
|
10
|
+
if (cached) return Math.min(cached.lastCount, items.length);
|
|
11
|
+
return Math.min(items.length, 1);
|
|
12
|
+
});
|
|
13
|
+
const itemsRef = useRef(items);
|
|
14
|
+
itemsRef.current = items;
|
|
7
15
|
const measurementRefs = useRef([]);
|
|
8
16
|
const indicatorRef = useRef(null);
|
|
17
|
+
const measurementLayerRef = useRef(null);
|
|
9
18
|
const cacheRef = useRef({
|
|
10
19
|
widths: [],
|
|
11
20
|
gap: 0,
|
|
12
21
|
indicatorWidth: reserveSpace
|
|
13
22
|
});
|
|
14
|
-
const recompute = useCallback(()=>{
|
|
23
|
+
const recompute = useCallback((availableWidth)=>{
|
|
15
24
|
const container = containerRef.current;
|
|
16
25
|
if (!container) return;
|
|
17
26
|
const { widths, gap, indicatorWidth } = cacheRef.current;
|
|
@@ -19,13 +28,14 @@ function useOverflowItems({ items, renderItem, renderMeasurementItem, overflowRe
|
|
|
19
28
|
const next = calculateVisibleCount({
|
|
20
29
|
itemWidths: widths,
|
|
21
30
|
gap,
|
|
22
|
-
availableWidth: container.offsetWidth,
|
|
31
|
+
availableWidth: availableWidth ?? container.offsetWidth,
|
|
23
32
|
indicatorWidth
|
|
24
33
|
});
|
|
34
|
+
const entry = crossMountCache.get(itemsRef.current);
|
|
35
|
+
if (entry) entry.lastCount = next;
|
|
25
36
|
setVisibleCount((prev)=>prev === next ? prev : next);
|
|
26
37
|
}, []);
|
|
27
38
|
useLayoutEffect(()=>{
|
|
28
|
-
const container = containerRef.current;
|
|
29
39
|
if (0 === items.length) {
|
|
30
40
|
cacheRef.current = {
|
|
31
41
|
widths: [],
|
|
@@ -35,15 +45,34 @@ function useOverflowItems({ items, renderItem, renderMeasurementItem, overflowRe
|
|
|
35
45
|
setVisibleCount(0);
|
|
36
46
|
return;
|
|
37
47
|
}
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
46
|
-
|
|
48
|
+
const cached = crossMountCache.get(items);
|
|
49
|
+
if (cached) {
|
|
50
|
+
cacheRef.current = cached;
|
|
51
|
+
return scheduleOverflowMeasurement(()=>{
|
|
52
|
+
const availableWidth = containerRef.current?.offsetWidth ?? 0;
|
|
53
|
+
return ()=>recompute(availableWidth);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
measurementLayerRef.current?.style.removeProperty('display');
|
|
57
|
+
return scheduleOverflowMeasurement(()=>{
|
|
58
|
+
const container = containerRef.current;
|
|
59
|
+
const gap = container ? Number.parseFloat(getComputedStyle(container).gap || '0') || 0 : 0;
|
|
60
|
+
const widths = measurementRefs.current.slice(0, items.length).map((ref)=>ref?.offsetWidth ?? 0);
|
|
61
|
+
const indicatorWidth = indicatorRef.current?.offsetWidth || reserveSpace;
|
|
62
|
+
const availableWidth = container?.offsetWidth ?? 0;
|
|
63
|
+
return ()=>{
|
|
64
|
+
const entry = {
|
|
65
|
+
widths,
|
|
66
|
+
gap,
|
|
67
|
+
indicatorWidth,
|
|
68
|
+
lastCount: items.length
|
|
69
|
+
};
|
|
70
|
+
cacheRef.current = entry;
|
|
71
|
+
crossMountCache.set(items, entry);
|
|
72
|
+
recompute(availableWidth);
|
|
73
|
+
measurementLayerRef.current?.style.setProperty('display', 'none');
|
|
74
|
+
};
|
|
75
|
+
});
|
|
47
76
|
}, [
|
|
48
77
|
items,
|
|
49
78
|
renderItem,
|
|
@@ -75,9 +104,10 @@ function useOverflowItems({ items, renderItem, renderMeasurementItem, overflowRe
|
|
|
75
104
|
const hiddenItems = items.slice(visibleCount);
|
|
76
105
|
const hiddenCount = hiddenItems.length;
|
|
77
106
|
const MeasurementContainer = useCallback(()=>{
|
|
78
|
-
if (0 === items.length) return null;
|
|
107
|
+
if (0 === items.length || crossMountCache.has(items)) return null;
|
|
79
108
|
const renderMeasure = renderMeasurementItem || renderItem;
|
|
80
109
|
return /*#__PURE__*/ jsxs("div", {
|
|
110
|
+
ref: measurementLayerRef,
|
|
81
111
|
className: "absolute invisible pointer-events-none",
|
|
82
112
|
"aria-hidden": "true",
|
|
83
113
|
children: [
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared read/write batch for overflow measurements. Many instances mount in
|
|
3
|
+
* one commit (a virtualized row chunk × several overflow cells); measuring in
|
|
4
|
+
* each layout effect reflows per instance, since its setState flushes before
|
|
5
|
+
* the next reads `offsetWidth`. One pre-paint microtask runs all reads against
|
|
6
|
+
* clean layout, then all writes — React batches them, no interleaving.
|
|
7
|
+
*/
|
|
8
|
+
type WritePhase = () => void;
|
|
9
|
+
type ReadPhase = () => WritePhase;
|
|
10
|
+
/**
|
|
11
|
+
* Queue a measurement for the next pre-paint flush. Returns a cancel
|
|
12
|
+
* function — call it on effect cleanup so unmounted or re-rendered
|
|
13
|
+
* instances never measure stale refs.
|
|
14
|
+
*/
|
|
15
|
+
export declare const scheduleOverflowMeasurement: (read: ReadPhase) => (() => void);
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const queue = new Set();
|
|
2
|
+
let flushScheduled = false;
|
|
3
|
+
const flush = ()=>{
|
|
4
|
+
flushScheduled = false;
|
|
5
|
+
const reads = Array.from(queue);
|
|
6
|
+
queue.clear();
|
|
7
|
+
const writes = [];
|
|
8
|
+
for (const read of reads)try {
|
|
9
|
+
writes.push(read());
|
|
10
|
+
} catch {}
|
|
11
|
+
for (const write of writes)try {
|
|
12
|
+
write();
|
|
13
|
+
} catch {}
|
|
14
|
+
};
|
|
15
|
+
const scheduleOverflowMeasurement = (read)=>{
|
|
16
|
+
queue.add(read);
|
|
17
|
+
if (!flushScheduled) {
|
|
18
|
+
flushScheduled = true;
|
|
19
|
+
queueMicrotask(flush);
|
|
20
|
+
}
|
|
21
|
+
return ()=>{
|
|
22
|
+
queue.delete(read);
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export { scheduleOverflowMeasurement };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.51.
|
|
3
|
-
"generatedAt": "2026-06-
|
|
2
|
+
"version": "0.51.2",
|
|
3
|
+
"generatedAt": "2026-06-03T10:23:04.183Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Accordion",
|
|
@@ -51518,6 +51518,12 @@
|
|
|
51518
51518
|
"description": "Show skeleton rows",
|
|
51519
51519
|
"defaultValue": "false"
|
|
51520
51520
|
},
|
|
51521
|
+
{
|
|
51522
|
+
"name": "isLoadingPrevious",
|
|
51523
|
+
"type": "boolean | undefined",
|
|
51524
|
+
"required": false,
|
|
51525
|
+
"description": "Show skeleton rows above the first row while a previous page is being\nfetched (bidirectional infinite scroll — the start-edge counterpart of\n`isLoading`). Pair with `onStartReached`."
|
|
51526
|
+
},
|
|
51521
51527
|
{
|
|
51522
51528
|
"name": "skeletonCount",
|
|
51523
51529
|
"type": "number | undefined",
|
|
@@ -51618,6 +51624,12 @@
|
|
|
51618
51624
|
"type": "number | undefined",
|
|
51619
51625
|
"required": false
|
|
51620
51626
|
},
|
|
51627
|
+
{
|
|
51628
|
+
"name": "initialScrollToRowId",
|
|
51629
|
+
"type": "string | undefined",
|
|
51630
|
+
"required": false,
|
|
51631
|
+
"description": "Row id to anchor the initial scroll position to. The table scrolls this row\ninto view on mount and arms the edge detectors only after that initial\nscroll settles. Use for deep-linking into the middle of a dataset."
|
|
51632
|
+
},
|
|
51621
51633
|
{
|
|
51622
51634
|
"name": "activeRowId",
|
|
51623
51635
|
"type": "string | null | undefined",
|
|
@@ -51773,6 +51785,14 @@
|
|
|
51773
51785
|
"name": "InfiniteScrollWindow",
|
|
51774
51786
|
"code": "() => {\n const { data, isFetching, hasMore, totalItems, fetchNextPage } = useInfiniteData();\n\n return (\n <VStack gap={8}>\n <Text size='sm' color='secondary'>\n Loaded {data.length} of {totalItems} rows {isFetching && '— loading...'}\n {!hasMore && ' — all loaded'}\n </Text>\n <Table\n data={data}\n columns={securityColumns}\n getRowId={row => row.id}\n virtualized='window'\n isLoading={isFetching}\n onEndReached={fetchNextPage}\n onEndReachedThreshold={300}\n />\n </VStack>\n );\n}"
|
|
51775
51787
|
},
|
|
51788
|
+
{
|
|
51789
|
+
"name": "BidirectionalInfiniteScroll",
|
|
51790
|
+
"code": "() => {\n const {\n data,\n anchorId,\n isFetchingPrev,\n isFetchingNext,\n hasPrev,\n hasNext,\n fetchPrevPage,\n fetchNextPage,\n } = useBidirectionalData();\n\n return (\n <VStack gap={8}>\n <Text size='sm' color='secondary'>\n Window of {data.length} rows around the anchor — scroll up for top skeletons, down for\n bottom ones{(isFetchingPrev || isFetchingNext) && ' — loading...'}\n {!hasPrev && ' — top reached'}\n {!hasNext && ' — bottom reached'}\n </Text>\n <Table\n className='h-500'\n data={data}\n columns={securityColumns}\n getRowId={row => row.id}\n virtualized='container'\n isLoading={isFetchingNext}\n isLoadingPrevious={isFetchingPrev}\n initialScrollToRowId={anchorId}\n onStartReached={fetchPrevPage}\n onStartReachedThreshold={200}\n onEndReached={fetchNextPage}\n onEndReachedThreshold={200}\n />\n </VStack>\n );\n}"
|
|
51791
|
+
},
|
|
51792
|
+
{
|
|
51793
|
+
"name": "BidirectionalInfiniteScrollWindow",
|
|
51794
|
+
"code": "() => {\n const {\n data,\n anchorId,\n isFetchingPrev,\n isFetchingNext,\n hasPrev,\n hasNext,\n fetchPrevPage,\n fetchNextPage,\n } = useBidirectionalData();\n\n return (\n <VStack gap={8}>\n <Text size='sm' color='secondary'>\n Window of {data.length} rows around the anchor\n {(isFetchingPrev || isFetchingNext) && ' — loading...'}\n {!hasPrev && ' — top reached'}\n {!hasNext && ' — bottom reached'}\n </Text>\n <Table\n data={data}\n columns={securityColumns}\n getRowId={row => row.id}\n virtualized='window'\n isLoading={isFetchingNext}\n isLoadingPrevious={isFetchingPrev}\n initialScrollToRowId={anchorId}\n onStartReached={fetchPrevPage}\n onStartReachedThreshold={200}\n onEndReached={fetchNextPage}\n onEndReachedThreshold={200}\n />\n </VStack>\n );\n}"
|
|
51795
|
+
},
|
|
51776
51796
|
{
|
|
51777
51797
|
"name": "HeaderColumnDescription",
|
|
51778
51798
|
"code": "() => {\n const [sorting, setSorting] = useState<TableSortingState>([]);\n\n const columns = useMemo<TableColumnDef<(typeof securityEvents)[number]>[]>(\n () =>\n securityColumns.map(col => {\n const key = 'accessorKey' in col ? col.accessorKey : undefined;\n if (key === 'objectName') {\n return {\n ...col,\n meta: {\n ...col.meta,\n description: { type: 'text' as const, content: 'Target resource' },\n },\n };\n }\n if (key === 'sourceIp') {\n return {\n ...col,\n meta: {\n ...col.meta,\n description: { type: 'tooltip' as const, content: 'Request origin IP' },\n },\n };\n }\n if (key === 'requests') {\n return {\n ...col,\n meta: { ...col.meta, description: { type: 'tooltip' as const, content: 'Total hits' } },\n };\n }\n if (key === 'parameter') {\n return {\n ...col,\n meta: {\n ...col.meta,\n description: { type: 'text' as const, content: 'Affected param' },\n },\n };\n }\n return col;\n }),\n [],\n );\n\n return (\n <Table\n data={securityEvents}\n columns={columns}\n getRowId={row => row.id}\n sorting={sorting}\n onSortingChange={setSorting}\n />\n );\n}"
|
package/package.json
CHANGED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { type RefObject } from 'react';
|
|
2
|
-
type ScrollMode = 'container' | 'window';
|
|
3
|
-
interface UseEndReachedOptions {
|
|
4
|
-
mode: ScrollMode;
|
|
5
|
-
/** Scroll element ref — required for `container` mode */
|
|
6
|
-
scrollRef?: RefObject<HTMLElement | null>;
|
|
7
|
-
onEndReached?: () => void;
|
|
8
|
-
threshold?: number;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Fires `onEndReached` once when the user scrolls within `threshold` px
|
|
12
|
-
* of the bottom. Re-arms automatically after the user scrolls back up
|
|
13
|
-
* past the threshold or when the scroll height grows (new data loaded).
|
|
14
|
-
*
|
|
15
|
-
* A cooldown guard prevents rapid re-fires that can occur when new rows
|
|
16
|
-
* are appended (scrollHeight grows → firedRef resets → still at bottom).
|
|
17
|
-
*/
|
|
18
|
-
export declare const useEndReached: ({ mode, scrollRef, onEndReached, threshold, }: UseEndReachedOptions) => void;
|
|
19
|
-
export {};
|