fix(downloads): keep collapse rows mounted across viewport

This commit is contained in:
Sucukdeluxe
2026-08-14 23:11:05 +02:00
parent 56aed073f3
commit 318a4f6913
3 changed files with 65 additions and 2 deletions
@@ -3,6 +3,7 @@ import { buildDownloadLogicalRows, type DownloadLogicalRow } from "./downloads-m
import {
activateDownloadDisclosureTransition,
DOWNLOAD_DISCLOSURE_DURATION_MS,
getDownloadDisclosurePinnedIds,
mergeDownloadDisclosureRows,
prepareDownloadDisclosureTransition,
scheduleDownloadDisclosureActivation,
@@ -98,6 +99,7 @@ export function VirtualizedDownloadsBody({ actions, model, state }: { actions: D
desiredRowsRef.current = desiredRows;
const previousRowsRef = useRef<readonly DownloadLogicalRow[]>(desiredRows);
const transitionRowsRef = useRef<DownloadDisclosureRow[] | null>(null);
const transitionPinnedIdsRef = useRef<string[]>([]);
const [transitionRows, setTransitionRows] = useState<DownloadDisclosureRow[] | null>(null);
useEffect(() => {
@@ -108,10 +110,16 @@ export function VirtualizedDownloadsBody({ actions, model, state }: { actions: D
const prepared = prepareDownloadDisclosureTransition(transitionRowsRef.current ?? previousRowsRef.current, desiredRowsRef.current);
if (!prepared.animated) {
transitionRowsRef.current = null;
transitionPinnedIdsRef.current = [];
previousRowsRef.current = desiredRowsRef.current;
setTransitionRows(null);
return;
}
transitionPinnedIdsRef.current = getDownloadDisclosurePinnedIds(prepared.rows, {
scrollTop: viewport.scrollTop,
viewportHeight: viewport.viewportHeight,
overscan: DOWNLOAD_VIRTUAL_OVERSCAN_ROWS
});
transitionRowsRef.current = prepared.rows;
setTransitionRows(prepared.rows);
let settleTimer = 0;
@@ -122,6 +130,7 @@ export function VirtualizedDownloadsBody({ actions, model, state }: { actions: D
settleTimer = window.setTimeout(() => {
previousRowsRef.current = desiredRowsRef.current;
transitionRowsRef.current = null;
transitionPinnedIdsRef.current = [];
setTransitionRows(null);
}, DOWNLOAD_DISCLOSURE_DURATION_MS);
});
@@ -136,7 +145,7 @@ export function VirtualizedDownloadsBody({ actions, model, state }: { actions: D
scrollTop: viewport.scrollTop,
viewportHeight: viewport.viewportHeight,
overscan: DOWNLOAD_VIRTUAL_OVERSCAN_ROWS,
pinnedIds: [model.editingPackageId]
pinnedIds: [model.editingPackageId, ...transitionPinnedIdsRef.current]
}), [model.editingPackageId, renderedRows, viewport.scrollTop, viewport.viewportHeight]);
const spacerStyle = { "--downloads-virtual-total-height": `${virtualWindow.totalHeight}px` } as CSSProperties;
@@ -1,5 +1,9 @@
import type { DownloadLogicalRow } from "./downloads-model";
import { DOWNLOAD_FILE_ROW_HEIGHT } from "./download-virtualizer";
import {
DOWNLOAD_FILE_ROW_HEIGHT,
calculateDownloadVirtualWindow,
type DownloadVirtualWindowOptions
} from "./download-virtualizer";
export const DOWNLOAD_DISCLOSURE_DURATION_MS = 300;
export const DOWNLOAD_DISCLOSURE_MAX_ANIMATED_ITEMS = 64;
@@ -149,3 +153,13 @@ export function activateDownloadDisclosureTransition(rows: readonly DownloadDisc
: { ...row, height: 0, disclosureOpacity: 0 };
});
}
export function getDownloadDisclosurePinnedIds(
preparedRows: readonly DownloadDisclosureRow[],
options: DownloadVirtualWindowOptions
): string[] {
const windowOptions = { ...options, pinnedIds: [] };
const startRows = calculateDownloadVirtualWindow(preparedRows, windowOptions).rows;
const targetRows = calculateDownloadVirtualWindow(activateDownloadDisclosureTransition(preparedRows), windowOptions).rows;
return [...new Set([...startRows, ...targetRows].map((entry) => entry.id))];
}