fix: board drop when open in document

This commit is contained in:
Nathan
2025-11-18 16:33:18 +08:00
parent ee6b25e076
commit c081c95210
2 changed files with 81 additions and 45 deletions

View File

@@ -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<HTMLDivElement>(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 (
<div
@@ -86,40 +93,67 @@ function CardList({
>
<div
style={{
height: virtualizer.getTotalSize(),
height: shouldVirtualize ? virtualizer.getTotalSize() : undefined,
width: '100%',
position: 'relative',
position: shouldVirtualize ? 'relative' : undefined,
paddingBottom: shouldVirtualize ? undefined : PADDING_END,
}}
>
{items.map((virtualRow) => {
const row = data[virtualRow.index];
{items.map((item) => {
const index = item.index;
const row = data[index];
const { id, type } = row;
return (
<div
key={virtualRow.key}
data-index={virtualRow.index}
ref={virtualizer.measureElement}
className={cn('w-full px-2 py-[3px]', isCreating && 'transform transition-all duration-150 ease-in-out')}
style={{
position: 'absolute',
top: 0,
left: 0,
transform: `translateY(${virtualRow.start - virtualizer.options.scrollMargin}px)`,
paddingTop: virtualRow.index === 0 ? 10 : undefined,
}}
>
<Card
type={type}
rowId={id}
groupFieldId={fieldId}
setIsCreating={setIsCreating}
isCreating={isCreating}
columnId={columnId}
beforeId={data[virtualRow.index - 1]?.id}
/>
</div>
);
if (shouldVirtualize) {
// Virtualized rendering for large lists
return (
<div
key={item.key}
data-index={index}
ref={virtualizer.measureElement}
className={cn('w-full px-2 py-[3px]', isCreating && 'transform transition-all duration-150 ease-in-out')}
style={{
position: 'absolute',
top: 0,
left: 0,
transform: `translateY(${(item as any).start - virtualizer.options.scrollMargin}px)`,
paddingTop: index === 0 ? 10 : undefined,
}}
>
<Card
type={type}
rowId={id}
groupFieldId={fieldId}
setIsCreating={setIsCreating}
isCreating={isCreating}
columnId={columnId}
beforeId={data[index - 1]?.id}
/>
</div>
);
} else {
// Direct rendering for small lists (prevents scroll jumping)
return (
<div
key={item.key}
data-index={index}
className={cn('w-full px-2 py-[3px]', isCreating && 'transform transition-all duration-150 ease-in-out')}
style={{
paddingTop: index === 0 ? 10 : undefined,
}}
>
<Card
type={type}
rowId={id}
groupFieldId={fieldId}
setIsCreating={setIsCreating}
isCreating={isCreating}
columnId={columnId}
beforeId={data[index - 1]?.id}
/>
</div>
);
}
})}
</div>
</div>

View File

@@ -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 (
<BoardDragContext.Provider value={contextValue}>
<div