diff --git a/.changeset/fix-typing-indicator-scroll-snap.md b/.changeset/fix-typing-indicator-scroll-snap.md
new file mode 100644
index 0000000000..4150ebc62e
--- /dev/null
+++ b/.changeset/fix-typing-indicator-scroll-snap.md
@@ -0,0 +1,5 @@
+---
+default: patch
+---
+
+Show the typing indicator as its own row instead of an overlay so it no longer hides the last message, and stop the timeline from snapping to the bottom when you scroll back down after messages arrived off-screen.
diff --git a/src/app/features/room/RoomViewTyping.css.ts b/src/app/features/room/RoomViewTyping.css.ts
index 5c90a178b8..d78bb19c13 100644
--- a/src/app/features/room/RoomViewTyping.css.ts
+++ b/src/app/features/room/RoomViewTyping.css.ts
@@ -17,8 +17,6 @@ export const RoomViewTyping = style([
width: '100%',
backgroundColor: color.Surface.Container,
color: color.Surface.OnContainer,
- position: 'absolute',
- bottom: 0,
animation: `${SlideUpAnime} 100ms ease-in-out`,
},
]);
diff --git a/src/app/features/room/RoomViewTyping.tsx b/src/app/features/room/RoomViewTyping.tsx
index 98267fdd85..74dd6c9b62 100644
--- a/src/app/features/room/RoomViewTyping.tsx
+++ b/src/app/features/room/RoomViewTyping.tsx
@@ -47,79 +47,77 @@ export const RoomViewTyping = as<'div', RoomViewTypingProps>(
};
return (
-
-
-
-
- {typingNames.length === 1 && (
- <>
- {typingNames[0]}
-
- {' is typing...'}
-
- >
- )}
- {typingNames.length === 2 && (
- <>
- {typingNames[0]}
-
- {' and '}
-
- {typingNames[1]}
-
- {' are typing...'}
-
- >
- )}
- {typingNames.length === 3 && (
- <>
- {typingNames[0]}
-
- {', '}
-
- {typingNames[1]}
-
- {' and '}
-
- {typingNames[2]}
-
- {' are typing...'}
-
- >
- )}
- {typingNames.length > 3 && (
- <>
- {typingNames[0]}
-
- {', '}
-
- {typingNames[1]}
-
- {', '}
-
- {typingNames[2]}
-
- {' and '}
-
- {typingNames.length - 3} others
-
- {' are typing...'}
-
- >
- )}
-
-
- {chipIcon(X)}
-
-
-
+
+
+
+ {typingNames.length === 1 && (
+ <>
+ {typingNames[0]}
+
+ {' is typing...'}
+
+ >
+ )}
+ {typingNames.length === 2 && (
+ <>
+ {typingNames[0]}
+
+ {' and '}
+
+ {typingNames[1]}
+
+ {' are typing...'}
+
+ >
+ )}
+ {typingNames.length === 3 && (
+ <>
+ {typingNames[0]}
+
+ {', '}
+
+ {typingNames[1]}
+
+ {' and '}
+
+ {typingNames[2]}
+
+ {' are typing...'}
+
+ >
+ )}
+ {typingNames.length > 3 && (
+ <>
+ {typingNames[0]}
+
+ {', '}
+
+ {typingNames[1]}
+
+ {', '}
+
+ {typingNames[2]}
+
+ {' and '}
+
+ {typingNames.length - 3} others
+
+ {' are typing...'}
+
+ >
+ )}
+
+
+ {chipIcon(X)}
+
+
);
}
);
diff --git a/src/app/hooks/timeline/useTimelineSync.ts b/src/app/hooks/timeline/useTimelineSync.ts
index 9aa923220f..67c02ea8d2 100644
--- a/src/app/hooks/timeline/useTimelineSync.ts
+++ b/src/app/hooks/timeline/useTimelineSync.ts
@@ -576,8 +576,10 @@ export function useTimelineSync({
!(isAtBottom || resetAutoScrollPending) ||
(!liveTimelineLinked && !resetAutoScrollPending) ||
eventsLength === 0
- )
+ ) {
+ lastScrolledAtEventsLengthRef.current = eventsLength;
return;
+ }
if (eventsLength <= lastScrolledAtEventsLengthRef.current && !resetAutoScrollPending) return;