2023-09-11 17:28:04 +08:00
|
|
|
import * as React from 'react';
|
2023-10-26 11:46:22 +08:00
|
|
|
import { useCallback, useMemo } from 'react';
|
2020-03-02 12:09:38 +08:00
|
|
|
import DownOutlined from '@ant-design/icons/DownOutlined';
|
2023-04-06 17:16:27 +08:00
|
|
|
import classNames from 'classnames';
|
2020-05-01 16:02:32 +08:00
|
|
|
import { INTERNAL_COL_DEFINE } from 'rc-table';
|
2022-05-07 14:31:54 +08:00
|
|
|
import type { FixedType } from 'rc-table/lib/interface';
|
2022-06-22 14:57:09 +08:00
|
|
|
import type { DataNode, GetCheckDisabled } from 'rc-tree/lib/interface';
|
|
|
|
import { arrAdd, arrDel } from 'rc-tree/lib/util';
|
|
|
|
import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
|
|
|
|
import { convertDataToEntities } from 'rc-tree/lib/utils/treeUtil';
|
2020-06-28 22:41:59 +08:00
|
|
|
import useMergedState from 'rc-util/lib/hooks/useMergedState';
|
2023-09-11 17:28:04 +08:00
|
|
|
|
2023-10-26 11:46:22 +08:00
|
|
|
import useMultipleSelect from '../../_util/hooks/useMultipleSelect';
|
2023-07-05 16:54:04 +08:00
|
|
|
import type { AnyObject } from '../../_util/type';
|
2023-09-11 17:28:04 +08:00
|
|
|
import { devUseWarning } from '../../_util/warning';
|
2022-05-07 14:31:54 +08:00
|
|
|
import type { CheckboxProps } from '../../checkbox';
|
|
|
|
import Checkbox from '../../checkbox';
|
2019-11-15 14:35:25 +08:00
|
|
|
import Dropdown from '../../dropdown';
|
|
|
|
import Radio from '../../radio';
|
2022-05-07 14:31:54 +08:00
|
|
|
import type {
|
2023-06-07 23:01:09 +08:00
|
|
|
ColumnsType,
|
2023-09-11 17:28:04 +08:00
|
|
|
ColumnType,
|
2019-11-15 14:35:25 +08:00
|
|
|
ExpandType,
|
2020-04-01 17:14:53 +08:00
|
|
|
GetPopupContainer,
|
2022-06-22 14:57:09 +08:00
|
|
|
GetRowKey,
|
|
|
|
Key,
|
2022-05-18 11:59:00 +08:00
|
|
|
RowSelectMethod,
|
2022-06-22 14:57:09 +08:00
|
|
|
SelectionItem,
|
|
|
|
TableLocale,
|
|
|
|
TableRowSelection,
|
|
|
|
TransformColumns,
|
2019-11-15 14:35:25 +08:00
|
|
|
} from '../interface';
|
|
|
|
|
|
|
|
// TODO: warning if use ajax!!!
|
2021-11-26 17:50:41 +08:00
|
|
|
|
|
|
|
export const SELECTION_COLUMN = {} as const;
|
2020-04-22 21:59:18 +08:00
|
|
|
export const SELECTION_ALL = 'SELECT_ALL' as const;
|
|
|
|
export const SELECTION_INVERT = 'SELECT_INVERT' as const;
|
2020-12-29 10:38:43 +08:00
|
|
|
export const SELECTION_NONE = 'SELECT_NONE' as const;
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2022-02-17 18:11:33 +08:00
|
|
|
const EMPTY_LIST: React.Key[] = [];
|
|
|
|
|
2023-07-05 16:54:04 +08:00
|
|
|
interface UseSelectionConfig<RecordType extends AnyObject = AnyObject> {
|
2019-11-15 14:35:25 +08:00
|
|
|
prefixCls: string;
|
|
|
|
pageData: RecordType[];
|
|
|
|
data: RecordType[];
|
|
|
|
getRowKey: GetRowKey<RecordType>;
|
|
|
|
getRecordByKey: (key: Key) => RecordType;
|
|
|
|
expandType: ExpandType;
|
|
|
|
childrenColumnName: string;
|
2020-03-02 17:15:39 +08:00
|
|
|
locale: TableLocale;
|
2020-04-01 17:14:53 +08:00
|
|
|
getPopupContainer?: GetPopupContainer;
|
2019-11-15 14:35:25 +08:00
|
|
|
}
|
|
|
|
|
2020-04-22 21:59:18 +08:00
|
|
|
export type INTERNAL_SELECTION_ITEM =
|
|
|
|
| SelectionItem
|
|
|
|
| typeof SELECTION_ALL
|
2020-12-29 10:38:43 +08:00
|
|
|
| typeof SELECTION_INVERT
|
|
|
|
| typeof SELECTION_NONE;
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2023-07-05 16:54:04 +08:00
|
|
|
const flattenData = <RecordType extends AnyObject = AnyObject>(
|
2023-04-06 17:16:27 +08:00
|
|
|
childrenColumnName: keyof RecordType,
|
|
|
|
data?: RecordType[],
|
|
|
|
): RecordType[] => {
|
2019-11-15 14:35:25 +08:00
|
|
|
let list: RecordType[] = [];
|
2022-11-19 13:47:33 +08:00
|
|
|
(data || []).forEach((record) => {
|
2019-11-15 14:35:25 +08:00
|
|
|
list.push(record);
|
2020-08-05 22:29:38 +08:00
|
|
|
if (record && typeof record === 'object' && childrenColumnName in record) {
|
2023-04-06 17:16:27 +08:00
|
|
|
list = [...list, ...flattenData<RecordType>(childrenColumnName, record[childrenColumnName])];
|
2019-11-15 14:35:25 +08:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return list;
|
2023-04-06 17:16:27 +08:00
|
|
|
};
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2023-07-05 16:54:04 +08:00
|
|
|
const useSelection = <RecordType extends AnyObject = AnyObject>(
|
2019-11-15 14:35:25 +08:00
|
|
|
config: UseSelectionConfig<RecordType>,
|
2023-03-03 14:56:12 +08:00
|
|
|
rowSelection?: TableRowSelection<RecordType>,
|
2023-04-06 17:16:27 +08:00
|
|
|
): readonly [TransformColumns<RecordType>, Set<Key>] => {
|
2019-11-15 14:35:25 +08:00
|
|
|
const {
|
2020-06-28 22:41:59 +08:00
|
|
|
preserveSelectedRowKeys,
|
2019-11-15 14:35:25 +08:00
|
|
|
selectedRowKeys,
|
2021-03-25 09:11:39 +08:00
|
|
|
defaultSelectedRowKeys,
|
2019-11-15 14:35:25 +08:00
|
|
|
getCheckboxProps,
|
|
|
|
onChange: onSelectionChange,
|
|
|
|
onSelect,
|
|
|
|
onSelectAll,
|
|
|
|
onSelectInvert,
|
2020-12-29 10:38:43 +08:00
|
|
|
onSelectNone,
|
2019-11-15 14:35:25 +08:00
|
|
|
onSelectMultiple,
|
2020-04-28 11:16:01 +08:00
|
|
|
columnWidth: selectionColWidth,
|
2019-11-15 14:35:25 +08:00
|
|
|
type: selectionType,
|
|
|
|
selections,
|
2020-01-07 19:17:37 +08:00
|
|
|
fixed,
|
2020-02-29 23:11:02 +08:00
|
|
|
renderCell: customizeRenderCell,
|
2020-05-29 23:50:56 +08:00
|
|
|
hideSelectAll,
|
2020-06-28 22:41:59 +08:00
|
|
|
checkStrictly = true,
|
2019-11-15 14:35:25 +08:00
|
|
|
} = rowSelection || {};
|
|
|
|
|
|
|
|
const {
|
|
|
|
prefixCls,
|
|
|
|
data,
|
|
|
|
pageData,
|
|
|
|
getRecordByKey,
|
|
|
|
getRowKey,
|
|
|
|
expandType,
|
|
|
|
childrenColumnName,
|
2020-03-02 17:15:39 +08:00
|
|
|
locale: tableLocale,
|
2020-04-01 17:14:53 +08:00
|
|
|
getPopupContainer,
|
2019-11-15 14:35:25 +08:00
|
|
|
} = config;
|
|
|
|
|
2023-09-13 22:07:33 +08:00
|
|
|
const warning = devUseWarning('Table');
|
2023-09-11 17:28:04 +08:00
|
|
|
|
2023-10-26 11:46:22 +08:00
|
|
|
// ========================= MultipleSelect =========================
|
|
|
|
const [multipleSelect, updatePrevSelectedIndex] = useMultipleSelect<React.Key, React.Key>(
|
|
|
|
(item) => item,
|
|
|
|
);
|
|
|
|
|
2020-06-28 22:41:59 +08:00
|
|
|
// ========================= Keys =========================
|
2021-03-25 09:11:39 +08:00
|
|
|
const [mergedSelectedKeys, setMergedSelectedKeys] = useMergedState(
|
2022-02-17 18:11:33 +08:00
|
|
|
selectedRowKeys || defaultSelectedRowKeys || EMPTY_LIST,
|
2021-03-25 09:11:39 +08:00
|
|
|
{
|
|
|
|
value: selectedRowKeys,
|
|
|
|
},
|
|
|
|
);
|
2020-06-28 22:41:59 +08:00
|
|
|
|
2021-07-02 13:37:55 +08:00
|
|
|
// ======================== Caches ========================
|
|
|
|
const preserveRecordsRef = React.useRef(new Map<Key, RecordType>());
|
|
|
|
|
|
|
|
const updatePreserveRecordsCache = useCallback(
|
|
|
|
(keys: Key[]) => {
|
|
|
|
if (preserveSelectedRowKeys) {
|
|
|
|
const newCache = new Map<Key, RecordType>();
|
|
|
|
// Keep key if mark as preserveSelectedRowKeys
|
2022-11-19 13:47:33 +08:00
|
|
|
keys.forEach((key) => {
|
2021-07-02 13:37:55 +08:00
|
|
|
let record = getRecordByKey(key);
|
|
|
|
|
|
|
|
if (!record && preserveRecordsRef.current.has(key)) {
|
|
|
|
record = preserveRecordsRef.current.get(key)!;
|
|
|
|
}
|
|
|
|
|
|
|
|
newCache.set(key, record);
|
|
|
|
});
|
|
|
|
// Refresh to new cache
|
|
|
|
preserveRecordsRef.current = newCache;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
[getRecordByKey, preserveSelectedRowKeys],
|
|
|
|
);
|
|
|
|
|
|
|
|
// Update cache with selectedKeys
|
|
|
|
React.useEffect(() => {
|
|
|
|
updatePreserveRecordsCache(mergedSelectedKeys);
|
|
|
|
}, [mergedSelectedKeys]);
|
|
|
|
|
2023-06-07 23:01:09 +08:00
|
|
|
const { keyEntities } = useMemo(() => {
|
|
|
|
if (checkStrictly) {
|
|
|
|
return { keyEntities: null };
|
|
|
|
}
|
|
|
|
let convertData = data;
|
|
|
|
if (preserveSelectedRowKeys) {
|
|
|
|
const keysSet = new Set(data.map((record, index) => getRowKey(record, index)));
|
|
|
|
// remove preserveRecords that duplicate data
|
|
|
|
const preserveRecords = Array.from(preserveRecordsRef.current).reduce(
|
|
|
|
(total: RecordType[], [key, value]) => (keysSet.has(key) ? total : total.concat(value)),
|
|
|
|
[],
|
|
|
|
);
|
|
|
|
convertData = [...convertData, ...preserveRecords];
|
|
|
|
}
|
|
|
|
return convertDataToEntities(convertData as unknown as DataNode[], {
|
|
|
|
externalGetKey: getRowKey as any,
|
|
|
|
childrenPropName: childrenColumnName,
|
|
|
|
});
|
|
|
|
}, [data, getRowKey, checkStrictly, childrenColumnName, preserveSelectedRowKeys]);
|
2020-06-28 22:41:59 +08:00
|
|
|
|
|
|
|
// Get flatten data
|
2021-06-02 12:15:00 +08:00
|
|
|
const flattedData = useMemo(
|
2023-01-16 16:18:01 +08:00
|
|
|
() => flattenData(childrenColumnName, pageData),
|
2023-04-06 17:16:27 +08:00
|
|
|
[childrenColumnName, pageData],
|
2021-06-02 12:15:00 +08:00
|
|
|
);
|
2020-06-28 22:41:59 +08:00
|
|
|
|
|
|
|
// Get all checkbox props
|
|
|
|
const checkboxPropsMap = useMemo(() => {
|
|
|
|
const map = new Map<Key, Partial<CheckboxProps>>();
|
|
|
|
flattedData.forEach((record, index) => {
|
|
|
|
const key = getRowKey(record, index);
|
|
|
|
const checkboxProps = (getCheckboxProps ? getCheckboxProps(record) : null) || {};
|
|
|
|
map.set(key, checkboxProps);
|
|
|
|
|
2022-05-10 15:43:29 +08:00
|
|
|
warning(
|
|
|
|
!('checked' in checkboxProps || 'defaultChecked' in checkboxProps),
|
2023-09-11 17:28:04 +08:00
|
|
|
'usage',
|
2022-05-10 15:43:29 +08:00
|
|
|
'Do not set `checked` or `defaultChecked` in `getCheckboxProps`. Please use `selectedRowKeys` instead.',
|
|
|
|
);
|
2020-06-28 22:41:59 +08:00
|
|
|
});
|
|
|
|
return map;
|
|
|
|
}, [flattedData, getRowKey, getCheckboxProps]);
|
|
|
|
|
|
|
|
const isCheckboxDisabled: GetCheckDisabled<RecordType> = useCallback(
|
2020-12-09 17:12:32 +08:00
|
|
|
(r: RecordType) => !!checkboxPropsMap.get(getRowKey(r))?.disabled,
|
2020-06-28 22:41:59 +08:00
|
|
|
[checkboxPropsMap, getRowKey],
|
|
|
|
);
|
|
|
|
|
|
|
|
const [derivedSelectedKeys, derivedHalfSelectedKeys] = useMemo(() => {
|
|
|
|
if (checkStrictly) {
|
2020-12-04 14:26:23 +08:00
|
|
|
return [mergedSelectedKeys || [], []];
|
2020-06-28 22:41:59 +08:00
|
|
|
}
|
|
|
|
const { checkedKeys, halfCheckedKeys } = conductCheck(
|
|
|
|
mergedSelectedKeys,
|
|
|
|
true,
|
|
|
|
keyEntities as any,
|
|
|
|
isCheckboxDisabled as any,
|
|
|
|
);
|
2020-12-04 14:26:23 +08:00
|
|
|
return [checkedKeys || [], halfCheckedKeys];
|
2020-06-28 22:41:59 +08:00
|
|
|
}, [mergedSelectedKeys, checkStrictly, keyEntities, isCheckboxDisabled]);
|
|
|
|
|
2023-04-06 17:16:27 +08:00
|
|
|
const derivedSelectedKeySet = useMemo<Set<Key>>(() => {
|
2020-06-28 22:41:59 +08:00
|
|
|
const keys = selectionType === 'radio' ? derivedSelectedKeys.slice(0, 1) : derivedSelectedKeys;
|
2019-11-15 14:35:25 +08:00
|
|
|
return new Set(keys);
|
2020-06-28 22:41:59 +08:00
|
|
|
}, [derivedSelectedKeys, selectionType]);
|
2023-04-06 17:16:27 +08:00
|
|
|
|
|
|
|
const derivedHalfSelectedKeySet = useMemo<Set<Key>>(
|
2020-12-09 17:12:32 +08:00
|
|
|
() => (selectionType === 'radio' ? new Set() : new Set(derivedHalfSelectedKeys)),
|
|
|
|
[derivedHalfSelectedKeys, selectionType],
|
|
|
|
);
|
2019-11-15 14:35:25 +08:00
|
|
|
|
|
|
|
// Reset if rowSelection reset
|
|
|
|
React.useEffect(() => {
|
|
|
|
if (!rowSelection) {
|
2022-02-17 18:11:33 +08:00
|
|
|
setMergedSelectedKeys(EMPTY_LIST);
|
2019-11-15 14:35:25 +08:00
|
|
|
}
|
|
|
|
}, [!!rowSelection]);
|
|
|
|
|
2020-06-28 22:41:59 +08:00
|
|
|
const setSelectedKeys = useCallback(
|
2022-05-18 11:59:00 +08:00
|
|
|
(keys: Key[], method: RowSelectMethod) => {
|
2020-06-28 22:41:59 +08:00
|
|
|
let availableKeys: Key[];
|
|
|
|
let records: RecordType[];
|
|
|
|
|
2021-07-02 13:37:55 +08:00
|
|
|
updatePreserveRecordsCache(keys);
|
|
|
|
|
2020-06-28 22:41:59 +08:00
|
|
|
if (preserveSelectedRowKeys) {
|
|
|
|
availableKeys = keys;
|
2022-11-19 13:47:33 +08:00
|
|
|
records = keys.map((key) => preserveRecordsRef.current.get(key)!);
|
2020-06-28 22:41:59 +08:00
|
|
|
} else {
|
|
|
|
// Filter key which not exist in the `dataSource`
|
|
|
|
availableKeys = [];
|
|
|
|
records = [];
|
|
|
|
|
2022-11-19 13:47:33 +08:00
|
|
|
keys.forEach((key) => {
|
2020-06-28 22:41:59 +08:00
|
|
|
const record = getRecordByKey(key);
|
|
|
|
if (record !== undefined) {
|
|
|
|
availableKeys.push(key);
|
|
|
|
records.push(record);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
setMergedSelectedKeys(availableKeys);
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2022-05-18 11:59:00 +08:00
|
|
|
onSelectionChange?.(availableKeys, records, { type: method });
|
2019-11-15 14:35:25 +08:00
|
|
|
},
|
2020-06-28 22:41:59 +08:00
|
|
|
[setMergedSelectedKeys, getRecordByKey, onSelectionChange, preserveSelectedRowKeys],
|
2019-11-15 14:35:25 +08:00
|
|
|
);
|
|
|
|
|
2020-06-28 22:41:59 +08:00
|
|
|
// ====================== Selections ======================
|
2019-11-15 14:35:25 +08:00
|
|
|
// Trigger single `onSelect` event
|
2020-06-28 22:41:59 +08:00
|
|
|
const triggerSingleSelection = useCallback(
|
2019-11-15 14:35:25 +08:00
|
|
|
(key: Key, selected: boolean, keys: Key[], event: Event) => {
|
|
|
|
if (onSelect) {
|
2022-11-19 13:47:33 +08:00
|
|
|
const rows = keys.map((k) => getRecordByKey(k));
|
2019-11-15 14:35:25 +08:00
|
|
|
onSelect(getRecordByKey(key), selected, rows, event);
|
|
|
|
}
|
|
|
|
|
2022-05-18 11:59:00 +08:00
|
|
|
setSelectedKeys(keys, 'single');
|
2019-11-15 14:35:25 +08:00
|
|
|
},
|
|
|
|
[onSelect, getRecordByKey, setSelectedKeys],
|
|
|
|
);
|
|
|
|
|
2020-06-28 22:41:59 +08:00
|
|
|
const mergedSelections = useMemo<SelectionItem[] | null>(() => {
|
2020-05-29 23:50:56 +08:00
|
|
|
if (!selections || hideSelectAll) {
|
2019-11-15 14:35:25 +08:00
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const selectionList: INTERNAL_SELECTION_ITEM[] =
|
2020-12-29 10:38:43 +08:00
|
|
|
selections === true ? [SELECTION_ALL, SELECTION_INVERT, SELECTION_NONE] : selections;
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2022-06-23 16:04:55 +08:00
|
|
|
return selectionList
|
|
|
|
.map((selection: INTERNAL_SELECTION_ITEM) => {
|
|
|
|
if (selection === SELECTION_ALL) {
|
|
|
|
return {
|
|
|
|
key: 'all',
|
|
|
|
text: tableLocale.selectionAll,
|
|
|
|
onSelect() {
|
|
|
|
setSelectedKeys(
|
|
|
|
data
|
|
|
|
.map((record, index) => getRowKey(record, index))
|
2022-11-19 13:47:33 +08:00
|
|
|
.filter((key) => {
|
2022-06-23 16:04:55 +08:00
|
|
|
const checkProps = checkboxPropsMap.get(key);
|
|
|
|
return !checkProps?.disabled || derivedSelectedKeySet.has(key);
|
|
|
|
}),
|
|
|
|
'all',
|
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (selection === SELECTION_INVERT) {
|
|
|
|
return {
|
|
|
|
key: 'invert',
|
|
|
|
text: tableLocale.selectInvert,
|
|
|
|
onSelect() {
|
|
|
|
const keySet = new Set(derivedSelectedKeySet);
|
|
|
|
pageData.forEach((record, index) => {
|
|
|
|
const key = getRowKey(record, index);
|
|
|
|
const checkProps = checkboxPropsMap.get(key);
|
|
|
|
|
|
|
|
if (!checkProps?.disabled) {
|
|
|
|
if (keySet.has(key)) {
|
|
|
|
keySet.delete(key);
|
|
|
|
} else {
|
|
|
|
keySet.add(key);
|
|
|
|
}
|
2021-09-03 19:47:23 +08:00
|
|
|
}
|
2022-06-23 16:04:55 +08:00
|
|
|
});
|
|
|
|
|
|
|
|
const keys = Array.from(keySet);
|
|
|
|
if (onSelectInvert) {
|
2023-09-13 22:07:33 +08:00
|
|
|
warning.deprecated(false, 'onSelectInvert', 'onChange');
|
2022-06-23 16:04:55 +08:00
|
|
|
onSelectInvert(keys);
|
2019-11-15 14:35:25 +08:00
|
|
|
}
|
2021-01-29 13:22:42 +08:00
|
|
|
|
2022-06-23 16:04:55 +08:00
|
|
|
setSelectedKeys(keys, 'invert');
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (selection === SELECTION_NONE) {
|
|
|
|
return {
|
|
|
|
key: 'none',
|
|
|
|
text: tableLocale.selectNone,
|
|
|
|
onSelect() {
|
|
|
|
onSelectNone?.();
|
|
|
|
setSelectedKeys(
|
2022-11-19 13:47:33 +08:00
|
|
|
Array.from(derivedSelectedKeySet).filter((key) => {
|
2022-06-23 16:04:55 +08:00
|
|
|
const checkProps = checkboxPropsMap.get(key);
|
|
|
|
return checkProps?.disabled;
|
|
|
|
}),
|
|
|
|
'none',
|
|
|
|
);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return selection as SelectionItem;
|
|
|
|
})
|
2022-11-19 13:47:33 +08:00
|
|
|
.map((selection) => ({
|
2022-06-23 16:04:55 +08:00
|
|
|
...selection,
|
|
|
|
onSelect: (...rest) => {
|
|
|
|
selection.onSelect?.(...rest);
|
2023-10-26 11:46:22 +08:00
|
|
|
updatePrevSelectedIndex(null);
|
2022-06-23 16:04:55 +08:00
|
|
|
},
|
|
|
|
}));
|
2020-06-28 22:41:59 +08:00
|
|
|
}, [selections, derivedSelectedKeySet, pageData, getRowKey, onSelectInvert, setSelectedKeys]);
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2020-06-28 22:41:59 +08:00
|
|
|
// ======================= Columns ========================
|
|
|
|
const transformColumns = useCallback(
|
2019-11-15 14:35:25 +08:00
|
|
|
(columns: ColumnsType<RecordType>): ColumnsType<RecordType> => {
|
2021-11-26 17:50:41 +08:00
|
|
|
// >>>>>>>>>>> Skip if not exists `rowSelection`
|
2019-11-15 14:35:25 +08:00
|
|
|
if (!rowSelection) {
|
2022-05-10 15:43:29 +08:00
|
|
|
warning(
|
|
|
|
!columns.includes(SELECTION_COLUMN),
|
2023-09-11 17:28:04 +08:00
|
|
|
'usage',
|
2022-05-10 15:43:29 +08:00
|
|
|
'`rowSelection` is not config but `SELECTION_COLUMN` exists in the `columns`.',
|
|
|
|
);
|
2021-11-26 17:50:41 +08:00
|
|
|
|
2022-11-19 13:47:33 +08:00
|
|
|
return columns.filter((col) => col !== SELECTION_COLUMN);
|
2019-11-15 14:35:25 +08:00
|
|
|
}
|
|
|
|
|
2021-11-26 17:50:41 +08:00
|
|
|
// >>>>>>>>>>> Support selection
|
|
|
|
let cloneColumns = [...columns];
|
2020-06-28 22:41:59 +08:00
|
|
|
const keySet = new Set(derivedSelectedKeySet);
|
2019-11-15 14:35:25 +08:00
|
|
|
|
|
|
|
// Record key only need check with enabled
|
|
|
|
const recordKeys = flattedData
|
|
|
|
.map(getRowKey)
|
2022-11-19 13:47:33 +08:00
|
|
|
.filter((key) => !checkboxPropsMap.get(key)!.disabled);
|
|
|
|
const checkedCurrentAll = recordKeys.every((key) => keySet.has(key));
|
|
|
|
const checkedCurrentSome = recordKeys.some((key) => keySet.has(key));
|
2019-11-15 14:35:25 +08:00
|
|
|
|
|
|
|
const onSelectAllChange = () => {
|
|
|
|
const changeKeys: Key[] = [];
|
|
|
|
|
|
|
|
if (checkedCurrentAll) {
|
2022-11-19 13:47:33 +08:00
|
|
|
recordKeys.forEach((key) => {
|
2019-11-15 14:35:25 +08:00
|
|
|
keySet.delete(key);
|
|
|
|
changeKeys.push(key);
|
|
|
|
});
|
|
|
|
} else {
|
2022-11-19 13:47:33 +08:00
|
|
|
recordKeys.forEach((key) => {
|
2020-06-28 22:41:59 +08:00
|
|
|
if (!keySet.has(key)) {
|
|
|
|
keySet.add(key);
|
|
|
|
changeKeys.push(key);
|
|
|
|
}
|
2019-11-15 14:35:25 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
const keys = Array.from(keySet);
|
|
|
|
|
2021-02-19 18:26:53 +08:00
|
|
|
onSelectAll?.(
|
|
|
|
!checkedCurrentAll,
|
2022-11-19 13:47:33 +08:00
|
|
|
keys.map((k) => getRecordByKey(k)),
|
|
|
|
changeKeys.map((k) => getRecordByKey(k)),
|
2021-02-19 18:26:53 +08:00
|
|
|
);
|
2021-01-29 13:22:42 +08:00
|
|
|
|
2022-05-18 11:59:00 +08:00
|
|
|
setSelectedKeys(keys, 'all');
|
2023-10-26 11:46:22 +08:00
|
|
|
updatePrevSelectedIndex(null);
|
2019-11-15 14:35:25 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
// ===================== Render =====================
|
|
|
|
// Title Cell
|
|
|
|
let title: React.ReactNode;
|
2023-10-24 16:05:29 +08:00
|
|
|
let columnTitleCheckbox: React.ReactNode;
|
2019-11-15 14:35:25 +08:00
|
|
|
if (selectionType !== 'radio') {
|
|
|
|
let customizeSelections: React.ReactNode;
|
|
|
|
if (mergedSelections) {
|
2022-10-23 00:33:45 +08:00
|
|
|
const menu = {
|
|
|
|
getPopupContainer,
|
|
|
|
items: mergedSelections.map((selection, index) => {
|
|
|
|
const { key, text, onSelect: onSelectionClick } = selection;
|
|
|
|
|
|
|
|
return {
|
2023-04-06 17:16:27 +08:00
|
|
|
key: key ?? index,
|
2022-10-23 00:33:45 +08:00
|
|
|
onClick: () => {
|
|
|
|
onSelectionClick?.(recordKeys);
|
|
|
|
},
|
|
|
|
label: text,
|
|
|
|
};
|
|
|
|
}),
|
|
|
|
};
|
2019-11-15 14:35:25 +08:00
|
|
|
customizeSelections = (
|
|
|
|
<div className={`${prefixCls}-selection-extra`}>
|
2022-10-23 00:33:45 +08:00
|
|
|
<Dropdown menu={menu} getPopupContainer={getPopupContainer}>
|
2019-11-15 14:35:25 +08:00
|
|
|
<span>
|
2019-11-28 12:34:33 +08:00
|
|
|
<DownOutlined />
|
2019-11-15 14:35:25 +08:00
|
|
|
</span>
|
|
|
|
</Dropdown>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2021-06-02 12:15:00 +08:00
|
|
|
const allDisabledData = flattedData
|
|
|
|
.map((record, index) => {
|
|
|
|
const key = getRowKey(record, index);
|
|
|
|
const checkboxProps = checkboxPropsMap.get(key) || {};
|
|
|
|
return { checked: keySet.has(key), ...checkboxProps };
|
|
|
|
})
|
|
|
|
.filter(({ disabled }) => disabled);
|
|
|
|
|
|
|
|
const allDisabled =
|
|
|
|
!!allDisabledData.length && allDisabledData.length === flattedData.length;
|
|
|
|
|
|
|
|
const allDisabledAndChecked =
|
|
|
|
allDisabled && allDisabledData.every(({ checked }) => checked);
|
|
|
|
const allDisabledSomeChecked =
|
|
|
|
allDisabled && allDisabledData.some(({ checked }) => checked);
|
2020-01-17 14:02:07 +08:00
|
|
|
|
2023-10-24 16:05:29 +08:00
|
|
|
columnTitleCheckbox = (
|
|
|
|
<Checkbox
|
|
|
|
checked={
|
|
|
|
!allDisabled ? !!flattedData.length && checkedCurrentAll : allDisabledAndChecked
|
|
|
|
}
|
|
|
|
indeterminate={
|
|
|
|
!allDisabled
|
|
|
|
? !checkedCurrentAll && checkedCurrentSome
|
|
|
|
: !allDisabledAndChecked && allDisabledSomeChecked
|
|
|
|
}
|
|
|
|
onChange={onSelectAllChange}
|
|
|
|
disabled={flattedData.length === 0 || allDisabled}
|
|
|
|
aria-label={customizeSelections ? 'Custom selection' : 'Select all'}
|
|
|
|
skipGroup
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
|
2020-05-29 23:50:56 +08:00
|
|
|
title = !hideSelectAll && (
|
2019-11-15 14:35:25 +08:00
|
|
|
<div className={`${prefixCls}-selection`}>
|
2023-10-24 16:05:29 +08:00
|
|
|
{columnTitleCheckbox}
|
2019-11-15 14:35:25 +08:00
|
|
|
{customizeSelections}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Body Cell
|
2020-02-29 23:11:02 +08:00
|
|
|
let renderCell: (
|
|
|
|
_: RecordType,
|
|
|
|
record: RecordType,
|
|
|
|
index: number,
|
|
|
|
) => { node: React.ReactNode; checked: boolean };
|
2019-11-15 14:35:25 +08:00
|
|
|
if (selectionType === 'radio') {
|
|
|
|
renderCell = (_, record, index) => {
|
|
|
|
const key = getRowKey(record, index);
|
2020-02-29 23:11:02 +08:00
|
|
|
const checked = keySet.has(key);
|
|
|
|
|
|
|
|
return {
|
|
|
|
node: (
|
|
|
|
<Radio
|
|
|
|
{...checkboxPropsMap.get(key)}
|
|
|
|
checked={checked}
|
2022-11-19 13:47:33 +08:00
|
|
|
onClick={(e) => e.stopPropagation()}
|
|
|
|
onChange={(event) => {
|
2020-02-29 23:11:02 +08:00
|
|
|
if (!keySet.has(key)) {
|
|
|
|
triggerSingleSelection(key, true, [key], event.nativeEvent);
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
checked,
|
|
|
|
};
|
2019-11-15 14:35:25 +08:00
|
|
|
};
|
|
|
|
} else {
|
|
|
|
renderCell = (_, record, index) => {
|
|
|
|
const key = getRowKey(record, index);
|
2020-02-29 23:11:02 +08:00
|
|
|
const checked = keySet.has(key);
|
2020-06-28 22:41:59 +08:00
|
|
|
const indeterminate = derivedHalfSelectedKeySet.has(key);
|
2020-07-24 18:46:56 +08:00
|
|
|
const checkboxProps = checkboxPropsMap.get(key);
|
|
|
|
let mergedIndeterminate: boolean;
|
|
|
|
if (expandType === 'nest') {
|
|
|
|
mergedIndeterminate = indeterminate;
|
2022-05-10 15:43:29 +08:00
|
|
|
warning(
|
2021-04-14 22:28:48 +08:00
|
|
|
typeof checkboxProps?.indeterminate !== 'boolean',
|
2023-09-11 17:28:04 +08:00
|
|
|
'usage',
|
2020-07-24 18:46:56 +08:00
|
|
|
'set `indeterminate` using `rowSelection.getCheckboxProps` is not allowed with tree structured dataSource.',
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
mergedIndeterminate = checkboxProps?.indeterminate ?? indeterminate;
|
|
|
|
}
|
2019-11-15 14:35:25 +08:00
|
|
|
// Record checked
|
2020-02-29 23:11:02 +08:00
|
|
|
return {
|
|
|
|
node: (
|
|
|
|
<Checkbox
|
2020-07-24 18:46:56 +08:00
|
|
|
{...checkboxProps}
|
|
|
|
indeterminate={mergedIndeterminate}
|
2020-02-29 23:11:02 +08:00
|
|
|
checked={checked}
|
2020-12-28 18:54:02 +08:00
|
|
|
skipGroup
|
2022-11-19 13:47:33 +08:00
|
|
|
onClick={(e) => e.stopPropagation()}
|
2020-02-29 23:11:02 +08:00
|
|
|
onChange={({ nativeEvent }) => {
|
|
|
|
const { shiftKey } = nativeEvent;
|
2023-10-26 11:46:22 +08:00
|
|
|
const currentSelectedIndex = recordKeys.findIndex((item) => item === key);
|
|
|
|
const isMultiple = derivedSelectedKeys.some((item) => recordKeys.includes(item));
|
2020-02-29 23:11:02 +08:00
|
|
|
|
2023-10-26 11:46:22 +08:00
|
|
|
if (shiftKey && checkStrictly && isMultiple) {
|
|
|
|
const changedKeys = multipleSelect(currentSelectedIndex, recordKeys, keySet);
|
2020-02-29 23:11:02 +08:00
|
|
|
const keys = Array.from(keySet);
|
2023-10-26 11:46:22 +08:00
|
|
|
|
2021-02-19 18:26:53 +08:00
|
|
|
onSelectMultiple?.(
|
|
|
|
!checked,
|
2022-11-19 13:47:33 +08:00
|
|
|
keys.map((recordKey) => getRecordByKey(recordKey)),
|
|
|
|
changedKeys.map((recordKey) => getRecordByKey(recordKey)),
|
2021-02-19 18:26:53 +08:00
|
|
|
);
|
2021-01-29 13:22:42 +08:00
|
|
|
|
2022-05-18 11:59:00 +08:00
|
|
|
setSelectedKeys(keys, 'multiple');
|
2019-11-15 14:35:25 +08:00
|
|
|
} else {
|
2020-02-29 23:11:02 +08:00
|
|
|
// Single record selected
|
2020-06-28 22:41:59 +08:00
|
|
|
const originCheckedKeys = derivedSelectedKeys;
|
|
|
|
if (checkStrictly) {
|
|
|
|
const checkedKeys = checked
|
|
|
|
? arrDel(originCheckedKeys, key)
|
|
|
|
: arrAdd(originCheckedKeys, key);
|
|
|
|
triggerSingleSelection(key, !checked, checkedKeys, nativeEvent);
|
2020-02-29 23:11:02 +08:00
|
|
|
} else {
|
2020-06-28 22:41:59 +08:00
|
|
|
// Always fill first
|
|
|
|
const result = conductCheck(
|
|
|
|
[...originCheckedKeys, key],
|
|
|
|
true,
|
|
|
|
keyEntities as any,
|
|
|
|
isCheckboxDisabled as any,
|
|
|
|
);
|
|
|
|
const { checkedKeys, halfCheckedKeys } = result;
|
|
|
|
let nextCheckedKeys = checkedKeys;
|
|
|
|
|
|
|
|
// If remove, we do it again to correction
|
|
|
|
if (checked) {
|
|
|
|
const tempKeySet = new Set(checkedKeys);
|
|
|
|
tempKeySet.delete(key);
|
|
|
|
nextCheckedKeys = conductCheck(
|
|
|
|
Array.from(tempKeySet),
|
|
|
|
{ checked: false, halfCheckedKeys },
|
|
|
|
keyEntities as any,
|
|
|
|
isCheckboxDisabled as any,
|
|
|
|
).checkedKeys;
|
|
|
|
}
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2020-06-28 22:41:59 +08:00
|
|
|
triggerSingleSelection(key, !checked, nextCheckedKeys, nativeEvent);
|
|
|
|
}
|
2020-02-29 23:11:02 +08:00
|
|
|
}
|
2019-11-15 14:35:25 +08:00
|
|
|
|
2022-06-23 16:04:55 +08:00
|
|
|
if (checked) {
|
2023-10-26 11:46:22 +08:00
|
|
|
updatePrevSelectedIndex(null);
|
2022-06-23 16:04:55 +08:00
|
|
|
} else {
|
2023-10-26 11:46:22 +08:00
|
|
|
updatePrevSelectedIndex(currentSelectedIndex);
|
2022-06-23 16:04:55 +08:00
|
|
|
}
|
2020-02-29 23:11:02 +08:00
|
|
|
}}
|
|
|
|
/>
|
|
|
|
),
|
|
|
|
checked,
|
|
|
|
};
|
2019-11-15 14:35:25 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-02-29 23:11:02 +08:00
|
|
|
const renderSelectionCell = (_: any, record: RecordType, index: number) => {
|
|
|
|
const { node, checked } = renderCell(_, record, index);
|
|
|
|
|
|
|
|
if (customizeRenderCell) {
|
|
|
|
return customizeRenderCell(checked, record, index, node);
|
|
|
|
}
|
|
|
|
|
|
|
|
return node;
|
|
|
|
};
|
|
|
|
|
2021-11-26 17:50:41 +08:00
|
|
|
// Insert selection column if not exist
|
|
|
|
if (!cloneColumns.includes(SELECTION_COLUMN)) {
|
|
|
|
// Always after expand icon
|
|
|
|
if (
|
|
|
|
cloneColumns.findIndex(
|
|
|
|
(col: any) => col[INTERNAL_COL_DEFINE]?.columnType === 'EXPAND_COLUMN',
|
|
|
|
) === 0
|
|
|
|
) {
|
|
|
|
const [expandColumn, ...restColumns] = cloneColumns;
|
|
|
|
cloneColumns = [expandColumn, SELECTION_COLUMN, ...restColumns];
|
|
|
|
} else {
|
|
|
|
// Normal insert at first column
|
|
|
|
cloneColumns = [SELECTION_COLUMN, ...cloneColumns];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Deduplicate selection column
|
|
|
|
const selectionColumnIndex = cloneColumns.indexOf(SELECTION_COLUMN);
|
2022-05-10 15:43:29 +08:00
|
|
|
|
|
|
|
warning(
|
2022-11-19 13:47:33 +08:00
|
|
|
cloneColumns.filter((col) => col === SELECTION_COLUMN).length <= 1,
|
2023-09-11 17:28:04 +08:00
|
|
|
'usage',
|
2022-05-10 15:43:29 +08:00
|
|
|
'Multiple `SELECTION_COLUMN` exist in `columns`.',
|
|
|
|
);
|
|
|
|
|
2021-11-26 17:50:41 +08:00
|
|
|
cloneColumns = cloneColumns.filter(
|
|
|
|
(column, index) => column !== SELECTION_COLUMN || index === selectionColumnIndex,
|
|
|
|
);
|
|
|
|
|
|
|
|
// Fixed column logic
|
|
|
|
const prevCol: ColumnType<RecordType> & Record<string, any> =
|
|
|
|
cloneColumns[selectionColumnIndex - 1];
|
|
|
|
const nextCol: ColumnType<RecordType> & Record<string, any> =
|
|
|
|
cloneColumns[selectionColumnIndex + 1];
|
|
|
|
|
|
|
|
let mergedFixed: FixedType | undefined = fixed;
|
|
|
|
|
|
|
|
if (mergedFixed === undefined) {
|
|
|
|
if (nextCol?.fixed !== undefined) {
|
|
|
|
mergedFixed = nextCol.fixed;
|
|
|
|
} else if (prevCol?.fixed !== undefined) {
|
|
|
|
mergedFixed = prevCol.fixed;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
mergedFixed &&
|
|
|
|
prevCol &&
|
|
|
|
prevCol[INTERNAL_COL_DEFINE]?.columnType === 'EXPAND_COLUMN' &&
|
|
|
|
prevCol.fixed === undefined
|
|
|
|
) {
|
|
|
|
prevCol.fixed = mergedFixed;
|
|
|
|
}
|
|
|
|
|
2023-04-06 17:16:27 +08:00
|
|
|
const columnCls = classNames(`${prefixCls}-selection-col`, {
|
|
|
|
[`${prefixCls}-selection-col-with-dropdown`]: selections && selectionType === 'checkbox',
|
|
|
|
});
|
|
|
|
|
2023-10-24 16:05:29 +08:00
|
|
|
const renderColumnTitle = () => {
|
|
|
|
if (!rowSelection?.columnTitle) {
|
|
|
|
return title;
|
|
|
|
}
|
|
|
|
if (typeof rowSelection.columnTitle === 'function') {
|
|
|
|
return rowSelection.columnTitle(columnTitleCheckbox);
|
|
|
|
}
|
|
|
|
return rowSelection.columnTitle;
|
|
|
|
};
|
|
|
|
|
2021-11-26 17:50:41 +08:00
|
|
|
// Replace with real selection column
|
2023-05-06 15:24:22 +08:00
|
|
|
const selectionColumn: ColumnsType<RecordType>[0] & {
|
|
|
|
RC_TABLE_INTERNAL_COL_DEFINE: Record<string, any>;
|
|
|
|
} = {
|
2021-11-26 17:50:41 +08:00
|
|
|
fixed: mergedFixed,
|
2019-11-15 14:35:25 +08:00
|
|
|
width: selectionColWidth,
|
|
|
|
className: `${prefixCls}-selection-column`,
|
2023-10-24 16:05:29 +08:00
|
|
|
title: renderColumnTitle(),
|
2020-02-29 23:11:02 +08:00
|
|
|
render: renderSelectionCell,
|
2023-05-06 15:24:22 +08:00
|
|
|
onCell: rowSelection.onCell,
|
2023-04-06 17:16:27 +08:00
|
|
|
[INTERNAL_COL_DEFINE]: { className: columnCls },
|
2019-11-15 14:35:25 +08:00
|
|
|
};
|
|
|
|
|
2022-11-19 13:47:33 +08:00
|
|
|
return cloneColumns.map((col) => (col === SELECTION_COLUMN ? selectionColumn : col));
|
2019-11-15 14:35:25 +08:00
|
|
|
},
|
|
|
|
[
|
|
|
|
getRowKey,
|
2020-06-28 22:41:59 +08:00
|
|
|
flattedData,
|
2019-11-15 14:35:25 +08:00
|
|
|
rowSelection,
|
2020-06-28 22:41:59 +08:00
|
|
|
derivedSelectedKeys,
|
|
|
|
derivedSelectedKeySet,
|
|
|
|
derivedHalfSelectedKeySet,
|
2019-11-15 14:35:25 +08:00
|
|
|
selectionColWidth,
|
|
|
|
mergedSelections,
|
|
|
|
expandType,
|
2020-06-28 22:41:59 +08:00
|
|
|
checkboxPropsMap,
|
2019-11-15 14:35:25 +08:00
|
|
|
onSelectMultiple,
|
|
|
|
triggerSingleSelection,
|
2020-06-28 22:41:59 +08:00
|
|
|
isCheckboxDisabled,
|
2019-11-15 14:35:25 +08:00
|
|
|
],
|
|
|
|
);
|
|
|
|
|
2023-03-03 14:56:12 +08:00
|
|
|
return [transformColumns, derivedSelectedKeySet] as const;
|
2023-04-06 17:16:27 +08:00
|
|
|
};
|
2023-03-03 14:56:12 +08:00
|
|
|
|
|
|
|
export default useSelection;
|