@are-visual/virtual-table 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.esm.js CHANGED
@@ -2,6 +2,28 @@ import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
2
2
  import clsx from 'clsx';
3
3
  import { useRef, createContext, useContext, useMemo, memo, useState, useLayoutEffect, useCallback, useEffect, Fragment, createElement, forwardRef } from 'react';
4
4
 
5
+ function _arrayLikeToArray(r, a) {
6
+ (null == a || a > r.length) && (a = r.length);
7
+ for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
8
+ return n;
9
+ }
10
+ function _createForOfIteratorHelperLoose(r, e) {
11
+ var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
12
+ if (t) return (t = t.call(r)).next.bind(t);
13
+ if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
14
+ t && (r = t);
15
+ var o = 0;
16
+ return function () {
17
+ return o >= r.length ? {
18
+ done: true
19
+ } : {
20
+ done: false,
21
+ value: r[o++]
22
+ };
23
+ };
24
+ }
25
+ throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
26
+ }
5
27
  function _extends() {
6
28
  return _extends = Object.assign ? Object.assign.bind() : function (n) {
7
29
  for (var e = 1; e < arguments.length; e++) {
@@ -20,6 +42,13 @@ function _objectWithoutPropertiesLoose(r, e) {
20
42
  }
21
43
  return t;
22
44
  }
45
+ function _unsupportedIterableToArray(r, a) {
46
+ if (r) {
47
+ if ("string" == typeof r) return _arrayLikeToArray(r, a);
48
+ var t = {}.toString.call(r).slice(8, -1);
49
+ return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0;
50
+ }
51
+ }
23
52
 
24
53
  function shallowEqualObjects(objA, objB) {
25
54
  if (objA === objB) {
@@ -559,6 +588,271 @@ function useTableRowManager() {
559
588
  return context;
560
589
  }
561
590
 
591
+ function onResize(target, callback) {
592
+ if (isWindow(target)) {
593
+ var listener = function listener() {
594
+ callback({
595
+ width: window.innerWidth,
596
+ height: window.innerHeight
597
+ });
598
+ };
599
+ listener();
600
+ window.addEventListener('resize', listener);
601
+ return function () {
602
+ window.removeEventListener('resize', listener);
603
+ };
604
+ } else {
605
+ var observer = new ResizeObserver(function (entries) {
606
+ var rect = entries[0].contentRect;
607
+ callback({
608
+ width: rect.width,
609
+ height: rect.height
610
+ });
611
+ });
612
+ observer.observe(target);
613
+ return function () {
614
+ observer.disconnect();
615
+ };
616
+ }
617
+ }
618
+
619
+ function anchorQuery$1(rects, scrollTop) {
620
+ var left = 0;
621
+ var right = rects.length - 1;
622
+ var index = -1;
623
+ while (left <= right) {
624
+ var mid = Math.floor((left + right) / 2);
625
+ if (rects[mid].bottom > scrollTop) {
626
+ index = mid;
627
+ right = mid - 1;
628
+ } else {
629
+ left = mid + 1;
630
+ }
631
+ }
632
+ if (index === -1) {
633
+ return undefined;
634
+ }
635
+ return rects[index];
636
+ }
637
+ var NormalRowHeightKey = 'NormalRow';
638
+ function useRowVirtualize(options) {
639
+ var getOffsetTop = options.getOffsetTop,
640
+ rowKey = options.rowKey,
641
+ rawData = options.dataSource,
642
+ getScroller = options.getScroller,
643
+ estimateSize = options.estimateSize,
644
+ overscan = options.overscan;
645
+ var _useState = useState(0),
646
+ startIndex = _useState[0],
647
+ setStartIndex = _useState[1];
648
+ var _useState2 = useState(0),
649
+ endIndex = _useState2[0],
650
+ setEndIndex = _useState2[1];
651
+ var dataSlice = useMemo(function () {
652
+ return rawData.slice(startIndex, endIndex);
653
+ }, [rawData, startIndex, endIndex]);
654
+ /**
655
+ * 记录的所有行高信息
656
+ * 一个 Row 可能有多个行高。例如:默认情况下,只有一个行高,展开后,展开面板的高度也被认为是同一个 Row 的
657
+ * 所以可展开时,行高有多个,所有行高之和,则为 Row 的高度
658
+ * 行高之间使用唯一的 key 作为区分
659
+ * Record<rowKey, Map<key, height>>
660
+ */
661
+ var rowHeightByRowKey = useRef(new Map());
662
+ var setRowHeightByRowKey = useCallback(function (rowKey, key, height) {
663
+ var _rowHeightByRowKey$cu;
664
+ var target = (_rowHeightByRowKey$cu = rowHeightByRowKey.current.get(rowKey)) != null ? _rowHeightByRowKey$cu : new Map();
665
+ target.set(key, height);
666
+ rowHeightByRowKey.current.set(rowKey, target);
667
+ }, []);
668
+ var getAllRowHeights = function getAllRowHeights() {
669
+ var heights = [];
670
+ rawData.forEach(function (item) {
671
+ var key = getRowKey(item, rowKey);
672
+ var row = rowHeightByRowKey.current.get(key);
673
+ if (row == null) {
674
+ heights.push(estimateSize);
675
+ } else {
676
+ var height = 0;
677
+ row.forEach(function (x) {
678
+ return height += x;
679
+ });
680
+ heights.push(height);
681
+ }
682
+ });
683
+ return heights;
684
+ };
685
+ // 行高信息(先填充预估高度,DOM渲染后再更新成实际高度)
686
+ var fillRowHeights = function fillRowHeights() {
687
+ if (rawData.length === 0) {
688
+ rowHeightByRowKey.current.clear();
689
+ return;
690
+ }
691
+ rawData.forEach(function (item) {
692
+ var _rowHeightByRowKey$cu2;
693
+ var key = getRowKey(item, rowKey);
694
+ var row = (_rowHeightByRowKey$cu2 = rowHeightByRowKey.current.get(key)) != null ? _rowHeightByRowKey$cu2 : new Map();
695
+ var target = row.get(NormalRowHeightKey);
696
+ if (target == null) {
697
+ row.set(NormalRowHeightKey, estimateSize);
698
+ }
699
+ rowHeightByRowKey.current.set(key, row);
700
+ });
701
+ };
702
+ fillRowHeights();
703
+ // 强制设置类型为 number[],在后面会初始化,只是为了减少 getAllRowHeights 的调用
704
+ var rowHeights = useRef();
705
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
706
+ if (rowHeights.current == null) {
707
+ rowHeights.current = getAllRowHeights();
708
+ } else if (rawData.length !== rowHeights.current.length) {
709
+ // 这个判断条件主要是为了处理:空数据切换为有数据时 tbody 上 padding 缺失的问题
710
+ rowHeights.current = getAllRowHeights();
711
+ }
712
+ // 布局信息(也就是锚点元素需要的信息,top,bottom,height,index)
713
+ var rowRects = useRef([]);
714
+ var updateRowRects = function updateRowRects() {
715
+ var _rawData$reduce = rawData.reduce(function (result, rowData, index) {
716
+ var _rowHeightByRowKey$cu3;
717
+ var key = getRowKey(rowData, rowKey);
718
+ var height = 0;
719
+ (_rowHeightByRowKey$cu3 = rowHeightByRowKey.current.get(key)) == null || _rowHeightByRowKey$cu3.forEach(function (item) {
720
+ height += item;
721
+ });
722
+ var nextTop = result.top + height;
723
+ result.rects.push({
724
+ index: index,
725
+ top: result.top,
726
+ height: height,
727
+ bottom: nextTop
728
+ });
729
+ result.top = nextTop;
730
+ return result;
731
+ }, {
732
+ top: 0,
733
+ rects: []
734
+ }),
735
+ rects = _rawData$reduce.rects;
736
+ rowRects.current = rects;
737
+ };
738
+ /** 刷新布局信息,行高变化的时候调用,要重新组织布局信息(rowRects) */
739
+ var flushLayout = function flushLayout(fn) {
740
+ fn == null || fn();
741
+ updateRowRects();
742
+ // 组件渲染后会触发 flushLayout,表示行高有更新所以需要更新一下 rowHeights
743
+ // 避免展开行之后记录的还是之前的行高信息,否则滚动后底部会出现空白区域
744
+ rowHeights.current = rowRects.current.map(function (x) {
745
+ return x.height;
746
+ });
747
+ };
748
+ // 锚点元素,当前虚拟列表中,最接近滚动容器顶部的元素
749
+ var anchorRef = useRef({
750
+ index: 0,
751
+ height: estimateSize,
752
+ top: 0,
753
+ bottom: estimateSize
754
+ });
755
+ // 用来判断滚动方向
756
+ var scrollTopRef = useRef(0);
757
+ var initial = useRef(false);
758
+ useEffect(function () {
759
+ var container = getScroller();
760
+ if (container == null) return;
761
+ var count = 0;
762
+ var getScrollTop = function getScrollTop() {
763
+ var offsetTop = getOffsetTop();
764
+ var result = 0;
765
+ if (isWindow(container) || isRoot(container)) {
766
+ result = window.scrollY;
767
+ } else {
768
+ var element = getScrollElement(container);
769
+ result = element.scrollTop;
770
+ }
771
+ return Math.max(result - offsetTop, 0);
772
+ };
773
+ var calcCount = function calcCount(scrollerContainerHeight) {
774
+ var prevCount = count;
775
+ count = Math.ceil(scrollerContainerHeight / estimateSize);
776
+ return {
777
+ prevCount: prevCount
778
+ };
779
+ };
780
+ var updateBoundary = function updateBoundary(scrollTop) {
781
+ var anchor = anchorQuery$1(rowRects.current, scrollTop);
782
+ if (anchor != null) {
783
+ anchorRef.current = anchor;
784
+ setStartIndex(Math.max(0, anchor.index - overscan));
785
+ setEndIndex(anchor.index + count + overscan);
786
+ }
787
+ };
788
+ var onScroll = function onScroll(e) {
789
+ var offsetTop = getOffsetTop();
790
+ var scrollElement = getScrollElement(e.target);
791
+ var scrollTop = Math.max(0, scrollElement.scrollTop - offsetTop);
792
+ // 是否为向下滚动
793
+ var isScrollDown = scrollTop > scrollTopRef.current;
794
+ if (isScrollDown) {
795
+ // 如果滚动距离比较小,没有超出锚点元素的边界,就不需要计算 startIndex、endIndex 了
796
+ if (scrollTop > anchorRef.current.bottom) {
797
+ updateBoundary(scrollTop);
798
+ }
799
+ } else {
800
+ if (scrollTop < anchorRef.current.top) {
801
+ updateBoundary(scrollTop);
802
+ }
803
+ }
804
+ scrollTopRef.current = scrollTop;
805
+ };
806
+ var prevHeight = 0;
807
+ var stopListen = onResize(container, function (rect) {
808
+ // 处理父元素 display:none 容器高度丢失,导致显示 row 不准确
809
+ if (rect.height === prevHeight || rect.height === 0) {
810
+ return;
811
+ }
812
+ prevHeight = rect.height;
813
+ calcCount(rect.height);
814
+ var scrollTop = getScrollTop();
815
+ if (!initial.current) {
816
+ initial.current = true;
817
+ var nextStartIndex = 0;
818
+ // 判断一下当前滚动位置,计算 startIndex(场景:SPA 页面切换且渲染非异步数据)
819
+ if (scrollTop >= estimateSize) {
820
+ nextStartIndex = Math.max(Math.floor(scrollTop / estimateSize) - 1 - overscan, 0);
821
+ }
822
+ var nextEndIndex = nextStartIndex + count + overscan;
823
+ setStartIndex(nextStartIndex);
824
+ setEndIndex(nextEndIndex);
825
+ } else {
826
+ updateBoundary(scrollTop);
827
+ }
828
+ });
829
+ container.addEventListener('scroll', onScroll);
830
+ return function () {
831
+ stopListen();
832
+ container.removeEventListener('scroll', onScroll);
833
+ };
834
+ }, [estimateSize, getOffsetTop, getScroller, overscan]);
835
+ var sum = function sum(startIndex, endIndex) {
836
+ return rowHeights.current.slice(startIndex, endIndex).reduce(function (a, b) {
837
+ return a + b;
838
+ }, 0);
839
+ };
840
+ // TODO: React Compiler 测试 topBlank 和 bottomBlank
841
+ var topBlank = sum(0, startIndex);
842
+ var bottomBlank = sum(endIndex);
843
+ return {
844
+ startIndex: startIndex,
845
+ endIndex: endIndex,
846
+ rowHeightList: rowHeights,
847
+ flushLayout: flushLayout,
848
+ rowHeightByRowKey: rowHeightByRowKey,
849
+ setRowHeightByRowKey: setRowHeightByRowKey,
850
+ topBlank: topBlank,
851
+ bottomBlank: bottomBlank,
852
+ dataSlice: dataSlice
853
+ };
854
+ }
855
+
562
856
  // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
563
857
  function useStableFn(callback) {
564
858
  var fn = useRef(null);
@@ -574,6 +868,145 @@ function useStableFn(callback) {
574
868
  return stableFn;
575
869
  }
576
870
 
871
+ var TableInstance = /*#__PURE__*/function () {
872
+ function TableInstance() {
873
+ Object.defineProperty(this, "instance", {
874
+ enumerable: true,
875
+ configurable: true,
876
+ writable: true,
877
+ value: {
878
+ getCurrentProps: function getCurrentProps() {
879
+ throw new Error('getCurrentProps() has not been implemented yet');
880
+ },
881
+ getColumns: function getColumns() {
882
+ throw new Error('getColumns() has not been implemented yet');
883
+ },
884
+ getDataSource: function getDataSource() {
885
+ throw new Error('getDataSource() has not been implemented yet');
886
+ },
887
+ getDOM: function getDOM() {
888
+ throw new Error('getDOM() has not been implemented yet');
889
+ },
890
+ getRowHeightMap: function getRowHeightMap() {
891
+ throw new Error('getRowHeightMap() has not been implemented yet');
892
+ },
893
+ getRowVirtualizeState: function getRowVirtualizeState() {
894
+ throw new Error('getRowVirtualizeState() has not been implemented yet');
895
+ },
896
+ getScrollValueByRowIndex: function getScrollValueByRowIndex() {
897
+ throw new Error('getScrollValueByRowIndex() has not been implemented yet');
898
+ },
899
+ getScrollValueByColumnKey: function getScrollValueByColumnKey() {
900
+ throw new Error('getScrollValueByColumnKey() has not been implemented yet');
901
+ },
902
+ scrollToRow: function scrollToRow() {
903
+ throw new Error('scrollToRow() has not been implemented yet');
904
+ },
905
+ scrollToColumn: function scrollToColumn() {
906
+ throw new Error('scrollToColumn() has not been implemented yet');
907
+ },
908
+ scrollTo: function scrollTo() {
909
+ throw new Error('scrollTo() has not been implemented yet');
910
+ },
911
+ getColumnByKey: function getColumnByKey() {
912
+ throw new Error('getColumnByKey() has not been implemented yet');
913
+ },
914
+ getColumnByIndex: function getColumnByIndex() {
915
+ throw new Error('getColumnByIndex() has not been implemented yet');
916
+ },
917
+ getColumnKeyByIndex: function getColumnKeyByIndex() {
918
+ throw new Error('getColumnKeyByIndex() has not been implemented yet');
919
+ },
920
+ getColumnWidths: function getColumnWidths() {
921
+ throw new Error('getColumnWidths() has not been implemented yet');
922
+ },
923
+ getColumnWidthByKey: function getColumnWidthByKey() {
924
+ throw new Error('getColumnWidthByKey() has not been implemented yet');
925
+ }
926
+ }
927
+ });
928
+ this.getInstance = this.getInstance.bind(this);
929
+ this.getInternalHooks = this.getInternalHooks.bind(this);
930
+ }
931
+ var _proto = TableInstance.prototype;
932
+ _proto.getInternalHooks = function getInternalHooks() {
933
+ var _this = this;
934
+ return {
935
+ implGetCurrentProps: function implGetCurrentProps(fn) {
936
+ _this.instance.getCurrentProps = fn;
937
+ },
938
+ implGetColumns: function implGetColumns(fn) {
939
+ _this.instance.getColumns = fn;
940
+ },
941
+ implGetDataSource: function implGetDataSource(fn) {
942
+ _this.instance.getDataSource = fn;
943
+ },
944
+ implGetDOM: function implGetDOM(fn) {
945
+ _this.instance.getDOM = fn;
946
+ },
947
+ implGetRowVirtualizeState: function implGetRowVirtualizeState(fn) {
948
+ _this.instance.getRowVirtualizeState = fn;
949
+ },
950
+ implGetRowHeightMap: function implGetRowHeightMap(fn) {
951
+ _this.instance.getRowHeightMap = fn;
952
+ },
953
+ implScrollValueByRowIndex: function implScrollValueByRowIndex(fn) {
954
+ _this.instance.getScrollValueByRowIndex = fn;
955
+ },
956
+ implScrollValueByColumnKey: function implScrollValueByColumnKey(fn) {
957
+ _this.instance.getScrollValueByColumnKey = fn;
958
+ },
959
+ implScrollToRow: function implScrollToRow(fn) {
960
+ _this.instance.scrollToRow = fn;
961
+ },
962
+ implScrollToColumn: function implScrollToColumn(fn) {
963
+ _this.instance.scrollToColumn = fn;
964
+ },
965
+ implScrollTo: function implScrollTo(fn) {
966
+ _this.instance.scrollTo = fn;
967
+ },
968
+ implGetColumnByKey: function implGetColumnByKey(fn) {
969
+ _this.instance.getColumnByKey = fn;
970
+ },
971
+ implGetColumnByIndex: function implGetColumnByIndex(fn) {
972
+ _this.instance.getColumnByIndex = fn;
973
+ },
974
+ implGetColumnKeyByIndex: function implGetColumnKeyByIndex(fn) {
975
+ _this.instance.getColumnKeyByIndex = fn;
976
+ },
977
+ implGetColumnWidths: function implGetColumnWidths(fn) {
978
+ _this.instance.getColumnWidths = fn;
979
+ },
980
+ implGetColumnWidthByKey: function implGetColumnWidthByKey(fn) {
981
+ _this.instance.getColumnWidthByKey = fn;
982
+ }
983
+ };
984
+ };
985
+ _proto.getInstance = function getInstance() {
986
+ var _this2 = this;
987
+ return _extends({}, Object.keys(this.instance).reduce(function (result, key) {
988
+ // @ts-expect-error I just don't want to write some boilerplate code, so I use traversal, but there is no problem at runtime
989
+ result[key] = function () {
990
+ var _this2$instance;
991
+ return (_this2$instance = _this2.instance)[key].apply(_this2$instance, arguments);
992
+ };
993
+ return result;
994
+ }, {}), {
995
+ // eslint-disable-next-line @typescript-eslint/unbound-method
996
+ getInternalHooks: this.getInternalHooks
997
+ });
998
+ };
999
+ return TableInstance;
1000
+ }();
1001
+ function useTableInstance(instance) {
1002
+ return useMemo(function () {
1003
+ if (instance == null) {
1004
+ return new TableInstance().getInstance();
1005
+ }
1006
+ return instance;
1007
+ }, [instance]);
1008
+ }
1009
+
577
1010
  /**
578
1011
  * 创建中间件,内部会浅比较 options,只有 options 改变才会返回新的函数。
579
1012
  */
@@ -766,20 +1199,20 @@ function findLastIndex(arr, predicate) {
766
1199
  return result;
767
1200
  }
768
1201
 
769
- var _excluded$1 = ["className", "rowIndex", "rowData", "columns", "onRow", "renderRow", "renderCell"],
1202
+ var _excluded$1 = ["className", "rowKey", "rowIndex", "rowData", "columns", "onRow", "renderRow", "renderCell", "onRefCallback"],
770
1203
  _excluded2 = ["className"];
771
1204
  function Row(props) {
772
1205
  var _onRow;
773
1206
  var className = props.className,
1207
+ rowKey = props.rowKey,
774
1208
  rowIndex = props.rowIndex,
775
1209
  rowData = props.rowData,
776
1210
  columnDescriptor = props.columns,
777
1211
  onRow = props.onRow,
778
1212
  renderRow = props.renderRow,
779
1213
  renderCell = props.renderCell,
1214
+ onRefCallback = props.onRefCallback,
780
1215
  rest = _objectWithoutPropertiesLoose(props, _excluded$1);
781
- var _useTableRowManager = useTableRowManager(),
782
- updateRowHeight = _useTableRowManager.updateRowHeight;
783
1216
  var columns = columnDescriptor.columns,
784
1217
  descriptor = columnDescriptor.descriptor;
785
1218
  var lastFixedLeftColumnIndex = findLastIndex(descriptor, function (x) {
@@ -801,9 +1234,12 @@ function Row(props) {
801
1234
  className: clsx('virtual-table-row', className, extraClassName),
802
1235
  "data-row-index": rowIndex,
803
1236
  ref: function ref(node) {
804
- if (node == null) return;
805
- // 小心陷阱:当 table 父元素为 display: none 时,依然会触发 updateRowHeight 函数,并设置高度为 0
806
- updateRowHeight(rowIndex, rowIndex, node.offsetHeight);
1237
+ onRefCallback == null || onRefCallback({
1238
+ node: node,
1239
+ rowKey: rowKey,
1240
+ rowIndex: rowIndex,
1241
+ rowData: rowData
1242
+ });
807
1243
  },
808
1244
  children: descriptor.map(function (item, index) {
809
1245
  var key = item.key;
@@ -821,6 +1257,7 @@ function Row(props) {
821
1257
  })
822
1258
  })), renderRow, {
823
1259
  columns: columns,
1260
+ rowKey: rowKey,
824
1261
  rowIndex: rowIndex,
825
1262
  rowData: rowData,
826
1263
  columnDescriptor: descriptor
@@ -853,217 +1290,6 @@ function useMergedRef() {
853
1290
  return useCallback(mergeRefs.apply(void 0, refs), refs);
854
1291
  }
855
1292
 
856
- function onResize(target, callback) {
857
- if (isWindow(target)) {
858
- var listener = function listener() {
859
- callback({
860
- width: window.innerWidth,
861
- height: window.innerHeight
862
- });
863
- };
864
- listener();
865
- window.addEventListener('resize', listener);
866
- return function () {
867
- window.removeEventListener('resize', listener);
868
- };
869
- } else {
870
- var observer = new ResizeObserver(function (entries) {
871
- var rect = entries[0].contentRect;
872
- callback({
873
- width: rect.width,
874
- height: rect.height
875
- });
876
- });
877
- observer.observe(target);
878
- return function () {
879
- observer.disconnect();
880
- };
881
- }
882
- }
883
-
884
- function anchorQuery$1(rects, scrollTop) {
885
- var left = 0;
886
- var right = rects.length - 1;
887
- var index = -1;
888
- while (left <= right) {
889
- var mid = Math.floor((left + right) / 2);
890
- if (rects[mid].bottom > scrollTop) {
891
- index = mid;
892
- right = mid - 1;
893
- } else {
894
- left = mid + 1;
895
- }
896
- }
897
- if (index === -1) {
898
- return undefined;
899
- }
900
- return rects[index];
901
- }
902
- function useRowVirtualize(options) {
903
- var getOffsetTop = options.getOffsetTop,
904
- rawData = options.dataSource,
905
- getScroller = options.getScroller,
906
- estimateSize = options.estimateSize,
907
- overscan = options.overscan;
908
- var _useState = useState(0),
909
- startIndex = _useState[0],
910
- setStartIndex = _useState[1];
911
- var _useState2 = useState(0),
912
- endIndex = _useState2[0],
913
- setEndIndex = _useState2[1];
914
- var dataSlice = useMemo(function () {
915
- return rawData.slice(startIndex, endIndex);
916
- }, [rawData, startIndex, endIndex]);
917
- // 行高信息(先填充预估高度,DOM渲染后再更新成实际高度)
918
- var rowHeights = useRef([]);
919
- var fillRowHeights = function fillRowHeights() {
920
- var len = rawData.length;
921
- for (var i = 0; i < len; i++) {
922
- var target = rowHeights.current[i];
923
- // 由于 fillRowHeights 是在渲染阶段调用,防止重复渲染时 estimateSize 覆盖了真实 DOM 的高度
924
- if (target == null) {
925
- rowHeights.current[i] = estimateSize;
926
- }
927
- }
928
- rowHeights.current = rowHeights.current.slice(0, len);
929
- };
930
- fillRowHeights();
931
- // 布局信息(也就是锚点元素需要的信息,top,bottom,height,index)
932
- var rowRects = useRef([]);
933
- var updateRowRectList = function updateRowRectList(shouldSkip) {
934
- if (shouldSkip === void 0) {
935
- shouldSkip = false;
936
- }
937
- if (shouldSkip && rowRects.current.length > 0) {
938
- return;
939
- }
940
- var _rowHeights$current$r = rowHeights.current.reduce(function (result, height, index) {
941
- var nextTop = result.top + height;
942
- result.rects.push({
943
- index: index,
944
- top: result.top,
945
- height: height,
946
- bottom: nextTop
947
- });
948
- result.top = nextTop;
949
- return result;
950
- }, {
951
- top: 0,
952
- rects: []
953
- }),
954
- rects = _rowHeights$current$r.rects;
955
- rowRects.current = rects;
956
- };
957
- var setRowHeight = function setRowHeight(index, height) {
958
- rowHeights.current[index] = height;
959
- };
960
- // 锚点元素,当前虚拟列表中,最接近滚动容器顶部的元素
961
- var anchorRef = useRef({
962
- index: 0,
963
- height: estimateSize,
964
- top: 0,
965
- bottom: estimateSize
966
- });
967
- // 用来判断滚动方向
968
- var scrollTopRef = useRef(0);
969
- var initial = useRef(false);
970
- useEffect(function () {
971
- var container = getScroller();
972
- if (container == null) return;
973
- var count = 0;
974
- var getScrollTop = function getScrollTop() {
975
- var offsetTop = getOffsetTop();
976
- var result = 0;
977
- if (isWindow(container) || isRoot(container)) {
978
- result = window.scrollY;
979
- } else {
980
- var element = getScrollElement(container);
981
- result = element.scrollTop;
982
- }
983
- return Math.max(result - offsetTop, 0);
984
- };
985
- var calcCount = function calcCount(scrollerContainerHeight) {
986
- var prevCount = count;
987
- count = Math.ceil(scrollerContainerHeight / estimateSize);
988
- return {
989
- prevCount: prevCount
990
- };
991
- };
992
- var updateBoundary = function updateBoundary(scrollTop) {
993
- var anchor = anchorQuery$1(rowRects.current, scrollTop);
994
- if (anchor != null) {
995
- anchorRef.current = anchor;
996
- setStartIndex(Math.max(0, anchor.index - overscan));
997
- setEndIndex(anchor.index + count + overscan);
998
- }
999
- };
1000
- var onScroll = function onScroll(e) {
1001
- var offsetTop = getOffsetTop();
1002
- var scrollElement = getScrollElement(e.target);
1003
- var scrollTop = Math.max(0, scrollElement.scrollTop - offsetTop);
1004
- // 是否为向下滚动
1005
- var isScrollDown = scrollTop > scrollTopRef.current;
1006
- if (isScrollDown) {
1007
- // 如果滚动距离比较小,没有超出锚点元素的边界,就不需要计算 startIndex、endIndex 了
1008
- if (scrollTop > anchorRef.current.bottom) {
1009
- updateBoundary(scrollTop);
1010
- }
1011
- } else {
1012
- if (scrollTop < anchorRef.current.top) {
1013
- updateBoundary(scrollTop);
1014
- }
1015
- }
1016
- scrollTopRef.current = scrollTop;
1017
- };
1018
- var prevHeight = 0;
1019
- var stopListen = onResize(container, function (rect) {
1020
- // 处理父元素 display:none 容器高度丢失,导致显示 row 不准确
1021
- if (rect.height === prevHeight || rect.height === 0) {
1022
- return;
1023
- }
1024
- prevHeight = rect.height;
1025
- calcCount(rect.height);
1026
- var scrollTop = getScrollTop();
1027
- if (!initial.current) {
1028
- initial.current = true;
1029
- var nextStartIndex = 0;
1030
- // 判断一下当前滚动位置,计算 startIndex(场景:SPA 页面切换且渲染非异步数据)
1031
- if (scrollTop >= estimateSize) {
1032
- nextStartIndex = Math.max(Math.floor(scrollTop / estimateSize) - 1 - overscan, 0);
1033
- }
1034
- var nextEndIndex = nextStartIndex + count + overscan;
1035
- setStartIndex(nextStartIndex);
1036
- setEndIndex(nextEndIndex);
1037
- } else {
1038
- updateBoundary(scrollTop);
1039
- }
1040
- });
1041
- container.addEventListener('scroll', onScroll);
1042
- return function () {
1043
- stopListen();
1044
- container.removeEventListener('scroll', onScroll);
1045
- };
1046
- }, [estimateSize, getOffsetTop, getScroller, overscan]);
1047
- var sum = function sum(startIndex, endIndex) {
1048
- return rowHeights.current.slice(startIndex, endIndex).reduce(function (a, b) {
1049
- return a + b;
1050
- }, 0);
1051
- };
1052
- // TODO: React Compiler 测试 topBlank 和 bottomBlank
1053
- var topBlank = sum(0, startIndex);
1054
- var bottomBlank = sum(endIndex);
1055
- return {
1056
- startIndex: startIndex,
1057
- endIndex: endIndex,
1058
- rowHeightList: rowHeights,
1059
- updateRowRectList: updateRowRectList,
1060
- setRowHeight: setRowHeight,
1061
- topBlank: topBlank,
1062
- bottomBlank: bottomBlank,
1063
- dataSlice: dataSlice
1064
- };
1065
- }
1066
-
1067
1293
  function TableBody(props) {
1068
1294
  var bodyWrapperRef = props.bodyWrapperRef,
1069
1295
  bodyRootRef = props.bodyRootRef,
@@ -1073,6 +1299,7 @@ function TableBody(props) {
1073
1299
  rawData = props.dataSource,
1074
1300
  columnDescriptor = props.columns,
1075
1301
  rowKey = props.rowKey,
1302
+ instance = props.instance,
1076
1303
  overscan = props.overscan,
1077
1304
  estimateSize = props.estimateSize,
1078
1305
  getScroller = props.getScroller,
@@ -1085,68 +1312,65 @@ function TableBody(props) {
1085
1312
  renderBodyContent = props.renderBodyContent,
1086
1313
  renderRow = props.renderRow,
1087
1314
  renderCell = props.renderCell;
1315
+ var internalHook = instance.getInternalHooks();
1088
1316
  var _useRowVirtualize = useRowVirtualize({
1089
1317
  getOffsetTop: getOffsetTop,
1318
+ rowKey: rowKey,
1090
1319
  dataSource: rawData,
1091
1320
  getScroller: getScroller,
1092
1321
  estimateSize: estimateSize,
1093
1322
  overscan: overscan
1094
1323
  }),
1095
1324
  startIndex = _useRowVirtualize.startIndex,
1325
+ endIndex = _useRowVirtualize.endIndex,
1096
1326
  dataSource = _useRowVirtualize.dataSlice,
1097
- setRowHeight = _useRowVirtualize.setRowHeight,
1098
- updateRowRectList = _useRowVirtualize.updateRowRectList,
1327
+ rowHeightByRowKey = _useRowVirtualize.rowHeightByRowKey,
1328
+ setRowHeightByRowKey = _useRowVirtualize.setRowHeightByRowKey,
1329
+ flushLayout = _useRowVirtualize.flushLayout,
1099
1330
  rowHeightList = _useRowVirtualize.rowHeightList,
1100
1331
  topBlank = _useRowVirtualize.topBlank,
1101
1332
  bottomBlank = _useRowVirtualize.bottomBlank;
1102
- // Record<rowIndex, Map(rowHeight)>
1103
- // 一个 Row 可能有多个行高。例如:默认情况下,只有一个行高,展开后,展开面板的高度也被认为是同一个 Row 的
1104
- // 所以可展开时,行高有多个,所有行高之和,则为 Row 的高度
1105
- // 行高之间使用唯一的 key 作为区分
1106
- var rowHeights = useRef(new Map());
1107
- // 更新行高。一般会在 body 渲染后、展开面板中调用
1108
- var updateRowHeight = useCallback(function (index, key, height) {
1109
- var _rowHeights$current$g;
1110
- var target = (_rowHeights$current$g = rowHeights.current.get(index)) != null ? _rowHeights$current$g : new Map();
1111
- target.set(key, height);
1112
- rowHeights.current.set(index, target);
1113
- }, []);
1114
- var rowManageState = useMemo(function () {
1333
+ internalHook.implGetRowVirtualizeState(function () {
1115
1334
  return {
1116
- updateRowHeight: updateRowHeight,
1117
- getRowHeightList: function getRowHeightList() {
1118
- return rowHeightList.current;
1119
- }
1335
+ startIndex: startIndex,
1336
+ endIndex: endIndex,
1337
+ overscan: overscan,
1338
+ estimateSize: estimateSize
1120
1339
  };
1121
- }, [rowHeightList, updateRowHeight]);
1122
- var columns = columnDescriptor.columns,
1123
- descriptor = columnDescriptor.descriptor;
1340
+ });
1341
+ internalHook.implGetRowHeightMap(function () {
1342
+ return rowHeightByRowKey.current;
1343
+ });
1124
1344
  var tbodyRef = useMergedRef(bodyRef, function (elm) {
1125
1345
  if (elm == null) return;
1126
1346
  var bodyHeight = elm.offsetHeight;
1127
1347
  if (bodyHeight === 0) return;
1128
1348
  // body 的 ref 回调函数中,说明 body 渲染完成,也就意味着所有的 tr 也已经渲染完成,
1129
- // 现在可以获取 tr 的高度,记录准确行高
1130
- var heights = rowHeights.current;
1131
- heights.forEach(function (row, rowIndex) {
1132
- var height = Array.from(row.values()).reduce(function (res, x) {
1133
- return res + x;
1134
- }, 0);
1135
- setRowHeight(rowIndex, height);
1136
- });
1137
- updateRowRectList();
1138
- rowHeights.current.clear();
1349
+ // 现在可以记录所有 tr 的高度
1350
+ flushLayout();
1139
1351
  });
1352
+ // 测量行高
1353
+ var onMeasureRowHeight = useCallback(function (args) {
1354
+ var node = args.node,
1355
+ rowKey = args.rowKey;
1356
+ if (node == null) return;
1357
+ // 小心陷阱:当 table 父元素为 display: none 时,依然会触发,并设置高度为 0
1358
+ setRowHeightByRowKey(rowKey, NormalRowHeightKey, node.offsetHeight);
1359
+ }, [setRowHeightByRowKey]);
1360
+ var columns = columnDescriptor.columns,
1361
+ descriptor = columnDescriptor.descriptor;
1140
1362
  var bodyContent = pipelineRender(dataSource.map(function (e, rowIndex) {
1141
1363
  var key = getRowKey(e, rowKey);
1142
1364
  return jsx(Row$1, {
1143
1365
  className: clsx(rowClassName == null ? void 0 : rowClassName(e, rowIndex)),
1366
+ rowKey: key,
1144
1367
  rowIndex: rowIndex + startIndex,
1145
1368
  rowData: e,
1146
1369
  columns: columnDescriptor,
1147
1370
  onRow: onRow,
1148
1371
  renderRow: renderRow,
1149
- renderCell: renderCell
1372
+ renderCell: renderCell,
1373
+ onRefCallback: onMeasureRowHeight
1150
1374
  }, key);
1151
1375
  }), renderBodyContent, {
1152
1376
  columns: columns,
@@ -1205,6 +1429,43 @@ function TableBody(props) {
1205
1429
  };
1206
1430
  }, [listen, notify]);
1207
1431
  var mergedRef = useMergedRef(wrapperRef, bodyWrapperRef);
1432
+ // 滚动到指定行。注意:如果 estimatedRowHeight 不够准确时,不一定能准确滚动到目标位置
1433
+ internalHook.implScrollValueByRowIndex(function (index) {
1434
+ var scroller = getScroller();
1435
+ if (scroller == null) {
1436
+ return 0;
1437
+ }
1438
+ var _instance$getCurrentP = instance.getCurrentProps(),
1439
+ stickyHeader = _instance$getCurrentP.stickyHeader;
1440
+ var _instance$getDOM = instance.getDOM(),
1441
+ headerWrapper = _instance$getDOM.headerWrapper;
1442
+ var offset = 0;
1443
+ // 没有 sticky,就要计算 header 高度
1444
+ if (stickyHeader == null || stickyHeader === false) {
1445
+ var headerOffsetHeight = headerWrapper == null ? 0 : headerWrapper.offsetHeight;
1446
+ offset = headerOffsetHeight;
1447
+ } else {
1448
+ offset = Number.isFinite(stickyHeader) ? stickyHeader * -1 : 0;
1449
+ }
1450
+ var targetScrollTop = rowHeightList.current.slice(0, index).reduce(function (a, b) {
1451
+ return a + b;
1452
+ }, 0);
1453
+ return targetScrollTop + getOffsetTop() + offset;
1454
+ });
1455
+ internalHook.implScrollToRow(function (index, behavior) {
1456
+ instance.scrollTo({
1457
+ top: instance.getScrollValueByRowIndex(index),
1458
+ behavior: behavior
1459
+ });
1460
+ });
1461
+ var rowManageState = useMemo(function () {
1462
+ return {
1463
+ setRowHeightByRowKey: setRowHeightByRowKey,
1464
+ getRowHeightList: function getRowHeightList() {
1465
+ return rowHeightList.current;
1466
+ }
1467
+ };
1468
+ }, [rowHeightList, setRowHeightByRowKey]);
1208
1469
  return jsx(TableRowManager.Provider, {
1209
1470
  value: rowManageState,
1210
1471
  children: pipelineRender(jsx("div", {
@@ -1634,6 +1895,7 @@ var TableRoot$1 = /*#__PURE__*/forwardRef(TableRoot);
1634
1895
 
1635
1896
  function VirtualTableCore(props, ref) {
1636
1897
  var bodyRootRef = props.bodyRootRef,
1898
+ rawInstance = props.instance,
1637
1899
  className = props.className,
1638
1900
  style = props.style,
1639
1901
  tableBodyClassName = props.tableBodyClassName,
@@ -1659,6 +1921,11 @@ function VirtualTableCore(props, ref) {
1659
1921
  getOffsetTopImpl = props.getOffsetTop,
1660
1922
  _props$virtualHeader = props.virtualHeader,
1661
1923
  virtualHeader = _props$virtualHeader === void 0 ? true : _props$virtualHeader;
1924
+ var instance = useTableInstance(rawInstance);
1925
+ var internalHook = instance.getInternalHooks();
1926
+ internalHook.implGetCurrentProps(function () {
1927
+ return props;
1928
+ });
1662
1929
  var rootNode = useRef(null);
1663
1930
  var headerWrapperRef = useRef(null);
1664
1931
  var bodyWrapperRef = useRef(null);
@@ -1696,7 +1963,8 @@ function VirtualTableCore(props, ref) {
1696
1963
  bodyRef: bodyRef,
1697
1964
  rootRef: rootNode,
1698
1965
  getOffsetTop: getOffsetTop,
1699
- getScroller: getScroller
1966
+ getScroller: getScroller,
1967
+ instance: instance
1700
1968
  }),
1701
1969
  dataSource = _pipeline$use.dataSource,
1702
1970
  pipelineColumns = _pipeline$use.columns,
@@ -1717,6 +1985,12 @@ function VirtualTableCore(props, ref) {
1717
1985
  renderRow = _pipeline$use.renderRow,
1718
1986
  renderCell = _pipeline$use.renderCell,
1719
1987
  onPipelineRow = _pipeline$use.onRow;
1988
+ internalHook.implGetColumns(function () {
1989
+ return pipelineColumns;
1990
+ });
1991
+ internalHook.implGetDataSource(function () {
1992
+ return dataSource;
1993
+ });
1720
1994
  var _useState = useState(function () {
1721
1995
  return new Map();
1722
1996
  }),
@@ -1731,6 +2005,12 @@ function VirtualTableCore(props, ref) {
1731
2005
  columnWidthsRef.current = result;
1732
2006
  setColumnWidths(result);
1733
2007
  }, []);
2008
+ internalHook.implGetColumnWidths(function () {
2009
+ return columnWidthsRef.current;
2010
+ });
2011
+ internalHook.implGetColumnWidthByKey(function (columnKey) {
2012
+ return columnWidthsRef.current.get(columnKey);
2013
+ });
1734
2014
  var tableColumnsContext = useMemo(function () {
1735
2015
  return {
1736
2016
  widthList: columnWidths,
@@ -1778,6 +2058,76 @@ function VirtualTableCore(props, ref) {
1778
2058
  }
1779
2059
  return fullHeaderColumns;
1780
2060
  }, [virtualHeader, fullHeaderColumns, columns]);
2061
+ internalHook.implGetDOM(function () {
2062
+ return {
2063
+ root: rootNode.current,
2064
+ headerWrapper: headerWrapperRef.current,
2065
+ bodyWrapper: bodyWrapperRef.current,
2066
+ bodyRoot: bodyRoot.current,
2067
+ body: bodyRef.current
2068
+ };
2069
+ });
2070
+ internalHook.implScrollTo(function (options) {
2071
+ var left = options.left,
2072
+ top = options.top;
2073
+ if (left != null) {
2074
+ var _bodyWrapperRef$curre;
2075
+ if (process.env.NODE_ENV === 'development') {
2076
+ if (bodyWrapperRef.current == null) {
2077
+ console.error('The bodyWrapper DOM is not obtained, and scrolling is not possible.');
2078
+ }
2079
+ }
2080
+ (_bodyWrapperRef$curre = bodyWrapperRef.current) == null || _bodyWrapperRef$curre.scrollTo(options);
2081
+ }
2082
+ if (top != null) {
2083
+ var scroller = getScroller();
2084
+ if (scroller == null) {
2085
+ console.error('The scroller DOM is not obtained, and scrolling is not possible.');
2086
+ return;
2087
+ }
2088
+ scroller.scrollTo(options);
2089
+ }
2090
+ });
2091
+ internalHook.implScrollValueByColumnKey(function (key) {
2092
+ var scrollLeft = 0;
2093
+ var leftFixedWidth = 0;
2094
+ // pipelineColumns 是一个经过中间件处理的 columns
2095
+ // 因为中间件可能会处理 columns,比如:添加、删除、排序 columns
2096
+ for (var _iterator = _createForOfIteratorHelperLoose(pipelineColumns), _step; !(_step = _iterator()).done;) {
2097
+ var _columnWidths$get;
2098
+ var element = _step.value;
2099
+ var columnKey = getKey(element);
2100
+ var width = (_columnWidths$get = columnWidths.get(columnKey)) != null ? _columnWidths$get : 0;
2101
+ if (isValidFixedLeft(element.fixed)) {
2102
+ leftFixedWidth += width;
2103
+ }
2104
+ if (columnKey === key) {
2105
+ break;
2106
+ }
2107
+ scrollLeft += width;
2108
+ }
2109
+ return scrollLeft - leftFixedWidth;
2110
+ });
2111
+ internalHook.implScrollToColumn(function (key, behavior) {
2112
+ instance.scrollTo({
2113
+ left: instance.getScrollValueByColumnKey(key),
2114
+ behavior: behavior
2115
+ });
2116
+ });
2117
+ internalHook.implGetColumnByKey(function (key) {
2118
+ return pipelineColumns.find(function (x) {
2119
+ return getKey(x) === key;
2120
+ });
2121
+ });
2122
+ internalHook.implGetColumnByIndex(function (index) {
2123
+ return pipelineColumns[index];
2124
+ });
2125
+ internalHook.implGetColumnKeyByIndex(function (index) {
2126
+ var column = instance.getColumnByIndex(index);
2127
+ if (column != null) {
2128
+ return getKey(column);
2129
+ }
2130
+ });
1781
2131
  var contentNode = pipelineRender(jsxs(Fragment$1, {
1782
2132
  children: [jsx(TableHeader$1, {
1783
2133
  wrapperRef: headerWrapperRef,
@@ -1789,6 +2139,7 @@ function VirtualTableCore(props, ref) {
1789
2139
  renderHeaderRow: renderHeaderRow,
1790
2140
  renderHeaderCell: renderHeaderCell
1791
2141
  }), jsx(TableBody, {
2142
+ instance: instance,
1792
2143
  bodyWrapperRef: bodyWrapperRef,
1793
2144
  bodyRootRef: mergedBodyRootRef,
1794
2145
  bodyRef: bodyRef,
@@ -1847,5 +2198,5 @@ if (process.env.NODE_ENV === 'development') {
1847
2198
  }
1848
2199
  var table = /*#__PURE__*/memo(/*#__PURE__*/forwardRef(VirtualTableCore));
1849
2200
 
1850
- export { Colgroup$1 as Colgroup, table as VirtualTable, Cell$1 as VirtualTableCell, Row$1 as VirtualTableRow, createMiddleware, findLastIndex, getKey, getRelativeOffsetTop, getRowKey, getScrollElement, getScrollParent, getScrollTop, isDocument, isRoot, isValidFixed, isValidFixedLeft, isValidFixedRight, isWindow, mergeRefs, onResize, useColumnSizes, useContainerSize, useHorizontalScrollContext, useMergedRef, useShallowMemo, useStableFn, useTablePipeline, useTableRowManager, useTableSticky };
2201
+ export { Colgroup$1 as Colgroup, NormalRowHeightKey, table as VirtualTable, Cell$1 as VirtualTableCell, Row$1 as VirtualTableRow, createMiddleware, findLastIndex, getKey, getRelativeOffsetTop, getRowKey, getScrollElement, getScrollParent, getScrollTop, isDocument, isRoot, isValidFixed, isValidFixedLeft, isValidFixedRight, isWindow, mergeRefs, onResize, useColumnSizes, useContainerSize, useHorizontalScrollContext, useMergedRef, useShallowMemo, useStableFn, useTableInstance, useTablePipeline, useTableRowManager, useTableSticky };
1851
2202
  //# sourceMappingURL=index.esm.js.map