From 68fe6ba3dd95cccaf6a49e520e9712ad1d5aa17b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E6=9C=BA=E5=99=A8=E4=BA=BA?= Date: Fri, 18 Aug 2023 11:26:47 +0800 Subject: [PATCH] feat: rowSpan & colSpan --- assets/virtual.less | 2 +- docs/examples/virtual.tsx | 65 +++++++++++++++++++++++++++--- package.json | 2 +- src/Body/BodyRow.tsx | 4 +- src/StaticTable/BodyGrid.tsx | 69 ++++++++++++++++++-------------- src/StaticTable/BodyLine.tsx | 2 +- src/StaticTable/StaticContext.ts | 10 ----- src/StaticTable/VirtualCell.tsx | 32 ++++++++++++--- src/StaticTable/context.ts | 14 +++++++ src/StaticTable/index.tsx | 2 +- 10 files changed, 147 insertions(+), 55 deletions(-) delete mode 100644 src/StaticTable/StaticContext.ts create mode 100644 src/StaticTable/context.ts diff --git a/assets/virtual.less b/assets/virtual.less index fa98ca3ea..2ce4f2ba8 100644 --- a/assets/virtual.less +++ b/assets/virtual.less @@ -15,7 +15,7 @@ } .@{tablePrefixCls}-cell { - flex: 1 0 auto; + flex: 0 0 var(--virtual-width); width: var(--virtual-width); padding: 8px 16px; border-right: @border; diff --git a/docs/examples/virtual.tsx b/docs/examples/virtual.tsx index 82ca4a50a..103ecea28 100644 --- a/docs/examples/virtual.tsx +++ b/docs/examples/virtual.tsx @@ -19,12 +19,19 @@ const columns: ColumnsType = [ dataIndex: 'c', key: 'c', onCell: (_, index) => { - if (index % 3 === 0) { + if (index % 4 === 0) { return { rowSpan: 3, }; } + if (index % 4 === 3) { + return { + rowSpan: 1, + colSpan: 3, + }; + } + return { rowSpan: 0, }; @@ -37,11 +44,59 @@ const columns: ColumnsType = [ key: 'd', children: [ // Children columns - { title: 'title4-1', dataIndex: 'b' }, - { title: 'title4-2', dataIndex: 'b' }, + { + title: 'title4-1', + dataIndex: 'b', + onCell: (_, index) => { + if (index % 4 === 0) { + return { + colSpan: 3, + }; + } + + if (index % 4 === 3) { + return { + colSpan: 0, + }; + } + }, + }, + { + title: 'title4-2', + dataIndex: 'b', + onCell: (_, index) => { + if (index % 4 === 0 || index % 4 === 3) { + return { + colSpan: 0, + }; + } + }, + }, ], }, - { title: 'title6', dataIndex: 'b', key: 'f' }, + { + title: 'title6', + dataIndex: 'b', + key: 'f', + onCell: (_, index) => { + if (index % 4 === 0) { + return { + rowSpan: 0, + colSpan: 0, + }; + } + + if (index % 4 === 1) { + return { + rowSpan: 3, + }; + } + + return { + rowSpan: 0, + }; + }, + }, { title: (
@@ -62,7 +117,7 @@ const columns: ColumnsType = [ { title: 'title12', dataIndex: 'b', key: 'l', width: 100, fixed: 'right' }, ]; -const data: RecordType[] = new Array(3 * 10000).fill(null).map((_, index) => ({ +const data: RecordType[] = new Array(4 * 10000).fill(null).map((_, index) => ({ a: `a${index}`, b: `b${index}`, c: `c${index}`, diff --git a/package.json b/package.json index c006a35c6..143137399 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "classnames": "^2.2.5", "rc-resize-observer": "^1.1.0", "rc-util": "^5.27.1", - "rc-virtual-list": "^3.10.1" + "rc-virtual-list": "^3.10.2" }, "devDependencies": { "@rc-component/father-plugin": "^1.0.2", diff --git a/src/Body/BodyRow.tsx b/src/Body/BodyRow.tsx index 56a6ffaaf..16e218262 100644 --- a/src/Body/BodyRow.tsx +++ b/src/Body/BodyRow.tsx @@ -137,7 +137,7 @@ export function getCellProps( ); } - let additionalCellProps: React.TdHTMLAttributes = {}; + let additionalCellProps: React.TdHTMLAttributes; if (column.onCell) { additionalCellProps = column.onCell(record, index); } @@ -146,7 +146,7 @@ export function getCellProps( key, fixedInfo, appendCellNode, - additionalCellProps, + additionalCellProps: additionalCellProps || {}, }; } diff --git a/src/StaticTable/BodyGrid.tsx b/src/StaticTable/BodyGrid.tsx index 4f0638b14..978d7c0aa 100644 --- a/src/StaticTable/BodyGrid.tsx +++ b/src/StaticTable/BodyGrid.tsx @@ -6,7 +6,7 @@ import TableContext from '../context/TableContext'; import useFlattenRecords, { type FlattenData } from '../hooks/useFlattenRecords'; import type { ColumnType, OnCustomizeScroll } from '../interface'; import BodyLine from './BodyLine'; -import StaticContext from './StaticContext'; +import { GridContext, StaticContext } from './context'; import { RowSpanVirtualCell } from './VirtualCell'; export interface GridProps { @@ -32,9 +32,6 @@ const Grid = React.forwardRef((props, ref) => { ]); const { scrollY, scrollX } = useContext(StaticContext); - // const context = useContext(TableContext); - // console.log('=>', context, scrollX, scrollY); - // =========================== Ref ============================ const listRef = React.useRef(); @@ -50,6 +47,11 @@ const Grid = React.forwardRef((props, ref) => { }); }, [flattenColumns]); + const columnsOffset = React.useMemo( + () => columnsWidth.map(colWidth => colWidth[2]), + [columnsWidth], + ); + React.useEffect(() => { columnsWidth.forEach(([key, width]) => { onColumnResize(key, width); @@ -123,6 +125,10 @@ const Grid = React.forwardRef((props, ref) => { for (let i = startIndex; i <= endIndex; i += 1) { const item = flattenData[i]; + if (!item) { + continue; + } + const rowKey = getRowKey(item.record, i); flattenColumns.forEach((column, colIndex) => { @@ -134,9 +140,9 @@ const Grid = React.forwardRef((props, ref) => { const endKey = getRowKey(endItem.record, endItemIndex); const sizeInfo = getSize(rowKey, endKey); - const right = columnsWidth[colIndex][2]; - const left = columnsWidth[colIndex - 1][2] || 0; - console.log('!!!', i, rowSpan, endItem, sizeInfo, right); + const right = columnsOffset[colIndex]; + const left = columnsOffset[colIndex - 1] || 0; + console.log('!!!', i, -offsetY + sizeInfo.top, left); nodes.push( ((props, ref) => { return nodes; }; + // ========================= Context ========================== + const gridContext = React.useMemo(() => ({ columnsOffset }), [columnsOffset]); + // ========================== Render ========================== const tblPrefixCls = `${prefixCls}-tbody`; return ( -
- > - ref={listRef} - className={classNames(tblPrefixCls, `${tblPrefixCls}-virtual`)} - height={scrollY} - itemHeight={24} - data={flattenData} - itemKey={item => getRowKey(item.record)} - scrollWidth={scrollX} - onVirtualScroll={({ x }) => { - onScroll({ - scrollLeft: x, - }); - }} - extraRender={extraRender} - > - {(item, index, itemProps) => { - const rowKey = getRowKey(item.record, index); - return ; - }} - -
+ +
+ > + ref={listRef} + className={classNames(tblPrefixCls, `${tblPrefixCls}-virtual`)} + height={scrollY} + itemHeight={24} + data={flattenData} + itemKey={item => getRowKey(item.record)} + scrollWidth={scrollX} + onVirtualScroll={({ x }) => { + onScroll({ + scrollLeft: x, + }); + }} + extraRender={extraRender} + > + {(item, index, itemProps) => { + const rowKey = getRowKey(item.record, index); + return ; + }} + +
+
); }); diff --git a/src/StaticTable/BodyLine.tsx b/src/StaticTable/BodyLine.tsx index ad1348174..635f3c60a 100644 --- a/src/StaticTable/BodyLine.tsx +++ b/src/StaticTable/BodyLine.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { useRowInfo } from '../Body/BodyRow'; import TableContext from '../context/TableContext'; import type { FlattenData } from '../hooks/useFlattenRecords'; -import StaticContext from './StaticContext'; +import { StaticContext } from './context'; import VirtualCell from './VirtualCell'; export interface BodyLineProps { diff --git a/src/StaticTable/StaticContext.ts b/src/StaticTable/StaticContext.ts deleted file mode 100644 index 67dd963e9..000000000 --- a/src/StaticTable/StaticContext.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createContext } from '@rc-component/context'; - -export interface StaticContextProps { - scrollX: number; - scrollY: number; -} - -const StaticContext = createContext(null); - -export default StaticContext; diff --git a/src/StaticTable/VirtualCell.tsx b/src/StaticTable/VirtualCell.tsx index 61068d07d..e4eb40edb 100644 --- a/src/StaticTable/VirtualCell.tsx +++ b/src/StaticTable/VirtualCell.tsx @@ -3,6 +3,8 @@ import { getCellProps, useRowInfo } from '../Body/BodyRow'; import Cell from '../Cell'; import type { ColumnType } from '../interface'; import classNames from 'classnames'; +import { useContext } from '@rc-component/context'; +import { GridContext } from './context'; export interface VirtualCellProps { rowInfo: ReturnType; @@ -25,6 +27,8 @@ function VirtualCell( const { render, dataIndex, className: columnClassName, width: colWidth } = column; + const { columnsOffset } = useContext(GridContext, ['columnsOffset']); + const { key, fixedInfo, appendCellNode, additionalCellProps } = getCellProps( rowInfo, column, @@ -33,19 +37,37 @@ function VirtualCell( index, ); - const { style: cellStyle, colSpan, rowSpan } = additionalCellProps; + const { style: cellStyle, colSpan = 1, rowSpan = 1 } = additionalCellProps; + + // ========================= ColWidth ========================= + // column width + const startColIndex = colIndex - 1; + const concatColWidth = + colSpan > 1 + ? columnsOffset[startColIndex + colSpan] - (columnsOffset[startColIndex] || 0) + : (colWidth as number); + + // margin offset + const marginOffset = colSpan > 1 ? (colWidth as number) - concatColWidth : 0; + + // ========================== Style =========================== const mergedStyle = { ...cellStyle, ...style, - '--virtual-width': `${colWidth}px`, + '--virtual-width': `${concatColWidth}px`, + marginRight: marginOffset, }; + // 0 rowSpan or colSpan should not render + if (colSpan === 0) { + mergedStyle.visibility = 'hidden'; + } + // When `colSpan` or `rowSpan` is `0`, should skip render. const mergedRender = - !forceRender && (colSpan === 0 || rowSpan === 0 || colSpan > 1 || rowSpan > 1) - ? () => null - : render; + !forceRender && (colSpan === 0 || rowSpan === 0 || rowSpan > 1) ? () => null : render; + // ========================== Render ========================== return ( (null); + +export interface GridContextProps { + columnsOffset: number[]; +} + +export const GridContext = createContext(null); diff --git a/src/StaticTable/index.tsx b/src/StaticTable/index.tsx index b9f7799e8..dc43bc883 100644 --- a/src/StaticTable/index.tsx +++ b/src/StaticTable/index.tsx @@ -3,7 +3,7 @@ import { INTERNAL_HOOKS } from '..'; import type { CustomizeScrollBody } from '../interface'; import Table, { type TableProps } from '../Table'; import Grid from './BodyGrid'; -import StaticContext from './StaticContext'; +import { StaticContext } from './context'; import useWidthColumns from './useWidthColumns'; const renderBody: CustomizeScrollBody = (rawData, props) => {