mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2026-03-13 10:00:26 +08:00
fix: board drop when open in document
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user