@wallarm-org/design-system 1.28.1 → 1.28.2-rc-fix-table-window-scroll-root.1
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/TableBodyVirtualizedCore.js +1 -1
- package/dist/components/Table/TableBody/TableBodyVirtualizedWindow.js +25 -9
- package/dist/components/Table/TableInner/TableInnerWindow.js +1 -0
- package/dist/components/Table/hooks/infiniteScroll/useInfiniteScroll.d.ts +1 -1
- package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.d.ts +1 -0
- package/dist/components/Table/hooks/infiniteScroll/usePrependScrollAnchor.js +12 -10
- package/dist/components/Table/hooks/infiniteScroll/useScrollEdge.d.ts +4 -1
- package/dist/components/Table/hooks/infiniteScroll/useScrollEdge.js +4 -17
- package/dist/components/Table/lib/index.d.ts +1 -0
- package/dist/components/Table/lib/index.js +1 -0
- package/dist/components/Table/lib/scrollRoot.d.ts +18 -0
- package/dist/components/Table/lib/scrollRoot.js +22 -0
- package/dist/components/Table/types.d.ts +4 -1
- package/dist/metadata/components.json +3 -3
- package/package.json +1 -1
|
@@ -47,7 +47,7 @@ const TableBodyVirtualizedCore = ({ tbodyRef, virtualizer })=>{
|
|
|
47
47
|
virtualRows.length > 0 && /*#__PURE__*/ jsx(Tr, {
|
|
48
48
|
children: /*#__PURE__*/ jsx(Td, {
|
|
49
49
|
style: {
|
|
50
|
-
height: `${totalSize - (virtualRows[virtualRows.length - 1]?.end ?? 0)}px`,
|
|
50
|
+
height: `${totalSize + scrollMargin - (virtualRows[virtualRows.length - 1]?.end ?? 0)}px`,
|
|
51
51
|
padding: 0,
|
|
52
52
|
border: 'none'
|
|
53
53
|
},
|
|
@@ -1,20 +1,37 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback, useEffect } from "react";
|
|
3
|
-
import {
|
|
4
|
-
import { TABLE_VIRTUALIZATION_OVERSCAN, getRowKey } from "../lib/index.js";
|
|
2
|
+
import { useCallback, useEffect, useRef } from "react";
|
|
3
|
+
import { elementScroll, observeElementOffset, observeElementRect, observeWindowOffset, observeWindowRect, useVirtualizer } from "@tanstack/react-virtual";
|
|
4
|
+
import { TABLE_VIRTUALIZATION_OVERSCAN, getOffsetTopInScrollRoot, getRowKey, getScrollMetrics, getScrollRoot } from "../lib/index.js";
|
|
5
5
|
import { useTableContext } from "../TableContext/index.js";
|
|
6
|
-
import { getDocumentOffsetTop } from "./lib/getDocumentOffsetTop.js";
|
|
7
6
|
import { measureRowElement } from "./lib/measureRowElement.js";
|
|
8
7
|
import { TableBodyVirtualizedCore } from "./TableBodyVirtualizedCore.js";
|
|
9
8
|
import { useResetVirtualizerOnDataChange } from "./useResetVirtualizerOnDataChange.js";
|
|
10
9
|
import { useSmoothScrollOnSort } from "./useSmoothScrollOnSort.js";
|
|
10
|
+
const isWindowRoot = (instance)=>instance.scrollElement === window;
|
|
11
|
+
const observeRootRect = (instance, cb)=>isWindowRoot(instance) ? observeWindowRect(instance, cb) : observeElementRect(instance, cb);
|
|
12
|
+
const observeRootOffset = (instance, cb)=>isWindowRoot(instance) ? observeWindowOffset(instance, cb) : observeElementOffset(instance, cb);
|
|
11
13
|
const TableBodyVirtualizedWindow = ()=>{
|
|
12
|
-
const { table, estimateRowHeight, overscan, tbodyRef, virtualizerRef } = useTableContext();
|
|
13
|
-
const
|
|
14
|
+
const { table, estimateRowHeight, overscan, tbodyRef, virtualizerRef, containerRef } = useTableContext();
|
|
15
|
+
const scrollRootRef = useRef(null);
|
|
16
|
+
const getScrollElement = useCallback(()=>{
|
|
17
|
+
const anchor = tbodyRef.current ?? containerRef.current;
|
|
18
|
+
if (!scrollRootRef.current && anchor) scrollRootRef.current = getScrollRoot(anchor);
|
|
19
|
+
return scrollRootRef.current;
|
|
20
|
+
}, [
|
|
21
|
+
tbodyRef,
|
|
22
|
+
containerRef
|
|
23
|
+
]);
|
|
24
|
+
const scrollRoot = getScrollElement();
|
|
25
|
+
const virtualizer = useVirtualizer({
|
|
14
26
|
count: table.getRowModel().rows.length,
|
|
27
|
+
getScrollElement: getScrollElement,
|
|
28
|
+
observeElementRect: observeRootRect,
|
|
29
|
+
observeElementOffset: observeRootOffset,
|
|
30
|
+
scrollToFn: elementScroll,
|
|
31
|
+
initialOffset: ()=>getScrollMetrics(getScrollElement() ?? window).scrollTop,
|
|
15
32
|
estimateSize: estimateRowHeight ?? (()=>40),
|
|
16
33
|
overscan: overscan ?? TABLE_VIRTUALIZATION_OVERSCAN,
|
|
17
|
-
scrollMargin: tbodyRef.current ?
|
|
34
|
+
scrollMargin: tbodyRef.current && scrollRoot ? getOffsetTopInScrollRoot(tbodyRef.current, scrollRoot) : 0,
|
|
18
35
|
getItemKey: useCallback((index)=>getRowKey(table.getRowModel().rows, index), [
|
|
19
36
|
table
|
|
20
37
|
]),
|
|
@@ -27,8 +44,7 @@ const TableBodyVirtualizedWindow = ()=>{
|
|
|
27
44
|
virtualizerRef
|
|
28
45
|
]);
|
|
29
46
|
useResetVirtualizerOnDataChange(table, virtualizer);
|
|
30
|
-
|
|
31
|
-
useSmoothScrollOnSort(table, getScrollTarget);
|
|
47
|
+
useSmoothScrollOnSort(table, getScrollElement);
|
|
32
48
|
return /*#__PURE__*/ jsx(TableBodyVirtualizedCore, {
|
|
33
49
|
tbodyRef: tbodyRef,
|
|
34
50
|
virtualizer: virtualizer
|
|
@@ -4,7 +4,7 @@ import { type DSTableFeatures } from '../../lib';
|
|
|
4
4
|
import type { TableVirtualizerInstance } from '../../TableContext/types';
|
|
5
5
|
interface UseInfiniteScrollOptions<T extends RowData> {
|
|
6
6
|
mode: 'container' | 'window';
|
|
7
|
-
/**
|
|
7
|
+
/** `container`: the scroll element. `window`: an element to resolve the scroll root from. */
|
|
8
8
|
scrollRef?: RefObject<HTMLElement | null>;
|
|
9
9
|
table: Table<DSTableFeatures, T>;
|
|
10
10
|
virtualizerRef: RefObject<TableVirtualizerInstance | null>;
|
|
@@ -2,6 +2,7 @@ import { type RefObject } from 'react';
|
|
|
2
2
|
import type { TableVirtualizerInstance } from '../../TableContext/types';
|
|
3
3
|
interface UsePrependScrollAnchorOptions {
|
|
4
4
|
mode: 'container' | 'window';
|
|
5
|
+
/** `container`: the scroll element. `window`: an element to resolve the scroll root from. */
|
|
5
6
|
scrollRef?: RefObject<HTMLElement | null>;
|
|
6
7
|
rows: {
|
|
7
8
|
id: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useLayoutEffect, useRef } from "react";
|
|
2
|
-
import { detectDataChange } from "../../lib/index.js";
|
|
2
|
+
import { detectDataChange, getScrollMetrics, getScrollRoot, scrollRootBy } from "../../lib/index.js";
|
|
3
3
|
const usePrependScrollAnchor = ({ mode, scrollRef, rows, virtualizerRef, tbodyRef, isLoadingPrevious })=>{
|
|
4
4
|
const prevFirstRowIdRef = useRef(void 0);
|
|
5
5
|
const prevLastRowIdRef = useRef(void 0);
|
|
@@ -12,7 +12,15 @@ const usePrependScrollAnchor = ({ mode, scrollRef, rows, virtualizerRef, tbodyRe
|
|
|
12
12
|
});
|
|
13
13
|
const seededRef = useRef(false);
|
|
14
14
|
useLayoutEffect(()=>{
|
|
15
|
-
const
|
|
15
|
+
const getRoot = ()=>'window' === mode ? getScrollRoot(scrollRef?.current ?? null) : scrollRef?.current;
|
|
16
|
+
const getScrollHeight = ()=>{
|
|
17
|
+
const root = getRoot();
|
|
18
|
+
return root ? getScrollMetrics(root).scrollHeight : 0;
|
|
19
|
+
};
|
|
20
|
+
const scrollBy = (delta)=>{
|
|
21
|
+
const root = getRoot();
|
|
22
|
+
if (root) scrollRootBy(root, delta);
|
|
23
|
+
};
|
|
16
24
|
const getStartLoaderHeight = ()=>{
|
|
17
25
|
const loaderRows = tbodyRef?.current?.querySelectorAll('tr[data-loading-position="start"]');
|
|
18
26
|
const count = loaderRows?.length ?? 0;
|
|
@@ -54,16 +62,10 @@ const usePrependScrollAnchor = ({ mode, scrollRef, rows, virtualizerRef, tbodyRe
|
|
|
54
62
|
const rowDelta = newStart - prevFirstRowStartRef.current;
|
|
55
63
|
const loaderDelta = getStartLoaderHeight() - prevLoaderHeight;
|
|
56
64
|
const delta = rowDelta + loaderDelta;
|
|
57
|
-
if (0 !== delta)
|
|
58
|
-
if ('window' === mode) window.scrollBy(0, delta);
|
|
59
|
-
else if (scrollRef?.current) scrollRef.current.scrollTop += delta;
|
|
60
|
-
}
|
|
65
|
+
if (0 !== delta) scrollBy(delta);
|
|
61
66
|
} else if (null !== prevScrollHeightRef.current) {
|
|
62
67
|
const delta = getScrollHeight() - prevScrollHeightRef.current;
|
|
63
|
-
if (delta > 0)
|
|
64
|
-
if ('window' === mode) window.scrollBy(0, delta);
|
|
65
|
-
else if (scrollRef?.current) scrollRef.current.scrollTop += delta;
|
|
66
|
-
}
|
|
68
|
+
if (delta > 0) scrollBy(delta);
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
prevFirstRowIdRef.current = rows[0]?.id;
|
|
@@ -4,7 +4,10 @@ type ScrollEdge = 'start' | 'end';
|
|
|
4
4
|
interface UseScrollEdgeOptions {
|
|
5
5
|
edge: ScrollEdge;
|
|
6
6
|
mode: ScrollMode;
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* `container`: the scroll element. `window`: any element inside the table —
|
|
9
|
+
* the scroll root is resolved from it (see `getScrollRoot`).
|
|
10
|
+
*/
|
|
8
11
|
scrollRef?: RefObject<HTMLElement | null>;
|
|
9
12
|
onReached?: () => void;
|
|
10
13
|
threshold: number;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useEffect, useRef } from "react";
|
|
2
|
-
import { SCROLL_EDGE_COOLDOWN_MS } from "../../lib/index.js";
|
|
2
|
+
import { SCROLL_EDGE_COOLDOWN_MS, getScrollMetrics, getScrollRoot } from "../../lib/index.js";
|
|
3
3
|
const useScrollEdge = ({ edge, mode, scrollRef, onReached, threshold, enabled = true })=>{
|
|
4
4
|
const firedRef = useRef(false);
|
|
5
5
|
const lastFiredAtRef = useRef(0);
|
|
@@ -12,23 +12,12 @@ const useScrollEdge = ({ edge, mode, scrollRef, onReached, threshold, enabled =
|
|
|
12
12
|
enabledRef.current = enabled;
|
|
13
13
|
});
|
|
14
14
|
useEffect(()=>{
|
|
15
|
+
const target = 'window' === mode ? getScrollRoot(scrollRef?.current ?? null) : scrollRef?.current;
|
|
16
|
+
if (!target) return;
|
|
15
17
|
const check = ()=>{
|
|
16
18
|
const callback = onReachedRef.current;
|
|
17
19
|
if (!callback || !enabledRef.current) return;
|
|
18
|
-
|
|
19
|
-
let clientHeight;
|
|
20
|
-
let scrollHeight;
|
|
21
|
-
if ('window' === mode) {
|
|
22
|
-
scrollTop = window.scrollY;
|
|
23
|
-
clientHeight = window.innerHeight;
|
|
24
|
-
scrollHeight = document.documentElement.scrollHeight;
|
|
25
|
-
} else {
|
|
26
|
-
const el = scrollRef?.current;
|
|
27
|
-
if (!el) return;
|
|
28
|
-
scrollTop = el.scrollTop;
|
|
29
|
-
clientHeight = el.clientHeight;
|
|
30
|
-
scrollHeight = el.scrollHeight;
|
|
31
|
-
}
|
|
20
|
+
const { scrollTop, clientHeight, scrollHeight } = getScrollMetrics(target);
|
|
32
21
|
const distance = 'start' === edge ? scrollTop : scrollHeight - scrollTop - clientHeight;
|
|
33
22
|
if (distance <= threshold) {
|
|
34
23
|
const now = Date.now();
|
|
@@ -39,8 +28,6 @@ const useScrollEdge = ({ edge, mode, scrollRef, onReached, threshold, enabled =
|
|
|
39
28
|
}
|
|
40
29
|
} else firedRef.current = false;
|
|
41
30
|
};
|
|
42
|
-
const target = 'window' === mode ? window : scrollRef?.current;
|
|
43
|
-
if (!target) return;
|
|
44
31
|
target.addEventListener('scroll', check, {
|
|
45
32
|
passive: true
|
|
46
33
|
});
|
|
@@ -11,6 +11,7 @@ export { getDndStyles } from './getDndStyles';
|
|
|
11
11
|
export { getPinningStyles } from './getPinningStyles';
|
|
12
12
|
export { getRowKey } from './getRowKey';
|
|
13
13
|
export { isLastPinnedLeft } from './isLastPinnedLeft';
|
|
14
|
+
export { getOffsetTopInScrollRoot, getScrollMetrics, getScrollRoot, isWindowScrollRoot, type ScrollRoot, scrollRootBy, } from './scrollRoot';
|
|
14
15
|
export { useColumnDnd } from './useColumnDnd';
|
|
15
16
|
export { useContainerWidth } from './useContainerWidth';
|
|
16
17
|
export { useRowDnd } from './useRowDnd';
|
|
@@ -11,6 +11,7 @@ export { getDndStyles } from "./getDndStyles.js";
|
|
|
11
11
|
export { getPinningStyles } from "./getPinningStyles.js";
|
|
12
12
|
export { getRowKey } from "./getRowKey.js";
|
|
13
13
|
export { isLastPinnedLeft } from "./isLastPinnedLeft.js";
|
|
14
|
+
export { getOffsetTopInScrollRoot, getScrollMetrics, getScrollRoot, isWindowScrollRoot, scrollRootBy } from "./scrollRoot.js";
|
|
14
15
|
export { useColumnDnd } from "./useColumnDnd.js";
|
|
15
16
|
export { useContainerWidth } from "./useContainerWidth.js";
|
|
16
17
|
export { useRowDnd } from "./useRowDnd.js";
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type ScrollRoot = HTMLElement | Window;
|
|
2
|
+
export declare const isWindowScrollRoot: (root: ScrollRoot) => root is Window;
|
|
3
|
+
/**
|
|
4
|
+
* What a `virtualized='window'` table actually scrolls with: the nearest
|
|
5
|
+
* vertically scrollable ancestor, else the window. An app shell that scrolls
|
|
6
|
+
* its content pane instead of the document (e.g. a micro-frontend host) keeps
|
|
7
|
+
* `window.scrollY` at 0 forever, so tracking the window there never virtualizes
|
|
8
|
+
* past the first screen and never reaches the end.
|
|
9
|
+
*/
|
|
10
|
+
export declare const getScrollRoot: (el: Element | null) => ScrollRoot;
|
|
11
|
+
export declare const getScrollMetrics: (root: ScrollRoot) => {
|
|
12
|
+
scrollTop: number;
|
|
13
|
+
clientHeight: number;
|
|
14
|
+
scrollHeight: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const scrollRootBy: (root: ScrollRoot, delta: number) => void;
|
|
17
|
+
/** `el`'s top in the root's scroll-content coordinates (the virtualizer's `scrollMargin`). */
|
|
18
|
+
export declare const getOffsetTopInScrollRoot: (el: HTMLElement, root: ScrollRoot) => number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { getDocumentOffsetTop } from "../TableBody/lib/getDocumentOffsetTop.js";
|
|
2
|
+
const SCROLLABLE_OVERFLOW = /(auto|scroll|overlay)/;
|
|
3
|
+
const isWindowScrollRoot = (root)=>root === window;
|
|
4
|
+
const getScrollRoot = (el)=>{
|
|
5
|
+
for(let node = el?.parentElement; node && node !== document.body; node = node.parentElement)if (SCROLLABLE_OVERFLOW.test(getComputedStyle(node).overflowY)) return node;
|
|
6
|
+
return window;
|
|
7
|
+
};
|
|
8
|
+
const getScrollMetrics = (root)=>isWindowScrollRoot(root) ? {
|
|
9
|
+
scrollTop: root.scrollY,
|
|
10
|
+
clientHeight: root.innerHeight,
|
|
11
|
+
scrollHeight: document.documentElement.scrollHeight
|
|
12
|
+
} : {
|
|
13
|
+
scrollTop: root.scrollTop,
|
|
14
|
+
clientHeight: root.clientHeight,
|
|
15
|
+
scrollHeight: root.scrollHeight
|
|
16
|
+
};
|
|
17
|
+
const scrollRootBy = (root, delta)=>{
|
|
18
|
+
if (isWindowScrollRoot(root)) root.scrollBy(0, delta);
|
|
19
|
+
else root.scrollTop += delta;
|
|
20
|
+
};
|
|
21
|
+
const getOffsetTopInScrollRoot = (el, root)=>isWindowScrollRoot(root) ? getDocumentOffsetTop(el) : el.getBoundingClientRect().top - root.getBoundingClientRect().top + root.scrollTop;
|
|
22
|
+
export { getOffsetTopInScrollRoot, getScrollMetrics, getScrollRoot, isWindowScrollRoot, scrollRootBy };
|
|
@@ -245,7 +245,10 @@ export interface TableProps<T> extends TestableProps {
|
|
|
245
245
|
* the close edge here.
|
|
246
246
|
*/
|
|
247
247
|
onSettingsOpenChange?: (open: boolean) => void;
|
|
248
|
-
/**
|
|
248
|
+
/**
|
|
249
|
+
* Enable row virtualization. `'container'` virtualizes within the table's own scroll container;
|
|
250
|
+
* `'window'` follows the page scroll — the nearest scrollable ancestor, or the browser window.
|
|
251
|
+
*/
|
|
249
252
|
virtualized?: TableVirtualized;
|
|
250
253
|
/**
|
|
251
254
|
* Whether columns stretch proportionally to fill the container width.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "1.28.
|
|
3
|
-
"generatedAt": "2026-09-
|
|
2
|
+
"version": "1.28.1",
|
|
3
|
+
"generatedAt": "2026-09-30T15:40:03.193Z",
|
|
4
4
|
"components": [
|
|
5
5
|
{
|
|
6
6
|
"name": "Accordion",
|
|
@@ -85754,7 +85754,7 @@
|
|
|
85754
85754
|
"name": "virtualized",
|
|
85755
85755
|
"type": "TableVirtualized | undefined",
|
|
85756
85756
|
"required": false,
|
|
85757
|
-
"description": "Enable row virtualization. `'container'` virtualizes within the scroll container
|
|
85757
|
+
"description": "Enable row virtualization. `'container'` virtualizes within the table's own scroll container;\n`'window'` follows the page scroll — the nearest scrollable ancestor, or the browser window."
|
|
85758
85758
|
},
|
|
85759
85759
|
{
|
|
85760
85760
|
"name": "stretch",
|
package/package.json
CHANGED