UNPKG

@are-visual/virtual-table

Version:
1,576 lines (1,544 loc) 71.3 kB
import { jsx, jsxs, Fragment as Fragment$1 } from 'react/jsx-runtime'; import clsx from 'clsx'; import { useRef, createContext, useContext, useMemo, memo, useEffect, useState, useLayoutEffect, useCallback, 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 nodeHeightValid = options.nodeHeightValid, 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]; // 解决 PR #5 的问题 // 在 scroll 事件中获取到当前的 node 高度为 0 就认为节点不可见, // 就不需要触发 updateBoundary // 把它推迟到下一次渲染检测 node 高度不为 0 的时候 var reUpdateBoundary = useRef(); useLayoutEffect(function () { var reUpdate = reUpdateBoundary.current; if (reUpdate == null) return; if (nodeHeightValid()) { reUpdate(); reUpdateBoundary.current = undefined; } }); 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 }); // 场景:分页请求 Table 数据,滚动到底部,查看下一页 // dataSource重置为空数组 // 网络请求结束后再 setDataSource // 此时的 startIndex 和 endIndex 还是上一页的结果,导致渲染出现空白 // 所以 dataSource.length 发生改变,重新计算 startIndex、endIndex var updateBoundaryFlagDep = rawData.length; // 用来判断滚动方向 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) { if (!nodeHeightValid()) { reUpdateBoundary.current = function () { return updateBoundary(scrollTop); }; } else { updateBoundary(scrollTop); } } } else { if (scrollTop < anchorRef.current.top) { if (!nodeHeightValid()) { reUpdateBoundary.current = function () { return updateBoundary(scrollTop); }; } else { updateBoundary(scrollTop); } } } scrollTopRef.current = scrollTop; }; var prevHeight = 0; var stopListen = onResize(container, function (rect) { // 使用 div 作为滚动容器时,并且放置在 <Tabs> 组件内,当前 tab 切换到非激活状态时, // 父元素会被设置 display:none,导致容器高度变为 0,导致调用 updateBoundary // 来回切换会发现每一次都会往下移动几个 row // 这里加一个判断,rect.height 为0时,不处理本次的 resize 事件(这就是 display:none) 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, updateBoundaryFlagDep, nodeHeightValid]); 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 useNodeHeight() { var nodeRef = useRef(null); var getIsVisible = useStableFn(function () { var node = nodeRef.current; if (node == null) return false; return node.offsetHeight > 0; }); return [nodeRef, getIsVisible]; } 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 _useNodeHeight = useNodeHeight(), tbodyNode = _useNodeHeight[0], nodeHeightValid = _useNodeHeight[1]; var _useRowVirtualize = useRowVirtualize({ nodeHeightValid: nodeHeightValid, 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, tbodyNode, 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);