beautiful-grid 1.0.2 → 1.0.4
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/README.md +1 -1
- package/cjs/BGrid.js +2 -2
- package/cjs/components/Table.js +204 -147
- package/cjs/components/selection/CellSelectionOverlayLayer.js +5 -5
- package/cjs/utils/coordinate.js +10 -10
- package/cjs/utils/index.js +1 -0
- package/cjs/utils/virtualScrollWindow.js +67 -0
- package/esm/BGrid.js +2 -2
- package/esm/components/Table.js +163 -109
- package/esm/components/selection/CellSelectionOverlayLayer.js +5 -5
- package/esm/utils/coordinate.js +4 -4
- package/esm/utils/index.js +1 -0
- package/esm/utils/virtualScrollWindow.js +59 -0
- package/package.json +1 -1
- package/types/BGrid.d.ts +1 -1
- package/types/components/Table.d.ts +1 -0
- package/types/components/selection/CellSelectionOverlay.d.ts +1 -0
- package/types/components/selection/CellSelectionOverlayLayer.d.ts +2 -1
- package/types/types.d.ts +13 -0
- package/types/utils/coordinate.d.ts +5 -1
- package/types/utils/index.d.ts +1 -0
- package/types/utils/virtualScrollWindow.d.ts +24 -0
package/esm/components/Table.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useCallback, useEffect, useRef } from 'react';
|
|
|
3
3
|
import { useShallow } from 'zustand/react/shallow';
|
|
4
4
|
import { useAppStore } from '../store';
|
|
5
5
|
import { BGridDataItemStatus, } from '../types';
|
|
6
|
-
import { getCellValueByRowKey, getFrozenColumnsWidth, getVisibleScrollableRowRange, isCheckboxValueChecked, markCellEdited, markCellValueChanged, resolveLogicalCell, shouldRenderBottomBar, } from '../utils';
|
|
6
|
+
import { getCellValueByRowKey, getFrozenColumnsWidth, getVisibleScrollableRowRange, getVirtualScrollWindowMetrics, getVirtualScrollWindowPosition, isCheckboxValueChecked, markCellEdited, markCellValueChanged, rebaseVirtualScrollWindow, resolveLogicalCell, shouldRenderBottomBar, } from '../utils';
|
|
7
7
|
import { clamp, ensureCellVisible } from '../utils/coordinate';
|
|
8
8
|
import Loading from './Loading';
|
|
9
9
|
import TableBody from './TableBody';
|
|
@@ -183,6 +183,23 @@ function Table(props) {
|
|
|
183
183
|
const trHeight = itemHeight + itemPadding * 2;
|
|
184
184
|
const scrollableBodyHeight = Math.max(contentBodyHeight - frozenRowsHeight, 0);
|
|
185
185
|
const mainViewportWidth = Math.max(width - (frozenColumnsWidth ?? 0), 0);
|
|
186
|
+
const logicalBodyContentHeight = data.length * trHeight;
|
|
187
|
+
const virtualScrollWindowMetrics = React.useMemo(() => getVirtualScrollWindowMetrics({
|
|
188
|
+
logicalContentHeight: logicalBodyContentHeight,
|
|
189
|
+
viewportHeight: contentBodyHeight,
|
|
190
|
+
enabled: scrollbar.variant !== 'native',
|
|
191
|
+
}), [contentBodyHeight, logicalBodyContentHeight, scrollbar.variant]);
|
|
192
|
+
const [virtualScrollBase, setVirtualScrollBase] = React.useState(0);
|
|
193
|
+
const virtualScrollBaseRef = useRef(0);
|
|
194
|
+
const containerRef = useRef(null);
|
|
195
|
+
const searchPopoverRef = useRef(null);
|
|
196
|
+
const editorPortalRef = useRef(null);
|
|
197
|
+
const bodyContainerRef = useRef(null);
|
|
198
|
+
const scrollContainerRef = useRef(null);
|
|
199
|
+
const logicalVerticalScrollState = React.useMemo(() => ({
|
|
200
|
+
scrollTop,
|
|
201
|
+
scrollHeight: virtualScrollWindowMetrics.logicalContentHeight,
|
|
202
|
+
}), [scrollTop, virtualScrollWindowMetrics.logicalContentHeight]);
|
|
186
203
|
const hasMultiCellSelection = React.useMemo(() => cellSelectionRanges.length > 1 ||
|
|
187
204
|
cellSelectionRanges.some(range => {
|
|
188
205
|
const normalized = normalizeCellSelectionRange(range);
|
|
@@ -273,7 +290,7 @@ function Table(props) {
|
|
|
273
290
|
top: topQuadrant ? 0 : scrollTop,
|
|
274
291
|
width: leftQuadrant ? frozenColumnsWidth ?? 0 : mainViewportWidth,
|
|
275
292
|
height: topQuadrant ? frozenRowsHeight : scrollableBodyHeight,
|
|
276
|
-
} }));
|
|
293
|
+
}, offsetTop: !topQuadrant && virtualScrollWindowMetrics.enabled ? virtualScrollBase : 0 }));
|
|
277
294
|
};
|
|
278
295
|
const visibleScrollableRows = React.useMemo(() => getVisibleScrollableRowRange({
|
|
279
296
|
scrollTop,
|
|
@@ -288,15 +305,14 @@ function Table(props) {
|
|
|
288
305
|
startRowIndex: visibleScrollableRows.startRowIndex,
|
|
289
306
|
endRowIndex: visibleScrollableRows.endRowIndex,
|
|
290
307
|
}), [visibleScrollableRows.endRowIndex, visibleScrollableRows.startRowIndex]);
|
|
291
|
-
const frozenScrollableBodyStyle = React.useMemo(() => ({
|
|
292
|
-
|
|
293
|
-
|
|
308
|
+
const frozenScrollableBodyStyle = React.useMemo(() => ({
|
|
309
|
+
top: virtualScrollWindowMetrics.enabled
|
|
310
|
+
? visibleScrollableRows.paddingTop - virtualScrollBase
|
|
311
|
+
: visibleScrollableRows.paddingTop,
|
|
312
|
+
}), [virtualScrollBase, virtualScrollWindowMetrics.enabled, visibleScrollableRows.paddingTop]);
|
|
294
313
|
const warnedContextMenuIdsRef = useRef(new Set());
|
|
295
314
|
const warnedContextMenuFactoryRef = useRef(false);
|
|
296
|
-
const editorPortalRef = useRef(null);
|
|
297
315
|
const editorPortalContext = React.useMemo(() => ({ gridRef: containerRef, portalRef: editorPortalRef }), []);
|
|
298
|
-
const bodyContainerRef = useRef(null);
|
|
299
|
-
const scrollContainerRef = useRef(null);
|
|
300
316
|
const rowReorderController = useRowReorderController({
|
|
301
317
|
containerRef,
|
|
302
318
|
bodyContainerRef,
|
|
@@ -404,7 +420,8 @@ function Table(props) {
|
|
|
404
420
|
const stickyBottomHeight = summary?.position === 'bottom' ? summaryHeight : 0;
|
|
405
421
|
const stickyFixedHeight = stickyTopHeight + stickyBottomHeight;
|
|
406
422
|
const scrollViewportHeight = contentBodyHeight + stickyFixedHeight;
|
|
407
|
-
const
|
|
423
|
+
const physicalScrollableRowsHeight = Math.max(virtualScrollWindowMetrics.physicalContentHeight - frozenRowsHeight, 0);
|
|
424
|
+
const scrollPlaneHeight = stickyFixedHeight + virtualScrollWindowMetrics.physicalContentHeight;
|
|
408
425
|
const scrollPlaneContentWidth = (frozenColumnsWidth ?? 0) + scrollableColumnsWidth;
|
|
409
426
|
const scrollPlaneWidth = Math.max(width, scrollPlaneContentWidth);
|
|
410
427
|
const customVerticalScrollbarGutter = scrollbar.vertical.visible && scrollbar.variant === 'classic'
|
|
@@ -415,7 +432,18 @@ function Table(props) {
|
|
|
415
432
|
const scrollPlaneMinWidth = customVerticalScrollbarGutter
|
|
416
433
|
? `max(100%, calc(${scrollPlaneContentWidth}px + ${customVerticalScrollbarGutter}))`
|
|
417
434
|
: scrollPlaneWidth;
|
|
418
|
-
const
|
|
435
|
+
const measuredScrollbarMetrics = useScrollbarMetrics(scrollContainerRef, [data.length, itemHeight, itemPadding, contentBodyHeight, width], scrollPlaneContentWidth, stickyFixedHeight);
|
|
436
|
+
const scrollbarMetrics = React.useMemo(() => virtualScrollWindowMetrics.enabled
|
|
437
|
+
? {
|
|
438
|
+
...measuredScrollbarMetrics,
|
|
439
|
+
vertical: {
|
|
440
|
+
viewportSize: contentBodyHeight,
|
|
441
|
+
contentSize: virtualScrollWindowMetrics.logicalContentHeight,
|
|
442
|
+
maxScroll: virtualScrollWindowMetrics.logicalMaxScroll,
|
|
443
|
+
hasOverflow: virtualScrollWindowMetrics.logicalMaxScroll > 1,
|
|
444
|
+
},
|
|
445
|
+
}
|
|
446
|
+
: measuredScrollbarMetrics, [contentBodyHeight, measuredScrollbarMetrics, virtualScrollWindowMetrics]);
|
|
419
447
|
const measuredVerticalScrollbarGutter = customVerticalScrollbarGutter && scrollbarMetrics.horizontal.hasOverflow
|
|
420
448
|
? Math.max(scrollbarMetrics.horizontal.contentSize - scrollPlaneContentWidth, 0)
|
|
421
449
|
: 0;
|
|
@@ -435,13 +463,16 @@ function Table(props) {
|
|
|
435
463
|
scrollContainerRef.current.scrollLeft = offset;
|
|
436
464
|
}
|
|
437
465
|
}, []);
|
|
438
|
-
const handleScrollTopChange = useCallback((offset) => {
|
|
439
|
-
if (scrollContainerRef.current) {
|
|
440
|
-
scrollContainerRef.current.scrollTop = offset;
|
|
441
|
-
}
|
|
442
|
-
}, []);
|
|
443
466
|
const latestScrollRef = useRef({ top: scrollTop, left: scrollLeft });
|
|
444
467
|
const latestCommittedScrollRef = useRef({ top: scrollTop, left: scrollLeft });
|
|
468
|
+
const scrollPropsRef = useRef({ top: props.scrollTop, left: props.scrollLeft });
|
|
469
|
+
const syncedDataPropRef = useRef(props.data);
|
|
470
|
+
const [rowScrollRequest, setRowScrollRequest] = React.useState(null);
|
|
471
|
+
React.useImperativeHandle(props.ref, () => ({
|
|
472
|
+
scrollToRow(rowIndex, options) {
|
|
473
|
+
setRowScrollRequest({ rowIndex, align: options?.align ?? 'nearest' });
|
|
474
|
+
},
|
|
475
|
+
}), []);
|
|
445
476
|
const scrollRafRef = useRef(null);
|
|
446
477
|
const scrollIdleTimerRef = useRef(null);
|
|
447
478
|
const selectionAutoScrollRafRef = useRef(null);
|
|
@@ -456,6 +487,7 @@ function Table(props) {
|
|
|
456
487
|
frozenRowCount,
|
|
457
488
|
frozenColumnsWidth: frozenColumnsWidth ?? 0,
|
|
458
489
|
frozenRowsHeight,
|
|
490
|
+
scrollTop,
|
|
459
491
|
trHeight,
|
|
460
492
|
});
|
|
461
493
|
useEffect(() => {
|
|
@@ -466,9 +498,10 @@ function Table(props) {
|
|
|
466
498
|
frozenRowCount,
|
|
467
499
|
frozenColumnsWidth: frozenColumnsWidth ?? 0,
|
|
468
500
|
frozenRowsHeight,
|
|
501
|
+
scrollTop,
|
|
469
502
|
trHeight,
|
|
470
503
|
};
|
|
471
|
-
}, [columns, data.length, props.frozenColumnIndex, frozenColumnsWidth, frozenRowCount, frozenRowsHeight, trHeight]);
|
|
504
|
+
}, [columns, data.length, props.frozenColumnIndex, frozenColumnsWidth, frozenRowCount, frozenRowsHeight, scrollTop, trHeight]);
|
|
472
505
|
const markScrollActive = useCallback(() => {
|
|
473
506
|
containerRef.current?.setAttribute('data-bgrid-scrolling', 'true');
|
|
474
507
|
if (scrollIdleTimerRef.current !== null)
|
|
@@ -478,6 +511,27 @@ function Table(props) {
|
|
|
478
511
|
containerRef.current?.removeAttribute('data-bgrid-scrolling');
|
|
479
512
|
}, SCROLL_IDLE_DELAY);
|
|
480
513
|
}, []);
|
|
514
|
+
const commitScroll = useCallback((top, left) => {
|
|
515
|
+
const scrollContainer = scrollContainerRef.current;
|
|
516
|
+
if (!scrollContainer)
|
|
517
|
+
return;
|
|
518
|
+
const position = getVirtualScrollWindowPosition(top, virtualScrollWindowMetrics);
|
|
519
|
+
if (virtualScrollBaseRef.current !== position.base) {
|
|
520
|
+
virtualScrollBaseRef.current = position.base;
|
|
521
|
+
setVirtualScrollBase(position.base);
|
|
522
|
+
}
|
|
523
|
+
if (scrollContainer.scrollTop !== position.physicalScrollTop) {
|
|
524
|
+
scrollContainer.scrollTop = position.physicalScrollTop;
|
|
525
|
+
}
|
|
526
|
+
if (scrollContainer.scrollLeft !== left)
|
|
527
|
+
scrollContainer.scrollLeft = left;
|
|
528
|
+
latestScrollRef.current = { top: position.logicalScrollTop, left };
|
|
529
|
+
latestCommittedScrollRef.current = { top: position.logicalScrollTop, left };
|
|
530
|
+
setScroll(position.logicalScrollTop, left);
|
|
531
|
+
}, [setScroll, virtualScrollWindowMetrics]);
|
|
532
|
+
const handleScrollTopChange = useCallback((offset) => {
|
|
533
|
+
commitScroll(offset, scrollContainerRef.current?.scrollLeft ?? scrollLeft);
|
|
534
|
+
}, [commitScroll, scrollLeft]);
|
|
481
535
|
const scheduleScrollFrame = useCallback(() => {
|
|
482
536
|
if (scrollRafRef.current !== null)
|
|
483
537
|
return;
|
|
@@ -486,90 +540,42 @@ function Table(props) {
|
|
|
486
540
|
const scrollContainer = scrollContainerRef.current;
|
|
487
541
|
if (!scrollContainer)
|
|
488
542
|
return;
|
|
489
|
-
const { scrollTop, scrollLeft } = scrollContainer;
|
|
490
|
-
|
|
543
|
+
const { scrollTop: physicalScrollTop, scrollLeft } = scrollContainer;
|
|
544
|
+
const position = rebaseVirtualScrollWindow(virtualScrollBaseRef.current, physicalScrollTop, virtualScrollWindowMetrics);
|
|
545
|
+
if (virtualScrollBaseRef.current !== position.base) {
|
|
546
|
+
virtualScrollBaseRef.current = position.base;
|
|
547
|
+
setVirtualScrollBase(position.base);
|
|
548
|
+
}
|
|
549
|
+
if (physicalScrollTop !== position.physicalScrollTop) {
|
|
550
|
+
scrollContainer.scrollTop = position.physicalScrollTop;
|
|
551
|
+
}
|
|
552
|
+
const logicalScrollTop = position.logicalScrollTop;
|
|
553
|
+
latestScrollRef.current = { top: logicalScrollTop, left: scrollLeft };
|
|
491
554
|
const { top: committedTop, left: committedLeft } = latestCommittedScrollRef.current;
|
|
492
|
-
if (committedTop !==
|
|
493
|
-
latestCommittedScrollRef.current = { top:
|
|
494
|
-
setScroll(
|
|
555
|
+
if (committedTop !== logicalScrollTop || committedLeft !== scrollLeft) {
|
|
556
|
+
latestCommittedScrollRef.current = { top: logicalScrollTop, left: scrollLeft };
|
|
557
|
+
setScroll(logicalScrollTop, scrollLeft);
|
|
495
558
|
}
|
|
496
559
|
if (containerRef.current?.hasAttribute('data-bgrid-scrolling')) {
|
|
497
560
|
scrollRafRef.current = requestAnimationFrame(pollNativeScrollPosition);
|
|
498
561
|
}
|
|
499
562
|
};
|
|
500
563
|
scrollRafRef.current = requestAnimationFrame(pollNativeScrollPosition);
|
|
501
|
-
}, [setScroll]);
|
|
564
|
+
}, [setScroll, virtualScrollWindowMetrics]);
|
|
502
565
|
const onScroll = useCallback(() => {
|
|
503
566
|
if (scrollContainerRef.current) {
|
|
504
|
-
const { scrollTop, scrollLeft } = scrollContainerRef.current;
|
|
505
|
-
latestScrollRef.current = { top: scrollTop, left: scrollLeft };
|
|
506
567
|
markScrollActive();
|
|
507
568
|
scheduleScrollFrame();
|
|
508
569
|
}
|
|
509
570
|
}, [markScrollActive, scheduleScrollFrame]);
|
|
510
|
-
const onWheel = useCallback(evt => {
|
|
511
|
-
const scrollContainer = scrollContainerRef.current;
|
|
512
|
-
if (!scrollContainer)
|
|
513
|
-
return;
|
|
514
|
-
const delta = { x: 0, y: 0 };
|
|
515
|
-
if (evt.detail) {
|
|
516
|
-
delta.y = evt.detail * 10;
|
|
517
|
-
}
|
|
518
|
-
else if (typeof evt.deltaY === 'undefined') {
|
|
519
|
-
delta.y = -evt.wheelDelta;
|
|
520
|
-
delta.x = 0;
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
523
|
-
delta.y = evt.deltaY;
|
|
524
|
-
delta.x = evt.deltaX;
|
|
525
|
-
}
|
|
526
|
-
if (evt.deltaMode === 1) {
|
|
527
|
-
delta.y = delta.y * 16;
|
|
528
|
-
delta.x = delta.x * 16;
|
|
529
|
-
}
|
|
530
|
-
else if (evt.deltaMode === 2) {
|
|
531
|
-
delta.y = delta.y * scrollContainer.clientHeight;
|
|
532
|
-
delta.x = delta.x * scrollContainer.clientWidth;
|
|
533
|
-
}
|
|
534
|
-
const maxScrollTop = Math.max(scrollContainer.scrollHeight - scrollContainer.clientHeight, 0);
|
|
535
|
-
const maxScrollLeft = Math.max(scrollContainer.scrollWidth - scrollContainer.clientWidth, 0);
|
|
536
|
-
const nextScrollTop = Math.min(maxScrollTop, Math.max(0, scrollContainer.scrollTop + delta.y));
|
|
537
|
-
const nextScrollLeft = Math.min(maxScrollLeft, Math.max(0, scrollContainer.scrollLeft + delta.x));
|
|
538
|
-
const didScrollTop = nextScrollTop !== scrollContainer.scrollTop;
|
|
539
|
-
const didScrollLeft = nextScrollLeft !== scrollContainer.scrollLeft;
|
|
540
|
-
const isVertical = Math.abs(delta.y) > Math.abs(delta.x);
|
|
541
|
-
if (isVertical) {
|
|
542
|
-
if (!didScrollTop)
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
if (!didScrollLeft)
|
|
547
|
-
return;
|
|
548
|
-
}
|
|
549
|
-
evt.preventDefault();
|
|
550
|
-
if (didScrollTop)
|
|
551
|
-
scrollContainer.scrollTop = nextScrollTop;
|
|
552
|
-
if (didScrollLeft)
|
|
553
|
-
scrollContainer.scrollLeft = nextScrollLeft;
|
|
554
|
-
}, []);
|
|
555
571
|
const resetScrollPosition = useCallback((type) => {
|
|
556
|
-
if (type === 'all' || type === 'left')
|
|
557
|
-
scrollContainerRef.current?.scrollTo({ left: 0 });
|
|
558
|
-
if (type === 'all' || type === 'top')
|
|
559
|
-
scrollContainerRef.current?.scrollTo({ top: 0 });
|
|
560
|
-
}, []);
|
|
561
|
-
const commitScroll = useCallback((top, left) => {
|
|
562
572
|
const scrollContainer = scrollContainerRef.current;
|
|
563
573
|
if (!scrollContainer)
|
|
564
574
|
return;
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
latestScrollRef.current = { top, left };
|
|
570
|
-
latestCommittedScrollRef.current = { top, left };
|
|
571
|
-
setScroll(top, left);
|
|
572
|
-
}, [setScroll]);
|
|
575
|
+
const nextTop = type === 'all' || type === 'top' ? 0 : latestScrollRef.current.top;
|
|
576
|
+
const nextLeft = type === 'all' || type === 'left' ? 0 : scrollContainer.scrollLeft;
|
|
577
|
+
commitScroll(nextTop, nextLeft);
|
|
578
|
+
}, [commitScroll]);
|
|
573
579
|
useEffect(() => {
|
|
574
580
|
if (!searchOpen || activeSearchMatchIndex === undefined)
|
|
575
581
|
return;
|
|
@@ -593,6 +599,7 @@ function Table(props) {
|
|
|
593
599
|
frozenRowCount,
|
|
594
600
|
columns,
|
|
595
601
|
rowHeight: trHeight,
|
|
602
|
+
verticalScrollState: logicalVerticalScrollState,
|
|
596
603
|
viewportInsets,
|
|
597
604
|
});
|
|
598
605
|
if (result.didScroll)
|
|
@@ -602,6 +609,7 @@ function Table(props) {
|
|
|
602
609
|
columns,
|
|
603
610
|
commitScroll,
|
|
604
611
|
frozenRowCount,
|
|
612
|
+
logicalVerticalScrollState,
|
|
605
613
|
props.frozenColumnIndex,
|
|
606
614
|
searchMatches,
|
|
607
615
|
searchOpen,
|
|
@@ -833,18 +841,18 @@ function Table(props) {
|
|
|
833
841
|
if (!dragState || !pointer || !scrollContainer || !bodyContainer)
|
|
834
842
|
return;
|
|
835
843
|
const delta = getSelectionAutoScrollDelta(pointer.clientX, pointer.clientY, bodyContainer, scrollContainer);
|
|
836
|
-
const maxScrollTop =
|
|
844
|
+
const maxScrollTop = virtualScrollWindowMetrics.logicalMaxScroll;
|
|
837
845
|
const maxScrollLeft = Math.max(scrollContainer.scrollWidth - scrollContainer.clientWidth, 0);
|
|
838
|
-
const nextScrollTop = clamp(
|
|
846
|
+
const nextScrollTop = clamp(latestScrollRef.current.top + delta.y, 0, maxScrollTop);
|
|
839
847
|
const nextScrollLeft = clamp(scrollContainer.scrollLeft + delta.x, 0, maxScrollLeft);
|
|
840
|
-
if (nextScrollTop !==
|
|
848
|
+
if (nextScrollTop !== latestScrollRef.current.top || nextScrollLeft !== scrollContainer.scrollLeft) {
|
|
841
849
|
commitScroll(nextScrollTop, nextScrollLeft);
|
|
842
850
|
}
|
|
843
851
|
updateSelectionByPointer(pointer.clientX, pointer.clientY);
|
|
844
852
|
if (cellSelectionDragRef.current && (delta.x !== 0 || delta.y !== 0)) {
|
|
845
853
|
selectionAutoScrollRafRef.current = requestAnimationFrame(runSelectionAutoScroll);
|
|
846
854
|
}
|
|
847
|
-
}, [commitScroll, updateSelectionByPointer]);
|
|
855
|
+
}, [commitScroll, updateSelectionByPointer, virtualScrollWindowMetrics.logicalMaxScroll]);
|
|
848
856
|
const selectAllCells = useCallback(() => {
|
|
849
857
|
if (!cellSelectionEnabled)
|
|
850
858
|
return false;
|
|
@@ -1333,6 +1341,7 @@ function Table(props) {
|
|
|
1333
1341
|
setSourceData(props.sourceData);
|
|
1334
1342
|
}
|
|
1335
1343
|
}
|
|
1344
|
+
syncedDataPropRef.current = props.data;
|
|
1336
1345
|
}, [
|
|
1337
1346
|
props.data,
|
|
1338
1347
|
props.sourceData,
|
|
@@ -1411,8 +1420,11 @@ function Table(props) {
|
|
|
1411
1420
|
setPage,
|
|
1412
1421
|
setDisplayPaginationLength,
|
|
1413
1422
|
]);
|
|
1423
|
+
const previousPageRef = useRef(props.page?.currentPage);
|
|
1414
1424
|
useEffect(() => {
|
|
1415
|
-
|
|
1425
|
+
if (previousPageRef.current !== props.page?.currentPage)
|
|
1426
|
+
resetScrollPosition('top');
|
|
1427
|
+
previousPageRef.current = props.page?.currentPage;
|
|
1416
1428
|
}, [resetScrollPosition, props.page?.currentPage]);
|
|
1417
1429
|
// [Group 8] Row key & selection
|
|
1418
1430
|
useEffect(() => {
|
|
@@ -1497,10 +1509,8 @@ function Table(props) {
|
|
|
1497
1509
|
const containerRefCurrent = containerRef.current;
|
|
1498
1510
|
const scrollContainerRefCurrent = scrollContainerRef?.current;
|
|
1499
1511
|
if (scrollContainerRefCurrent) {
|
|
1500
|
-
scrollContainerRefCurrent.removeEventListener('scroll', onScroll);
|
|
1512
|
+
scrollContainerRefCurrent.removeEventListener('scroll', onScroll, true);
|
|
1501
1513
|
scrollContainerRefCurrent.addEventListener('scroll', onScroll, { passive: true, capture: true });
|
|
1502
|
-
// scrollContainerRefCurrent.removeEventListener('wheel', onWheel);
|
|
1503
|
-
// scrollContainerRefCurrent.addEventListener('wheel', onWheel, { passive: false, capture: true });
|
|
1504
1514
|
}
|
|
1505
1515
|
return () => {
|
|
1506
1516
|
if (scrollRafRef.current !== null) {
|
|
@@ -1512,20 +1522,56 @@ function Table(props) {
|
|
|
1512
1522
|
scrollIdleTimerRef.current = null;
|
|
1513
1523
|
}
|
|
1514
1524
|
containerRefCurrent?.removeAttribute('data-bgrid-scrolling');
|
|
1515
|
-
scrollContainerRefCurrent?.removeEventListener('scroll', onScroll);
|
|
1525
|
+
scrollContainerRefCurrent?.removeEventListener('scroll', onScroll, true);
|
|
1516
1526
|
};
|
|
1517
|
-
}, [onScroll,
|
|
1527
|
+
}, [onScroll, setInitialized]);
|
|
1518
1528
|
React.useLayoutEffect(() => {
|
|
1519
1529
|
if (!scrollContainerRef.current)
|
|
1520
1530
|
return;
|
|
1521
|
-
if (props.
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1531
|
+
if (props.data !== syncedDataPropRef.current)
|
|
1532
|
+
return;
|
|
1533
|
+
// Resizing or changing the data also changes commitScroll. Only a changed
|
|
1534
|
+
// scroll prop should override the user's current position.
|
|
1535
|
+
const nextTop = props.scrollTop !== scrollPropsRef.current.top
|
|
1536
|
+
? props.scrollTop ?? latestScrollRef.current.top
|
|
1537
|
+
: latestScrollRef.current.top;
|
|
1538
|
+
const nextLeft = props.scrollLeft !== scrollPropsRef.current.left
|
|
1539
|
+
? props.scrollLeft ?? latestScrollRef.current.left
|
|
1540
|
+
: latestScrollRef.current.left;
|
|
1541
|
+
scrollPropsRef.current = { top: props.scrollTop, left: props.scrollLeft };
|
|
1542
|
+
commitScroll(nextTop, nextLeft);
|
|
1543
|
+
}, [commitScroll, data, props.data, props.scrollLeft, props.scrollTop]);
|
|
1544
|
+
React.useLayoutEffect(() => {
|
|
1545
|
+
if (!rowScrollRequest)
|
|
1546
|
+
return;
|
|
1547
|
+
// Prop data reaches the per-grid store in an effect. Wait for that render
|
|
1548
|
+
// so appending a row and requesting it in the same event is supported.
|
|
1549
|
+
if (props.data !== syncedDataPropRef.current)
|
|
1550
|
+
return;
|
|
1551
|
+
setRowScrollRequest(null);
|
|
1552
|
+
const { rowIndex, align } = rowScrollRequest;
|
|
1553
|
+
if (!Number.isInteger(rowIndex) || rowIndex < frozenRowCount || rowIndex >= data.length)
|
|
1554
|
+
return;
|
|
1555
|
+
if (scrollableBodyHeight <= 0)
|
|
1556
|
+
return;
|
|
1557
|
+
const rowTop = (rowIndex - frozenRowCount) * trHeight;
|
|
1558
|
+
const rowBottom = rowTop + trHeight;
|
|
1559
|
+
const { top, left } = latestScrollRef.current;
|
|
1560
|
+
let nextTop = top;
|
|
1561
|
+
if (align === 'start')
|
|
1562
|
+
nextTop = rowTop;
|
|
1563
|
+
else if (align === 'center')
|
|
1564
|
+
nextTop = rowTop - (scrollableBodyHeight - trHeight) / 2;
|
|
1565
|
+
else if (align === 'end')
|
|
1566
|
+
nextTop = rowBottom - scrollableBodyHeight;
|
|
1567
|
+
else if (rowTop < top)
|
|
1568
|
+
nextTop = rowTop;
|
|
1569
|
+
else if (rowBottom > top + scrollableBodyHeight)
|
|
1570
|
+
nextTop = rowBottom - scrollableBodyHeight;
|
|
1571
|
+
commitScroll(nextTop, left);
|
|
1572
|
+
}, [commitScroll, data, frozenRowCount, props.data, rowScrollRequest, scrollableBodyHeight, trHeight]);
|
|
1528
1573
|
useEffect(() => {
|
|
1574
|
+
latestScrollRef.current = { top: scrollTop, left: scrollLeft };
|
|
1529
1575
|
latestCommittedScrollRef.current = { top: scrollTop, left: scrollLeft };
|
|
1530
1576
|
}, [scrollTop, scrollLeft]);
|
|
1531
1577
|
useEffect(() => {
|
|
@@ -1606,6 +1652,7 @@ function Table(props) {
|
|
|
1606
1652
|
searchOpen,
|
|
1607
1653
|
searchOptions,
|
|
1608
1654
|
trHeight,
|
|
1655
|
+
verticalScrollState: logicalVerticalScrollState,
|
|
1609
1656
|
viewportInsets: keyboardViewportInsets,
|
|
1610
1657
|
});
|
|
1611
1658
|
keyboardRuntimeRef.current = {
|
|
@@ -1638,6 +1685,7 @@ function Table(props) {
|
|
|
1638
1685
|
searchOpen,
|
|
1639
1686
|
searchOptions,
|
|
1640
1687
|
trHeight,
|
|
1688
|
+
verticalScrollState: logicalVerticalScrollState,
|
|
1641
1689
|
viewportInsets: keyboardViewportInsets,
|
|
1642
1690
|
};
|
|
1643
1691
|
useEffect(() => {
|
|
@@ -1653,7 +1701,7 @@ function Table(props) {
|
|
|
1653
1701
|
}
|
|
1654
1702
|
};
|
|
1655
1703
|
const moveWithArrowKey = (key, extendSelection, toBoundary = false) => {
|
|
1656
|
-
const { activeCell, cellInteractionSession, cellNavigationOptions, cellSelectionEnabled, columns, commitScroll, dataLength, frozenColumnIndex, frozenRowCount, moveActiveCell, trHeight, viewportInsets, } = keyboardRuntimeRef.current;
|
|
1704
|
+
const { activeCell, cellInteractionSession, cellNavigationOptions, cellSelectionEnabled, columns, commitScroll, dataLength, frozenColumnIndex, frozenRowCount, moveActiveCell, trHeight, verticalScrollState, viewportInsets, } = keyboardRuntimeRef.current;
|
|
1657
1705
|
const container = containerRef.current;
|
|
1658
1706
|
const activeElement = document.activeElement;
|
|
1659
1707
|
if (!container ||
|
|
@@ -1687,6 +1735,7 @@ function Table(props) {
|
|
|
1687
1735
|
frozenRowCount,
|
|
1688
1736
|
columns,
|
|
1689
1737
|
rowHeight: trHeight,
|
|
1738
|
+
verticalScrollState,
|
|
1690
1739
|
viewportInsets,
|
|
1691
1740
|
});
|
|
1692
1741
|
if (result.didScroll) {
|
|
@@ -1718,7 +1767,7 @@ function Table(props) {
|
|
|
1718
1767
|
arrowRepeatFrame = requestAnimationFrame(runArrowRepeatFrame);
|
|
1719
1768
|
};
|
|
1720
1769
|
const handleKeyDown = (evt) => {
|
|
1721
|
-
const { activeCell, cellInteractionSession, cellNavigationOptions, cellSelectionEnabled, clearCellSelection, clearCellSelectionOnEscape, columns, commitCheckboxCell, commitScroll, copySelectedCells, data, dataLength, editable, editItemIndex, endCellSelectionDrag, frozenColumnIndex, frozenRowCount, height, handleClick, moveActiveCell, openCellContextMenu, selectAllCells, setEditItem, requestSearchOpen, searchOpen, searchOptions, trHeight, viewportInsets, } = keyboardRuntimeRef.current;
|
|
1770
|
+
const { activeCell, cellInteractionSession, cellNavigationOptions, cellSelectionEnabled, clearCellSelection, clearCellSelectionOnEscape, columns, commitCheckboxCell, commitScroll, copySelectedCells, data, dataLength, editable, editItemIndex, endCellSelectionDrag, frozenColumnIndex, frozenRowCount, height, handleClick, moveActiveCell, openCellContextMenu, selectAllCells, setEditItem, requestSearchOpen, searchOpen, searchOptions, trHeight, verticalScrollState, viewportInsets, } = keyboardRuntimeRef.current;
|
|
1722
1771
|
const container = containerRef.current;
|
|
1723
1772
|
const activeElement = document.activeElement;
|
|
1724
1773
|
if (!container || (activeElement !== container && !container.contains(activeElement)))
|
|
@@ -1846,6 +1895,7 @@ function Table(props) {
|
|
|
1846
1895
|
frozenRowCount,
|
|
1847
1896
|
columns,
|
|
1848
1897
|
rowHeight: trHeight,
|
|
1898
|
+
verticalScrollState,
|
|
1849
1899
|
viewportInsets,
|
|
1850
1900
|
});
|
|
1851
1901
|
if (result.didScroll) {
|
|
@@ -1889,6 +1939,7 @@ function Table(props) {
|
|
|
1889
1939
|
frozenRowCount,
|
|
1890
1940
|
columns,
|
|
1891
1941
|
rowHeight: trHeight,
|
|
1942
|
+
verticalScrollState,
|
|
1892
1943
|
viewportInsets,
|
|
1893
1944
|
});
|
|
1894
1945
|
if (result.didScroll) {
|
|
@@ -1916,6 +1967,7 @@ function Table(props) {
|
|
|
1916
1967
|
frozenRowCount,
|
|
1917
1968
|
columns,
|
|
1918
1969
|
rowHeight: trHeight,
|
|
1970
|
+
verticalScrollState,
|
|
1919
1971
|
viewportInsets,
|
|
1920
1972
|
});
|
|
1921
1973
|
if (result.didScroll) {
|
|
@@ -2022,7 +2074,7 @@ function Table(props) {
|
|
|
2022
2074
|
event.stopPropagation();
|
|
2023
2075
|
}
|
|
2024
2076
|
} },
|
|
2025
|
-
React.createElement(ScrollPlane, { style: { height: scrollPlaneHeight, minWidth: scrollPlaneMinWidth } },
|
|
2077
|
+
React.createElement(ScrollPlane, { style: { height: scrollPlaneHeight, minWidth: scrollPlaneMinWidth }, "data-bgrid-virtual-scroll-window": virtualScrollWindowMetrics.enabled ? 'true' : undefined, "data-bgrid-logical-height": virtualScrollWindowMetrics.logicalContentHeight, "data-bgrid-physical-height": virtualScrollWindowMetrics.physicalContentHeight, "data-bgrid-logical-scroll-top": Math.round(scrollTop), "data-bgrid-virtual-scroll-base": virtualScrollWindowMetrics.enabled ? Math.round(virtualScrollBase) : undefined },
|
|
2026
2078
|
React.createElement(HeaderContainer, { style: { height: headerHeight, top: 0 }, role: 'rfdg-header-container' },
|
|
2027
2079
|
(frozenColumnsWidth ?? 0) > 0 && (React.createElement(FrozenHeader, { style: { width: frozenColumnsWidth }, role: 'rfdg-frozen-header' },
|
|
2028
2080
|
React.createElement(TableHeadFrozen, { container: containerRef }))),
|
|
@@ -2033,7 +2085,7 @@ function Table(props) {
|
|
|
2033
2085
|
React.createElement(TableSummaryFrozen, { position: 'top' }))),
|
|
2034
2086
|
React.createElement(Summary, { style: { left: frozenColumnsWidth }, role: 'rfdg-summary' },
|
|
2035
2087
|
React.createElement(TableSummary, { position: 'top' })))),
|
|
2036
|
-
React.createElement("div", { className: 'bgrid-body-scroll-content', style: { height:
|
|
2088
|
+
React.createElement("div", { className: 'bgrid-body-scroll-content', style: { height: virtualScrollWindowMetrics.physicalContentHeight } },
|
|
2037
2089
|
frozenRowCount > 0 && (React.createElement("div", { className: 'bgrid-frozen-rows-layer', style: { height: frozenRowsHeight, top: stickyTopHeight }, "data-bgrid-row-band": 'frozen' },
|
|
2038
2090
|
(frozenColumnsWidth ?? 0) > 0 && (React.createElement("div", { className: 'bgrid-frozen-rows-left bgrid-frozen-column-boundary', style: { width: frozenColumnsWidth }, role: 'rfdg-frozen-rows-left' },
|
|
2039
2091
|
React.createElement(TableBodyFrozen, { scrollContainerRef: scrollContainerRef, rowRange: frozenRowRange, role: 'rfdg-body-top-frozen', quadrant: 'top-left', allowRowReorder: false }),
|
|
@@ -2041,17 +2093,19 @@ function Table(props) {
|
|
|
2041
2093
|
React.createElement("div", { className: 'bgrid-frozen-rows-main', style: { left: frozenColumnsWidth }, role: 'rfdg-frozen-rows-main' },
|
|
2042
2094
|
React.createElement(TableBody, { scrollContainerRef: scrollContainerRef, rowRange: frozenRowRange, role: 'rfdg-body-top', quadrant: 'top-main', allowRowReorder: false }),
|
|
2043
2095
|
renderSelectionOverlay('top-main')))),
|
|
2044
|
-
React.createElement("div", { className: 'bgrid-scrollable-rows-layer', style: { height:
|
|
2096
|
+
React.createElement("div", { className: 'bgrid-scrollable-rows-layer', style: { height: physicalScrollableRowsHeight }, "data-bgrid-row-band": 'scrollable' },
|
|
2045
2097
|
(frozenColumnsWidth ?? 0) > 0 && (React.createElement(FrozenScrollContent, { style: {
|
|
2046
2098
|
width: frozenColumnsWidth,
|
|
2047
|
-
height:
|
|
2099
|
+
height: physicalScrollableRowsHeight,
|
|
2048
2100
|
}, role: 'rfdg-frozen-scroll-container' },
|
|
2049
2101
|
React.createElement(TableBodyFrozen, { scrollContainerRef: scrollContainerRef, rowRange: scrollableRowRange, style: frozenScrollableBodyStyle, quadrant: 'body-left', onRowReorderPointerDown: rowReorderController.onPointerDown, onRowReorderKeyDown: rowReorderController.onKeyDown }),
|
|
2050
2102
|
renderSelectionOverlay('body-left'))),
|
|
2051
2103
|
React.createElement(ScrollContent, { style: {
|
|
2052
2104
|
left: frozenColumnsWidth,
|
|
2053
|
-
paddingTop:
|
|
2054
|
-
|
|
2105
|
+
paddingTop: virtualScrollWindowMetrics.enabled
|
|
2106
|
+
? visibleScrollableRows.paddingTop - virtualScrollBase
|
|
2107
|
+
: visibleScrollableRows.paddingTop,
|
|
2108
|
+
height: physicalScrollableRowsHeight,
|
|
2055
2109
|
} },
|
|
2056
2110
|
React.createElement(TableBody, { scrollContainerRef: scrollContainerRef, rowRange: scrollableRowRange, quadrant: 'body-main' }),
|
|
2057
2111
|
renderSelectionOverlay('body-main')))),
|
|
@@ -2141,7 +2195,7 @@ function getCellPositionFromPointer({ clientX, clientY, bodyContainer, scrollCon
|
|
|
2141
2195
|
const rowIndex = metrics.frozenRowCount > 0 && clientY < frozenRowsBottom
|
|
2142
2196
|
? clamp(Math.floor((clientY - bodyRect.top) / metrics.trHeight), 0, metrics.frozenRowCount - 1)
|
|
2143
2197
|
: clamp(metrics.frozenRowCount +
|
|
2144
|
-
Math.floor((
|
|
2198
|
+
Math.floor((metrics.scrollTop +
|
|
2145
2199
|
clamp(clientY - scrollRect.top, 0, Math.max(scrollContainer.clientHeight - 1, 0))) /
|
|
2146
2200
|
metrics.trHeight), 0, metrics.dataLength - 1);
|
|
2147
2201
|
const columnIndex = getColumnIndexFromPointer({
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
2
|
import { clipCellSelectionFragment, } from '../../utils/cellSelectionGeometry';
|
|
3
|
-
export function CellSelectionOverlayLayer({ quadrant, selectionFragments, activeFragments, activeFill, activeRing, viewport, }) {
|
|
3
|
+
export function CellSelectionOverlayLayer({ quadrant, selectionFragments, activeFragments, activeFill, activeRing, viewport, offsetTop = 0, }) {
|
|
4
4
|
const quadrantSelectionFragments = getVisibleFragments(selectionFragments, quadrant, viewport);
|
|
5
5
|
const quadrantActiveFragments = getVisibleFragments(activeFragments, quadrant, viewport);
|
|
6
6
|
if (quadrantSelectionFragments.length === 0 && quadrantActiveFragments.length === 0)
|
|
7
7
|
return null;
|
|
8
8
|
return (React.createElement("div", { className: 'bgrid-cell-selection-overlay-layer', "data-bgrid-selection-quadrant": quadrant, "aria-hidden": 'true' },
|
|
9
|
-
quadrantSelectionFragments.map(fragment => (React.createElement("div", { key: `selection-${fragment.rangeIndex}`, className: 'bgrid-cell-selection-fragment', "data-bgrid-selection-fragment": 'true', "data-bgrid-selection-range-index": fragment.rangeIndex, "data-edge-top": fragment.edges.top ? 'true' : undefined, "data-edge-right": fragment.edges.right ? 'true' : undefined, "data-edge-bottom": fragment.edges.bottom ? 'true' : undefined, "data-edge-left": fragment.edges.left ? 'true' : undefined, style: getFragmentStyle(fragment) }))),
|
|
9
|
+
quadrantSelectionFragments.map(fragment => (React.createElement("div", { key: `selection-${fragment.rangeIndex}`, className: 'bgrid-cell-selection-fragment', "data-bgrid-selection-fragment": 'true', "data-bgrid-selection-range-index": fragment.rangeIndex, "data-edge-top": fragment.edges.top ? 'true' : undefined, "data-edge-right": fragment.edges.right ? 'true' : undefined, "data-edge-bottom": fragment.edges.bottom ? 'true' : undefined, "data-edge-left": fragment.edges.left ? 'true' : undefined, style: getFragmentStyle(fragment, offsetTop) }))),
|
|
10
10
|
(activeFill || activeRing) &&
|
|
11
|
-
quadrantActiveFragments.map(fragment => (React.createElement("div", { key: `active-${fragment.rangeIndex}`, className: 'bgrid-cell-active-fragment', "data-bgrid-active-fragment": 'true', "data-active-fill": activeFill ? 'true' : undefined, "data-active-ring": activeRing ? 'true' : undefined, style: getFragmentStyle(fragment) })))));
|
|
11
|
+
quadrantActiveFragments.map(fragment => (React.createElement("div", { key: `active-${fragment.rangeIndex}`, className: 'bgrid-cell-active-fragment', "data-bgrid-active-fragment": 'true', "data-active-fill": activeFill ? 'true' : undefined, "data-active-ring": activeRing ? 'true' : undefined, style: getFragmentStyle(fragment, offsetTop) })))));
|
|
12
12
|
}
|
|
13
13
|
function getVisibleFragments(fragments, quadrant, viewport) {
|
|
14
14
|
return fragments.flatMap(fragment => {
|
|
@@ -18,10 +18,10 @@ function getVisibleFragments(fragments, quadrant, viewport) {
|
|
|
18
18
|
return visibleFragment ? [visibleFragment] : [];
|
|
19
19
|
});
|
|
20
20
|
}
|
|
21
|
-
function getFragmentStyle(fragment) {
|
|
21
|
+
function getFragmentStyle(fragment, offsetTop) {
|
|
22
22
|
return {
|
|
23
23
|
left: fragment.left,
|
|
24
|
-
top: fragment.top,
|
|
24
|
+
top: fragment.top - offsetTop,
|
|
25
25
|
width: fragment.width,
|
|
26
26
|
height: fragment.height,
|
|
27
27
|
};
|
package/esm/utils/coordinate.js
CHANGED
|
@@ -41,8 +41,8 @@ export function getRowBottom(rowIndex, rowHeight) {
|
|
|
41
41
|
return 0;
|
|
42
42
|
return (rowIndex + 1) * rowHeight;
|
|
43
43
|
}
|
|
44
|
-
export function ensureCellVisible({ cell, scrollContainer, frozenColumnCount = 0, frozenRowCount = 0, columns, rowHeight, viewportInsets, }) {
|
|
45
|
-
const currentScrollTop = scrollContainer?.scrollTop ?? 0;
|
|
44
|
+
export function ensureCellVisible({ cell, scrollContainer, frozenColumnCount = 0, frozenRowCount = 0, columns, rowHeight, verticalScrollState, viewportInsets, }) {
|
|
45
|
+
const currentScrollTop = verticalScrollState?.scrollTop ?? scrollContainer?.scrollTop ?? 0;
|
|
46
46
|
const currentScrollLeft = scrollContainer?.scrollLeft ?? 0;
|
|
47
47
|
let nextScrollTop = currentScrollTop;
|
|
48
48
|
let nextScrollLeft = currentScrollLeft;
|
|
@@ -52,7 +52,7 @@ export function ensureCellVisible({ cell, scrollContainer, frozenColumnCount = 0
|
|
|
52
52
|
const rowTop = getRowTop(targetRowIndex, rowHeight);
|
|
53
53
|
const rowBottom = getRowBottom(targetRowIndex, rowHeight);
|
|
54
54
|
const clientHeight = scrollContainer.clientHeight;
|
|
55
|
-
const scrollHeight = scrollContainer.scrollHeight;
|
|
55
|
+
const scrollHeight = verticalScrollState?.scrollHeight ?? scrollContainer.scrollHeight;
|
|
56
56
|
const maxScrollTop = Math.max(0, scrollHeight - clientHeight);
|
|
57
57
|
if (clientHeight > 0) {
|
|
58
58
|
const topInset = clamp(viewportInsets?.top ?? 0, 0, clientHeight);
|
|
@@ -95,7 +95,7 @@ export function ensureCellVisible({ cell, scrollContainer, frozenColumnCount = 0
|
|
|
95
95
|
const didScrollLeft = nextScrollLeft !== currentScrollLeft;
|
|
96
96
|
const didScroll = didScrollTop || didScrollLeft;
|
|
97
97
|
if (scrollContainer) {
|
|
98
|
-
if (didScrollTop) {
|
|
98
|
+
if (didScrollTop && !verticalScrollState) {
|
|
99
99
|
scrollContainer.scrollTop = nextScrollTop;
|
|
100
100
|
}
|
|
101
101
|
if (didScrollLeft) {
|
package/esm/utils/index.js
CHANGED
|
@@ -15,6 +15,7 @@ export * from './scrollbar';
|
|
|
15
15
|
export * from './number';
|
|
16
16
|
export * from './buildHeaderMatrix';
|
|
17
17
|
export * from './getVisibleScrollableRowRange';
|
|
18
|
+
export * from './virtualScrollWindow';
|
|
18
19
|
export * from './cellEditState';
|
|
19
20
|
export * from './getCellSelectionAxisState';
|
|
20
21
|
export * from './mergedCells';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export const BGRID_MAX_PHYSICAL_SCROLL_HEIGHT = 1000000;
|
|
2
|
+
export function getVirtualScrollWindowMetrics({ logicalContentHeight, viewportHeight, enabled, maxPhysicalHeight = BGRID_MAX_PHYSICAL_SCROLL_HEIGHT, }) {
|
|
3
|
+
const safeViewportHeight = Math.max(viewportHeight, 0);
|
|
4
|
+
const safeLogicalContentHeight = Math.max(logicalContentHeight, safeViewportHeight);
|
|
5
|
+
const safeMaxPhysicalHeight = Math.max(maxPhysicalHeight, safeViewportHeight);
|
|
6
|
+
const windowingEnabled = enabled && safeLogicalContentHeight > safeMaxPhysicalHeight;
|
|
7
|
+
const physicalContentHeight = windowingEnabled ? safeMaxPhysicalHeight : safeLogicalContentHeight;
|
|
8
|
+
return {
|
|
9
|
+
enabled: windowingEnabled,
|
|
10
|
+
logicalContentHeight: safeLogicalContentHeight,
|
|
11
|
+
physicalContentHeight,
|
|
12
|
+
logicalMaxScroll: Math.max(safeLogicalContentHeight - safeViewportHeight, 0),
|
|
13
|
+
physicalMaxScroll: Math.max(physicalContentHeight - safeViewportHeight, 0),
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export function getLogicalScrollTop(base, physicalScrollTop, metrics) {
|
|
17
|
+
return clamp(base + physicalScrollTop, 0, metrics.logicalMaxScroll);
|
|
18
|
+
}
|
|
19
|
+
export function getVirtualScrollWindowPosition(logicalScrollTop, metrics) {
|
|
20
|
+
const logicalTop = clamp(logicalScrollTop, 0, metrics.logicalMaxScroll);
|
|
21
|
+
if (!metrics.enabled) {
|
|
22
|
+
return {
|
|
23
|
+
base: 0,
|
|
24
|
+
physicalScrollTop: logicalTop,
|
|
25
|
+
logicalScrollTop: logicalTop,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const preferredPhysicalTop = metrics.physicalMaxScroll / 2;
|
|
29
|
+
const maxBase = Math.max(metrics.logicalMaxScroll - metrics.physicalMaxScroll, 0);
|
|
30
|
+
const base = clamp(logicalTop - preferredPhysicalTop, 0, maxBase);
|
|
31
|
+
return {
|
|
32
|
+
base,
|
|
33
|
+
physicalScrollTop: logicalTop - base,
|
|
34
|
+
logicalScrollTop: logicalTop,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function rebaseVirtualScrollWindow(base, physicalScrollTop, metrics) {
|
|
38
|
+
const logicalScrollTop = getLogicalScrollTop(base, physicalScrollTop, metrics);
|
|
39
|
+
if (!metrics.enabled || metrics.physicalMaxScroll <= 0) {
|
|
40
|
+
return getVirtualScrollWindowPosition(logicalScrollTop, metrics);
|
|
41
|
+
}
|
|
42
|
+
const lowerGuard = metrics.physicalMaxScroll * 0.25;
|
|
43
|
+
const upperGuard = metrics.physicalMaxScroll * 0.75;
|
|
44
|
+
const maxBase = Math.max(metrics.logicalMaxScroll - metrics.physicalMaxScroll, 0);
|
|
45
|
+
const canMoveBackward = base > 0;
|
|
46
|
+
const canMoveForward = base < maxBase;
|
|
47
|
+
if ((physicalScrollTop < lowerGuard && canMoveBackward) ||
|
|
48
|
+
(physicalScrollTop > upperGuard && canMoveForward)) {
|
|
49
|
+
return getVirtualScrollWindowPosition(logicalScrollTop, metrics);
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
base,
|
|
53
|
+
physicalScrollTop: clamp(physicalScrollTop, 0, metrics.physicalMaxScroll),
|
|
54
|
+
logicalScrollTop,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function clamp(value, min, max) {
|
|
58
|
+
return Math.min(Math.max(value, min), max);
|
|
59
|
+
}
|