feat(ui): unify list virtualization on @tanstack/react-virtual and polish scroll behavior
- Migrate sidebar session groups, git changes panel, virtualized code blocks, and JSON tree viewer from virtua to @tanstack/react-virtual; virtua remains only inside the Pierre diff viewer integration - Sidebar: preserve scroll position when virtualization enables mid-session (enable only once the ancestor scroll element is resolved, seed initial offset from its live scrollTop, render plain rows for the single pre-paint frame); disable native scroll anchoring on the sessions scroller; keep row spacing identical between plain and virtualized modes; absolute row positioning so variable-height rows cannot drift past the container - Chat: expand tool/thinking blocks downward by only adjusting scroll for rows growing above the viewport; raise the desktop history-load lead to 1.5 viewports so prepends land above the visible area - Git changes: compute the prefetch window from the first visible row, skipping overscan rows above the viewport - Sidebar rows: make the whole highlighted row area clickable, guarded against double-firing from interactive children
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
*
|
||||
* Renders large code/read outputs without mounting one highlighter per line:
|
||||
* 1. ONE worker tokenization of the whole block (off the main thread)
|
||||
* 2. virtua to only render visible rows
|
||||
* 2. @tanstack/react-virtual to only render visible rows
|
||||
*
|
||||
* Tokenizing the whole block at once also preserves cross-line syntax context
|
||||
* (multi-line strings/comments) that per-line highlighting loses. Colors resolve
|
||||
@@ -11,7 +11,7 @@
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Virtualizer } from 'virtua';
|
||||
import { useVirtualizer } from '@tanstack/react-virtual';
|
||||
import { useThemeSystem } from '@/contexts/useThemeSystem';
|
||||
import { getMarkdownSyntaxVars } from '@/components/chat/markdown/markdownTheme';
|
||||
import { useWorkerHighlightedLines } from '@/components/code/useWorkerHighlightedLines';
|
||||
@@ -114,28 +114,41 @@ const VirtualizedRows: React.FC<VirtualizedRowsProps> = React.memo(({
|
||||
const parentRef = React.useRef<HTMLDivElement>(null);
|
||||
const viewportHeight = `min(${lines.length * ROW_HEIGHT}px, ${maxHeight})`;
|
||||
|
||||
const virtualizer = useVirtualizer<HTMLDivElement, HTMLDivElement>({
|
||||
count: lines.length,
|
||||
getScrollElement: () => parentRef.current,
|
||||
estimateSize: () => ROW_HEIGHT,
|
||||
overscan: 20,
|
||||
});
|
||||
const virtualItems = virtualizer.getVirtualItems();
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={parentRef}
|
||||
className="typography-code font-mono w-full min-w-0"
|
||||
style={{ ...(syntaxVars as React.CSSProperties), height: viewportHeight, maxHeight, overflow: 'auto' }}
|
||||
>
|
||||
<Virtualizer
|
||||
data={lines}
|
||||
itemSize={ROW_HEIGHT}
|
||||
bufferSize={ROW_HEIGHT * 20}
|
||||
scrollRef={parentRef}
|
||||
>
|
||||
{(line, index) => (
|
||||
<Row
|
||||
key={index}
|
||||
line={line}
|
||||
html={highlighted?.[index]}
|
||||
showLineNumbers={showLineNumbers}
|
||||
style={lineStyles?.(line)}
|
||||
/>
|
||||
)}
|
||||
</Virtualizer>
|
||||
<div style={{ height: virtualizer.getTotalSize(), position: 'relative' }}>
|
||||
{virtualItems.map((item) => {
|
||||
const line = lines[item.index];
|
||||
if (!line) return null;
|
||||
return (
|
||||
<div
|
||||
key={item.index}
|
||||
data-index={item.index}
|
||||
ref={virtualizer.measureElement}
|
||||
style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${item.start}px)` }}
|
||||
>
|
||||
<Row
|
||||
line={line}
|
||||
html={highlighted?.[item.index]}
|
||||
showLineNumbers={showLineNumbers}
|
||||
style={lineStyles?.(line)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user