UNPKG

ali-react-table

Version:
1,445 lines (1,360 loc) 98.9 kB
'use strict'; var cx = require('classnames'); var React = require('react'); var rxjs = require('rxjs'); var op = require('rxjs/operators'); var styled = require('styled-components'); var ResizeObserver = require('resize-observer-polyfill'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n['default'] = e; return Object.freeze(n); } var cx__default = /*#__PURE__*/_interopDefaultLegacy(cx); var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var op__namespace = /*#__PURE__*/_interopNamespace(op); var styled__namespace = /*#__PURE__*/_interopNamespace(styled); var styled__default = /*#__PURE__*/_interopDefaultLegacy(styled); var ResizeObserver__default = /*#__PURE__*/_interopDefaultLegacy(ResizeObserver); function isLeafNode(node) { return node.children == null || node.children.length === 0; } /** 遍历所有节点,并将节点收集到一个数组中. * order 参数可用于指定遍历规则: * * `pre` 前序遍历 (默认) * * `post` 后续遍历 * * `leaf-only` 忽略内部节点,只收集叶子节点 * */ function collectNodes(nodes, order = 'pre') { const result = []; dfs(nodes); return result; function dfs(nodes) { if (nodes == null) { return; } for (const node of nodes) { if (isLeafNode(node)) { result.push(node); } else { if (order === 'pre') { result.push(node); dfs(node.children); } else if (order === 'post') { dfs(node.children); result.push(node); } else { dfs(node.children); } } } } } /** 获取一棵树的高度/深度 (0-based) */ function getTreeDepth(nodes) { let maxDepth = -1; dfs(nodes, 0); return maxDepth; function dfs(columns, depth) { for (const column of columns) { if (isLeafNode(column)) { maxDepth = Math.max(maxDepth, depth); } else { dfs(column.children, depth + 1); } } } } function groupBy2(list, iteratee) { const groups = new Map(); for (const item of list) { const key = iteratee(item); if (!groups.has(key)) { groups.set(key, []); } groups.get(key).push(item); } return groups; } function flatMap(array, callback) { const result = []; array.forEach((value, index) => { result.push(...callback(value, index, array)); }); return result; } function fromEntries(entries) { const result = {}; for (const [key, value] of entries) { result[key] = value; } return result; } const arrayUtils = { diff(arr1, arr2) { const set = new Set(arr2); return arr1.filter((x) => !set.has(x)); }, merge(arr1, arr2) { const set = new Set(arr1); return arr1.concat(arr2.filter((x) => !set.has(x))); }, }; function always(value) { return (...args) => value; } /** 在表格的单元格的渲染过程中,先渲染的单元格的 colSpan/rowSpan 会影响到后续单元格是否被渲染 * `SpanManager` 会在内部维护一份状态来记录最近渲染单元格的 colSpan/rowSpan, * 方便后续的单元格快速判断 "是否需要跳过渲染" */ class SpanManager { constructor() { this.rects = []; } testSkip(rowIndex, colIndex) { return this.rects.some(({ left, right, top, bottom }) => left <= colIndex && colIndex < right && top <= rowIndex && rowIndex < bottom); } stripUpwards(rowIndex) { this.rects = this.rects.filter(rect => rect.bottom > rowIndex); } add(rowIndex, colIndex, colSpan, rowSpan) { this.rects.push({ left: colIndex, right: colIndex + colSpan, top: rowIndex, bottom: rowIndex + rowSpan, }); } } function safeRenderHeader(column) { var _a; return (_a = column.title) !== null && _a !== void 0 ? _a : column.name; } function safeGetValue(column, record, rowIndex) { if (column.getValue) { return column.getValue(record, rowIndex); } return record[column.code]; } function safeGetRowKey(primaryKey, record, rowIndex) { let key; if (typeof primaryKey === 'string') { key = record[primaryKey]; } else if (typeof primaryKey === 'function') { key = primaryKey(record); } if (key == null) { key = String(rowIndex); } return key; } function safeGetCellProps(column, record, rowIndex) { if (column.getCellProps) { const value = safeGetValue(column, record, rowIndex); return column.getCellProps(value, record, rowIndex) || {}; } return {}; } function safeRender(column, record, rowIndex) { const value = safeGetValue(column, record, rowIndex); if (column.render) { return column.render(value, record, rowIndex); } return value; } const internals = { safeRenderHeader, safeGetValue, safeGetRowKey, safeGetCellProps, safeRender, }; function composeEventHandler(handler1, handler2) { return (...args) => { // 先执行原有的事件回调函数 handler1(args); handler2(args); // 事件回调函数没有返回值,故这里不进行 return }; } /** 合并两个 cellProps(单元格属性)对象,返回一个合并后的全新对象。 * * mergeCellProps 会按照以下规则来合并两个对象: * * 对于 数字、字符串、布尔值类型的字段,extra 中的字段值将直接覆盖 base 中的字段值(className 是个特例,会进行字符串拼接) * * 对于函数/方法类型的字段(对应单元格的事件回调函数),mergeCellProps 将生成一个新的函数,新函数将按序调用 base 和 extra 中的方法 * * 对于普通对象类型的字段(对应单元格的样式),mergeCellProps 将合并两个对象 * */ function mergeCellProps(base, extra) { if (base == null) { return extra; } if (extra == null) { return base; } const result = Object.assign({}, base); for (const key of Object.keys(extra)) { const value = extra[key]; const type = typeof value; if (value === null) { // value=null 时 覆盖原来的值 result[key] = null; } else if (value === undefined) ; else if (type === 'number' || type === 'string' || type === 'boolean') { if (key === 'className') { result[key] = cx__default['default'](result[key], value); } else { result[key] = value; } } else if (type === 'function') { const prev = result[key]; if (prev == null) { result[key] = value; } else { result[key] = composeEventHandler(prev, value); } } else if (type === 'object') { result[key] = Object.assign({}, result[key], value); } // else `type` is 'bigint' or 'symbol', `value` is an invalid cellProp, ignore it } return result; } /** styled-components 类库的版本,ali-react-table 同时支持 v3 和 v5 */ const STYLED_VERSION = styled__namespace.createGlobalStyle != null ? 'v5' : 'v3'; const STYLED_REF_PROP = STYLED_VERSION === 'v3' ? 'innerRef' : 'ref'; const OVERSCAN_SIZE = 100; const AUTO_VIRTUAL_THRESHOLD = 100; function sum(arr) { let result = 0; arr.forEach((x) => { result += x; }); return result; } // 使用 defer 避免过早引用 window,导致在 SSR 场景下报错 const throttledWindowResize$ = rxjs.defer(() => rxjs.fromEvent(window, 'resize', { passive: true }).pipe(op.throttleTime(150, rxjs.asyncScheduler, { leading: true, trailing: true }))); /** 获取默认的滚动条大小 */ function getScrollbarSizeImpl() { const scrollDiv = document.createElement('div'); scrollDiv.style.position = 'absolute'; scrollDiv.style.width = '100px'; scrollDiv.style.height = '100px'; scrollDiv.style.overflow = 'scroll'; scrollDiv.style.top = '-9999px'; document.body.appendChild(scrollDiv); const scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth; const scrollbarHeight = scrollDiv.offsetHeight - scrollDiv.clientHeight; document.body.removeChild(scrollDiv); return { width: scrollbarWidth, height: scrollbarHeight }; } let scrollBarSize$; function getScrollbarSize() { if (scrollBarSize$ == null) { scrollBarSize$ = new rxjs.BehaviorSubject(getScrollbarSizeImpl()); throttledWindowResize$.pipe(op.map(() => getScrollbarSizeImpl())).subscribe(scrollBarSize$); } return scrollBarSize$.value; } /** 同步多个元素之间的 scrollLeft, 每当 scrollLeft 发生变化时 callback 会被调用 */ function syncScrollLeft(elements, callback) { const bypassSet = new Set(); function publishScrollLeft(origin, scrollLeft) { bypassSet.clear(); for (const elem of elements) { if (elem === origin) { continue; } elem.scrollLeft = scrollLeft; bypassSet.add(elem); } } const subscription = new rxjs.Subscription(); for (const ele of elements) { const listener = () => { if (bypassSet.has(ele)) { bypassSet.delete(ele); return; } const scrollLeft = ele.scrollLeft; publishScrollLeft(ele, scrollLeft); callback(scrollLeft); }; ele.addEventListener('scroll', listener, { passive: true }); subscription.add(() => ele.removeEventListener('scroll', listener)); } return subscription; } /** * Performs equality by iterating through keys on an object and returning false * when any key has values which are not strictly equal between the arguments. * Returns true when the values of all keys are strictly equal. */ function shallowEqual(objA, objB) { const hasOwnProperty = Object.prototype.hasOwnProperty; if (Object.is(objA, objB)) { return true; } if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { return false; } const keysA = Object.keys(objA); const keysB = Object.keys(objB); if (keysA.length !== keysB.length) { return false; } // Test for A's keys different from B. for (let i = 0; i < keysA.length; i++) { if (!hasOwnProperty.call(objB, keysA[i]) || !Object.is(objA[keysA[i]], objB[keysA[i]])) { return false; } } return true; } function resolveVirtualEnabled(virtualEnum, defaultValue) { if (virtualEnum == null || virtualEnum === 'auto') { return defaultValue; } return virtualEnum; } let lockColumnNeedSpecifiedWidthWarned = false; function warnLockColumnNeedSpecifiedWidth(column) { if (!lockColumnNeedSpecifiedWidthWarned) { lockColumnNeedSpecifiedWidthWarned = true; console.warn('[ali-react-table] lock=true 的列需要指定宽度', column); } } let columnHiddenDeprecatedWarned = false; function warnColumnHiddenDeprecated(column) { if (!columnHiddenDeprecatedWarned) { columnHiddenDeprecatedWarned = true; console.warn('[ali-react-table] column.hidden 已经过时,如果需要隐藏该列,请将其从 columns 数组中移除', column); } } /** 检查列配置 & 设置默认宽度 & 剔除隐藏的列 */ function processColumns(columns, defaultColumnWidth) { if (columns == null || !Array.isArray(columns)) { console.warn('[ali-react-table] <BaseTable /> props.columns 需要传入一个数组', columns); columns = []; } function dfs(columns) { const result = []; for (let column of columns) { if (column.width == null) { if (defaultColumnWidth != null) { column = Object.assign(Object.assign({}, column), { width: defaultColumnWidth }); } else if (process.env.NODE_ENV !== 'production' && isLeafNode(column) && column.lock) { warnLockColumnNeedSpecifiedWidth(column); } } if (isLeafNode(column)) { if (column.hidden) { // 被隐藏的列 会在这里被剔除 warnColumnHiddenDeprecated(column); } else { result.push(column); } } else { const nextChildren = dfs(column.children); // 如果 nextChildren 为空,说明所有的子节点均被隐藏了,在这里隐藏父节点 if (nextChildren.length > 0) { result.push(Object.assign(Object.assign({}, column), { children: nextChildren })); } } } return result; } return dfs(columns); } function getLeftNestedLockCount(columns) { let nestedCount = 0; for (const col of columns) { if (isLock(col)) { nestedCount += 1; } else { break; } } return nestedCount; function isLock(col) { if (isLeafNode(col)) { return col.lock; } else { return col.lock || col.children.some(isLock); } } } function getHorizontalRenderRange({ offsetX, maxRenderWidth, flat, useVirtual, }) { if (!useVirtual.horizontal) { return { leftIndex: 0, leftBlank: 0, rightIndex: flat.full.length, rightBlank: 0 }; } let leftIndex = 0; let centerCount = 0; let leftBlank = 0; let centerRenderWidth = 0; const overscannedOffsetX = Math.max(0, offsetX - OVERSCAN_SIZE); while (leftIndex < flat.center.length) { const col = flat.center[leftIndex]; if (col.width + leftBlank < overscannedOffsetX) { leftIndex += 1; leftBlank += col.width; } else { break; } } // 考虑 over scan 之后,中间部分的列至少需要渲染的宽度 const minCenterRenderWidth = maxRenderWidth + (overscannedOffsetX - leftBlank) + 2 * OVERSCAN_SIZE; while (leftIndex + centerCount < flat.center.length) { const col = flat.center[leftIndex + centerCount]; if (col.width + centerRenderWidth < minCenterRenderWidth) { centerRenderWidth += col.width; centerCount += 1; } else { break; } } const rightBlankCount = flat.center.length - leftIndex - centerCount; const rightBlank = sum(flat.center.slice(flat.center.length - rightBlankCount).map((col) => col.width)); return { leftIndex: leftIndex, leftBlank, rightIndex: leftIndex + centerCount, rightBlank, }; } // 一顿计算,将表格本次渲染所需要的数据都给算出来(代码写得有点乱,有较大优化空间) // todo 可以考虑下将 header 部分的计算逻辑也放到这个文件中,目前应该有一些重复的计算逻辑 function calculateRenderInfo(table) { const { offsetX, maxRenderWidth } = table.state; const { useVirtual: useVirtualProp, columns: columnsProp, dataSource: dataSourceProp, defaultColumnWidth, } = table.props; const columns = processColumns(columnsProp, defaultColumnWidth); const leftNestedLockCount = getLeftNestedLockCount(columns); const fullFlat = collectNodes(columns, 'leaf-only'); let flat; let nested; let useVirtual; if (leftNestedLockCount === columns.length) { flat = { left: [], right: [], full: fullFlat, center: fullFlat }; nested = { left: [], right: [], full: columns, center: columns }; useVirtual = { horizontal: false, vertical: false, header: false }; } else { const leftNested = columns.slice(0, leftNestedLockCount); const rightNestedLockCount = getLeftNestedLockCount(columns.slice().reverse()); const centerNested = columns.slice(leftNestedLockCount, columns.length - rightNestedLockCount); const rightNested = columns.slice(columns.length - rightNestedLockCount); const shouldEnableHozVirtual = fullFlat.length >= AUTO_VIRTUAL_THRESHOLD && fullFlat.every((col) => col.width != null); const shouldEnableVerVirtual = dataSourceProp.length >= AUTO_VIRTUAL_THRESHOLD; useVirtual = { horizontal: resolveVirtualEnabled(typeof useVirtualProp === 'object' ? useVirtualProp.horizontal : useVirtualProp, shouldEnableHozVirtual), vertical: resolveVirtualEnabled(typeof useVirtualProp === 'object' ? useVirtualProp.vertical : useVirtualProp, shouldEnableVerVirtual), header: resolveVirtualEnabled(typeof useVirtualProp === 'object' ? useVirtualProp.header : useVirtualProp, false), }; flat = { left: collectNodes(leftNested, 'leaf-only'), full: fullFlat, right: collectNodes(rightNested, 'leaf-only'), center: collectNodes(centerNested, 'leaf-only'), }; nested = { left: leftNested, full: columns, right: rightNested, center: centerNested, }; } const horizontalRenderRange = getHorizontalRenderRange({ maxRenderWidth, offsetX, useVirtual, flat }); const verticalRenderRange = table.getVerticalRenderRange(useVirtual); const { leftBlank, leftIndex, rightBlank, rightIndex } = horizontalRenderRange; const unfilteredVisibleColumnDescriptors = [ ...flat.left.map((col, i) => ({ type: 'normal', col, colIndex: i })), leftBlank > 0 && { type: 'blank', blankSide: 'left', width: leftBlank }, ...flat.center .slice(leftIndex, rightIndex) .map((col, i) => ({ type: 'normal', col, colIndex: flat.left.length + leftIndex + i })), rightBlank > 0 && { type: 'blank', blankSide: 'right', width: rightBlank }, ...flat.right.map((col, i) => ({ type: 'normal', col, colIndex: flat.full.length - flat.right.length + i })), ]; const visibleColumnDescriptors = unfilteredVisibleColumnDescriptors.filter(Boolean); const fullFlatCount = flat.full.length; const leftFlatCount = flat.left.length; const rightFlatCount = flat.right.length; const stickyLeftMap = new Map(); let stickyLeft = 0; for (let i = 0; i < leftFlatCount; i++) { stickyLeftMap.set(i, stickyLeft); stickyLeft += flat.full[i].width; } const stickyRightMap = new Map(); let stickyRight = 0; for (let i = 0; i < rightFlatCount; i++) { stickyRightMap.set(fullFlatCount - 1 - i, stickyRight); stickyRight += flat.full[fullFlatCount - 1 - i].width; } const leftLockTotalWidth = sum(flat.left.map((col) => col.width)); const rightLockTotalWidth = sum(flat.right.map((col) => col.width)); return { horizontalRenderRange, verticalRenderRange, visible: visibleColumnDescriptors, flat, nested, useVirtual, stickyLeftMap, stickyRightMap, leftLockTotalWidth, rightLockTotalWidth, hasLockColumn: nested.left.length > 0 || nested.right.length > 0, }; } function Colgroup({ descriptors }) { return (React__default['default'].createElement("colgroup", null, descriptors.map((descriptor) => { if (descriptor.type === 'blank') { return React__default['default'].createElement("col", { key: descriptor.blankSide, style: { width: descriptor.width } }); } return React__default['default'].createElement("col", { key: descriptor.colIndex, style: { width: descriptor.col.width } }); }))); } const LOCK_SHADOW_PADDING = 20; const prefix = 'art-'; const Classes = { /** BaseTable 表格组件的外层包裹 div */ artTableWrapper: `${prefix}table-wrapper`, artTable: `${prefix}table`, tableHeader: `${prefix}table-header`, tableBody: `${prefix}table-body`, tableFooter: `${prefix}table-footer`, /** 表格行 */ tableRow: `${prefix}table-row`, /** 表头行 */ tableHeaderRow: `${prefix}table-header-row`, /** 单元格 */ tableCell: `${prefix}table-cell`, /** 表头的单元格 */ tableHeaderCell: `${prefix}table-header-cell`, virtualBlank: `${prefix}virtual-blank`, stickyScroll: `${prefix}sticky-scroll`, stickyScrollItem: `${prefix}sticky-scroll-item`, horizontalScrollContainer: `${prefix}horizontal-scroll-container`, lockShadowMask: `${prefix}lock-shadow-mask`, lockShadow: `${prefix}lock-shadow`, leftLockShadow: `${prefix}left-lock-shadow`, rightLockShadow: `${prefix}right-lock-shadow`, /** 数据为空时表格内容的外层 div */ emptyWrapper: `${prefix}empty-wrapper`, loadingWrapper: `${prefix}loading-wrapper`, loadingIndicatorWrapper: `${prefix}loading-indicator-wrapper`, loadingIndicator: `${prefix}loading-indicator`, }; const Z = { lock: 5, header: 15, footer: 10, lockShadow: 20, scrollItem: 30, loadingIndicator: 40, }; const outerBorderStyleMixin = styled.css ` border-top: var(--cell-border-horizontal); border-right: var(--cell-border-vertical); border-bottom: var(--cell-border-horizontal); border-left: var(--cell-border-vertical); td.first, th.first { border-left: none; } td.last, th.last { border-right: none; } thead tr.first th, tbody tr.first td { border-top: none; } &.has-footer tfoot tr.last td { border-bottom: none; } &:not(.has-footer) tbody tr.last td { border-bottom: none; } `; const StyledArtTableWrapper = styled__default['default'].div ` --row-height: 48px; --color: #333; --bgcolor: white; --hover-bgcolor: var(--hover-color, #f5f5f5); --highlight-bgcolor: #eee; --header-row-height: 32px; --header-color: #5a6c84; --header-bgcolor: #e9edf2; --header-hover-bgcolor: #ddd; --header-highlight-bgcolor: #e4e8ed; --cell-padding: 8px 12px; --font-size: 12px; --line-height: 1.28571; --lock-shadow: rgba(152, 152, 152, 0.5) 0 0 6px 2px; --border-color: #dfe3e8; --cell-border: 1px solid var(--border-color); --cell-border-horizontal: var(--cell-border); --cell-border-vertical: var(--cell-border); --header-cell-border: 1px solid var(--border-color); --header-cell-border-horizontal: var(--header-cell-border); --header-cell-border-vertical: var(--header-cell-border); box-sizing: border-box; * { box-sizing: border-box; } cursor: default; color: var(--color); font-size: var(--font-size); line-height: var(--line-height); position: relative; overflow-anchor: none; // 表格外边框由 art-table-wrapper 提供,而不是由单元格提供 &.use-outer-border { ${outerBorderStyleMixin}; } .no-scrollbar { // firefox 中移除滚动条 scrollbar-width: none; // 其他浏览器中移除滚动条 ::-webkit-scrollbar { display: none; } } .${Classes.tableHeader} { overflow-x: auto; overflow-y: hidden; background: var(--header-bgcolor); } .${Classes.tableBody}, .${Classes.tableFooter} { overflow-x: auto; overflow-y: hidden; background: var(--bgcolor); } &.sticky-header .${Classes.tableHeader} { position: sticky; top: 0; z-index: ${Z.header}; } &.sticky-footer .${Classes.tableFooter} { position: sticky; bottom: 0; z-index: ${Z.footer}; } table { width: 100%; table-layout: fixed; border-collapse: separate; border-spacing: 0; display: table; margin: 0; padding: 0; } // 在 tr 上设置 .no-hover 可以禁用鼠标悬停效果 tr:not(.no-hover):hover > td { background: var(--hover-bgcolor); } // 在 tr 设置 highlight 可以为底下的 td 设置为高亮色 // 而设置 .no-highlight 的话则可以禁用高亮效果; tr:not(.no-highlight).highlight > td { background: var(--highlight-bgcolor); } th { font-weight: normal; text-align: left; padding: var(--cell-padding); height: var(--header-row-height); color: var(--header-color); background: var(--header-bgcolor); border: none; border-right: var(--header-cell-border-vertical); border-bottom: var(--header-cell-border-horizontal); } tr.first th { border-top: var(--header-cell-border-horizontal); } th.first { border-left: var(--header-cell-border-vertical); } td { padding: var(--cell-padding); background: var(--bgcolor); height: var(--row-height); border: none; border-right: var(--cell-border-vertical); border-bottom: var(--cell-border-horizontal); } td.first { border-left: var(--cell-border-vertical); } tr.first td { border-top: var(--cell-border-horizontal); } &.has-header tbody tr.first td { border-top: none; } &.has-footer tbody tr.last td { border-bottom: none; } .lock-left, .lock-right { z-index: ${Z.lock}; } //#region 锁列阴影 .${Classes.lockShadowMask} { position: absolute; top: 0; bottom: 0; z-index: ${Z.lockShadow}; pointer-events: none; overflow: hidden; .${Classes.lockShadow} { height: 100%; } .${Classes.leftLockShadow} { margin-right: ${LOCK_SHADOW_PADDING}px; box-shadow: none; &.show-shadow { box-shadow: var(--lock-shadow); border-right: var(--cell-border-vertical); } } .${Classes.rightLockShadow} { margin-left: ${LOCK_SHADOW_PADDING}px; box-shadow: none; &.show-shadow { box-shadow: var(--lock-shadow); border-left: var(--cell-border-vertical); } } } //#endregion //#region 空表格展现 .${Classes.emptyWrapper} { pointer-events: none; color: #99a3b3; font-size: 12px; text-align: center; position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); .empty-image { width: 50px; height: 50px; } .empty-tips { margin-top: 16px; line-height: 1.5; } } //#endregion //#region 粘性滚动条 .${Classes.stickyScroll} { overflow: auto; position: sticky; bottom: 0; z-index: ${Z.scrollItem}; margin-top: -17px; } .${Classes.stickyScrollItem} { // 必须有高度才能出现滚动条 height: 1px; visibility: hidden; } //#endregion //#region 加载样式 .${Classes.loadingWrapper} { position: relative; .${Classes.loadingIndicatorWrapper} { position: absolute; left: 0; right: 0; top: 0; bottom: 0; pointer-events: none; } .${Classes.loadingIndicator} { position: sticky; z-index: ${Z.loadingIndicator}; transform: translateY(-50%); } } //#endregion `; const DefaultEmptyContent = React__default['default'].memo(() => (React__default['default'].createElement(React__default['default'].Fragment, null, React__default['default'].createElement("img", { alt: "empty-image", className: "empty-image", src: "//img.alicdn.com/tfs/TB1l1LcM3HqK1RjSZJnXXbNLpXa-50-50.svg" }), React__default['default'].createElement("div", { className: "empty-tips" }, "\u6CA1\u6709\u7B26\u5408\u67E5\u8BE2\u6761\u4EF6\u7684\u6570\u636E", React__default['default'].createElement("br", null), "\u8BF7\u4FEE\u6539\u6761\u4EF6\u540E\u91CD\u65B0\u67E5\u8BE2")))); function EmptyHtmlTable({ descriptors, isLoading, emptyCellHeight, EmptyContent = DefaultEmptyContent, }) { const show = !isLoading; return (React__default['default'].createElement("table", null, React__default['default'].createElement(Colgroup, { descriptors: descriptors }), React__default['default'].createElement("tbody", null, React__default['default'].createElement("tr", { className: cx__default['default'](Classes.tableRow, 'first', 'last', 'no-hover'), "data-rowindex": 0 }, React__default['default'].createElement("td", { className: cx__default['default'](Classes.tableCell, 'first', 'last'), colSpan: descriptors.length, style: { height: emptyCellHeight !== null && emptyCellHeight !== void 0 ? emptyCellHeight : 200 } }, show && (React__default['default'].createElement("div", { className: Classes.emptyWrapper }, React__default['default'].createElement(EmptyContent, null)))))))); } function range(n) { const array = []; for (let i = 0; i < n; i++) { array.push(i); } return array; } /** 根据当前横向虚拟滚动 对 nested.center 进行过滤,结果只保留当前视野内可见的那些列配置 */ function filterNestedCenter(centerNested, hoz, leftFlatCount) { return dfs(centerNested, leftFlatCount).filtered; function dfs(cols, startColIndex) { let leafCount = 0; const filtered = []; for (const col of cols) { const colIndex = startColIndex + leafCount; if (isLeafNode(col)) { leafCount += 1; if (leftFlatCount + hoz.leftIndex <= colIndex && colIndex < leftFlatCount + hoz.rightIndex) { filtered.push({ colIndex, col }); } } else { const dfsRes = dfs(col.children, colIndex); leafCount += dfsRes.leafCount; if (dfsRes.filtered.length > 0) { filtered.push({ colIndex, col, children: dfsRes.filtered }); } } } return { filtered, leafCount }; } } /** 根据输入的 nested 列配置,算出相应的 leveled & flat 配置方便渲染 */ function calculateLeveledAndFlat(inputNested, rowCount) { const leveled = []; for (let depth = 0; depth < rowCount; depth++) { leveled.push([]); } const flat = []; dfs(inputNested, 0); return { flat, leveled }; function dfs(input, depth) { let leafCount = 0; for (let i = 0; i < input.length; i++) { const indexedCol = input[i]; if (isLeafNode(indexedCol)) { leafCount += 1; const wrapped = { type: 'normal', width: indexedCol.col.width, col: indexedCol.col, colIndex: indexedCol.colIndex, colSpan: 1, isLeaf: true, }; leveled[depth].push(wrapped); flat.push(wrapped); } else { const dfsRes = dfs(indexedCol.children, depth + 1); leafCount += dfsRes.leafCount; if (dfsRes.leafCount > 0) { leveled[depth].push({ type: 'normal', width: indexedCol.col.width, col: indexedCol.col, colIndex: indexedCol.colIndex, colSpan: dfsRes.leafCount, isLeaf: false, }); } } } return { leafCount }; } } /** 包装列配置,附加上 colIndex 属性 */ function attachColIndex(inputNested, colIndexOffset) { return dfs(inputNested, colIndexOffset).result; function dfs(input, startColIndex) { const result = []; let leafCount = 0; for (let i = 0; i < input.length; i++) { const col = input[i]; const colIndex = startColIndex + leafCount; if (isLeafNode(col)) { leafCount += 1; result.push({ colIndex, col }); } else { const sub = dfs(col.children, colIndex); leafCount += sub.leafCount; if (sub.leafCount > 0) { result.push({ col, colIndex, children: sub.result }); } } } return { result, leafCount }; } } /** 计算用于渲染表头的数据结构 */ function calculateHeaderRenderInfo({ flat, nested, horizontalRenderRange: hoz, useVirtual }, rowCount) { if (useVirtual.header) { const leftPart = calculateLeveledAndFlat(attachColIndex(nested.left, 0), rowCount); const filtered = filterNestedCenter(nested.center, hoz, flat.left.length); const centerPart = calculateLeveledAndFlat(filtered, rowCount); const rightPart = calculateLeveledAndFlat(attachColIndex(nested.right, flat.left.length + flat.center.length), rowCount); return { flat: [ ...leftPart.flat, { type: 'blank', width: hoz.leftBlank, blankSide: 'left' }, ...centerPart.flat, { type: 'blank', width: hoz.rightBlank, blankSide: 'right' }, ...rightPart.flat, ], leveled: range(rowCount).map((depth) => [ ...leftPart.leveled[depth], { type: 'blank', width: hoz.leftBlank, blankSide: 'left' }, ...centerPart.leveled[depth], { type: 'blank', width: hoz.rightBlank, blankSide: 'right' }, ...rightPart.leveled[depth], ]), }; } return calculateLeveledAndFlat(attachColIndex(nested.full, 0), rowCount); } function TableHeader({ info }) { const { nested, flat, stickyLeftMap, stickyRightMap } = info; const rowCount = getTreeDepth(nested.full) + 1; const headerRenderInfo = calculateHeaderRenderInfo(info, rowCount); const fullFlatCount = flat.full.length; const leftFlatCount = flat.left.length; const rightFlatCount = flat.right.length; const thead = headerRenderInfo.leveled.map((wrappedCols, level) => { const headerCells = wrappedCols.map((wrapped) => { var _a, _b; if (wrapped.type === 'normal') { const { colIndex, colSpan, isLeaf, col } = wrapped; const headerCellProps = (_a = col.headerCellProps) !== null && _a !== void 0 ? _a : {}; const positionStyle = {}; if (colIndex < leftFlatCount) { positionStyle.position = 'sticky'; positionStyle.left = stickyLeftMap.get(colIndex); } else if (colIndex >= fullFlatCount - rightFlatCount) { positionStyle.position = 'sticky'; positionStyle.right = stickyRightMap.get(colIndex + colSpan - 1); } return (React__default['default'].createElement("th", Object.assign({ key: colIndex }, headerCellProps, { className: cx__default['default'](Classes.tableHeaderCell, headerCellProps.className, { first: colIndex === 0, last: colIndex + colSpan === fullFlatCount, 'lock-left': colIndex < leftFlatCount, 'lock-right': colIndex >= fullFlatCount - rightFlatCount, }), colSpan: colSpan, rowSpan: isLeaf ? rowCount - level : undefined, style: Object.assign(Object.assign({ textAlign: col.align }, headerCellProps.style), positionStyle) }), (_b = col.title) !== null && _b !== void 0 ? _b : col.name)); } else { if (wrapped.width > 0) { return React__default['default'].createElement("th", { key: wrapped.blankSide }); } else { return null; } } }); return (React__default['default'].createElement("tr", { key: level, className: cx__default['default'](Classes.tableHeaderRow, { first: level === 0, last: level === rowCount - 1, }) }, headerCells)); }); return (React__default['default'].createElement("table", null, React__default['default'].createElement("colgroup", null, headerRenderInfo.flat.map((wrapped) => { if (wrapped.type === 'blank') { if (wrapped.width > 0) { return React__default['default'].createElement("col", { key: wrapped.blankSide, style: { width: wrapped.width } }); } else { return null; } } else { return React__default['default'].createElement("col", { key: wrapped.colIndex, style: { width: wrapped.width } }); } })), React__default['default'].createElement("thead", null, thead))); } function getNodeName(element) { return element ? (element.nodeName || '').toLowerCase() : null; } function getWindow(node) { if (node == null) { return window; } if (node.toString() !== '[object Window]') { var ownerDocument = node.ownerDocument; return ownerDocument ? ownerDocument.defaultView || window : window; } return node; } function getComputedStyle(element) { return getWindow(element).getComputedStyle(element); } function isElement(node) { var OwnElement = getWindow(node).Element; return node instanceof OwnElement || node instanceof Element; } function isHTMLElement(node) { var OwnElement = getWindow(node).HTMLElement; return node instanceof OwnElement || node instanceof HTMLElement; } function isShadowRoot(node) { // IE 11 has no ShadowRoot if (typeof ShadowRoot === 'undefined') { return false; } var OwnElement = getWindow(node).ShadowRoot; return node instanceof OwnElement || node instanceof ShadowRoot; } function isTableElement(element) { return ['table', 'td', 'th'].indexOf(getNodeName(element)) >= 0; } function getDocumentElement(element) { // $FlowFixMe[incompatible-return]: assume body is always available return ((isElement(element) ? element.ownerDocument : // $FlowFixMe[prop-missing] element.document) || window.document).documentElement; } function getParentNode(element) { if (getNodeName(element) === 'html') { return element; } return (// this is a quicker (but less type safe) way to save quite some bytes from the bundle // $FlowFixMe[incompatible-return] // $FlowFixMe[prop-missing] element.assignedSlot || // step into the shadow DOM of the parent of a slotted node element.parentNode || ( // DOM Element detected isShadowRoot(element) ? element.host : null) || // ShadowRoot detected // $FlowFixMe[incompatible-call]: HTMLElement is a Node getDocumentElement(element) // fallback ); } function getTrueOffsetParent(element) { if (!isHTMLElement(element) || // https://github.com/popperjs/popper-core/issues/837 getComputedStyle(element).position === 'fixed') { return null; } return element.offsetParent; } // `.offsetParent` reports `null` for fixed elements, while absolute elements // return the containing block function getContainingBlock(element) { var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') !== -1; var isIE = navigator.userAgent.indexOf('Trident') !== -1; if (isIE && isHTMLElement(element)) { // In IE 9, 10 and 11 fixed elements containing block is always established by the viewport var elementCss = getComputedStyle(element); if (elementCss.position === 'fixed') { return null; } } var currentNode = getParentNode(element); while (isHTMLElement(currentNode) && ['html', 'body'].indexOf(getNodeName(currentNode)) < 0) { var css = getComputedStyle(currentNode); // This is non-exhaustive but covers the most common CSS properties that // create a containing block. // https://developer.mozilla.org/en-US/docs/Web/CSS/Containing_block#identifying_the_containing_block if (css.transform !== 'none' || css.perspective !== 'none' || css.contain === 'paint' || ['transform', 'perspective'].indexOf(css.willChange) !== -1 || isFirefox && css.willChange === 'filter' || isFirefox && css.filter && css.filter !== 'none') { return currentNode; } else { currentNode = currentNode.parentNode; } } return null; } // Gets the closest ancestor positioned element. Handles some edge cases, // such as table ancestors and cross browser bugs. function getOffsetParent(element) { var window = getWindow(element); var offsetParent = getTrueOffsetParent(element); while (offsetParent && isTableElement(offsetParent) && getComputedStyle(offsetParent).position === 'static') { offsetParent = getTrueOffsetParent(offsetParent); } if (offsetParent && (getNodeName(offsetParent) === 'html' || getNodeName(offsetParent) === 'body' && getComputedStyle(offsetParent).position === 'static')) { return window; } return offsetParent || getContainingBlock(element) || window; } function getWindowScroll(node) { var win = getWindow(node); var scrollLeft = win.pageXOffset; var scrollTop = win.pageYOffset; return { scrollLeft: scrollLeft, scrollTop: scrollTop }; } function isScrollParent(element) { // Firefox wants us to check `-x` and `-y` variations as well var _getComputedStyle = getComputedStyle(element), overflow = _getComputedStyle.overflow, overflowX = _getComputedStyle.overflowX, overflowY = _getComputedStyle.overflowY; return /auto|scroll|overlay|hidden/.test(overflow + overflowY + overflowX); } function isWindow(arg) { return arg.toString() === '[object Window]'; } function isBody(arg) { return getNodeName(arg) === 'body'; } function isHtml(arg) { return getNodeName(arg) === 'html'; } function isHtmlOrBody(arg) { return isHtml(arg) || isBody(arg); } // 计算从 start(子元素)到 stop(祖先元素)之间所有元素的 scrollTop 或 scrollLeft 的和 // 注意 start 和 stop 都是 INCLUSIVE 的,即两者的 scrollTop 或 scrollLeft 都会统计在内 function accumulateScrollOffset(start, stop, scrollOffsetKey) { let result = 0; let elem = start; while (elem != null) { result += elem[scrollOffsetKey]; if (elem === stop || (isWindow(stop) && isHtmlOrBody(elem))) { break; } elem = elem.parentElement; } if (isWindow(stop)) { result += getWindowScroll(elem)[scrollOffsetKey]; } return result; } /** * 获取 target 相对于 base 的布局大小和相对位置。 * 注意该方法会考虑滚动所带来的影响 */ function getRelativeLayoutRect(base, target) { if (isWindow(target) || isHtmlOrBody(target)) { return { left: 0, right: window.innerWidth, top: 0, bottom: window.innerHeight, }; } let deltaX = 0; let deltaY = 0; let elem = target; while (elem != null && elem != base) { deltaY += elem.offsetTop; deltaX += elem.offsetLeft; const offsetParent = getOffsetParent(elem); deltaY -= accumulateScrollOffset(elem.parentElement, offsetParent, 'scrollTop'); deltaX -= accumulateScrollOffset(elem.parentElement, offsetParent, 'scrollLeft'); if (isWindow(offsetParent)) { break; } deltaY += offsetParent.clientTop; deltaX += offsetParent.clientLeft; elem = offsetParent; } return { top: deltaY, bottom: deltaY + target.offsetHeight, left: deltaX, right: deltaX + target.offsetWidth, }; } function findCommonOffsetAncestor(target, scrollParent) { if (isWindow(scrollParent)) { return scrollParent; } const offsetParents = listOffsetParents(target); if (offsetParents.includes(scrollParent)) { return scrollParent; } return getOffsetParent(scrollParent); } // 列出 target 元素上层的所有 offset parents function listOffsetParents(target) { const result = []; let elem = target; while (true) { if (isWindow(elem)) { break; } elem = getOffsetParent(elem); result.push(elem); } return result; } function fromScrollEvent(element) { return rxjs.fromEvent(element, 'scroll', { passive: true }); } function fromResizeEvent(element) { if (isWindow(element)) { return rxjs.fromEvent(element, 'resize', { passive: true }); } return new rxjs.Observable((subscriber) => { const resizeObserver = new ResizeObserver__default['default']((entries) => { subscriber.next(entries); }); resizeObserver.observe(element); return () => { resizeObserver.disconnect(); }; }); } function getScrollParent(elem) { if (['html', 'body', '#document'].includes(getNodeName(elem))) { return getWindow(elem); } if (isHTMLElement(elem) && isScrollParent(elem)) { return elem; } return getScrollParent(getParentNode(elem)); } // 获取 target 相对于「它的滚动父元素」的可见部分的大小与位置 function getRichVisibleRectsStream(target, structureMayChange$, virtualDebugLabel) { return structureMayChange$.pipe(op__namespace.startWith('init'), op__namespace.map(() => { // target 的第一个滚动父元素,我们认为这就是虚拟滚动发生的地方 // 即虚拟滚动不考虑「更上层元素发生滚动」的情况 const scrollParent = getScrollParent(target); // target 和 scrollParent 的共同 offset 祖先,作为布局尺寸计算时的参照元素 const commonOffsetAncestor = findCommonOffsetAncestor(target, scrollParent); return { scrollParent, commonOffsetAncestor }; }), op__namespace.distinctUntilChanged(shallowEqual), op__namespace.tap((structure) => { if (virtualDebugLabel) { console.log(`%c[ali-react-table STRUCTURE ${virtualDebugLabel}]`, 'color: #4f9052; font-weight: bold', '\ntarget:', target, '\nscrollParent:', structure.scrollParent, '\ncommonOffsetAncestor:', structure.commonOffsetAncestor); } }), op__namespace.switchMap(({ scrollParent, commonOffsetAncestor }) => { const events$ = rxjs.merge(fromScrollEvent(scrollParent), fromResizeEvent(scrollParent), fromResizeEvent(target)); return events$.pipe(op__namespace.map((event) => ({ targetRect: getRelativeLayoutRect(commonOffsetAncestor, target), scrollParentRect: getRelativeLayoutRect(commonOffsetAncestor, scrollParent), event, })), op__namespace.map(({ event, scrollParentRect, targetRect }) => ({ event, targetRect, scrollParentRect, offsetY: Math.max(0, scrollParentRect.top - targetRect.top), // 表格的横向滚动总是发生在表格内部,所以这里不需要计算 offsetX // offsetX: Math.max(0, scrollParentRect.left - targetRect.left), clipRect: { left: Math.max(targetRect.left, scrollParentRect.left), top: Math.max(targetRect.top, scrollParentRect.top), right: Math.min(targetRect.right, scrollParentRect.right), bottom: Math.min(targetRect.bottom, scrollParentRect.bottom), }, }))); }), op__namespace.tap((rects) => { if (virtualDebugLabel) { console.log(`%c[ali-react-table RECTS ${virtualDebugLabel}]`, 'color: #4f9052; font-weight: bold', '\noffsetY:', rects.offsetY, '\ntargetRect:', rects.targetRect, '\nscrollParentRect:', rects.scrollParentRect, '\nclipRect:', rects.clipRect, '\nevent:', rects.event); } })); } function getFullRenderRange(rowCount) { return { topIndex: 0, topBlank: 0, bottomIndex: rowCount, bottomBlank: 0, }; } function makeRowHeightManager(initRowCount, estimatedRowHeight) { const cache = new Array(initRowCount).fill(estimatedRowHeight); function getRenderRange(offset, maxRenderHeight, rowCount) { if (cache.length !== rowCount) { setRowCount(rowCount); } if (maxRenderHeight <= 0) { // maxRenderHeight <= 0 说明表格目前在 viewport 之外 if (offset <= 0) { // 表格在 viewport 下方 return getRenderRangeWhenBelowView(); } else { // 表格在 viewport 上方 return getRenderRangeWhenAboveView(); } } else { // 表格与 viewport 相交 return getRenderRangeWhenInView(); } function getRenderRangeWhenBelowView() { const start = { topIndex: 0, topBlank: 0 }; const end = getEnd(0, start); return Object.assign(Object.assign({}, start), end); } function getRenderRangeWhenAboveView() { const totalSize = getEstimatedTotalSize(rowCount); const start = getStart(totalSize); const end = getEnd(totalSize, start); return Object.assign(Object.assign({}, start), end); } function getRenderRangeWhenInView() { const start = getStart(offset); const end = getEnd(offset + maxRenderHeight, start); return Object.assign(Object.assign({}, start), end); } /** 获取虚拟滚动在 开始位置上的信息 */ function getStart(offset) { if (cache.length === 0) { return { topIndex: 0, topBlank: 0 }; } let topIndex = 0; let topBlank = 0; while (topIndex < cache.length) { const h = cache[topIndex]; if (topBlank + h >= offset) { break; } topBlank += h; topIndex += 1; } return overscanUpwards(topIndex, topBlank); } function overscanUpwards(topIndex, topBlank) { let overscanSize = 0; let overscanCount = 0; while (overscanCount < topIndex && overscanSize < OVERSCAN_SIZE) { overscanCount += 1; overscanSize += cache[topIndex - overscanCount]; } return { topIndex: topIndex - overscanCount, topBlank: topBlank - overscanSize, }; } /** 获取虚拟滚动 在结束位置上的信息 */ function getEnd(endOffset, startInfo) { let bottomIndex = startInfo.topIndex; let offset = startInfo.topBlank;