fix: retain textarea focus on send and auto-scroll on content expansion

Remove disabled={isUploading} from the textarea so focus() isn't silently
ignored on a disabled element. Fix ResizeObserver effect deps so the
observer is actually created after the loading spinner transitions to
message content.
This commit is contained in:
Jannis Braun
2026-03-08 21:01:27 +01:00
parent edcd2ff728
commit 0aba5ef8c1
2 changed files with 24 additions and 3 deletions
@@ -329,7 +329,7 @@ export function MessageInput({ channelId, channelName }: MessageInputProps) {
placeholder={`Message ${channelName.startsWith('@') ? channelName : `#${channelName}`}`} placeholder={`Message ${channelName.startsWith('@') ? channelName : `#${channelName}`}`}
className="flex-1 py-[10px] px-1 bg-transparent text-txt-primary placeholder-txt-tertiary/60 outline-none resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin" className="flex-1 py-[10px] px-1 bg-transparent text-txt-primary placeholder-txt-tertiary/60 outline-none resize-none text-[15px] leading-[1.375rem] max-h-[50vh] scrollbar-thin"
rows={1} rows={1}
disabled={isUploading}
/> />
{/* Send indicator */} {/* Send indicator */}
@@ -46,7 +46,9 @@ export function MessageList({ channelId }: MessageListProps) {
const ackChannel = useChatStore((s) => s.ackChannel); const ackChannel = useChatStore((s) => s.ackChannel);
const bottomRef = useRef<HTMLDivElement>(null); const bottomRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const [isNearBottom, setIsNearBottom] = useState(true); const [isNearBottom, setIsNearBottom] = useState(true);
const isNearBottomRef = useRef(true);
const [isLoadingMore, setIsLoadingMore] = useState(false); const [isLoadingMore, setIsLoadingMore] = useState(false);
const prevMessagesLength = useRef(0); const prevMessagesLength = useRef(0);
const ackTimerRef = useRef<ReturnType<typeof setTimeout>>(); const ackTimerRef = useRef<ReturnType<typeof setTimeout>>();
@@ -68,6 +70,7 @@ export function MessageList({ channelId }: MessageListProps) {
useEffect(() => { useEffect(() => {
prevMessagesLength.current = 0; prevMessagesLength.current = 0;
setIsNearBottom(true); setIsNearBottom(true);
isNearBottomRef.current = true;
}, [channelId]); }, [channelId]);
// Handle scrolling: initial load snaps to bottom, new messages smooth-scroll if near bottom // Handle scrolling: initial load snaps to bottom, new messages smooth-scroll if near bottom
@@ -91,13 +94,31 @@ export function MessageList({ channelId }: MessageListProps) {
} }
}, [messages.length, isNearBottom]); }, [messages.length, isNearBottom]);
// Auto-scroll when content height grows (embeds/images loading) while near bottom
const hasMessages = messages.length > 0;
useEffect(() => {
const content = contentRef.current;
const container = containerRef.current;
if (!content || !container) return;
const observer = new ResizeObserver(() => {
if (isNearBottomRef.current && containerRef.current) {
containerRef.current.scrollTop = containerRef.current.scrollHeight;
}
});
observer.observe(content);
return () => observer.disconnect();
}, [hasMessages, channelId]);
const handleScroll = useCallback(async () => { const handleScroll = useCallback(async () => {
const container = containerRef.current; const container = containerRef.current;
if (!container) return; if (!container) return;
// Check if near bottom // Check if near bottom
const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight; const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
setIsNearBottom(distanceFromBottom < 100); const nearBottom = distanceFromBottom < 100;
setIsNearBottom(nearBottom);
isNearBottomRef.current = nearBottom;
// Load more when scrolled to top // Load more when scrolled to top
if (container.scrollTop < 50 && hasMore && !isLoadingMore) { if (container.scrollTop < 50 && hasMore && !isLoadingMore) {
@@ -136,7 +157,7 @@ export function MessageList({ channelId }: MessageListProps) {
{!hasMore && <WelcomeHeader channelId={channelId} />} {!hasMore && <WelcomeHeader channelId={channelId} />}
<div className="pt-4 pb-6 md:pb-20"> <div ref={contentRef} className="pt-4 pb-6 md:pb-20">
{messages.map((msg, i) => { {messages.map((msg, i) => {
const prevMsg = messages[i - 1]; const prevMsg = messages[i - 1];
const showDate = shouldShowDateDivider(prevMsg, msg); const showDate = shouldShowDateDivider(prevMsg, msg);