beautiful-grid 1.0.1 → 1.0.3

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 CHANGED
@@ -5,7 +5,7 @@
5
5
  [![NPM version](https://img.shields.io/npm/v/beautiful-grid.svg?style=flat)](https://npmjs.org/package/beautiful-grid)
6
6
  [![NPM downloads](https://img.shields.io/npm/dm/beautiful-grid.svg?style=flat)](https://npmjs.org/package/beautiful-grid)
7
7
  [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)
8
- [![Coverage](https://img.shields.io/badge/coverage-84.51%25-brightgreen.svg)](https://github.com/axisj/beautiful-grid/actions/workflows/tests.yml)
8
+ [![Coverage](https://img.shields.io/badge/coverage-85.02%25-brightgreen.svg)](https://github.com/axisj/beautiful-grid/actions/workflows/tests.yml)
9
9
 
10
10
  BeautifulGrid is a high-performance, feature-packed, and beautifully designed open-source React Data Grid for data-heavy business applications. It combines zero-runtime-CSS styling with virtual scrolling, spreadsheet-like cell selection and clipboard operations, built-in and plugin cell editing, multi-level grouped headers, filtering & sorting toolboxes, server/client pagination, pivot table transforms, row reordering, and flexible theming.
11
11
 
@@ -319,6 +319,25 @@ function Table(props) {
319
319
  var trHeight = itemHeight + itemPadding * 2;
320
320
  var scrollableBodyHeight = Math.max(contentBodyHeight - frozenRowsHeight, 0);
321
321
  var mainViewportWidth = Math.max(width - (frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0), 0);
322
+ var logicalBodyContentHeight = data.length * trHeight;
323
+ var virtualScrollWindowMetrics = React.useMemo(function () {
324
+ return (0, utils_1.getVirtualScrollWindowMetrics)({
325
+ logicalContentHeight: logicalBodyContentHeight,
326
+ viewportHeight: contentBodyHeight,
327
+ enabled: scrollbar.variant !== 'native',
328
+ });
329
+ }, [contentBodyHeight, logicalBodyContentHeight, scrollbar.variant]);
330
+ var _18 = __read(React.useState(0), 2), virtualScrollBase = _18[0], setVirtualScrollBase = _18[1];
331
+ var virtualScrollBaseRef = (0, react_1.useRef)(0);
332
+ var containerRef = (0, react_1.useRef)(null);
333
+ var searchPopoverRef = (0, react_1.useRef)(null);
334
+ var editorPortalRef = (0, react_1.useRef)(null);
335
+ var bodyContainerRef = (0, react_1.useRef)(null);
336
+ var scrollContainerRef = (0, react_1.useRef)(null);
337
+ var logicalVerticalScrollState = React.useMemo(function () { return ({
338
+ scrollTop: scrollTop,
339
+ scrollHeight: virtualScrollWindowMetrics.logicalContentHeight,
340
+ }); }, [scrollTop, virtualScrollWindowMetrics.logicalContentHeight]);
322
341
  var hasMultiCellSelection = React.useMemo(function () {
323
342
  return cellSelectionRanges.length > 1 ||
324
343
  cellSelectionRanges.some(function (range) {
@@ -418,7 +437,7 @@ function Table(props) {
418
437
  top: topQuadrant ? 0 : scrollTop,
419
438
  width: leftQuadrant ? frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0 : mainViewportWidth,
420
439
  height: topQuadrant ? frozenRowsHeight : scrollableBodyHeight,
421
- } }));
440
+ }, offsetTop: !topQuadrant && virtualScrollWindowMetrics.enabled ? virtualScrollBase : 0 }));
422
441
  };
423
442
  var visibleScrollableRows = React.useMemo(function () {
424
443
  var _a;
@@ -436,15 +455,14 @@ function Table(props) {
436
455
  startRowIndex: visibleScrollableRows.startRowIndex,
437
456
  endRowIndex: visibleScrollableRows.endRowIndex,
438
457
  }); }, [visibleScrollableRows.endRowIndex, visibleScrollableRows.startRowIndex]);
439
- var frozenScrollableBodyStyle = React.useMemo(function () { return ({ top: visibleScrollableRows.paddingTop }); }, [visibleScrollableRows.paddingTop]);
440
- var containerRef = (0, react_1.useRef)(null);
441
- var searchPopoverRef = (0, react_1.useRef)(null);
458
+ var frozenScrollableBodyStyle = React.useMemo(function () { return ({
459
+ top: virtualScrollWindowMetrics.enabled
460
+ ? visibleScrollableRows.paddingTop - virtualScrollBase
461
+ : visibleScrollableRows.paddingTop,
462
+ }); }, [virtualScrollBase, virtualScrollWindowMetrics.enabled, visibleScrollableRows.paddingTop]);
442
463
  var warnedContextMenuIdsRef = (0, react_1.useRef)(new Set());
443
464
  var warnedContextMenuFactoryRef = (0, react_1.useRef)(false);
444
- var editorPortalRef = (0, react_1.useRef)(null);
445
465
  var editorPortalContext = React.useMemo(function () { return ({ gridRef: containerRef, portalRef: editorPortalRef }); }, []);
446
- var bodyContainerRef = (0, react_1.useRef)(null);
447
- var scrollContainerRef = (0, react_1.useRef)(null);
448
466
  var rowReorderController = (0, useRowReorderController_1.useRowReorderController)({
449
467
  containerRef: containerRef,
450
468
  bodyContainerRef: bodyContainerRef,
@@ -556,7 +574,8 @@ function Table(props) {
556
574
  var stickyBottomHeight = (summary === null || summary === void 0 ? void 0 : summary.position) === 'bottom' ? summaryHeight : 0;
557
575
  var stickyFixedHeight = stickyTopHeight + stickyBottomHeight;
558
576
  var scrollViewportHeight = contentBodyHeight + stickyFixedHeight;
559
- var scrollPlaneHeight = stickyFixedHeight + frozenRowsHeight + visibleScrollableRows.scrollContentHeight;
577
+ var physicalScrollableRowsHeight = Math.max(virtualScrollWindowMetrics.physicalContentHeight - frozenRowsHeight, 0);
578
+ var scrollPlaneHeight = stickyFixedHeight + virtualScrollWindowMetrics.physicalContentHeight;
560
579
  var scrollPlaneContentWidth = (frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0) + scrollableColumnsWidth;
561
580
  var scrollPlaneWidth = Math.max(width, scrollPlaneContentWidth);
562
581
  var customVerticalScrollbarGutter = scrollbar.vertical.visible && scrollbar.variant === 'classic'
@@ -567,7 +586,16 @@ function Table(props) {
567
586
  var scrollPlaneMinWidth = customVerticalScrollbarGutter
568
587
  ? "max(100%, calc(".concat(scrollPlaneContentWidth, "px + ").concat(customVerticalScrollbarGutter, "))")
569
588
  : scrollPlaneWidth;
570
- var scrollbarMetrics = (0, scrollbar_1.useScrollbarMetrics)(scrollContainerRef, [data.length, itemHeight, itemPadding, contentBodyHeight, width], scrollPlaneContentWidth, stickyFixedHeight);
589
+ var measuredScrollbarMetrics = (0, scrollbar_1.useScrollbarMetrics)(scrollContainerRef, [data.length, itemHeight, itemPadding, contentBodyHeight, width], scrollPlaneContentWidth, stickyFixedHeight);
590
+ var scrollbarMetrics = React.useMemo(function () {
591
+ return virtualScrollWindowMetrics.enabled
592
+ ? __assign(__assign({}, measuredScrollbarMetrics), { vertical: {
593
+ viewportSize: contentBodyHeight,
594
+ contentSize: virtualScrollWindowMetrics.logicalContentHeight,
595
+ maxScroll: virtualScrollWindowMetrics.logicalMaxScroll,
596
+ hasOverflow: virtualScrollWindowMetrics.logicalMaxScroll > 1,
597
+ } }) : measuredScrollbarMetrics;
598
+ }, [contentBodyHeight, measuredScrollbarMetrics, virtualScrollWindowMetrics]);
571
599
  var measuredVerticalScrollbarGutter = customVerticalScrollbarGutter && scrollbarMetrics.horizontal.hasOverflow
572
600
  ? Math.max(scrollbarMetrics.horizontal.contentSize - scrollPlaneContentWidth, 0)
573
601
  : 0;
@@ -587,11 +615,6 @@ function Table(props) {
587
615
  scrollContainerRef.current.scrollLeft = offset;
588
616
  }
589
617
  }, []);
590
- var handleScrollTopChange = (0, react_1.useCallback)(function (offset) {
591
- if (scrollContainerRef.current) {
592
- scrollContainerRef.current.scrollTop = offset;
593
- }
594
- }, []);
595
618
  var latestScrollRef = (0, react_1.useRef)({ top: scrollTop, left: scrollLeft });
596
619
  var latestCommittedScrollRef = (0, react_1.useRef)({ top: scrollTop, left: scrollLeft });
597
620
  var scrollRafRef = (0, react_1.useRef)(null);
@@ -608,6 +631,7 @@ function Table(props) {
608
631
  frozenRowCount: frozenRowCount,
609
632
  frozenColumnsWidth: frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0,
610
633
  frozenRowsHeight: frozenRowsHeight,
634
+ scrollTop: scrollTop,
611
635
  trHeight: trHeight,
612
636
  });
613
637
  (0, react_1.useEffect)(function () {
@@ -619,9 +643,10 @@ function Table(props) {
619
643
  frozenRowCount: frozenRowCount,
620
644
  frozenColumnsWidth: frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0,
621
645
  frozenRowsHeight: frozenRowsHeight,
646
+ scrollTop: scrollTop,
622
647
  trHeight: trHeight,
623
648
  };
624
- }, [columns, data.length, props.frozenColumnIndex, frozenColumnsWidth, frozenRowCount, frozenRowsHeight, trHeight]);
649
+ }, [columns, data.length, props.frozenColumnIndex, frozenColumnsWidth, frozenRowCount, frozenRowsHeight, scrollTop, trHeight]);
625
650
  var markScrollActive = (0, react_1.useCallback)(function () {
626
651
  var _a;
627
652
  (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.setAttribute('data-bgrid-scrolling', 'true');
@@ -633,6 +658,28 @@ function Table(props) {
633
658
  (_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.removeAttribute('data-bgrid-scrolling');
634
659
  }, SCROLL_IDLE_DELAY);
635
660
  }, []);
661
+ var commitScroll = (0, react_1.useCallback)(function (top, left) {
662
+ var scrollContainer = scrollContainerRef.current;
663
+ if (!scrollContainer)
664
+ return;
665
+ var position = (0, utils_1.getVirtualScrollWindowPosition)(top, virtualScrollWindowMetrics);
666
+ if (virtualScrollBaseRef.current !== position.base) {
667
+ virtualScrollBaseRef.current = position.base;
668
+ setVirtualScrollBase(position.base);
669
+ }
670
+ if (scrollContainer.scrollTop !== position.physicalScrollTop) {
671
+ scrollContainer.scrollTop = position.physicalScrollTop;
672
+ }
673
+ if (scrollContainer.scrollLeft !== left)
674
+ scrollContainer.scrollLeft = left;
675
+ latestScrollRef.current = { top: position.logicalScrollTop, left: left };
676
+ latestCommittedScrollRef.current = { top: position.logicalScrollTop, left: left };
677
+ setScroll(position.logicalScrollTop, left);
678
+ }, [setScroll, virtualScrollWindowMetrics]);
679
+ var handleScrollTopChange = (0, react_1.useCallback)(function (offset) {
680
+ var _a, _b;
681
+ commitScroll(offset, (_b = (_a = scrollContainerRef.current) === null || _a === void 0 ? void 0 : _a.scrollLeft) !== null && _b !== void 0 ? _b : scrollLeft);
682
+ }, [commitScroll, scrollLeft]);
636
683
  var scheduleScrollFrame = (0, react_1.useCallback)(function () {
637
684
  if (scrollRafRef.current !== null)
638
685
  return;
@@ -642,91 +689,42 @@ function Table(props) {
642
689
  var scrollContainer = scrollContainerRef.current;
643
690
  if (!scrollContainer)
644
691
  return;
645
- var scrollTop = scrollContainer.scrollTop, scrollLeft = scrollContainer.scrollLeft;
646
- latestScrollRef.current = { top: scrollTop, left: scrollLeft };
692
+ var physicalScrollTop = scrollContainer.scrollTop, scrollLeft = scrollContainer.scrollLeft;
693
+ var position = (0, utils_1.rebaseVirtualScrollWindow)(virtualScrollBaseRef.current, physicalScrollTop, virtualScrollWindowMetrics);
694
+ if (virtualScrollBaseRef.current !== position.base) {
695
+ virtualScrollBaseRef.current = position.base;
696
+ setVirtualScrollBase(position.base);
697
+ }
698
+ if (physicalScrollTop !== position.physicalScrollTop) {
699
+ scrollContainer.scrollTop = position.physicalScrollTop;
700
+ }
701
+ var logicalScrollTop = position.logicalScrollTop;
702
+ latestScrollRef.current = { top: logicalScrollTop, left: scrollLeft };
647
703
  var _b = latestCommittedScrollRef.current, committedTop = _b.top, committedLeft = _b.left;
648
- if (committedTop !== scrollTop || committedLeft !== scrollLeft) {
649
- latestCommittedScrollRef.current = { top: scrollTop, left: scrollLeft };
650
- setScroll(scrollTop, scrollLeft);
704
+ if (committedTop !== logicalScrollTop || committedLeft !== scrollLeft) {
705
+ latestCommittedScrollRef.current = { top: logicalScrollTop, left: scrollLeft };
706
+ setScroll(logicalScrollTop, scrollLeft);
651
707
  }
652
708
  if ((_a = containerRef.current) === null || _a === void 0 ? void 0 : _a.hasAttribute('data-bgrid-scrolling')) {
653
709
  scrollRafRef.current = requestAnimationFrame(pollNativeScrollPosition);
654
710
  }
655
711
  };
656
712
  scrollRafRef.current = requestAnimationFrame(pollNativeScrollPosition);
657
- }, [setScroll]);
713
+ }, [setScroll, virtualScrollWindowMetrics]);
658
714
  var onScroll = (0, react_1.useCallback)(function () {
659
715
  if (scrollContainerRef.current) {
660
- var _a = scrollContainerRef.current, scrollTop_1 = _a.scrollTop, scrollLeft_1 = _a.scrollLeft;
661
- latestScrollRef.current = { top: scrollTop_1, left: scrollLeft_1 };
662
716
  markScrollActive();
663
717
  scheduleScrollFrame();
664
718
  }
665
719
  }, [markScrollActive, scheduleScrollFrame]);
666
- var onWheel = (0, react_1.useCallback)(function (evt) {
667
- var scrollContainer = scrollContainerRef.current;
668
- if (!scrollContainer)
669
- return;
670
- var delta = { x: 0, y: 0 };
671
- if (evt.detail) {
672
- delta.y = evt.detail * 10;
673
- }
674
- else if (typeof evt.deltaY === 'undefined') {
675
- delta.y = -evt.wheelDelta;
676
- delta.x = 0;
677
- }
678
- else {
679
- delta.y = evt.deltaY;
680
- delta.x = evt.deltaX;
681
- }
682
- if (evt.deltaMode === 1) {
683
- delta.y = delta.y * 16;
684
- delta.x = delta.x * 16;
685
- }
686
- else if (evt.deltaMode === 2) {
687
- delta.y = delta.y * scrollContainer.clientHeight;
688
- delta.x = delta.x * scrollContainer.clientWidth;
689
- }
690
- var maxScrollTop = Math.max(scrollContainer.scrollHeight - scrollContainer.clientHeight, 0);
691
- var maxScrollLeft = Math.max(scrollContainer.scrollWidth - scrollContainer.clientWidth, 0);
692
- var nextScrollTop = Math.min(maxScrollTop, Math.max(0, scrollContainer.scrollTop + delta.y));
693
- var nextScrollLeft = Math.min(maxScrollLeft, Math.max(0, scrollContainer.scrollLeft + delta.x));
694
- var didScrollTop = nextScrollTop !== scrollContainer.scrollTop;
695
- var didScrollLeft = nextScrollLeft !== scrollContainer.scrollLeft;
696
- var isVertical = Math.abs(delta.y) > Math.abs(delta.x);
697
- if (isVertical) {
698
- if (!didScrollTop)
699
- return;
700
- }
701
- else {
702
- if (!didScrollLeft)
703
- return;
704
- }
705
- evt.preventDefault();
706
- if (didScrollTop)
707
- scrollContainer.scrollTop = nextScrollTop;
708
- if (didScrollLeft)
709
- scrollContainer.scrollLeft = nextScrollLeft;
710
- }, []);
711
720
  var resetScrollPosition = (0, react_1.useCallback)(function (type) {
712
- var _a, _b;
713
- if (type === 'all' || type === 'left')
714
- (_a = scrollContainerRef.current) === null || _a === void 0 ? void 0 : _a.scrollTo({ left: 0 });
715
- if (type === 'all' || type === 'top')
716
- (_b = scrollContainerRef.current) === null || _b === void 0 ? void 0 : _b.scrollTo({ top: 0 });
717
- }, []);
718
- var commitScroll = (0, react_1.useCallback)(function (top, left) {
719
721
  var scrollContainer = scrollContainerRef.current;
720
722
  if (!scrollContainer)
721
723
  return;
722
- if (scrollContainer.scrollTop !== top)
723
- scrollContainer.scrollTop = top;
724
- if (scrollContainer.scrollLeft !== left)
725
- scrollContainer.scrollLeft = left;
726
- latestScrollRef.current = { top: top, left: left };
727
- latestCommittedScrollRef.current = { top: top, left: left };
728
- setScroll(top, left);
729
- }, [setScroll]);
724
+ var nextTop = type === 'all' || type === 'top' ? 0 : latestScrollRef.current.top;
725
+ var nextLeft = type === 'all' || type === 'left' ? 0 : scrollContainer.scrollLeft;
726
+ commitScroll(nextTop, nextLeft);
727
+ }, [commitScroll]);
730
728
  (0, react_1.useEffect)(function () {
731
729
  var _a, _b;
732
730
  if (!searchOpen || activeSearchMatchIndex === undefined)
@@ -751,6 +749,7 @@ function Table(props) {
751
749
  frozenRowCount: frozenRowCount,
752
750
  columns: columns,
753
751
  rowHeight: trHeight,
752
+ verticalScrollState: logicalVerticalScrollState,
754
753
  viewportInsets: viewportInsets,
755
754
  });
756
755
  if (result.didScroll)
@@ -760,6 +759,7 @@ function Table(props) {
760
759
  columns,
761
760
  commitScroll,
762
761
  frozenRowCount,
762
+ logicalVerticalScrollState,
763
763
  props.frozenColumnIndex,
764
764
  searchMatches,
765
765
  searchOpen,
@@ -992,18 +992,18 @@ function Table(props) {
992
992
  if (!dragState || !pointer || !scrollContainer || !bodyContainer)
993
993
  return;
994
994
  var delta = getSelectionAutoScrollDelta(pointer.clientX, pointer.clientY, bodyContainer, scrollContainer);
995
- var maxScrollTop = Math.max(scrollContainer.scrollHeight - scrollContainer.clientHeight, 0);
995
+ var maxScrollTop = virtualScrollWindowMetrics.logicalMaxScroll;
996
996
  var maxScrollLeft = Math.max(scrollContainer.scrollWidth - scrollContainer.clientWidth, 0);
997
- var nextScrollTop = (0, coordinate_1.clamp)(scrollContainer.scrollTop + delta.y, 0, maxScrollTop);
997
+ var nextScrollTop = (0, coordinate_1.clamp)(latestScrollRef.current.top + delta.y, 0, maxScrollTop);
998
998
  var nextScrollLeft = (0, coordinate_1.clamp)(scrollContainer.scrollLeft + delta.x, 0, maxScrollLeft);
999
- if (nextScrollTop !== scrollContainer.scrollTop || nextScrollLeft !== scrollContainer.scrollLeft) {
999
+ if (nextScrollTop !== latestScrollRef.current.top || nextScrollLeft !== scrollContainer.scrollLeft) {
1000
1000
  commitScroll(nextScrollTop, nextScrollLeft);
1001
1001
  }
1002
1002
  updateSelectionByPointer(pointer.clientX, pointer.clientY);
1003
1003
  if (cellSelectionDragRef.current && (delta.x !== 0 || delta.y !== 0)) {
1004
1004
  selectionAutoScrollRafRef.current = requestAnimationFrame(runSelectionAutoScroll);
1005
1005
  }
1006
- }, [commitScroll, updateSelectionByPointer]);
1006
+ }, [commitScroll, updateSelectionByPointer, virtualScrollWindowMetrics.logicalMaxScroll]);
1007
1007
  var selectAllCells = (0, react_1.useCallback)(function () {
1008
1008
  if (!cellSelectionEnabled)
1009
1009
  return false;
@@ -1249,7 +1249,9 @@ function Table(props) {
1249
1249
  clipboardRow.forEach(function (clipboardValue, columnOffset) {
1250
1250
  var columnIndex = startColumnIndex + columnOffset;
1251
1251
  var column = columns[columnIndex];
1252
- if (!column || column.editable === false)
1252
+ // Pasting mutates row data without opening an editor, so only columns
1253
+ // explicitly opted into editing may receive clipboard values.
1254
+ if (!column || column.editable !== true)
1253
1255
  return;
1254
1256
  var logicalCell = (0, utils_1.resolveLogicalCell)(logicalResolutionData, props.cellMergeOptions, {
1255
1257
  rowIndex: rowIndex,
@@ -1691,8 +1693,6 @@ function Table(props) {
1691
1693
  if (scrollContainerRefCurrent) {
1692
1694
  scrollContainerRefCurrent.removeEventListener('scroll', onScroll);
1693
1695
  scrollContainerRefCurrent.addEventListener('scroll', onScroll, { passive: true, capture: true });
1694
- // scrollContainerRefCurrent.removeEventListener('wheel', onWheel);
1695
- // scrollContainerRefCurrent.addEventListener('wheel', onWheel, { passive: false, capture: true });
1696
1696
  }
1697
1697
  return function () {
1698
1698
  if (scrollRafRef.current !== null) {
@@ -1706,18 +1706,17 @@ function Table(props) {
1706
1706
  containerRefCurrent === null || containerRefCurrent === void 0 ? void 0 : containerRefCurrent.removeAttribute('data-bgrid-scrolling');
1707
1707
  scrollContainerRefCurrent === null || scrollContainerRefCurrent === void 0 ? void 0 : scrollContainerRefCurrent.removeEventListener('scroll', onScroll);
1708
1708
  };
1709
- }, [onScroll, onWheel, setInitialized]);
1709
+ }, [onScroll, setInitialized]);
1710
1710
  React.useLayoutEffect(function () {
1711
+ var _a, _b;
1711
1712
  if (!scrollContainerRef.current)
1712
1713
  return;
1713
- if (props.scrollLeft !== undefined) {
1714
- scrollContainerRef.current.scrollLeft = props.scrollLeft;
1715
- }
1716
- if (props.scrollTop !== undefined) {
1717
- scrollContainerRef.current.scrollTop = props.scrollTop;
1718
- }
1719
- }, [props.scrollLeft, props.scrollTop]);
1714
+ var nextTop = (_a = props.scrollTop) !== null && _a !== void 0 ? _a : latestScrollRef.current.top;
1715
+ var nextLeft = (_b = props.scrollLeft) !== null && _b !== void 0 ? _b : latestScrollRef.current.left;
1716
+ commitScroll(nextTop, nextLeft);
1717
+ }, [commitScroll, props.scrollLeft, props.scrollTop]);
1720
1718
  (0, react_1.useEffect)(function () {
1719
+ latestScrollRef.current = { top: scrollTop, left: scrollLeft };
1721
1720
  latestCommittedScrollRef.current = { top: scrollTop, left: scrollLeft };
1722
1721
  }, [scrollTop, scrollLeft]);
1723
1722
  (0, react_1.useEffect)(function () {
@@ -1799,6 +1798,7 @@ function Table(props) {
1799
1798
  searchOpen: searchOpen,
1800
1799
  searchOptions: searchOptions,
1801
1800
  trHeight: trHeight,
1801
+ verticalScrollState: logicalVerticalScrollState,
1802
1802
  viewportInsets: keyboardViewportInsets,
1803
1803
  });
1804
1804
  keyboardRuntimeRef.current = {
@@ -1831,6 +1831,7 @@ function Table(props) {
1831
1831
  searchOpen: searchOpen,
1832
1832
  searchOptions: searchOptions,
1833
1833
  trHeight: trHeight,
1834
+ verticalScrollState: logicalVerticalScrollState,
1834
1835
  viewportInsets: keyboardViewportInsets,
1835
1836
  };
1836
1837
  (0, react_1.useEffect)(function () {
@@ -1847,7 +1848,7 @@ function Table(props) {
1847
1848
  };
1848
1849
  var moveWithArrowKey = function (key, extendSelection, toBoundary) {
1849
1850
  if (toBoundary === void 0) { toBoundary = false; }
1850
- var _a = keyboardRuntimeRef.current, activeCell = _a.activeCell, cellInteractionSession = _a.cellInteractionSession, cellNavigationOptions = _a.cellNavigationOptions, cellSelectionEnabled = _a.cellSelectionEnabled, columns = _a.columns, commitScroll = _a.commitScroll, dataLength = _a.dataLength, frozenColumnIndex = _a.frozenColumnIndex, frozenRowCount = _a.frozenRowCount, moveActiveCell = _a.moveActiveCell, trHeight = _a.trHeight, viewportInsets = _a.viewportInsets;
1851
+ var _a = keyboardRuntimeRef.current, activeCell = _a.activeCell, cellInteractionSession = _a.cellInteractionSession, cellNavigationOptions = _a.cellNavigationOptions, cellSelectionEnabled = _a.cellSelectionEnabled, columns = _a.columns, commitScroll = _a.commitScroll, dataLength = _a.dataLength, frozenColumnIndex = _a.frozenColumnIndex, frozenRowCount = _a.frozenRowCount, moveActiveCell = _a.moveActiveCell, trHeight = _a.trHeight, verticalScrollState = _a.verticalScrollState, viewportInsets = _a.viewportInsets;
1851
1852
  var container = containerRef.current;
1852
1853
  var activeElement = document.activeElement;
1853
1854
  if (!container ||
@@ -1881,6 +1882,7 @@ function Table(props) {
1881
1882
  frozenRowCount: frozenRowCount,
1882
1883
  columns: columns,
1883
1884
  rowHeight: trHeight,
1885
+ verticalScrollState: verticalScrollState,
1884
1886
  viewportInsets: viewportInsets,
1885
1887
  });
1886
1888
  if (result.didScroll) {
@@ -1914,7 +1916,7 @@ function Table(props) {
1914
1916
  };
1915
1917
  var handleKeyDown = function (evt) {
1916
1918
  var _a, _b, _c, _d, _e;
1917
- var _f = keyboardRuntimeRef.current, activeCell = _f.activeCell, cellInteractionSession = _f.cellInteractionSession, cellNavigationOptions = _f.cellNavigationOptions, cellSelectionEnabled = _f.cellSelectionEnabled, clearCellSelection = _f.clearCellSelection, clearCellSelectionOnEscape = _f.clearCellSelectionOnEscape, columns = _f.columns, commitCheckboxCell = _f.commitCheckboxCell, commitScroll = _f.commitScroll, copySelectedCells = _f.copySelectedCells, data = _f.data, dataLength = _f.dataLength, editable = _f.editable, editItemIndex = _f.editItemIndex, endCellSelectionDrag = _f.endCellSelectionDrag, frozenColumnIndex = _f.frozenColumnIndex, frozenRowCount = _f.frozenRowCount, height = _f.height, handleClick = _f.handleClick, moveActiveCell = _f.moveActiveCell, openCellContextMenu = _f.openCellContextMenu, selectAllCells = _f.selectAllCells, setEditItem = _f.setEditItem, requestSearchOpen = _f.requestSearchOpen, searchOpen = _f.searchOpen, searchOptions = _f.searchOptions, trHeight = _f.trHeight, viewportInsets = _f.viewportInsets;
1919
+ var _f = keyboardRuntimeRef.current, activeCell = _f.activeCell, cellInteractionSession = _f.cellInteractionSession, cellNavigationOptions = _f.cellNavigationOptions, cellSelectionEnabled = _f.cellSelectionEnabled, clearCellSelection = _f.clearCellSelection, clearCellSelectionOnEscape = _f.clearCellSelectionOnEscape, columns = _f.columns, commitCheckboxCell = _f.commitCheckboxCell, commitScroll = _f.commitScroll, copySelectedCells = _f.copySelectedCells, data = _f.data, dataLength = _f.dataLength, editable = _f.editable, editItemIndex = _f.editItemIndex, endCellSelectionDrag = _f.endCellSelectionDrag, frozenColumnIndex = _f.frozenColumnIndex, frozenRowCount = _f.frozenRowCount, height = _f.height, handleClick = _f.handleClick, moveActiveCell = _f.moveActiveCell, openCellContextMenu = _f.openCellContextMenu, selectAllCells = _f.selectAllCells, setEditItem = _f.setEditItem, requestSearchOpen = _f.requestSearchOpen, searchOpen = _f.searchOpen, searchOptions = _f.searchOptions, trHeight = _f.trHeight, verticalScrollState = _f.verticalScrollState, viewportInsets = _f.viewportInsets;
1918
1920
  var container = containerRef.current;
1919
1921
  var activeElement = document.activeElement;
1920
1922
  if (!container || (activeElement !== container && !container.contains(activeElement)))
@@ -2043,6 +2045,7 @@ function Table(props) {
2043
2045
  frozenRowCount: frozenRowCount,
2044
2046
  columns: columns,
2045
2047
  rowHeight: trHeight,
2048
+ verticalScrollState: verticalScrollState,
2046
2049
  viewportInsets: viewportInsets,
2047
2050
  });
2048
2051
  if (result.didScroll) {
@@ -2086,6 +2089,7 @@ function Table(props) {
2086
2089
  frozenRowCount: frozenRowCount,
2087
2090
  columns: columns,
2088
2091
  rowHeight: trHeight,
2092
+ verticalScrollState: verticalScrollState,
2089
2093
  viewportInsets: viewportInsets,
2090
2094
  });
2091
2095
  if (result.didScroll) {
@@ -2113,6 +2117,7 @@ function Table(props) {
2113
2117
  frozenRowCount: frozenRowCount,
2114
2118
  columns: columns,
2115
2119
  rowHeight: trHeight,
2120
+ verticalScrollState: verticalScrollState,
2116
2121
  viewportInsets: viewportInsets,
2117
2122
  });
2118
2123
  if (result.didScroll) {
@@ -2220,7 +2225,7 @@ function Table(props) {
2220
2225
  event.stopPropagation();
2221
2226
  }
2222
2227
  } },
2223
- React.createElement(ScrollPlane, { style: { height: scrollPlaneHeight, minWidth: scrollPlaneMinWidth } },
2228
+ 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 },
2224
2229
  React.createElement(HeaderContainer, { style: { height: headerHeight, top: 0 }, role: 'rfdg-header-container' },
2225
2230
  (frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0) > 0 && (React.createElement(FrozenHeader, { style: { width: frozenColumnsWidth }, role: 'rfdg-frozen-header' },
2226
2231
  React.createElement(TableHeadFrozen_1.default, { container: containerRef }))),
@@ -2231,7 +2236,7 @@ function Table(props) {
2231
2236
  React.createElement(TableSummaryFronzen_1.TableSummaryFrozen, { position: 'top' }))),
2232
2237
  React.createElement(Summary, { style: { left: frozenColumnsWidth }, role: 'rfdg-summary' },
2233
2238
  React.createElement(TableSummary_1.TableSummary, { position: 'top' })))),
2234
- React.createElement("div", { className: 'bgrid-body-scroll-content', style: { height: frozenRowsHeight + visibleScrollableRows.scrollContentHeight } },
2239
+ React.createElement("div", { className: 'bgrid-body-scroll-content', style: { height: virtualScrollWindowMetrics.physicalContentHeight } },
2235
2240
  frozenRowCount > 0 && (React.createElement("div", { className: 'bgrid-frozen-rows-layer', style: { height: frozenRowsHeight, top: stickyTopHeight }, "data-bgrid-row-band": 'frozen' },
2236
2241
  (frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0) > 0 && (React.createElement("div", { className: 'bgrid-frozen-rows-left bgrid-frozen-column-boundary', style: { width: frozenColumnsWidth }, role: 'rfdg-frozen-rows-left' },
2237
2242
  React.createElement(TableBodyFrozen_1.default, { scrollContainerRef: scrollContainerRef, rowRange: frozenRowRange, role: 'rfdg-body-top-frozen', quadrant: 'top-left', allowRowReorder: false }),
@@ -2239,17 +2244,19 @@ function Table(props) {
2239
2244
  React.createElement("div", { className: 'bgrid-frozen-rows-main', style: { left: frozenColumnsWidth }, role: 'rfdg-frozen-rows-main' },
2240
2245
  React.createElement(TableBody_1.default, { scrollContainerRef: scrollContainerRef, rowRange: frozenRowRange, role: 'rfdg-body-top', quadrant: 'top-main', allowRowReorder: false }),
2241
2246
  renderSelectionOverlay('top-main')))),
2242
- React.createElement("div", { className: 'bgrid-scrollable-rows-layer', style: { height: visibleScrollableRows.scrollContentHeight }, "data-bgrid-row-band": 'scrollable' },
2247
+ React.createElement("div", { className: 'bgrid-scrollable-rows-layer', style: { height: physicalScrollableRowsHeight }, "data-bgrid-row-band": 'scrollable' },
2243
2248
  (frozenColumnsWidth !== null && frozenColumnsWidth !== void 0 ? frozenColumnsWidth : 0) > 0 && (React.createElement(FrozenScrollContent, { style: {
2244
2249
  width: frozenColumnsWidth,
2245
- height: visibleScrollableRows.scrollContentHeight,
2250
+ height: physicalScrollableRowsHeight,
2246
2251
  }, role: 'rfdg-frozen-scroll-container' },
2247
2252
  React.createElement(TableBodyFrozen_1.default, { scrollContainerRef: scrollContainerRef, rowRange: scrollableRowRange, style: frozenScrollableBodyStyle, quadrant: 'body-left', onRowReorderPointerDown: rowReorderController.onPointerDown, onRowReorderKeyDown: rowReorderController.onKeyDown }),
2248
2253
  renderSelectionOverlay('body-left'))),
2249
2254
  React.createElement(ScrollContent, { style: {
2250
2255
  left: frozenColumnsWidth,
2251
- paddingTop: visibleScrollableRows.paddingTop,
2252
- height: visibleScrollableRows.scrollContentHeight,
2256
+ paddingTop: virtualScrollWindowMetrics.enabled
2257
+ ? visibleScrollableRows.paddingTop - virtualScrollBase
2258
+ : visibleScrollableRows.paddingTop,
2259
+ height: physicalScrollableRowsHeight,
2253
2260
  } },
2254
2261
  React.createElement(TableBody_1.default, { scrollContainerRef: scrollContainerRef, rowRange: scrollableRowRange, quadrant: 'body-main' }),
2255
2262
  renderSelectionOverlay('body-main')))),
@@ -2341,7 +2348,7 @@ function getCellPositionFromPointer(_a) {
2341
2348
  var rowIndex = metrics.frozenRowCount > 0 && clientY < frozenRowsBottom
2342
2349
  ? (0, coordinate_1.clamp)(Math.floor((clientY - bodyRect.top) / metrics.trHeight), 0, metrics.frozenRowCount - 1)
2343
2350
  : (0, coordinate_1.clamp)(metrics.frozenRowCount +
2344
- Math.floor((scrollContainer.scrollTop +
2351
+ Math.floor((metrics.scrollTop +
2345
2352
  (0, coordinate_1.clamp)(clientY - scrollRect.top, 0, Math.max(scrollContainer.clientHeight - 1, 0))) /
2346
2353
  metrics.trHeight), 0, metrics.dataLength - 1);
2347
2354
  var columnIndex = getColumnIndexFromPointer({
@@ -37,15 +37,15 @@ exports.CellSelectionOverlayLayer = CellSelectionOverlayLayer;
37
37
  var React = __importStar(require("react"));
38
38
  var cellSelectionGeometry_1 = require("../../utils/cellSelectionGeometry");
39
39
  function CellSelectionOverlayLayer(_a) {
40
- var quadrant = _a.quadrant, selectionFragments = _a.selectionFragments, activeFragments = _a.activeFragments, activeFill = _a.activeFill, activeRing = _a.activeRing, viewport = _a.viewport;
40
+ var quadrant = _a.quadrant, selectionFragments = _a.selectionFragments, activeFragments = _a.activeFragments, activeFill = _a.activeFill, activeRing = _a.activeRing, viewport = _a.viewport, _b = _a.offsetTop, offsetTop = _b === void 0 ? 0 : _b;
41
41
  var quadrantSelectionFragments = getVisibleFragments(selectionFragments, quadrant, viewport);
42
42
  var quadrantActiveFragments = getVisibleFragments(activeFragments, quadrant, viewport);
43
43
  if (quadrantSelectionFragments.length === 0 && quadrantActiveFragments.length === 0)
44
44
  return null;
45
45
  return (React.createElement("div", { className: 'bgrid-cell-selection-overlay-layer', "data-bgrid-selection-quadrant": quadrant, "aria-hidden": 'true' },
46
- quadrantSelectionFragments.map(function (fragment) { return (React.createElement("div", { key: "selection-".concat(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) })); }),
46
+ quadrantSelectionFragments.map(function (fragment) { return (React.createElement("div", { key: "selection-".concat(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) })); }),
47
47
  (activeFill || activeRing) &&
48
- quadrantActiveFragments.map(function (fragment) { return (React.createElement("div", { key: "active-".concat(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) })); })));
48
+ quadrantActiveFragments.map(function (fragment) { return (React.createElement("div", { key: "active-".concat(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) })); })));
49
49
  }
50
50
  function getVisibleFragments(fragments, quadrant, viewport) {
51
51
  return fragments.flatMap(function (fragment) {
@@ -55,10 +55,10 @@ function getVisibleFragments(fragments, quadrant, viewport) {
55
55
  return visibleFragment ? [visibleFragment] : [];
56
56
  });
57
57
  }
58
- function getFragmentStyle(fragment) {
58
+ function getFragmentStyle(fragment, offsetTop) {
59
59
  return {
60
60
  left: fragment.left,
61
- top: fragment.top,
61
+ top: fragment.top - offsetTop,
62
62
  width: fragment.width,
63
63
  height: fragment.height,
64
64
  };
@@ -53,10 +53,10 @@ function getRowBottom(rowIndex, rowHeight) {
53
53
  return (rowIndex + 1) * rowHeight;
54
54
  }
55
55
  function ensureCellVisible(_a) {
56
- var _b, _c, _d, _e, _f, _g;
57
- var cell = _a.cell, scrollContainer = _a.scrollContainer, _h = _a.frozenColumnCount, frozenColumnCount = _h === void 0 ? 0 : _h, _j = _a.frozenRowCount, frozenRowCount = _j === void 0 ? 0 : _j, columns = _a.columns, rowHeight = _a.rowHeight, viewportInsets = _a.viewportInsets;
58
- var currentScrollTop = (_b = scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollTop) !== null && _b !== void 0 ? _b : 0;
59
- var currentScrollLeft = (_c = scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollLeft) !== null && _c !== void 0 ? _c : 0;
56
+ var _b, _c, _d, _e, _f, _g, _h, _j;
57
+ var cell = _a.cell, scrollContainer = _a.scrollContainer, _k = _a.frozenColumnCount, frozenColumnCount = _k === void 0 ? 0 : _k, _l = _a.frozenRowCount, frozenRowCount = _l === void 0 ? 0 : _l, columns = _a.columns, rowHeight = _a.rowHeight, verticalScrollState = _a.verticalScrollState, viewportInsets = _a.viewportInsets;
58
+ var currentScrollTop = (_c = (_b = verticalScrollState === null || verticalScrollState === void 0 ? void 0 : verticalScrollState.scrollTop) !== null && _b !== void 0 ? _b : scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollTop) !== null && _c !== void 0 ? _c : 0;
59
+ var currentScrollLeft = (_d = scrollContainer === null || scrollContainer === void 0 ? void 0 : scrollContainer.scrollLeft) !== null && _d !== void 0 ? _d : 0;
60
60
  var nextScrollTop = currentScrollTop;
61
61
  var nextScrollLeft = currentScrollLeft;
62
62
  // Vertical scroll calculation
@@ -65,11 +65,11 @@ function ensureCellVisible(_a) {
65
65
  var rowTop = getRowTop(targetRowIndex, rowHeight);
66
66
  var rowBottom = getRowBottom(targetRowIndex, rowHeight);
67
67
  var clientHeight = scrollContainer.clientHeight;
68
- var scrollHeight = scrollContainer.scrollHeight;
68
+ var scrollHeight = (_e = verticalScrollState === null || verticalScrollState === void 0 ? void 0 : verticalScrollState.scrollHeight) !== null && _e !== void 0 ? _e : scrollContainer.scrollHeight;
69
69
  var maxScrollTop = Math.max(0, scrollHeight - clientHeight);
70
70
  if (clientHeight > 0) {
71
- var topInset = clamp((_d = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.top) !== null && _d !== void 0 ? _d : 0, 0, clientHeight);
72
- var bottomInset = clamp((_e = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.bottom) !== null && _e !== void 0 ? _e : 0, 0, Math.max(clientHeight - topInset, 0));
71
+ var topInset = clamp((_f = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.top) !== null && _f !== void 0 ? _f : 0, 0, clientHeight);
72
+ var bottomInset = clamp((_g = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.bottom) !== null && _g !== void 0 ? _g : 0, 0, Math.max(clientHeight - topInset, 0));
73
73
  var viewportTop = currentScrollTop + topInset;
74
74
  var viewportBottom = currentScrollTop + clientHeight - bottomInset;
75
75
  if (rowTop < viewportTop) {
@@ -90,8 +90,8 @@ function ensureCellVisible(_a) {
90
90
  var scrollWidth = scrollContainer.scrollWidth;
91
91
  var maxScrollLeft = Math.max(0, scrollWidth - clientWidth);
92
92
  if (clientWidth > 0) {
93
- var leftInset = clamp((_f = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.left) !== null && _f !== void 0 ? _f : 0, 0, clientWidth);
94
- var rightInset = clamp((_g = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.right) !== null && _g !== void 0 ? _g : 0, 0, Math.max(clientWidth - leftInset, 0));
93
+ var leftInset = clamp((_h = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.left) !== null && _h !== void 0 ? _h : 0, 0, clientWidth);
94
+ var rightInset = clamp((_j = viewportInsets === null || viewportInsets === void 0 ? void 0 : viewportInsets.right) !== null && _j !== void 0 ? _j : 0, 0, Math.max(clientWidth - leftInset, 0));
95
95
  var viewportLeft = currentScrollLeft + leftInset;
96
96
  var viewportRight = currentScrollLeft + clientWidth - rightInset;
97
97
  var visibleWidth = Math.max(clientWidth - leftInset - rightInset, 0);
@@ -108,7 +108,7 @@ function ensureCellVisible(_a) {
108
108
  var didScrollLeft = nextScrollLeft !== currentScrollLeft;
109
109
  var didScroll = didScrollTop || didScrollLeft;
110
110
  if (scrollContainer) {
111
- if (didScrollTop) {
111
+ if (didScrollTop && !verticalScrollState) {
112
112
  scrollContainer.scrollTop = nextScrollTop;
113
113
  }
114
114
  if (didScrollLeft) {
@@ -31,6 +31,7 @@ __exportStar(require("./scrollbar"), exports);
31
31
  __exportStar(require("./number"), exports);
32
32
  __exportStar(require("./buildHeaderMatrix"), exports);
33
33
  __exportStar(require("./getVisibleScrollableRowRange"), exports);
34
+ __exportStar(require("./virtualScrollWindow"), exports);
34
35
  __exportStar(require("./cellEditState"), exports);
35
36
  __exportStar(require("./getCellSelectionAxisState"), exports);
36
37
  __exportStar(require("./mergedCells"), exports);
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BGRID_MAX_PHYSICAL_SCROLL_HEIGHT = void 0;
4
+ exports.getVirtualScrollWindowMetrics = getVirtualScrollWindowMetrics;
5
+ exports.getLogicalScrollTop = getLogicalScrollTop;
6
+ exports.getVirtualScrollWindowPosition = getVirtualScrollWindowPosition;
7
+ exports.rebaseVirtualScrollWindow = rebaseVirtualScrollWindow;
8
+ exports.BGRID_MAX_PHYSICAL_SCROLL_HEIGHT = 1000000;
9
+ function getVirtualScrollWindowMetrics(_a) {
10
+ var logicalContentHeight = _a.logicalContentHeight, viewportHeight = _a.viewportHeight, enabled = _a.enabled, _b = _a.maxPhysicalHeight, maxPhysicalHeight = _b === void 0 ? exports.BGRID_MAX_PHYSICAL_SCROLL_HEIGHT : _b;
11
+ var safeViewportHeight = Math.max(viewportHeight, 0);
12
+ var safeLogicalContentHeight = Math.max(logicalContentHeight, safeViewportHeight);
13
+ var safeMaxPhysicalHeight = Math.max(maxPhysicalHeight, safeViewportHeight);
14
+ var windowingEnabled = enabled && safeLogicalContentHeight > safeMaxPhysicalHeight;
15
+ var physicalContentHeight = windowingEnabled ? safeMaxPhysicalHeight : safeLogicalContentHeight;
16
+ return {
17
+ enabled: windowingEnabled,
18
+ logicalContentHeight: safeLogicalContentHeight,
19
+ physicalContentHeight: physicalContentHeight,
20
+ logicalMaxScroll: Math.max(safeLogicalContentHeight - safeViewportHeight, 0),
21
+ physicalMaxScroll: Math.max(physicalContentHeight - safeViewportHeight, 0),
22
+ };
23
+ }
24
+ function getLogicalScrollTop(base, physicalScrollTop, metrics) {
25
+ return clamp(base + physicalScrollTop, 0, metrics.logicalMaxScroll);
26
+ }
27
+ function getVirtualScrollWindowPosition(logicalScrollTop, metrics) {
28
+ var logicalTop = clamp(logicalScrollTop, 0, metrics.logicalMaxScroll);
29
+ if (!metrics.enabled) {
30
+ return {
31
+ base: 0,
32
+ physicalScrollTop: logicalTop,
33
+ logicalScrollTop: logicalTop,
34
+ };
35
+ }
36
+ var preferredPhysicalTop = metrics.physicalMaxScroll / 2;
37
+ var maxBase = Math.max(metrics.logicalMaxScroll - metrics.physicalMaxScroll, 0);
38
+ var base = clamp(logicalTop - preferredPhysicalTop, 0, maxBase);
39
+ return {
40
+ base: base,
41
+ physicalScrollTop: logicalTop - base,
42
+ logicalScrollTop: logicalTop,
43
+ };
44
+ }
45
+ function rebaseVirtualScrollWindow(base, physicalScrollTop, metrics) {
46
+ var logicalScrollTop = getLogicalScrollTop(base, physicalScrollTop, metrics);
47
+ if (!metrics.enabled || metrics.physicalMaxScroll <= 0) {
48
+ return getVirtualScrollWindowPosition(logicalScrollTop, metrics);
49
+ }
50
+ var lowerGuard = metrics.physicalMaxScroll * 0.25;
51
+ var upperGuard = metrics.physicalMaxScroll * 0.75;
52
+ var maxBase = Math.max(metrics.logicalMaxScroll - metrics.physicalMaxScroll, 0);
53
+ var canMoveBackward = base > 0;
54
+ var canMoveForward = base < maxBase;
55
+ if ((physicalScrollTop < lowerGuard && canMoveBackward) ||
56
+ (physicalScrollTop > upperGuard && canMoveForward)) {
57
+ return getVirtualScrollWindowPosition(logicalScrollTop, metrics);
58
+ }
59
+ return {
60
+ base: base,
61
+ physicalScrollTop: clamp(physicalScrollTop, 0, metrics.physicalMaxScroll),
62
+ logicalScrollTop: logicalScrollTop,
63
+ };
64
+ }
65
+ function clamp(value, min, max) {
66
+ return Math.min(Math.max(value, min), max);
67
+ }