openhands / src /hooks /chat /use-chat-input-events.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
3d700dd verified
Raw History Blame Contribute Delete
3.5 kB
import { useCallback } from "react";
import { isMobileUserAgent } from "#/utils/utils";
import {
ensureCursorVisible,
clearEmptyContent,
getClipboardFiles,
} from "#/components/features/chat/utils/chat-input.utils";
/**
* Hook for handling chat input events
*/
export const useChatInputEvents = (
chatInputRef: React.RefObject<HTMLDivElement | null>,
smartResize: () => void,
increaseHeightForEmptyContent: () => void,
checkIsContentEmpty: () => boolean,
clearEmptyContentHandler: () => void,
onFocus?: () => void,
onBlur?: () => void,
) => {
// Handle input events
const handleInput = useCallback(() => {
smartResize();
// Clear empty content to ensure placeholder shows
if (chatInputRef.current) {
clearEmptyContent(chatInputRef.current);
}
// Ensure cursor stays visible when content is scrollable
ensureCursorVisible(chatInputRef.current);
}, [smartResize, chatInputRef]);
// Handle paste events to clean up formatting and handle files
const handlePaste = useCallback(
(e: React.ClipboardEvent) => {
e.preventDefault();
const files = getClipboardFiles(e.clipboardData);
const hasFiles = files.length > 0;
if (hasFiles) {
// Handle file paste - let the file handling system process the files
// We'll trigger a custom event that the file handling system can listen to
const customEvent = new CustomEvent("pasteFiles", {
detail: { files },
});
document.dispatchEvent(customEvent);
return;
}
// Handle text paste as before
const text = e.clipboardData.getData("text/plain");
if (text) {
// Insert plain text
document.execCommand("insertText", false, text);
// Trigger resize
setTimeout(smartResize, 0);
}
},
[smartResize],
);
// Handle key events
const handleKeyDown = useCallback(
(e: React.KeyboardEvent, disabled: boolean, handleSubmit: () => void) => {
if (e.key !== "Enter") {
return;
}
// Ignore Enter key during IME composition (e.g., Chinese, Japanese, Korean input)
// When using IME, Enter is used to confirm the composition, not to submit
if (e.nativeEvent.isComposing) {
return;
}
if (checkIsContentEmpty()) {
e.preventDefault();
increaseHeightForEmptyContent();
return;
}
// Submit on Enter for everything except phones/tablets (where Enter
// inserts a newline and the user taps the send button). We gate on the
// user agent rather than touch capability so a desktop OS with a
// touchscreen (e.g. a Windows 2-in-1) still submits on Enter.
if (!isMobileUserAgent() && !e.shiftKey && !disabled) {
e.preventDefault();
handleSubmit();
}
},
[checkIsContentEmpty, increaseHeightForEmptyContent],
);
// Handle blur events to ensure placeholder shows when empty
const handleBlur = useCallback(() => {
// Clear empty content to ensure placeholder shows
if (chatInputRef.current) {
clearEmptyContent(chatInputRef.current);
}
// Call the original onBlur callback if provided
if (onBlur) {
onBlur();
}
}, [chatInputRef, onBlur]);
// Handle focus events
const handleFocus = useCallback(() => {
if (onFocus) {
onFocus();
}
}, [onFocus]);
return {
handleInput,
handlePaste,
handleKeyDown,
handleBlur,
handleFocus,
};
};