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.

362 lines 11.8 kB
import _isEqual from "lodash/isEqual"; import _merge from "lodash/merge"; import _omit from "lodash/omit"; import _set from "lodash/set"; import _noop from "lodash/noop"; import _get from "lodash/get"; import React, { createRef, Fragment } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import { cssClasses, numbers } from '@douyinfe/semi-foundation/lib/es/table/constants'; import TableCellFoundation from '@douyinfe/semi-foundation/lib/es/table/cellFoundation'; import { isSelectionColumn, isExpandedColumn, getRTLAlign, shouldShowEllipsisTitle, getRTLFlexAlign } from '@douyinfe/semi-foundation/lib/es/table/utils'; import BaseComponent from '../_base/baseComponent'; import Context from './table-context'; import { amendTableWidth } from './utils'; function isInvalidRenderCellText(text) { return text && ! /*#__PURE__*/React.isValidElement(text) && Object.prototype.toString.call(text) === '[object Object]'; } export default class TableCell extends BaseComponent { get adapter() { var _this = this; return Object.assign(Object.assign({}, super.adapter), { notifyClick: function () { const { onClick } = _this.props; if (typeof onClick === 'function') { onClick(...arguments); } } }); } constructor(props) { super(props); this.setRef = ref => this.ref = ref; this.handleClick = e => { this.foundation.handleClick(e); const customCellProps = this.adapter.getCache('customCellProps'); if (customCellProps && typeof customCellProps.onClick === 'function') { customCellProps.onClick(e); } }; this.ref = /*#__PURE__*/createRef(); this.foundation = new TableCellFoundation(this.adapter); } /** * Control whether to execute the render function of the cell * 1. Scenes that return true * - The cell contains the selection state, you need to calculate whether its selection state has changed during selection * - The cell contains the folding state, it needs to be calculated when the folding state has changed * 2. Scenarios that return false * - Cells without table operation operation status, only need to judge that their props have changed * At this time, the update of the table cell is controlled by the user. At this time, its update will not affect other cells * * 控制是否执行cell的render函数 * 1. 返回true的场景 * - cell内包含选择状态,需要在选择时计算它的选择态是否发生变化 * - cell内包含折叠状态,需要在折叠时计算它的折叠态是否发生了变化 * 2. 返回false的场景 * - 没有table操作操作状态的cell,只需判断自己的props发生了变化 * 此时table cell的更新由用户自己控制,此时它的更新不会影响其他cell * * @param {*} nextProps * @returns */ shouldComponentUpdate(nextProps) { const props = this.props; const { column, expandIcon } = props; const cellInSelectionColumn = isSelectionColumn(column); const { shouldCellUpdate } = column; if (typeof shouldCellUpdate === 'function') { return shouldCellUpdate(nextProps, props); } // The expand button may be in a separate column or in the first data column const columnHasExpandIcon = isExpandedColumn(column) || expandIcon; if ((cellInSelectionColumn || columnHasExpandIcon) && !_isEqual(nextProps, this.props)) { return true; } else { const omitProps = ['selected', 'expanded', 'expandIcon', 'disabled']; const propsOmitSelected = _omit(props, omitProps); const nextPropsOmitSelected = _omit(nextProps, omitProps); if (!_isEqual(nextPropsOmitSelected, propsOmitSelected)) { return true; } } return false; } componentDidUpdate() { this.props.onDidUpdate(this.ref); } getTdProps() { const { record, index, column = {}, fixedLeft, fixedRight, width, height } = this.props; let tdProps = {}; let customCellProps = {}; const { direction } = this.context; const isRTL = direction === 'rtl'; const fixedLeftFlag = fixedLeft || typeof fixedLeft === 'number'; const fixedRightFlag = fixedRight || typeof fixedRight === 'number'; if (fixedLeftFlag) { _set(tdProps, isRTL ? 'style.right' : 'style.left', typeof fixedLeft === 'number' ? fixedLeft : 0); } else if (fixedRightFlag) { _set(tdProps, isRTL ? 'style.left' : 'style.right', typeof fixedRight === 'number' ? fixedRight : 0); } if (width != null) { _set(tdProps, 'style.width', width); } if (height != null) { _set(tdProps, 'style.height', height); } if (column.onCell) { customCellProps = column.onCell(record, index); this.adapter.setCache('customCellProps', Object.assign({}, customCellProps)); tdProps = Object.assign(Object.assign({}, tdProps), _omit(customCellProps, ['style', 'className', 'onClick'])); const customCellStyle = _get(customCellProps, 'style') || {}; tdProps.style = Object.assign(Object.assign({}, tdProps.style), customCellStyle); } if (column.align) { const textAlign = getRTLAlign(column.align, direction); const justifyContent = getRTLFlexAlign(column.align, direction); tdProps.style = Object.assign(Object.assign({}, tdProps.style), { textAlign, justifyContent }); } return { tdProps, customCellProps }; } /** * We should return undefined if no dataIndex is specified, but in order to * be compatible with object-path's behavior, we return the record object instead. */ renderText(tdProps) { const { record, indentSize, prefixCls, indent, index, expandIcon, renderExpandIcon, column = {} } = this.props; const { dataIndex, render, useFullRender } = column; let text, colSpan, rowSpan; if (typeof dataIndex === 'number') { text = _get(record, dataIndex); } else if (!dataIndex || dataIndex.length === 0) { text = record; } else { text = _get(record, dataIndex); } const indentText = indent && indentSize ? (/*#__PURE__*/React.createElement("span", { style: { paddingLeft: `${indentSize * indent}px` }, className: `${prefixCls}-row-indent indent-level-${indent}` })) : null; // column.render const realExpandIcon = typeof renderExpandIcon === 'function' ? renderExpandIcon(record) : expandIcon; if (render) { const renderOptions = { expandIcon: realExpandIcon }; // column.useFullRender if (useFullRender) { const { renderSelection } = this.context; const realSelection = typeof renderSelection === 'function' ? renderSelection(record) : null; Object.assign(renderOptions, { selection: realSelection, indentText }); } text = render(text, record, index, renderOptions); if (isInvalidRenderCellText(text)) { tdProps = text.props ? _merge(tdProps, text.props) : tdProps; colSpan = tdProps.colSpan; rowSpan = tdProps.rowSpan; text = text.children; } } return { text, indentText, rowSpan, colSpan, realExpandIcon, tdProps }; } renderInner(text, indentText, realExpandIcon) { const { prefixCls, isSection, expandIcon, column = {} } = this.props; const { tableWidth, anyColumnFixed } = this.context; const { useFullRender } = column; let inner = null; if (useFullRender) { inner = text; } else { inner = [/*#__PURE__*/React.createElement(Fragment, { key: 'indentText' }, indentText), /*#__PURE__*/React.createElement(Fragment, { key: 'expandIcon' }, expandIcon ? realExpandIcon : null), /*#__PURE__*/React.createElement(Fragment, { key: 'text' }, text)]; } if (isSection) { inner = /*#__PURE__*/React.createElement("div", { className: classnames(`${prefixCls}-section-inner`), style: { width: anyColumnFixed ? amendTableWidth(tableWidth) : undefined } }, inner); } return inner; } render() { const { prefixCls, column = {}, component: BodyCell, fixedLeft, fixedRight, lastFixedLeft, firstFixedRight, colIndex } = this.props; const { direction } = this.context; const isRTL = direction === 'rtl'; const { className, ellipsis } = column; const fixedLeftFlag = fixedLeft || typeof fixedLeft === 'number'; const fixedRightFlag = fixedRight || typeof fixedRight === 'number'; const { tdProps, customCellProps } = this.getTdProps(); const renderTextResult = this.renderText(tdProps); let { text } = renderTextResult; const { indentText, rowSpan, colSpan, realExpandIcon, tdProps: newTdProps } = renderTextResult; let title; const shouldShowTitle = shouldShowEllipsisTitle(ellipsis); if (shouldShowTitle) { if (typeof text === 'string') { title = text; } } if (rowSpan === 0 || colSpan === 0) { return null; } if (isInvalidRenderCellText(text)) { text = null; } const inner = this.renderInner(text, indentText, realExpandIcon); let isFixedLeft, isFixedLeftLast, isFixedRight, isFixedRightFirst; if (isRTL) { isFixedLeft = fixedRightFlag; isFixedLeftLast = firstFixedRight; isFixedRight = fixedLeftFlag; isFixedRightFirst = lastFixedLeft; } else { isFixedLeft = fixedLeftFlag; isFixedLeftLast = lastFixedLeft; isFixedRight = fixedRightFlag; isFixedRightFirst = firstFixedRight; } const columnCls = classnames(className, `${prefixCls}-row-cell`, _get(customCellProps, 'className'), { [`${prefixCls}-cell-fixed-left`]: isFixedLeft, [`${prefixCls}-cell-fixed-left-last`]: isFixedLeftLast, [`${prefixCls}-cell-fixed-right`]: isFixedRight, [`${prefixCls}-cell-fixed-right-first`]: isFixedRightFirst, [`${prefixCls}-row-cell-ellipsis`]: ellipsis }); return /*#__PURE__*/React.createElement(BodyCell, Object.assign({ role: "gridcell", "aria-colindex": colIndex + 1, className: columnCls, onClick: this.handleClick, title: title }, newTdProps, { ref: this.setRef }), inner); } } TableCell.contextType = Context; TableCell.defaultProps = { indent: 0, indentSize: numbers.DEFAULT_INDENT_WIDTH, onClick: _noop, prefixCls: cssClasses.PREFIX, component: 'td', onDidUpdate: _noop, column: {} }; TableCell.propTypes = { record: PropTypes.object, prefixCls: PropTypes.string, index: PropTypes.number, fixedLeft: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), lastFixedLeft: PropTypes.bool, fixedRight: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]), firstFixedRight: PropTypes.bool, indent: PropTypes.number, indentSize: PropTypes.number, column: PropTypes.object, expandIcon: PropTypes.any, renderExpandIcon: PropTypes.func, hideExpandedColumn: PropTypes.bool, component: PropTypes.any, onClick: PropTypes.func, onDidUpdate: PropTypes.func, isSection: PropTypes.bool, width: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), height: PropTypes.oneOfType([PropTypes.string, PropTypes.number]), selected: PropTypes.bool, expanded: PropTypes.bool, colIndex: PropTypes.number };