|
|
|
import * as React from 'react';
|
|
|
|
import classNames from 'classnames';
|
|
|
|
import RcTable from 'rc-table';
|
|
|
|
import { TableProps as RcTableProps, INTERNAL_HOOKS } from 'rc-table/lib/Table';
|
|
|
|
import Spin, { SpinProps } from '../spin';
|
|
|
|
import Pagination, { PaginationConfig } from '../pagination';
|
|
|
|
import { ConfigContext } from '../config-provider/context';
|
|
|
|
import usePagination, { DEFAULT_PAGE_SIZE, getPaginationParam } from './hooks/usePagination';
|
|
|
|
import useLazyKVMap from './hooks/useLazyKVMap';
|
|
|
|
import {
|
|
|
|
TableRowSelection,
|
|
|
|
GetRowKey,
|
|
|
|
ColumnsType,
|
|
|
|
TableCurrentDataSource,
|
|
|
|
SorterResult,
|
|
|
|
Key,
|
|
|
|
GetPopupContainer,
|
|
|
|
ExpandableConfig,
|
|
|
|
ExpandType,
|
|
|
|
TablePaginationConfig,
|
|
|
|
SortOrder,
|
|
|
|
TableLocale,
|
|
|
|
} from './interface';
|
|
|
|
import useSelection, { SELECTION_ALL, SELECTION_INVERT } from './hooks/useSelection';
|
|
|
|
import useSorter, { getSortData, SortState } from './hooks/useSorter';
|
|
|
|
import useFilter, { getFilterData, FilterState } from './hooks/useFilter';
|
|
|
|
import useTitleColumns from './hooks/useTitleColumns';
|
|
|
|
import renderExpandIcon from './ExpandIcon';
|
|
|
|
import scrollTo from '../_util/scrollTo';
|
|
|
|
import defaultLocale from '../locale/en_US';
|
|
|
|
import SizeContext, { SizeType } from '../config-provider/SizeContext';
|
|
|
|
import Column from './Column';
|
|
|
|
import ColumnGroup from './ColumnGroup';
|
|
|
|
|
|
|
|
export { ColumnsType, TablePaginationConfig };
|
|
|
|
|
|
|
|
const EMPTY_LIST: any[] = [];
|
|
|
|
|
|
|
|
interface ChangeEventInfo<RecordType> {
|
|
|
|
pagination: {
|
|
|
|
current?: number;
|
|
|
|
pageSize?: number;
|
|
|
|
total?: number;
|
|
|
|
};
|
|
|
|
filters: Record<string, Key[] | null>;
|
|
|
|
sorter: SorterResult<RecordType> | SorterResult<RecordType>[];
|
|
|
|
|
|
|
|
filterStates: FilterState<RecordType>[];
|
|
|
|
sorterStates: SortState<RecordType>[];
|
|
|
|
|
|
|
|
resetPagination: Function;
|
|
|
|
}
|
|
|
|
|
|
|
|
export interface TableProps<RecordType>
|
|
|
|
extends Omit<
|
|
|
|
RcTableProps<RecordType>,
|
|
|
|
| 'transformColumns'
|
|
|
|
| 'internalHooks'
|
|
|
|
| 'internalRefs'
|
|
|
|
| 'data'
|
|
|
|
| 'columns'
|
|
|
|
| 'expandIconColumnIndex'
|
|
|
|
| 'scroll'
|
|
|
|
> {
|
|
|
|
dropdownPrefixCls?: string;
|
|
|
|
dataSource?: RcTableProps<RecordType>['data'];
|
|
|
|
columns?: ColumnsType<RecordType>;
|
|
|
|
pagination?: false | TablePaginationConfig;
|
|
|
|
loading?: boolean | SpinProps;
|
|
|
|
size?: SizeType;
|
|
|
|
bordered?: boolean;
|
|
|
|
locale?: TableLocale;
|
|
|
|
|
|
|
|
onChange?: (
|
|
|
|
pagination: PaginationConfig,
|
|
|
|
filters: Record<string, Key[] | null>,
|
|
|
|
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
|
|
|
|
extra: TableCurrentDataSource<RecordType>,
|
|
|
|
) => void;
|
|
|
|
rowSelection?: TableRowSelection<RecordType>;
|
|
|
|
|
|
|
|
getPopupContainer?: GetPopupContainer;
|
|
|
|
scroll?: RcTableProps<RecordType>['scroll'] & {
|
|
|
|
scrollToFirstRowOnChange?: boolean;
|
|
|
|
};
|
|
|
|
sortDirections?: SortOrder[];
|
|
|
|
}
|
|
|
|
|
|
|
|
function Table<RecordType extends object = any>(props: TableProps<RecordType>) {
|
|
|
|
const {
|
|
|
|
prefixCls: customizePrefixCls,
|
|
|
|
className,
|
|
|
|
size: customizeSize,
|
|
|
|
bordered,
|
|
|
|
dropdownPrefixCls,
|
|
|
|
dataSource,
|
|
|
|
pagination,
|
|
|
|
rowSelection,
|
|
|
|
rowKey,
|
|
|
|
rowClassName,
|
|
|
|
columns,
|
|
|
|
children,
|
|
|
|
onChange,
|
|
|
|
getPopupContainer,
|
|
|
|
loading,
|
|
|
|
expandIcon,
|
|
|
|
expandable,
|
|
|
|
expandedRowRender,
|
|
|
|
indentSize,
|
|
|
|
childrenColumnName = 'children',
|
|
|
|
scroll,
|
|
|
|
sortDirections,
|
|
|
|
locale,
|
|
|
|
} = props;
|
|
|
|
const size = React.useContext(SizeContext);
|
|
|
|
const { locale: contextLocale = defaultLocale, renderEmpty, direction } = React.useContext(
|
|
|
|
ConfigContext,
|
|
|
|
);
|
|
|
|
const mergedSize = customizeSize || size;
|
|
|
|
const tableLocale = locale || contextLocale.Table;
|
|
|
|
const rawData: RecordType[] = dataSource || EMPTY_LIST;
|
|
|
|
|
|
|
|
const { getPrefixCls } = React.useContext(ConfigContext);
|
|
|
|
const prefixCls = getPrefixCls('table', customizePrefixCls);
|
|
|
|
|
|
|
|
const expandType: ExpandType = React.useMemo<ExpandType>(() => {
|
|
|
|
if (rawData.some(item => (item as any)[childrenColumnName])) {
|
|
|
|
return 'nest';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (expandedRowRender || (expandable && expandable.expandedRowRender)) {
|
|
|
|
return 'row';
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}, [rawData]);
|
|
|
|
|
|
|
|
const internalRefs = {
|
|
|
|
body: React.useRef<HTMLDivElement>(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// ============================ RowKey ============================
|
|
|
|
const getRowKey = React.useMemo<GetRowKey<RecordType>>(() => {
|
|
|
|
if (typeof rowKey === 'function') {
|
|
|
|
return rowKey;
|
|
|
|
}
|
|
|
|
|
|
|
|
return (record: RecordType) => (record as any)[rowKey as string];
|
|
|
|
}, [rowKey]);
|
|
|
|
|
|
|
|
const [getRecordByKey] = useLazyKVMap(rawData, childrenColumnName, getRowKey);
|
|
|
|
|
|
|
|
// ============================ Events =============================
|
|
|
|
const changeEventInfo: Partial<ChangeEventInfo<RecordType>> = {};
|
|
|
|
|
|
|
|
const triggerOnChange = (info: Partial<ChangeEventInfo<RecordType>>, reset: boolean = false) => {
|
|
|
|
const changeInfo = {
|
|
|
|
...changeEventInfo,
|
|
|
|
...info,
|
|
|
|
};
|
|
|
|
|
|
|
|
if (reset) {
|
|
|
|
changeEventInfo.resetPagination!();
|
|
|
|
|
|
|
|
// Reset event param
|
|
|
|
if (changeInfo.pagination!.current) {
|
|
|
|
changeInfo.pagination!.current = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Trigger pagination events
|
|
|
|
if (pagination && pagination.onChange) {
|
|
|
|
pagination.onChange(1, changeInfo.pagination!.pageSize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (scroll && scroll.scrollToFirstRowOnChange !== false && internalRefs.body.current) {
|
|
|
|
scrollTo(0, {
|
|
|
|
getContainer: () => internalRefs.body.current!,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (onChange) {
|
|
|
|
onChange(changeInfo.pagination!, changeInfo.filters!, changeInfo.sorter!, {
|
|
|
|
currentDataSource: getFilterData(
|
|
|
|
getSortData(rawData, changeInfo.sorterStates!, childrenColumnName),
|
|
|
|
changeInfo.filterStates!,
|
|
|
|
),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Controlled state in `columns` is not a good idea that makes too many code (1000+ line?)
|
|
|
|
* to read state out and then put it back to title render.
|
|
|
|
* Move these code into `hooks` but still too complex.
|
|
|
|
* We should provides Table props like `sorter` & `filter` to handle control in next big version.
|
|
|
|
*/
|
|
|
|
|
|
|
|
// ============================ Sorter =============================
|
|
|
|
const onSorterChange = (
|
|
|
|
sorter: SorterResult<RecordType> | SorterResult<RecordType>[],
|
|
|
|
sorterStates: SortState<RecordType>[],
|
|
|
|
) => {
|
|
|
|
triggerOnChange(
|
|
|
|
{
|
|
|
|
sorter,
|
|
|
|
sorterStates,
|
|
|
|
},
|
|
|
|
false,
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const [transformSorterColumns, sortStates, sorterTitleProps, getSorters] = useSorter<RecordType>({
|
|
|
|
prefixCls,
|
|
|
|
columns,
|
|
|
|
children,
|
|
|
|
onSorterChange,
|
|
|
|
sortDirections: sortDirections || ['ascend', 'descend'],
|
|