From cf86ba26557f44d5978eaf60bc355301f38449a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D1=81=D0=B5=D0=BD=D0=B8=D1=8F?= <31247233+kseniya57@users.noreply.github.com> Date: Mon, 1 Jul 2024 18:42:48 +0300 Subject: [PATCH] fix(Table): fix drag when virtualization enabled (#7) Co-authored-by: kseniyakuzina --- src/components/Table/Table.tsx | 15 ++++- src/components/VirtualRow/VirtualRow.tsx | 2 +- .../VirtualizationProvider.tsx | 2 + .../WindowVirtualizationProvider.tsx | 6 +- src/components/__stories__/Grid2.stories.tsx | 16 ++++++ .../ReorderingWithVirtualizationDemo.tsx | 55 +++++++++++++++++++ .../utils/getVirtualRangeExtractor.ts | 17 ++++++ 7 files changed, 108 insertions(+), 5 deletions(-) create mode 100644 src/components/__stories__/ReorderingWithVirtualizationDemo.tsx create mode 100644 src/components/utils/getVirtualRangeExtractor.ts diff --git a/src/components/Table/Table.tsx b/src/components/Table/Table.tsx index 005a55a..978e5d0 100644 --- a/src/components/Table/Table.tsx +++ b/src/components/Table/Table.tsx @@ -130,7 +130,9 @@ export const Table = React.memo( onColumnSizingChange, onColumnSizingInfoChange, }: TableProps) => { - const draggable = Boolean(React.useContext(SortableListContext)); + const draggableContext = React.useContext(SortableListContext); + const draggable = Boolean(draggableContext); + const draggableItemKey = draggableContext?.activeItemKey; const virtualizationContext = React.useContext(VirtualizationContext); const virtual = Boolean(virtualizationContext); @@ -212,6 +214,13 @@ export const Table = React.memo( tableOptions.onColumnSizingInfoChange = onColumnSizingInfoChange; } + const draggableIndex = React.useMemo(() => { + if (!draggableItemKey) { + return undefined; + } + return data.findIndex((item) => getRowId(item) === draggableItemKey); + }, [data, draggableItemKey, getRowId]); + const table = useReactTable(tableOptions); const getRowByIndex = React.useCallback['getRowByIndex']>( @@ -266,7 +275,7 @@ export const Table = React.memo( enableNesting={enableNesting} getTableState={table.getState} > - +
{withHeader && ( {headerGroups.map((headerGroup, index) => ( @@ -285,7 +294,7 @@ export const Table = React.memo( )} ({ - transform: `translateY(${virtualRow.start}px)`, + top: virtualRow.start, ...style, }), [style, virtualRow.start], diff --git a/src/components/VirtualizationProvider/VirtualizationProvider.tsx b/src/components/VirtualizationProvider/VirtualizationProvider.tsx index 11da1b4..586766d 100644 --- a/src/components/VirtualizationProvider/VirtualizationProvider.tsx +++ b/src/components/VirtualizationProvider/VirtualizationProvider.tsx @@ -4,6 +4,7 @@ import {useVirtualizer} from '@tanstack/react-virtual'; import {VirtualizationContainer} from '../VirtualizationContainer'; import {VirtualizationContext} from '../VirtualizationContext'; +import {getVirtualRangeExtractor} from '../utils/getVirtualRangeExtractor'; export interface VirtualizationProviderProps { children?: React.ReactNode; @@ -30,6 +31,7 @@ export const VirtualizationProvider = ({ getScrollElement: () => containerRef.current, measureElement: (element) => element?.getBoundingClientRect().height, overscan: overscanRowCount, + rangeExtractor: getVirtualRangeExtractor(containerRef.current), }); const virtualItems = rowVirtualizer.getVirtualItems(); diff --git a/src/components/WindowVirtualizationProvider/WindowVirtualizationProvider.tsx b/src/components/WindowVirtualizationProvider/WindowVirtualizationProvider.tsx index 4c115f0..a551067 100644 --- a/src/components/WindowVirtualizationProvider/WindowVirtualizationProvider.tsx +++ b/src/components/WindowVirtualizationProvider/WindowVirtualizationProvider.tsx @@ -4,6 +4,7 @@ import {useWindowVirtualizer} from '@tanstack/react-virtual'; import {VirtualizationContainer} from '../VirtualizationContainer'; import {VirtualizationContext} from '../VirtualizationContext'; +import {getVirtualRangeExtractor} from '../utils/getVirtualRangeExtractor'; export interface WindowVirtualizationProviderProps { rowsCount: number; @@ -20,11 +21,14 @@ export const WindowVirtualizationProvider = ({ containerClassName, estimateRowSize, }: WindowVirtualizationProviderProps) => { + const containerRef = React.useRef(null); + const rowVirtualizer = useWindowVirtualizer({ count: rowsCount, estimateSize: estimateRowSize, measureElement: (element) => element?.getBoundingClientRect().height, overscan: overscanRowCount, + rangeExtractor: getVirtualRangeExtractor(containerRef.current), }); const virtualItems = rowVirtualizer.getVirtualItems(); @@ -41,7 +45,7 @@ export const WindowVirtualizationProvider = ({ return ( - + {children} diff --git a/src/components/__stories__/Grid2.stories.tsx b/src/components/__stories__/Grid2.stories.tsx index 4168ea4..317a232 100644 --- a/src/components/__stories__/Grid2.stories.tsx +++ b/src/components/__stories__/Grid2.stories.tsx @@ -10,6 +10,7 @@ import {GroupingDemo2} from './GroupingDemo2'; import {GroupingWithSelectionDemo} from './GroupingWithSelectionDemo'; import {ReorderingDemo} from './ReorderingDemo'; import {ReorderingTreeDemo} from './ReorderingTreeDemo'; +import {ReorderingWithVirtualizationDemo} from './ReorderingWithVirtualizationDemo'; import {SortingDemo} from './SortingDemo'; import {TreeDemo} from './TreeDemo'; import {TreeWithGroupsDemo} from './TreeWithGroupsDemo'; @@ -185,6 +186,21 @@ WindowVirtualization.args = { onSelectedChange: undefined, }; +const ReorderingWithVirtualizationTemplate: StoryFn> = (args) => ( + +); + +export const ReorderingWithVirtualization: StoryFn> = + ReorderingWithVirtualizationTemplate.bind({}); + +ReorderingWithVirtualization.args = { + data: generateData(300), + columns, + getRowId: (item: Item) => item.id, + onRowClick: undefined, + onSelectedChange: undefined, +}; + export const Resizing: StoryFn> = Template.bind({}); Resizing.args = { diff --git a/src/components/__stories__/ReorderingWithVirtualizationDemo.tsx b/src/components/__stories__/ReorderingWithVirtualizationDemo.tsx new file mode 100644 index 0000000..ca5590c --- /dev/null +++ b/src/components/__stories__/ReorderingWithVirtualizationDemo.tsx @@ -0,0 +1,55 @@ +import React from 'react'; + +import type {TableProps} from '../../components/Table'; +import {Table} from '../../components/Table'; +import {withTableReorder} from '../../hocs'; +import type {SortableListDragResult} from '../SortableList'; +import {WindowVirtualizationProvider} from '../WindowVirtualizationProvider'; + +const GridWithReordering = withTableReorder(Table); + +const rowHeight = 20; +const estimateRowSize = () => rowHeight; + +export const ReorderingWithVirtualizationDemo = ( + props: TableProps, +) => { + const {getRowId} = props; + + const [data, setData] = React.useState(props.data); + + const handleDragEnd = React.useCallback( + ({draggedItemKey, baseItemKey}: SortableListDragResult) => { + setData((data) => { + const dataClone = data.slice(); + + const index = dataClone.findIndex((item) => getRowId(item) === draggedItemKey); + if (index >= 0) { + const dragged = dataClone.splice(index, 1)[0]!; + + const insertIndex = dataClone.findIndex( + (value) => getRowId(value) === baseItemKey, + ); + if (insertIndex >= 0) { + dataClone.splice(insertIndex + 1, 0, dragged); + } else { + dataClone.unshift(dragged); + } + } + + return dataClone; + }); + }, + [getRowId], + ); + + return ( + + {...props} data={data} onReorder={handleDragEnd} /> + + ); +}; diff --git a/src/components/utils/getVirtualRangeExtractor.ts b/src/components/utils/getVirtualRangeExtractor.ts new file mode 100644 index 0000000..b2994ec --- /dev/null +++ b/src/components/utils/getVirtualRangeExtractor.ts @@ -0,0 +1,17 @@ +import {defaultRangeExtractor} from '@tanstack/react-virtual'; +import type {Range} from '@tanstack/react-virtual'; + +export function getVirtualRangeExtractor(container?: HTMLElement | null) { + return function (range: Range) { + const table = container?.querySelector('[data-draggable-index]'); + const draggableItemIndex = Number(table?.getAttribute('data-draggable-index') ?? -1); + + const result = defaultRangeExtractor(range); + + if (draggableItemIndex !== -1 && !result.includes(draggableItemIndex)) { + result.push(draggableItemIndex); + } + + return result; + }; +}