import React, { useMemo } from 'react'; import { Outlet, useLocation } from 'react-router-dom'; const decodeSegment = (segment: string): string => { let decoded = segment; try { let next = decodeURIComponent(decoded); while (next !== decoded) { decoded = next; next = decodeURIComponent(decoded); } } catch { // keep partially decoded value } return decoded; }; /** * Room routes are `:roomIdOrAlias/:eventId?/`. Jumping to an event (or clearing it) * changes the pathname but not the room — keep the outlet mounted so we don't replay * the route enter animation or remount drawers like Shared Media. * * Path segments are decoded so `!room:server` and `%21room%3Aserver` share a key. */ const getOutletTransitionKey = (pathname: string): string => { const segments = pathname.split('/').filter(Boolean).map(decodeSegment); if (segments.length === 0) return pathname; // Matrix event IDs start with `$` if (segments[segments.length - 1].startsWith('$')) { segments.pop(); } return `/${segments.join('/')}/`; }; /** * Wrapper for Outlet that adds route-based animation. * Remounts (and animates) when leaving a room / switching rooms, not on same-room event hops. */ export function AnimatedOutlet() { const location = useLocation(); const transitionKey = useMemo( () => getOutletTransitionKey(location.pathname), [location.pathname] ); return (