import React, { forwardRef, Fragment, useRef, useContext } from "react"; import classNames from "classnames"; import { Text } from "../text"; import { TableBox } from "./TableBox"; import { TableProps, TableColumn } from "./TableProps"; import { getRowKeyFromRecordKey } from "./util/get-row-key-from-record-key"; import { useMiddleware } from "./util/use-middleware"; import { SlideTransition } from "../transition"; import { useConfig } from "../_util/config-context"; import { TableVirtualizedBody, TableVirtualizedBodyProps, } from "./TableVirtualizedBody"; import { useResizeObserver } from "../_util/use-resize-observer"; import { DomRef } from "../domref"; import { noop } from "../_util/noop"; import { TableContext } from "./TableContext"; import { TableDraggableContext } from "./TableDraggableContext"; import { useIsomorphicLayoutEffect } from "../_util/use-isomorphic-layout-effect"; import { DragContext } from "./addons/draggable/DraggableTable"; import { getValueByPathKey } from "./util/get-value-by-path-key"; export function CellResizeObserver({ children, onResize, }: { children: React.ReactNode; onResize(width: number): void; }) { const ref = useRef(null); const { width } = useResizeObserver(ref); // 拖拽中不重置宽度 const { isDragging } = useContext(TableDraggableContext) || {}; useIsomorphicLayoutEffect(() => { let didCancel = false; if (!didCancel && ref.current && width && !isDragging) { onResize(width); } return () => { didCancel = true; }; // eslint-disable-next-line react-hooks/exhaustive-deps }, [width]); return {children}; } // 计算固定列偏移 export function getFixedOffset( columns: TableProps["columns"], index: number, columnsWidths: number[], fixed: TableColumn["fixed"] ): number { let offset = 0; if (fixed === "left") { for (let i = 0; i < index; ++i) { if (columns[i].fixed === "left" && columnsWidths[i]) { offset += columnsWidths[i]; } } } else { for (let i = columns.length - 1; i > index; --i) { if (columns[i].fixed === "right" && columnsWidths[i]) { offset += columnsWidths[i]; } } } return offset; } // 获取固定列最后一列以显示阴影 export function getFixedEndColumn(columns: TableProps["columns"]) { let left = 0; let right = columns.length - 1; for (let i = 0; i < right; ++i) { if (columns[i].fixed === "left") { left = i; } } for (let i = right; i >= 0; --i) { if (columns[i].fixed === "right") { right = i; } } return { left, right }; } // 表格组件核心实现 export const TableBody = forwardRef(function TableBody( { columns, records, addons, recordKey, rowClassName, rowDisabled, topTip, bottomTip, tableBoxStyle, disableHoverHighlight, disableTextOverflow, onScrollCapture = noop, draggable, columnsDraggable, virtualizedOptions, onResize, recordsExpandable, }: TableProps & { tableBoxStyle?: React.CSSProperties; onScrollCapture?: (event: React.UIEvent) => void; // `scrollable` 开启虚拟滚动时注入该 props virtualizedOptions?: TableVirtualizedBodyProps; // `expandable` 开启且包含可展开行 recordsExpandable?: boolean; // `draggable` 开启时注入该 props draggable?: boolean; // `columnsDraggable` 开启时注入该 props columnsDraggable?: boolean; // 固定列计算宽度 onResize?: React.Dispatch>; }, ref: React.Ref ) { const { classPrefix } = useConfig(); const { scrollStatus, columnsWidths } = useContext(TableContext); const hasFixedColumn = columns.find(column => !!column.fixed); const { columnClassNames } = useContext(DragContext); // 键值的获取方式 const getRowKey = getRowKeyFromRecordKey(recordKey); // 列渲染:(record, column) => { props, children } const renderColumn = useMiddleware( addons, "onInjectColumn" )((record, rowKey, recordIndex, column, columnIndex) => { let children = null; if (isFunction(column.render)) { children = column.render( record, rowKey, recordIndex, column, columnIndex ); } else if (typeof record === "object" && record) { children = getValueByPathKey(record, column.key); } if (typeof children === "undefined") { // never render undefined children = null; } if (typeof children !== "object") { children = ( {children} ); } return { props: {}, children, }; }); // 行渲染:(columns, record) => { prepends, row, appends } const renderRow = useMiddleware( addons, "onInjectRow" )((record, rowKey, recordIndex, columns) => { const className = classNames( isFunction(rowClassName) ? rowClassName(record) : null, isFunction(rowDisabled) ? { "is-disabled": rowDisabled(record) } : null, { "no-hover": !!disableHoverHighlight } ); return { prepends: [], row: ( {columns.map((column, index) => { const cKey = column.key; const { props, children } = renderColumn( record, rowKey, recordIndex, column, index ); if (props.colSpan === 0 || props.rowSpan === 0) { return null; } // 开启虚拟滚动 if (virtualizedOptions) { props.style = { ...(props.style || {}), display: "inline-block", overflow: "hidden", boxSizing: "border-box", flex: column.width ? undefined : 1, flexBasis: column.width || 1, }; } // 固定列 if ( (column.fixed === "left" || column.fixed === "right") && column.fixed !== scrollStatus && scrollStatus !== "no-scroll" ) { const end = getFixedEndColumn(columns); props.className = classNames( props.className, `${classPrefix}-table__td--fixed`, { [`${classPrefix}-table__td--fixed-${column.fixed}`]: end[column.fixed] === index, } ); props.style = { ...(props.style || {}), position: "sticky", [column.fixed]: getFixedOffset( columns, index, columnsWidths, column.fixed ), }; } // 拖拽排序 if (columnsDraggable) { props["data-column"] = cKey; if ( columnClassNames[cKey] && Array.isArray(columnClassNames[cKey]) ) { props.className = classNames( props.className, ...columnClassNames[cKey] ); } } const cell = (
{children}
); // 需要获取列宽 if (recordIndex === 0 && (hasFixedColumn || draggable)) { return ( { onResize(widths => { const columnsWidths = [...widths]; columnsWidths[index] = width; return columnsWidths; }); }} > {cell} ); } return cell; })} ), appends: [], }; }); // 记录渲染:(record) => [] const renderRecord = (record: any, recordIndex: number) => { // 生成 rowKey const rowKey = getRowKey(record, recordIndex); const { prepends, row, appends } = renderRow( record, rowKey, recordIndex, columns ); return [...prepends, row, ...appends].filter(Boolean); }; const renderBody = useMiddleware( addons, "onInjectBody" )((records, columns, topTip, bottomTip) => // 开启虚拟滚动 virtualizedOptions ? ( {topTip} ) } bottomTip={ bottomTip && ( {bottomTip} ) } {...virtualizedOptions} /> ) : (
{topTip} {(records || []).map((record, index) => ( {renderRecord(record, index)} ))} {bottomTip}
) ); return renderBody(records, columns, topTip, bottomTip); }); TableBody.displayName = "TableBody"; function isFunction(target: any): target is Function { return typeof target === "function"; }