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:
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user