@douyinfe/semi-ui
Version:
A modern, comprehensive, flexible design system and UI library. Connect DesignOps & DevOps. Quickly build beautiful React apps. Maintained by Douyin-fe team.
1,250 lines • 58.1 kB
JavaScript
import _isPlainObject from "lodash/isPlainObject";
import _isObject from "lodash/isObject";
import _isFunction from "lodash/isFunction";
import _difference from "lodash/difference";
import _omit from "lodash/omit";
import _each from "lodash/each";
import _flattenDeep from "lodash/flattenDeep";
import _debounce from "lodash/debounce";
import _some from "lodash/some";
import _findIndex from "lodash/findIndex";
import _find from "lodash/find";
import _includes from "lodash/includes";
import _noop from "lodash/noop";
import _get from "lodash/get";
var __rest = this && this.__rest || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]];
}
return t;
};
/* eslint-disable react/no-did-update-set-state */
import React, { createRef, isValidElement } from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { mergeQueries, equalWith, assignColumnKeys, flattenColumns, getAllDisabledRowKeys, shouldShowEllipsisTitle } from '@douyinfe/semi-foundation/lib/es/table/utils';
import Store from '@douyinfe/semi-foundation/lib/es/utils/Store';
import TableFoundation from '@douyinfe/semi-foundation/lib/es/table/foundation';
import { strings, cssClasses, numbers } from '@douyinfe/semi-foundation/lib/es/table/constants';
import '@douyinfe/semi-foundation/lib/es/table/table.css';
import Spin from '../spin';
import BaseComponent from '../_base/baseComponent';
import LocaleConsumer from '../locale/localeConsumer';
import ColumnShape from './ColumnShape';
import getColumns from './getColumns';
import TableContext from './table-context';
import TableContextProvider from './TableContextProvider';
import ColumnSelection from './ColumnSelection';
import TablePagination from './TablePagination';
import ColumnFilter from './ColumnFilter';
import ColumnSorter from './ColumnSorter';
import ExpandedIcon from './CustomExpandIcon';
import HeadTable from './HeadTable';
import BodyTable from './Body';
import { logger, cloneDeep, mergeComponents, mergeColumns } from './utils';
class Table extends BaseComponent {
get adapter() {
var _this = this;
return Object.assign(Object.assign({}, super.adapter), {
resetScrollY: () => {
if (this.bodyWrapRef.current) {
this.bodyWrapRef.current.scrollTop = 0;
}
},
setSelectedRowKeys: selectedRowKeys => {
this.setState({
rowSelection: Object.assign(Object.assign({}, this.state.rowSelection), {
selectedRowKeys: [...selectedRowKeys],
selectedRowKeysSet: new Set(selectedRowKeys)
})
});
},
setDisabledRowKeys: disabledRowKeys => {
this.setState({
disabledRowKeys,
disabledRowKeysSet: new Set(disabledRowKeys)
});
},
setCurrentPage: currentPage => {
const {
pagination
} = this.state;
if (typeof pagination === 'object') {
this.setState({
pagination: Object.assign(Object.assign({}, pagination), {
currentPage
})
});
} else {
this.setState({
pagination: {
currentPage
}
});
}
},
setPagination: pagination => this.setState({
pagination
}),
setGroups: groups => this.setState({
groups
}),
setDataSource: dataSource => this.setState({
dataSource
}),
setExpandedRowKeys: expandedRowKeys => this.setState({
expandedRowKeys: [...expandedRowKeys]
}),
setQuery: function () {
let query = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let queries = [..._this.state.queries];
queries = mergeQueries(query, queries);
_this.setState({
queries
});
},
// Update queries when filtering or sorting
setQueries: queries => this.setState({
queries
}),
setFlattenData: flattenData => this.setState({
flattenData
}),
setAllRowKeys: allRowKeys => this.setState({
allRowKeys
}),
setHoveredRowKey: hoveredRowKey => {
this.store.setState({
hoveredRowKey
});
},
setCachedFilteredSortedDataSource: filteredSortedDataSource => {
this.cachedFilteredSortedDataSource = filteredSortedDataSource;
},
setCachedFilteredSortedRowKeys: filteredSortedRowKeys => {
this.cachedFilteredSortedRowKeys = filteredSortedRowKeys;
this.cachedFilteredSortedRowKeysSet = new Set(filteredSortedRowKeys);
},
setAllDisabledRowKeys: allDisabledRowKeys => {
const allDisabledRowKeysSet = new Set(allDisabledRowKeys);
this.setState({
allDisabledRowKeys,
allDisabledRowKeysSet
});
},
getCurrentPage: () => _get(this.state, 'pagination.currentPage', 1),
getCurrentPageSize: () => _get(this.state, 'pagination.pageSize', numbers.DEFAULT_PAGE_SIZE),
getCachedFilteredSortedDataSource: () => this.cachedFilteredSortedDataSource,
getCachedFilteredSortedRowKeys: () => this.cachedFilteredSortedRowKeys,
getCachedFilteredSortedRowKeysSet: () => this.cachedFilteredSortedRowKeysSet,
getAllDisabledRowKeys: () => this.state.allDisabledRowKeys,
getAllDisabledRowKeysSet: () => this.state.allDisabledRowKeysSet,
notifyFilterDropdownVisibleChange: (visible, dataIndex) => this._invokeColumnFn(dataIndex, 'onFilterDropdownVisibleChange', visible),
notifyChange: function () {
return _this.props.onChange(...arguments);
},
notifyExpand: function () {
return _this.props.onExpand(...arguments);
},
notifyExpandedRowsChange: function () {
return _this.props.onExpandedRowsChange(...arguments);
},
notifySelect: function () {
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
return _this._invokeRowSelection('onSelect', ...args);
},
notifySelectAll: function () {
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
args[_key2] = arguments[_key2];
}
return _this._invokeRowSelection('onSelectAll', ...args);
},
notifySelectInvert: function () {
for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
args[_key3] = arguments[_key3];
}
return _this._invokeRowSelection('onSelectInvert', ...args);
},
notifySelectionChange: function () {
for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
args[_key4] = arguments[_key4];
}
return _this._invokeRowSelection('onChange', ...args);
},
isAnyColumnFixed: columns => _some(this.getColumns(columns || this.props.columns, this.props.children), column => Boolean(column.fixed)),
useFixedHeader: () => {
const {
scroll,
sticky
} = this.props;
if (_get(scroll, 'y')) {
return true;
}
if (sticky) {
return true;
}
return false;
},
getTableLayout: () => {
let isFixed = false;
const {
flattenColumns
} = this.state;
if (Array.isArray(flattenColumns)) {
isFixed = flattenColumns.some(column => Boolean(column.ellipsis) || Boolean(column.fixed));
}
if (this.adapter.useFixedHeader()) {
isFixed = true;
}
return isFixed ? 'fixed' : 'auto';
},
setHeadWidths: function (headWidths) {
let index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
if (!equalWith(_this.state.headWidths[index], headWidths)) {
// The map call depends on the last state
_this.setState(state => {
const newHeadWidths = [...state.headWidths];
newHeadWidths[index] = [...headWidths];
return {
headWidths: newHeadWidths
};
});
}
},
getHeadWidths: function () {
let index = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 0;
if (_this.state.headWidths.length && typeof index === 'number') {
const configs = _this.state.headWidths[index] || [];
return configs.map(item => item.width);
}
return [];
},
// This method is called by row rendering function
getCellWidths: function (flattenedColumns) {
let flattenedWidths = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
let ignoreScrollBarKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;
if (Array.isArray(flattenedColumns) && flattenedColumns.length) {
flattenedWidths = flattenedWidths == null && _this.state.headWidths.length ? _flattenDeep(_this.state.headWidths) : [];
if (Array.isArray(flattenedWidths) && flattenedWidths.length) {
return flattenedColumns.reduce((result, column) => {
const found = column.key === strings.DEFAULT_KEY_COLUMN_SCROLLBAR && ignoreScrollBarKey ? null : _find(flattenedWidths, item => item && item.key != null && item.key === column.key);
if (found) {
result.push(found.width);
}
return result;
}, []);
}
}
return [];
},
mergedRowExpandable: record => {
const {
expandedRowRender,
childrenRecordName,
rowExpandable
} = this.props;
const children = _get(record, childrenRecordName);
const hasExpandedRowRender = typeof expandedRowRender === 'function';
const hasRowExpandable = typeof rowExpandable === 'function';
const hasChildren = Array.isArray(children) && children.length;
const strictExpandableResult = hasRowExpandable && rowExpandable(record);
const looseExpandableResult = !hasRowExpandable || strictExpandableResult;
return (hasExpandedRowRender || hasChildren) && looseExpandableResult || !(hasExpandedRowRender || hasChildren) && strictExpandableResult;
},
isAnyColumnUseFullRender: columns => _some(columns, column => Boolean(column.useFullRender)),
getNormalizeColumns: () => this.normalizeColumns,
getHandleColumns: () => this.handleColumns,
getMergePagination: () => this.mergePagination,
setBodyHasScrollbar: bodyHasScrollBar => {
if (bodyHasScrollBar !== this.state.bodyHasScrollBar) {
this.setState({
bodyHasScrollBar
});
}
},
stopPropagation(e) {
// The event definition here is not very accurate for now, it belongs to a broad structure definition
if (e && typeof e === 'object') {
if (typeof e.stopPropagation === 'function') {
e.stopPropagation();
}
if (e.nativeEvent && typeof e.nativeEvent.stopPropagation === 'function') {
e.nativeEvent.stopPropagation();
} else if (typeof e.stopImmediatePropagation === 'function') {
e.stopImmediatePropagation();
}
}
}
});
}
constructor(props, context) {
var _this2;
// TODO: notify when data don't have key
super(props);
_this2 = this;
this._warnIfNoKey = () => {
if ((this.props.rowSelection || this.props.expandedRowRender) && _some(this.props.dataSource, record => this.foundation.getRecordKey(record) == null)) {
logger.error('You must specify a key for each element in the dataSource or use "rowKey" to specify an attribute name as the primary key!');
}
};
this._invokeRowSelection = function (funcName) {
const func = _get(_this2.state, ['rowSelection', funcName]);
if (typeof func === 'function') {
for (var _len5 = arguments.length, args = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
args[_key5 - 1] = arguments[_key5];
}
func(...args);
}
};
this._invokeColumnFn = function (key, funcName) {
if (key && funcName) {
const column = _this2.foundation.getQuery(key);
const func = _get(column, funcName, null);
if (typeof func === 'function') {
for (var _len6 = arguments.length, args = new Array(_len6 > 2 ? _len6 - 2 : 0), _key6 = 2; _key6 < _len6; _key6++) {
args[_key6 - 2] = arguments[_key6];
}
func(...args);
}
}
};
this._cacheHeaderRef = node => {
this.headerWrapRef.current = node;
};
this.getCurrentPageData = () => {
const pageData = this.foundation.getCurrentPageData();
const retObj = ['dataSource', 'groups'].reduce((result, key) => {
if (pageData[key]) {
result[key] = pageData[key];
}
return result;
}, {});
return cloneDeep(retObj);
};
this.getColumns = (columns, children) => !Array.isArray(columns) || !columns || !columns.length ? getColumns(children) : columns;
// @ts-ignore
this.getCellWidths = function () {
return _this2.foundation.getCellWidths(...arguments);
};
// @ts-ignore
this.setHeadWidths = function () {
return _this2.foundation.setHeadWidths(...arguments);
};
// @ts-ignore
this.getHeadWidths = function () {
return _this2.foundation.getHeadWidths(...arguments);
};
// @ts-ignore
this.mergedRowExpandable = function () {
return _this2.foundation.mergedRowExpandable(...arguments);
};
// @ts-ignore
this.setBodyHasScrollbar = function () {
return _this2.foundation.setBodyHasScrollbar(...arguments);
};
this.handleWheel = event => {
const {
scroll = {}
} = this.props;
if (window.navigator.userAgent.match(/Trident\/7\./) && scroll.y) {
event.preventDefault();
const wd = event.deltaY;
const {
target
} = event;
// const { bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
const bodyTable = this.bodyWrapRef.current;
let scrollTop = 0;
if (this.lastScrollTop) {
scrollTop = this.lastScrollTop + wd;
} else {
scrollTop = wd;
}
if (bodyTable && target !== bodyTable) {
bodyTable.scrollTop = scrollTop;
}
}
};
this.handleBodyScrollLeft = e => {
if (e.currentTarget !== e.target) {
return;
}
const {
target
} = e;
// const { headTable, bodyTable } = this;
const headTable = this.headerWrapRef.current;
const bodyTable = this.bodyWrapRef.current;
if (target.scrollLeft !== this.lastScrollLeft) {
if (target === bodyTable && headTable) {
headTable.scrollLeft = target.scrollLeft;
} else if (target === headTable && bodyTable) {
bodyTable.scrollLeft = target.scrollLeft;
}
this.setScrollPositionClassName();
}
// Remember last scrollLeft for scroll direction detecting.
this.lastScrollLeft = target.scrollLeft;
};
this.handleWindowResize = () => {
this.syncTableWidth();
this.setScrollPositionClassName();
};
this.handleBodyScrollTop = e => {
const {
target
} = e;
if (e.currentTarget !== target) {
return;
}
const {
scroll = {}
} = this.props;
// const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
const headTable = this.headerWrapRef.current;
const bodyTable = this.bodyWrapRef.current;
if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) {
const {
scrollTop
} = target;
if (bodyTable && target !== bodyTable) {
bodyTable.scrollTop = scrollTop;
}
}
// Remember last scrollTop for scroll direction detecting.
this.lastScrollTop = target.scrollTop;
};
this.handleBodyScroll = e => {
this.handleBodyScrollLeft(e);
this.handleBodyScrollTop(e);
};
this.setScrollPosition = position => {
const {
prefixCls
} = this.props;
const positionAll = [`${prefixCls}-scroll-position-both`, `${prefixCls}-scroll-position-middle`, `${prefixCls}-scroll-position-left`, `${prefixCls}-scroll-position-right`];
this.scrollPosition = position;
const tableNode = this.wrapRef.current;
if (tableNode && tableNode.nodeType) {
if (position === 'both') {
const acceptPosition = [`${prefixCls}-scroll-position-left`, `${prefixCls}-scroll-position-right`];
tableNode.classList.remove(..._difference(positionAll, acceptPosition));
tableNode.classList.add(...acceptPosition);
} else {
const acceptPosition = [`${prefixCls}-scroll-position-${position}`];
tableNode.classList.remove(..._difference(positionAll, acceptPosition));
tableNode.classList.add(...acceptPosition);
}
}
};
this.setScrollPositionClassName = () => {
const node = this.bodyWrapRef.current;
if (node && node.children && node.children.length) {
const scrollToLeft = node.scrollLeft === 0;
// why use Math.abs? @see https://bugzilla.mozilla.org/show_bug.cgi?id=1447743
const scrollToRight = Math.abs(node.scrollLeft) + 1 >= node.children[0].getBoundingClientRect().width - node.getBoundingClientRect().width;
if (scrollToLeft && scrollToRight) {
this.setScrollPosition('both');
} else if (scrollToLeft) {
this.setScrollPosition('left');
} else if (scrollToRight) {
this.setScrollPosition('right');
} else if (this.scrollPosition !== 'middle') {
this.setScrollPosition('middle');
}
}
};
this.syncTableWidth = () => {
if (this.rootWrapRef && this.rootWrapRef.current) {
this.setState({
tableWidth: this.rootWrapRef.current.getBoundingClientRect().width
});
}
};
this.renderSelection = function () {
let record = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let inHeader = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
let index = arguments.length > 2 ? arguments[2] : undefined;
const {
rowSelection,
allDisabledRowKeysSet
} = _this2.state;
if (rowSelection && typeof rowSelection === 'object') {
const {
selectedRowKeys = [],
selectedRowKeysSet = new Set(),
getCheckboxProps,
disabled,
renderCell
} = rowSelection;
const allRowKeys = _this2.cachedFilteredSortedRowKeys;
const allRowKeysSet = _this2.cachedFilteredSortedRowKeysSet;
const allIsSelected = _this2.foundation.allIsSelected(selectedRowKeysSet, allDisabledRowKeysSet, allRowKeys);
const hasRowSelected = _this2.foundation.hasRowSelected(selectedRowKeys, allRowKeysSet);
const indeterminate = hasRowSelected && !allIsSelected;
if (inHeader) {
const columnKey = _get(rowSelection, 'key', strings.DEFAULT_KEY_COLUMN_SELECTION);
const originNode = /*#__PURE__*/React.createElement(ColumnSelection, {
"aria-label": `${allIsSelected ? 'Deselect' : 'Select'} all rows`,
disabled: disabled,
key: columnKey,
selected: allIsSelected,
indeterminate: indeterminate,
onChange: (selected, e) => {
_this2.toggleSelectAllRow(selected, e);
}
});
const selectAll = (selected, e) => _this2.toggleSelectAllRow(selected, e);
return _isFunction(renderCell) ? renderCell({
selected: allIsSelected,
record,
originNode,
inHeader,
disabled,
indeterminate,
selectAll
}) : originNode;
} else {
const key = _this2.foundation.getRecordKey(record);
const selected = selectedRowKeysSet.has(key);
const checkboxPropsFn = () => typeof getCheckboxProps === 'function' ? getCheckboxProps(record) : {};
const originNode = /*#__PURE__*/React.createElement(ColumnSelection, {
"aria-label": `${selected ? 'Deselect' : 'Select'} this row`,
getCheckboxProps: checkboxPropsFn,
selected: selected,
onChange: (status, e) => _this2.toggleSelectRow(status, key, e)
});
const selectRow = (selected, e) => _this2.toggleSelectRow(selected, key, e);
return _isFunction(renderCell) ? renderCell({
selected,
record,
index,
originNode,
inHeader: false,
disabled,
indeterminate,
selectRow
}) : originNode;
}
}
return null;
};
this.renderRowSelectionCallback = function (text) {
let record = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
let index = arguments.length > 2 ? arguments[2] : undefined;
return _this2.renderSelection(record, false, index);
};
this.renderTitleSelectionCallback = () => this.renderSelection(undefined, true);
this.normalizeSelectionColumn = function () {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
rowSelection,
prefixCls
} = props;
let column = {};
if (rowSelection) {
const needOmitSelectionKey = ['selectedRowKeys', 'selectedRowKeysSet'];
column = {
key: strings.DEFAULT_KEY_COLUMN_SELECTION
};
if (_isObject(rowSelection)) {
column = Object.assign(Object.assign({}, column), _omit(rowSelection, needOmitSelectionKey));
}
column.className = classnames(column.className, `${prefixCls}-column-selection`);
column.title = _this2.renderTitleSelectionCallback;
column.render = _this2.renderRowSelectionCallback;
}
return column;
};
// If there is a scroll bar, manually construct a column and insert it into the header
this.normalizeScrollbarColumn = function () {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
scrollbarWidth = 0
} = props;
return {
key: strings.DEFAULT_KEY_COLUMN_SCROLLBAR,
width: scrollbarWidth,
fixed: 'right'
};
};
/**
* render expand icon
* @param {Object} record
* @param {Boolean} isNested
* @param {String} groupKey
* @returns {ReactNode}
*/
this.renderExpandIcon = function () {
let record = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let isNested = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
let groupKey = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
const {
expandedRowKeys
} = _this2.state;
const {
expandIcon
} = _this2.props;
const key = typeof groupKey === 'string' || typeof groupKey === 'number' ? groupKey : _this2.foundation.getRecordKey(record);
return /*#__PURE__*/React.createElement(ExpandedIcon, {
key: key,
componentType: isNested ? 'tree' : 'expand',
expanded: _includes(expandedRowKeys, key),
expandIcon: expandIcon,
onClick: (expanded, e) => _this2.handleRowExpanded(expanded, key, e)
});
};
// @ts-ignore
this.handleRowExpanded = function () {
return _this2.foundation.handleRowExpanded(...arguments);
};
this.normalizeExpandColumn = function () {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let column = null;
const {
prefixCls,
expandCellFixed,
expandIcon
} = props;
column = {
fixed: expandCellFixed,
key: strings.DEFAULT_KEY_COLUMN_EXPAND
};
column.className = classnames(column.className, `${prefixCls}-column-expand`);
column.render = expandIcon !== false ? function () {
let text = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '';
let record = arguments.length > 1 ? arguments[1] : undefined;
let index = arguments.length > 2 ? arguments[2] : undefined;
return _this2.adapter.mergedRowExpandable(record) ? _this2.renderExpandIcon(record) : null;
} : () => null;
return column;
};
/**
* Add sorting, filtering, and rendering functions to columns, and add column event handling
* Title support function, passing parameters as {filter: node, sorter: node, selection: node}
* @param {*} column
*/
this.addFnsInColumn = function () {
let column = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
prefixCls
} = _this2.props;
if (column && (column.sorter || column.filters || column.onFilter || column.useFullRender)) {
let hasSorter = typeof column.sorter === 'function' || column.sorter === true;
let hasFilter = Array.isArray(column.filters) && column.filters.length || /*#__PURE__*/isValidElement(column.filterDropdown) || typeof column.renderFilterDropdown === 'function';
let hasSorterOrFilter = false;
const sortOrderNotControlled = !('sortOrder' in column);
const showSortTip = sortOrderNotControlled && column.showSortTip === true;
const {
dataIndex,
title: rawTitle,
useFullRender
} = column;
const clickColumnToSorter = hasSorter && !hasFilter && !Boolean(useFullRender);
const curQuery = _this2.foundation.getQuery(dataIndex);
const titleMap = {};
const titleArr = [];
// useFullRender adds select buttons to each column
if (useFullRender) {
titleMap.selection = _this2.renderSelection(null, true);
}
const stateSortOrder = _get(curQuery, 'sortOrder');
const defaultSortOrder = _get(curQuery, 'defaultSortOrder', false);
const sortOrder = _this2.foundation.isSortOrderValid(stateSortOrder) ? stateSortOrder : defaultSortOrder;
const showEllipsisTitle = shouldShowEllipsisTitle(column.ellipsis);
const TitleNode = typeof rawTitle !== 'function' && (/*#__PURE__*/React.createElement("span", {
className: `${prefixCls}-row-head-title`,
key: strings.DEFAULT_KEY_COLUMN_TITLE,
title: showEllipsisTitle && typeof rawTitle === 'string' ? rawTitle : undefined
}, rawTitle));
if (hasSorter) {
// In order to increase the click hot area of sorting, when sorting is required & useFullRender is false,
// both the title and sorting areas are used as the click hot area for sorting。
const sorter = /*#__PURE__*/React.createElement(ColumnSorter, {
key: strings.DEFAULT_KEY_COLUMN_SORTER,
sortOrder: sortOrder,
sortIcon: column.sortIcon,
onClick: useFullRender || hasFilter ? e => _this2.foundation.handleSort(column, e) : null,
title: TitleNode,
showTooltip: !clickColumnToSorter && showSortTip
});
useFullRender && (titleMap.sorter = sorter);
hasSorterOrFilter = true;
titleArr.push(sorter);
} else {
titleArr.push(TitleNode);
}
const stateFilteredValue = _get(curQuery, 'filteredValue');
const defaultFilteredValue = _get(curQuery, 'defaultFilteredValue');
const filteredValue = stateFilteredValue ? stateFilteredValue : defaultFilteredValue;
if (hasFilter) {
const filter = /*#__PURE__*/React.createElement(ColumnFilter, Object.assign({
key: strings.DEFAULT_KEY_COLUMN_FILTER
}, _omit(curQuery, 'children'), {
filteredValue: filteredValue,
onFilterDropdownVisibleChange: visible => _this2.foundation.toggleShowFilter(dataIndex, visible),
onSelect: data => _this2.foundation.handleFilterSelect(dataIndex, data)
}));
useFullRender && (titleMap.filter = filter);
hasSorterOrFilter = true;
titleArr.push(filter);
}
const newTitle = typeof rawTitle === 'function' ? () => rawTitle(titleMap) : hasSorterOrFilter ? (/*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-operate-wrapper`
}, titleArr)) : titleArr;
column = Object.assign(Object.assign({}, column), {
title: newTitle
});
if (clickColumnToSorter) {
column.clickToSort = e => {
_this2.foundation.handleSort(column, e, true);
};
column.mouseDown = _this2.foundation.handleMouseDown;
column.sortOrder = sortOrder;
column.showSortTip = showSortTip;
}
}
return column;
};
this.toggleSelectRow = (selected, realKey, e) => {
this.foundation.handleSelectRow(realKey, selected, e);
};
this.toggleSelectAllRow = (selected, e) => {
this.foundation.handleSelectAllRow(selected, e);
};
/**
* render pagination
* @param {object} pagination
* @param {object} propRenderPagination
*/
this.renderPagination = (pagination, propRenderPagination) => {
if (!pagination) {
return null;
}
// use memoized pagination
const mergedPagination = this.foundation.memoizedPagination(pagination);
return /*#__PURE__*/React.createElement(LocaleConsumer, {
componentName: "Table"
}, locale => {
const info = this.foundation.formatPaginationInfo(mergedPagination, locale.pageText);
return /*#__PURE__*/React.createElement(TablePagination, {
info: info,
pagination: mergedPagination,
renderPagination: propRenderPagination
});
});
};
this.renderTitle = function () {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let {
title
} = props;
const {
prefixCls,
dataSource
} = props;
if (typeof title === 'function') {
title = title(dataSource);
}
return /*#__PURE__*/isValidElement(title) || typeof title === 'string' ? (/*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-title`,
"x-semi-prop": "title"
}, title)) : null;
};
this.renderEmpty = function () {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
const {
prefixCls,
empty,
dataSource
} = props;
const wrapCls = `${prefixCls}-placeholder`;
const isEmpty = _this2.foundation.isEmpty(dataSource);
if (!isEmpty) {
return null;
}
return /*#__PURE__*/React.createElement(LocaleConsumer, {
componentName: "Table",
key: 'emptyText'
}, (locale, localeCode) => (/*#__PURE__*/React.createElement("div", {
className: wrapCls
}, /*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-empty`,
"x-semi-prop": "empty"
}, empty || locale.emptyText))));
};
this.renderFooter = function () {
let props = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
let {
footer
} = props;
const {
prefixCls,
dataSource
} = props;
if (typeof footer === 'function') {
footer = footer(dataSource);
}
return /*#__PURE__*/isValidElement(footer) || typeof footer === 'string' ? (/*#__PURE__*/React.createElement("div", {
className: `${prefixCls}-footer`,
key: "footer",
"x-semi-prop": "footer"
}, footer)) : null;
};
this.renderMainTable = props => {
const useFixedHeader = this.adapter.useFixedHeader();
const emptySlot = this.renderEmpty(props);
const table = [this.renderTable(Object.assign(Object.assign({}, props), {
fixed: false,
useFixedHeader,
headerRef: this._cacheHeaderRef,
bodyRef: this.bodyWrapRef,
includeHeader: !useFixedHeader,
emptySlot
})), this.renderFooter(props)];
return table;
};
this.renderTable = props => {
const {
columns,
filteredColumns,
fixed,
useFixedHeader,
scroll,
prefixCls,
anyColumnFixed,
includeHeader,
showHeader,
components,
headerRef,
bodyRef,
onHeaderRow,
rowSelection,
dataSource,
bodyHasScrollBar,
disabledRowKeysSet,
sticky
} = props;
const selectedRowKeysSet = _get(rowSelection, 'selectedRowKeysSet', new Set());
const tableLayout = this.adapter.getTableLayout();
const headTable = fixed || useFixedHeader ? (/*#__PURE__*/React.createElement(HeadTable, {
key: "head",
tableLayout: tableLayout,
ref: headerRef,
columns: filteredColumns,
prefixCls: prefixCls,
fixed: fixed,
handleBodyScroll: this.handleBodyScrollLeft,
components: components,
scroll: scroll,
showHeader: showHeader,
selectedRowKeysSet: selectedRowKeysSet,
onHeaderRow: onHeaderRow,
dataSource: dataSource,
bodyHasScrollBar: bodyHasScrollBar,
sticky: sticky
})) : null;
const bodyTable = /*#__PURE__*/React.createElement(BodyTable, Object.assign({}, _omit(props, ['rowSelection', 'headWidths']), {
key: "body",
ref: bodyRef,
columns: filteredColumns,
fixed: fixed,
prefixCls: prefixCls,
handleWheel: this.handleWheel,
handleBodyScroll: this.handleBodyScroll,
anyColumnFixed: anyColumnFixed,
tableLayout: tableLayout,
includeHeader: includeHeader,
showHeader: showHeader,
scroll: scroll,
components: components,
store: this.store,
selectedRowKeysSet: selectedRowKeysSet,
disabledRowKeysSet: disabledRowKeysSet
}));
return [headTable, bodyTable];
};
/**
* When columns change, call this function to get the latest withFnsColumns
* In addition to changes in columns, these props changes must be recalculated
* - hideExpandedColumn
* -rowSelection changes from trusy to falsy or rowSelection.hidden changes
* -isAnyFixedRight(columns) || get(scroll,'y') changes
*
* columns变化时,调用此函数获取最新的withFnsColumns
* 除了 columns 变化,这些 props 变化也要重新计算
* - hideExpandedColumn
* - rowSelection 从 trusy 变为 falsy 或 rowSelection.hidden 发生变化
* - isAnyFixedRight(columns) || get(scroll, 'y') 发生变化
*
* @param {Array} queries
* @param {Array} cachedColumns
* @returns columns after adding extended functions
*/
this.handleColumns = (queries, cachedColumns) => {
const {
hideExpandedColumn,
scroll,
prefixCls,
expandCellFixed,
expandIcon,
rowSelection
} = this.props;
const childrenColumnName = 'children';
let columns = cloneDeep(cachedColumns);
const addFns = function () {
let columns = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
if (Array.isArray(columns) && columns.length) {
_each(columns, (column, index, originColumns) => {
const newColumn = _this2.addFnsInColumn(column);
const children = column[childrenColumnName];
if (Array.isArray(children) && children.length) {
const newChildren = [...children];
addFns(newChildren);
newColumn[childrenColumnName] = newChildren;
}
originColumns[index] = newColumn;
});
}
};
addFns(columns);
// hideExpandedColumn=false render expand column separately
if (!hideExpandedColumn) {
const column = this.normalizeExpandColumn({
prefixCls,
expandCellFixed,
expandIcon
});
const destIndex = _findIndex(columns, item => item.key === strings.DEFAULT_KEY_COLUMN_EXPAND);
if (column) {
if (destIndex > -1) {
columns[destIndex] = Object.assign(Object.assign({}, column), columns[destIndex]);
} else if (column.fixed === 'right') {
columns = [...columns, column];
} else {
columns = [column, ...columns];
}
}
}
// selection column
if (rowSelection && !_get(rowSelection, 'hidden')) {
const destIndex = _findIndex(columns, item => item.key === strings.DEFAULT_KEY_COLUMN_SELECTION);
const column = this.normalizeSelectionColumn({
rowSelection,
prefixCls
});
if (destIndex > -1) {
columns[destIndex] = Object.assign(Object.assign({}, column), columns[destIndex]);
} else if (column.fixed === 'right') {
columns = [...columns, column];
} else {
columns = [column, ...columns];
}
}
assignColumnKeys(columns);
return columns;
};
/**
* Convert children to columns object
* @param {Array} columns
* @param {ReactNode} children
* @returns {Array}
*/
this.normalizeColumns = (columns, children) => {
const normalColumns = cloneDeep(this.getColumns(columns, children));
return normalColumns;
};
/**
* Combine pagination and table paging processing functions
*/
this.mergePagination = pagination => {
const newPagination = Object.assign({
onChange: this.foundation.setPage
}, pagination);
return newPagination;
};
this.foundation = new TableFoundation(this.adapter);
// columns cannot be deepClone, otherwise the comparison will be false
const columns = this.getColumns(props.columns, props.children);
const cachedflattenColumns = flattenColumns(columns);
const queries = TableFoundation.initColumnsFilteredValueAndSorterOrder(cloneDeep(cachedflattenColumns));
const filteredSortedDataSource = this.foundation.getFilteredSortedDataSource(this.props.dataSource, queries);
const newPagination = _isPlainObject(this.props.pagination) ? this.props.pagination : {};
const pageData = this.foundation.getCurrentPageData(filteredSortedDataSource, newPagination, queries);
this.state = {
/**
* Cached props
*/
cachedColumns: columns,
cachedChildren: props.children,
flattenColumns: cachedflattenColumns,
components: mergeComponents(props.components, props.virtualized),
/**
* State calculated based on prop
*/
queries,
dataSource: pageData.dataSource,
flattenData: [],
expandedRowKeys: [...(props.expandedRowKeys || []), ...(props.defaultExpandedRowKeys || [])],
rowSelection: props.rowSelection ? _isObject(props.rowSelection) ? Object.assign({}, props.rowSelection) : {} : null,
pagination: pageData.pagination,
/**
* Internal state
*/
groups: null,
allRowKeys: [],
disabledRowKeys: [],
disabledRowKeysSet: new Set(),
allDisabledRowKeys: [],
allDisabledRowKeysSet: new Set(),
headWidths: [],
bodyHasScrollBar: false,
prePropRowSelection: undefined,
prePagination: undefined
};
this.rootWrapRef = /*#__PURE__*/createRef();
this.wrapRef = /*#__PURE__*/createRef(); // table's outside wrap
this.bodyWrapRef = /*#__PURE__*/createRef();
this.headerWrapRef = /*#__PURE__*/createRef();
this.store = new Store({
hoveredRowKey: null
});
this.debouncedWindowResize = _debounce(this.handleWindowResize, 150);
this.cachedFilteredSortedDataSource = [];
this.cachedFilteredSortedRowKeys = [];
this.cachedFilteredSortedRowKeysSet = new Set();
}
static getDerivedStateFromProps(props, state) {
const willUpdateStates = {};
const {
rowSelection,
dataSource,
childrenRecordName,
rowKey,
pagination
} = props;
props.columns && props.children && logger.warn('columns should not given by object and children at the same time');
if (props.columns && props.columns !== state.cachedColumns) {
const newFlattenColumns = flattenColumns(props.columns);
willUpdateStates.flattenColumns = newFlattenColumns;
willUpdateStates.queries = mergeColumns(state.queries, newFlattenColumns, null, false);
willUpdateStates.cachedColumns = props.columns;
willUpdateStates.cachedChildren = null;
} else if (props.children && props.children !== state.cachedChildren) {
const newNestedColumns = getColumns(props.children);
const newFlattenColumns = flattenColumns(newNestedColumns);
const columns = mergeColumns(state.queries, newFlattenColumns, null, false);
willUpdateStates.flattenColumns = newFlattenColumns;
willUpdateStates.queries = [...columns];
willUpdateStates.cachedColumns = [...newNestedColumns];
willUpdateStates.cachedChildren = props.children;
}
// Update controlled selection column
if (rowSelection !== state.prePropRowSelection) {
let newSelectionStates = {};
if (_isObject(state.rowSelection)) {
newSelectionStates = Object.assign(Object.assign({}, newSelectionStates), state.rowSelection);
}
if (_isObject(rowSelection)) {
newSelectionStates = Object.assign(Object.assign({}, newSelectionStates), rowSelection);
}
const selectedRowKeys = _get(rowSelection, 'selectedRowKeys');
const getCheckboxProps = _get(rowSelection, 'getCheckboxProps');
if (selectedRowKeys && Array.isArray(selectedRowKeys)) {
newSelectionStates.selectedRowKeysSet = new Set(selectedRowKeys);
}
// The return value of getCheckboxProps affects the disabled rows
if (_isFunction(getCheckboxProps)) {
const disabledRowKeys = getAllDisabledRowKeys({
dataSource,
getCheckboxProps,
childrenRecordName,
rowKey
});
const disabledRowKeysSet = new Set(disabledRowKeys);
willUpdateStates.disabledRowKeys = disabledRowKeys;
willUpdateStates.disabledRowKeysSet = disabledRowKeysSet;
willUpdateStates.allDisabledRowKeys = disabledRowKeys;
willUpdateStates.allDisabledRowKeysSet = disabledRowKeysSet;
}
willUpdateStates.rowSelection = newSelectionStates;
willUpdateStates.prePropRowSelection = rowSelection;
}
if (pagination !== state.prePagination) {
let newPagination = {};
if (_isObject(state.pagination)) {
newPagination = Object.assign(Object.assign({}, newPagination), state.pagination);
}
if (_isObject(pagination)) {
newPagination = Object.assign(Object.assign({}, newPagination), pagination);
}
willUpdateStates.pagination = newPagination;
willUpdateStates.prePagination = pagination;
}
return willUpdateStates;
}
componentDidMount() {
super.componentDidMount();
this.setScrollPosition('left');
if (this.adapter.isAnyColumnFixed() || this.props.showHeader && this.adapter.useFixedHeader()) {
this.handleWindowResize();
window.addEventListener('resize', this.debouncedWindowResize);
}
}
// TODO: Extract the setState operation to the adapter or getDerivedStateFromProps function
componentDidUpdate(prevProps, prevState) {
const {
dataSource,
expandedRowKeys,
expandAllRows,
expandAllGroupRows,
virtualized,
components,
pagination: propsPagination
} = this.props;
const {
pagination: statePagination,
queries: stateQueries,
cachedColumns: stateCachedColumns,
cachedChildren: stateCachedChildren,
groups: stateGroups
} = this.state;
/**
* State related to paging
*
* @param dataSource
* @param groups
* @param pagination
* @param disabledRowKeys
* @param allRowKeys
* @param queries
*/
const states = {};
this._warnIfNoKey();
/**
* The state that needs to be updated after props changes
*/
// Update controlled expand column
if (Array.isArray(expandedRowKeys) && expandedRowKeys !== prevProps.expandedRowKeys) {
this.setState({
expandedRowKeys
});
}
// Update components
if (components !== prevProps.components || virtualized !== prevProps.virtualized) {
this.setState({
components: mergeComponents(components, virtualized)
});
}
// Update the default expanded column
if (expandAllRows !== prevProps.expandAllRows || expandAllGroupRows !== prevProps.expandAllGroupRows) {
this.foundation.initExpandedRowKeys({
groups: stateGroups
});
}
/**
* After dataSource is updated || (cachedColumns || cachedChildren updated)
* 1. Cache filtered sorted data and a collection of data rows, stored in this
* 2. Update pager and group, stored in state
*/
if (dataSource !== prevProps.dataSource || stateCachedColumns !== prevState.cachedColumns || stateCachedChildren !== prevState.cachedChildren) {
// TODO: foundation.getFilteredSortedDataSource has side effects and will be modified to the dataSource reference
// Temporarily use _dataSource=[...dataSource] for processing
const _dataSource = [...dataSource];
const filteredSortedDataSource = this.foundation.getFilteredSortedDataSource(_dataSource, stateQueries);
const allDataDisabledRowKeys = this.foundation.getAllDisabledRowKeys(filteredSortedDataSource);
this.foundation.setCachedFilteredSortedDataSource(filteredSortedDataSource);
this.foundation.setAllDisabledRowKeys(allDataDisabledRowKeys);
states.dataSource = filteredSortedDataSource;
if (this.props.groupBy) {
states.groups = null;
}
}
// when dataSource has change, should reset currentPage
if (dataSource !== prevProps.dataSource) {
states.pagination = _isObject(statePagination) ? Object.assign(Object.assign({}, statePagination), {
currentPage: _isObject(propsPagination) && propsPagination.currentPage ? propsPagination.currentPage : 1
}) : statePagination;
}
if (Object.keys(states).length) {
const {
pagination: mergedStatePagination = null,
queries: stateQueries = null,
dataSource: stateDataSource = null
} = states;
const handledProps = this.foundation.getCurrentPageData(stateDataSource, mergedStatePagination, stateQueries);
// After the pager is updated, reset allRowKeys of the current page
this.adapter.setAllRowKeys(handledProps.allRowKeys);
this.adapter.setDisabledRowKeys(handledProps.disabledRowKeys);
if ('dataSource' in states) {
if (this.props.defaultExpandAllRows && handledProps.groups && handledProps.groups.size || this.props.expandAllRows || this.props.expandAllGroupRows) {
this.foundation.initExpandedRowKeys(handledProps);
}
states.pagination = handledProps.pagination;
}
// Centrally update paging related state
const statesKeys = Object.keys(states);
for (const k of statesKeys) {
this.setState({
[k]: handledProps[k]
});
}
}
if (this.adapter.isAnyColumnFixed() || this.props.showHeader && this.adapter.useFixedHeader()) {
if (!this.debouncedWindowResize) {
window.addEventListener('resize', this.debouncedWindowResize);
}
}
}
componentWillUnmount() {
super.componentWillUnmount();
if (this.debouncedWindowResize) {
window.removeEventListener('resize', this.debouncedWindowResize);
this.debouncedWindowResize.cancel();
this.debouncedWindowResize = null;
}
}
render() {
let _a = this.props,
{
scroll,
prefixCls,
className,
style: wrapStyle = {},
bordered,
id,
pagination: propPagination,
virtualized,
size,
renderPagination: propRenderPagination,
getVirtualizedListRef,
loading,
hideExpandedColumn,
rowSelection: propRowSelection
} = _a,
rest = __rest(_a, ["scroll", "prefixCls", "className", "style", "bordered", "id", "pagination", "virtualized", "size", "renderPagination", "getVirtualizedListRef", "loading", "hideExpandedColumn", "rowSelection"]);
let {
rowSelection,
expandedRowKeys,
headWidths,
tableWidth,
pagination,
dataSource,
queries,
cachedColumns,
bodyHasScrollBar
} = this.state;
wrapStyle = Object.assign({}, wrapStyle);
let columns;
/**
* As state.queries will change, the columns should be refreshed as a whole at this time
* The scene of changes in queries
* 1. Filter
* 2. Pagination
*
* useFullRender needs to be passed to the user selection ReactNode, so columns need to be recalculated every time the selectedRowKeys changes
* TODO: In the future, the selecti