From c081c9521090ca3b1d233dd4e95ed8f789ee2124 Mon Sep 17 00:00:00 2001 From: Nathan Date: Tue, 18 Nov 2025 16:33:18 +0800 Subject: [PATCH] fix: board drop when open in document --- .../components/board/column/CardList.tsx | 124 +++++++++++------- .../database/components/board/group/Group.tsx | 2 + 2 files changed, 81 insertions(+), 45 deletions(-) diff --git a/src/components/database/components/board/column/CardList.tsx b/src/components/database/components/board/column/CardList.tsx index 5693d73e..b23e59b8 100644 --- a/src/components/database/components/board/column/CardList.tsx +++ b/src/components/database/components/board/column/CardList.tsx @@ -1,10 +1,9 @@ import { useVirtualizer } from '@tanstack/react-virtual'; -import { memo, useCallback, useLayoutEffect, useMemo, useRef } from 'react'; +import { memo, useMemo, useCallback, useRef, useLayoutEffect } from 'react'; import { PADDING_END } from '@/application/database-yjs'; import { useBoardContext } from '@/components/database/board/BoardProvider'; import { Card } from '@/components/database/components/board/card'; -import { getScrollParent } from '@/components/global-comment/utils'; import { cn } from '@/lib/utils'; export enum CardType { @@ -17,16 +16,22 @@ export interface RenderCard { id: string; } +// Threshold for enabling virtualization - only virtualize when cards exceed this count +// This prevents scroll jumping issues for small lists while maintaining performance for large ones +const VIRTUALIZATION_THRESHOLD = 100; + function CardList({ data, fieldId, columnId, setScrollElement, + virtualizationThreshold = VIRTUALIZATION_THRESHOLD, }: { columnId: string; data: RenderCard[]; fieldId: string; setScrollElement?: (element: HTMLDivElement | null) => void; + virtualizationThreshold?: number; }) { const parentRef = useRef(null); const parentOffsetRef = useRef(0); @@ -47,34 +52,36 @@ function CardList({ [columnId, setCreatingColumnId] ); + // Determine if we should use virtualization based on data size + const shouldVirtualize = data.length > virtualizationThreshold; + useLayoutEffect(() => { - parentOffsetRef.current = parentRef.current?.getBoundingClientRect()?.top ?? 0; - }, []); - - const getScrollElement = useCallback(() => { - if (!parentRef.current) return null; - const el = (parentRef.current.closest('.appflowy-scroll-container') || - getScrollParent(parentRef.current)) as HTMLDivElement; - - if (setScrollElement) { - setScrollElement(el); + if (shouldVirtualize) { + parentOffsetRef.current = parentRef.current?.getBoundingClientRect()?.top ?? 0; } + }, [shouldVirtualize]); - return el; - }, [setScrollElement]); + // Only use virtualization for large datasets to prevent scroll jumping on small lists + const getScrollElement = useCallback(() => { + if (!shouldVirtualize || !parentRef.current) return null; + // For large lists, attach to parent container (local scroll) instead of document scroll + // This prevents scroll jumping when Board mounts + return parentRef.current; + }, [shouldVirtualize]); const virtualizer = useVirtualizer({ count: data.length, - scrollMargin: parentOffsetRef.current, + scrollMargin: shouldVirtualize ? parentOffsetRef.current : 0, overscan: 5, getScrollElement, estimateSize: () => 36, paddingStart: 0, paddingEnd: PADDING_END, getItemKey: (index) => data[index].id || String(index), + enabled: shouldVirtualize, }); - const items = virtualizer.getVirtualItems(); + const items = shouldVirtualize ? virtualizer.getVirtualItems() : data.map((row, index) => ({ index, key: row.id })); return (
- {items.map((virtualRow) => { - const row = data[virtualRow.index]; + {items.map((item) => { + const index = item.index; + const row = data[index]; const { id, type } = row; - return ( -
- -
- ); + if (shouldVirtualize) { + // Virtualized rendering for large lists + return ( +
+ +
+ ); + } else { + // Direct rendering for small lists (prevents scroll jumping) + return ( +
+ +
+ ); + } })}
diff --git a/src/components/database/components/board/group/Group.tsx b/src/components/database/components/board/group/Group.tsx index b0ea0cb0..c84de958 100644 --- a/src/components/database/components/board/group/Group.tsx +++ b/src/components/database/components/board/group/Group.tsx @@ -101,6 +101,7 @@ export const Group = ({ groupId }: GroupProps) => { return cleanup; }, [verticalScrollContainer, readOnly, contextValue.instanceId]); + // Sticky header scroll listener useEffect(() => { const inner = innerRef.current; const columnsEl = ref.current; @@ -142,6 +143,7 @@ export const Group = ({ groupId }: GroupProps) => { if (!fieldId) return null; if (readOnly && columns.length === 0) return null; + return (