feat(FN-4182): keep keyboard open on first tap of mobile room send button
Fixes a mobile UX bug where the keyboard would dismiss when tapping the room send button for the first time, plus adds a regression test in ChatView to guard against this behavior. Fusion-Task-Id: FN-4182
This commit is contained in:
5
.changeset/FN-4182-mobile-send-button.md
Normal file
5
.changeset/FN-4182-mobile-send-button.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
"@runfusion/fusion": patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix chat room send button on mobile: first touch now sends the message instead of dismissing the keyboard.
|
||||||
@@ -2461,6 +2461,22 @@ export function ChatView({ projectId, addToast, experimentalFeatures }: ChatView
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="chat-input-send"
|
className="chat-input-send"
|
||||||
|
// Keep keyboard up when sending. preventDefault fires on
|
||||||
|
// pointerdown for touch pointers (BEFORE iOS blurs the
|
||||||
|
// textarea — the synthesized mousedown is too late on
|
||||||
|
// iOS), and on mousedown for desktop. Crucially we do NOT
|
||||||
|
// call preventDefault on touchstart and we do NOT run the
|
||||||
|
// action here — both of those broke quick taps. Click
|
||||||
|
// still fires from the iOS touch sequence and runs the
|
||||||
|
// action reliably.
|
||||||
|
onPointerDown={(event) => {
|
||||||
|
if (event.pointerType && event.pointerType !== "mouse") {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
onMouseDown={(event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void handleSendDispatch();
|
void handleSendDispatch();
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -3304,6 +3304,51 @@ describe("ChatView mobile behavior", () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("mobile mode: room send button sends on first touch and keeps composer focused", async () => {
|
||||||
|
const restoreMatchMedia = mockMobileViewport();
|
||||||
|
const sendRoomMessage = vi.fn().mockResolvedValue(undefined);
|
||||||
|
const sendMessage = vi.fn();
|
||||||
|
|
||||||
|
try {
|
||||||
|
localStorage.setItem("fusion:chat-scope", "rooms");
|
||||||
|
setupMockChat({
|
||||||
|
activeSession: activeSessionFixture,
|
||||||
|
messages: [],
|
||||||
|
sendMessage,
|
||||||
|
});
|
||||||
|
setupMockRooms({
|
||||||
|
activeRoom: {
|
||||||
|
id: "room-001",
|
||||||
|
projectId: "proj-123",
|
||||||
|
name: "backend",
|
||||||
|
createdAt: "2026-04-08T00:00:00.000Z",
|
||||||
|
updatedAt: "2026-04-08T00:00:00.000Z",
|
||||||
|
},
|
||||||
|
sendRoomMessage,
|
||||||
|
});
|
||||||
|
|
||||||
|
render(<ChatView projectId="proj-123" addToast={vi.fn()} experimentalFeatures={{ chatRooms: true }} />);
|
||||||
|
|
||||||
|
const input = screen.getByTestId("chat-input") as HTMLTextAreaElement;
|
||||||
|
fireEvent.change(input, { target: { value: "Hello mobile room" } });
|
||||||
|
input.focus();
|
||||||
|
|
||||||
|
const sendButton = screen.getByTestId("chat-send-btn");
|
||||||
|
fireEvent.touchStart(sendButton);
|
||||||
|
fireEvent.click(sendButton);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(sendRoomMessage).toHaveBeenCalledTimes(1);
|
||||||
|
expect(sendRoomMessage).toHaveBeenCalledWith("Hello mobile room");
|
||||||
|
});
|
||||||
|
expect(sendMessage).not.toHaveBeenCalled();
|
||||||
|
expect(document.activeElement).toBe(input);
|
||||||
|
} finally {
|
||||||
|
localStorage.removeItem("fusion:chat-scope");
|
||||||
|
restoreMatchMedia.mockRestore();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
it("mobile mode: sets and clears keyboard overlap CSS vars on chat thread", async () => {
|
it("mobile mode: sets and clears keyboard overlap CSS vars on chat thread", async () => {
|
||||||
const restoreMatchMedia = mockMobileViewport();
|
const restoreMatchMedia = mockMobileViewport();
|
||||||
const { listeners, mockVV } = mockMobileVisualViewport({
|
const { listeners, mockVV } = mockMobileVisualViewport({
|
||||||
|
|||||||
Reference in New Issue
Block a user