@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
JavaScript
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
};