@are-visual/virtual-table 0.0.1 → 0.1.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
@@ -288,22 +288,31 @@ var Cell$1 = /*#__PURE__*/memo(Cell);
288
288
 
289
289
  var Colgroup = function Colgroup(props) {
290
290
  var columns = props.columns,
291
- colRef = props.colRef;
291
+ onColumnSizesMeasure = props.onColumnSizesMeasure;
292
+ var enableMeasure = onColumnSizesMeasure != null;
293
+ var columnSizes = useRef(new Map());
292
294
  return jsx("colgroup", {
293
- children: columns.map(function (item, index) {
295
+ ref: function ref() {
296
+ if (!enableMeasure) return;
297
+ onColumnSizesMeasure(columnSizes.current);
298
+ },
299
+ children: columns.map(function (item) {
294
300
  var key = item.key;
295
301
  if (item.type === 'blank') {
296
302
  return jsx("col", {
303
+ className: "blank",
297
304
  style: {
298
- width: item.width
305
+ width: item.width,
306
+ color: '#f00'
299
307
  }
300
308
  }, key);
301
309
  }
302
310
  var column = item.column;
303
311
  return jsx("col", {
304
- ref: typeof colRef === 'function' ? function (instance) {
305
- colRef(instance, column, index);
306
- } : colRef,
312
+ ref: function ref(node) {
313
+ if (node == null || !enableMeasure) return;
314
+ columnSizes.current.set(key, node.offsetWidth);
315
+ },
307
316
  style: {
308
317
  width: column.width,
309
318
  minWidth: column.minWidth
@@ -464,25 +473,34 @@ function HorizontalScrollContext(props) {
464
473
  var children = props.children;
465
474
  var listenerMap = useRef(new Map());
466
475
  var context = useMemo(function () {
476
+ var skipEvent = new Set();
467
477
  return {
468
478
  listen: function listen(key, listener) {
469
- listenerMap.current.set(key, listener);
479
+ listenerMap.current.set(key, function (scrollLeft, node) {
480
+ listener(scrollLeft, node);
481
+ });
470
482
  return function () {
471
483
  listenerMap.current["delete"](key);
472
484
  };
473
485
  },
474
- notify: function notify(key, scrollLeft, node) {
486
+ notify: function notify(key, options) {
487
+ if (skipEvent.has(key)) {
488
+ skipEvent["delete"](key);
489
+ return;
490
+ }
491
+ var scrollLeft = options.scrollLeft,
492
+ node = options.node;
475
493
  var rAF = window.requestAnimationFrame;
476
494
  if (rAF == null) {
477
495
  rAF = function rAF(fn) {
478
496
  fn();
479
497
  };
480
498
  }
481
- rAF(function () {
482
- listenerMap.current.forEach(function (listener, itemKey) {
483
- if (itemKey !== key) {
484
- listener(scrollLeft, node);
485
- }
499
+ listenerMap.current.forEach(function (listener, itemKey) {
500
+ if (key === itemKey) return;
501
+ skipEvent.add(itemKey);
502
+ rAF(function () {
503
+ listener(scrollLeft(), node);
486
504
  });
487
505
  });
488
506
  }
@@ -518,7 +536,7 @@ function useStableFn(callback) {
518
536
  var fn = useRef(null);
519
537
  useLayoutEffect(function () {
520
538
  fn.current = callback;
521
- }, [callback]);
539
+ });
522
540
  var stableFn = useCallback(function () {
523
541
  for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
524
542
  args[_key] = arguments[_key];
@@ -754,7 +772,7 @@ function Row(props) {
754
772
  ref: function ref(node) {
755
773
  if (node == null) return;
756
774
  // 小心陷阱:当 table 父元素为 display: none 时,依然会触发 updateRowHeight 函数,并设置高度为 0
757
- updateRowHeight(rowIndex, node.offsetHeight);
775
+ updateRowHeight(rowIndex, rowIndex, node.offsetHeight);
758
776
  },
759
777
  children: descriptor.map(function (item, index) {
760
778
  var key = item.key;
@@ -804,16 +822,230 @@ function useMergedRef() {
804
822
  return useCallback(mergeRefs.apply(undefined, refs), refs);
805
823
  }
806
824
 
825
+ function onResize(target, callback) {
826
+ if (isWindow(target)) {
827
+ var listener = function listener() {
828
+ callback({
829
+ width: window.innerWidth,
830
+ height: window.innerHeight
831
+ });
832
+ };
833
+ listener();
834
+ window.addEventListener('resize', listener);
835
+ return function () {
836
+ window.removeEventListener('resize', listener);
837
+ };
838
+ } else {
839
+ var observer = new ResizeObserver(function (entries) {
840
+ var rect = entries[0].contentRect;
841
+ callback({
842
+ width: rect.width,
843
+ height: rect.height
844
+ });
845
+ });
846
+ observer.observe(target);
847
+ return function () {
848
+ observer.disconnect();
849
+ };
850
+ }
851
+ }
852
+
853
+ function anchorQuery$1(rects, scrollTop) {
854
+ var left = 0;
855
+ var right = rects.length - 1;
856
+ var index = -1;
857
+ while (left <= right) {
858
+ var mid = Math.floor((left + right) / 2);
859
+ if (rects[mid].bottom > scrollTop) {
860
+ index = mid;
861
+ right = mid - 1;
862
+ } else {
863
+ left = mid + 1;
864
+ }
865
+ }
866
+ if (index === -1) {
867
+ return undefined;
868
+ }
869
+ return rects[index];
870
+ }
871
+ function useRowVirtualize(options) {
872
+ var getOffsetTop = options.getOffsetTop,
873
+ rawData = options.dataSource,
874
+ getScroller = options.getScroller,
875
+ estimateSize = options.estimateSize,
876
+ overscan = options.overscan;
877
+ var _useState = useState(0),
878
+ startIndex = _useState[0],
879
+ setStartIndex = _useState[1];
880
+ var _useState2 = useState(0),
881
+ endIndex = _useState2[0],
882
+ setEndIndex = _useState2[1];
883
+ var dataSlice = useMemo(function () {
884
+ return rawData.slice(startIndex, endIndex);
885
+ }, [rawData, startIndex, endIndex]);
886
+ // 行高信息(先填充预估高度,DOM渲染后再更新成实际高度)
887
+ var rowHeights = useRef([]);
888
+ var fillRowHeights = function fillRowHeights() {
889
+ var len = rawData.length;
890
+ for (var i = 0; i < len; i++) {
891
+ var target = rowHeights.current[i];
892
+ // 由于 fillRowHeights 是在渲染阶段调用,防止重复渲染时 estimateSize 覆盖了真实 DOM 的高度
893
+ if (target == null) {
894
+ rowHeights.current[i] = estimateSize;
895
+ }
896
+ }
897
+ rowHeights.current = rowHeights.current.slice(0, len);
898
+ };
899
+ fillRowHeights();
900
+ // 布局信息(也就是锚点元素需要的信息,top,bottom,height,index)
901
+ var rowRects = useRef([]);
902
+ var updateRowRectList = function updateRowRectList(shouldSkip) {
903
+ if (shouldSkip === undefined) {
904
+ shouldSkip = false;
905
+ }
906
+ if (shouldSkip && rowRects.current.length > 0) {
907
+ return;
908
+ }
909
+ var _rowHeights$current$r = rowHeights.current.reduce(function (result, height, index) {
910
+ var nextTop = result.top + height;
911
+ result.rects.push({
912
+ index: index,
913
+ top: result.top,
914
+ height: height,
915
+ bottom: nextTop
916
+ });
917
+ result.top = nextTop;
918
+ return result;
919
+ }, {
920
+ top: 0,
921
+ rects: []
922
+ }),
923
+ rects = _rowHeights$current$r.rects;
924
+ rowRects.current = rects;
925
+ };
926
+ var setRowHeight = function setRowHeight(index, height) {
927
+ rowHeights.current[index] = height;
928
+ };
929
+ // 锚点元素,当前虚拟列表中,最接近滚动容器顶部的元素
930
+ var anchorRef = useRef({
931
+ index: 0,
932
+ height: estimateSize,
933
+ top: 0,
934
+ bottom: estimateSize
935
+ });
936
+ // 用来判断滚动方向
937
+ var scrollTopRef = useRef(0);
938
+ var initial = useRef(false);
939
+ useEffect(function () {
940
+ var container = getScroller();
941
+ if (container == null) return;
942
+ var count = 0;
943
+ var getScrollTop = function getScrollTop() {
944
+ var offsetTop = getOffsetTop();
945
+ var result = 0;
946
+ if (isWindow(container) || isRoot(container)) {
947
+ result = window.scrollY;
948
+ } else {
949
+ var element = getScrollElement(container);
950
+ result = element.scrollTop;
951
+ }
952
+ return Math.max(result - offsetTop, 0);
953
+ };
954
+ var calcCount = function calcCount(scrollerContainerHeight) {
955
+ var prevCount = count;
956
+ count = Math.ceil(scrollerContainerHeight / estimateSize);
957
+ return {
958
+ prevCount: prevCount
959
+ };
960
+ };
961
+ var updateBoundary = function updateBoundary(scrollTop) {
962
+ var anchor = anchorQuery$1(rowRects.current, scrollTop);
963
+ if (anchor != null) {
964
+ anchorRef.current = anchor;
965
+ setStartIndex(Math.max(0, anchor.index - overscan));
966
+ setEndIndex(anchor.index + count + overscan);
967
+ }
968
+ };
969
+ var onScroll = function onScroll(e) {
970
+ var offsetTop = getOffsetTop();
971
+ var scrollElement = getScrollElement(e.target);
972
+ var scrollTop = Math.max(0, scrollElement.scrollTop - offsetTop);
973
+ // 是否为向下滚动
974
+ var isScrollDown = scrollTop > scrollTopRef.current;
975
+ if (isScrollDown) {
976
+ // 如果滚动距离比较小,没有超出锚点元素的边界,就不需要计算 startIndex、endIndex 了
977
+ if (scrollTop > anchorRef.current.bottom) {
978
+ updateBoundary(scrollTop);
979
+ }
980
+ } else {
981
+ if (scrollTop < anchorRef.current.top) {
982
+ updateBoundary(scrollTop);
983
+ }
984
+ }
985
+ scrollTopRef.current = scrollTop;
986
+ };
987
+ var prevHeight = 0;
988
+ var stopListen = onResize(container, function (rect) {
989
+ // 处理父元素 display:none 容器高度丢失,导致显示 row 不准确
990
+ if (rect.height === prevHeight || rect.height === 0) {
991
+ return;
992
+ }
993
+ prevHeight = rect.height;
994
+ calcCount(rect.height);
995
+ var scrollTop = getScrollTop();
996
+ if (!initial.current) {
997
+ initial.current = true;
998
+ var nextStartIndex = 0;
999
+ // 判断一下当前滚动位置,计算 startIndex(场景:SPA 页面切换且渲染非异步数据)
1000
+ if (scrollTop >= estimateSize) {
1001
+ nextStartIndex = Math.max(Math.floor(scrollTop / estimateSize) - 1 - overscan, 0);
1002
+ }
1003
+ var nextEndIndex = nextStartIndex + count + overscan;
1004
+ setStartIndex(nextStartIndex);
1005
+ setEndIndex(nextEndIndex);
1006
+ } else {
1007
+ updateBoundary(scrollTop);
1008
+ }
1009
+ });
1010
+ container.addEventListener('scroll', onScroll);
1011
+ return function () {
1012
+ stopListen();
1013
+ container.removeEventListener('scroll', onScroll);
1014
+ };
1015
+ }, [estimateSize, getOffsetTop, getScroller, overscan]);
1016
+ var sum = function sum(startIndex, endIndex) {
1017
+ return rowHeights.current.slice(startIndex, endIndex).reduce(function (a, b) {
1018
+ return a + b;
1019
+ }, 0);
1020
+ };
1021
+ // TODO: React Compiler 测试 topBlank 和 bottomBlank
1022
+ var topBlank = sum(0, startIndex);
1023
+ var bottomBlank = sum(endIndex);
1024
+ return {
1025
+ startIndex: startIndex,
1026
+ endIndex: endIndex,
1027
+ rowHeightList: rowHeights,
1028
+ updateRowRectList: updateRowRectList,
1029
+ setRowHeight: setRowHeight,
1030
+ topBlank: topBlank,
1031
+ bottomBlank: bottomBlank,
1032
+ dataSlice: dataSlice
1033
+ };
1034
+ }
1035
+
807
1036
  function TableBody(props) {
808
1037
  var bodyWrapperRef = props.bodyWrapperRef,
809
1038
  bodyRootRef = props.bodyRootRef,
810
1039
  bodyRef = props.bodyRef,
811
1040
  className = props.className,
812
1041
  style = props.style,
813
- dataSource = props.dataSource,
1042
+ rawData = props.dataSource,
814
1043
  columnDescriptor = props.columns,
815
1044
  rowKey = props.rowKey,
816
- startIndex = props.startIndex,
1045
+ overscan = props.overscan,
1046
+ estimateSize = props.estimateSize,
1047
+ getScroller = props.getScroller,
1048
+ getOffsetTop = props.getOffsetTop,
817
1049
  rowClassName = props.rowClassName,
818
1050
  onRow = props.onRow,
819
1051
  renderBodyWrapper = props.renderBodyWrapper,
@@ -821,23 +1053,53 @@ function TableBody(props) {
821
1053
  renderBody = props.renderBody,
822
1054
  renderRow = props.renderRow,
823
1055
  renderCell = props.renderCell;
1056
+ var _useRowVirtualize = useRowVirtualize({
1057
+ getOffsetTop: getOffsetTop,
1058
+ dataSource: rawData,
1059
+ getScroller: getScroller,
1060
+ estimateSize: estimateSize,
1061
+ overscan: overscan
1062
+ }),
1063
+ startIndex = _useRowVirtualize.startIndex,
1064
+ dataSource = _useRowVirtualize.dataSlice,
1065
+ setRowHeight = _useRowVirtualize.setRowHeight,
1066
+ updateRowRectList = _useRowVirtualize.updateRowRectList,
1067
+ rowHeightList = _useRowVirtualize.rowHeightList,
1068
+ topBlank = _useRowVirtualize.topBlank,
1069
+ bottomBlank = _useRowVirtualize.bottomBlank;
1070
+ var rowHeights = useRef(new Map());
1071
+ var updateRowHeight = useCallback(function (index, key, height) {
1072
+ var _rowHeights$current$g;
1073
+ var target = (_rowHeights$current$g = rowHeights.current.get(index)) != null ? _rowHeights$current$g : new Map();
1074
+ target.set(key, height);
1075
+ rowHeights.current.set(index, target);
1076
+ }, []);
1077
+ var rowManageState = useMemo(function () {
1078
+ return {
1079
+ updateRowHeight: updateRowHeight,
1080
+ getRowHeightList: function getRowHeightList() {
1081
+ return rowHeightList.current;
1082
+ }
1083
+ };
1084
+ }, [rowHeightList, updateRowHeight]);
824
1085
  var columns = columnDescriptor.columns,
825
1086
  descriptor = columnDescriptor.descriptor;
826
- var _useHorizontalScrollC = useHorizontalScrollContext(),
827
- listen = _useHorizontalScrollC.listen,
828
- notify = _useHorizontalScrollC.notify;
829
- var _useColumnSizes = useColumnSizes(),
830
- widthList = _useColumnSizes.widthList,
831
- setWidthList = _useColumnSizes.setWidthList;
832
- var columnWidthsRef = useRef(new Map());
833
- useLayoutEffect(function () {
834
- var snap = widthList;
835
- if (!isShallowEqual(snap, columnWidthsRef.current)) {
836
- setWidthList(new Map(columnWidthsRef.current));
837
- }
1087
+ var tbodyRef = useMergedRef(bodyRef, function (elm) {
1088
+ if (elm == null) return;
1089
+ var bodyHeight = elm.offsetHeight;
1090
+ if (bodyHeight === 0) return;
1091
+ var heights = rowHeights.current;
1092
+ heights.forEach(function (row, rowIndex) {
1093
+ var height = [].concat(row.values()).reduce(function (res, x) {
1094
+ return res + x;
1095
+ }, 0);
1096
+ setRowHeight(rowIndex, height);
1097
+ });
1098
+ updateRowRectList();
1099
+ rowHeights.current.clear();
838
1100
  });
839
1101
  var bodyNode = pipelineRender(jsx("tbody", {
840
- ref: bodyRef,
1102
+ ref: tbodyRef,
841
1103
  children: dataSource.map(function (e, rowIndex) {
842
1104
  var _rowKey = e[rowKey];
843
1105
  return jsx(Row$1, {
@@ -854,30 +1116,44 @@ function TableBody(props) {
854
1116
  columns: columns,
855
1117
  columnDescriptor: descriptor
856
1118
  });
1119
+ var _useColumnSizes = useColumnSizes(),
1120
+ widthList = _useColumnSizes.widthList,
1121
+ setWidthList = _useColumnSizes.setWidthList;
1122
+ var onColumnSizesMeasure = useCallback(function (columnSizes) {
1123
+ if (!isShallowEqual(widthList, columnSizes)) {
1124
+ setWidthList(new Map(columnSizes));
1125
+ }
1126
+ }, [setWidthList, widthList]);
857
1127
  var tableNode = pipelineRender(jsxs("table", {
858
1128
  className: clsx(className, 'virtual-table-body'),
859
- style: style,
1129
+ style: _extends({}, style, {
1130
+ paddingBottom: bottomBlank,
1131
+ paddingTop: topBlank
1132
+ }),
860
1133
  ref: bodyRootRef,
861
1134
  children: [jsx(Colgroup, {
862
1135
  columns: descriptor,
863
- colRef: function colRef(node, column) {
864
- if (node == null) return;
865
- var key = getKey(column);
866
- columnWidthsRef.current.set(key, node.offsetWidth);
867
- }
1136
+ onColumnSizesMeasure: onColumnSizesMeasure
868
1137
  }), bodyNode]
869
1138
  }), renderBodyRoot, {
870
1139
  columns: columns,
871
1140
  columnDescriptor: descriptor
872
1141
  });
1142
+ var _useHorizontalScrollC = useHorizontalScrollContext(),
1143
+ listen = _useHorizontalScrollC.listen,
1144
+ notify = _useHorizontalScrollC.notify;
873
1145
  var wrapperRef = useRef(null);
874
1146
  useEffect(function () {
875
1147
  var node = wrapperRef.current;
876
1148
  if (node == null) return;
877
1149
  var key = 'virtual-table-body';
878
1150
  var onScroll = function onScroll() {
879
- var nextScrollLeft = node.scrollLeft;
880
- notify(key, nextScrollLeft, node);
1151
+ notify(key, {
1152
+ scrollLeft: function scrollLeft() {
1153
+ return node.scrollLeft;
1154
+ },
1155
+ node: node
1156
+ });
881
1157
  };
882
1158
  var dispose = listen(key, function (scrollLeft) {
883
1159
  node.scrollLeft = scrollLeft;
@@ -889,13 +1165,16 @@ function TableBody(props) {
889
1165
  };
890
1166
  }, [listen, notify]);
891
1167
  var mergedRef = useMergedRef(wrapperRef, bodyWrapperRef);
892
- return pipelineRender(jsx("div", {
893
- ref: mergedRef,
894
- className: "virtual-table-body-wrapper",
895
- children: tableNode
896
- }), renderBodyWrapper, {
897
- columns: columns,
898
- columnDescriptor: descriptor
1168
+ return jsx(TableRowManager.Provider, {
1169
+ value: rowManageState,
1170
+ children: pipelineRender(jsx("div", {
1171
+ ref: mergedRef,
1172
+ className: "virtual-table-body-wrapper",
1173
+ children: tableNode
1174
+ }), renderBodyWrapper, {
1175
+ columns: columns,
1176
+ columnDescriptor: descriptor
1177
+ })
899
1178
  });
900
1179
  }
901
1180
 
@@ -913,6 +1192,7 @@ var TableHeader = function TableHeader(props) {
913
1192
  renderHeaderCell = props.renderHeaderCell;
914
1193
  var columns = columnDescriptor.columns,
915
1194
  descriptor = columnDescriptor.descriptor;
1195
+ var lastColumn = columns[columns.length - 1];
916
1196
  var _useColumnSizes = useColumnSizes(),
917
1197
  widthList = _useColumnSizes.widthList;
918
1198
  var _useTableSticky = useTableSticky(),
@@ -938,8 +1218,12 @@ var TableHeader = function TableHeader(props) {
938
1218
  if (node == null) return;
939
1219
  var key = 'virtual-table-header';
940
1220
  var onScroll = function onScroll() {
941
- var nextScrollLeft = node.scrollLeft;
942
- notify(key, nextScrollLeft, node);
1221
+ notify(key, {
1222
+ scrollLeft: function scrollLeft() {
1223
+ return node.scrollLeft;
1224
+ },
1225
+ node: node
1226
+ });
943
1227
  };
944
1228
  var dispose = listen(key, function (scrollLeft) {
945
1229
  node.scrollLeft = scrollLeft;
@@ -955,12 +1239,15 @@ var TableHeader = function TableHeader(props) {
955
1239
  var _column$onHeaderCell;
956
1240
  var key = item.key;
957
1241
  if (item.type === 'blank') {
958
- return jsx("th", {}, key);
1242
+ return jsx("th", {
1243
+ "data-blank": true
1244
+ }, key);
959
1245
  }
960
1246
  var column = item.column;
961
1247
  if (column.colSpan === 0) {
962
1248
  return null;
963
1249
  }
1250
+ var isLast = getKey(lastColumn) === key;
964
1251
  var _ref = (_column$onHeaderCell = column.onHeaderCell == null ? undefined : column.onHeaderCell(column, index)) != null ? _column$onHeaderCell : {},
965
1252
  thClassName = _ref.className,
966
1253
  thStyle = _ref.style,
@@ -970,7 +1257,7 @@ var TableHeader = function TableHeader(props) {
970
1257
  scope: 'col'
971
1258
  }, rest, {
972
1259
  colSpan: column.colSpan,
973
- className: clsx('virtual-table-header-cell', column.align != null && "virtual-table-align-" + column.align, isValidFixed(column.fixed) && 'virtual-table-sticky-cell', lastFixedLeftColumnIndex === index && 'virtual-table-cell-fix-left-last', firstFixedRightColumnIndex === index && 'virtual-table-cell-fix-right-first', column.className, thClassName),
1260
+ className: clsx('virtual-table-header-cell', isLast && 'no-split', column.align != null && "virtual-table-align-" + column.align, isValidFixed(column.fixed) && 'virtual-table-sticky-cell', lastFixedLeftColumnIndex === index && 'virtual-table-cell-fix-left-last', firstFixedRightColumnIndex === index && 'virtual-table-cell-fix-right-first', column.className, thClassName),
974
1261
  style: _extends({}, thStyle, {
975
1262
  left: isValidFixedLeft(column.fixed) ? stickySizes.get(key) : undefined,
976
1263
  right: isValidFixedRight(column.fixed) ? stickySizes.get(key) : undefined
@@ -1019,35 +1306,7 @@ var TableHeader = function TableHeader(props) {
1019
1306
  });
1020
1307
  };
1021
1308
 
1022
- function onResize(target, callback) {
1023
- if (isWindow(target)) {
1024
- var listener = function listener() {
1025
- callback({
1026
- width: window.innerWidth,
1027
- height: window.innerHeight
1028
- });
1029
- };
1030
- listener();
1031
- window.addEventListener('resize', listener);
1032
- return function () {
1033
- window.removeEventListener('resize', listener);
1034
- };
1035
- } else {
1036
- var observer = new ResizeObserver(function (entries) {
1037
- var rect = entries[0].contentRect;
1038
- callback({
1039
- width: rect.width,
1040
- height: rect.height
1041
- });
1042
- });
1043
- observer.observe(target);
1044
- return function () {
1045
- observer.disconnect();
1046
- };
1047
- }
1048
- }
1049
-
1050
- function anchorQuery$1(rects, scrollLeft) {
1309
+ function anchorQuery(rects, scrollLeft) {
1051
1310
  var left = 0;
1052
1311
  var right = rects.length - 1;
1053
1312
  var index = -1;
@@ -1128,7 +1387,7 @@ function useColumnVirtualize(options) {
1128
1387
  right: estimateSize
1129
1388
  });
1130
1389
  var findAnchorRef = useStableFn(function (scrollLeft) {
1131
- return anchorQuery$1(rects, scrollLeft);
1390
+ return anchorQuery(rects, scrollLeft);
1132
1391
  });
1133
1392
  var updateBoundary = useStableFn(function (scrollLeft, count) {
1134
1393
  var anchor = findAnchorRef(scrollLeft);
@@ -1204,34 +1463,56 @@ function useColumnVirtualize(options) {
1204
1463
  });
1205
1464
  }, [disabled, rawColumns, startIndex, endIndex, lastFixedLeftIndex, firstFixedRightIndex]);
1206
1465
  var descriptor = useMemo(function () {
1207
- return columnSlice.reduce(function (result, column) {
1466
+ return columnSlice.reduce(function (result, column, index) {
1208
1467
  var key = getKey(column);
1209
1468
  if (key === leftKey) {
1210
1469
  result.push({
1211
- key: key,
1212
1470
  type: 'normal',
1471
+ key: key,
1213
1472
  column: column
1214
1473
  });
1215
1474
  result.push({
1475
+ type: 'blank',
1216
1476
  key: '_blank_left',
1477
+ width: leftBlank
1478
+ });
1479
+ } else if (leftKey == null && index === 0) {
1480
+ result.push({
1217
1481
  type: 'blank',
1482
+ key: '_blank_left',
1218
1483
  width: leftBlank
1219
1484
  });
1485
+ result.push({
1486
+ type: 'normal',
1487
+ key: key,
1488
+ column: column
1489
+ });
1220
1490
  } else if (key === rightKey) {
1221
1491
  result.push({
1222
- key: '_blank_right',
1223
1492
  type: 'blank',
1493
+ key: '_blank_right',
1224
1494
  width: rightBlank
1225
1495
  });
1226
1496
  result.push({
1497
+ type: 'normal',
1227
1498
  key: key,
1499
+ column: column
1500
+ });
1501
+ } else if (rightKey == null && index === columnSlice.length - 1) {
1502
+ result.push({
1228
1503
  type: 'normal',
1504
+ key: key,
1229
1505
  column: column
1230
1506
  });
1507
+ result.push({
1508
+ type: 'blank',
1509
+ key: '_blank_right',
1510
+ width: rightBlank
1511
+ });
1231
1512
  } else {
1232
1513
  result.push({
1233
- key: key,
1234
1514
  type: 'normal',
1515
+ key: key,
1235
1516
  column: column
1236
1517
  });
1237
1518
  }
@@ -1252,229 +1533,6 @@ function useColumnVirtualize(options) {
1252
1533
  };
1253
1534
  }
1254
1535
 
1255
- function useRowRectManager(options) {
1256
- var itemCount = options.itemCount,
1257
- estimateSize = options.estimateSize,
1258
- onChange = options.onChange;
1259
- var rowHeightList = useRef([]);
1260
- var calc = function calc() {
1261
- for (var i = 0; i < itemCount; i += 1) {
1262
- var target = rowHeightList.current[i];
1263
- if (target == null) {
1264
- rowHeightList.current[i] = estimateSize;
1265
- }
1266
- }
1267
- rowHeightList.current = rowHeightList.current.slice(0, itemCount);
1268
- };
1269
- // eslint-disable-next-line react-compiler/react-compiler
1270
- calc();
1271
- // const initialCount = useRef(itemCount)
1272
- // const [, forceUpdate] = useReducer((x) => !x, false)
1273
- // useLayoutEffect(() => {
1274
- // for (let i = 0; i < itemCount; i += 1) {
1275
- // const target = rowHeightList.current[i]
1276
- // if (target == null) {
1277
- // rowHeightList.current[i] = estimatedRowHeight
1278
- // }
1279
- // }
1280
- // // 数据量骤减时如果 row 数量不匹配则会导致容器高度错误
1281
- // rowHeightList.current = rowHeightList.current.slice(0, itemCount)
1282
- // // 这里的 useLayoutEffect 是为了在第一次渲染时,根据数据条数预先填充 row 的高度,这样内容足够高,容器才能滚动
1283
- // // 但是 dataSource 一般是异步获取的,第一次渲染时,length 是空的,所以无法进行填充,这样可能导致容器不能滚动
1284
- // // 所以 dataSource 有数据后,再填充一次,但是因为 useLayoutEffect 是渲染结束后才调用的,此时填充数据还是导致容器不能滚动,
1285
- // // 所以,只在第一次 dataSource 不为空时强制触发一次更新
1286
- // // 可能正因为这一次强制渲染,所以 VirtualTable 内部很多组件可能多次渲染。尝试优化为 calc 函数
1287
- // if (initialCount.current === 0 && itemCount > 0) {
1288
- // initialCount.current = itemCount
1289
- // forceUpdate()
1290
- // }
1291
- // }, [itemCount, estimatedRowHeight])
1292
- var rectList = useRef([]);
1293
- var updateRectList = function updateRectList() {
1294
- var result = [];
1295
- var top = 0;
1296
- rowHeightList.current.forEach(function (item, index) {
1297
- if (index !== 0) {
1298
- top += item;
1299
- }
1300
- result.push({
1301
- index: index,
1302
- height: item,
1303
- top: top,
1304
- bottom: top + item
1305
- });
1306
- });
1307
- rectList.current = result;
1308
- };
1309
- // DOM 渲染结束后,进行高度测量,再修改 rowHeightList
1310
- // 小心陷阱:当 table 父元素为 display: none 时,依然会触发 updateRowHeight 函数,并设置高度为 0
1311
- var updateRowHeight = useStableFn(function (index, height) {
1312
- rowHeightList.current[index] = height;
1313
- updateRectList();
1314
- onChange == null || onChange(index, height, rectList.current);
1315
- });
1316
- var sum = function sum(startIndex, endIndex) {
1317
- return rowHeightList.current.slice(startIndex, endIndex).reduce(function (a, b) {
1318
- return a + b;
1319
- }, 0);
1320
- };
1321
- return {
1322
- rowHeightList: rowHeightList,
1323
- updateRowHeight: updateRowHeight,
1324
- sum: sum,
1325
- rects: useCallback(function () {
1326
- return rectList.current;
1327
- }, [])
1328
- };
1329
- }
1330
-
1331
- function anchorQuery(rects, scrollTop) {
1332
- var left = 0;
1333
- var right = rects.length - 1;
1334
- var index = -1;
1335
- while (left <= right) {
1336
- var mid = Math.floor((left + right) / 2);
1337
- if (rects[mid].bottom > scrollTop) {
1338
- index = mid;
1339
- right = mid - 1;
1340
- } else {
1341
- left = mid + 1;
1342
- }
1343
- }
1344
- if (index === -1) {
1345
- return undefined;
1346
- }
1347
- return rects[index];
1348
- }
1349
- function useRowVirtualize(options) {
1350
- var getOffsetTop = options.getOffsetTop,
1351
- rawData = options.dataSource,
1352
- getScroller = options.getScroller,
1353
- estimateSize = options.estimateSize,
1354
- overscan = options.overscan;
1355
- var _useState = useState(0),
1356
- startIndex = _useState[0],
1357
- setStartIndex = _useState[1];
1358
- var _useState2 = useState(0),
1359
- endIndex = _useState2[0],
1360
- setEndIndex = _useState2[1];
1361
- // 锚点元素,当前虚拟列表中,最接近滚动容器顶部的元素
1362
- var anchorRef = useRef({
1363
- index: 0,
1364
- height: estimateSize,
1365
- top: 0,
1366
- bottom: estimateSize
1367
- });
1368
- var _useRowRectManager = useRowRectManager({
1369
- itemCount: rawData.length,
1370
- estimateSize: estimateSize,
1371
- onChange: function onChange(index, _height, rowRects) {
1372
- if (anchorRef.current.index === index) {
1373
- anchorRef.current = rowRects[index];
1374
- }
1375
- }
1376
- }),
1377
- rowHeightList = _useRowRectManager.rowHeightList,
1378
- rects = _useRowRectManager.rects,
1379
- updateRowHeight = _useRowRectManager.updateRowHeight,
1380
- sum = _useRowRectManager.sum;
1381
- // 用来判断滚动方向
1382
- var scrollTopRef = useRef(0);
1383
- var initial = useRef(false);
1384
- useEffect(function () {
1385
- var container = getScroller();
1386
- if (container == null) return;
1387
- var count = 0;
1388
- var getScrollTop = function getScrollTop() {
1389
- var offsetTop = getOffsetTop();
1390
- var result = 0;
1391
- if (isWindow(container) || isRoot(container)) {
1392
- result = window.scrollY;
1393
- } else {
1394
- var element = getScrollElement(container);
1395
- result = element.scrollTop;
1396
- }
1397
- return Math.max(result - offsetTop, 0);
1398
- };
1399
- var calcCount = function calcCount(scrollerContainerHeight) {
1400
- var prevCount = count;
1401
- count = Math.ceil(scrollerContainerHeight / estimateSize);
1402
- return {
1403
- prevCount: prevCount
1404
- };
1405
- };
1406
- var updateBoundary = function updateBoundary(scrollTop) {
1407
- var anchor = anchorQuery(rects(), scrollTop);
1408
- if (anchor != null) {
1409
- anchorRef.current = anchor;
1410
- setStartIndex(Math.max(0, anchor.index - overscan));
1411
- setEndIndex(anchor.index + count + overscan);
1412
- }
1413
- };
1414
- var onScroll = function onScroll(e) {
1415
- var offsetTop = getOffsetTop();
1416
- var scrollElement = getScrollElement(e.target);
1417
- var scrollTop = Math.max(0, scrollElement.scrollTop - offsetTop);
1418
- // 是否为向下滚动
1419
- var isScrollDown = scrollTop > scrollTopRef.current;
1420
- if (isScrollDown) {
1421
- // 如果滚动距离比较小,没有超出锚点元素的边界,就不需要计算 startIndex、endIndex 了
1422
- if (scrollTop > anchorRef.current.bottom) {
1423
- updateBoundary(scrollTop);
1424
- }
1425
- } else {
1426
- if (scrollTop < anchorRef.current.top) {
1427
- updateBoundary(scrollTop);
1428
- }
1429
- }
1430
- scrollTopRef.current = scrollTop;
1431
- };
1432
- var prevHeight = 0;
1433
- var stopListen = onResize(container, function (rect) {
1434
- // 处理父元素 display:none 容器高度丢失,导致显示 row 不准确
1435
- if (rect.height === prevHeight || rect.height === 0) {
1436
- return;
1437
- }
1438
- prevHeight = rect.height;
1439
- calcCount(rect.height);
1440
- var scrollTop = getScrollTop();
1441
- if (!initial.current) {
1442
- initial.current = true;
1443
- var nextStartIndex = 0;
1444
- // 判断一下当前滚动位置,计算 startIndex(场景:SPA 页面切换且渲染非异步数据)
1445
- if (scrollTop >= estimateSize) {
1446
- nextStartIndex = Math.max(Math.floor(scrollTop / estimateSize) - 1 - overscan, 0);
1447
- }
1448
- var nextEndIndex = nextStartIndex + count + overscan;
1449
- setStartIndex(nextStartIndex);
1450
- setEndIndex(nextEndIndex);
1451
- } else {
1452
- updateBoundary(scrollTop);
1453
- }
1454
- });
1455
- container.addEventListener('scroll', onScroll);
1456
- return function () {
1457
- stopListen();
1458
- container.removeEventListener('scroll', onScroll);
1459
- };
1460
- }, [estimateSize, getOffsetTop, getScroller, overscan, rects]);
1461
- // TODO: React Compiler 测试 topBlank 和 bottomBlank
1462
- var topBlank = sum(0, startIndex);
1463
- var bottomBlank = sum(endIndex);
1464
- var dataSlice = useMemo(function () {
1465
- return rawData.slice(startIndex, endIndex);
1466
- }, [rawData, startIndex, endIndex]);
1467
- return {
1468
- startIndex: startIndex,
1469
- endIndex: endIndex,
1470
- rowHeightList: rowHeightList,
1471
- updateRowHeight: updateRowHeight,
1472
- topBlank: topBlank,
1473
- bottomBlank: bottomBlank,
1474
- dataSlice: dataSlice
1475
- };
1476
- }
1477
-
1478
1536
  function TableRoot(props, ref) {
1479
1537
  var className = props.className,
1480
1538
  style = props.style,
@@ -1549,7 +1607,7 @@ function VirtualTableCore(props, ref) {
1549
1607
  onRow = props.onRow,
1550
1608
  getOffsetTopImpl = props.getOffsetTop,
1551
1609
  _props$virtualHeader = props.virtualHeader,
1552
- virtualHeader = _props$virtualHeader === undefined ? false : _props$virtualHeader;
1610
+ virtualHeader = _props$virtualHeader === undefined ? true : _props$virtualHeader;
1553
1611
  var rootNode = useRef(null);
1554
1612
  var headerWrapperRef = useRef(null);
1555
1613
  var bodyWrapperRef = useRef(null);
@@ -1624,19 +1682,6 @@ function VirtualTableCore(props, ref) {
1624
1682
  setWidthList: updateColumnWidths
1625
1683
  };
1626
1684
  }, [columnWidths]);
1627
- var _useRowVirtualize = useRowVirtualize({
1628
- getOffsetTop: getOffsetTop,
1629
- dataSource: dataSource,
1630
- getScroller: getScroller,
1631
- estimateSize: estimatedRowHeight,
1632
- overscan: overscanRows
1633
- }),
1634
- startIndex = _useRowVirtualize.startIndex,
1635
- dataSlice = _useRowVirtualize.dataSlice,
1636
- updateRowHeight = _useRowVirtualize.updateRowHeight,
1637
- rowHeightList = _useRowVirtualize.rowHeightList,
1638
- topBlank = _useRowVirtualize.topBlank,
1639
- bottomBlank = _useRowVirtualize.bottomBlank;
1640
1685
  var _useColumnVirtualize = useColumnVirtualize({
1641
1686
  estimateSize: estimatedColumnWidth != null ? estimatedColumnWidth : 100,
1642
1687
  overscan: overscanColumns,
@@ -1669,14 +1714,6 @@ function VirtualTableCore(props, ref) {
1669
1714
  var hasFixedRightColumn = pipelineColumns.some(function (x) {
1670
1715
  return isValidFixedRight(x.fixed);
1671
1716
  });
1672
- var rowManager = useMemo(function () {
1673
- return {
1674
- getRowHeightList: function getRowHeightList() {
1675
- return rowHeightList.current;
1676
- },
1677
- updateRowHeight: updateRowHeight
1678
- };
1679
- }, [rowHeightList, updateRowHeight]);
1680
1717
  var fullHeaderColumns = useMemo(function () {
1681
1718
  return {
1682
1719
  columns: pipelineColumns,
@@ -1711,14 +1748,14 @@ function VirtualTableCore(props, ref) {
1711
1748
  bodyRootRef: mergedBodyRootRef,
1712
1749
  bodyRef: bodyRef,
1713
1750
  className: tableBodyClassName,
1714
- style: _extends({}, tableBodyStyle, {
1715
- paddingBottom: bottomBlank,
1716
- paddingTop: topBlank
1717
- }),
1751
+ style: tableBodyStyle,
1718
1752
  columns: columns,
1719
1753
  rowKey: rowKey,
1720
- dataSource: dataSlice,
1721
- startIndex: startIndex,
1754
+ dataSource: dataSource,
1755
+ overscan: overscanRows,
1756
+ estimateSize: estimatedRowHeight,
1757
+ getScroller: getScroller,
1758
+ getOffsetTop: getOffsetTop,
1722
1759
  rowClassName: onRowClassName,
1723
1760
  onRow: onRowProps,
1724
1761
  renderBodyWrapper: renderBodyWrapper,
@@ -1745,18 +1782,15 @@ function VirtualTableCore(props, ref) {
1745
1782
  columns: columns.columns,
1746
1783
  columnDescriptor: columns.descriptor
1747
1784
  });
1748
- return jsx(TableRowManager.Provider, {
1749
- value: rowManager,
1750
- children: jsx(ColumnSizes.Provider, {
1751
- value: tableColumnsContext,
1752
- children: jsx(StickyContext, {
1753
- columns: pipelineColumns,
1754
- children: jsx(ContainerSizeContext, {
1755
- getScroller: getScroller,
1756
- root: rootNode,
1757
- children: jsx(HorizontalScrollContext, {
1758
- children: table
1759
- })
1785
+ return jsx(ColumnSizes.Provider, {
1786
+ value: tableColumnsContext,
1787
+ children: jsx(StickyContext, {
1788
+ columns: pipelineColumns,
1789
+ children: jsx(ContainerSizeContext, {
1790
+ getScroller: getScroller,
1791
+ root: rootNode,
1792
+ children: jsx(HorizontalScrollContext, {
1793
+ children: table
1760
1794
  })
1761
1795
  })
1762
1796
  })