2017-11-17 14:38:54 +08:00
import * as React from 'react' ;
2016-06-19 10:56:42 +08:00
import classNames from 'classnames' ;
2020-03-07 23:17:28 +08:00
import omit from 'omit.js' ;
2020-01-06 17:28:28 +08:00
import RcTable from 'rc-table' ;
2019-11-15 14:35:25 +08:00
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' ;
2017-11-21 13:48:37 +08:00
import {
2019-11-15 14:35:25 +08:00
TableRowSelection ,
GetRowKey ,
ColumnsType ,
TableCurrentDataSource ,
SorterResult ,
Key ,
GetPopupContainer ,
ExpandableConfig ,
ExpandType ,
TablePaginationConfig ,
2019-01-08 19:38:45 +08:00
SortOrder ,
2019-11-15 14:35:25 +08:00
TableLocale ,
2017-11-21 13:48:37 +08:00
} from './interface' ;
2019-11-15 14:35:25 +08:00
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' ;
2020-01-03 13:38:16 +08:00
import SizeContext , { SizeType } from '../config-provider/SizeContext' ;
2020-01-06 17:28:28 +08:00
import Column from './Column' ;
import ColumnGroup from './ColumnGroup' ;
2020-04-10 14:13:14 +08:00
import warning from '../_util/warning' ;
2016-06-19 10:56:42 +08:00
2019-11-17 17:57:35 +08:00
export { ColumnsType , TablePaginationConfig } ;
2019-11-15 14:35:25 +08:00
const EMPTY_LIST : any [ ] = [ ] ;
2017-03-14 19:51:28 +08:00
2019-11-15 14:35:25 +08:00
interface ChangeEventInfo < RecordType > {
pagination : {
current? : number ;
pageSize? : number ;
total? : number ;
2019-08-14 19:17:23 +08:00
} ;
2019-11-15 14:35:25 +08:00
filters : Record < string , Key [ ] | null > ;
sorter : SorterResult < RecordType > | SorterResult < RecordType > [ ] ;
2019-09-28 14:14:44 +08:00
2019-11-15 14:35:25 +08:00
filterStates : FilterState < RecordType > [ ] ;
sorterStates : SortState < RecordType > [ ] ;
2019-08-05 18:38:10 +08:00
2019-11-15 14:35:25 +08:00
resetPagination : Function ;
2019-08-28 17:39:38 +08:00
}
2016-11-22 10:11:12 +08:00
2019-11-15 14:35:25 +08:00
export interface TableProps < RecordType >
extends Omit <
RcTableProps < RecordType > ,
2020-03-06 00:58:26 +08:00
'transformColumns' | 'internalHooks' | 'internalRefs' | 'data' | 'columns' | 'scroll'
2019-11-15 14:35:25 +08:00
> {
dropdownPrefixCls? : string ;
dataSource? : RcTableProps < RecordType > [ 'data' ] ;
2019-11-19 10:03:03 +08:00
columns? : ColumnsType < RecordType > ;
2019-11-15 14:35:25 +08:00
pagination? : false | TablePaginationConfig ;
loading? : boolean | SpinProps ;
2020-01-03 13:38:16 +08:00
size? : SizeType ;
2019-11-15 14:35:25 +08:00
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 ;
2016-07-13 11:14:24 +08:00
} ;
2019-11-15 14:35:25 +08:00
sortDirections? : SortOrder [ ] ;
2020-02-29 22:01:13 +08:00
showSorterTooltip? : boolean ;
2019-11-15 14:35:25 +08:00
}
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
function Table < RecordType extends object = any > ( props : TableProps < RecordType > ) {
const {
prefixCls : customizePrefixCls ,
className ,
2020-03-07 23:17:28 +08:00
style ,
2020-01-03 13:38:16 +08:00
size : customizeSize ,
2019-11-15 14:35:25 +08:00
bordered ,
2020-03-11 23:58:32 +08:00
dropdownPrefixCls : customizeDropdownPrefixCls ,
2019-11-15 14:35:25 +08:00
dataSource ,
pagination ,
rowSelection ,
rowKey ,
rowClassName ,
columns ,
2020-03-01 13:11:28 +08:00
children ,
2019-11-15 14:35:25 +08:00
onChange ,
getPopupContainer ,
loading ,
expandIcon ,
expandable ,
expandedRowRender ,
2020-03-06 00:58:26 +08:00
expandIconColumnIndex ,
2019-11-15 14:35:25 +08:00
indentSize ,
childrenColumnName = 'children' ,
scroll ,
sortDirections ,
locale ,
2020-02-29 22:01:13 +08:00
showSorterTooltip = true ,
2019-11-15 14:35:25 +08:00
} = props ;
2020-03-07 23:17:28 +08:00
const tableProps = omit ( props , [ 'className' , 'style' ] ) as TableProps < RecordType > ;
2020-01-03 13:38:16 +08:00
const size = React . useContext ( SizeContext ) ;
2020-01-02 19:10:16 +08:00
const { locale : contextLocale = defaultLocale , renderEmpty , direction } = React . useContext (
ConfigContext ,
) ;
2020-01-03 13:38:16 +08:00
const mergedSize = customizeSize || size ;
2020-03-02 17:15:39 +08:00
const tableLocale = { . . . contextLocale . Table , . . . locale } as TableLocale ;
2019-11-15 14:35:25 +08:00
const rawData : RecordType [ ] = dataSource || EMPTY_LIST ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
const { getPrefixCls } = React . useContext ( ConfigContext ) ;
const prefixCls = getPrefixCls ( 'table' , customizePrefixCls ) ;
2020-03-11 23:58:32 +08:00
const dropdownPrefixCls = getPrefixCls ( 'dropdown' , customizeDropdownPrefixCls ) ;
2019-04-17 17:31:39 +08:00
2020-03-06 00:58:26 +08:00
const mergedExpandable : ExpandableConfig < RecordType > = {
expandIconColumnIndex ,
. . . expandable ,
} ;
2019-11-15 14:35:25 +08:00
const expandType : ExpandType = React . useMemo < ExpandType > ( ( ) = > {
2020-04-01 17:14:53 +08:00
if ( rawData . some ( item = > ( item as any ) [ childrenColumnName ] ) ) {
2019-11-15 14:35:25 +08:00
return 'nest' ;
2019-04-17 17:31:39 +08:00
}
2019-01-08 20:52:31 +08:00
2019-11-15 14:35:25 +08:00
if ( expandedRowRender || ( expandable && expandable . expandedRowRender ) ) {
return 'row' ;
2016-06-19 10:56:42 +08:00
}
2017-11-18 00:03:30 +08:00
2019-11-15 14:35:25 +08:00
return null ;
} , [ rawData ] ) ;
2019-10-17 16:01:46 +08:00
2019-11-15 14:35:25 +08:00
const internalRefs = {
body : React.useRef < HTMLDivElement > ( ) ,
2019-08-05 18:38:10 +08:00
} ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
// ============================ RowKey ============================
const getRowKey = React . useMemo < GetRowKey < RecordType > > ( ( ) = > {
if ( typeof rowKey === 'function' ) {
return rowKey ;
2017-11-09 19:30:24 +08:00
}
2019-11-15 14:35:25 +08:00
return ( record : RecordType ) = > ( record as any ) [ rowKey as string ] ;
} , [ rowKey ] ) ;
2017-11-09 19:30:24 +08:00
2019-11-15 14:35:25 +08:00
const [ getRecordByKey ] = useLazyKVMap ( rawData , childrenColumnName , getRowKey ) ;
2017-11-09 19:30:24 +08:00
2019-11-15 14:35:25 +08:00
// ============================ Events =============================
const changeEventInfo : Partial < ChangeEventInfo < RecordType > > = { } ;
2017-11-09 19:30:24 +08:00
2019-11-15 14:35:25 +08:00
const triggerOnChange = ( info : Partial < ChangeEventInfo < RecordType > > , reset : boolean = false ) = > {
const changeInfo = {
. . . changeEventInfo ,
. . . info ,
2016-06-19 10:56:42 +08:00
} ;
2019-08-05 18:38:10 +08:00
2019-11-15 14:35:25 +08:00
if ( reset ) {
changeEventInfo . resetPagination ! ( ) ;
2017-11-21 13:48:37 +08:00
2019-11-15 14:35:25 +08:00
// Reset event param
if ( changeInfo . pagination ! . current ) {
changeInfo . pagination ! . current = 1 ;
2016-06-19 10:56:42 +08:00
}
2019-11-15 14:35:25 +08:00
// Trigger pagination events
if ( pagination && pagination . onChange ) {
pagination . onChange ( 1 , changeInfo . pagination ! . pageSize ) ;
}
2018-11-01 13:42:53 +08:00
}
2019-11-15 14:35:25 +08:00
if ( scroll && scroll . scrollToFirstRowOnChange !== false && internalRefs . body . current ) {
scrollTo ( 0 , {
getContainer : ( ) = > internalRefs . body . current ! ,
} ) ;
2018-10-06 18:20:43 +08:00
}
2019-06-10 11:59:46 +08:00
2019-11-15 14:35:25 +08:00
if ( onChange ) {
onChange ( changeInfo . pagination ! , changeInfo . filters ! , changeInfo . sorter ! , {
currentDataSource : getFilterData (
getSortData ( rawData , changeInfo . sorterStates ! , childrenColumnName ) ,
changeInfo . filterStates ! ,
) ,
2019-08-05 18:38:10 +08:00
} ) ;
}
} ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
/ * *
* 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 ,
} ,
2019-12-30 16:43:34 +08:00
false ,
2019-08-05 18:38:10 +08:00
) ;
} ;
2019-11-15 14:35:25 +08:00
const [ transformSorterColumns , sortStates , sorterTitleProps , getSorters ] = useSorter < RecordType > ( {
prefixCls ,
2020-03-01 13:11:28 +08:00
columns ,
children ,
2019-11-15 14:35:25 +08:00
onSorterChange ,
sortDirections : sortDirections || [ 'ascend' , 'descend' ] ,
2020-02-29 22:01:13 +08:00
tableLocale ,
showSorterTooltip ,
2019-11-15 14:35:25 +08:00
} ) ;
const sortedData = React . useMemo ( ( ) = > getSortData ( rawData , sortStates , childrenColumnName ) , [
rawData ,
sortStates ,
] ) ;
changeEventInfo . sorter = getSorters ( ) ;
changeEventInfo . sorterStates = sortStates ;
// ============================ Filter ============================
const onFilterChange = (
filters : Record < string , Key [ ] > ,
filterStates : FilterState < RecordType > [ ] ,
) = > {
triggerOnChange (
{
filters ,
filterStates ,
} ,
true ,
) ;
2018-12-07 20:02:01 +08:00
} ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
const [ transformFilterColumns , filterStates , getFilters ] = useFilter < RecordType > ( {
prefixCls ,
2020-03-02 17:15:39 +08:00
locale : tableLocale ,
2019-11-15 14:35:25 +08:00
dropdownPrefixCls ,
2020-04-03 16:32:50 +08:00
columns ,
children ,
2019-11-15 14:35:25 +08:00
onFilterChange ,
getPopupContainer ,
} ) ;
const mergedData = getFilterData ( sortedData , filterStates ) ;
2018-07-24 14:49:23 +08:00
2019-11-15 14:35:25 +08:00
changeEventInfo . filters = getFilters ( ) ;
changeEventInfo . filterStates = filterStates ;
2018-07-24 14:49:23 +08:00
2019-11-15 14:35:25 +08:00
// ============================ Column ============================
const columnTitleProps = React . useMemo (
( ) = > ( {
. . . sorterTitleProps ,
} ) ,
[ sorterTitleProps ] ,
) ;
const [ transformTitleColumns ] = useTitleColumns ( columnTitleProps ) ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
// ========================== Pagination ==========================
const onPaginationChange = ( current : number , pageSize : number ) = > {
triggerOnChange ( {
pagination : { . . . changeEventInfo . pagination , current , pageSize } ,
2016-08-03 21:08:13 +08:00
} ) ;
2018-12-07 20:02:01 +08:00
} ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
const [ mergedPagination , resetPagination ] = usePagination (
mergedData . length ,
pagination ,
onPaginationChange ,
) ;
2017-02-23 19:29:47 +08:00
2019-11-15 14:35:25 +08:00
changeEventInfo . pagination =
pagination === false ? { } : getPaginationParam ( pagination , mergedPagination ) ;
changeEventInfo . resetPagination = resetPagination ;
// ============================= Data =============================
const pageData = React . useMemo < RecordType [ ] > ( ( ) = > {
2020-04-10 14:13:14 +08:00
if ( pagination === false || ! mergedPagination . pageSize ) {
return mergedData ;
}
const { current = 1 , total , pageSize = DEFAULT_PAGE_SIZE } = mergedPagination ;
// Dynamic table data
if ( mergedData . length < total ! ) {
if ( mergedData . length > pageSize ) {
warning (
false ,
'Table' ,
'`dataSource` length is less than `pagination.total` but large than `pagination.pageSize`. Please make sure your config correct data with async mode.' ,
) ;
return mergedData . slice ( ( current - 1 ) * pageSize , current * pageSize ) ;
}
2019-11-15 14:35:25 +08:00
return mergedData ;
}
const currentPageData = mergedData . slice ( ( current - 1 ) * pageSize , current * pageSize ) ;
return currentPageData ;
} , [
! ! pagination ,
mergedData ,
mergedPagination && mergedPagination . current ,
mergedPagination && mergedPagination . pageSize ,
mergedPagination && mergedPagination . total ,
] ) ;
// ========================== Selections ==========================
const [ transformSelectionColumns , selectedKeySet ] = useSelection < RecordType > ( rowSelection , {
prefixCls ,
data : mergedData ,
pageData ,
getRowKey ,
getRecordByKey ,
expandType ,
childrenColumnName ,
2020-03-02 17:15:39 +08:00
locale : tableLocale ,
2020-03-06 00:58:26 +08:00
expandIconColumnIndex : mergedExpandable.expandIconColumnIndex ,
2020-04-01 17:14:53 +08:00
getPopupContainer ,
2019-11-15 14:35:25 +08:00
} ) ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
const internalRowClassName = ( record : RecordType , index : number , indent : number ) = > {
let mergedRowClassName ;
if ( typeof rowClassName === 'function' ) {
mergedRowClassName = classNames ( rowClassName ( record , index , indent ) ) ;
2016-06-19 10:56:42 +08:00
} else {
2019-11-15 14:35:25 +08:00
mergedRowClassName = classNames ( rowClassName ) ;
2016-06-19 10:56:42 +08:00
}
2016-11-11 15:26:51 +08:00
2019-11-15 14:35:25 +08:00
return classNames (
{
[ ` ${ prefixCls } -row-selected ` ] : selectedKeySet . has ( getRowKey ( record , index ) ) ,
} ,
mergedRowClassName ,
) ;
2018-12-07 20:02:01 +08:00
} ;
2016-06-19 10:56:42 +08:00
2019-11-15 14:35:25 +08:00
// ========================== Expandable ==========================
2016-06-19 10:56:42 +08:00
2020-02-01 22:50:58 +08:00
// Pass origin render status into `rc-table`, this can be removed when refactor with `rc-table`
( mergedExpandable as any ) . __PARENT_RENDER_ICON__ = mergedExpandable . expandIcon ;
2019-11-15 14:35:25 +08:00
// Customize expandable icon
mergedExpandable . expandIcon =
mergedExpandable . expandIcon || expandIcon || renderExpandIcon ( tableLocale ! ) ;
2019-08-05 18:38:10 +08:00
2020-02-10 15:04:22 +08:00
// Adjust expand icon index, no overwrite expandIconColumnIndex if set.
2020-03-06 00:58:26 +08:00
if ( expandType === 'nest' && mergedExpandable . expandIconColumnIndex === undefined ) {
2019-11-15 14:35:25 +08:00
mergedExpandable . expandIconColumnIndex = rowSelection ? 1 : 0 ;
2020-03-06 00:58:26 +08:00
} else if ( mergedExpandable . expandIconColumnIndex ! > 0 && rowSelection ) {
mergedExpandable . expandIconColumnIndex ! -= 1 ;
2019-08-05 18:38:10 +08:00
}
2019-11-15 14:35:25 +08:00
// Indent size
mergedExpandable . indentSize = mergedExpandable . indentSize || indentSize || 15 ;
2019-08-05 18:38:10 +08:00
2019-11-15 14:35:25 +08:00
// ============================ Render ============================
const transformColumns = React . useCallback (
( innerColumns : ColumnsType < RecordType > ) : ColumnsType < RecordType > = > {
return transformTitleColumns (
transformSelectionColumns ( transformFilterColumns ( transformSorterColumns ( innerColumns ) ) ) ,
2019-08-05 18:38:10 +08:00
) ;
2019-11-15 14:35:25 +08:00
} ,
[ transformSorterColumns , transformFilterColumns , transformSelectionColumns ] ,
) ;
2019-08-05 18:38:10 +08:00
2019-11-15 14:35:25 +08:00
let topPaginationNode : React.ReactNode ;
let bottomPaginationNode : React.ReactNode ;
if ( pagination !== false ) {
let paginationSize : PaginationConfig [ 'size' ] ;
if ( mergedPagination . size ) {
paginationSize = mergedPagination . size ;
} else {
2020-01-03 13:38:16 +08:00
paginationSize = mergedSize === 'small' || mergedSize === 'middle' ? 'small' : undefined ;
2019-08-05 18:38:10 +08:00
}
2020-03-27 15:48:42 +08:00
const renderPagination = ( position : string = 'right' ) = > (
2019-08-05 18:38:10 +08:00
< Pagination
2020-03-27 15:48:42 +08:00
className = { ` ${ prefixCls } -pagination ${ prefixCls } -pagination- ${ position } ` }
2019-11-15 14:35:25 +08:00
{ . . . mergedPagination }
size = { paginationSize }
2019-08-05 18:38:10 +08:00
/ >
2019-11-15 14:35:25 +08:00
) ;
2020-03-27 15:48:42 +08:00
if ( mergedPagination . position !== null && Array . isArray ( mergedPagination . position ) ) {
2020-04-01 17:14:53 +08:00
const topPos = mergedPagination . position . find ( p = > p . indexOf ( 'top' ) !== - 1 ) ;
const bottomPos = mergedPagination . position . find ( p = > p . indexOf ( 'bottom' ) !== - 1 ) ;
2020-03-27 15:48:42 +08:00
if ( ! topPos && ! bottomPos ) {
2019-11-15 14:35:25 +08:00
bottomPaginationNode = renderPagination ( ) ;
2020-03-27 15:48:42 +08:00
} else {
if ( topPos ) {
topPaginationNode = renderPagination ( topPos ! . toLowerCase ( ) . replace ( 'top' , '' ) ) ;
}
if ( bottomPos ) {
bottomPaginationNode = renderPagination ( bottomPos ! . toLowerCase ( ) . replace ( 'bottom' , '' ) ) ;
}
}
} else {
2020-03-30 15:45:14 +08:00
bottomPaginationNode = renderPagination ( ) ;
2016-06-19 10:56:42 +08:00
}
}
2019-11-15 14:35:25 +08:00
// >>>>>>>>> Spinning
let spinProps : SpinProps | undefined ;
if ( typeof loading === 'boolean' ) {
spinProps = {
spinning : loading ,
} ;
2020-03-30 15:45:14 +08:00
} else if ( typeof loading === 'object' ) {
spinProps = {
spinning : true ,
. . . loading ,
} ;
2018-09-17 19:52:24 +08:00
}
2020-03-07 23:17:28 +08:00
const wrapperClassNames = classNames ( ` ${ prefixCls } -wrapper ` , className , {
2020-01-02 19:10:16 +08:00
[ ` ${ prefixCls } -wrapper-rtl ` ] : direction === 'rtl' ,
} ) ;
2019-11-15 14:35:25 +08:00
return (
2020-04-10 14:13:14 +08:00
< div className = { wrapperClassNames } style = { style } >
2019-11-15 14:35:25 +08:00
< Spin spinning = { false } { ...spinProps } >
{ topPaginationNode }
< RcTable < RecordType >
2020-03-07 23:17:28 +08:00
{ . . . tableProps }
2020-03-10 20:30:31 +08:00
direction = { direction }
2019-11-15 14:35:25 +08:00
expandable = { mergedExpandable }
prefixCls = { prefixCls }
2020-03-07 23:17:28 +08:00
className = { classNames ( {
2020-01-03 13:38:16 +08:00
[ ` ${ prefixCls } -middle ` ] : mergedSize === 'middle' ,
[ ` ${ prefixCls } -small ` ] : mergedSize === 'small' ,
2019-11-15 14:35:25 +08:00
[ ` ${ prefixCls } -bordered ` ] : bordered ,
} ) }
data = { pageData }
rowKey = { getRowKey }
rowClassName = { internalRowClassName }
2020-01-20 15:12:49 +08:00
emptyText = { ( locale && locale . emptyText ) || renderEmpty ( 'Table' ) }
2019-11-15 14:35:25 +08:00
// Internal
internalHooks = { INTERNAL_HOOKS }
internalRefs = { internalRefs as any }
transformColumns = { transformColumns }
/ >
2020-03-24 19:51:34 +08:00
{ pageData && pageData . length > 0 && bottomPaginationNode }
2019-11-15 14:35:25 +08:00
< / Spin >
< / div >
) ;
2016-06-19 10:56:42 +08:00
}
2019-08-28 02:35:39 +08:00
2019-11-15 14:35:25 +08:00
Table . defaultProps = {
rowKey : 'key' ,
} ;
2019-08-28 02:35:39 +08:00
2019-11-15 14:35:25 +08:00
Table . SELECTION_ALL = SELECTION_ALL ;
Table . SELECTION_INVERT = SELECTION_INVERT ;
Table . Column = Column ;
Table . ColumnGroup = ColumnGroup ;
2019-08-28 02:35:39 +08:00
2019-11-15 14:35:25 +08:00
export default Table ;