import React, { Fragment, useContext } from "react"; import classNames from "classnames"; import { TableAddon, TableColumn } from "../../TableProps"; import { passiveEventSupported } from "../../../_util/get-passive-event-support"; import { getElementOffset } from "../../../_util/get-element-offset"; import { ResizableTable, ResizeContext, ResizableTableHead, } from "./ResizableTable"; import { TableContext } from "../../TableContext"; /** * `columnsResizable` 插件用于支持表格列宽度调整。 */ export interface ColumnsResizableOptions { /** * 列可调整到的最小宽度 */ minWidth?: number; /** * 列可调整到的最大宽度 */ maxWidth?: number; /** * 调整完成回调 */ onResizeEnd: (columns: Column[]) => void; /** * 指定哪些列支持列宽度调整,可传入键值 * @since 2.7.4 */ columns?: string[]; } export function columnsResizable({ minWidth, maxWidth, onResizeEnd, columns: resizeableColumns, }: ColumnsResizableOptions): TableAddon { return { getInfo: () => ({ name: "columnsResizable" }), onInjectProps: props => { return { ...props, columnsResizable: true }; }, onInjectTable: render => props => { const table = render(props); const { columns } = props; return ; }, onInjectHead: render => (...args) => { const head = render(...args); return ; }, onInjectColumn: render => ( record, rowKey, recordIndex, column, columnIndex ) => { const result = render(record, rowKey, recordIndex, column, columnIndex); if (recordIndex === -1) { result.props = Object.assign({}, result.props, { column: column.key, }); } /** * 仅表头注入拖拽元素 * 传入 resizeableColumns 后,只对需要的列注入拖拽元素 */ if ( recordIndex !== -1 || "ignore" in column || (resizeableColumns && !resizeableColumns.includes(column.key)) ) { return { props: result.props, children: result.children }; } const children = ( ); return { props: result.props, children }; }, onInjectRow: renderRow => (record, rowKey, recordIndex, columns) => { const result = renderRow(record, rowKey, recordIndex, columns); result.row = ( ); return result; }, }; } interface ResizableRowCellProps { childrenRowCell: JSX.Element; className?: string; } function ResizableRowCell({ childrenRowCell, ...props }: ResizableRowCellProps) { const { isResizing } = useContext(ResizeContext); const className = classNames( childrenRowCell.props.className, { "no-hover": !!isResizing, }, props.className ); return React.cloneElement(childrenRowCell, { ...props, className }); } interface ResizableTableCellProps extends ColumnsResizableOptions { childrenCell: React.ReactNode; columnIndex: number; } function ResizableTableCell({ minWidth, maxWidth, onResizeEnd, childrenCell, columnIndex, ...props }: ResizableTableCellProps) { const { wrapperRef, lineRef, columns, classPrefix, headRef, setIsResizing, } = useContext(ResizeContext); const { columnsWidths } = useContext(TableContext); type ResizeEvent = React.MouseEvent & React.TouchEvent; let startX = 0; const fitColumnWidths = (columns: TableColumn[]): TableColumn[] => { const tableWidth = headRef.current?.clientWidth; let totalWidth = 0; columns.forEach(column => { if (column.width) { totalWidth += typeof column.width === "string" ? parseInt(column.width, 10) : column.width; } }); if (totalWidth < tableWidth) { const lastIndex = columns.length - 1; if (typeof columns[lastIndex].width === "undefined") { return [...columns]; } if (typeof columns[lastIndex].width === "string") { columns[lastIndex].width = parseInt(columns[lastIndex].width as string, 10) + (tableWidth - totalWidth); } else { columns[lastIndex].width = (columns[lastIndex].width as number) + (tableWidth - totalWidth); } } return [...columns]; }; const onResizeStart = (event: ResizeEvent): void => { setIsResizing(true); let isTouchEvent = false; const filterColumns = columns.filter(item => item.width); const hasReset = filterColumns.length === columnsWidths.length; const trNodes: HTMLElement[] = Array.prototype.slice.call( headRef.current.getElementsByTagName("tr"), 0 ); const tdNodes: HTMLElement[] = trNodes && trNodes.length > 0 ? Array.prototype.slice.call(trNodes[0].childNodes, 0) : []; if (tdNodes && tdNodes.length === columns.length && !hasReset) { tdNodes.forEach((tdNode, index) => { columns[index].width = tdNode.clientWidth; }); const newColumns = columns.filter(column => !("ignore" in column)); onResizeEnd(newColumns); } if (event.type === "touchstart") { if (event.touches && event.touches.length > 1) { return; } isTouchEvent = true; } startX = isTouchEvent ? Math.round(event.touches[0].clientX) : event.clientX; const left = startX - getElementOffset(wrapperRef.current).left; requestAnimationFrame(() => { lineRef.current.style.transform = `translate3d(${left}px, 0px, 0px)`; lineRef.current.style.display = "block"; }); const eventTarget = event.currentTarget; const thNode: any = eventTarget.parentNode && eventTarget.parentNode.parentNode; const moveHandler = (clientX: number) => { const resizeWidth = clientX - startX + thNode.clientWidth; let left = clientX - getElementOffset(wrapperRef.current).left; const baseLeftPosition = getElementOffset(thNode).left - getElementOffset(wrapperRef.current).left; if (resizeWidth <= 0) { left = baseLeftPosition + minWidth; } else if (maxWidth && resizeWidth > maxWidth) { left = baseLeftPosition + maxWidth; } else if (minWidth && resizeWidth < minWidth) { left = baseLeftPosition + minWidth; } requestAnimationFrame(() => { lineRef.current.style.transform = `translate3d(${left}px, 0px, 0px)`; }); }; const handlersAndEvents = { mouse: { moveEvent: "mousemove", moveHandler: (event: ResizeEvent) => { if (event.cancelable) { event.preventDefault(); event.stopPropagation(); } moveHandler(event.clientX); return false; }, upEvent: "mouseup", upHandler: (event: ResizeEvent) => { document.removeEventListener( "mousemove", handlersAndEvents.mouse.moveHandler as any ); document.removeEventListener( "mouseup", handlersAndEvents.mouse.upHandler as any ); if (lineRef.current && lineRef.current.style) { lineRef.current.style.display = "none"; } let resizeWidth = event.clientX - startX + thNode.clientWidth; if (resizeWidth <= 0) { resizeWidth = minWidth; } if (maxWidth && resizeWidth > maxWidth) { resizeWidth = maxWidth; } if (minWidth && resizeWidth < minWidth) { resizeWidth = minWidth; } columns[columnIndex] = Object.assign({}, columns[columnIndex], { width: resizeWidth, }); const newColumns = fitColumnWidths(columns); setIsResizing(false); onResizeEnd(newColumns.filter(column => !("ignore" in column))); }, }, touch: { moveEvent: "touchmove", moveHandler: (event: ResizeEvent) => { if (event.cancelable) { event.preventDefault(); event.stopPropagation(); } moveHandler(event.touches[0].clientX); return false; }, upEvent: "touchend", upHandler: (event: ResizeEvent) => { document.removeEventListener( handlersAndEvents.touch.moveEvent, handlersAndEvents.touch.moveHandler as any ); document.removeEventListener( handlersAndEvents.touch.upEvent, handlersAndEvents.touch.moveHandler as any ); if (lineRef.current && lineRef.current.style) { lineRef.current.style.display = "none"; } let resizeWidth = event.clientX - startX + thNode.clientWidth; if (resizeWidth <= 0) { resizeWidth = minWidth; } if (maxWidth && resizeWidth > maxWidth) { resizeWidth = maxWidth; } if (minWidth && resizeWidth < minWidth) { resizeWidth = minWidth; } columns[columnIndex] = Object.assign({}, columns[columnIndex], { width: resizeWidth, }); const newColumns = fitColumnWidths(columns); setIsResizing(false); onResizeEnd(newColumns.filter(column => !("ignore" in column))); }, }, }; const events = isTouchEvent ? handlersAndEvents.touch : handlersAndEvents.mouse; const passiveIfSupported = passiveEventSupported() ? { passive: false } : false; document.addEventListener( events.moveEvent, events.moveHandler as any, passiveIfSupported ); document.addEventListener( events.upEvent, events.upHandler as any, passiveIfSupported ); }; return ( {childrenCell} ); }