feat: implement room list reordering and animation hooks; add scroll to latest behavior settings
This commit is contained in:
110
src/app/hooks/useRoomListReorder.ts
Normal file
110
src/app/hooks/useRoomListReorder.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { MatrixClient, MatrixEvent, Room, RoomEvent, IRoomTimelineData } from 'matrix-js-sdk';
|
||||
|
||||
/**
|
||||
* Hook that tracks timeline events across all rooms to trigger re-sorting of room lists.
|
||||
* Returns a counter that increments whenever a message is sent or received in any room.
|
||||
* This can be used as a dependency in useMemo to trigger re-sorting of room lists.
|
||||
*
|
||||
* @param mx - The Matrix client instance
|
||||
* @param roomIds - Optional array of room IDs to track. If not provided, tracks all rooms.
|
||||
* @returns A number that increments on each timeline event
|
||||
*/
|
||||
export const useRoomListReorder = (mx: MatrixClient, roomIds?: string[]): number => {
|
||||
const [updateCounter, setUpdateCounter] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
const handleTimelineEvent = (
|
||||
mEvent: MatrixEvent,
|
||||
room: Room | undefined,
|
||||
toStartOfTimeline: boolean | undefined,
|
||||
removed: boolean,
|
||||
data: IRoomTimelineData
|
||||
) => {
|
||||
if (!room || !data.liveEvent) return;
|
||||
if (toStartOfTimeline) return;
|
||||
|
||||
if (roomIds && !roomIds.includes(room.roomId)) return;
|
||||
|
||||
setUpdateCounter((prev) => prev + 1);
|
||||
};
|
||||
|
||||
mx.on(RoomEvent.Timeline, handleTimelineEvent);
|
||||
|
||||
return () => {
|
||||
mx.removeListener(RoomEvent.Timeline, handleTimelineEvent);
|
||||
};
|
||||
}, [mx, roomIds]);
|
||||
|
||||
return updateCounter;
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook that implements FLIP (First, Last, Invert, Play) animation for list reordering.
|
||||
* Tracks element positions before and after reorder, then animates the difference.
|
||||
*
|
||||
* @param items - Array of item IDs in their current order
|
||||
* @returns Ref callback to attach to the list container
|
||||
*/
|
||||
export const useListReorderAnimation = <T extends string>(
|
||||
items: T[]
|
||||
): React.RefCallback<HTMLElement> => {
|
||||
const positionsRef = useRef<Map<T, number>>(new Map());
|
||||
const containerRef = useRef<HTMLElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
|
||||
const currentPositions = new Map<T, number>();
|
||||
const elements = new Map<T, HTMLElement>();
|
||||
|
||||
items.forEach((itemId) => {
|
||||
const element = containerRef.current!.querySelector(`[data-room-id="${itemId}"]`) as HTMLElement;
|
||||
if (element) {
|
||||
currentPositions.set(itemId, element.offsetTop);
|
||||
elements.set(itemId, element);
|
||||
}
|
||||
});
|
||||
|
||||
if (positionsRef.current.size === 0) {
|
||||
positionsRef.current = currentPositions;
|
||||
return;
|
||||
}
|
||||
|
||||
const animations: Array<{ element: HTMLElement; deltaY: number }> = [];
|
||||
|
||||
items.forEach((itemId) => {
|
||||
const element = elements.get(itemId);
|
||||
const oldPos = positionsRef.current.get(itemId);
|
||||
const newPos = currentPositions.get(itemId);
|
||||
|
||||
if (element && oldPos !== undefined && newPos !== undefined) {
|
||||
const deltaY = oldPos - newPos;
|
||||
|
||||
if (Math.abs(deltaY) > 1) {
|
||||
animations.push({ element, deltaY });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (animations.length > 0) {
|
||||
animations.forEach(({ element, deltaY }) => {
|
||||
element.style.transform = `translateY(${deltaY}px)`;
|
||||
element.style.transition = 'none';
|
||||
});
|
||||
|
||||
requestAnimationFrame(() => {
|
||||
animations.forEach(({ element }) => {
|
||||
element.style.transition = 'transform 300ms cubic-bezier(0.4, 0, 0.2, 1)';
|
||||
element.style.transform = '';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
positionsRef.current = currentPositions;
|
||||
}, [items]);
|
||||
|
||||
return (container: HTMLElement | null) => {
|
||||
containerRef.current = container;
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user