From 2e6e7839c1270d97536c388ef946453d94ff6144 Mon Sep 17 00:00:00 2001 From: jeffvli Date: Sat, 11 Jul 2026 19:20:22 -0700 Subject: [PATCH] fix album group height calculation on index reorder --- .../item-table-list/album-group-header.tsx | 30 +++++++++++++------ .../columns/album-group-column.tsx | 9 ++++-- .../hooks/use-table-scroll-to-index.ts | 2 +- .../item-table-list-column.tsx | 21 ++++++++++++- .../item-table-list/item-table-list.tsx | 23 ++++++++------ 5 files changed, 63 insertions(+), 22 deletions(-) diff --git a/src/renderer/components/item-list/item-table-list/album-group-header.tsx b/src/renderer/components/item-list/item-table-list/album-group-header.tsx index e0fc90856..39221ecf1 100644 --- a/src/renderer/components/item-list/item-table-list/album-group-header.tsx +++ b/src/renderer/components/item-list/item-table-list/album-group-header.tsx @@ -31,21 +31,21 @@ import { LibraryItem, Song } from '/@/shared/types/domain-types'; import { Play } from '/@/shared/types/types'; interface AlbumGroupHeaderProps { + groupKey?: string; groupRowCount?: number; metadata: AlbumGroupMetadata; onPlay?: (playType: Play) => void; - rowIndex?: number; - setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void; + setAlbumGroupContentHeight?: (groupKey: string, height: number) => void; size?: AlbumGroupTextSize; song: Song | undefined; storedContentHeight?: number; } export const AlbumGroupHeader = ({ + groupKey, groupRowCount, metadata, onPlay, - rowIndex, setAlbumGroupContentHeight, size = 'normal', song, @@ -55,7 +55,9 @@ export const AlbumGroupHeader = ({ const albumGroupItems = useAlbumGroupItems(); const showFavoriteRating = useAlbumGroupShowFavoriteRating(); const [isHovered, setIsHovered] = useState(false); - const [resolvedInfoHeight, setResolvedInfoHeight] = useState(); + const [resolved, setResolved] = useState( + null, + ); const playButtonBehavior = usePlayButtonBehavior(); const albumImageSize = useAlbumGroupImageSize(); const rowHeight = { @@ -87,6 +89,13 @@ export const AlbumGroupHeader = ({ : groupRowCount * rowHeight : undefined; + // Ignore resolved height from a previous (larger) group span so minHeight + // cannot keep scrollHeight measurement stuck after a split/shrink. + const resolvedInfoHeight = + resolved && infoHeight !== undefined && resolved.forInfoHeight === infoHeight + ? resolved.height + : undefined; + const imageContainerStyle = albumImageSize > 0 ? { @@ -108,12 +117,14 @@ export const AlbumGroupHeader = ({ const measure = () => { const contentHeight = infoEl.scrollHeight; - const resolved = Math.max(infoHeight ?? 0, contentHeight); + const resolvedHeight = Math.max(infoHeight ?? 0, contentHeight); - setResolvedInfoHeight(resolved); + if (infoHeight !== undefined) { + setResolved({ forInfoHeight: infoHeight, height: resolvedHeight }); + } - if (rowIndex !== undefined && setAlbumGroupContentHeight) { - setAlbumGroupContentHeight(rowIndex, contentHeight); + if (groupKey !== undefined && setAlbumGroupContentHeight) { + setAlbumGroupContentHeight(groupKey, contentHeight); } }; @@ -124,9 +135,10 @@ export const AlbumGroupHeader = ({ return () => resizeObserver.disconnect(); }, [ + groupKey, + groupRowCount, infoHeight, metadataRows.length, - rowIndex, setAlbumGroupContentHeight, storedContentHeight, ]); diff --git a/src/renderer/components/item-list/item-table-list/columns/album-group-column.tsx b/src/renderer/components/item-list/item-table-list/columns/album-group-column.tsx index 80d5ce5d1..aecbcdb02 100644 --- a/src/renderer/components/item-list/item-table-list/columns/album-group-column.tsx +++ b/src/renderer/components/item-list/item-table-list/columns/album-group-column.tsx @@ -4,6 +4,7 @@ import { useTranslation } from 'react-i18next'; import { AlbumGroupHeader } from '/@/renderer/components/item-list/item-table-list/album-group-header'; import { computeAlbumGroupMetadata } from '/@/renderer/components/item-list/item-table-list/album-group-metadata'; import { + getAlbumGroupHeightKey, isLastInAlbumGroup, ItemTableListInnerColumn, TableColumnContainer, @@ -89,6 +90,10 @@ export const AlbumGroupColumn = (props: ItemTableListInnerColumn) => { } const metadata = computeAlbumGroupMetadata(groupSongs, groupRowCount, t); + const groupHeightKey = getAlbumGroupHeightKey(item, groupRowCount); + const storedContentHeight = groupHeightKey + ? props.albumGroupContentHeights?.get(groupHeightKey) + : undefined; return ( { isDraggedOver={null} > ); diff --git a/src/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index.ts b/src/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index.ts index 998322dce..335667d30 100644 --- a/src/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index.ts +++ b/src/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index.ts @@ -19,7 +19,7 @@ export const useTableScrollToIndex = ({ rowRef, scrollCellProps, }: { - albumGroupContentHeights: Map; + albumGroupContentHeights: Map; autoScrollToActiveRow: boolean; enableHeader: boolean; getRowHeight: (index: number, cellProps: TableItemProps) => number; diff --git a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx index d700a758a..3991d7ded 100644 --- a/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx +++ b/src/renderer/components/item-list/item-table-list/item-table-list-column.tsx @@ -384,6 +384,22 @@ export const ItemTableListColumn = memo(ItemTableListColumnBase, (prevProps, nex const NonMutedColumns = [TableColumn.TITLE, TableColumn.TITLE_ARTIST, TableColumn.TITLE_COMBINED]; +/** Stable key for album-group content heights (survives row moves; not row index). */ +export function getAlbumGroupHeightKey(item: unknown, groupRowCount?: number): string | undefined { + if (!item || typeof item !== 'object') return undefined; + + let itemKey: string | undefined; + if ('_uniqueId' in item && typeof (item as { _uniqueId?: unknown })._uniqueId === 'string') { + itemKey = (item as { _uniqueId: string })._uniqueId; + } else if ('id' in item && typeof (item as { id?: unknown }).id === 'string') { + itemKey = (item as { id: string }).id; + } + + if (!itemKey) return undefined; + if (groupRowCount === undefined) return itemKey; + return `${itemKey}:${groupRowCount}`; +} + // Counts how many consecutive rows belong to the same album group as `rowIndex`. export function getAlbumGroupRowCount( rowIndex: number, @@ -550,7 +566,10 @@ function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | numbe props.getRowItem, props.enableHeader, ); - const contentHeight = props.albumGroupContentHeights?.get(groupStartRowIndex) ?? 0; + const groupStartItem = props.getRowItem?.(groupStartRowIndex); + const groupHeightKey = getAlbumGroupHeightKey(groupStartItem, groupRowCount); + const contentHeight = + (groupHeightKey ? props.albumGroupContentHeights?.get(groupHeightKey) : undefined) ?? 0; const totalGroupHeight = getAlbumGroupSpanHeight( groupRowCount, baseHeight, diff --git a/src/renderer/components/item-list/item-table-list/item-table-list.tsx b/src/renderer/components/item-list/item-table-list/item-table-list.tsx index 9cebeb637..264693733 100644 --- a/src/renderer/components/item-list/item-table-list/item-table-list.tsx +++ b/src/renderer/components/item-list/item-table-list/item-table-list.tsx @@ -45,6 +45,7 @@ import { useTablePaneSync } from '/@/renderer/components/item-list/item-table-li import { useTableRowModel } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-row-model'; import { useTableScrollToIndex } from '/@/renderer/components/item-list/item-table-list/hooks/use-table-scroll-to-index'; import { + getAlbumGroupHeightKey, getAlbumGroupRowCount, getAlbumGroupSpanHeight, getAlbumGroupStartRowIndex, @@ -176,7 +177,7 @@ const ItemTableScrollShadowRight = memo(function ItemTableScrollShadowRight({ ItemTableScrollShadowRight.displayName = 'ItemTableScrollShadowRight'; interface VirtualizedTableGridProps { - albumGroupContentHeights: Map; + albumGroupContentHeights: Map; calculatedColumnWidths: number[]; CellComponent: JSXElementConstructor>; data: unknown[]; @@ -194,7 +195,7 @@ interface VirtualizedTableGridProps { pinnedRowCount: number; pinnedRowRef: React.RefObject; scrollShadowStore: TableScrollShadowStore; - setAlbumGroupContentHeight: (rowIndex: number, height: number) => void; + setAlbumGroupContentHeight: (groupKey: string, height: number) => void; tableConfig: ItemTableListConfig; totalColumnCount: number; totalRowCount: number; @@ -773,7 +774,7 @@ export interface TableGroupHeader { export interface TableItemProps { adjustedRowIndexMap?: Map; - albumGroupContentHeights?: Map; + albumGroupContentHeights?: Map; albumGroupImageSize?: number; calculatedColumnWidths?: number[]; cellPadding?: ItemTableListProps['cellPadding']; @@ -807,7 +808,7 @@ export interface TableItemProps { pinnedRightColumnWidths?: number[]; playerContext: PlayerContext; playlistId?: string; - setAlbumGroupContentHeight?: (rowIndex: number, height: number) => void; + setAlbumGroupContentHeight?: (groupKey: string, height: number) => void; size?: ItemTableListProps['size']; startRowIndex?: number; tableId: string; @@ -1298,14 +1299,14 @@ const BaseItemTableList = ({ const albumGroupImageSize = useAlbumGroupImageSize(); const baseItemCount = itemCount ?? data.length; const [albumGroupContentHeights, setAlbumGroupContentHeights] = useState( - () => new Map(), + () => new Map(), ); - const setAlbumGroupContentHeight = useCallback((rowIndex: number, height: number) => { + const setAlbumGroupContentHeight = useCallback((groupKey: string, height: number) => { setAlbumGroupContentHeights((prev) => { - if (prev.get(rowIndex) === height) return prev; + if (prev.get(groupKey) === height) return prev; const next = new Map(prev); - next.set(rowIndex, height); + next.set(groupKey, height); return next; }); }, []); @@ -1460,8 +1461,12 @@ const BaseItemTableList = ({ cellProps.getRowItem, cellProps.enableHeader, ); + const groupStartItem = cellProps.getRowItem?.(groupStartRowIndex); + const groupHeightKey = getAlbumGroupHeightKey(groupStartItem, groupRowCount); const contentHeight = - cellProps.albumGroupContentHeights?.get(groupStartRowIndex) ?? 0; + (groupHeightKey + ? cellProps.albumGroupContentHeights?.get(groupHeightKey) + : undefined) ?? 0; const totalGroupHeight = getAlbumGroupSpanHeight( groupRowCount, baseHeight,