Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@
"emoji-mart": "^5.4.0",
"react": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0",
"react-dom": "^19.0.0 || ^18.0.0 || ^17.0.0 || ^16.14.0",
"stream-chat": "^9.17.0"
"stream-chat": "^9.19.0"
},
"peerDependenciesMeta": {
"@breezystack/lamejs": {
Expand Down Expand Up @@ -236,7 +236,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"semantic-release": "^24.2.3",
"stream-chat": "^9.17.0",
"stream-chat": "^9.19.0",
"ts-jest": "^29.2.5",
"typescript": "^5.4.5",
"typescript-eslint": "^8.17.0"
Expand Down
41 changes: 17 additions & 24 deletions src/components/TextareaComposer/TextareaComposer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import type {
TextareaHTMLAttributes,
UIEventHandler,
} from 'react';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
import Textarea from 'react-textarea-autosize';
import { useMessageComposer } from '../MessageInput';
import type {
Expand Down Expand Up @@ -200,7 +200,6 @@ export const TextareaComposer = ({
event.preventDefault();
}
handleSubmit();
textareaRef.current.selectionEnd = 0;
}
},
[
Expand All @@ -225,7 +224,7 @@ export const TextareaComposer = ({
[onScroll, textComposer],
);

const setSelectionDebounced = useCallback(
const setSelection = useCallback(
(e: SyntheticEvent<HTMLTextAreaElement>) => {
onSelect?.(e);
textComposer.setSelection({
Expand All @@ -236,17 +235,6 @@ export const TextareaComposer = ({
[onSelect, textComposer],
);

useEffect(() => {
// FIXME: find the real reason for cursor being set to the end on each change
// This is a workaround to prevent the cursor from jumping
// to the end of the textarea when the user is typing
// at the position that is not at the end of the textarea value.
if (textareaRef.current && !isComposing) {
textareaRef.current.selectionStart = selection.start;
textareaRef.current.selectionEnd = selection.end;
}
}, [text, textareaRef, selection.start, selection.end, isComposing]);

useEffect(() => {
if (textComposer.suggestions) {
setFocusedItemIndex(0);
Expand All @@ -259,18 +247,22 @@ export const TextareaComposer = ({
textareaRef.current.focus();
}, [attachments, focus, quotedMessage, textareaRef]);

useEffect(() => {
useLayoutEffect(() => {
/**
* The textarea value has to be overridden outside the render cycle so that the events like compositionend can be triggered.
* If we have overridden the value during the component rendering, the compositionend event would not be triggered, and
* it would not be possible to type composed characters (ô).
* On the other hand, just removing the value override via prop (value={text}) would not allow us to change the text based on
* middleware results (e.g. replace characters with emojis)
* It is important to perform set text and after that the range
* to prevent cursor reset to the end of the textarea if doing it in separate effects.
*/
const textarea = textareaRef.current;
if (!textarea) return;
textarea.value = text;
}, [textareaRef, text]);
if (!textarea || isComposing) return;

const length = textarea.value.length;
const start = Math.max(0, Math.min(selection.start, length));
const end = Math.max(start, Math.min(selection.end, length));

if (textarea.selectionStart === start && textarea.selectionEnd === end) return;

textarea.setSelectionRange(start, end, 'forward');
}, [text, selection.start, selection.end, isComposing, textareaRef]);

return (
<div
Expand Down Expand Up @@ -303,11 +295,12 @@ export const TextareaComposer = ({
onKeyDown={keyDownHandler}
onPaste={onPaste}
onScroll={scrollHandler}
onSelect={setSelectionDebounced}
onSelect={setSelection}
placeholder={placeholder || t('Type your message')}
ref={(ref) => {
textareaRef.current = ref;
}}
value={text}
/>
{/* todo: X document the layout change for the accessibility purpose (tabIndex) */}
{!isComposing && (
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12042,10 +12042,10 @@ statuses@2.0.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63"
integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==

stream-chat@^9.17.0:
version "9.17.0"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.17.0.tgz#540cf1ea03b08a394d6140696aae8528e9ba9ce2"
integrity sha512-ys6K73wIVWs5+qsfPJ9wumEUtgbMXYVbH1dhmAZ1oYtQ01dY/avsvt25PYDakVjKeyrnT+y8T/xEzfeF/WDJsg==
stream-chat@^9.19.0:
version "9.19.0"
resolved "https://registry.yarnpkg.com/stream-chat/-/stream-chat-9.19.0.tgz#8a2055be0f7c073ee8ca10cbc40af7d36648c476"
integrity sha512-ooRLubHPWxVr8Ws3fZvR30BFhVNM1xcrEgRnGGBxNINYYH/Wq+uc6AWONYIeu+n8crwRp3NSrtNfGWpWhLSy7Q==
dependencies:
"@types/jsonwebtoken" "^9.0.8"
"@types/ws" "^8.5.14"
Expand Down
Loading