Skip to content

Commit

Permalink
[AC-1769] feat: dismiss mobile keyboard after sending a message (#129)
Browse files Browse the repository at this point in the history
Addresses https://sendbird.atlassian.net/browse/AC-1769

### AS-IS

https://github.com/sendbird/chat-ai-widget/assets/10060731/ca7e2bc8-0cec-4f9f-9939-20b0f75661bf
-> The latest sent message is invisible because the mobile keyboard
takes up most of the screen area.


### Expected scenario
1. Start typing in an input element 
2. The paper plane icon appears next to the input
3. Once the send button is clicked (or pressing Enter key on iOS), the
mobile keyboard should be dismissed so the latest sent message is
visible.

#### Android
<img width="300px"
src="https://github.com/sendbird/chat-ai-widget/assets/10060731/e9508442-76e8-4be8-9c8b-4a984d245c4d"
alt="android" />

#### iOS
<img width="300px"
src="https://github.com/sendbird/chat-ai-widget/assets/10060731/ccfe1106-ecb9-4148-a376-f134d41cde7d"
alt="ios" />
  • Loading branch information
AhyoungRyu authored Mar 25, 2024
2 parents 354f4a4 + 25e3e89 commit 2394ce2
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/components/CustomChannelComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import CustomMessage from './CustomMessage';
import DynamicRepliesPanel from './DynamicRepliesPanel';
import StaticRepliesPanel from './StaticRepliesPanel';
import { useConstantState } from '../context/ConstantContext';
import useAutoDismissMobileKyeboardHandler from '../hooks/useAutoDismissMobileKyeboardHandler';
import { useScrollOnStreaming } from '../hooks/useScrollOnStreaming';
import { hideChatBottomBanner, isIOSMobile } from '../utils';
import {
Expand Down Expand Up @@ -126,6 +127,9 @@ export function CustomChannelComponent(props: CustomChannelComponentProps) {
scrollToBottom,
} = useGroupChannelContext();
const lastMessageRef = useRef<HTMLDivElement>(null);

useAutoDismissMobileKyeboardHandler();

const lastMessage = allMessages?.[allMessages?.length - 1] as
| SendableMessage
| undefined;
Expand Down
83 changes: 83 additions & 0 deletions src/hooks/useAutoDismissMobileKyeboardHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { useEffect, useRef } from 'react';

import { isIOSMobile } from '../utils';

const INPUT_ELEMENT_SELECTOR = '.sendbird-message-input';
const SEND_BUTTON_SELECTOR = '.sendbird-message-input--send';

function useAutoDismissMobileKeyboardHandler(): void {
const addedButtons = useRef<HTMLElement[]>([]);

useEffect(() => {
const handleDismissKeyboard = (): void => {
setTimeout(() => {
if (document.activeElement instanceof HTMLElement) {
// blur the active element(send button) to dismiss the keyboard on mobile
document.activeElement.blur();
}
}, 200);
};

const handleKeyDown = (event: KeyboardEvent): void => {
if (
event.key === 'Enter' &&
// TODO: Pressing Enter key on Android keyboard does't trigger the sending message event
// but carriage return event is fired instead which is a different behavior from UIKit React.
// Need to find a way to handle this case.
isIOSMobile
) {
handleDismissKeyboard();
}
};

const observerCallback = (mutations: MutationRecord[]): void => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (
node.nodeType === Node.ELEMENT_NODE &&
(node as Element).matches(SEND_BUTTON_SELECTOR)
) {
(node as HTMLElement).removeEventListener(
'click',
handleDismissKeyboard
);
(node as HTMLElement).addEventListener(
'click',
handleDismissKeyboard
);
// Store added node for later removal
addedButtons.current.push(node as HTMLElement);
}
});
}
});
};

const observerRef = new MutationObserver(observerCallback);
const config = { childList: true, subtree: true };

const inputElement = document.querySelector<HTMLInputElement>(
INPUT_ELEMENT_SELECTOR
);
if (inputElement) {
observerRef.observe(inputElement, config);
inputElement.removeEventListener('keydown', handleKeyDown);
inputElement.addEventListener('keydown', handleKeyDown);
} else {
console.warn('Input element not found for mutation observer');
}

return () => {
observerRef.disconnect();
addedButtons.current.forEach((button) =>
button.removeEventListener('click', handleDismissKeyboard)
);
if (inputElement) {
inputElement.removeEventListener('keydown', handleKeyDown);
}
};
}, []);
}

export default useAutoDismissMobileKeyboardHandler;

0 comments on commit 2394ce2

Please sign in to comment.