/* eslint-disable prefer-destructuring */ /* eslint-disable eqeqeq */ import React, { createRef, Fragment, ReactNode } from 'react'; import classnames from 'classnames'; import PropTypes from 'prop-types'; import { get, noop, set, omit, isEqual, merge } from 'lodash'; import { cssClasses, numbers } from '@douyinfe/semi-foundation/table/constants'; import TableCellFoundation, { TableCellAdapter } from '@douyinfe/semi-foundation/table/cellFoundation'; import { isSelectionColumn, isExpandedColumn } from '@douyinfe/semi-foundation/table/utils'; import BaseComponent, { BaseProps } from '../_base/baseComponent'; import Context from './table-context'; import { amendTableWidth } from './utils'; import { Align, ColumnProps } from './interface'; export interface TableCellProps extends BaseProps { record?: Record; prefixCls?: string; index?: number; // index of dataSource fixedLeft?: boolean | number; lastFixedLeft?: boolean; fixedRight?: boolean | number; firstFixedRight?: boolean; indent?: number; // The level of the tree structure indentSize?: number; // Tree structure indent size column?: ColumnProps; // The column of the current cell /** * Does the first column include expandIcon * When hideExpandedColumn is true or isSection is true * expandIcon is a custom icon or true */ expandIcon?: ReactNode | boolean; renderExpandIcon?: (record: Record) => ReactNode; hideExpandedColumn?: boolean; component?: any; onClick?: (record: Record, e: React.MouseEvent) => void; // callback of click cell event onDidUpdate?: (ref: React.MutableRefObject) => void; isSection?: boolean; // Whether it is in group row width?: string | number; // cell width height?: string | number; // cell height selected?: boolean; // Whether the current row is selected expanded?: boolean; // Whether the current line is expanded disabled?: boolean; colIndex?: number; } function isInvalidRenderCellText(text: any) { return text && !React.isValidElement(text) && Object.prototype.toString.call(text) === '[object Object]'; } export default class TableCell extends BaseComponent> { static contextType = Context; static defaultProps = { indent: 0, indentSize: numbers.DEFAULT_INDENT_WIDTH, onClick: noop, prefixCls: cssClasses.PREFIX, component: 'td', onDidUpdate: noop, column: {}, }; static 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, }; get adapter(): TableCellAdapter { return { ...super.adapter, notifyClick: (...args) => { const { onClick } = this.props; if (typeof onClick === 'function') { onClick(...args); } }, }; } ref: React.MutableRefObject; constructor(props: TableCellProps) { super(props); this.ref = 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: TableCellProps) { const props = this.props; const { column, expandIcon } = props; const cellInSelectionColumn = isSelectionColumn(column); // 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); } setRef = (ref: React.MutableRefObject) => (this.ref = ref); handleClick = (e: React.MouseEvent) => { this.foundation.handleClick(e); const customCellProps = this.adapter.getCache('customCellProps'); if (customCellProps && typeof customCellProps.onClick === 'function') { customCellProps.onClick(e); } }; getTdProps() { const { record, index, column = {}, fixedLeft, fixedRight, width, height, } = this.props; let tdProps: { style?: Partial } = {}; let customCellProps = {}; const fixedLeftFlag = fixedLeft || typeof fixedLeft === 'number'; const fixedRightFlag = fixedRight || typeof fixedRight === 'number'; if (fixedLeftFlag) { set(tdProps, 'style.left', typeof fixedLeft === 'number' ? fixedLeft : 0); } else if (fixedRightFlag) { set(tdProps, '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 as any).onCell(record, index); this.adapter.setCache('customCellProps', { ...customCellProps }); tdProps = { ...tdProps, ...omit(customCellProps, ['style', 'className', 'onClick']) }; const customCellStyle = get(customCellProps, 'style') || {}; tdProps.style = { ...tdProps.style, ...customCellStyle }; } if (column.align) { tdProps.style = { ...tdProps.style, textAlign: column.align as Align }; } 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: { style?: React.CSSProperties; colSpan?: number; rowSpan?: number }) { const { record, indentSize, prefixCls, indent, index, expandIcon, renderExpandIcon, column = {}, } = this.props; const { dataIndex, render, useFullRender } = column; let text: any, colSpan: number, rowSpan: number; 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) ? ( ) : 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)) { // eslint-disable-next-line no-param-reassign 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: ReactNode, indentText: ReactNode, realExpandIcon: ReactNode) { const { prefixCls, isSection, expandIcon, column = {}, } = this.props; const { tableWidth, anyColumnFixed } = this.context; const { useFullRender } = column; let inner = null; if (useFullRender) { inner = text; } else { inner = [ {indentText}, {expandIcon ? realExpandIcon : null}, {text}, ]; } if (isSection) { inner = (
{inner}
); } return inner; } render() { const { prefixCls, column = {}, component: BodyCell, fixedLeft, fixedRight, lastFixedLeft, firstFixedRight, colIndex } = this.props; const { className } = 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; if (rowSpan === 0 || colSpan === 0) { return null; } if (isInvalidRenderCellText(text)) { text = null; } const inner = this.renderInner(text, indentText, realExpandIcon); const columnCls = classnames( className, `${prefixCls}-row-cell`, get(customCellProps, 'className'), { [`${prefixCls}-cell-fixed-left`]: fixedLeftFlag, [`${prefixCls}-cell-fixed-left-last`]: lastFixedLeft, [`${prefixCls}-cell-fixed-right`]: fixedRightFlag, [`${prefixCls}-cell-fixed-right-first`]: firstFixedRight, } ); return ( {inner} ); } }