import React from 'react' import { fromEvent } from 'rxjs' import * as op from 'rxjs/operators' import styled from 'styled-components' import { internals } from '../../internals' import { collectNodes, makeRecursiveMapper, mergeCellProps } from '../../utils' import { TablePipeline } from '../pipeline' function clamp(min: number, x: number, max: number) { return Math.max(min, Math.min(max, x)) } const ResizeHandle = styled.span` position: absolute; top: 0; bottom: 0; right: -5px; width: 10px; cursor: col-resize; z-index: 1; transition: background-color 200ms; background: ${(props: any) => { return props['var-handleBackground'] }}; &:hover { background: ${(props: any) => { return props['var-handleHoverBackground'] }}; } ` export interface ColumnResizeFeatureOptions { /** 非受控用法:默认的列宽数组 */ defaultSizes?: number[] /** 受控用法:列宽数组 */ sizes?: number[] /** 受控用法:修改宽度的回调函数 */ onChangeSizes?(nextSizes: number[]): void /** 列的最小宽度,默认为 60 */ minSize?: number /** 如果列宽数组中没有提供有效的宽度,fallbackSize 将作为该列的宽度,默认为 150 */ fallbackSize?: number /** 列的最大宽度,默认为 1000 */ maxSize?: number /** 是否在调整列宽时禁用 user-select,默认为 true */ disableUserSelectWhenResizing?: boolean /** 把手的背景色 */ handleBackground?: string /** 鼠标悬停时,把手的背景色 */ handleHoverBackground?: string /** 把手激活时的背景色 */ handleActiveBackground?: string } export function columnResize(opts: ColumnResizeFeatureOptions = {}) { const stateKey = 'columnResize' const disableUserSelectWhenResizing = opts.disableUserSelectWhenResizing ?? true const minSize = opts.minSize ?? 60 const fallbackSize = opts.fallbackSize ?? 150 const maxSize = opts.maxSize ?? 1000 return function columnResizeFeature(pipeline: TablePipeline) { const sizes = opts.sizes ?? pipeline.getStateAtKey(stateKey) ?? opts.defaultSizes ?? [] const leafColumns = collectNodes(pipeline.getColumns(), 'leaf-only') leafColumns.forEach((col, colIndex) => { if (sizes[colIndex] == null) { if (typeof col.width === 'number') { sizes[colIndex] = col.width } else { sizes[colIndex] = fallbackSize } } }) const onChangeSizes = (nextSizes: number[]) => { pipeline.setStateAtKey(stateKey, nextSizes) opts.onChangeSizes?.(nextSizes) } const startResize = (startIndex: number, endIndex: number, e: React.MouseEvent) => { const startX = e.clientX const target = e.target as HTMLSpanElement const nextSizes$ = fromEvent(window, 'mousemove').pipe( op.takeUntil(fromEvent(window, 'mouseup')), op.map((e) => { const movingX = e.clientX const nextSizes = sizes.slice() const deltaSum = movingX - startX const sizeSum = sizes.slice(startIndex, endIndex).reduce((sum, s) => sum + s, 0) let deltaRemaining = deltaSum for (let colIndex = startIndex; colIndex < endIndex - 1; colIndex++) { const startSize = sizes[colIndex] // 将每一列的宽度变化量 都四舍五入至 整数,不然看起来非常难受 const colDelta = Math.round(deltaSum * (startSize / sizeSum)) nextSizes[colIndex] = clamp(minSize, startSize + colDelta, maxSize) deltaRemaining -= colDelta } // 因为前面的列宽都进行了四舍五入,最后一列的变化量需要使用 deltaRemaining 以避免误差 nextSizes[endIndex - 1] = clamp(minSize, sizes[endIndex - 1] + deltaRemaining, maxSize) return nextSizes }), ) let prevUserSelect = '' let docElemStyle: CSSStyleDeclaration if (disableUserSelectWhenResizing) { docElemStyle = document.documentElement.style prevUserSelect = docElemStyle.userSelect docElemStyle.userSelect = 'none' } if (opts.handleActiveBackground) { target.style.background = opts.handleActiveBackground } nextSizes$.subscribe({ next: onChangeSizes, complete() { if (disableUserSelectWhenResizing) { docElemStyle.userSelect = prevUserSelect } if (opts.handleActiveBackground) { target.style.background = '' } }, }) } return pipeline.mapColumns( makeRecursiveMapper((col, { startIndex, endIndex }) => { const prevTitle = internals.safeRenderHeader(col) if (typeof col.groupIndex === 'number' || col.isDragColumn || col.isCheckBox) { return col } return { ...col, width: sizes[startIndex], title: ( <> {prevTitle} ) => startResize(startIndex, endIndex, e)} /> ), headerCellProps: mergeCellProps(col.headerCellProps, { style: { overflow: 'visible', position: 'relative', }, }), } }), ) } }