@are-visual/virtual-table
Version:
### VirtualTable
1,578 lines (1,548 loc) • 69.2 kB
JavaScript
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime';
import clsx from 'clsx';
import { useRef, createContext, useContext, useMemo, memo, useEffect, useState, useCallback, useLayoutEffect, Fragment, createElement, forwardRef } from 'react';
function _arrayLikeToArray(r, a) {
(null == a || a > r.length) && (a = r.length);
for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e];
return n;
}
function _createForOfIteratorHelperLoose(r, e) {
var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
if (t) return (t = t.call(r)).next.bind(t);
if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) {
t && (r = t);
var o = 0;
return function () {
return o >= r.length ? {
done: true
} : {
done: false,
value: r[o++]
};
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
function _objectWithoutPropertiesLoose(r, e) {
if (null == r) return {};
var t = {};
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
if (-1 !== e.indexOf(n)) continue;
t[n] = r[n];
}
return t;
}
function _unsupportedIterableToArray(r, a) {
if (r) {
if ("string" == typeof r) return _arrayLikeToArray(r, a);
var t = {}.toString.call(r).slice(8, -1);
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;
}
}
function shallowEqualObjects(objA, objB) {
if (objA === objB) {
return true;
}
if (!objA || !objB) {
return false;
}
var aKeys = Object.keys(objA);
var bKeys = Object.keys(objB);
var len = aKeys.length;
if (bKeys.length !== len) {
return false;
}
for (var i = 0; i < len; i += 1) {
var key = aKeys[i];
if (objA[key] !== objB[key] || !Object.prototype.hasOwnProperty.call(objB, key)) {
return false;
}
}
return true;
}
function shallowEqualArrays(arrA, arrB) {
if (arrA === arrB) {
return true;
}
if (!arrA || !arrB) {
return false;
}
var len = arrA.length;
if (arrB.length !== len) {
return false;
}
for (var i = 0; i < len; i += 1) {
if (arrA[i] !== arrB[i]) {
return false;
}
}
return true;
}
function isShallowEqual(value, oldValue) {
if (Object.is(value, oldValue)) {
return true;
}
if (Array.isArray(value) && Array.isArray(oldValue)) {
if (shallowEqualArrays(value, oldValue)) {
return true;
}
if (value.length === oldValue.length) {
var hasObject = value.findIndex(function (x) {
return typeof x === 'object' && x != null;
}) > -1;
if (hasObject) {
return value.every(function (state, index) {
if (Array.isArray(state)) {
return shallowEqualArrays(state, oldValue[index]);
}
if (typeof state === 'object' && state != null) {
return shallowEqualObjects(state, oldValue[index]);
}
return Object.is(state, oldValue[index]);
});
}
}
return false;
}
if (value instanceof Map && oldValue instanceof Map) {
var keys = Array.from(value.keys());
var oldKeys = Array.from(oldValue.keys());
if (isShallowEqual(keys, oldKeys)) {
var values = Array.from(value.values());
var oldValues = Array.from(oldValue.values());
if (isShallowEqual(values, oldValues)) {
return true;
}
}
return false;
}
if (typeof value === 'object' && value != null) {
return shallowEqualObjects(value, oldValue);
}
return false;
}
/* eslint-disable react-compiler/react-compiler */
/**
* 缓存数据(浅比较,如果是数组则对第一层进行浅比较)
*/
function useShallowMemo(pickData) {
var nextState = pickData();
var value = useRef(nextState);
if (!isShallowEqual(nextState, value.current)) {
value.current = nextState;
}
return value.current;
}
function getKey(column) {
return 'key' in column ? column.key : column.dataIndex;
}
function getRowKey(rowData, rowKey) {
if (typeof rowKey === 'function') {
return rowKey(rowData);
}
return rowData[rowKey];
}
function isValidFixedLeft(fixed) {
return fixed === 'left';
}
function isValidFixedRight(fixed) {
return fixed === 'right';
}
function isValidFixed(fixed) {
return isValidFixedLeft(fixed) || isValidFixedRight(fixed);
}
var ColumnSizes = /*#__PURE__*/createContext(null);
if (process.env.NODE_ENV === 'development') {
ColumnSizes.displayName = 'VirtualTable.ColumnSizes';
}
function useColumnSizes() {
var context = useContext(ColumnSizes);
if (context == null) {
throw new Error('useColumnSizes provider not found');
}
return context;
}
var Sticky = /*#__PURE__*/createContext(null);
if (process.env.NODE_ENV === 'development') {
Sticky.displayName = 'VirtualTable.Sticky';
}
function StickyContext(props) {
var columns = props.columns,
children = props.children;
var _useColumnSizes = useColumnSizes(),
widthList = _useColumnSizes.widthList;
var columnsFixedRecord = useMemo(function () {
return columns.map(function (column) {
var key = getKey(column);
return {
key: key,
fixed: column.fixed
};
});
}, [columns]);
var stickySizes = useMemo(function () {
var left = 0;
var leftOffset = columnsFixedRecord.reduce(function (res, item, index) {
var fixed = item.fixed,
key = item.key;
if (isValidFixedLeft(fixed)) {
var _widthList$get;
res[index] = left;
// eslint-disable-next-line react-compiler/react-compiler
left += (_widthList$get = widthList.get(key)) != null ? _widthList$get : 0;
}
return res;
}, []);
var right = 0;
var rightOffset = columnsFixedRecord.reduceRight(function (res, item, index) {
var fixed = item.fixed,
key = item.key;
if (isValidFixedRight(fixed)) {
var _widthList$get2;
res[index] = right;
right += (_widthList$get2 = widthList.get(key)) != null ? _widthList$get2 : 0;
}
return res;
}, []);
return columnsFixedRecord.reduce(function (result, item, index) {
var key = item.key,
fixed = item.fixed;
if (isValidFixedLeft(fixed)) {
result.set(key, leftOffset[index]);
return result;
}
if (isValidFixedRight(fixed)) {
result.set(key, rightOffset[index]);
return result;
}
return result;
}, new Map());
}, [columnsFixedRecord, widthList]);
var shallowMemoFixedRecord = useShallowMemo(function () {
return columnsFixedRecord;
});
var shallowMemoStickySizes = useShallowMemo(function () {
return stickySizes;
});
var stickyState = useMemo(function () {
return {
size: shallowMemoStickySizes,
fixed: shallowMemoFixedRecord
};
}, [shallowMemoFixedRecord, shallowMemoStickySizes]);
return jsx(Sticky.Provider, {
value: stickyState,
children: children
});
}
function useTableSticky() {
var context = useContext(Sticky);
if (context == null) {
throw new Error('useTableSticky provider not found');
}
return context;
}
function pipelineRender(node, render, options) {
if (typeof render === 'function') {
// @ts-expect-error: There is no way to declare the type correctly, but it works at runtime.
return render(node, options);
}
return node;
}
var _excluded$3 = ["className", "style", "column", "rowData", "rowIndex", "renderCell"],
_excluded2$1 = ["className", "style", "colSpan", "rowSpan"];
function getTableCellContent(index, data, column) {
var _render;
var render = column.render;
var rowData = data;
if ('dataIndex' in column) {
var dataIndex = column.dataIndex;
if (typeof render !== 'function') {
return rowData[dataIndex];
}
return render(rowData[dataIndex], data, index);
}
return (_render = render == null ? void 0 : render(data, data, index)) != null ? _render : null;
}
function Cell(props) {
var _onCell;
var className = props.className,
style = props.style,
column = props.column,
rowData = props.rowData,
rowIndex = props.rowIndex,
renderCell = props.renderCell,
restProps = _objectWithoutPropertiesLoose(props, _excluded$3);
var align = column.align,
fixed = column.fixed,
onCell = column.onCell;
var key = getKey(column);
var _useTableSticky = useTableSticky(),
stickySizes = _useTableSticky.size;
var _ref = (_onCell = onCell == null ? void 0 : onCell(rowData, rowIndex)) != null ? _onCell : {},
extraClassName = _ref.className,
extraStyle = _ref.style,
colSpan = _ref.colSpan,
rowSpan = _ref.rowSpan,
extraProps = _objectWithoutPropertiesLoose(_ref, _excluded2$1);
if (colSpan === 0 || rowSpan === 0) {
return null;
}
return pipelineRender(jsx("td", _extends({}, restProps, extraProps, {
colSpan: colSpan,
rowSpan: rowSpan,
className: clsx('virtual-table-cell', align != null && "virtual-table-align-" + align, isValidFixed(fixed) && 'virtual-table-sticky-cell', className, column.className, extraClassName),
style: _extends({}, style, extraStyle, {
left: isValidFixedLeft(fixed) ? stickySizes.get(key) : undefined,
right: isValidFixedRight(fixed) ? stickySizes.get(key) : undefined
}),
"data-col-key": key,
children: getTableCellContent(rowIndex, rowData, column)
})), renderCell, {
column: column
});
}
var Cell$1 = /*#__PURE__*/memo(Cell);
function getColumnWidth(column, defaultColumnWidth) {
return Number.isFinite(column.width) ? column.width : defaultColumnWidth;
}
var Colgroup = function Colgroup(props) {
var columns = props.columns,
defaultColumnWidth = props.defaultColumnWidth;
return jsx("colgroup", {
children: columns.map(function (item) {
var key = item.key;
if (item.type === 'blank') {
return jsx("col", {
className: "blank",
style: {
width: item.width
}
}, key);
}
var column = item.column;
var width = getColumnWidth(column, defaultColumnWidth);
return jsx("col", {
"data-col-key": key,
style: {
width: width
}
}, key);
})
});
};
var Colgroup$1 = /*#__PURE__*/memo(Colgroup);
var ContainerSize = /*#__PURE__*/createContext(null);
function useContainerSize() {
var context = useContext(ContainerSize);
if (context == null) {
throw new Error('useContainerSize provider not found');
}
return context;
}
var HorizontalScroll = /*#__PURE__*/createContext(null);
if (process.env.NODE_ENV === 'development') {
HorizontalScroll.displayName = 'VirtualTable.HorizontalScroll';
}
function HorizontalScrollContext(props) {
var children = props.children;
var listenerMap = useRef(new Map());
var context = useMemo(function () {
var skipEvent = new Set();
return {
listen: function listen(key, listener) {
listenerMap.current.set(key, function (scrollLeft, node) {
listener(scrollLeft, node);
});
return function () {
listenerMap.current["delete"](key);
};
},
notify: function notify(key, options) {
if (skipEvent.has(key)) {
skipEvent["delete"](key);
return;
}
var scrollLeft = options.scrollLeft,
node = options.node;
var rAF = window.requestAnimationFrame;
if (rAF == null) {
rAF = function rAF(fn) {
fn();
};
}
listenerMap.current.forEach(function (listener, itemKey) {
if (key === itemKey) return;
skipEvent.add(itemKey);
rAF(function () {
listener(scrollLeft(), node);
});
});
}
};
}, []);
return jsx(HorizontalScroll.Provider, {
value: context,
children: children
});
}
function useHorizontalScrollContext() {
var context = useContext(HorizontalScroll);
if (context == null) {
throw new Error('useHorizontalScrollContext provider not found');
}
return context;
}
function useScrollSynchronize(key) {
var _useHorizontalScrollC = useHorizontalScrollContext(),
listen = _useHorizontalScrollC.listen,
notify = _useHorizontalScrollC.notify;
var nodeRef = useRef(null);
useEffect(function () {
var node = nodeRef.current;
if (node == null) return;
var onScroll = function onScroll() {
notify(key, {
scrollLeft: function scrollLeft() {
return node.scrollLeft;
},
node: node
});
};
var addEvent = function addEvent() {
// eslint-disable-next-line @eslint-react/web-api/no-leaked-event-listener
node.addEventListener('scroll', onScroll, {
passive: true
});
};
var removeEvent = function removeEvent() {
node.removeEventListener('scroll', onScroll);
};
var dispose = listen(key, function (scrollLeft) {
node.style.willChange = 'scroll-position';
node.scrollLeft = scrollLeft;
});
addEvent();
return function () {
removeEvent();
dispose();
};
}, [key, listen, notify]);
return nodeRef;
}
var TableRowManager = /*#__PURE__*/createContext(null);
if (process.env.NODE_ENV === 'development') {
TableRowManager.displayName = 'VirtualTable.RowManager';
}
function useTableRowManager() {
var context = useContext(TableRowManager);
if (context == null) {
throw new Error('useTableRowManager provider not found');
}
return context;
}
var overflowStylePatterns = /auto|scroll|overlay|hidden/;
function isElement(node) {
if (!(node instanceof Node)) {
return false;
}
var ELEMENT_NODE_TYPE = 1;
return node.nodeType === ELEMENT_NODE_TYPE;
}
function getScrollParent(el, root) {
var _el$ownerDocument$def;
if (root === void 0) {
root = window;
}
var node = el;
while (node !== root && isElement(node)) {
if (node === document.body) {
return root;
}
var _window$getComputedSt = window.getComputedStyle(node),
overflow = _window$getComputedSt.overflow,
overflowY = _window$getComputedSt.overflowY,
overflowX = _window$getComputedSt.overflowX;
if (overflowStylePatterns.test(overflow + overflowY + overflowX)) {
return node;
}
node = node.parentNode;
}
return (_el$ownerDocument$def = el.ownerDocument.defaultView) != null ? _el$ownerDocument$def : window;
}
function isWindow(arg) {
return Object.prototype.toString.call(arg) === '[object Window]';
}
function isDocument(arg) {
return Object.prototype.toString.call(arg) === '[object HTMLDocument]';
}
function isRoot(arg) {
return Object.prototype.toString.call(arg) === '[object HTMLHtmlElement]';
}
function getScrollElement(el) {
if (isDocument(el)) {
return el.scrollingElement;
}
if (isElement(el)) {
return el;
}
return document.scrollingElement;
}
function getScrollTop(node) {
var result = 0;
if (isWindow(node) || isRoot(node)) {
result = window.scrollY;
} else {
var element = getScrollElement(node);
result = element.scrollTop;
}
return result;
}
function getRelativeOffsetTop(el, ancestor) {
var elRect = el.getBoundingClientRect();
var ancestorRect = ancestor.getBoundingClientRect();
var ancestorBorderTop = Number.parseFloat(window.getComputedStyle(ancestor).borderTopWidth) || 0;
var ancestorScrollTop = ancestor.scrollTop;
return elRect.top - ancestorRect.top - ancestorBorderTop + ancestorScrollTop;
}
function onResize(target, callback) {
if (isWindow(target)) {
var listener = function listener() {
callback({
width: window.innerWidth,
height: window.innerHeight
});
};
listener();
window.addEventListener('resize', listener);
return function () {
window.removeEventListener('resize', listener);
};
} else {
var observer = new ResizeObserver(function (entries) {
var rect = entries[0].contentRect;
callback({
width: rect.width,
height: rect.height
});
});
observer.observe(target);
return function () {
observer.disconnect();
};
}
}
function anchorQuery$1(rects, scrollTop) {
var left = 0;
var right = rects.length - 1;
var index = -1;
while (left <= right) {
var mid = Math.floor((left + right) / 2);
if (rects[mid].bottom > scrollTop) {
index = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
if (index === -1) {
return undefined;
}
return rects[index];
}
var NormalRowHeightKey = 'NormalRow';
function useRowVirtualize(options) {
var getOffsetTop = options.getOffsetTop,
rowKey = options.rowKey,
rawData = options.dataSource,
getScroller = options.getScroller,
estimateSize = options.estimateSize,
overscan = options.overscan;
var _useState = useState(0),
startIndex = _useState[0],
setStartIndex = _useState[1];
var _useState2 = useState(0),
endIndex = _useState2[0],
setEndIndex = _useState2[1];
var dataSlice = useMemo(function () {
return rawData.slice(startIndex, endIndex);
}, [rawData, startIndex, endIndex]);
/**
* 记录的所有行高信息
* 一个 Row 可能有多个行高。例如:默认情况下,只有一个行高,展开后,展开面板的高度也被认为是同一个 Row 的
* 所以可展开时,行高有多个,所有行高之和,则为 Row 的高度
* 行高之间使用唯一的 key 作为区分
* Record<rowKey, Map<key, height>>
*/
var rowHeightByRowKey = useRef(new Map());
var setRowHeightByRowKey = useCallback(function (rowKey, key, height) {
var _rowHeightByRowKey$cu;
var target = (_rowHeightByRowKey$cu = rowHeightByRowKey.current.get(rowKey)) != null ? _rowHeightByRowKey$cu : new Map();
target.set(key, height);
rowHeightByRowKey.current.set(rowKey, target);
}, []);
var getAllRowHeights = function getAllRowHeights() {
var heights = [];
rawData.forEach(function (item) {
var key = getRowKey(item, rowKey);
var row = rowHeightByRowKey.current.get(key);
if (row == null) {
heights.push(estimateSize);
} else {
var height = 0;
row.forEach(function (x) {
return height += x;
});
heights.push(height);
}
});
return heights;
};
// 行高信息(先填充预估高度,DOM渲染后再更新成实际高度)
var fillRowHeights = function fillRowHeights() {
if (rawData.length === 0) {
rowHeightByRowKey.current.clear();
return;
}
rawData.forEach(function (item) {
var _rowHeightByRowKey$cu2;
var key = getRowKey(item, rowKey);
var row = (_rowHeightByRowKey$cu2 = rowHeightByRowKey.current.get(key)) != null ? _rowHeightByRowKey$cu2 : new Map();
var target = row.get(NormalRowHeightKey);
if (target == null) {
row.set(NormalRowHeightKey, estimateSize);
}
rowHeightByRowKey.current.set(key, row);
});
};
fillRowHeights();
// 强制设置类型为 number[],在后面会初始化,只是为了减少 getAllRowHeights 的调用
var rowHeights = useRef();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
if (rowHeights.current == null) {
rowHeights.current = getAllRowHeights();
} else if (rawData.length !== rowHeights.current.length) {
// 这个判断条件主要是为了处理:空数据切换为有数据时 tbody 上 padding 缺失的问题
rowHeights.current = getAllRowHeights();
}
// 布局信息(也就是锚点元素需要的信息,top,bottom,height,index)
var rowRects = useRef([]);
var updateRowRects = function updateRowRects() {
var _rawData$reduce = rawData.reduce(function (result, rowData, index) {
var _rowHeightByRowKey$cu3;
var key = getRowKey(rowData, rowKey);
var height = 0;
(_rowHeightByRowKey$cu3 = rowHeightByRowKey.current.get(key)) == null || _rowHeightByRowKey$cu3.forEach(function (item) {
height += item;
});
var nextTop = result.top + height;
result.rects.push({
index: index,
top: result.top,
height: height,
bottom: nextTop
});
result.top = nextTop;
return result;
}, {
top: 0,
rects: []
}),
rects = _rawData$reduce.rects;
rowRects.current = rects;
};
/** 刷新布局信息,行高变化的时候调用,要重新组织布局信息(rowRects) */
var flushLayout = function flushLayout(fn) {
fn == null || fn();
updateRowRects();
// 组件渲染后会触发 flushLayout,表示行高有更新所以需要更新一下 rowHeights
// 避免展开行之后记录的还是之前的行高信息,否则滚动后底部会出现空白区域
rowHeights.current = rowRects.current.map(function (x) {
return x.height;
});
};
// 锚点元素,当前虚拟列表中,最接近滚动容器顶部的元素
var anchorRef = useRef({
index: 0,
height: estimateSize,
top: 0,
bottom: estimateSize
});
// 用来判断滚动方向
var scrollTopRef = useRef(0);
var initial = useRef(false);
useEffect(function () {
var container = getScroller();
if (container == null) return;
var count = 0;
var getScrollTop = function getScrollTop() {
var offsetTop = getOffsetTop();
var result = 0;
if (isWindow(container) || isRoot(container)) {
result = window.scrollY;
} else {
var element = getScrollElement(container);
result = element.scrollTop;
}
return Math.max(result - offsetTop, 0);
};
var calcCount = function calcCount(scrollerContainerHeight) {
var prevCount = count;
count = Math.ceil(scrollerContainerHeight / estimateSize);
return {
prevCount: prevCount
};
};
var updateBoundary = function updateBoundary(scrollTop) {
var anchor = anchorQuery$1(rowRects.current, scrollTop);
if (anchor != null) {
anchorRef.current = anchor;
setStartIndex(Math.max(0, anchor.index - overscan));
setEndIndex(anchor.index + count + overscan);
}
};
var onScroll = function onScroll(e) {
var offsetTop = getOffsetTop();
var scrollElement = getScrollElement(e.target);
var scrollTop = Math.max(0, scrollElement.scrollTop - offsetTop);
// 是否为向下滚动
var isScrollDown = scrollTop > scrollTopRef.current;
if (isScrollDown) {
// 如果滚动距离比较小,没有超出锚点元素的边界,就不需要计算 startIndex、endIndex 了
if (scrollTop > anchorRef.current.bottom) {
updateBoundary(scrollTop);
}
} else {
if (scrollTop < anchorRef.current.top) {
updateBoundary(scrollTop);
}
}
scrollTopRef.current = scrollTop;
};
var prevHeight = 0;
var stopListen = onResize(container, function (rect) {
// 处理父元素 display:none 容器高度丢失,导致显示 row 不准确
if (rect.height === prevHeight || rect.height === 0) {
return;
}
prevHeight = rect.height;
calcCount(rect.height);
var scrollTop = getScrollTop();
if (!initial.current) {
initial.current = true;
var nextStartIndex = 0;
// 判断一下当前滚动位置,计算 startIndex(场景:SPA 页面切换且渲染非异步数据)
if (scrollTop >= estimateSize) {
nextStartIndex = Math.max(Math.floor(scrollTop / estimateSize) - 1 - overscan, 0);
}
var nextEndIndex = nextStartIndex + count + overscan;
setStartIndex(nextStartIndex);
setEndIndex(nextEndIndex);
} else {
updateBoundary(scrollTop);
}
});
container.addEventListener('scroll', onScroll);
return function () {
stopListen();
container.removeEventListener('scroll', onScroll);
};
}, [estimateSize, getOffsetTop, getScroller, overscan]);
var sum = function sum(startIndex, endIndex) {
return rowHeights.current.slice(startIndex, endIndex).reduce(function (a, b) {
return a + b;
}, 0);
};
// TODO: React Compiler 测试 topBlank 和 bottomBlank
var topBlank = sum(0, startIndex);
var bottomBlank = sum(endIndex);
return {
startIndex: startIndex,
endIndex: endIndex,
rowHeightList: rowHeights,
flushLayout: flushLayout,
rowHeightByRowKey: rowHeightByRowKey,
setRowHeightByRowKey: setRowHeightByRowKey,
topBlank: topBlank,
bottomBlank: bottomBlank,
dataSlice: dataSlice
};
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
function useStableFn(callback) {
var fn = useRef(null);
useLayoutEffect(function () {
fn.current = callback;
});
var stableFn = useCallback(function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return fn.current == null ? void 0 : fn.current.apply(fn, args);
}, []);
return stableFn;
}
var TableInstance = /*#__PURE__*/function () {
function TableInstance() {
this.getInstance = this.getInstance.bind(this);
}
var _proto = TableInstance.prototype;
_proto.getInstance = function getInstance() {
var buildInMethods = ['getCurrentProps', 'getColumns', 'getDataSource', 'getDOM', 'getRowHeightMap', 'getRowVirtualizeState', 'getScrollValueByRowIndex', 'getScrollValueByColumnKey', 'scrollToRow', 'scrollToColumn', 'scrollTo', 'getColumnByKey', 'getColumnByIndex', 'getColumnKeyByIndex', 'getColumnWidths', 'getColumnWidthByKey'];
var extendInstance = _extends({}, buildInMethods.reduce(function (result, fnName) {
result[fnName] = function () {
throw new Error(fnName + "() has not been implemented yet");
};
return result;
}, {}), {
extend: function extend(args) {
Object.keys(args).forEach(function (fnName) {
extendInstance[fnName] = args[fnName];
});
}
});
return extendInstance;
};
return TableInstance;
}();
function useTableInstance(instance) {
var _useState = useState(function () {
if (instance == null) {
return new TableInstance().getInstance();
}
return instance;
}),
state = _useState[0];
return state;
}
/**
* 创建中间件,内部会浅比较 options,只有 options 改变才会返回新的函数。
*/
function createMiddleware(hook) {
var cache = {
current: null,
options: null
};
return function () {
for (var _len = arguments.length, options = new Array(_len), _key = 0; _key < _len; _key++) {
options[_key] = arguments[_key];
}
var useMiddleware = function useMiddleware(ctx) {
return hook.apply(void 0, [ctx].concat(options));
};
if (isShallowEqual(options, cache.options)) {
var _cache$current;
return (_cache$current = cache.current) != null ? _cache$current : useMiddleware;
}
cache.options = options;
cache.current = useMiddleware;
return useMiddleware;
};
}
var _excluded$2 = ["render", "renderRoot", "renderContent", "renderHeaderWrapper", "renderHeaderRoot", "renderHeader", "renderHeaderRow", "renderHeaderCell", "renderBodyWrapper", "renderBodyRoot", "renderBody", "renderBodyContent", "renderRow", "renderCell", "rowClassName", "onRow"];
function shakeUnsafeHooks(hooks) {
if (Array.isArray(hooks)) {
return hooks.filter(function (x) {
return x != null && (typeof x === 'function' || typeof x.hook === 'function');
}).map(function (hook) {
if (typeof hook === 'function') {
return {
priority: 100,
hook: hook
};
}
return hook;
});
}
return [];
}
var TablePipeline = /*#__PURE__*/function () {
function TablePipeline() {
Object.defineProperty(this, "hooks", {
enumerable: true,
configurable: true,
writable: true,
value: []
});
this.use = this.use.bind(this);
}
var _proto = TablePipeline.prototype;
_proto.setHooks = function setHooks(value) {
this.hooks = value;
};
_proto.use = function use(options) {
if (this.hooks.length === 0) {
return options;
}
var context = {
current: options
};
var renderFunctionMap = {
render: [],
renderRoot: [],
renderContent: [],
renderHeaderWrapper: [],
renderHeaderRoot: [],
renderHeader: [],
renderHeaderRow: [],
renderHeaderCell: [],
renderBodyWrapper: [],
renderBodyRoot: [],
renderBody: [],
renderBodyContent: [],
renderRow: [],
renderCell: []
};
var rowClassNameFunctions = [];
var onRowFunctions = [];
var hooks = this.hooks.slice();
hooks.sort(function (a, b) {
return a.priority - b.priority;
});
hooks.map(function (x) {
return x.hook;
}).forEach(function (hook) {
var nextCtx = hook(context.current);
nextCtx.render;
nextCtx.renderRoot;
nextCtx.renderContent;
nextCtx.renderHeaderWrapper;
nextCtx.renderHeaderRoot;
nextCtx.renderHeader;
nextCtx.renderHeaderRow;
nextCtx.renderHeaderCell;
nextCtx.renderBodyWrapper;
nextCtx.renderBodyRoot;
nextCtx.renderBody;
nextCtx.renderBodyContent;
nextCtx.renderRow;
nextCtx.renderCell;
var rowClassName = nextCtx.rowClassName,
onRow = nextCtx.onRow,
rest = _objectWithoutPropertiesLoose(nextCtx, _excluded$2);
Object.entries(renderFunctionMap).forEach(function (_ref) {
var key = _ref[0],
value = _ref[1];
var target = nextCtx[key];
if (typeof target === 'function') {
// @ts-expect-error: There is no way to declare the type correctly, but it works at runtime.
value.push(target);
}
});
if (typeof rowClassName === 'function') {
rowClassNameFunctions.push(rowClassName);
}
if (typeof onRow === 'function') {
onRowFunctions.push(onRow);
}
context.current = rest;
});
Object.entries(renderFunctionMap).forEach(function (_ref2) {
var key = _ref2[0],
renders = _ref2[1];
if (renders.length > 0) {
context.current[key] = function (children, args) {
// reduce 把 [render1, render2] 转为 render2(render1(children))
return renders.reduce(function (node, render) {
// @ts-expect-error: There is no way to declare the type correctly, but it works at runtime.
return render(node, args);
}, children);
};
}
});
if (rowClassNameFunctions.length > 0) {
context.current.rowClassName = function (record, index) {
return clsx.apply(void 0, rowClassNameFunctions.map(function (fn) {
return fn(record, index);
}));
};
}
if (onRowFunctions.length > 0) {
context.current.onRow = function (record, index) {
return onRowFunctions.reduce(function (result, item) {
return _extends({}, result, item(record, index));
}, {});
};
}
return context.current;
};
return TablePipeline;
}();
Object.defineProperty(TablePipeline, "defaultPipeline", {
enumerable: true,
configurable: true,
writable: true,
value: new TablePipeline()
});
function useTablePipeline(options) {
var use = options.use,
extraPipeline = options.pipeline;
var cached = useMemo(function () {
return {
current: new TablePipeline()
};
}, []);
if (use != null) {
var _extraPipeline$hooks;
var nextHooks = shakeUnsafeHooks([].concat(use, (_extraPipeline$hooks = extraPipeline == null ? void 0 : extraPipeline.hooks) != null ? _extraPipeline$hooks : []));
if (!shallowEqualArrays(cached.current.hooks, nextHooks)) {
var pipeline = new TablePipeline();
pipeline.setHooks(nextHooks);
cached.current = pipeline;
return pipeline;
}
}
return cached.current;
}
function findLastIndex(arr, predicate) {
var result = -1;
for (var i = 0; i < arr.length; i += 1) {
if (predicate(arr[i], i)) {
result = i;
}
}
return result;
}
var _excluded$1 = ["className", "rowKey", "rowIndex", "rowData", "columns", "onRow", "renderRow", "renderCell", "onRefCallback"],
_excluded2 = ["className"];
function Row(props) {
var _onRow;
var className = props.className,
rowKey = props.rowKey,
rowIndex = props.rowIndex,
rowData = props.rowData,
columnDescriptor = props.columns,
onRow = props.onRow,
renderRow = props.renderRow,
renderCell = props.renderCell,
onRefCallback = props.onRefCallback,
rest = _objectWithoutPropertiesLoose(props, _excluded$1);
var columns = columnDescriptor.columns,
descriptor = columnDescriptor.descriptor;
var lastFixedLeftColumnIndex = findLastIndex(descriptor, function (x) {
if (x.type === 'blank') {
return false;
}
return isValidFixedLeft(x.column.fixed);
});
var firstFixedRightColumnIndex = descriptor.findIndex(function (x) {
if (x.type === 'blank') {
return false;
}
return isValidFixedRight(x.column.fixed);
});
var _ref = (_onRow = onRow == null ? void 0 : onRow(rowData, rowIndex)) != null ? _onRow : {},
extraClassName = _ref.className,
extraProps = _objectWithoutPropertiesLoose(_ref, _excluded2);
return pipelineRender(jsx("tr", _extends({}, rest, extraProps, {
className: clsx('virtual-table-row', className, extraClassName),
"data-row-index": rowIndex,
ref: function ref(node) {
onRefCallback == null || onRefCallback({
node: node,
rowKey: rowKey,
rowIndex: rowIndex,
rowData: rowData
});
},
children: descriptor.map(function (item, index) {
var key = item.key;
if (item.type === 'blank') {
return jsx("td", {}, key);
}
var column = item.column;
return jsx(Cell$1, {
className: clsx(lastFixedLeftColumnIndex === index && 'virtual-table-cell-fix-left-last', firstFixedRightColumnIndex === index && 'virtual-table-cell-fix-right-first'),
column: column,
rowIndex: rowIndex,
rowData: rowData,
renderCell: renderCell
}, key);
})
})), renderRow, {
columns: columns,
rowKey: rowKey,
rowIndex: rowIndex,
rowData: rowData,
columnDescriptor: descriptor
});
}
var Row$1 = /*#__PURE__*/memo(Row);
function assignRef(ref, value) {
if (typeof ref === 'function') {
ref(value);
} else if (typeof ref === 'object' && ref !== null && 'current' in ref) {
ref.current = value;
}
}
function mergeRefs() {
for (var _len = arguments.length, refs = new Array(_len), _key = 0; _key < _len; _key++) {
refs[_key] = arguments[_key];
}
return function (node) {
refs.forEach(function (ref) {
return assignRef(ref, node);
});
};
}
function useMergedRef() {
for (var _len2 = arguments.length, refs = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
refs[_key2] = arguments[_key2];
}
// eslint-disable-next-line react-compiler/react-compiler
return useCallback(mergeRefs.apply(void 0, refs), refs);
}
function TableBody(props) {
var bodyWrapperRef = props.bodyWrapperRef,
bodyRootRef = props.bodyRootRef,
bodyRef = props.bodyRef,
className = props.className,
style = props.style,
defaultColumnWidth = props.defaultColumnWidth,
rawData = props.dataSource,
columnDescriptor = props.columns,
rowKey = props.rowKey,
instance = props.instance,
overscan = props.overscan,
estimateSize = props.estimateSize,
getScroller = props.getScroller,
getOffsetTop = props.getOffsetTop,
rowClassName = props.rowClassName,
onRow = props.onRow,
renderBodyWrapper = props.renderBodyWrapper,
renderBodyRoot = props.renderBodyRoot,
renderBody = props.renderBody,
renderBodyContent = props.renderBodyContent,
renderRow = props.renderRow,
renderCell = props.renderCell;
var _useRowVirtualize = useRowVirtualize({
getOffsetTop: getOffsetTop,
rowKey: rowKey,
dataSource: rawData,
getScroller: getScroller,
estimateSize: estimateSize,
overscan: overscan
}),
startIndex = _useRowVirtualize.startIndex,
endIndex = _useRowVirtualize.endIndex,
dataSource = _useRowVirtualize.dataSlice,
rowHeightByRowKey = _useRowVirtualize.rowHeightByRowKey,
setRowHeightByRowKey = _useRowVirtualize.setRowHeightByRowKey,
flushLayout = _useRowVirtualize.flushLayout,
rowHeightList = _useRowVirtualize.rowHeightList,
topBlank = _useRowVirtualize.topBlank,
bottomBlank = _useRowVirtualize.bottomBlank;
instance.extend({
getRowVirtualizeState: function getRowVirtualizeState() {
return {
startIndex: startIndex,
endIndex: endIndex,
overscan: overscan,
estimateSize: estimateSize
};
},
getRowHeightMap: function getRowHeightMap() {
return rowHeightByRowKey.current;
}
});
var tbodyRef = useMergedRef(bodyRef, function (elm) {
if (elm == null) return;
var bodyHeight = elm.offsetHeight;
if (bodyHeight === 0) return;
// body 的 ref 回调函数中,说明 body 渲染完成,也就意味着所有的 tr 也已经渲染完成,
// 现在可以记录所有 tr 的高度
flushLayout();
});
// 测量行高
var onMeasureRowHeight = useCallback(function (args) {
var node = args.node,
rowKey = args.rowKey;
if (node == null) return;
// 小心陷阱:当 table 父元素为 display: none 时,依然会触发,并设置高度为 0
setRowHeightByRowKey(rowKey, NormalRowHeightKey, node.offsetHeight);
}, [setRowHeightByRowKey]);
var columns = columnDescriptor.columns,
descriptor = columnDescriptor.descriptor;
var bodyContent = pipelineRender(dataSource.map(function (e, rowIndex) {
var key = getRowKey(e, rowKey);
return jsx(Row$1, {
className: clsx(rowClassName == null ? void 0 : rowClassName(e, rowIndex)),
rowKey: key,
rowIndex: rowIndex + startIndex,
rowData: e,
columns: columnDescriptor,
onRow: onRow,
renderRow: renderRow,
renderCell: renderCell,
onRefCallback: onMeasureRowHeight
}, key);
}), renderBodyContent, {
columns: columns,
columnDescriptor: descriptor,
startRowIndex: startIndex
});
var bodyNode = pipelineRender(jsx("tbody", {
ref: tbodyRef,
children: bodyContent
}), renderBody, {
columns: columns,
columnDescriptor: descriptor,
startRowIndex: startIndex
});
var tableNode = pipelineRender(jsxs("table", {
className: clsx(className, 'virtual-table-body'),
style: _extends({}, style, {
paddingBottom: bottomBlank,
paddingTop: topBlank
}),
ref: bodyRootRef,
children: [jsx(Colgroup$1, {
columns: descriptor,
defaultColumnWidth: defaultColumnWidth
}), bodyNode]
}), renderBodyRoot, {
columns: columns,
columnDescriptor: descriptor,
startRowIndex: startIndex
});
var wrapperRef = useScrollSynchronize('virtual-table-body');
var mergedRef = useMergedRef(wrapperRef, bodyWrapperRef);
// 滚动到指定行。注意:如果 estimatedRowHeight 不够准确时,不一定能准确滚动到目标位置
instance.extend({
getScrollValueByRowIndex: function getScrollValueByRowIndex(index) {
var scroller = getScroller();
if (scroller == null) {
return 0;
}
var _instance$getCurrentP = instance.getCurrentProps(),
stickyHeader = _instance$getCurrentP.stickyHeader;
var _instance$getDOM = instance.getDOM(),
headerWrapper = _instance$getDOM.headerWrapper;
var offset = 0;
// 没有 sticky,就要计算 header 高度
if (stickyHeader == null || stickyHeader === false) {
var headerOffsetHeight = headerWrapper == null ? 0 : headerWrapper.offsetHeight;
offset = headerOffsetHeight;
} else {
offset = Number.isFinite(stickyHeader) ? stickyHeader * -1 : 0;
}
var targetScrollTop = rowHeightList.current.slice(0, index).reduce(function (a, b) {
return a + b;
}, 0);
return targetScrollTop + getOffsetTop() + offset;
},
scrollToRow: function scrollToRow(index, behavior) {
instance.scrollTo({
top: instance.getScrollValueByRowIndex(index),
behavior: behavior
});
}
});
var rowManageState = useMemo(function () {
return {
setRowHeightByRowKey: setRowHeightByRowKey,
getRowHeightList: function getRowHeightList() {
return rowHeightList.current;
}
};
}, [rowHeightList, setRowHeightByRowKey]);
return jsx(TableRowManager.Provider, {
value: rowManageState,
children: pipelineRender(jsx("div", {
ref: mergedRef,
className: "virtual-table-body-wrapper",
children: tableNode
}), renderBodyWrapper, {
columns: columns,
columnDescriptor: descriptor,
startRowIndex: startIndex
})
});
}
var _excluded = ["className", "style"];
var TableHeader = function TableHeader(props) {
var className = props.className,
style = props.style,
defaultColumnWidth = props.defaultColumnWidth,
wrapperRef = props.wrapperRef,
columnDescriptor = props.columns,
stickyHeader = props.stickyHeader,
renderHeaderWrapper = props.renderHeaderWrapper,
renderHeaderRoot = props.renderHeaderRoot,
renderHeader = props.renderHeader,
renderHeaderRow = props.renderHeaderRow,
renderHeaderCell = props.renderHeaderCell;
var columns = columnDescriptor.columns,
descriptor = columnDescriptor.descriptor;
var lastColumn = columns[columns.length - 1];
var _useColumnSizes = useColumnSizes(),
widthList = _useColumnSizes.widthList;
var _useTableSticky = useTableSticky(),
stickySizes = _useTableSticky.size;
var lastFixedLeftColumnIndex = findLastIndex(descriptor, function (x) {
if (x.type === 'blank') {
return false;
}
return isValidFixedLeft(x.column.fixed);
});
var firstFixedRightColumnIndex = descriptor.findIndex(function (x) {
if (x.type === 'blank') {
return false;
}
return isValidFixedRight(x.column.fixed);
});
var headerWrapperRef = useScrollSynchronize('virtual-table-header');
var row = pipelineRender(jsx("tr", {
children: descriptor.map(function (item, index) {
var _column$onHeaderCell;
var key = item.key;
if (item.type === 'blank') {
return jsx("th", {
"data-blank": true
}, key);
}
var column = item.column;
if (column.colSpan === 0) {
return null;
}
var isLast = getKey(lastColumn) === key;
var _ref = (_column$onHeaderCell = column.onHeaderCell == null ? void 0 : column.onHeaderCell(column, index)) != null ? _column$onHeaderCell : {},
thClassName = _ref.className,
thStyle = _ref.style,
rest = _objectWithoutPropertiesLoose(_ref, _excluded);
return jsx(Fragment, {
children: pipelineRender(/*#__PURE__*/createElement('th', _extends({
scope: 'col'
}, rest, {
colSpan: column.colSpan,
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),
style: _extends({}, thStyle, {
left: isValidFixedLeft(column.fixed) ? stickySizes.get(key) : undefined,
right: isValidFixedRight(column.fixed) ? stickySizes.get(key) : undefined
})
}), column.title), renderHeaderCell, {
column: column,
columns: columns,
columnWidths: widthList,
columnDescriptor: descriptor
})
}, key);
})
}), renderHeaderRow, {
columns: columns,
columnDescriptor: descriptor
});
var theadNode = pipelineRender(jsx("thead", {
className: "virtual-table-thead",
children: row
}), renderHeader, {
columns: columns,
columnDescriptor: descriptor
});
var tableNode = pipelineRender(jsxs("table", {
style: style,
children: [jsx(Colgroup$1, {
columns: descriptor,
defaultColumnWidth: defaultColumnWidth
}), theadNode]
}), renderHeaderRoot, {
columns: columns,
columnDescriptor: descriptor
});
var mergedRef = useMergedRef(wrapperRef, headerWrapperRef);
return pipelineRender(jsx("div", {
ref: mergedRef,
className: clsx('virtual-table-header', className, {
'virtual-table-header-sticky': stickyHeader
}),
style: {
top: Number.isFinite(stickyHeader) ? stickyHeader : undefined
},
children: tableNode
}), renderHeaderWrapper, {
columns: columns,
columnDescriptor: descriptor
});
};
var TableHeader$1 = /*#__PURE__*/memo(TableHeader);
function useCalcSize(options) {
var getScroller = options.getScroller,
root = options.root;
var _useState = useState(0),
scrollContainerWidth = _useState[0],
setScrollContainerWidth = _useState[1];
var _useState2 = useState(0),
scrollContainerHeight = _useState2[0],
setScrollContainerHeight = _useState2[1];
var _useState3 = useState(800),
tableWidth = _useState3[0],
setTableWidth = _useState3[1]; // 设置一个默认值(因为这两个默认值很快就会被覆盖)
var _useState4 = useState(600),
tableHeight = _useState4[0],
setTableHeight = _useState4[1]; // 设置一个默认值(因为这两个默认值很快就会被覆盖)
var scrollerContainerRef = useRef(null);
useLayoutEffect(function () {
var scrollerContainer = getScroller();
if (scrollerContainer == null) return;
var scroller;
if (isWindow(scrollerContainer)) {
scroller = document.scrollingElement;
} else {
scroller = scrollerContainer;
}
scrollerContainerRef.current = scroller;
var observer = new ResizeObserver(function (entries) {
var _entries$0$contentRec = entries[0].contentRect,
width = _entries$0$contentRec.width,
height = _entries$0$contentRec.height;
setScrollContainerWidth(width);
setScrollContainerHeight(height);
});
observer.observe(scroller);
return function () {
observer.disconnect();
};
}, [getScroller]);
useLayoutEffect(function () {
var node = root.current;
if (node == null) return;
var observer = new ResizeObserver(function (entries) {
var _entries$0$contentRec2 = entries[0].contentRect,
width = _entries$0$contentRec2.width,
height = _entries$0$contentRec2.height;
setTableWidth(width);
setTableHeight(height);
});
observer.observe(node);
return function () {
observer.disconnect();
};
}, [root]);
var state = useMemo(function () {
return {
scrollContainerWidth: scrollContainerWidth,
scrollContainerHeight: scrollContainerHeight,
tableWidth: tableWidth,
tableHeight: tableHeight
};
}, [scrollContainerWidth, scrollContainerHeight, tableWidth, tableHeight]);
return state;
}
function useCollectColumnWidth(columns, defaultColumnWidth) {
var widths = useMemo(function () {
return columns.reduce(function (result, column) {
var width = getColumnWidth(column, defaultColumnWidth);
result.set(getKey(column), width);
return result;
}, new Map());
}, [columns, defaultColumnWidth]);
return useShallowMemo(function () {
return widths;
});
}
function anchorQuery(rects, scrollLeft) {
var left = 0;
var right = rects.length - 1;
var index = -1;
while (left <= right) {
var mid = Math.floor((left + right) / 2);
if (rects[mid].right > scrollLeft) {
index = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
if (index === -1) {
return undefined;
}
return rects[index];
}
function findFirstFixedRightIndex(columns) {
var index = -1;
for (var i = columns.length - 1; i >= 0; i--) {
var column = columns[i];
if (isValidFixedRight(column.fixed)) {
index = i;
} else {
break;
}
}
return index;
}
function findLastFixedLeftIndex(columns) {
return findLastIndex(columns, function (x) {
return isValidFixedLeft(x.fixed);
});
}
function useColumnVirtualize(options) {
var estimateSize = options.estimateSize,
defaultColumnWidth = options.defaultColumnWidth,
overscan = options.overscan,
rawColumns = options.columns,
containerWidth = options.containerWidth,
bodyWrapper = options.bodyWrapper,
_options$disabled = options.disabled,
disabled = _options$disabled === void 0 ? false : _options$disabled;
var lastFixedLeftIndex = findLastFixedLeftIndex(rawColumns);
var firstFixedRightIndex = findFirstFixedRightIndex(rawColumns);
var leftKey = lastFixedLeftIndex > -1 ? getKey(rawColumns[lastFixedLeftIndex]) : null;
var rightKey = firstFixedRightIndex > -1 ? getKey(rawColumns[firstFixedRightIndex]) : null;
var _useState = useState(0),
startIndex = _useState[0],
setStartIndex = _useState[1];
var _useState2 = useState(0),
endIndex = _useState2[0],
setEndIndex = _useState2[1];
var rects = useMemo(function () {
if (disabled) {
return [];
}
var left = 0;
return rawColumns.map(function (column, index) {
var width = getColumnWidth(column, defaultColumnWidth);
var right = left + width;
var rect = {
index: index,
width: width,
left: left,
right: right
};
left = right;
return rect;
});
}, [disabled, rawColumns, defaultColumnWidth]);
// 锚点元素
var anchorRef = useRef({
index: 0,
width: estimateSize,
left: 0,
right: estimateSize
});
var findAnchorRef = useStableFn(function (scrollLeft) {
return anchorQuery(rects, scrollLeft);
});
var updateBoundary = useStableFn(function (scrollLeft, count) {
var anchor = findAnchorRef(scrollLeft);
if (anchor != null) {
anchorRef.current = anchor;
setStartIndex(Math.max(0, anchor.index - overscan));
setEndIndex(anchor.index + count + overscan);
}
});
// 用来判断滚动方向
var scrollLeftRef = useRef(0);
useEffect(function () {
if (disabled) return;
var scrollContainer = bodyWrapper.current;
if (scrollContainer == null) return;
var count = Math.ceil(containerWidth / estimateS