import React from "react"; import classNames from "classnames"; import { Icon } from "../../icon"; import { TableAddon, RowRenderContext, TableProps } from "../TableProps"; import { getRowKeyFromRecordKey } from "../util/get-row-key-from-record-key"; import { useMiddleware } from "../util/use-middleware"; import { injectPropsIfTargetNotExisted } from "../util/inject-props-if-target-not-existed"; import { useConfig } from "../../_util/config-context"; /** * `expandable` 插件用于支持表格行展开。 */ export interface ExpandableAddonOptions { /** * 展开的行如何渲染 */ render?(record: Record): React.ReactNode; /** * 将当前记录展开成更多的记录 */ expand?(record: Record): Record[]; /** * 展开行的键值 */ expandedKeys?: string[]; /** * 用户进行展开/收起操作的时候,知会最新的键值 */ onExpandedKeysChange?: ( value: string[], context: { event: React.SyntheticEvent; operateType: "expand" | "collapse"; operateKey: string; operateRecord: Record; } ) => void; /** * 判断记录是否允许展开,如果不提供该判断,则认为所有记录均可展开 */ shouldRecordExpandable?: (record: Record) => boolean; /** * 展开行内容渲染之前,插入多少个间隙单元格 */ gapCell?: number; /** * 是否整行点击可展开 * @default false * @since 2.2.0 */ rowExpand?: boolean; /** * 可以在插入的间隙单元格中,渲染指定的内容(需要配置 gapCell > 0) */ gapCellRender?: (record: Record) => React.ReactNode; /** * 提供一个列的 `key`,将展开组件插入到一个目标列 * * 默认在最前新建一列插入 */ targetColumnKey?: string; /** * 列宽度,可以指定 CSS 属性或数字 (单位:px) * @default 26 */ width?: string | number; /** * **高级用法** * 更改该插件的在每行的渲染内容,`element` 为默认渲染内容,`context` 中包含该行数据相关信息 * @default x => x */ iconRender?: ( element: JSX.Element, context: RowRenderContext ) => React.ReactNode; } const fallbackColumnKey = "__expandable_addon__"; let rowDisabled: TableProps["rowDisabled"] = null; export function expandable( options: ExpandableAddonOptions ): TableAddon { const { render, expand, shouldRecordExpandable, expandedKeys, onExpandedKeysChange, targetColumnKey, gapCell, gapCellRender, width = 26, iconRender = x => x, rowExpand, } = options; const expandedKeySet = new Set(expandedKeys || []); let getRowKey: ReturnType; let addons: TableAddon[] = []; const extraProps = { recordsExpandable: !!expand, }; return { getInfo: () => ({ name: "expandable" }), onInjectProps: props => { const { recordKey } = props; getRowKey = getRowKeyFromRecordKey(recordKey); /* eslint-disable prefer-destructuring */ rowDisabled = props.rowDisabled || (() => false); addons = props.addons; const columns = injectPropsIfTargetNotExisted( props.columns, targetColumnKey, { key: fallbackColumnKey, width, header: null, render: () => null, } ); /* eslint-enable prefer-destructuring */ return { ...props, columns, ...extraProps }; }, onInjectColumn: previous => ( record, rowKey, recordIndex, column, columnIndex ) => { // 不是目标列 if (column.key !== targetColumnKey && column.key !== fallbackColumnKey) { return previous(record, rowKey, recordIndex, column, columnIndex); } const { children: preChildren, props, ...result } = previous( record, rowKey, recordIndex, column, columnIndex ); let children = preChildren; // 非表头 if (recordIndex !== -1) { const key = getRowKey(record, recordIndex); const isExpandable = !shouldRecordExpandable || shouldRecordExpandable(record); const isExpanded = expandedKeySet.has(key); if (isExpandable) { const toggle = (event: React.MouseEvent) => { if (isExpanded) { const nextSet = new Set(expandedKeySet); nextSet.delete(key); onExpandedKeysChange(Array.from(nextSet.keys()), { operateType: "collapse", operateKey: key, operateRecord: record, event, }); } else { onExpandedKeysChange(expandedKeys.concat(key), { operateType: "expand", operateKey: key, operateRecord: record, event, }); } }; const element = (
{children}
); children = iconRender(element, { children: preChildren, record, rowKey, recordIndex, disabled: rowDisabled(record), }); } } return { ...result, props, children }; }, onInjectRow: renderRow => (record, rowKey, recordIndex, columns) => { const key = getRowKey(record, recordIndex); const isExpandable = !shouldRecordExpandable || shouldRecordExpandable(record); let onClick = e => e; if (rowExpand && isExpandable) { const isExpanded = expandedKeySet.has(key); onClick = (event: React.MouseEvent) => { if (isExpanded) { const nextSet = new Set(expandedKeySet); nextSet.delete(key); onExpandedKeysChange(Array.from(nextSet.keys()), { operateType: "collapse", operateKey: key, operateRecord: record, event, }); } else { onExpandedKeysChange(expandedKeys.concat(key), { operateType: "expand", operateKey: key, operateRecord: record, event, }); } }; } // 不是展开的行,原样渲染即可 if (!expandedKeySet.has(rowKey)) { const { prepends, row, appends } = renderRow( record, rowKey, recordIndex, columns ); return { prepends, row: React.cloneElement(row, { onClick: (event: React.MouseEvent) => { if (typeof row.props.onClick === "function") { row.props.onClick(event); } onClick(event); }, }), appends, }; } // 原始行的渲染内容 const { prepends, row, appends } = renderRow( record, rowKey, recordIndex, columns ); // 展开的内容 const expands: JSX.Element[] = []; // 支持两种展开方式,提供了 render 方法的话,渲染一个行给 render() 作为渲染容器 if (typeof render === "function") { expands.push( {gapCell > 0 && ( {gapCellRender &&
{gapCellRender(record)}
} )} 0 ? gapCell : 0)}>
{render(record)}
); } // 如果提供了 expand 方法,则意图为展开当前数据为更多的数据 // 更多的数据走原来表格的渲染逻辑 else if (typeof expand === "function") { // 展开的数据 const expandedRecords = expand(record) || []; // 展开数据的渲染和追加 expandedRecords.forEach((expandedRecord, expandedRecordIndex) => { const expandedRowKey = getRowKey(expandedRecord, expandedRecordIndex); // 这里如果直接使用 renderRow,则展开的数据无法经过插件的处理,所以调用了 useMiddleware const expandedInfo = useMiddleware(addons, "onInjectRow")(renderRow)( expandedRecord, expandedRowKey, recordIndex, columns ); expands.push(...expandedInfo.prepends); expands.push(expandedInfo.row); expands.push(...expandedInfo.appends); }); } return { prepends, row: React.cloneElement(row, { className: classNames(row.props.className, "tr__masterrow"), onClick: (event: React.MouseEvent) => { if (typeof row.props.onClick === "function") { row.props.onClick(event); } onClick(event); }, }), appends: [...appends, ...expands], }; }, }; } function IconWrap({ onClick, children }) { const { classPrefix } = useConfig(); return ( {children} ); }