clamp album group title, improve height calulation

This commit is contained in:
jeffvli
2026-07-14 15:04:40 -07:00
parent 5c7a644a71
commit 14e66c3ec9
4 changed files with 93 additions and 9 deletions
@@ -55,9 +55,14 @@
}
.album-title {
display: -webkit-box;
overflow: hidden;
-webkit-line-clamp: 3;
line-clamp: 3;
font-weight: 500;
overflow-wrap: anywhere;
white-space: normal;
-webkit-box-orient: vertical;
}
.metadata-row {
@@ -123,7 +123,13 @@ export const AlbumGroupHeader = ({
setResolved({ forInfoHeight: infoHeight, height: resolvedHeight });
}
if (groupKey !== undefined && setAlbumGroupContentHeight) {
// Only persist heights that exceed the image/row floor. Equal values
// still replaced the Map and re-rendered the virtualizer on mount.
if (
groupKey !== undefined &&
setAlbumGroupContentHeight &&
contentHeight > (infoHeight ?? 0)
) {
setAlbumGroupContentHeight(groupKey, contentHeight);
}
};
@@ -135,6 +141,7 @@ export const AlbumGroupHeader = ({
return () => resizeObserver.disconnect();
}, [
albumImageSize,
groupKey,
groupRowCount,
infoHeight,
@@ -384,6 +384,35 @@ export const ItemTableListColumn = memo(ItemTableListColumnBase, (prevProps, nex
const NonMutedColumns = [TableColumn.TITLE, TableColumn.TITLE_ARTIST, TableColumn.TITLE_COMBINED];
/**
* Stable content-height estimate for album-group info (title + metadata + controls).
* Used by the virtualizer before a group header mounts/measures, so scrolling in
* new groups does not jump when measured height is written later.
* Keep in sync with album-group-header styles (title line-clamp, metadata xs, controls).
*/
export function estimateAlbumGroupContentHeight({
hasEnlargedImage,
metadataRowCount,
showControls,
}: {
hasEnlargedImage: boolean;
metadataRowCount: number;
showControls: boolean;
}): number {
const TITLE_LINE_HEIGHT = 20;
const TITLE_MAX_LINES = 3;
const METADATA_LINE_HEIGHT = 18;
const CONTROLS_HEIGHT = 26;
const PADDING_TOP = hasEnlargedImage ? 8 : 0;
return (
PADDING_TOP +
TITLE_LINE_HEIGHT * TITLE_MAX_LINES +
Math.max(0, metadataRowCount) * METADATA_LINE_HEIGHT +
(showControls ? CONTROLS_HEIGHT : 0)
);
}
/** 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;
@@ -568,8 +597,10 @@ function getAlbumGroupClampHeight(props: ItemTableListInnerColumn): null | numbe
);
const groupStartItem = props.getRowItem?.(groupStartRowIndex);
const groupHeightKey = getAlbumGroupHeightKey(groupStartItem, groupRowCount);
const contentHeight =
(groupHeightKey ? props.albumGroupContentHeights?.get(groupHeightKey) : undefined) ?? 0;
const measuredContentHeight = groupHeightKey
? props.albumGroupContentHeights?.get(groupHeightKey)
: undefined;
const contentHeight = measuredContentHeight ?? props.estimatedAlbumGroupContentHeight ?? 0;
const totalGroupHeight = getAlbumGroupSpanHeight(
groupRowCount,
baseHeight,
@@ -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 {
estimateAlbumGroupContentHeight,
getAlbumGroupHeightKey,
getAlbumGroupRowCount,
getAlbumGroupSpanHeight,
@@ -73,7 +74,12 @@ import {
ItemTableListColumnConfig,
} from '/@/renderer/components/item-list/types';
import { PlayerContext, usePlayer } from '/@/renderer/features/player/context/player-context';
import { useAlbumGroupImageSize, usePlayerStore } from '/@/renderer/store';
import {
useAlbumGroupImageSize,
useAlbumGroupItems,
useAlbumGroupShowFavoriteRating,
usePlayerStore,
} from '/@/renderer/store';
import { animationProps } from '/@/shared/components/animations/animation-props';
import { useFocusWithin } from '/@/shared/hooks/use-focus-within';
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
@@ -183,6 +189,7 @@ interface VirtualizedTableGridProps {
data: unknown[];
dataWithGroups: (null | unknown)[];
enableScrollShadow: boolean;
estimatedAlbumGroupContentHeight: number;
getItem?: (index: number) => undefined | unknown;
headerHeight: number;
mergedRowRef: React.Ref<HTMLDivElement>;
@@ -208,6 +215,7 @@ const VirtualizedTableGrid = ({
data,
dataWithGroups,
enableScrollShadow,
estimatedAlbumGroupContentHeight,
getItem,
headerHeight,
mergedRowRef,
@@ -403,6 +411,7 @@ const VirtualizedTableGrid = ({
enableRowHoverHighlight: tableConfig.enableRowHoverHighlight,
enableSelection: tableConfig.enableSelection,
enableVerticalBorders: tableConfig.enableVerticalBorders,
estimatedAlbumGroupContentHeight,
getAdjustedRowIndex,
getGroupRenderData,
getRowHeight: tableConfig.getRowHeight,
@@ -428,6 +437,7 @@ const VirtualizedTableGrid = ({
albumGroupImageSize,
calculatedColumnWidths,
dataWithGroups,
estimatedAlbumGroupContentHeight,
getAdjustedRowIndex,
getGroupRenderData,
getRowItem,
@@ -736,6 +746,8 @@ const MemoizedVirtualizedTableGrid = memo(VirtualizedTableGrid, (prevProps, next
nextProps.calculatedColumnWidths,
) &&
prevProps.albumGroupContentHeights === nextProps.albumGroupContentHeights &&
prevProps.estimatedAlbumGroupContentHeight ===
nextProps.estimatedAlbumGroupContentHeight &&
prevProps.setAlbumGroupContentHeight === nextProps.setAlbumGroupContentHeight &&
prevProps.tableConfig === nextProps.tableConfig &&
prevProps.data === nextProps.data &&
@@ -776,6 +788,7 @@ export interface TableItemProps {
adjustedRowIndexMap?: Map<number, number>;
albumGroupContentHeights?: Map<string, number>;
albumGroupImageSize?: number;
estimatedAlbumGroupContentHeight?: number;
calculatedColumnWidths?: number[];
cellPadding?: ItemTableListProps['cellPadding'];
columns: ItemTableListColumnConfig[];
@@ -1297,6 +1310,21 @@ const BaseItemTableList = ({
const { playlistId: routePlaylistId } = useParams() as { playlistId?: string };
const tableId = useId();
const albumGroupImageSize = useAlbumGroupImageSize();
const albumGroupItems = useAlbumGroupItems();
const albumGroupShowFavoriteRating = useAlbumGroupShowFavoriteRating();
const albumGroupMetadataRowCount = useMemo(
() => albumGroupItems.filter((item) => !item.disabled).length,
[albumGroupItems],
);
const estimatedAlbumGroupContentHeight = useMemo(
() =>
estimateAlbumGroupContentHeight({
hasEnlargedImage: (albumGroupImageSize || 96) > 0,
metadataRowCount: albumGroupMetadataRowCount,
showControls: albumGroupShowFavoriteRating,
}),
[albumGroupImageSize, albumGroupMetadataRowCount, albumGroupShowFavoriteRating],
);
const baseItemCount = itemCount ?? data.length;
const [albumGroupContentHeights, setAlbumGroupContentHeights] = useState(
() => new Map<string, number>(),
@@ -1463,10 +1491,12 @@ const BaseItemTableList = ({
);
const groupStartItem = cellProps.getRowItem?.(groupStartRowIndex);
const groupHeightKey = getAlbumGroupHeightKey(groupStartItem, groupRowCount);
const contentHeight =
(groupHeightKey
? cellProps.albumGroupContentHeights?.get(groupHeightKey)
: undefined) ?? 0;
const measuredContentHeight = groupHeightKey
? cellProps.albumGroupContentHeights?.get(groupHeightKey)
: undefined;
// Prefer measured height when present; otherwise reserve with a stable
// estimate so newly virtualized groups do not jump after mount measure.
const contentHeight = measuredContentHeight ?? estimatedAlbumGroupContentHeight;
const totalGroupHeight = getAlbumGroupSpanHeight(
groupRowCount,
baseHeight,
@@ -1482,7 +1512,15 @@ const BaseItemTableList = ({
return baseHeight;
},
[albumGroupImageSize, enableHeader, headerHeight, rowHeight, pinnedRowCount, size],
[
albumGroupImageSize,
enableHeader,
estimatedAlbumGroupContentHeight,
headerHeight,
rowHeight,
pinnedRowCount,
size,
],
);
const scrollCellProps = useMemo<TableItemProps>(
@@ -1500,6 +1538,7 @@ const BaseItemTableList = ({
enableRowHoverHighlight,
enableSelection,
enableVerticalBorders,
estimatedAlbumGroupContentHeight,
getRowHeight,
getRowItem: (rowIndex: number) => {
if (shouldUseAccessor && getItem) {
@@ -1532,6 +1571,7 @@ const BaseItemTableList = ({
enableRowHoverHighlight,
enableSelection,
enableVerticalBorders,
estimatedAlbumGroupContentHeight,
getItem,
getRowHeight,
hasAlbumGroupColumn,
@@ -1887,6 +1927,7 @@ const BaseItemTableList = ({
data={data}
dataWithGroups={dataWithGroups}
enableScrollShadow={enableScrollShadow}
estimatedAlbumGroupContentHeight={estimatedAlbumGroupContentHeight}
getItem={getItem}
headerHeight={headerHeight}
mergedRowRef={mergedRowRef}