Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions src/components/tags/TagTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -78,24 +78,35 @@ export function TagTree({ onOpenTagSettings }: TagTreeProps = {}) {
return map;
}, [flatTags]);

// Always keep a ref to the latest tagIndexMap so the scroll effect can read
// current indices without subscribing to map changes. The ref is updated on
// every render (outside any effect), so it is always fresh when the effect fires.
const tagIndexMapRef = useRef(tagIndexMap);
tagIndexMapRef.current = tagIndexMap;
const virtualizer = useVirtualizer({
count: flatTags.length,
getScrollElement: () => scrollContainerRef.current,
estimateSize: () => 32,
overscan: 20,
});

// Scroll to selected tag
// Remeasure virtualizer when tree structure changes (expansion/collapse)
useEffect(() => {
virtualizer.measure();
}, [flatTags, virtualizer]);

// Scroll to selected tag only when the selection changes. We intentionally
// read tagIndexMapRef.current (not tagIndexMap directly) so that accordion
// expand/collapse — which rebuilds tagIndexMap but does not change
// selectedTagId — does NOT trigger a scroll back to the active tag.
useEffect(() => {
if (selectedTagId) {
const index = tagIndexMap.get(selectedTagId);
const index = tagIndexMapRef.current.get(selectedTagId);
if (index !== undefined) {
setTimeout(() => {
virtualizer.scrollToIndex(index, { align: 'auto', behavior: 'smooth' });
}, 50);
virtualizer.scrollToIndex(index, { align: 'center', behavior: 'smooth' });
}
}
}, [selectedTagId, tagIndexMap, virtualizer]);
}, [selectedTagId, virtualizer]);

const [contextMenu, setContextMenu] = useState<{
position: { x: number; y: number } | null;
Expand Down
Loading