import cx from 'classnames' import React from 'react' import { ExpansionCell, icons, InlineFlexCell } from '../../common-views' import type { ArtColumn } from '../../interfaces' import { internals } from '../../internals' import { collectNodes, isLeafNode, mergeCellProps } from '../../utils' import { flatMap } from '../../utils/others' import { TablePipeline } from '../pipeline' const groupingMetaSymbol = Symbol('row-grouping-meta') function attachGroupingMeta(row: any) { return { [groupingMetaSymbol]: { expandable: !isLeafNode(row) }, ...row } } function getGroupingMeta(row: any): { isGroupHeader: boolean; expandable: boolean } { if (row[groupingMetaSymbol] == null) { return { isGroupHeader: false, expandable: false } } return { isGroupHeader: true, expandable: row[groupingMetaSymbol].expandable } } function rowGroupingRowPropsGetter(row: any) { if (getGroupingMeta(row).isGroupHeader) { return { className: 'alternative' } } } export interface RowGroupingFeatureOptions { /** 非受控用法:是否默认展开所有分组 */ defaultOpenAll?: boolean /** 非受控用法:默认展开的 keys */ defaultOpenKeys?: string[] /** 受控用法:当前展开的 keys */ openKeys?: string[] /** 受控用法:当前展开 keys 改变回调 */ onChangeOpenKeys?(nextKeys: string[], key: string, action: 'expand' | 'collapse'): void /** 是否对触发 onChangeOpenKeys 的 click 事件调用 event.stopPropagation() */ stopClickEventPropagation?: boolean } export function rowGrouping(opts: RowGroupingFeatureOptions = {}) { return (pipeline: TablePipeline) => { const stateKey = 'rowGrouping' const indents = pipeline.ctx.indents const textOffset = indents.iconIndent + indents.iconWidth + indents.iconGap const primaryKey = pipeline.ensurePrimaryKey('rowGrouping') as string if (typeof primaryKey !== 'string') { throw new Error('rowGrouping 仅支持字符串作为 primaryKey') } const openKeys: string[] = opts.openKeys ?? pipeline.getStateAtKey(stateKey) ?? (opts.defaultOpenAll ? pipeline.getDataSource().map((row) => row[primaryKey]) : opts.defaultOpenKeys) ?? [] const openKeySet = new Set(openKeys) const onChangeOpenKeys: RowGroupingFeatureOptions['onChangeOpenKeys'] = (nextKeys, key, action) => { opts.onChangeOpenKeys?.(nextKeys, key, action) pipeline.setStateAtKey(stateKey, nextKeys, { key, action }) } return pipeline .mapDataSource(processDataSource) .mapColumns(processColumns) .appendRowPropsGetter(rowGroupingRowPropsGetter) function processDataSource(input: any[]) { return flatMap(input, (row) => { let result = [attachGroupingMeta(row)] const expanded = openKeySet.has(row[primaryKey]) if (expanded) { if (Array.isArray(row.children)) { result = result.concat(row.children) } } return result }) } function processColumns(columns: ArtColumn[]) { if (columns.length === 0) { return columns } const columnFlatCount = collectNodes(columns, 'leaf-only').length const [firstCol, ...others] = columns const render = (value: any, row: any, rowIndex: number) => { const content = internals.safeRender(firstCol, row, rowIndex) const meta = getGroupingMeta(row) if (!meta.isGroupHeader || !meta.expandable) { const marginLeft = textOffset + (meta.isGroupHeader ? 0 : indents.indentSize) return ( {meta.isGroupHeader ? row.groupTitle ?? content : content} ) } const renderText = row.__art_custom_group_text const expanded = openKeySet.has(row[primaryKey]) const expandCls = expanded ? 'expanded' : 'collapsed' return ( {row.groupTitle ?? content} ) } const getCellProps = (value: any, row: any, rowIndex: number) => { const meta = getGroupingMeta(row) if (!meta.isGroupHeader) { return } const { expandable } = meta const rowKey = row[primaryKey] const expanded = openKeySet.has(rowKey) let onClick: any if (expandable) { onClick = (e: React.MouseEvent) => { if (opts.stopClickEventPropagation) { e.stopPropagation() } if (expanded) { onChangeOpenKeys( openKeys.filter((key) => key !== rowKey), rowKey, 'collapse', ) } else { onChangeOpenKeys([...openKeys, rowKey], rowKey, 'expand') } } } const prevProps = firstCol.getCellProps?.(value, row, rowIndex) return mergeCellProps(prevProps, { onClick, style: { cursor: expandable ? 'pointer' : undefined }, }) } return [ { ...firstCol, title: (
{internals.safeRenderHeader(firstCol)}
), render, getCellProps, getSpanRect(value: any, row: any, rowIndex: number) { if (getGroupingMeta(row).isGroupHeader) { return { top: rowIndex, bottom: rowIndex + 1, left: 0, right: columnFlatCount } } }, }, ...others, ] } } }