123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365 |
- /* 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<string, any>;
- 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<string, any>) => ReactNode;
- hideExpandedColumn?: boolean;
- component?: any;
- onClick?: (record: Record<string, any>, e: React.MouseEvent) => void; // callback of click cell event
- onDidUpdate?: (ref: React.MutableRefObject<any>) => 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<TableCellProps, Record<string, any>> {
- 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<any>;
- 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<any>) => (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<React.CSSProperties> } = {};
- 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) ? (
- <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)) {
- // 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 = [
- <Fragment key={'indentText'}>{indentText}</Fragment>,
- <Fragment key={'expandIcon'}>{expandIcon ? realExpandIcon : null}</Fragment>,
- <Fragment key={'text'}>{text}</Fragment>,
- ];
- }
- if (isSection) {
- inner = (
- <div
- className={classnames(`${prefixCls}-section-inner`)}
- style={{ width: anyColumnFixed ? amendTableWidth(tableWidth) : undefined }}
- >
- {inner}
- </div>
- );
- }
- 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 (
- <BodyCell
- role="gridcell"
- aria-colindex={colIndex + 1}
- className={columnCls}
- onClick={this.handleClick}
- {...newTdProps}
- ref={this.setRef}
- >
- {inner}
- </BodyCell>
- );
- }
- }
|