diff --git a/src/components/CustomChannelComponent.tsx b/src/components/CustomChannelComponent.tsx index fd429e996..2a911bd17 100644 --- a/src/components/CustomChannelComponent.tsx +++ b/src/components/CustomChannelComponent.tsx @@ -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 { @@ -126,6 +127,9 @@ export function CustomChannelComponent(props: CustomChannelComponentProps) { scrollToBottom, } = useGroupChannelContext(); const lastMessageRef = useRef(null); + + useAutoDismissMobileKyeboardHandler(); + const lastMessage = allMessages?.[allMessages?.length - 1] as | SendableMessage | undefined; diff --git a/src/hooks/useAutoDismissMobileKyeboardHandler.ts b/src/hooks/useAutoDismissMobileKyeboardHandler.ts new file mode 100644 index 000000000..5c8669c1b --- /dev/null +++ b/src/hooks/useAutoDismissMobileKyeboardHandler.ts @@ -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([]); + + 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( + 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;