UNPKG

@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
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