UNPKG

@are-visual/virtual-table

Version:
282 lines (278 loc) 9.89 kB
import { jsx } from 'react/jsx-runtime'; import { useMergedRef, createMiddleware, getRowKey, useShallowMemo, useStableFn } from '@are-visual/virtual-table'; import { useControllableValue } from '@are-visual/virtual-table/middleware/utils/useControllableValue'; import { forwardRef, useRef, useEffect, createElement, useCallback, useMemo, isValidElement } from 'react'; import clsx from 'clsx'; function Selection(props, ref) { const { className, multiple, indeterminate, value = false, onChange, component, ...rest } = props; const isCustomComponent = component != null; const inputNode = useRef(null); useEffect(() => { if (isCustomComponent) return; const input = inputNode.current; if (input == null) return; input.indeterminate = !!indeterminate; }, [isCustomComponent, indeterminate]); const mergedRef = useMergedRef(inputNode, ref); if (isCustomComponent) { return /*#__PURE__*/createElement(component, props); } return jsx("input", { ...rest, ref: mergedRef, className: clsx('virtual-table-selection', className), type: multiple ? 'checkbox' : 'radio', checked: value, onChange: e => { onChange?.(e.target.checked, e.nativeEvent); } }); } var Selection$1 = /*#__PURE__*/forwardRef(Selection); const SELECTION_COLUMN_KEY = 'VirtualTable.SELECTION_COLUMN'; const EMPTY_ARR = []; function useSelection(ctx, options) { const disablePlugin = options == null; const { columns: rawColumns, rowKey } = ctx; const dataSource = disablePlugin ? EMPTY_ARR : ctx.dataSource; const { defaultSelectedRowKeys, component, preserveSelectedRowKeys, multiple = true, getSelectionProps, onSelect, hideSelectAll = false, fixed, columnWidth, columnTitle, extraColumnProps, // checkStrictly 属性使用场景: // const dataSource = [{ key: 1, name: "张三", children: [{ key: 1.1, name: "李四" }] }] // dataSource 中含有 children 属性,antd Table 组件会显示为“树形”结构,Table 左侧会新增一个展开按钮,点击后会显示“李四”的数据 // 勾选“张三”的时候,会把对应 children 全部选中。如果 checkStrictly=false 则不选中。 // checkStrictly: _checkStrictly, renderCell, onCell } = options ?? {}; const [selectedRowKeys = [], setSelectedRowKeys] = useControllableValue(options ?? {}, { defaultValue: defaultSelectedRowKeys, valuePropName: 'selectedRowKeys', trigger: 'onChange' }); const rowClassName = useCallback(record => { const key = getRowKey(record, rowKey); const checked = selectedRowKeys.includes(key); return checked ? 'virtual-table-row-selected' : ''; }, [rowKey, selectedRowKeys]); const selectionPropsList = useShallowMemo(() => { return dataSource.map(row => { if (!getSelectionProps) return {}; return getSelectionProps(row); }); }); // 当有某一行数据 Checkbox disabled 时,过滤它 const allKeys = useShallowMemo(() => { return dataSource.filter((_data, index) => !selectionPropsList[index].disabled).map(x => getRowKey(x, rowKey)); }); // preserveSelectedRowKeys=true 时,勾选过的数据,都会在这里存一份 const cache = useRef(new Map()); const allDisabled = !selectionPropsList.some(item => !item.disabled); const getRowsByKeys = useStableFn(keys => { const result = []; keys.forEach(key => { let value = dataSource.find(row => getRowKey(row, rowKey) === key); // 在 cache 中还是找不到对应的数据,就放弃寻找,填入一个 undefined(与 antd 默认表现一致) // 场景: selectedRowKeys 中有一个在 dataSource 中不存在的 key,用这个 key 是找不到数据的 if (value == null && preserveSelectedRowKeys) { value = cache.current.get(key); } if (value == null && !preserveSelectedRowKeys) return; result.push(value); }); return result; }); const prevSelectedIndex = useRef(null); const mergeColumns = useMemo(() => { if (disablePlugin) { return rawColumns; } const isSelected = selectedRowKeys.length > 0; const indeterminate = isSelected && allKeys.length > selectedRowKeys.length; const isSelectedAll = isSelected ? allKeys.length > 0 && allKeys.every(key => selectedRowKeys.includes(key)) : false; const shakeDeadKeys = keys => { const unionKeys = new Set(keys); if (!preserveSelectedRowKeys) { return Array.from(unionKeys).filter(key => { return allKeys.includes(key); }); } return Array.from(unionKeys); }; const multipleSelect = (currentIndex, nextChecked) => { const index = prevSelectedIndex.current ?? currentIndex; const startIndex = Math.min(index, currentIndex); const endIndex = Math.max(index, currentIndex); const rangeKeys = allKeys.slice(startIndex, endIndex + 1); const shouldSelected = rangeKeys.some(rangeKey => !selectedRowKeys.includes(rangeKey)); prevSelectedIndex.current = shouldSelected ? endIndex : null; const keys = nextChecked ? shakeDeadKeys([...selectedRowKeys, ...rangeKeys]) : shakeDeadKeys(selectedRowKeys.filter(x => !rangeKeys.includes(x))); const rows = getRowsByKeys(keys); setSelectedRowKeys(keys, rows, { type: 'multiple' }); }; const onSelectAll = () => { const keys = shakeDeadKeys([...selectedRowKeys, ...allKeys]); const rows = getRowsByKeys(keys); setSelectedRowKeys(keys, rows, { type: 'all' }); }; const onSelectInvert = () => { const keys = allKeys.filter(key => !selectedRowKeys.includes(key)); const rows = getRowsByKeys(keys); setSelectedRowKeys(keys, rows, { type: 'invert' }); }; const onClearAll = () => { setSelectedRowKeys([], [], { type: 'none' }); }; const columnTitleProps = { indeterminate, value: isSelectedAll, disabled: allDisabled, multiple, onChange: checked => { if (checked) { onSelectAll(); } else { onClearAll(); } }, onClear: onClearAll, onSelectAll, onSelectInvert, allKeys }; const onCreateTitle = () => { if (!multiple) { if (/*#__PURE__*/isValidElement(columnTitle)) { return columnTitle; } if (typeof columnTitle === 'function') { return columnTitle(undefined, columnTitleProps); } return null; } let title = jsx("div", { className: "virtual-table-selection", children: jsx(Selection$1, { component: component, multiple: multiple, value: isSelectedAll, indeterminate: indeterminate, disabled: allDisabled, onChange: checked => { if (checked) { onSelectAll(); } else { onClearAll(); } } }) }); if (/*#__PURE__*/isValidElement(columnTitle)) { title = columnTitle; } else if (typeof columnTitle === 'function') { title = columnTitle(title, columnTitleProps); } return title; }; const column = { ...extraColumnProps, title: hideSelectAll ? undefined : onCreateTitle(), width: columnWidth ?? 32, align: 'center', fixed: fixed ? 'left' : undefined, key: SELECTION_COLUMN_KEY, render(_value, record, index) { const key = getRowKey(record, rowKey); const checked = selectedRowKeys.includes(key); const extraProps = selectionPropsList[index]; const updateCache = () => { if (preserveSelectedRowKeys) { cache.current.set(key, record); } }; const node = jsx(Selection$1, { ...extraProps, component: component, value: checked, multiple: multiple, onChange: (nextChecked, e) => { updateCache(); if (multiple && e.shiftKey) { multipleSelect(index, nextChecked); return; } if (nextChecked) { prevSelectedIndex.current = index; } else { prevSelectedIndex.current = null; } let keys = [key]; if (multiple) { keys = nextChecked ? shakeDeadKeys([...selectedRowKeys, key]) : shakeDeadKeys(selectedRowKeys.filter(x => x !== key)); } const rows = getRowsByKeys(keys); // @ts-expect-error rows 并不是安全的 T[] 类型,因为有 preserveSelectedRowKeys 功能存在,在此处只能忽略类型错误 onSelect?.(record, nextChecked, rows, e); setSelectedRowKeys(keys, rows, { type: 'single' }); } }); if (typeof renderCell === 'function') { return renderCell(checked, record, index, node); } return node; }, onCell, onHeaderCell() { return { className: 'virtual-table-selection-column' }; } }; return [column, ...rawColumns]; }, [disablePlugin, extraColumnProps, allDisabled, allKeys, columnTitle, columnWidth, fixed, getRowsByKeys, hideSelectAll, onCell, onSelect, preserveSelectedRowKeys, rawColumns, renderCell, rowKey, selectedRowKeys, selectionPropsList, setSelectedRowKeys, multiple, component]); if (disablePlugin) { return ctx; } return { ...ctx, rowClassName, columns: mergeColumns }; } /** * 为 Table 实现多选、单选功能,不传入 options 则是禁用插件 */ const tableSelection = createMiddleware(useSelection); export { SELECTION_COLUMN_KEY, tableSelection }; //# sourceMappingURL=index.js.map