mirror of
https://github.com/jeffvli/feishin.git
synced 2026-07-15 23:19:58 +02:00
fix queue scroll follow with album group
This commit is contained in:
+17
-3
@@ -4,11 +4,15 @@ import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/i
|
|||||||
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
import { ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||||
|
|
||||||
interface UseTableImperativeHandleProps {
|
interface UseTableImperativeHandleProps {
|
||||||
|
autoScrollToActiveRow: boolean;
|
||||||
enableHeader: boolean;
|
enableHeader: boolean;
|
||||||
handleRef: React.RefObject<ItemListHandle | null>;
|
handleRef: React.RefObject<ItemListHandle | null>;
|
||||||
internalState: ItemListStateActions;
|
internalState: ItemListStateActions;
|
||||||
ref?: React.Ref<ItemListHandle>;
|
ref?: React.Ref<ItemListHandle>;
|
||||||
scrollToTableIndex: (index: number, options?: { align?: 'bottom' | 'center' | 'top' }) => void;
|
scrollToTableIndex: (
|
||||||
|
index: number,
|
||||||
|
options?: { align?: 'bottom' | 'center' | 'top'; followActiveRow?: boolean },
|
||||||
|
) => void;
|
||||||
scrollToTableOffset: (offset: number) => void;
|
scrollToTableOffset: (offset: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -16,6 +20,7 @@ interface UseTableImperativeHandleProps {
|
|||||||
* Hook to set up the imperative handle for ItemTableList, providing scroll methods and internal state.
|
* Hook to set up the imperative handle for ItemTableList, providing scroll methods and internal state.
|
||||||
*/
|
*/
|
||||||
export const useTableImperativeHandle = ({
|
export const useTableImperativeHandle = ({
|
||||||
|
autoScrollToActiveRow,
|
||||||
enableHeader,
|
enableHeader,
|
||||||
handleRef,
|
handleRef,
|
||||||
internalState,
|
internalState,
|
||||||
@@ -27,13 +32,22 @@ export const useTableImperativeHandle = ({
|
|||||||
() => ({
|
() => ({
|
||||||
internalState,
|
internalState,
|
||||||
scrollToIndex: (index: number, options?: { align?: 'bottom' | 'center' | 'top' }) => {
|
scrollToIndex: (index: number, options?: { align?: 'bottom' | 'center' | 'top' }) => {
|
||||||
scrollToTableIndex(enableHeader ? index + 1 : index, options);
|
scrollToTableIndex(enableHeader ? index + 1 : index, {
|
||||||
|
...options,
|
||||||
|
followActiveRow: autoScrollToActiveRow,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
scrollToOffset: (offset: number) => {
|
scrollToOffset: (offset: number) => {
|
||||||
scrollToTableOffset(offset);
|
scrollToTableOffset(offset);
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[enableHeader, internalState, scrollToTableIndex, scrollToTableOffset],
|
[
|
||||||
|
autoScrollToActiveRow,
|
||||||
|
enableHeader,
|
||||||
|
internalState,
|
||||||
|
scrollToTableIndex,
|
||||||
|
scrollToTableOffset,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
useImperativeHandle(ref, () => imperativeHandle);
|
useImperativeHandle(ref, () => imperativeHandle);
|
||||||
|
|||||||
+4
-62
@@ -4,21 +4,17 @@ import {
|
|||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
ItemListStateItemWithRequiredProperties,
|
ItemListStateItemWithRequiredProperties,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
|
||||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
|
||||||
import { PlayerContext } from '/@/renderer/features/player/context/player-context';
|
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
|
|
||||||
interface UseTableKeyboardNavigationProps {
|
interface UseTableKeyboardNavigationProps {
|
||||||
calculateScrollTopForIndex: (index: number) => number;
|
calculateScrollTopForIndex: (index: number) => number;
|
||||||
cellPadding: TableItemProps['cellPadding'];
|
|
||||||
data: unknown[];
|
data: unknown[];
|
||||||
DEFAULT_ROW_HEIGHT: number;
|
|
||||||
enableHeader: boolean;
|
enableHeader: boolean;
|
||||||
enableSelection: boolean;
|
enableSelection: boolean;
|
||||||
extractRowId: (item: unknown) => string | undefined;
|
extractRowId: (item: unknown) => string | undefined;
|
||||||
getItem?: (index: number) => undefined | unknown;
|
getItem?: (index: number) => undefined | unknown;
|
||||||
getItemIndex?: (rowId: string) => number | undefined;
|
getItemIndex?: (rowId: string) => number | undefined;
|
||||||
|
getRowHeightAtIndex: (index: number) => number;
|
||||||
getStateItem: (item: any) => ItemListStateItemWithRequiredProperties | null;
|
getStateItem: (item: any) => ItemListStateItemWithRequiredProperties | null;
|
||||||
hasRequiredStateItemProperties: (
|
hasRequiredStateItemProperties: (
|
||||||
item: unknown,
|
item: unknown,
|
||||||
@@ -26,15 +22,10 @@ interface UseTableKeyboardNavigationProps {
|
|||||||
internalState: ItemListStateActions;
|
internalState: ItemListStateActions;
|
||||||
itemCount?: number;
|
itemCount?: number;
|
||||||
itemType: LibraryItem;
|
itemType: LibraryItem;
|
||||||
parsedColumns: TableItemProps['columns'];
|
|
||||||
pinnedRightColumnCount: number;
|
pinnedRightColumnCount: number;
|
||||||
pinnedRightColumnRef: React.RefObject<HTMLDivElement | null>;
|
pinnedRightColumnRef: React.RefObject<HTMLDivElement | null>;
|
||||||
playerContext: PlayerContext;
|
|
||||||
rowHeight: ((index: number, cellProps: TableItemProps) => number) | number | undefined;
|
|
||||||
rowRef: React.RefObject<HTMLDivElement | null>;
|
rowRef: React.RefObject<HTMLDivElement | null>;
|
||||||
scrollToTableIndex: (index: number, options?: { align?: 'bottom' | 'center' | 'top' }) => void;
|
scrollToTableIndex: (index: number, options?: { align?: 'bottom' | 'center' | 'top' }) => void;
|
||||||
size: TableItemProps['size'];
|
|
||||||
tableId: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,28 +33,21 @@ interface UseTableKeyboardNavigationProps {
|
|||||||
*/
|
*/
|
||||||
export const useTableKeyboardNavigation = ({
|
export const useTableKeyboardNavigation = ({
|
||||||
calculateScrollTopForIndex,
|
calculateScrollTopForIndex,
|
||||||
cellPadding,
|
|
||||||
data,
|
data,
|
||||||
DEFAULT_ROW_HEIGHT,
|
|
||||||
enableHeader,
|
enableHeader,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
extractRowId,
|
extractRowId,
|
||||||
getItem,
|
getItem,
|
||||||
getItemIndex,
|
getItemIndex,
|
||||||
|
getRowHeightAtIndex,
|
||||||
getStateItem,
|
getStateItem,
|
||||||
hasRequiredStateItemProperties,
|
hasRequiredStateItemProperties,
|
||||||
internalState,
|
internalState,
|
||||||
itemCount,
|
itemCount,
|
||||||
itemType,
|
|
||||||
parsedColumns,
|
|
||||||
pinnedRightColumnCount,
|
pinnedRightColumnCount,
|
||||||
pinnedRightColumnRef,
|
pinnedRightColumnRef,
|
||||||
playerContext,
|
|
||||||
rowHeight,
|
|
||||||
rowRef,
|
rowRef,
|
||||||
scrollToTableIndex,
|
scrollToTableIndex,
|
||||||
size,
|
|
||||||
tableId,
|
|
||||||
}: UseTableKeyboardNavigationProps) => {
|
}: UseTableKeyboardNavigationProps) => {
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||||
@@ -122,42 +106,7 @@ export const useTableKeyboardNavigation = ({
|
|||||||
const viewportBottom = viewportTop + viewportHeight;
|
const viewportBottom = viewportTop + viewportHeight;
|
||||||
|
|
||||||
const rowTop = calculateScrollTopForIndex(gridIndex);
|
const rowTop = calculateScrollTopForIndex(gridIndex);
|
||||||
const adjustedIndex = enableHeader ? Math.max(0, newIndex - 1) : newIndex;
|
const calculatedRowHeight = getRowHeightAtIndex(gridIndex);
|
||||||
const mockCellProps: TableItemProps = {
|
|
||||||
cellPadding,
|
|
||||||
columns: parsedColumns,
|
|
||||||
controls: {} as ItemControls,
|
|
||||||
data: enableHeader ? [null] : [],
|
|
||||||
enableAlternateRowColors: false,
|
|
||||||
enableExpansion: false,
|
|
||||||
enableHeader,
|
|
||||||
enableHorizontalBorders: false,
|
|
||||||
enableRowHoverHighlight: false,
|
|
||||||
enableSelection,
|
|
||||||
enableVerticalBorders: false,
|
|
||||||
getRowHeight: () => DEFAULT_ROW_HEIGHT,
|
|
||||||
getRowItem: (rowIndex: number) => {
|
|
||||||
if (!getItem) return undefined;
|
|
||||||
if (enableHeader && rowIndex === 0) return null;
|
|
||||||
const dataIndex = enableHeader ? rowIndex - 1 : rowIndex;
|
|
||||||
return getItem(dataIndex);
|
|
||||||
},
|
|
||||||
internalState: {} as ItemListStateActions,
|
|
||||||
itemType,
|
|
||||||
playerContext,
|
|
||||||
size,
|
|
||||||
tableId,
|
|
||||||
};
|
|
||||||
|
|
||||||
let calculatedRowHeight: number;
|
|
||||||
if (typeof rowHeight === 'number') {
|
|
||||||
calculatedRowHeight = rowHeight;
|
|
||||||
} else if (typeof rowHeight === 'function') {
|
|
||||||
calculatedRowHeight = rowHeight(adjustedIndex, mockCellProps);
|
|
||||||
} else {
|
|
||||||
calculatedRowHeight = DEFAULT_ROW_HEIGHT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const rowBottom = rowTop + calculatedRowHeight;
|
const rowBottom = rowTop + calculatedRowHeight;
|
||||||
|
|
||||||
// Check if row is fully visible within viewport
|
// Check if row is fully visible within viewport
|
||||||
@@ -187,28 +136,21 @@ export const useTableKeyboardNavigation = ({
|
|||||||
},
|
},
|
||||||
[
|
[
|
||||||
calculateScrollTopForIndex,
|
calculateScrollTopForIndex,
|
||||||
cellPadding,
|
|
||||||
data,
|
data,
|
||||||
getItem,
|
getItem,
|
||||||
getItemIndex,
|
getItemIndex,
|
||||||
DEFAULT_ROW_HEIGHT,
|
|
||||||
enableHeader,
|
enableHeader,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
extractRowId,
|
extractRowId,
|
||||||
|
getRowHeightAtIndex,
|
||||||
getStateItem,
|
getStateItem,
|
||||||
hasRequiredStateItemProperties,
|
hasRequiredStateItemProperties,
|
||||||
internalState,
|
internalState,
|
||||||
itemCount,
|
itemCount,
|
||||||
itemType,
|
|
||||||
parsedColumns,
|
|
||||||
pinnedRightColumnCount,
|
pinnedRightColumnCount,
|
||||||
pinnedRightColumnRef,
|
pinnedRightColumnRef,
|
||||||
playerContext,
|
|
||||||
rowHeight,
|
|
||||||
rowRef,
|
rowRef,
|
||||||
scrollToTableIndex,
|
scrollToTableIndex,
|
||||||
size,
|
|
||||||
tableId,
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
+89
-100
@@ -1,101 +1,45 @@
|
|||||||
import { useCallback, useMemo } from 'react';
|
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||||
|
|
||||||
import { TableItemProps } from '../item-table-list';
|
import { TableItemProps } from '../item-table-list';
|
||||||
|
|
||||||
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
export type TableScrollToIndexOptions = {
|
||||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
align?: 'bottom' | 'center' | 'top';
|
||||||
import { PlayerContext } from '/@/renderer/features/player/context/player-context';
|
followActiveRow?: boolean;
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
};
|
||||||
|
|
||||||
export const useTableScrollToIndex = ({
|
export const useTableScrollToIndex = ({
|
||||||
cellPadding,
|
albumGroupContentHeights,
|
||||||
columns,
|
autoScrollToActiveRow,
|
||||||
data,
|
|
||||||
enableAlternateRowColors,
|
|
||||||
enableExpansion,
|
|
||||||
enableHeader,
|
enableHeader,
|
||||||
enableHorizontalBorders,
|
getRowHeight,
|
||||||
enableRowHoverHighlight,
|
hasAlbumGroupColumn,
|
||||||
enableSelection,
|
|
||||||
enableVerticalBorders,
|
|
||||||
itemType,
|
|
||||||
pinnedLeftColumnRef,
|
pinnedLeftColumnRef,
|
||||||
pinnedRightColumnRef,
|
pinnedRightColumnRef,
|
||||||
playerContext,
|
pinnedRowCount,
|
||||||
rowHeight,
|
|
||||||
rowRef,
|
rowRef,
|
||||||
size,
|
scrollCellProps,
|
||||||
tableId,
|
|
||||||
}: {
|
}: {
|
||||||
cellPadding: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
albumGroupContentHeights: Map<number, number>;
|
||||||
columns: TableItemProps['columns'];
|
autoScrollToActiveRow: boolean;
|
||||||
data: unknown[];
|
|
||||||
enableAlternateRowColors: boolean;
|
|
||||||
enableExpansion: boolean;
|
|
||||||
enableHeader: boolean;
|
enableHeader: boolean;
|
||||||
enableHorizontalBorders: boolean;
|
getRowHeight: (index: number, cellProps: TableItemProps) => number;
|
||||||
enableRowHoverHighlight: boolean;
|
hasAlbumGroupColumn: boolean;
|
||||||
enableSelection: boolean;
|
|
||||||
enableVerticalBorders: boolean;
|
|
||||||
itemType: LibraryItem;
|
|
||||||
pinnedLeftColumnRef: React.RefObject<HTMLDivElement | null>;
|
pinnedLeftColumnRef: React.RefObject<HTMLDivElement | null>;
|
||||||
pinnedRightColumnRef: React.RefObject<HTMLDivElement | null>;
|
pinnedRightColumnRef: React.RefObject<HTMLDivElement | null>;
|
||||||
playerContext: PlayerContext;
|
pinnedRowCount: number;
|
||||||
rowHeight: ((index: number, cellProps: TableItemProps) => number) | number | undefined;
|
|
||||||
rowRef: React.RefObject<HTMLDivElement | null>;
|
rowRef: React.RefObject<HTMLDivElement | null>;
|
||||||
size: 'compact' | 'default' | 'large';
|
scrollCellProps: TableItemProps;
|
||||||
tableId: string;
|
|
||||||
}) => {
|
}) => {
|
||||||
const DEFAULT_ROW_HEIGHT = useMemo(() => {
|
const isProgrammaticScrollRef = useRef(false);
|
||||||
return size === 'compact' ? 40 : size === 'large' ? 88 : 64;
|
const userInterruptedFollowRef = useRef(false);
|
||||||
}, [size]);
|
const pendingFollowScrollRef = useRef<null | {
|
||||||
|
index: number;
|
||||||
const mockCellPropsBase = useMemo<TableItemProps>(
|
options?: TableScrollToIndexOptions;
|
||||||
() => ({
|
}>(null);
|
||||||
cellPadding,
|
|
||||||
columns,
|
|
||||||
controls: {} as ItemControls,
|
|
||||||
data: enableHeader ? [null, ...data] : data,
|
|
||||||
enableAlternateRowColors,
|
|
||||||
enableExpansion,
|
|
||||||
enableHeader,
|
|
||||||
enableHorizontalBorders,
|
|
||||||
enableRowHoverHighlight,
|
|
||||||
enableSelection,
|
|
||||||
enableVerticalBorders,
|
|
||||||
getRowHeight: () => DEFAULT_ROW_HEIGHT,
|
|
||||||
internalState: {} as ItemListStateActions,
|
|
||||||
itemType,
|
|
||||||
playerContext,
|
|
||||||
size,
|
|
||||||
tableId,
|
|
||||||
}),
|
|
||||||
[
|
|
||||||
DEFAULT_ROW_HEIGHT,
|
|
||||||
cellPadding,
|
|
||||||
columns,
|
|
||||||
data,
|
|
||||||
enableAlternateRowColors,
|
|
||||||
enableExpansion,
|
|
||||||
enableHeader,
|
|
||||||
enableHorizontalBorders,
|
|
||||||
enableRowHoverHighlight,
|
|
||||||
enableSelection,
|
|
||||||
enableVerticalBorders,
|
|
||||||
itemType,
|
|
||||||
playerContext,
|
|
||||||
size,
|
|
||||||
tableId,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const getRowHeightAtIndex = useCallback(
|
const getRowHeightAtIndex = useCallback(
|
||||||
(index: number) => {
|
(index: number) => getRowHeight(index, scrollCellProps),
|
||||||
if (typeof rowHeight === 'number') return rowHeight;
|
[getRowHeight, scrollCellProps],
|
||||||
if (typeof rowHeight === 'function') return rowHeight(index, mockCellPropsBase);
|
|
||||||
return DEFAULT_ROW_HEIGHT;
|
|
||||||
},
|
|
||||||
[DEFAULT_ROW_HEIGHT, mockCellPropsBase, rowHeight],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollToTableOffset = useCallback(
|
const scrollToTableOffset = useCallback(
|
||||||
@@ -110,6 +54,8 @@ export const useTableScrollToIndex = ({
|
|||||||
|
|
||||||
const behavior = 'instant';
|
const behavior = 'instant';
|
||||||
|
|
||||||
|
isProgrammaticScrollRef.current = true;
|
||||||
|
|
||||||
if (mainContainer) {
|
if (mainContainer) {
|
||||||
mainContainer.scrollTo({ behavior, top: offset });
|
mainContainer.scrollTo({ behavior, top: offset });
|
||||||
}
|
}
|
||||||
@@ -119,37 +65,38 @@ export const useTableScrollToIndex = ({
|
|||||||
if (pinnedRightContainer) {
|
if (pinnedRightContainer) {
|
||||||
pinnedRightContainer.scrollTo({ behavior, top: offset });
|
pinnedRightContainer.scrollTo({ behavior, top: offset });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
isProgrammaticScrollRef.current = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
[pinnedLeftColumnRef, pinnedRightColumnRef, rowRef],
|
[pinnedLeftColumnRef, pinnedRightColumnRef, rowRef],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scrollRowOffset = enableHeader ? pinnedRowCount : 0;
|
||||||
|
|
||||||
const calculateScrollTopForIndex = useCallback(
|
const calculateScrollTopForIndex = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
const adjustedIndex = enableHeader ? Math.max(0, index - 1) : index;
|
|
||||||
let scrollTop = 0;
|
let scrollTop = 0;
|
||||||
|
|
||||||
for (let i = 0; i < adjustedIndex; i++) {
|
for (let i = scrollRowOffset; i < index; i++) {
|
||||||
scrollTop += getRowHeightAtIndex(i);
|
scrollTop += getRowHeightAtIndex(i);
|
||||||
}
|
}
|
||||||
return scrollTop;
|
return scrollTop;
|
||||||
},
|
},
|
||||||
[enableHeader, getRowHeightAtIndex],
|
[getRowHeightAtIndex, scrollRowOffset],
|
||||||
);
|
);
|
||||||
|
|
||||||
const scrollToTableIndex = useCallback(
|
const applyScrollToIndex = useCallback(
|
||||||
(index: number, options?: { align?: 'bottom' | 'center' | 'top' }) => {
|
(index: number, options?: TableScrollToIndexOptions) => {
|
||||||
const mainContainer = rowRef.current?.childNodes[0] as HTMLDivElement | undefined;
|
const mainContainer = rowRef.current?.childNodes[0] as HTMLDivElement | undefined;
|
||||||
if (!mainContainer) return;
|
if (!mainContainer) return;
|
||||||
|
|
||||||
const viewportHeight = mainContainer.clientHeight;
|
const viewportHeight = mainContainer.clientHeight;
|
||||||
const align = options?.align || 'top';
|
const align = options?.align || 'top';
|
||||||
|
|
||||||
// Calculate the base scroll offset (top of the row)
|
|
||||||
let offset = calculateScrollTopForIndex(index);
|
let offset = calculateScrollTopForIndex(index);
|
||||||
|
const targetRowHeight = getRowHeightAtIndex(index);
|
||||||
// Calculate row height for the target index
|
|
||||||
const adjustedIndex = enableHeader ? Math.max(0, index - 1) : index;
|
|
||||||
const targetRowHeight = getRowHeightAtIndex(adjustedIndex);
|
|
||||||
|
|
||||||
if (align === 'center') {
|
if (align === 'center') {
|
||||||
offset = offset - viewportHeight / 2 + targetRowHeight / 2;
|
offset = offset - viewportHeight / 2 + targetRowHeight / 2;
|
||||||
@@ -160,22 +107,64 @@ export const useTableScrollToIndex = ({
|
|||||||
offset = Math.max(0, offset);
|
offset = Math.max(0, offset);
|
||||||
scrollToTableOffset(offset);
|
scrollToTableOffset(offset);
|
||||||
},
|
},
|
||||||
[
|
[calculateScrollTopForIndex, getRowHeightAtIndex, rowRef, scrollToTableOffset],
|
||||||
calculateScrollTopForIndex,
|
|
||||||
enableHeader,
|
|
||||||
getRowHeightAtIndex,
|
|
||||||
rowRef,
|
|
||||||
scrollToTableOffset,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scrollToTableIndex = useCallback(
|
||||||
|
(index: number, options?: TableScrollToIndexOptions) => {
|
||||||
|
const shouldFollow = autoScrollToActiveRow && options?.followActiveRow;
|
||||||
|
if (shouldFollow) {
|
||||||
|
pendingFollowScrollRef.current = { index, options };
|
||||||
|
userInterruptedFollowRef.current = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyScrollToIndex(index, options);
|
||||||
|
},
|
||||||
|
[applyScrollToIndex, autoScrollToActiveRow],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const viewport = rowRef.current?.childNodes[0] as HTMLDivElement | undefined;
|
||||||
|
if (!viewport) return;
|
||||||
|
|
||||||
|
const handleUserScroll = () => {
|
||||||
|
if (isProgrammaticScrollRef.current) return;
|
||||||
|
|
||||||
|
userInterruptedFollowRef.current = true;
|
||||||
|
pendingFollowScrollRef.current = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
viewport.addEventListener('scroll', handleUserScroll, { passive: true });
|
||||||
|
|
||||||
|
return () => viewport.removeEventListener('scroll', handleUserScroll);
|
||||||
|
}, [rowRef]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!autoScrollToActiveRow || !hasAlbumGroupColumn) return;
|
||||||
|
|
||||||
|
const pending = pendingFollowScrollRef.current;
|
||||||
|
if (!pending || userInterruptedFollowRef.current) return;
|
||||||
|
if (albumGroupContentHeights.size === 0) return;
|
||||||
|
|
||||||
|
const timeout = window.setTimeout(() => {
|
||||||
|
if (userInterruptedFollowRef.current) return;
|
||||||
|
|
||||||
|
const currentPending = pendingFollowScrollRef.current;
|
||||||
|
if (!currentPending) return;
|
||||||
|
|
||||||
|
applyScrollToIndex(currentPending.index, currentPending.options);
|
||||||
|
}, 50);
|
||||||
|
|
||||||
|
return () => window.clearTimeout(timeout);
|
||||||
|
}, [albumGroupContentHeights, applyScrollToIndex, autoScrollToActiveRow, hasAlbumGroupColumn]);
|
||||||
|
|
||||||
return useMemo(
|
return useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
calculateScrollTopForIndex,
|
calculateScrollTopForIndex,
|
||||||
DEFAULT_ROW_HEIGHT,
|
getRowHeightAtIndex,
|
||||||
scrollToTableIndex,
|
scrollToTableIndex,
|
||||||
scrollToTableOffset,
|
scrollToTableOffset,
|
||||||
}),
|
}),
|
||||||
[calculateScrollTopForIndex, DEFAULT_ROW_HEIGHT, scrollToTableIndex, scrollToTableOffset],
|
[calculateScrollTopForIndex, getRowHeightAtIndex, scrollToTableIndex, scrollToTableOffset],
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -816,6 +816,7 @@ export interface TableItemProps {
|
|||||||
interface ItemTableListProps {
|
interface ItemTableListProps {
|
||||||
activeRowId?: string;
|
activeRowId?: string;
|
||||||
autoFitColumns?: boolean;
|
autoFitColumns?: boolean;
|
||||||
|
autoScrollToActiveRow?: boolean;
|
||||||
CellComponent?: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
CellComponent?: JSXElementConstructor<CellComponentProps<TableItemProps>>;
|
||||||
cellPadding?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
cellPadding?: 'lg' | 'md' | 'sm' | 'xl' | 'xs';
|
||||||
columns: ItemTableListColumnConfig[];
|
columns: ItemTableListColumnConfig[];
|
||||||
@@ -1255,6 +1256,7 @@ ItemTableListStickyUI.displayName = 'ItemTableListStickyUI';
|
|||||||
const BaseItemTableList = ({
|
const BaseItemTableList = ({
|
||||||
activeRowId,
|
activeRowId,
|
||||||
autoFitColumns = false,
|
autoFitColumns = false,
|
||||||
|
autoScrollToActiveRow = false,
|
||||||
CellComponent = ItemTableListColumn,
|
CellComponent = ItemTableListColumn,
|
||||||
cellPadding = 'sm',
|
cellPadding = 'sm',
|
||||||
columns,
|
columns,
|
||||||
@@ -1411,47 +1413,10 @@ const BaseItemTableList = ({
|
|||||||
onScrollEndRef.current = onScrollEnd;
|
onScrollEndRef.current = onScrollEnd;
|
||||||
}, [onScrollEnd]);
|
}, [onScrollEnd]);
|
||||||
|
|
||||||
const {
|
const hasAlbumGroupColumn = useMemo(
|
||||||
calculateScrollTopForIndex,
|
() => parsedColumns.some((col) => col.id === TableColumn.ALBUM_GROUP),
|
||||||
DEFAULT_ROW_HEIGHT,
|
[parsedColumns],
|
||||||
scrollToTableIndex,
|
);
|
||||||
scrollToTableOffset,
|
|
||||||
} = useTableScrollToIndex({
|
|
||||||
cellPadding,
|
|
||||||
columns: parsedColumns,
|
|
||||||
data,
|
|
||||||
enableAlternateRowColors,
|
|
||||||
enableExpansion,
|
|
||||||
enableHeader,
|
|
||||||
enableHorizontalBorders,
|
|
||||||
enableRowHoverHighlight,
|
|
||||||
enableSelection,
|
|
||||||
enableVerticalBorders,
|
|
||||||
itemType,
|
|
||||||
pinnedLeftColumnRef,
|
|
||||||
pinnedRightColumnRef,
|
|
||||||
playerContext,
|
|
||||||
rowHeight,
|
|
||||||
rowRef,
|
|
||||||
size,
|
|
||||||
tableId,
|
|
||||||
});
|
|
||||||
|
|
||||||
useTablePaneSync({
|
|
||||||
enableDrag,
|
|
||||||
enableDragScroll,
|
|
||||||
enableHeader,
|
|
||||||
handleRef,
|
|
||||||
onScrollEndRef,
|
|
||||||
pinnedLeftColumnCount,
|
|
||||||
pinnedLeftColumnRef,
|
|
||||||
pinnedRightColumnCount,
|
|
||||||
pinnedRightColumnRef,
|
|
||||||
pinnedRowRef,
|
|
||||||
rowRef,
|
|
||||||
scrollContainerRef,
|
|
||||||
scrollShadowStore,
|
|
||||||
});
|
|
||||||
|
|
||||||
const getRowHeight = useCallback(
|
const getRowHeight = useCallback(
|
||||||
(index: number, cellProps: TableItemProps) => {
|
(index: number, cellProps: TableItemProps) => {
|
||||||
@@ -1515,6 +1480,99 @@ const BaseItemTableList = ({
|
|||||||
[albumGroupImageSize, enableHeader, headerHeight, rowHeight, pinnedRowCount, size],
|
[albumGroupImageSize, enableHeader, headerHeight, rowHeight, pinnedRowCount, size],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const scrollCellProps = useMemo<TableItemProps>(
|
||||||
|
() => ({
|
||||||
|
albumGroupContentHeights,
|
||||||
|
albumGroupImageSize,
|
||||||
|
cellPadding,
|
||||||
|
columns: parsedColumns,
|
||||||
|
controls: {} as ItemControls,
|
||||||
|
data: dataWithGroups,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableExpansion,
|
||||||
|
enableHeader,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableSelection,
|
||||||
|
enableVerticalBorders,
|
||||||
|
getRowHeight,
|
||||||
|
getRowItem: (rowIndex: number) => {
|
||||||
|
if (shouldUseAccessor && getItem) {
|
||||||
|
if (enableHeader && rowIndex === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataIndex = enableHeader ? rowIndex - 1 : rowIndex;
|
||||||
|
return getItem(dataIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dataWithGroups[rowIndex];
|
||||||
|
},
|
||||||
|
hasAlbumGroupColumn,
|
||||||
|
internalState: {} as ItemListStateActions,
|
||||||
|
itemType,
|
||||||
|
playerContext,
|
||||||
|
size,
|
||||||
|
tableId,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
albumGroupContentHeights,
|
||||||
|
albumGroupImageSize,
|
||||||
|
cellPadding,
|
||||||
|
dataWithGroups,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableExpansion,
|
||||||
|
enableHeader,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableSelection,
|
||||||
|
enableVerticalBorders,
|
||||||
|
getItem,
|
||||||
|
getRowHeight,
|
||||||
|
hasAlbumGroupColumn,
|
||||||
|
itemType,
|
||||||
|
parsedColumns,
|
||||||
|
playerContext,
|
||||||
|
shouldUseAccessor,
|
||||||
|
size,
|
||||||
|
tableId,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
calculateScrollTopForIndex,
|
||||||
|
getRowHeightAtIndex,
|
||||||
|
scrollToTableIndex,
|
||||||
|
scrollToTableOffset,
|
||||||
|
} = useTableScrollToIndex({
|
||||||
|
albumGroupContentHeights,
|
||||||
|
autoScrollToActiveRow,
|
||||||
|
enableHeader,
|
||||||
|
getRowHeight,
|
||||||
|
hasAlbumGroupColumn,
|
||||||
|
pinnedLeftColumnRef,
|
||||||
|
pinnedRightColumnRef,
|
||||||
|
pinnedRowCount,
|
||||||
|
rowRef,
|
||||||
|
scrollCellProps,
|
||||||
|
});
|
||||||
|
|
||||||
|
useTablePaneSync({
|
||||||
|
enableDrag,
|
||||||
|
enableDragScroll,
|
||||||
|
enableHeader,
|
||||||
|
handleRef,
|
||||||
|
onScrollEndRef,
|
||||||
|
pinnedLeftColumnCount,
|
||||||
|
pinnedLeftColumnRef,
|
||||||
|
pinnedRightColumnCount,
|
||||||
|
pinnedRightColumnRef,
|
||||||
|
pinnedRowRef,
|
||||||
|
rowRef,
|
||||||
|
scrollContainerRef,
|
||||||
|
scrollShadowStore,
|
||||||
|
});
|
||||||
|
|
||||||
// Create a wrapper for getRowHeight that doesn't require cellProps (for sticky group rows hook)
|
// Create a wrapper for getRowHeight that doesn't require cellProps (for sticky group rows hook)
|
||||||
const getRowHeightWrapper = useCallback(
|
const getRowHeightWrapper = useCallback(
|
||||||
(index: number) => {
|
(index: number) => {
|
||||||
@@ -1574,28 +1632,21 @@ const BaseItemTableList = ({
|
|||||||
|
|
||||||
const { handleKeyDown } = useTableKeyboardNavigation({
|
const { handleKeyDown } = useTableKeyboardNavigation({
|
||||||
calculateScrollTopForIndex,
|
calculateScrollTopForIndex,
|
||||||
cellPadding,
|
|
||||||
data,
|
data,
|
||||||
DEFAULT_ROW_HEIGHT,
|
|
||||||
enableHeader,
|
enableHeader,
|
||||||
enableSelection,
|
enableSelection,
|
||||||
extractRowId,
|
extractRowId,
|
||||||
getItem,
|
getItem,
|
||||||
getItemIndex,
|
getItemIndex,
|
||||||
|
getRowHeightAtIndex,
|
||||||
getStateItem,
|
getStateItem,
|
||||||
hasRequiredStateItemProperties,
|
hasRequiredStateItemProperties,
|
||||||
internalState,
|
internalState,
|
||||||
itemCount: baseItemCount,
|
itemCount: baseItemCount,
|
||||||
itemType,
|
|
||||||
parsedColumns,
|
|
||||||
pinnedRightColumnCount,
|
pinnedRightColumnCount,
|
||||||
pinnedRightColumnRef,
|
pinnedRightColumnRef,
|
||||||
playerContext,
|
|
||||||
rowHeight,
|
|
||||||
rowRef,
|
rowRef,
|
||||||
scrollToTableIndex,
|
scrollToTableIndex,
|
||||||
size,
|
|
||||||
tableId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
useTableInitialScroll({
|
useTableInitialScroll({
|
||||||
@@ -1606,6 +1657,7 @@ const BaseItemTableList = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
useTableImperativeHandle({
|
useTableImperativeHandle({
|
||||||
|
autoScrollToActiveRow,
|
||||||
enableHeader,
|
enableHeader,
|
||||||
handleRef,
|
handleRef,
|
||||||
internalState,
|
internalState,
|
||||||
|
|||||||
@@ -179,6 +179,7 @@ export const PlayQueue = forwardRef<ItemListHandle, QueueProps>(
|
|||||||
<ItemTableList
|
<ItemTableList
|
||||||
activeRowId={currentSongUniqueId}
|
activeRowId={currentSongUniqueId}
|
||||||
autoFitColumns={table.autoFitColumns}
|
autoFitColumns={table.autoFitColumns}
|
||||||
|
autoScrollToActiveRow={followCurrentSong}
|
||||||
CellComponent={ItemTableListColumn}
|
CellComponent={ItemTableListColumn}
|
||||||
columns={table.columns}
|
columns={table.columns}
|
||||||
data={filteredData}
|
data={filteredData}
|
||||||
|
|||||||
Reference in New Issue
Block a user