diff --git a/src/Table.tsx b/src/Table.tsx index 1342d172d..ad1e623a0 100644 --- a/src/Table.tsx +++ b/src/Table.tsx @@ -76,6 +76,7 @@ import FixedHolder from './FixedHolder'; import type { SummaryProps } from './Footer/Summary'; import Summary from './Footer/Summary'; import StickyContext from './context/StickyContext'; +import { EXPAND_COLUMN } from './constant'; // Used for conditions cache const EMPTY_DATA = []; @@ -106,7 +107,8 @@ const MemoTableContent = React.memo( }, ); -export interface TableProps extends LegacyExpandableProps { +export interface TableProps + extends Omit, 'showExpandColumn'> { prefixCls?: string; className?: string; style?: React.CSSProperties; @@ -849,6 +851,8 @@ function Table(props: TableProps( children: React.ReactNode, @@ -144,11 +145,41 @@ function useColumns( [columns, children], ); - // Add expand column + // ========================== Expand ========================== const withExpandColumns = React.useMemo>(() => { if (expandable) { - const expandColIndex = expandIconColumnIndex || 0; - const prevColumn = baseColumns[expandColIndex]; + let cloneColumns = baseColumns.slice(); + + // >>> Warning if use `expandIconColumnIndex` + if (process.env.NODE_ENV !== 'production' && expandIconColumnIndex >= 0) { + warning( + false, + '`expandIconColumnIndex` is deprecated. Please use `Table.EXPAND_COLUMN` in `columns` instead.', + ); + } + + // >>> Insert expand column if not exist + if (!cloneColumns.includes(EXPAND_COLUMN)) { + const expandColIndex = expandIconColumnIndex || 0; + if (expandColIndex >= 0) { + cloneColumns.splice(expandColIndex, 0, EXPAND_COLUMN); + } + } + + // >>> Deduplicate additional expand column + if ( + process.env.NODE_ENV !== 'production' && + cloneColumns.filter(c => c === EXPAND_COLUMN).length > 1 + ) { + warning(false, 'There exist more than one `EXPAND_COLUMN` in `columns`.'); + } + const expandColumnIndex = cloneColumns.indexOf(EXPAND_COLUMN); + cloneColumns = cloneColumns.filter( + (column, index) => column !== EXPAND_COLUMN || index === expandColumnIndex, + ); + + // >>> Check if expand column need to fixed + const prevColumn = baseColumns[expandColumnIndex]; let fixedColumn: FixedType | null; if ((fixed === 'left' || fixed) && !expandIconColumnIndex) { @@ -159,6 +190,7 @@ function useColumns( fixedColumn = prevColumn ? prevColumn.fixed : null; } + // >>> Create expandable column const expandColumn = { [INTERNAL_COL_DEFINE]: { className: `${prefixCls}-expand-icon-col`, @@ -187,16 +219,12 @@ function useColumns( }, }; - // Insert expand column in the target position - const cloneColumns = baseColumns.slice(); - if (expandColIndex >= 0) { - cloneColumns.splice(expandColIndex, 0, expandColumn); - } - return cloneColumns; + return cloneColumns.map(col => (col === EXPAND_COLUMN ? expandColumn : col)); } return baseColumns; }, [expandable, baseColumns, getRowKey, expandedKeys, expandIcon, direction]); + // ========================= Transform ======================== const mergedColumns = React.useMemo(() => { let finalColumns = withExpandColumns; if (transformColumns) { @@ -214,6 +242,7 @@ function useColumns( return finalColumns; }, [transformColumns, withExpandColumns, direction]); + // ========================== Flatten ========================= const flattenColumns = React.useMemo(() => { if (direction === 'rtl') { return revertForRtl(flatColumns(mergedColumns)); diff --git a/src/interface.ts b/src/interface.ts index 25575ed7e..e762b10e9 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -206,7 +206,9 @@ export interface ExpandableConfig { onExpandedRowsChange?: (expandedKeys: readonly Key[]) => void; defaultExpandAllRows?: boolean; indentSize?: number; + /** @deprecated Please use `EXPAND_COLUMN` in `columns` directly */ expandIconColumnIndex?: number; + showExpandColumn?: boolean; expandedRowClassName?: RowClassName; childrenColumnName?: string; rowExpandable?: (record: RecordType) => boolean; diff --git a/src/utils/legacyUtil.ts b/src/utils/legacyUtil.ts index ec48cab53..109574a0f 100644 --- a/src/utils/legacyUtil.ts +++ b/src/utils/legacyUtil.ts @@ -9,32 +9,40 @@ export function getExpandableProps( }, ): ExpandableConfig { const { expandable, ...legacyExpandableConfig } = props; + let config: ExpandableConfig; if ('expandable' in props) { - return { + config = { ...legacyExpandableConfig, ...expandable, }; + } else { + if ( + process.env.NODE_ENV !== 'production' && + [ + 'indentSize', + 'expandedRowKeys', + 'defaultExpandedRowKeys', + 'defaultExpandAllRows', + 'expandedRowRender', + 'expandRowByClick', + 'expandIcon', + 'onExpand', + 'onExpandedRowsChange', + 'expandedRowClassName', + 'expandIconColumnIndex', + 'showExpandColumn', + ].some(prop => prop in props) + ) { + warning(false, 'expanded related props have been moved into `expandable`.'); + } + + config = legacyExpandableConfig; } - if ( - process.env.NODE_ENV !== 'production' && - [ - 'indentSize', - 'expandedRowKeys', - 'defaultExpandedRowKeys', - 'defaultExpandAllRows', - 'expandedRowRender', - 'expandRowByClick', - 'expandIcon', - 'onExpand', - 'onExpandedRowsChange', - 'expandedRowClassName', - 'expandIconColumnIndex', - ].some(prop => prop in props) - ) { - warning(false, 'expanded related props have been moved into `expandable`.'); + if (config.showExpandColumn === false) { + config.expandIconColumnIndex = -1; } - return legacyExpandableConfig; + return config; } diff --git a/tests/ExpandRow.spec.js b/tests/ExpandRow.spec.js index 6dffdbfe1..1e31cb120 100644 --- a/tests/ExpandRow.spec.js +++ b/tests/ExpandRow.spec.js @@ -200,31 +200,95 @@ describe('Table.Expand', () => { expect(wrapper2.find('.rc-table-has-fix-right').length).toBe(0); }); - it('renders expand icon to the specify column', () => { - const wrapper = mount( - createTable({ - expandable: { - expandedRowRender, - expandIconColumnIndex: 1, - }, - }), - ); - expect( - wrapper.find('tbody tr td').at(1).hasClass('rc-table-row-expand-icon-cell'), - ).toBeTruthy(); + describe('config expand column index', () => { + it('renders expand icon to the specify column', () => { + resetWarned(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + const wrapper = mount( + createTable({ + expandable: { + expandedRowRender, + expandIconColumnIndex: 1, + }, + }), + ); + expect( + wrapper.find('tbody tr td').at(1).hasClass('rc-table-row-expand-icon-cell'), + ).toBeTruthy(); + + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: `expandIconColumnIndex` is deprecated. Please use `Table.EXPAND_COLUMN` in `columns` instead.', + ); + errorSpy.mockRestore(); + }); + + it('order with EXPAND_COLUMN', () => { + const wrapper = mount( + createTable({ + columns: [...sampleColumns, Table.EXPAND_COLUMN], + expandable: { + expandedRowRender, + }, + }), + ); + + expect( + wrapper.find('tbody tr td').at(2).hasClass('rc-table-row-expand-icon-cell'), + ).toBeTruthy(); + }); + + it('de-duplicate of EXPAND_COLUMN', () => { + resetWarned(); + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + const wrapper = mount( + createTable({ + columns: [Table.EXPAND_COLUMN, ...sampleColumns, Table.EXPAND_COLUMN], + expandable: { + expandedRowRender, + }, + }), + ); + + expect( + wrapper.find('tbody tr td').at(0).hasClass('rc-table-row-expand-icon-cell'), + ).toBeTruthy(); + expect(wrapper.find('tbody tr').first().find('td')).toHaveLength(3); + + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: There exist more than one `EXPAND_COLUMN` in `columns`.', + ); + + errorSpy.mockRestore(); + }); }); - // https://github.com/ant-design/ant-design/issues/24129 - it('should not render expand icon column when expandIconColumnIndex is negative', () => { - const wrapper = mount( - createTable({ - expandable: { - expandedRowRender, - expandIconColumnIndex: -1, - }, - }), - ); - expect(wrapper.find('.rc-table-row-expand-icon-cell').length).toBe(0); + describe('hide expandColumn', () => { + // https://github.com/ant-design/ant-design/issues/24129 + it('should not render expand icon column when expandIconColumnIndex is negative', () => { + const wrapper = mount( + createTable({ + expandable: { + expandedRowRender, + expandIconColumnIndex: -1, + }, + }), + ); + expect(wrapper.find('.rc-table-row-expand-icon-cell').length).toBe(0); + }); + + it('showExpandColumn = false', () => { + const wrapper = mount( + createTable({ + expandable: { + expandedRowRender, + showExpandColumn: false, + }, + }), + ); + expect(wrapper.find('.rc-table-row-expand-icon-cell').length).toBe(0); + }); }); it('renders a custom icon', () => { diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 015ff263f..f1019d4c2 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -938,6 +938,9 @@ describe('Table.Basic', () => { const wrapper = mount(createTable()); wrapper.find('tbody td').first().simulate('mouseEnter'); expect(wrapper.exists('.rc-table-cell-row-hover')).toBeTruthy(); + + wrapper.find('tbody td').first().simulate('mouseLeave'); + expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); }); it('skip when config should cell update', () => {