diff --git a/examples/dropdown.js b/examples/dropdown.js index 55255ac68..38c2f5e2c 100644 --- a/examples/dropdown.js +++ b/examples/dropdown.js @@ -18,35 +18,34 @@ for (let i = 0; i < 10; i++) { }); } -const Test = React.createClass({ - getInitialState() { - this.filters = []; - return { - visible: false, - }; - }, +class Demo extends React.Component { + state = { + visible: false, + } + + filters = [] - handleVisibleChange(visible) { + handleVisibleChange = (visible) => { this.setState({ visible }); - }, + } - handleSelect(selected) { + handleSelect = (selected) => { this.filters.push(selected); - }, + } - handleDeselect(key) { + handleDeselect = (key) => { const index = this.filters.indexOf(key); if (index !== -1) { this.filters.splice(index, 1); } - }, + } - confirmFilter() { + confirmFilter = () => { console.log(this.filters.join(',')); this.setState({ visible: false, }); - }, + } render() { const menu = ( @@ -100,13 +99,13 @@ const Test = React.createClass({ rowKey={record => record.key} /> ); - }, -}); + } +} ReactDOM.render(

use dropdown

- +
, document.getElementById('__react-content') ); diff --git a/examples/expandedRowRender.js b/examples/expandedRowRender.js index aafa30e2a..6e04bcc12 100644 --- a/examples/expandedRowRender.js +++ b/examples/expandedRowRender.js @@ -10,43 +10,42 @@ const tableData = [ { key: 2, a: '1333', c: 'eee', d: 2 }, ]; -const App = React.createClass({ - getInitialState() { - this.columns = [ - { title: 'title 1', dataIndex: 'a', key: 'a', width: 100 }, - { title: 'title 2', dataIndex: 'b', key: 'b', width: 100 }, - { title: 'title 3', dataIndex: 'c', key: 'c', width: 200 }, - { title: 'Operation', dataIndex: '', key: 'x', render: this.renderAction }, - ]; - return { - data: tableData, - expandedRowKeys: [], - expandIconAsCell: true, - expandRowByClick: false, - }; - }, +class Demo extends React.Component { + state = { + data: tableData, + expandedRowKeys: [], + expandIconAsCell: true, + expandRowByClick: false, + } - onExpand(expanded, record) { + onExpand = (expanded, record) => { console.log('onExpand', expanded, record); - }, + } - onExpandedRowsChange(rows) { + onExpandedRowsChange = (rows) => { this.setState({ expandedRowKeys: rows, }); - }, + } - onExpandIconAsCellChange(e) { + onExpandIconAsCellChange = (e) => { this.setState({ expandIconAsCell: e.target.checked, }); - }, + } - onExpandRowByClickChange(e) { + onExpandRowByClickChange = (e) => { this.setState({ expandRowByClick: e.target.checked, }); - }, + } + + columns = [ + { title: 'title 1', dataIndex: 'a', key: 'a', width: 100 }, + { title: 'title 2', dataIndex: 'b', key: 'b', width: 100 }, + { title: 'title 3', dataIndex: 'c', key: 'c', width: 200 }, + { title: 'Operation', dataIndex: '', key: 'x', render: this.renderAction }, + ] toggleButton() { if (this.state.expandedRowKeys.length) { @@ -55,22 +54,22 @@ const App = React.createClass({ } const openAll = () => this.setState({ expandedRowKeys: [0, 1, 2] }); return ; - }, + } remove(index) { const data = this.state.data; data.splice(index, 1); this.setState({ data }); - }, + } expandedRowRender(record) { - console.log(record); + // console.log(record); return

extra: {record.a}

; - }, + } renderAction(o, row, index) { return this.remove(index)}>Delete; - }, + } render() { const { expandIconAsCell, expandRowByClick, expandedRowKeys, data } = this.state; @@ -103,13 +102,13 @@ const App = React.createClass({ /> ); - }, -}); + } +} ReactDOM.render(

expandedRowRender

- +
, document.getElementById('__react-content') ); diff --git a/examples/fixedColumns-auto-height.js b/examples/fixedColumns-auto-height.js index 791e92d20..5a2973333 100644 --- a/examples/fixedColumns-auto-height.js +++ b/examples/fixedColumns-auto-height.js @@ -21,15 +21,15 @@ const columns = [ ]; const data = [ - { a: '123', b: 'xxxxxxxx', d: 3, key: '1' }, - { a: 'cdd', b: 'edd12221', d: 3, key: '2' }, - { a: '133', c: 'edd12221', d: 2, key: '3' }, - { a: '133', c: 'edd12221', d: 2, key: '4' }, - { a: '133', c: 'edd12221', d: 2, key: '5' }, - { a: '133', c: 'edd12221', d: 2, key: '6' }, - { a: '133', c: 'edd12221', d: 2, key: '7' }, - { a: '133', c: 'edd12221', d: 2, key: '8' }, - { a: '133', c: 'edd12221', d: 2, key: '9' }, + { a: '123', b: 'xxxxxxxx', d: 3, key: '1', title: 'hello' }, + { a: 'cdd', b: 'edd12221', d: 3, key: '2', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '3', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '4', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '5', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '6', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '7', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '8', title: 'hello' }, + { a: '133', c: 'edd12221', d: 2, key: '9', title: 'hello' }, ]; ReactDOM.render( diff --git a/examples/fixedColumnsAndHeader.js b/examples/fixedColumnsAndHeader.js index 88a8832d1..e62776a59 100644 --- a/examples/fixedColumnsAndHeader.js +++ b/examples/fixedColumnsAndHeader.js @@ -34,6 +34,6 @@ const data = [ ReactDOM.render(

Fixed columns and header

- +
, document.getElementById('__react-content')); diff --git a/examples/key.js b/examples/key.js index dcec2c6b7..d468ba201 100644 --- a/examples/key.js +++ b/examples/key.js @@ -1,28 +1,29 @@ /* eslint-disable no-console,func-names,react/no-multi-comp */ import React from 'react'; import ReactDOM from 'react-dom'; +import PropTypes from 'prop-types'; import Table from 'rc-table'; import 'rc-table/assets/index.less'; -const CheckBox = React.createClass({ - render() { - const props = this.props; - return ( - - ); - }, -}); +const CheckBox = ({ id }) => ( + +); -const MyTable = React.createClass({ - getInitialState() { - const props = this.props; - return { +class Demo extends React.Component { + static propTypes = { + data: PropTypes.array.isRequired, + } + + constructor(props) { + super(props); + + this.state = { data: props.data, }; - }, + } remove(index) { const rows = this.state.data; @@ -30,22 +31,19 @@ const MyTable = React.createClass({ this.setState({ data: rows, }); - }, + } - handleClick(index) { - const self = this; - return () => { - self.remove(index); - }; - }, + handleClick = (index) => () => { + this.remove(index); + } checkbox(a) { return ; - }, + } - renderAction(o, row, index) { + renderAction = (o, row, index) => { return Delete; - }, + } render() { const state = this.state; @@ -58,15 +56,15 @@ const MyTable = React.createClass({ return (
record.a} /> ); - }, -}); + } +} const data = [{ a: '123' }, { a: 'cdd', b: 'edd' }, { a: '1333', c: 'eee', d: 2 }]; ReactDOM.render(

specify key

- +
, document.getElementById('__react-content') ); diff --git a/examples/scrollY.js b/examples/scrollY.js index 90848ee93..ae286c5ed 100644 --- a/examples/scrollY.js +++ b/examples/scrollY.js @@ -14,18 +14,16 @@ for (let i = 0; i < 10; i++) { }); } -const Test = React.createClass({ - getInitialState() { - return { - showBody: true, - }; - }, +class Demo extends React.Component { + state = { + showBody: true, + } - toggleBody() { + toggleBody = () => { this.setState({ showBody: !this.state.showBody, }); - }, + } render() { const columns = [ @@ -52,13 +50,13 @@ const Test = React.createClass({ }} /> ); - }, -}); + } +} ReactDOM.render(

scroll body table

- +
, document.getElementById('__react-content') ); diff --git a/examples/subTable.js b/examples/subTable.js index 107d05304..459428e36 100644 --- a/examples/subTable.js +++ b/examples/subTable.js @@ -29,11 +29,12 @@ const data = [ }, ]; -const MyTable = React.createClass({ - handleClick(record, e) { +class Demo extends React.Component { + handleClick = (record, e) => { e.preventDefault(); console.log(record.a); - }, + } + render() { const columns = [ { title: 'title1', dataIndex: 'a', key: 'a', width: 100 }, @@ -56,7 +57,7 @@ const MyTable = React.createClass({ /> ); - }, -}); + } +} -ReactDOM.render(, document.getElementById('__react-content')); +ReactDOM.render(, document.getElementById('__react-content')); diff --git a/package.json b/package.json index d742bd0fe..e75884906 100644 --- a/package.json +++ b/package.json @@ -62,12 +62,16 @@ ], "transform": { "\\.jsx?$": "./node_modules/rc-tools/scripts/jestPreprocessor.js" - } + }, + "snapshotSerializers": [ + "enzyme-to-json/serializer" + ] }, "dependencies": { "babel-runtime": "6.x", "component-classes": "^1.2.6", "lodash.get": "^4.4.2", + "mini-store": "^1.0.2", "prop-types": "^15.5.8", "rc-util": "^4.0.4", "shallowequal": "^1.0.2", @@ -78,14 +82,13 @@ "enzyme": "^3.1.0", "enzyme-adapter-react-16": "^1.0.1", "enzyme-to-json": "^3.1.2", - "jest": "^21.2.0", + "jest": "^21.2.1", "pre-commit": "1.x", "rc-animate": "^2.3.0", - "rc-dropdown": "~1.4.10", + "rc-dropdown": "~2.0.1", "rc-menu": "^5.0.11", "rc-tools": "7.x", "react": "^16.0.0", - "react-addons-test-utils": "^15.3.1", "react-dom": "^16.0.0", "react-test-renderer": "^16.0.0" }, diff --git a/src/BaseTable.js b/src/BaseTable.js new file mode 100644 index 000000000..91d3cc867 --- /dev/null +++ b/src/BaseTable.js @@ -0,0 +1,159 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'mini-store'; +import ColGroup from './ColGroup'; +import TableHeader from './TableHeader'; +import TableRow from './TableRow'; +import ExpandableRow from './ExpandableRow'; + +class BaseTable extends React.Component { + static propTypes = { + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + columns: PropTypes.array.isRequired, + tableClassName: PropTypes.string.isRequired, + hasHead: PropTypes.bool.isRequired, + hasBody: PropTypes.bool.isRequired, + store: PropTypes.object.isRequired, + expander: PropTypes.object.isRequired, + getRowKey: PropTypes.func, + } + + static contextTypes = { + table: PropTypes.any, + } + + handleRowHover = (isHover, key) => { + this.props.store.setState({ + currentHoverKey: isHover ? key : null, + }); + } + + renderRows = (renderData, indent, ancestorKeys = []) => { + const { table } = this.context; + const { columnManager } = table; + const { + prefixCls, + childrenColumnName, + rowClassName, + rowRef, + onRowClick, + onRowDoubleClick, + onRowContextMenu, + onRowMouseEnter, + onRowMouseLeave, + } = table.props; + const { getRowKey, fixed, expander } = this.props; + + const rows = []; + + for (let i = 0; i < renderData.length; i++) { + const record = renderData[i]; + const key = getRowKey(record, i); + const className = typeof rowClassName === 'string' + ? rowClassName + : rowClassName(record, i, indent); + + const onHoverProps = {}; + if (columnManager.isAnyColumnsFixed()) { + onHoverProps.onHover = this.handleRowHover; + } + + let leafColumns; + if (fixed === 'left') { + leafColumns = columnManager.leftLeafColumns(); + } else if (fixed === 'right') { + leafColumns = columnManager.rightLeafColumns(); + } else { + leafColumns = columnManager.leafColumns(); + } + + const rowPrefixCls = `${prefixCls}-row`; + + const row = ( + + {(expandableRow) => ( // eslint-disable-line + + )} + + ); + + rows.push(row); + + const expandedRows = expander.renderRows( + this.renderRows, + record, + i, + indent, + fixed, + key, + ancestorKeys + ); + + if (expandedRows) { + rows.push(...expandedRows); + } + } + return rows; + } + + render() { + const { prefixCls, scroll, data, getBodyWrapper } = this.context.table.props; + const { expander, tableClassName, hasHead, hasBody, fixed, columns } = this.props; + const tableStyle = {}; + + if (!fixed && scroll.x) { + // not set width, then use content fixed width + if (scroll.x === true) { + tableStyle.tableLayout = 'fixed'; + } else { + tableStyle.width = scroll.x; + } + } + + return ( +
+ + {hasHead && } + {hasBody && getBodyWrapper( + + {this.renderRows(data, 0)} + + )} +
+ ); + } +} + +export default connect()(BaseTable); diff --git a/src/BodyTable.js b/src/BodyTable.js new file mode 100644 index 000000000..b35f0d85c --- /dev/null +++ b/src/BodyTable.js @@ -0,0 +1,107 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { measureScrollbar } from './utils'; +import BaseTable from './BaseTable'; + +export default function BodyTable(props, { table }) { + const { prefixCls, scroll } = table.props; + const { columns, fixed, tableClassName, getRowKey, handleBodyScroll, expander } = props; + const { saveRef } = table; + let { useFixedHeader } = table.props; + const bodyStyle = { ...table.props.bodyStyle }; + const innerBodyStyle = {}; + + if (scroll.x || fixed) { + bodyStyle.overflowX = bodyStyle.overflowX || 'auto'; + // Fix weired webkit render bug + // https://github.com/ant-design/ant-design/issues/7783 + bodyStyle.WebkitTransform = 'translate3d (0, 0, 0)'; + } + + if (scroll.y) { + // maxHeight will make fixed-Table scrolling not working + // so we only set maxHeight to body-Table here + if (fixed) { + innerBodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y; + innerBodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; + } else { + bodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y; + } + bodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; + useFixedHeader = true; + + // Add negative margin bottom for scroll bar overflow bug + const scrollbarWidth = measureScrollbar(); + if (scrollbarWidth > 0 && fixed) { + bodyStyle.marginBottom = `-${scrollbarWidth}px`; + bodyStyle.paddingBottom = '0px'; + } + } + + const baseTable = ( + + ); + + if (fixed && columns.length) { + let refName; + if (columns[0].fixed === 'left' || columns[0].fixed === true) { + refName = 'fixedColumnsBodyLeft'; + } else if (columns[0].fixed === 'right') { + refName = 'fixedColumnsBodyRight'; + } + delete bodyStyle.overflowX; + delete bodyStyle.overflowY; + return ( +
+
+ {baseTable} +
+
+ ); + } + + return ( +
+ {baseTable} +
+ ); +} + +BodyTable.propTypes = { + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + columns: PropTypes.array.isRequired, + tableClassName: PropTypes.string.isRequired, + handleBodyScroll: PropTypes.func.isRequired, + getRowKey: PropTypes.func.isRequired, + expander: PropTypes.object.isRequired, +}; + +BodyTable.contextTypes = { + table: PropTypes.any, +}; diff --git a/src/ColGroup.js b/src/ColGroup.js new file mode 100644 index 000000000..8b0ce83c9 --- /dev/null +++ b/src/ColGroup.js @@ -0,0 +1,52 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +export default function ColGroup(props, { table }) { + const { prefixCls, expandIconAsCell } = table.props; + const { fixed } = props; + + let cols = []; + + if (expandIconAsCell && fixed !== 'right') { + cols.push( + + ); + } + + let leafColumns; + + if (fixed === 'left') { + leafColumns = table.columnManager.leftLeafColumns(); + } else if (fixed === 'right') { + leafColumns = table.columnManager.rightLeafColumns(); + } else { + leafColumns = table.columnManager.leafColumns(); + } + cols = cols.concat( + leafColumns.map(c => { + return ( + + ); + }) + ); + + return ( + + {cols} + + ); +} + +ColGroup.propTypes = { + fixed: PropTypes.string, +}; + +ColGroup.contextTypes = { + table: PropTypes.any, +}; diff --git a/src/Column.jsx b/src/Column.js similarity index 100% rename from src/Column.jsx rename to src/Column.js diff --git a/src/ColumnGroup.jsx b/src/ColumnGroup.js similarity index 100% rename from src/ColumnGroup.jsx rename to src/ColumnGroup.js diff --git a/src/ExpandIcon.jsx b/src/ExpandIcon.js similarity index 94% rename from src/ExpandIcon.jsx rename to src/ExpandIcon.js index 56f9ecf9e..244930562 100644 --- a/src/ExpandIcon.jsx +++ b/src/ExpandIcon.js @@ -23,7 +23,7 @@ export default class ExpandIcon extends React.Component { return ( onExpand(!expanded, record, e)} + onClick={(e) => onExpand(record, e)} /> ); } else if (needIndentSpaced) { diff --git a/src/ExpandableRow.js b/src/ExpandableRow.js new file mode 100644 index 000000000..8b6c6f185 --- /dev/null +++ b/src/ExpandableRow.js @@ -0,0 +1,123 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'mini-store'; +import ExpandIcon from './ExpandIcon'; + +class ExpandableRow extends React.Component { + static propTypes = { + prefixCls: PropTypes.string.isRequired, + rowKey: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]).isRequired, + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + record: PropTypes.object.isRequired, + indentSize: PropTypes.number, + needIndentSpaced: PropTypes.bool.isRequired, + expandRowByClick: PropTypes.bool, + expanded: PropTypes.bool.isRequired, + expandIconAsCell: PropTypes.bool, + expandIconColumnIndex: PropTypes.number, + childrenColumnName: PropTypes.string, + expandedRowRender: PropTypes.func, + onExpandedChange: PropTypes.func.isRequired, + onRowClick: PropTypes.func, + children: PropTypes.func.isRequired, + } + + componentWillUnmount() { + this.handleDestroy(); + } + + hasExpandIcon = (columnIndex) => { + const { expandRowByClick } = this.props; + return !this.expandIconAsCell && + !expandRowByClick && + columnIndex === this.expandIconColumnIndex; + } + + handleExpandChange = (record, event) => { + const { onExpandedChange, expanded, rowKey } = this.props; + if (this.expandable) { + onExpandedChange(!expanded, record, event, rowKey); + } + } + + handleDestroy() { + const { onExpandedChange, rowKey, record } = this.props; + if (this.expandable) { + onExpandedChange(false, record, null, rowKey); + } + } + + handleRowClick = (record, index, event) => { + const { expandRowByClick, onRowClick } = this.props; + if (expandRowByClick) { + this.handleExpandChange(record, event); + } + onRowClick(record, index, event); + } + + renderExpandIcon = () => { + const { prefixCls, expanded, record, needIndentSpaced } = this.props; + + return ( + + ); + } + + renderExpandIconCell = (cells) => { + if (!this.expandIconAsCell) { + return; + } + const { prefixCls } = this.props; + + cells.push( + + {this.renderExpandIcon()} + + ); + } + + render() { + const { + childrenColumnName, + expandedRowRender, + indentSize, + record, + fixed, + } = this.props; + + this.expandIconAsCell = fixed !== 'right' ? this.props.expandIconAsCell : false; + this.expandIconColumnIndex = fixed !== 'right' ? this.props.expandIconColumnIndex : -1; + const childrenData = record[childrenColumnName]; + this.expandable = !!(childrenData || expandedRowRender); + + const expandableRowProps = { + indentSize, + onRowClick: this.handleRowClick, + hasExpandIcon: this.hasExpandIcon, + renderExpandIcon: this.renderExpandIcon, + renderExpandIconCell: this.renderExpandIconCell, + }; + + return this.props.children(expandableRowProps); + } +} + +export default connect(({ expandedRowKeys }, { rowKey }) => ({ + expanded: !!~expandedRowKeys.indexOf(rowKey), +}))(ExpandableRow); diff --git a/src/ExpandableTable.js b/src/ExpandableTable.js new file mode 100644 index 000000000..797b49817 --- /dev/null +++ b/src/ExpandableTable.js @@ -0,0 +1,204 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'mini-store'; +import TableRow from './TableRow'; + +class ExpandableTable extends React.Component { + static propTypes = { + expandIconAsCell: PropTypes.bool, + expandedRowKeys: PropTypes.array, + expandedRowClassName: PropTypes.func, + defaultExpandAllRows: PropTypes.bool, + defaultExpandedRowKeys: PropTypes.array, + expandIconColumnIndex: PropTypes.number, + expandedRowRender: PropTypes.func, + childrenColumnName: PropTypes.string, + indentSize: PropTypes.number, + onExpand: PropTypes.func, + onExpandedRowsChange: PropTypes.func, + columnManager: PropTypes.object.isRequired, + store: PropTypes.object.isRequired, + prefixCls: PropTypes.string.isRequired, + data: PropTypes.array, + children: PropTypes.func.isRequired, + } + + static defaultProps = { + expandIconAsCell: false, + expandedRowClassName: () => '', + expandIconColumnIndex: 0, + defaultExpandAllRows: false, + defaultExpandedRowKeys: [], + childrenColumnName: 'children', + indentSize: 15, + onExpand() {}, + onExpandedRowsChange() {}, + } + + constructor(props) { + super(props); + + const { + data, + childrenColumnName, + defaultExpandAllRows, + expandedRowKeys, + defaultExpandedRowKeys, + getRowKey, + } = props; + + let finnalExpandedRowKeys = []; + let rows = [...data]; + + if (defaultExpandAllRows) { + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + finnalExpandedRowKeys.push(getRowKey(row, i)); + rows = rows.concat(row[childrenColumnName] || []); + } + } else { + finnalExpandedRowKeys = expandedRowKeys || defaultExpandedRowKeys; + } + + this.columnManager = props.columnManager; + this.store = props.store; + + this.store.setState({ + expandedRowsHeight: {}, + expandedRowKeys: finnalExpandedRowKeys, + }); + } + + componentWillReceiveProps(nextProps) { + if ('expandedRowKeys' in nextProps) { + this.store.setState({ + expandedRowKeys: nextProps.expandedRowKeys, + }); + } + } + + handleExpandChange = (expanded, record, event, rowKey) => { + if (event) { + event.preventDefault(); + event.stopPropagation(); + } + + const { onExpandedRowsChange, onExpand } = this.props; + const { expandedRowKeys } = this.store.getState(); + + if (expanded) { + // row was expaned + expandedRowKeys.push(rowKey); + } else { + // row was collapse + const expandedRowIndex = expandedRowKeys.indexOf(rowKey); + if (expandedRowIndex !== -1) { + expandedRowKeys.splice(expandedRowIndex, 1); + } + } + + if (!this.props.expandedRowKeys) { + this.store.setState({ expandedRowKeys }); + } + + onExpandedRowsChange(expandedRowKeys); + onExpand(expanded, record); + } + + renderExpandIndentCell = (rows, fixed) => { + const { prefixCls, expandIconAsCell } = this.props; + if (!expandIconAsCell || fixed === 'right') { + return; + } + + rows[0].unshift({ + key: 'rc-table-expandIconAsCell', + className: `${prefixCls}-expand-icon-th`, + title: '', + rowSpan: rows.length, + }); + } + + renderExpandedRow(content, className, ancestorKeys, fixed) { + const { prefixCls, expandIconAsCell } = this.props; + let colCount; + if (fixed === 'left') { + colCount = this.columnManager.leftLeafColumns().length; + } else if (fixed === 'right') { + colCount = this.columnManager.rightLeafColumns().length; + } else { + colCount = this.columnManager.leafColumns().length; + } + const columns = [{ + key: 'extra-row', + render: () => ({ + props: { + colSpan: colCount, + }, + children: fixed !== 'right' ? content : ' ', + }), + }]; + if (expandIconAsCell && fixed !== 'right') { + columns.unshift({ + key: 'expand-icon-placeholder', + render: () => null, + }); + } + + const rowKey = `${ancestorKeys[0]}-extra-row`; + + return ( + + ); + } + + renderRows = (renderRows, record, index, indent, fixed, parentKey, ancestorKeys) => { + const { expandedRowClassName, expandedRowRender, childrenColumnName } = this.props; + const childrenData = record[childrenColumnName]; + const expandedRowContent = expandedRowRender && expandedRowRender(record, index, indent); + const nextAncestorKeys = [...ancestorKeys, parentKey]; + + if (expandedRowContent) { + return [ + this.renderExpandedRow( + expandedRowContent, + expandedRowClassName(record, index, indent), + nextAncestorKeys, + fixed, + ), + ]; + } + + if (childrenData) { + return renderRows( + childrenData, + indent + 1, + nextAncestorKeys, + ); + } + } + + render() { + const { data, childrenColumnName, children } = this.props; + const needIndentSpaced = data.some(record => record[childrenColumnName]); + + return children({ + props: this.props, + needIndentSpaced, + renderRows: this.renderRows, + handleExpandChange: this.handleExpandChange, + renderExpandIndentCell: this.renderExpandIndentCell, + }); + } +} + +export default connect()(ExpandableTable); diff --git a/src/HeadTable.js b/src/HeadTable.js new file mode 100644 index 000000000..6e088ee8a --- /dev/null +++ b/src/HeadTable.js @@ -0,0 +1,60 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { measureScrollbar } from './utils'; +import BaseTable from './BaseTable'; + +export default function HeadTable(props, { table }) { + const { prefixCls, scroll, showHeader } = table.props; + const { columns, fixed, tableClassName, handleBodyScrollLeft, expander } = props; + const { saveRef } = table; + let { useFixedHeader } = table.props; + const headStyle = {}; + + if (scroll.y) { + useFixedHeader = true; + // Add negative margin bottom for scroll bar overflow bug + const scrollbarWidth = measureScrollbar(); + if (scrollbarWidth > 0 && !fixed) { + headStyle.marginBottom = `-${scrollbarWidth}px`; + headStyle.paddingBottom = '0px'; + } + } + + if (!useFixedHeader || !showHeader) { + return null; + } + + return ( +
+ +
+ ); +} + +HeadTable.propTypes = { + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + columns: PropTypes.array.isRequired, + tableClassName: PropTypes.string.isRequired, + handleBodyScrollLeft: PropTypes.func.isRequired, + expander: PropTypes.object.isRequired, +}; + +HeadTable.contextTypes = { + table: PropTypes.any, +}; diff --git a/src/Table.js b/src/Table.js new file mode 100644 index 000000000..36f5700a6 --- /dev/null +++ b/src/Table.js @@ -0,0 +1,427 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { debounce, warningOnce } from './utils'; +import shallowequal from 'shallowequal'; +import addEventListener from 'rc-util/lib/Dom/addEventListener'; +import { Provider, create } from 'mini-store'; +import ColumnManager from './ColumnManager'; +import classes from 'component-classes'; +import HeadTable from './HeadTable'; +import BodyTable from './BodyTable'; +import ExpandableTable from './ExpandableTable'; + +export default class Table extends React.Component { + static propTypes = { + data: PropTypes.array, + useFixedHeader: PropTypes.bool, + columns: PropTypes.array, + prefixCls: PropTypes.string, + bodyStyle: PropTypes.object, + style: PropTypes.object, + rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), + onRowClick: PropTypes.func, + onRowDoubleClick: PropTypes.func, + onRowContextMenu: PropTypes.func, + onRowMouseEnter: PropTypes.func, + onRowMouseLeave: PropTypes.func, + showHeader: PropTypes.bool, + title: PropTypes.func, + footer: PropTypes.func, + emptyText: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), + scroll: PropTypes.object, + rowRef: PropTypes.func, + getBodyWrapper: PropTypes.func, + children: PropTypes.node, + ...ExpandableTable.PropTypes, + } + + static childContextTypes = { + table: PropTypes.any, + } + + static defaultProps = { + data: [], + useFixedHeader: false, + rowKey: 'key', + rowClassName: () => '', + onRowClick() {}, + onRowDoubleClick() {}, + onRowContextMenu() {}, + onRowMouseEnter() {}, + onRowMouseLeave() {}, + prefixCls: 'rc-table', + bodyStyle: {}, + style: {}, + showHeader: true, + scroll: {}, + rowRef: () => null, + getBodyWrapper: body => body, + emptyText: () => 'No Data', + } + + constructor(props) { + super(props); + + this.columnManager = new ColumnManager(props.columns, props.children); + + this.store = create({ + currentHoverKey: null, + fixedColumnsHeadRowsHeight: [], + fixedColumnsBodyRowsHeight: [], + }); + + this.setScrollPosition('left'); + + this.debouncedWindowResize = debounce(this.handleWindowResize, 150); + } + + getChildContext() { + return { + table: { + props: this.props, + columnManager: this.columnManager, + saveRef: this.saveRef, + }, + }; + } + + componentDidMount() { + if (this.columnManager.isAnyColumnsFixed()) { + this.handleWindowResize(); + this.resizeEvent = addEventListener( + window, 'resize', this.debouncedWindowResize + ); + } + } + + componentWillReceiveProps(nextProps) { + if (nextProps.columns && nextProps.columns !== this.props.columns) { + this.columnManager.reset(nextProps.columns); + } else if (nextProps.children !== this.props.children) { + this.columnManager.reset(null, nextProps.children); + } + } + + componentDidUpdate(prevProps) { + if (this.columnManager.isAnyColumnsFixed()) { + this.handleWindowResize(); + if (!this.resizeEvent) { + this.resizeEvent = addEventListener( + window, 'resize', this.debouncedWindowResize + ); + } + } + // when table changes to empty, reset scrollLeft + if (prevProps.data.length > 0 && this.props.data.length === 0 && this.hasScrollX()) { + this.resetScrollX(); + } + } + + componentWillUnmount() { + if (this.resizeEvent) { + this.resizeEvent.remove(); + } + if (this.debouncedWindowResize) { + this.debouncedWindowResize.cancel(); + } + } + + getRowKey = (record, index) => { + const rowKey = this.props.rowKey; + const key = (typeof rowKey === 'function') ? + rowKey(record, index) : record[rowKey]; + warningOnce( + key !== undefined, + 'Each record in table should have a unique `key` prop,' + + 'or set `rowKey` to an unique primary key.' + ); + return key === undefined ? index : key; + } + + setScrollPosition(position) { + this.scrollPosition = position; + if (this.tableNode) { + const { prefixCls } = this.props; + if (position === 'both') { + classes(this.tableNode) + .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`)) + .add(`${prefixCls}-scroll-position-left`) + .add(`${prefixCls}-scroll-position-right`); + } else { + classes(this.tableNode) + .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`)) + .add(`${prefixCls}-scroll-position-${position}`); + } + } + } + + setScrollPositionClassName() { + const node = this.bodyTable; + const scrollToLeft = node.scrollLeft === 0; + const scrollToRight = 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'); + } + } + + handleWindowResize = () => { + this.syncFixedTableRowHeight(); + this.setScrollPositionClassName(); + } + + syncFixedTableRowHeight = () => { + const tableRect = this.tableNode.getBoundingClientRect(); + // If tableNode's height less than 0, suppose it is hidden and don't recalculate rowHeight. + // see: https://github.com/ant-design/ant-design/issues/4836 + if (tableRect.height !== undefined && tableRect.height <= 0) { + return; + } + const { prefixCls } = this.props; + const headRows = this.headTable ? + this.headTable.querySelectorAll('thead') : + this.bodyTable.querySelectorAll('thead'); + const bodyRows = this.bodyTable.querySelectorAll(`.${prefixCls}-row`) || []; + const fixedColumnsHeadRowsHeight = [].map.call( + headRows, row => row.getBoundingClientRect().height || 'auto' + ); + const fixedColumnsBodyRowsHeight = [].map.call( + bodyRows, row => row.getBoundingClientRect().height || 'auto' + ); + const state = this.store.getState(); + if (shallowequal(state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) && + shallowequal(state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)) { + return; + } + + this.store.setState({ + fixedColumnsHeadRowsHeight, + fixedColumnsBodyRowsHeight, + }); + } + + resetScrollX() { + if (this.headTable) { + this.headTable.scrollLeft = 0; + } + if (this.bodyTable) { + this.bodyTable.scrollLeft = 0; + } + } + + hasScrollX() { + const { scroll = {} } = this.props; + return 'x' in scroll; + } + + handleBodyScrollLeft = (e) => { + // Fix https://github.com/ant-design/ant-design/issues/7635 + if (e.currentTarget !== e.target) { + return; + } + const target = e.target; + const { scroll = {} } = this.props; + const { headTable, bodyTable } = this; + if (target.scrollLeft !== this.lastScrollLeft && scroll.x) { + 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; + } + + handleBodyScrollTop = (e) => { + const target = e.target; + const { scroll = {} } = this.props; + const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this; + if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) { + const scrollTop = target.scrollTop; + if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) { + fixedColumnsBodyLeft.scrollTop = scrollTop; + } + if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) { + fixedColumnsBodyRight.scrollTop = scrollTop; + } + if (bodyTable && target !== bodyTable) { + bodyTable.scrollTop = scrollTop; + } + } + // Remember last scrollTop for scroll direction detecting. + this.lastScrollTop = target.scrollTop; + } + + handleBodyScroll = (e) => { + this.handleBodyScrollLeft(e); + this.handleBodyScrollTop(e); + } + + saveRef = (name) => (node) => { + this[name] = node; + } + + renderMainTable() { + const { scroll, prefixCls } = this.props; + const scrollable = + this.columnManager.isAnyColumnsFixed() || scroll.x || scroll.y; + + const table = [ + this.renderTable({ + columns: this.columnManager.groupedColumns(), + }), + this.renderEmptyText(), + this.renderFooter(), + ]; + + return scrollable ? ( +
{table}
+ ) : table; + } + + renderLeftFixedTable() { + const { prefixCls } = this.props; + + return ( +
+ {this.renderTable({ + columns: this.columnManager.leftColumns(), + fixed: 'left', + })} +
+ ); + } + + renderRightFixedTable() { + const { prefixCls } = this.props; + + return ( +
+ {this.renderTable({ + columns: this.columnManager.rightColumns(), + fixed: 'right', + })} +
+ ); + } + + renderTable(options) { + const { columns, fixed } = options; + const { prefixCls, scroll = {} } = this.props; + const tableClassName = (scroll.x || fixed) ? `${prefixCls}-fixed` : ''; + + const headTable = ( + + ); + + const bodyTable = ( + + ); + + return [headTable, bodyTable]; + } + + renderTitle() { + const { title, prefixCls } = this.props; + return title ? ( +
+ {title(this.props.data)} +
+ ) : null; + } + + renderFooter() { + const { footer, prefixCls } = this.props; + return footer ? ( +
+ {footer(this.props.data)} +
+ ) : null; + } + + renderEmptyText() { + const { emptyText, prefixCls, data } = this.props; + if (data.length) { + return null; + } + const emptyClassName = `${prefixCls}-placeholder`; + return ( +
+ {(typeof emptyText === 'function') ? emptyText() : emptyText} +
+ ); + } + + render() { + const props = this.props; + const prefixCls = props.prefixCls; + + let className = props.prefixCls; + if (props.className) { + className += ` ${props.className}`; + } + if (props.useFixedHeader || (props.scroll && props.scroll.y)) { + className += ` ${prefixCls}-fixed-header`; + } + if (this.scrollPosition === 'both') { + className += ` ${prefixCls}-scroll-position-left ${prefixCls}-scroll-position-right`; + } else { + className += ` ${prefixCls}-scroll-position-${this.scrollPosition}`; + } + const hasLeftFixed = this.columnManager.isAnyColumnsLeftFixed(); + const hasRightFixed = this.columnManager.isAnyColumnsRightFixed(); + + return ( + + + {(expander) => { + this.expander = expander; + return ( +
+ {this.renderTitle()} +
+ {this.renderMainTable()} + {hasLeftFixed && this.renderLeftFixedTable()} + {hasRightFixed && this.renderRightFixedTable()} +
+
+ ); + }} +
+
+ ); + } +} diff --git a/src/Table.jsx b/src/Table.jsx deleted file mode 100644 index 921f15926..000000000 --- a/src/Table.jsx +++ /dev/null @@ -1,795 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import TableRow from './TableRow'; -import TableHeader from './TableHeader'; -import { measureScrollbar, debounce, warningOnce } from './utils'; -import shallowequal from 'shallowequal'; -import addEventListener from 'rc-util/lib/Dom/addEventListener'; -import ColumnManager from './ColumnManager'; -import createStore from './createStore'; -import classes from 'component-classes'; - -export default class Table extends React.Component { - static propTypes = { - data: PropTypes.array, - expandIconAsCell: PropTypes.bool, - defaultExpandAllRows: PropTypes.bool, - expandedRowKeys: PropTypes.array, - defaultExpandedRowKeys: PropTypes.array, - useFixedHeader: PropTypes.bool, - columns: PropTypes.array, - prefixCls: PropTypes.string, - bodyStyle: PropTypes.object, - style: PropTypes.object, - rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]), - expandedRowClassName: PropTypes.func, - childrenColumnName: PropTypes.string, - onExpand: PropTypes.func, - onExpandedRowsChange: PropTypes.func, - indentSize: PropTypes.number, - onRowClick: PropTypes.func, - onRowDoubleClick: PropTypes.func, - onRowContextMenu: PropTypes.func, - onRowMouseEnter: PropTypes.func, - onRowMouseLeave: PropTypes.func, - expandIconColumnIndex: PropTypes.number, - showHeader: PropTypes.bool, - title: PropTypes.func, - footer: PropTypes.func, - emptyText: PropTypes.oneOfType([PropTypes.node, PropTypes.func]), - scroll: PropTypes.object, - rowRef: PropTypes.func, - getBodyWrapper: PropTypes.func, - children: PropTypes.node, - } - - static defaultProps = { - data: [], - useFixedHeader: false, - expandIconAsCell: false, - defaultExpandAllRows: false, - defaultExpandedRowKeys: [], - rowKey: 'key', - rowClassName: () => '', - expandedRowClassName: () => '', - onExpand() {}, - onExpandedRowsChange() {}, - onRowClick() {}, - onRowDoubleClick() {}, - onRowContextMenu() {}, - onRowMouseEnter() {}, - onRowMouseLeave() {}, - prefixCls: 'rc-table', - bodyStyle: {}, - style: {}, - childrenColumnName: 'children', - indentSize: 15, - expandIconColumnIndex: 0, - showHeader: true, - scroll: {}, - rowRef: () => null, - getBodyWrapper: body => body, - emptyText: () => 'No Data', - } - - constructor(props) { - super(props); - let expandedRowKeys = []; - let rows = [...props.data]; - this.columnManager = new ColumnManager(props.columns, props.children); - this.store = createStore({ - currentHoverKey: null, - expandedRowsHeight: {}, - }); - this.setScrollPosition('left'); - - if (props.defaultExpandAllRows) { - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - expandedRowKeys.push(this.getRowKey(row, i)); - rows = rows.concat(row[props.childrenColumnName] || []); - } - } else { - expandedRowKeys = props.expandedRowKeys || props.defaultExpandedRowKeys; - } - this.state = { - expandedRowKeys, - currentHoverKey: null, - fixedColumnsHeadRowsHeight: [], - fixedColumnsBodyRowsHeight: [], - }; - this.debouncedWindowResize = debounce(this.handleWindowResize, 150); - } - - componentDidMount() { - if (this.columnManager.isAnyColumnsFixed()) { - this.handleWindowResize(); - this.resizeEvent = addEventListener( - window, 'resize', this.debouncedWindowResize - ); - } - } - - componentWillReceiveProps(nextProps) { - if ('expandedRowKeys' in nextProps) { - this.setState({ - expandedRowKeys: nextProps.expandedRowKeys, - }); - } - if (nextProps.columns && nextProps.columns !== this.props.columns) { - this.columnManager.reset(nextProps.columns); - } else if (nextProps.children !== this.props.children) { - this.columnManager.reset(null, nextProps.children); - } - } - - componentDidUpdate(prevProps) { - if (this.columnManager.isAnyColumnsFixed()) { - this.handleWindowResize(); - if (!this.resizeEvent) { - this.resizeEvent = addEventListener( - window, 'resize', this.debouncedWindowResize - ); - } - } - // when table changes to empty, reset scrollLeft - if (prevProps.data.length > 0 && this.props.data.length === 0 && this.hasScrollX()) { - this.resetScrollX(); - } - } - - componentWillUnmount() { - if (this.resizeEvent) { - this.resizeEvent.remove(); - } - if (this.debouncedWindowResize) { - this.debouncedWindowResize.cancel(); - } - } - - onExpandedRowsChange(expandedRowKeys) { - if (!this.props.expandedRowKeys) { - this.setState({ expandedRowKeys }); - } - this.props.onExpandedRowsChange(expandedRowKeys); - } - - onExpanded = (expanded, record, e, index) => { - if (e) { - e.preventDefault(); - e.stopPropagation(); - } - const info = this.findExpandedRow(record); - if (typeof info !== 'undefined' && !expanded) { - this.onRowDestroy(record, index); - } else if (!info && expanded) { - const expandedRows = this.getExpandedRows().concat(); - expandedRows.push(this.getRowKey(record, index)); - this.onExpandedRowsChange(expandedRows); - } - this.props.onExpand(expanded, record); - } - - onRowDestroy = (record, rowIndex) => { - const expandedRows = this.getExpandedRows().concat(); - const rowKey = this.getRowKey(record, rowIndex); - let index = -1; - expandedRows.forEach((r, i) => { - if (r === rowKey) { - index = i; - } - }); - if (index !== -1) { - expandedRows.splice(index, 1); - } - this.onExpandedRowsChange(expandedRows); - } - - getRowKey(record, index) { - const rowKey = this.props.rowKey; - const key = (typeof rowKey === 'function') ? - rowKey(record, index) : record[rowKey]; - warningOnce( - key !== undefined, - 'Each record in table should have a unique `key` prop,' + - 'or set `rowKey` to an unique primary key.' - ); - return key === undefined ? index : key; - } - - getExpandedRows() { - return this.props.expandedRowKeys || this.state.expandedRowKeys; - } - - getHeader(columns, fixed) { - const { showHeader, expandIconAsCell, prefixCls } = this.props; - const rows = this.getHeaderRows(columns); - - if (expandIconAsCell && fixed !== 'right') { - rows[0].unshift({ - key: 'rc-table-expandIconAsCell', - className: `${prefixCls}-expand-icon-th`, - title: '', - rowSpan: rows.length, - }); - } - - const trStyle = fixed ? this.getHeaderRowStyle(columns, rows) : null; - - return showHeader ? ( - - ) : null; - } - - getHeaderRows(columns, currentRow = 0, rows) { - rows = rows || []; - rows[currentRow] = rows[currentRow] || []; - - columns.forEach(column => { - if (column.rowSpan && rows.length < column.rowSpan) { - while (rows.length < column.rowSpan) { - rows.push([]); - } - } - const cell = { - key: column.key, - className: column.className || '', - children: column.title, - }; - if (column.children) { - this.getHeaderRows(column.children, currentRow + 1, rows); - } - if ('colSpan' in column) { - cell.colSpan = column.colSpan; - } - if ('rowSpan' in column) { - cell.rowSpan = column.rowSpan; - } - if (cell.colSpan !== 0) { - rows[currentRow].push(cell); - } - }); - return rows.filter(row => row.length > 0); - } - - getExpandedRow(key, content, visible, className, fixed) { - const { prefixCls, expandIconAsCell } = this.props; - let colCount; - if (fixed === 'left') { - colCount = this.columnManager.leftLeafColumns().length; - } else if (fixed === 'right') { - colCount = this.columnManager.rightLeafColumns().length; - } else { - colCount = this.columnManager.leafColumns().length; - } - const columns = [{ - key: 'extra-row', - render: () => ({ - props: { - colSpan: colCount, - }, - children: fixed !== 'right' ? content : ' ', - }), - }]; - if (expandIconAsCell && fixed !== 'right') { - columns.unshift({ - key: 'expand-icon-placeholder', - render: () => null, - }); - } - return ( - - ); - } - - getRowsByData(originalData, visible, indent, columns, fixed) { - const props = this.props; - const { - childrenColumnName, - expandedRowRender, - expandRowByClick, - rowClassName, - rowRef, - expandedRowClassName, - onRowClick, - onRowDoubleClick, - onRowContextMenu, - onRowMouseEnter, - onRowMouseLeave, - } = props; - const { fixedColumnsBodyRowsHeight } = this.state; - let rst = []; - const needIndentSpaced = props.data.some(record => record[childrenColumnName]); - - const expandIconAsCell = fixed !== 'right' ? props.expandIconAsCell : false; - const expandIconColumnIndex = fixed !== 'right' ? props.expandIconColumnIndex : -1; - const data = originalData; - for (let i = 0; i < data.length; i++) { - const record = data[i]; - const key = this.getRowKey(record, i); - const childrenColumn = record[childrenColumnName]; - const isRowExpanded = this.isRowExpanded(record, i); - let expandedRowContent; - if (expandedRowRender && isRowExpanded) { - expandedRowContent = expandedRowRender(record, i, indent); - } - const className = typeof rowClassName === 'string' - ? rowClassName - : rowClassName(record, i, indent); - - const onHoverProps = {}; - if (this.columnManager.isAnyColumnsFixed()) { - onHoverProps.onHover = this.handleRowHover; - } - - const height = (fixed && fixedColumnsBodyRowsHeight[i]) ? - fixedColumnsBodyRowsHeight[i] : null; - - - let leafColumns; - if (fixed === 'left') { - leafColumns = this.columnManager.leftLeafColumns(); - } else if (fixed === 'right') { - leafColumns = this.columnManager.rightLeafColumns(); - } else { - leafColumns = this.columnManager.leafColumns(); - } - - rst.push( - - ); - - const subVisible = visible && isRowExpanded; - - if (expandedRowContent && isRowExpanded) { - rst.push(this.getExpandedRow( - key, expandedRowContent, subVisible, expandedRowClassName(record, i, indent), fixed - )); - } - if (childrenColumn) { - rst = rst.concat(this.getRowsByData( - childrenColumn, subVisible, indent + 1, columns, fixed - )); - } - } - return rst; - } - - getRows(columns, fixed) { - return this.getRowsByData(this.props.data, true, 0, columns, fixed); - } - - getColGroup(columns, fixed) { - let cols = []; - if (this.props.expandIconAsCell && fixed !== 'right') { - cols.push( - - ); - } - let leafColumns; - if (fixed === 'left') { - leafColumns = this.columnManager.leftLeafColumns(); - } else if (fixed === 'right') { - leafColumns = this.columnManager.rightLeafColumns(); - } else { - leafColumns = this.columnManager.leafColumns(); - } - cols = cols.concat(leafColumns.map(c => { - return ; - })); - return {cols}; - } - - getLeftFixedTable() { - return this.getTable({ - columns: this.columnManager.leftColumns(), - fixed: 'left', - }); - } - - getRightFixedTable() { - return this.getTable({ - columns: this.columnManager.rightColumns(), - fixed: 'right', - }); - } - - getTable(options = {}) { - const { columns, fixed } = options; - const { prefixCls, scroll = {}, getBodyWrapper, showHeader } = this.props; - let { useFixedHeader } = this.props; - const bodyStyle = { ...this.props.bodyStyle }; - const headStyle = {}; - - let tableClassName = ''; - if (scroll.x || fixed) { - tableClassName = `${prefixCls}-fixed`; - bodyStyle.overflowX = bodyStyle.overflowX || 'auto'; - // Fix weired webkit render bug - // https://github.com/ant-design/ant-design/issues/7783 - bodyStyle.WebkitTransform = 'translate3d (0, 0, 0)'; - } - - const innerBodyStyle = {}; - if (scroll.y) { - // maxHeight will make fixed-Table scrolling not working - // so we only set maxHeight to body-Table here - if (fixed) { - innerBodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y; - innerBodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; - } else { - bodyStyle.maxHeight = bodyStyle.maxHeight || scroll.y; - } - bodyStyle.overflowY = bodyStyle.overflowY || 'scroll'; - useFixedHeader = true; - - // Add negative margin bottom for scroll bar overflow bug - const scrollbarWidth = measureScrollbar(); - if (scrollbarWidth > 0) { - (fixed ? bodyStyle : headStyle).marginBottom = `-${scrollbarWidth}px`; - (fixed ? bodyStyle : headStyle).paddingBottom = '0px'; - } - } - - const renderTable = (hasHead = true, hasBody = true) => { - const tableStyle = {}; - if (!fixed && scroll.x) { - // not set width, then use content fixed width - if (scroll.x === true) { - tableStyle.tableLayout = 'fixed'; - } else { - tableStyle.width = scroll.x; - } - } - const tableBody = hasBody ? getBodyWrapper( - - {this.getRows(columns, fixed)} - - ) : null; - return ( - - {this.getColGroup(columns, fixed)} - {hasHead ? this.getHeader(columns, fixed) : null} - {tableBody} -
- ); - }; - - let headTable; - - if (useFixedHeader && showHeader) { - headTable = ( -
- {renderTable(true, false)} -
- ); - } - - let bodyTable = ( -
- {renderTable(!useFixedHeader)} -
- ); - - if (fixed && columns.length) { - let refName; - if (columns[0].fixed === 'left' || columns[0].fixed === true) { - refName = 'fixedColumnsBodyLeft'; - } else if (columns[0].fixed === 'right') { - refName = 'fixedColumnsBodyRight'; - } - delete bodyStyle.overflowX; - delete bodyStyle.overflowY; - bodyTable = ( -
-
- {renderTable(!useFixedHeader)} -
-
- ); - } - return [headTable, bodyTable]; - } - - getTitle() { - const { title, prefixCls } = this.props; - return title ? ( -
- {title(this.props.data)} -
- ) : null; - } - - getFooter() { - const { footer, prefixCls } = this.props; - return footer ? ( -
- {footer(this.props.data)} -
- ) : null; - } - - getEmptyText() { - const { emptyText, prefixCls, data } = this.props; - if (data.length) { - return null; - } - const emptyClassName = `${prefixCls}-placeholder`; - return ( -
- {(typeof emptyText === 'function') ? emptyText() : emptyText} -
- ); - } - - getHeaderRowStyle(columns, rows) { - const { fixedColumnsHeadRowsHeight } = this.state; - const headerHeight = fixedColumnsHeadRowsHeight[0]; - if (headerHeight && columns) { - if (headerHeight === 'auto') { - return { height: 'auto' }; - } - return { height: headerHeight / rows.length }; - } - return null; - } - - setScrollPosition(position) { - this.scrollPosition = position; - if (this.tableNode) { - const { prefixCls } = this.props; - if (position === 'both') { - classes(this.tableNode) - .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`)) - .add(`${prefixCls}-scroll-position-left`) - .add(`${prefixCls}-scroll-position-right`); - } else { - classes(this.tableNode) - .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`)) - .add(`${prefixCls}-scroll-position-${position}`); - } - } - } - - setScrollPositionClassName() { - const node = this.refs.bodyTable; - const scrollToLeft = node.scrollLeft === 0; - const scrollToRight = 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'); - } - } - - handleWindowResize = () => { - this.syncFixedTableRowHeight(); - this.setScrollPositionClassName(); - } - - syncFixedTableRowHeight = () => { - const tableRect = this.tableNode.getBoundingClientRect(); - // If tableNode's height less than 0, suppose it is hidden and don't recalculate rowHeight. - // see: https://github.com/ant-design/ant-design/issues/4836 - if (tableRect.height !== undefined && tableRect.height <= 0) { - return; - } - const { prefixCls } = this.props; - const headRows = this.refs.headTable ? - this.refs.headTable.querySelectorAll('thead') : - this.refs.bodyTable.querySelectorAll('thead'); - const bodyRows = this.refs.bodyTable.querySelectorAll(`.${prefixCls}-row`) || []; - const fixedColumnsHeadRowsHeight = [].map.call( - headRows, row => row.getBoundingClientRect().height || 'auto' - ); - const fixedColumnsBodyRowsHeight = [].map.call( - bodyRows, row => row.getBoundingClientRect().height || 'auto' - ); - if (shallowequal(this.state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) && - shallowequal(this.state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)) { - return; - } - this.setState({ - fixedColumnsHeadRowsHeight, - fixedColumnsBodyRowsHeight, - }); - } - - resetScrollX() { - if (this.refs.headTable) { - this.refs.headTable.scrollLeft = 0; - } - if (this.refs.bodyTable) { - this.refs.bodyTable.scrollLeft = 0; - } - } - - findExpandedRow(record, index) { - const rows = this.getExpandedRows().filter(i => i === this.getRowKey(record, index)); - return rows[0]; - } - - isRowExpanded(record, index) { - return typeof this.findExpandedRow(record, index) !== 'undefined'; - } - - hasScrollX() { - const { scroll = {} } = this.props; - return 'x' in scroll; - } - - handleBodyScrollLeft = (e) => { - // Fix https://github.com/ant-design/ant-design/issues/7635 - if (e.currentTarget !== e.target) { - return; - } - const target = e.target; - const { scroll = {} } = this.props; - const { headTable, bodyTable } = this.refs; - if (target.scrollLeft !== this.lastScrollLeft && scroll.x) { - 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; - } - - handleBodyScrollTop = (e) => { - const target = e.target; - const { scroll = {} } = this.props; - const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this.refs; - if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) { - const scrollTop = target.scrollTop; - if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) { - fixedColumnsBodyLeft.scrollTop = scrollTop; - } - if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) { - fixedColumnsBodyRight.scrollTop = scrollTop; - } - if (bodyTable && target !== bodyTable) { - bodyTable.scrollTop = scrollTop; - } - } - // Remember last scrollTop for scroll direction detecting. - this.lastScrollTop = target.scrollTop; - } - - handleBodyScroll = (e) => { - this.handleBodyScrollLeft(e); - this.handleBodyScrollTop(e); - } - - handleRowHover = (isHover, key) => { - this.store.setState({ - currentHoverKey: isHover ? key : null, - }); - } - - render() { - const props = this.props; - const prefixCls = props.prefixCls; - - let className = props.prefixCls; - if (props.className) { - className += ` ${props.className}`; - } - if (props.useFixedHeader || (props.scroll && props.scroll.y)) { - className += ` ${prefixCls}-fixed-header`; - } - if (this.scrollPosition === 'both') { - className += ` ${prefixCls}-scroll-position-left ${prefixCls}-scroll-position-right`; - } else { - className += ` ${prefixCls}-scroll-position-${this.scrollPosition}`; - } - - const isTableScroll = - this.columnManager.isAnyColumnsFixed() || props.scroll.x || props.scroll.y; - - const content = [ - this.getTable({ columns: this.columnManager.groupedColumns() }), - this.getEmptyText(), - this.getFooter(), - ]; - - const scrollTable = isTableScroll - ?
{content}
- : content; - - return ( -
(this.tableNode = node)} className={className} style={props.style}> - {this.getTitle()} -
- {scrollTable} - {this.columnManager.isAnyColumnsLeftFixed() && -
- {this.getLeftFixedTable()} -
} - {this.columnManager.isAnyColumnsRightFixed() && -
- {this.getRightFixedTable()} -
} -
-
- ); - } -} diff --git a/src/TableCell.jsx b/src/TableCell.js similarity index 100% rename from src/TableCell.jsx rename to src/TableCell.js diff --git a/src/TableHeader.js b/src/TableHeader.js new file mode 100644 index 000000000..51f124fcf --- /dev/null +++ b/src/TableHeader.js @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import TableHeaderRow from './TableHeaderRow'; + +function getHeaderRows(columns, currentRow = 0, rows) { + rows = rows || []; + rows[currentRow] = rows[currentRow] || []; + + columns.forEach(column => { + if (column.rowSpan && rows.length < column.rowSpan) { + while (rows.length < column.rowSpan) { + rows.push([]); + } + } + const cell = { + key: column.key, + className: column.className || '', + children: column.title, + }; + if (column.children) { + getHeaderRows(column.children, currentRow + 1, rows); + } + if ('colSpan' in column) { + cell.colSpan = column.colSpan; + } + if ('rowSpan' in column) { + cell.rowSpan = column.rowSpan; + } + if (cell.colSpan !== 0) { + rows[currentRow].push(cell); + } + }); + return rows.filter(row => row.length > 0); +} + +export default function TableHeader(props, { table }) { + const { prefixCls, showHeader } = table.props; + const { expander, columns, fixed } = props; + + if (!showHeader) { + return null; + } + + const rows = getHeaderRows(columns); + + expander.renderExpandIndentCell(rows, fixed); + + return ( + + { + rows.map((row, index) => ( + + )) + } + + ); +} + +TableHeader.propTypes = { + fixed: PropTypes.string, + columns: PropTypes.array.isRequired, + expander: PropTypes.object.isRequired, +}; + +TableHeader.contextTypes = { + table: PropTypes.any, +}; diff --git a/src/TableHeader.jsx b/src/TableHeader.jsx deleted file mode 100644 index 35a544c75..000000000 --- a/src/TableHeader.jsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import shallowequal from 'shallowequal'; - -export default class TableHeader extends React.Component { - static propTypes = { - prefixCls: PropTypes.string, - rowStyle: PropTypes.object, - rows: PropTypes.array, - } - - shouldComponentUpdate(nextProps) { - return !shallowequal(nextProps, this.props); - } - - render() { - const { prefixCls, rowStyle, rows } = this.props; - return ( - - { - rows.map((row, index) => ( - - {row.map((cellProps, i) => )} - - )) - } - - ); - } -} diff --git a/src/TableHeaderRow.js b/src/TableHeaderRow.js new file mode 100644 index 000000000..6b2301684 --- /dev/null +++ b/src/TableHeaderRow.js @@ -0,0 +1,32 @@ +import React from 'react'; +import { connect } from 'mini-store'; + +function TableHeaderRow({ row, height }) { + const style = { height }; + + return ( + + {row.map((cell, i) => )} + + ); +} + +function getRowHeight(state, props) { + const { fixedColumnsHeadRowsHeight } = state; + const { columns, rows } = props; + const headerHeight = fixedColumnsHeadRowsHeight[0]; + + if (headerHeight && columns) { + if (headerHeight === 'auto') { + return 'auto'; + } + return headerHeight / rows.length; + } + return null; +} + +export default connect((state, props) => { + return { + height: getRowHeight(state, props), + }; +})(TableHeaderRow); diff --git a/src/TableRow.js b/src/TableRow.js new file mode 100644 index 000000000..92fea8ae2 --- /dev/null +++ b/src/TableRow.js @@ -0,0 +1,211 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'mini-store'; +import TableCell from './TableCell'; + +class TableRow extends React.Component { + static propTypes = { + onRowClick: PropTypes.func, + onRowDoubleClick: PropTypes.func, + onRowContextMenu: PropTypes.func, + onRowMouseEnter: PropTypes.func, + onRowMouseLeave: PropTypes.func, + record: PropTypes.object, + prefixCls: PropTypes.string, + onHover: PropTypes.func, + columns: PropTypes.array, + height: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]), + index: PropTypes.number, + rowKey: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.number, + ]).isRequired, + className: PropTypes.string, + indent: PropTypes.number, + indentSize: PropTypes.number, + hasExpandIcon: PropTypes.func.isRequired, + hovered: PropTypes.bool.isRequired, + visible: PropTypes.bool.isRequired, + store: PropTypes.object.isRequired, + fixed: PropTypes.oneOfType([ + PropTypes.string, + PropTypes.bool, + ]), + renderExpandIcon: PropTypes.func, + renderExpandIconCell: PropTypes.func, + } + + static defaultProps = { + onRowClick() {}, + onRowDoubleClick() {}, + onRowContextMenu() {}, + onRowMouseEnter() {}, + onRowMouseLeave() {}, + expandIconColumnIndex: 0, + expandRowByClick: false, + onHover() {}, + hasExpandIcon() {}, + renderExpandIcon() {}, + renderExpandIconCell() {}, + } + + constructor(props) { + super(props); + + this.shouldRender = props.visible; + } + + componentWillReceiveProps(nextProps) { + if (this.props.visible || (!this.props.visible && nextProps.visible)) { + this.shouldRender = true; + } + } + + onRowClick = (event) => { + const { record, index, onRowClick } = this.props; + onRowClick(record, index, event); + } + + onRowDoubleClick = (event) => { + const { record, index, onRowDoubleClick } = this.props; + onRowDoubleClick(record, index, event); + } + + onContextMenu = (event) => { + const { record, index, onRowContextMenu } = this.props; + onRowContextMenu(record, index, event); + } + + onMouseEnter = (event) => { + const { record, index, onRowMouseEnter, onHover, rowKey } = this.props; + onHover(true, rowKey); + onRowMouseEnter(record, index, event); + } + + onMouseLeave = (event) => { + const { record, index, onRowMouseLeave, onHover, rowKey } = this.props; + onHover(false, rowKey); + onRowMouseLeave(record, index, event); + } + + setHeight() { + const { store, rowKey } = this.props; + const { expandedRowsHeight } = store.getState(); + const height = this.rowRef.getBoundingClientRect().height; + expandedRowsHeight[rowKey] = height; + store.setState({ expandedRowsHeight }); + } + + saveRowRef = (node) => { + this.rowRef = node; + if (node) { + if (!this.props.fixed) { + this.setHeight(); + } + } + } + + render() { + if (!this.shouldRender) { + return null; + } + + const { + prefixCls, + columns, + record, + index, + indent, + indentSize, + visible, + height, + hovered, + hasExpandIcon, + renderExpandIcon, + renderExpandIconCell, + } = this.props; + + let { className } = this.props; + + if (hovered) { + className += ` ${prefixCls}-hover`; + } + + const cells = []; + + renderExpandIconCell(cells); + + for (let i = 0; i < columns.length; i++) { + cells.push( + + ); + } + + const style = { height }; + + if (!visible) { + style.display = 'none'; + } + + const rowClassName = + `${prefixCls} ${className} ${prefixCls}-level-${indent}`.trim(); + + return ( + + {cells} + + ); + } +} + +function getRowHeight(state, props) { + const { expandedRowsHeight, fixedColumnsBodyRowsHeight } = state; + const { fixed, index, rowKey } = props; + + if (!fixed) { + return null; + } + + if (expandedRowsHeight[rowKey]) { + return expandedRowsHeight[rowKey]; + } + + if (fixedColumnsBodyRowsHeight[index]) { + return fixedColumnsBodyRowsHeight[index]; + } + + return null; +} + +export default connect((state, props) => { + const { currentHoverKey, expandedRowKeys } = state; + const { rowKey, ancestorKeys } = props; + const visible = ancestorKeys.length === 0 || ancestorKeys.every(k => ~expandedRowKeys.indexOf(k)); + + return ({ + visible, + hovered: currentHoverKey === rowKey, + height: getRowHeight(state, props), + }); +})(TableRow); diff --git a/src/TableRow.jsx b/src/TableRow.jsx deleted file mode 100644 index 976abb852..000000000 --- a/src/TableRow.jsx +++ /dev/null @@ -1,219 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import TableCell from './TableCell'; -import ExpandIcon from './ExpandIcon'; - -export default class TableRow extends React.Component { - static propTypes = { - onDestroy: PropTypes.func, - onRowClick: PropTypes.func, - onRowDoubleClick: PropTypes.func, - onRowContextMenu: PropTypes.func, - onRowMouseEnter: PropTypes.func, - onRowMouseLeave: PropTypes.func, - record: PropTypes.object, - prefixCls: PropTypes.string, - expandIconColumnIndex: PropTypes.number, - onHover: PropTypes.func, - columns: PropTypes.array, - height: PropTypes.oneOfType([ - PropTypes.string, - PropTypes.number, - ]), - visible: PropTypes.bool, - index: PropTypes.number, - hoverKey: PropTypes.any, - expanded: PropTypes.bool, - expandable: PropTypes.any, - onExpand: PropTypes.func, - needIndentSpaced: PropTypes.bool, - className: PropTypes.string, - indent: PropTypes.number, - indentSize: PropTypes.number, - expandIconAsCell: PropTypes.bool, - expandRowByClick: PropTypes.bool, - store: PropTypes.object.isRequired, - expandedRow: PropTypes.bool, - fixed: PropTypes.bool, - rowKey: PropTypes.string, - } - - static defaultProps = { - onRowClick() {}, - onRowDoubleClick() {}, - onRowContextMenu() {}, - onRowMouseEnter() {}, - onRowMouseLeave() {}, - onDestroy() {}, - expandIconColumnIndex: 0, - expandRowByClick: false, - onHover() {}, - } - - state = { - hovered: false, - height: null, - } - - componentDidMount() { - const { store } = this.props; - this.pushHeight(); - this.pullHeight(); - this.unsubscribe = store.subscribe(() => { - this.setHover(); - this.pullHeight(); - }); - } - - componentWillUnmount() { - const { record, onDestroy, index } = this.props; - onDestroy(record, index); - if (this.unsubscribe) { - this.unsubscribe(); - } - } - - onRowClick = (event) => { - const { - record, - index, - onRowClick, - expandable, - expandRowByClick, - expanded, - onExpand, - } = this.props; - if (expandable && expandRowByClick) { - onExpand(!expanded, record, event, index); - } - onRowClick(record, index, event); - } - - onRowDoubleClick = (event) => { - const { record, index, onRowDoubleClick } = this.props; - onRowDoubleClick(record, index, event); - } - - onContextMenu = (event) => { - const { record, index, onRowContextMenu } = this.props; - onRowContextMenu(record, index, event); - } - - onMouseEnter = (event) => { - const { record, index, onRowMouseEnter, onHover, hoverKey } = this.props; - onHover(true, hoverKey); - onRowMouseEnter(record, index, event); - } - - onMouseLeave = (event) => { - const { record, index, onRowMouseLeave, onHover, hoverKey } = this.props; - onHover(false, hoverKey); - onRowMouseLeave(record, index, event); - } - - setHover() { - const { store, hoverKey } = this.props; - const { currentHoverKey } = store.getState(); - if (currentHoverKey === hoverKey) { - this.setState({ hovered: true }); - } else if (this.state.hovered === true) { - this.setState({ hovered: false }); - } - } - - - pullHeight() { - const { store, expandedRow, fixed, rowKey } = this.props; - const { expandedRowsHeight } = store.getState(); - if (expandedRow && fixed && expandedRowsHeight[rowKey]) { - this.setState({ height: expandedRowsHeight[rowKey] }); - } - } - - pushHeight() { - const { store, expandedRow, fixed, rowKey } = this.props; - if (expandedRow && !fixed) { - const { expandedRowsHeight } = store.getState(); - const height = this.trRef.getBoundingClientRect().height; - expandedRowsHeight[rowKey] = height; - store.setState({ expandedRowsHeight }); - } - } - - render() { - const { - prefixCls, columns, record, visible, index, - expandIconColumnIndex, expandIconAsCell, expanded, expandRowByClick, - expandable, onExpand, needIndentSpaced, indent, indentSize, - } = this.props; - - let { className } = this.props; - - if (this.state.hovered) { - className += ` ${prefixCls}-hover`; - } - - const cells = []; - - const expandIcon = ( - - ); - - for (let i = 0; i < columns.length; i++) { - if (expandIconAsCell && i === 0) { - cells.push( - - {expandIcon} - - ); - } - const isColumnHaveExpandIcon = (expandIconAsCell || expandRowByClick) - ? false : (i === expandIconColumnIndex); - cells.push( - - ); - } - const height = this.props.height || this.state.height; - const style = { height }; - if (!visible) { - style.display = 'none'; - } - - const rowClassName = - `${prefixCls} ${className} ${prefixCls}-level-${indent}`.trim(); - - return ( - (this.trRef = node)} - onClick={this.onRowClick} - onDoubleClick={this.onRowDoubleClick} - onMouseEnter={this.onMouseEnter} - onMouseLeave={this.onMouseLeave} - onContextMenu={this.onContextMenu} - className={rowClassName} - style={style} - > - {cells} - - ); - } -} diff --git a/src/createStore.js b/src/createStore.js deleted file mode 100644 index 883ab0f1c..000000000 --- a/src/createStore.js +++ /dev/null @@ -1,30 +0,0 @@ -export default function createStore(initialState) { - let state = initialState; - const listeners = []; - - function setState(partial) { - state = { ...state, ...partial }; - for (let i = 0; i < listeners.length; i++) { - listeners[i](); - } - } - - function getState() { - return state; - } - - function subscribe(listener) { - listeners.push(listener); - - return function unsubscribe() { - const index = listeners.indexOf(listener); - listeners.splice(index, 1); - }; - } - - return { - setState, - getState, - subscribe, - }; -} diff --git a/tests/Table.expandRow.spec.js b/tests/Table.expandRow.spec.js index b85c60627..dc69f508e 100644 --- a/tests/Table.expandRow.spec.js +++ b/tests/Table.expandRow.spec.js @@ -1,41 +1,57 @@ /* eslint-disable no-undef */ import React from 'react'; import { render, mount } from 'enzyme'; -import { renderToJson, mountToJson } from 'enzyme-to-json'; import Table from '../src'; describe('Table.expand', () => { - const columns = [ + const expandedRowRender = () =>

extra data

; + + const sampleColumns = [ { title: 'Name', dataIndex: 'name', key: 'name' }, { title: 'Age', dataIndex: 'age', key: 'age' }, ]; - const data = [ + + const sampleData = [ { key: 0, name: 'Lucy', age: 27 }, { key: 1, name: 'Jack', age: 28 }, ]; - const expandedRowRender = () =>

extra data

; + const createTable = (props) => ( ); it('renders expand row correctly', () => { const wrapper = render(createTable({ expandedRowRender })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders tree row correctly', () => { - const localData = [ + const data = [ { key: 0, name: 'Lucy', age: 27, children: [ { key: 2, name: 'Jim', age: 1 }, ] }, { key: 1, name: 'Jack', age: 28 }, ]; - const wrapper = render(createTable({ data: localData })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + const wrapper = render(createTable({ data })); + expect(wrapper).toMatchSnapshot(); + }); + + it('renders fixed column correctly', () => { + const columns = [ + { title: 'Name', dataIndex: 'name', key: 'name', fixed: true }, + { title: 'Age', dataIndex: 'age', key: 'age' }, + { title: 'Gender', dataIndex: 'gender', key: 'gender', fixed: 'right' }, + ]; + const data = [ + { key: 0, name: 'Lucy', age: 27, gender: 'F' }, + { key: 1, name: 'Jack', age: 28, gender: 'M' }, + ]; + const wrapper = render(createTable({ columns, data, expandedRowRender })); + expect(wrapper).toMatchSnapshot(); }); it('renders expand icon as cell', () => { @@ -43,7 +59,7 @@ describe('Table.expand', () => { expandedRowRender, expandIconAsCell: true, })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders expand icon to the specify column', () => { @@ -51,7 +67,7 @@ describe('Table.expand', () => { expandedRowRender, expandIconColumnIndex: 1, })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders nested data correctly', () => { @@ -71,7 +87,7 @@ describe('Table.expand', () => { }, ]; const wrapper = render(createTable({ data: localData })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('expand all rows by default', () => { @@ -79,7 +95,7 @@ describe('Table.expand', () => { expandedRowRender, defaultExpandAllRows: true, })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('expand rows by defaultExpandedRowKeys', () => { @@ -87,7 +103,7 @@ describe('Table.expand', () => { expandedRowRender, defaultExpandedRowKeys: [1], })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('controlled by expandedRowKeys', () => { @@ -95,9 +111,9 @@ describe('Table.expand', () => { expandedRowRender, expandedRowKeys: [0], })); - expect(mountToJson(wrapper)).toMatchSnapshot(); + expect(wrapper.render()).toMatchSnapshot(); wrapper.setProps({ expandedRowKeys: [1] }); - expect(mountToJson(wrapper)).toMatchSnapshot(); + expect(wrapper.render()).toMatchSnapshot(); }); it('renders expend row class correctly', () => { @@ -107,8 +123,8 @@ describe('Table.expand', () => { expandedRowKeys: [0], expandedRowClassName, })); - expect(renderToJson(wrapper)).toMatchSnapshot(); - expect(expandedRowClassName).toBeCalledWith(data[0], 0, 0); + expect(wrapper).toMatchSnapshot(); + expect(expandedRowClassName).toBeCalledWith(sampleData[0], 0, 0); }); it('fires expand change event', () => { @@ -118,9 +134,9 @@ describe('Table.expand', () => { onExpand, })); wrapper.find('ExpandIcon').first().simulate('click'); - expect(onExpand).toBeCalledWith(true, data[0]); + expect(onExpand).toBeCalledWith(true, sampleData[0]); wrapper.find('ExpandIcon').first().simulate('click'); - expect(onExpand).toBeCalledWith(false, data[0]); + expect(onExpand).toBeCalledWith(false, sampleData[0]); }); it('fires onExpandedRowsChange event', () => { @@ -149,6 +165,6 @@ describe('Table.expand', () => { it('expand row by click', () => { const wrapper = mount(createTable({ expandedRowRender })); wrapper.find('ExpandIcon').first().simulate('click'); - expect(mountToJson(wrapper)).toMatchSnapshot(); + expect(wrapper.render()).toMatchSnapshot(); }); }); diff --git a/tests/Table.fixedColumns.spec.js b/tests/Table.fixedColumns.spec.js index 22d851507..f3c8deb64 100644 --- a/tests/Table.fixedColumns.spec.js +++ b/tests/Table.fixedColumns.spec.js @@ -1,7 +1,6 @@ /* eslint-disable no-undef */ import React from 'react'; import { render, mount } from 'enzyme'; -import { renderToJson } from 'enzyme-to-json'; import Table from '../src'; describe('Table.fixedColumns', () => { @@ -47,7 +46,7 @@ describe('Table.fixedColumns', () => { scroll={{ x: 1200 }} /> ); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('toggles hover class when user hovering', () => { diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 2729e5372..22aab3717 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -1,7 +1,6 @@ /* eslint-disable no-undef, no-console */ import React from 'react'; import { render, mount } from 'enzyme'; -import { renderToJson } from 'enzyme-to-json'; import Table from '../src'; describe('Table', () => { @@ -28,34 +27,34 @@ describe('Table', () => { prefixCls: 'test-prefix', className: 'test-class-name', })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders empty text correctly', () => { const wrapper1 = render(createTable({ data: [], emptyText: 'No data' })); const wrapper2 = render(createTable({ data: [], emptyText: () => 'No data' })); - expect(renderToJson(wrapper1)).toMatchSnapshot(); - expect(renderToJson(wrapper2)).toMatchSnapshot(); + expect(wrapper1).toMatchSnapshot(); + expect(wrapper2).toMatchSnapshot(); }); it('renders without header', () => { const wrapper = render(createTable({ showHeader: false })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders fixed header correctly', () => { const wrapper = render(createTable({ useFixedHeader: true })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders title correctly', () => { const wrapper = render(createTable({ title: () =>

title

})); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders footer correctly', () => { const wrapper = render(createTable({ footer: () =>

footer

})); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders table body to the wrapper', () => { @@ -65,10 +64,10 @@ describe('Table', () => { ); const wrapper = render(createTable({ getBodyWrapper })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); - it('sets row refs', () => { + xit('sets row refs', () => { const wrapper = mount(createTable({ rowRef: (record) => record.key })); expect(wrapper.instance().refs.key0).toBe(wrapper.find('TableRow').at(0).instance()); expect(wrapper.instance().refs.key1).toBe(wrapper.find('TableRow').at(1).instance()); @@ -77,37 +76,37 @@ describe('Table', () => { describe('rowKey', () => { it('uses record.key', () => { const wrapper = mount(createTable()); - expect(wrapper.find('TableRow').at(0).prop('hoverKey')).toBe('key0'); - expect(wrapper.find('TableRow').at(1).prop('hoverKey')).toBe('key1'); + expect(wrapper.find('TableRow').at(0).prop('rowKey')).toBe('key0'); + expect(wrapper.find('TableRow').at(1).prop('rowKey')).toBe('key1'); }); it('sets by rowKey', () => { const wrapper = mount(createTable({ rowKey: 'name' })); - expect(wrapper.find('TableRow').at(0).prop('hoverKey')).toBe('Lucy'); - expect(wrapper.find('TableRow').at(1).prop('hoverKey')).toBe('Jack'); + expect(wrapper.find('TableRow').at(0).prop('rowKey')).toBe('Lucy'); + expect(wrapper.find('TableRow').at(1).prop('rowKey')).toBe('Jack'); }); it('sets by rowKey function', () => { const wrapper = mount(createTable({ rowKey: (record) => `${record.key}1` })); - expect(wrapper.find('TableRow').at(0).prop('hoverKey')).toBe('key01'); - expect(wrapper.find('TableRow').at(1).prop('hoverKey')).toBe('key11'); + expect(wrapper.find('TableRow').at(0).prop('rowKey')).toBe('key01'); + expect(wrapper.find('TableRow').at(1).prop('rowKey')).toBe('key11'); }); }); describe('scroll', () => { it('renders scroll.x is true', () => { const wrapper = render(createTable({ scroll: { x: true } })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders scroll.x is a number', () => { const wrapper = render(createTable({ scroll: { x: 200 } })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders scroll.y is a number', () => { const wrapper = render(createTable({ scroll: { y: 200 } })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('fire scroll event', () => { @@ -227,7 +226,7 @@ describe('Table', () => { }, ]; const wrapper = render(createTable({ columns })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders custom cell correctly', () => { @@ -240,7 +239,7 @@ describe('Table', () => { }, ]; const wrapper = render(createTable({ columns })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('fires cell click event', () => { @@ -286,7 +285,7 @@ describe('Table', () => { { key: 'key1', name: { first: 'Terry', last: 'Garner' } }, ]; const wrapper = render(createTable({ columns, data: localData })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); @@ -339,7 +338,7 @@ describe('Table', () => { { key: 'key1', firstName: 'Terry', lastName: 'Garner' }, ]; const wrapper = render(createTable({ columns, data: localData })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders rowSpan correctly', () => { @@ -372,7 +371,7 @@ describe('Table', () => { { key: 'key1', firstName: 'Terry', lastName: 'Garner' }, ]; const wrapper = render(createTable({ columns, data: localData })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('shows error if no rowKey specify', () => { @@ -432,13 +431,13 @@ describe('Table', () => { const wrapper = render(createTable({ rowClassName: 'test-row-class-name-asStr', })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); it('renders correctly RowClassName as function', () => { const wrapper = render(createTable({ rowClassName: () => 'test-row-class-name-asFn', })); - expect(renderToJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/tests/__snapshots__/Table.expandRow.spec.js.snap b/tests/__snapshots__/Table.expandRow.spec.js.snap index edadcc159..0317a57d3 100644 --- a/tests/__snapshots__/Table.expandRow.spec.js.snap +++ b/tests/__snapshots__/Table.expandRow.spec.js.snap @@ -1,1185 +1,210 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Table.expand controlled by expandedRowKeys 1`] = ` -
-
-
+ + + + - - - - - + + + + + + - - - - - - - - + + Lucy + + + + - - - - } - indent={0} - indentSize={15} - index={0} - key="name" - prefixCls="rc-table-row" - record={ - Object { - "age": 27, - "key": 0, - "name": "Lucy", - } - } - > - - - - - - - - + extra data +

+ + +
+ - - } - indent={1} - indentSize={undefined} - index={undefined} - key="extra-row" - prefixCls="rc-table-expanded-row" - record={undefined} - > - - - - - + + Jack + + - - } - indent={0} - indentSize={15} - index={1} - key="name" - prefixCls="rc-table-row" - record={ - Object { - "age": 28, - "key": 1, - "name": "Jack", - } - } - > - - - - - - - - -
+ Name + + Age +
- Name - - Age -
+ 27 +
- - - - - Lucy - - 27 -
-
- - -

- extra data -

-
-
- - - - - Jack - - 28 -
-
+ 28 + + + + - + `; exports[`Table.expand controlled by expandedRowKeys 2`] = ` -
-
-
+ + + + - - - - - + + + + + + - - - - - - - - + + Lucy + + + + - - - - } - indent={0} - indentSize={15} - index={0} - key="name" - prefixCls="rc-table-row" - record={ - Object { - "age": 27, - "key": 0, - "name": "Lucy", - } - } - > - - - - - - - - + extra data +

+ + +
+ - - } - indent={0} - indentSize={15} - index={1} - key="name" - prefixCls="rc-table-row" - record={ - Object { - "age": 28, - "key": 1, - "name": "Jack", - } - } - > - - - - - - - - + + Jack + + - - } - indent={1} - indentSize={undefined} - index={undefined} - key="extra-row" - prefixCls="rc-table-expanded-row" - record={undefined} - > - - - - - -
+ Name + + Age +
- Name - - Age -
+ 27 +
- - - - - Lucy - - 27 -
-
- - - - - Jack - - 28 -
-
- - -

- extra data -

-
- + 28 + + + + +

+ extra data +

+ + + + - + `; exports[`Table.expand expand all rows by default 1`] = ` @@ -1246,10 +271,6 @@ exports[`Table.expand expand all rows by default 1`] = ` class="" colspan="2" > -

extra data

@@ -1283,10 +304,6 @@ exports[`Table.expand expand all rows by default 1`] = ` class="" colspan="2" > -

extra data

@@ -1300,589 +317,101 @@ exports[`Table.expand expand all rows by default 1`] = ` `; exports[`Table.expand expand row by click 1`] = ` -
-
-
+ + + + - - - - - + + + + + + - - - - - - - - + + Lucy + + + + - - - - } - indent={0} - indentSize={15} - index={0} - key="name" - prefixCls="rc-table-row" - record={ - Object { - "age": 27, - "key": 0, - "name": "Lucy", - } - } - > - - - - - - - - + extra data +

+ + +
+ - - } - indent={1} - indentSize={undefined} - index={undefined} - key="extra-row" - prefixCls="rc-table-expanded-row" - record={undefined} - > - - - - - + + Jack + + - - } - indent={0} - indentSize={15} - index={1} - key="name" - prefixCls="rc-table-row" - record={ - Object { - "age": 28, - "key": 1, - "name": "Jack", - } - } - > - - - - - - - - -
+ Name + + Age +
- Name - - Age -
+ 27 +
- - - - - Lucy - - 27 -
-
- - -

- extra data -

-
-
- - - - - Jack - - 28 -
- + 28 + + + + - + `; exports[`Table.expand expand rows by defaultExpandedRowKeys 1`] = ` @@ -1970,10 +499,6 @@ exports[`Table.expand expand rows by defaultExpandedRowKeys 1`] = ` class="" colspan="2" > -

extra data

@@ -2313,10 +838,6 @@ exports[`Table.expand renders expend row class correctly 1`] = ` class="" colspan="2" > -

extra data

@@ -2350,6 +871,231 @@ exports[`Table.expand renders expend row class correctly 1`] = ` `; +exports[`Table.expand renders fixed column correctly 1`] = ` +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + +
+ Name + + Age + + Gender +
+ + + Lucy + + 27 + + F +
+ + + Jack + + 28 + + M +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+ Name +
+ + + Lucy +
+ + + Jack +
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+ Gender +
+ F +
+ M +
+
+
+
+
+
+`; + exports[`Table.expand renders nested data correctly 1`] = `
- - - - - Jim - - - 2 - - @@ -2514,28 +1238,6 @@ exports[`Table.expand renders tree row correctly 1`] = ` 27 - - - - - Jim - - - 1 - - diff --git a/tests/createStore.spec.js b/tests/createStore.spec.js deleted file mode 100644 index 463897305..000000000 --- a/tests/createStore.spec.js +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint-disable no-undef */ -import createStore from '../src/createStore'; - -describe('createStore', () => { - it('create with initial state', () => { - const store = createStore({ a: 1 }); - expect(store.getState()).toEqual({ a: 1 }); - }); - - it('setState', () => { - const store = createStore({ a: 1 }); - store.setState({ a: 2 }); - expect(store.getState()).toEqual({ a: 2 }); - }); - - it('subscribe', () => { - const store = createStore({ a: 1 }); - const unsubscribe = store.subscribe(() => { - expect(store.getState()).toEqual({ a: 2 }); - }); - store.setState({ a: 2 }); - unsubscribe(); - store.setState({ a: 3 }); - }); -}); diff --git a/tests/setup.js b/tests/setup.js index 3df8ba4d3..684b8a6fb 100644 --- a/tests/setup.js +++ b/tests/setup.js @@ -1,4 +1,4 @@ -global.requestAnimationFrame = global.requestAnimationFrame || function (cb) { // eslint-disable-line +global.requestAnimationFrame = global.requestAnimationFrame || function requestAnimationFrame(cb) { return setTimeout(cb, 0); };