ali-react-table
Version:
1,445 lines (1,360 loc) • 98.9 kB
JavaScript
'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;