stream-chat-react 13.2.0 → 13.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Channel/Channel.js +3 -1
- package/dist/components/ChannelList/ChannelList.d.ts +2 -1
- package/dist/components/ChannelList/ChannelListMessenger.d.ts +2 -1
- package/dist/components/ChannelList/ChannelListMessenger.js +1 -1
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/Dialog/FormDialog.js +1 -1
- package/dist/components/Loading/LoadingErrorIndicator.d.ts +1 -1
- package/dist/components/Message/renderText/renderText.d.ts +1 -1
- package/dist/components/Message/renderText/renderText.js +25 -16
- package/dist/components/MessageInput/EditMessageForm.js +2 -2
- package/dist/components/MessageInput/MessageInput.js +4 -1
- package/dist/components/Poll/PollCreationDialog/MultipleAnswersField.js +1 -1
- package/dist/components/Poll/PollCreationDialog/OptionFieldSet.js +2 -1
- package/dist/components/Poll/PollCreationDialog/PollCreationDialogControls.js +1 -1
- package/dist/components/ReactFileUtilities/UploadButton.js +3 -2
- package/dist/components/TextareaComposer/TextareaComposer.js +29 -2
- package/dist/css/v2/index.css +1 -1
- package/dist/index.browser.cjs +83 -62
- package/dist/index.browser.cjs.map +3 -3
- package/dist/index.node.cjs +83 -62
- package/dist/index.node.cjs.map +3 -3
- package/dist/scss/v2/Form/Form-theme.scss +17 -14
- package/package.json +4 -4
|
@@ -98,7 +98,9 @@ const ChannelInner = (props) => {
|
|
|
98
98
|
}
|
|
99
99
|
else {
|
|
100
100
|
const markReadResponse = await channel.markRead();
|
|
101
|
-
|
|
101
|
+
// markReadResponse.event can be null in case of a user that is not a member of a channel being marked read
|
|
102
|
+
// in that case event is null and we should not set unread UI
|
|
103
|
+
if (updateChannelUiUnreadState && markReadResponse?.event) {
|
|
102
104
|
_setChannelUnreadUiState({
|
|
103
105
|
last_read: lastRead.current,
|
|
104
106
|
last_read_message_id: markReadResponse.event.last_read_message_id,
|
|
@@ -11,6 +11,7 @@ import type { ChatContextValue } from '../../context';
|
|
|
11
11
|
import type { ChannelAvatarProps } from '../Avatar';
|
|
12
12
|
import type { TranslationContextValue } from '../../context/TranslationContext';
|
|
13
13
|
import type { PaginatorProps } from '../../types/types';
|
|
14
|
+
import type { LoadingErrorIndicatorProps } from '../Loading';
|
|
14
15
|
export type ChannelListProps = {
|
|
15
16
|
/** Additional props for underlying ChannelSearch component and channel search controller, [available props](https://getstream.io/chat/docs/sdk/react/utility-components/channel_search/#props) */
|
|
16
17
|
additionalChannelSearchProps?: Omit<ChannelSearchProps, 'setChannels'>;
|
|
@@ -40,7 +41,7 @@ export type ChannelListProps = {
|
|
|
40
41
|
/** Custom UI component to display the container for the queried channels, defaults to and accepts same props as: [ChannelListMessenger](https://github.com/GetStream/stream-chat-react/blob/master/src/components/ChannelList/ChannelListMessenger.tsx) */
|
|
41
42
|
List?: React.ComponentType<ChannelListMessengerProps>;
|
|
42
43
|
/** Custom UI component to display the loading error indicator, defaults to component that renders null */
|
|
43
|
-
LoadingErrorIndicator?: React.ComponentType
|
|
44
|
+
LoadingErrorIndicator?: React.ComponentType<LoadingErrorIndicatorProps>;
|
|
44
45
|
/** Custom UI component to display the loading state, defaults to and accepts same props as: [LoadingChannels](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Loading/LoadingChannels.tsx) */
|
|
45
46
|
LoadingIndicator?: React.ComponentType;
|
|
46
47
|
/** When true, channels won't dynamically sort by most recent message */
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { PropsWithChildren } from 'react';
|
|
3
3
|
import type { APIErrorResponse, Channel, ErrorFromResponse } from 'stream-chat';
|
|
4
|
+
import type { LoadingErrorIndicatorProps } from '../Loading';
|
|
4
5
|
export type ChannelListMessengerProps = {
|
|
5
6
|
/** Whether the channel query request returned an errored response */
|
|
6
7
|
error: ErrorFromResponse<APIErrorResponse> | null;
|
|
@@ -9,7 +10,7 @@ export type ChannelListMessengerProps = {
|
|
|
9
10
|
/** Whether the channels are currently loading */
|
|
10
11
|
loading?: boolean;
|
|
11
12
|
/** Custom UI component to display the loading error indicator, defaults to component that renders null */
|
|
12
|
-
LoadingErrorIndicator?: React.ComponentType
|
|
13
|
+
LoadingErrorIndicator?: React.ComponentType<LoadingErrorIndicatorProps>;
|
|
13
14
|
/** Custom UI component to display a loading indicator, defaults to and accepts same props as: [LoadingChannels](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Loading/LoadingChannels.tsx) */
|
|
14
15
|
LoadingIndicator?: React.ComponentType;
|
|
15
16
|
/** Local state hook that resets the currently loaded channels */
|
|
@@ -9,7 +9,7 @@ export const ChannelListMessenger = (props) => {
|
|
|
9
9
|
const { children, error = null, loading, LoadingErrorIndicator = NullComponent, LoadingIndicator = LoadingChannels, } = props;
|
|
10
10
|
const { t } = useTranslationContext('ChannelListMessenger');
|
|
11
11
|
if (error) {
|
|
12
|
-
return React.createElement(LoadingErrorIndicator,
|
|
12
|
+
return React.createElement(LoadingErrorIndicator, { error: error });
|
|
13
13
|
}
|
|
14
14
|
if (loading) {
|
|
15
15
|
return React.createElement(LoadingIndicator, null);
|
|
@@ -24,7 +24,7 @@ export const useChat = ({ client, defaultLanguage = 'en', i18nInstance, initialN
|
|
|
24
24
|
useEffect(() => {
|
|
25
25
|
if (!client)
|
|
26
26
|
return;
|
|
27
|
-
const version = "13.2.
|
|
27
|
+
const version = "13.2.2";
|
|
28
28
|
const userAgent = client.getUserAgent();
|
|
29
29
|
if (!userAgent.includes('stream-chat-react')) {
|
|
30
30
|
// result looks like: 'stream-chat-react-2.3.2-stream-chat-javascript-client-browser-2.2.2'
|
|
@@ -71,6 +71,6 @@ export const FormDialog = ({ className, close, fields, onSubmit, shouldDisableSu
|
|
|
71
71
|
}),
|
|
72
72
|
React.createElement(FieldError, { text: fieldErrors[id]?.message })))),
|
|
73
73
|
React.createElement("div", { className: 'str-chat__dialog__controls' },
|
|
74
|
-
React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--cancel', onClick: close }, t('Cancel')),
|
|
74
|
+
React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--cancel', onClick: close, type: 'button' }, t('Cancel')),
|
|
75
75
|
React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--submit', disabled: Object.keys(fieldErrors).length > 0 || shouldDisableSubmitButton?.(value), type: 'submit' }, t('Send')))))));
|
|
76
76
|
};
|
|
@@ -16,4 +16,4 @@ export type RenderTextOptions = {
|
|
|
16
16
|
getRehypePlugins?: RenderTextPluginConfigurator;
|
|
17
17
|
getRemarkPlugins?: RenderTextPluginConfigurator;
|
|
18
18
|
};
|
|
19
|
-
export declare const renderText: (text?: string, mentionedUsers?: UserResponse[], { allowedTagNames, customMarkDownRenderers, getRehypePlugins, getRemarkPlugins, }?: RenderTextOptions) => React.JSX.Element | null;
|
|
19
|
+
export declare const renderText: (text?: string, mentionedUsers?: UserResponse[], { allowedTagNames, customMarkDownRenderers, getRehypePlugins, getRemarkPlugins, }?: RenderTextOptions) => React.JSX.Element | null | undefined;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import ReactMarkdown, { defaultUrlTransform } from 'react-markdown';
|
|
3
3
|
import { find } from 'linkifyjs';
|
|
4
|
-
import uniqBy from 'lodash.uniqby';
|
|
5
4
|
import remarkGfm from 'remark-gfm';
|
|
6
5
|
import { Anchor, Emoji, Mention } from './componentRenderers';
|
|
7
|
-
import { detectHttp,
|
|
6
|
+
import { detectHttp, matchMarkdownLinks, messageCodeBlocks } from './regex';
|
|
8
7
|
import { emojiMarkdownPlugin, mentionsMarkdownPlugin } from './rehypePlugins';
|
|
9
8
|
import { htmlToTextPlugin, keepLineBreaksPlugin } from './remarkPlugins';
|
|
10
9
|
import { ErrorBoundary } from '../../UtilityComponents';
|
|
@@ -67,8 +66,12 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
|
|
|
67
66
|
let newText = text;
|
|
68
67
|
const markdownLinks = matchMarkdownLinks(newText);
|
|
69
68
|
const codeBlocks = messageCodeBlocks(newText);
|
|
70
|
-
//
|
|
71
|
-
|
|
69
|
+
// Extract all valid links/emails within text and replace it with proper markup
|
|
70
|
+
// Revert the link order to avoid getting out of sync of the original start and end positions of links
|
|
71
|
+
// - due to the addition of new characters when creating Markdown links
|
|
72
|
+
const links = [...find(newText, 'email'), ...find(newText, 'url')];
|
|
73
|
+
for (let i = links.length - 1; i >= 0; i--) {
|
|
74
|
+
const { end, href, start, type, value } = links[i];
|
|
72
75
|
const linkIsInBlock = codeBlocks.some((block) => block?.includes(value));
|
|
73
76
|
// check if message is already markdown
|
|
74
77
|
const noParsingNeeded = markdownLinks &&
|
|
@@ -77,7 +80,7 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
|
|
|
77
80
|
const strippedText = text?.replace(detectHttp, '');
|
|
78
81
|
if (!strippedHref || !strippedText)
|
|
79
82
|
return false;
|
|
80
|
-
return
|
|
83
|
+
return strippedHref.includes(strippedText) || strippedText.includes(strippedHref);
|
|
81
84
|
});
|
|
82
85
|
if (noParsingNeeded.length > 0 || linkIsInBlock)
|
|
83
86
|
return;
|
|
@@ -87,24 +90,30 @@ export const renderText = (text, mentionedUsers, { allowedTagNames = defaultAllo
|
|
|
87
90
|
// in that case, we check whether the found e-mail is actually a mention
|
|
88
91
|
// by naively checking for an existence of @ sign in front of it.
|
|
89
92
|
if (type === 'email' && mentionedUsers) {
|
|
90
|
-
const emailMatchesWithName = mentionedUsers.
|
|
93
|
+
const emailMatchesWithName = mentionedUsers.find((u) => u.name === value);
|
|
91
94
|
if (emailMatchesWithName) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
// FIXME: breaks if the mention symbol is not '@'
|
|
96
|
+
const isMention = newText.charAt(start - 1) === '@';
|
|
97
|
+
// in case of mention, we leave the match in its original form,
|
|
98
|
+
// and we let `mentionsMarkdownPlugin` to do its job
|
|
99
|
+
newText =
|
|
100
|
+
newText.slice(0, start) +
|
|
101
|
+
(isMention ? value : `[${value}](${encodeDecode(href)})`) +
|
|
102
|
+
newText.slice(end);
|
|
99
103
|
}
|
|
100
104
|
}
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
else {
|
|
106
|
+
const displayLink = type === 'email' ? value : formatUrlForDisplay(href);
|
|
107
|
+
newText =
|
|
108
|
+
newText.slice(0, start) +
|
|
109
|
+
`[${displayLink}](${encodeDecode(href)})` +
|
|
110
|
+
newText.slice(end);
|
|
111
|
+
}
|
|
103
112
|
}
|
|
104
113
|
catch (e) {
|
|
105
114
|
void e;
|
|
106
115
|
}
|
|
107
|
-
}
|
|
116
|
+
}
|
|
108
117
|
const remarkPlugins = [
|
|
109
118
|
htmlToTextPlugin,
|
|
110
119
|
keepLineBreaksPlugin,
|
|
@@ -28,7 +28,7 @@ export const EditMessageForm = () => {
|
|
|
28
28
|
return (React.createElement("form", { autoComplete: 'off', className: 'str-chat__edit-message-form', onSubmit: handleSubmit },
|
|
29
29
|
React.createElement(MessageInputFlat, null),
|
|
30
30
|
React.createElement("div", { className: 'str-chat__edit-message-form-options' },
|
|
31
|
-
React.createElement("button", { className: 'str-chat__edit-message-cancel', "data-testid": 'cancel-button', onClick: cancel }, t('Cancel')),
|
|
31
|
+
React.createElement("button", { className: 'str-chat__edit-message-cancel', "data-testid": 'cancel-button', onClick: cancel, type: 'button' }, t('Cancel')),
|
|
32
32
|
React.createElement(EditMessageFormSendButton, null))));
|
|
33
33
|
};
|
|
34
34
|
export const EditMessageModal = ({ additionalMessageInputProps, }) => {
|
|
@@ -40,5 +40,5 @@ export const EditMessageModal = ({ additionalMessageInputProps, }) => {
|
|
|
40
40
|
messageComposer.restore();
|
|
41
41
|
}, [clearEditingState, messageComposer]);
|
|
42
42
|
return (React.createElement(Modal, { className: 'str-chat__edit-message-modal', onClose: onEditModalClose, open: true },
|
|
43
|
-
React.createElement(MessageInput, { clearEditingState: clearEditingState, hideSendButton: true, Input: EditMessageInput, ...additionalMessageInputProps })));
|
|
43
|
+
React.createElement(MessageInput, { clearEditingState: clearEditingState, focus: true, hideSendButton: true, Input: EditMessageInput, ...additionalMessageInputProps })));
|
|
44
44
|
};
|
|
@@ -25,7 +25,10 @@ const MessageInputProvider = (props) => {
|
|
|
25
25
|
}, [messageComposer]);
|
|
26
26
|
useEffect(() => {
|
|
27
27
|
const threadId = messageComposer.threadId;
|
|
28
|
-
if (!threadId ||
|
|
28
|
+
if (!threadId ||
|
|
29
|
+
!messageComposer.channel ||
|
|
30
|
+
!messageComposer.compositionIsEmpty ||
|
|
31
|
+
!messageComposer.config.drafts.enabled)
|
|
29
32
|
return;
|
|
30
33
|
// get draft data for legacy thead composer
|
|
31
34
|
messageComposer.channel.getDraft({ parent_id: threadId }).then(({ draft }) => {
|
|
@@ -38,5 +38,5 @@ export const MultipleAnswersField = () => {
|
|
|
38
38
|
? e.target.value
|
|
39
39
|
: pollComposer.max_votes_allowed,
|
|
40
40
|
}, nativeFieldValidation);
|
|
41
|
-
}, placeholder: t('Maximum number of votes (from 2 to 10)'), type: '
|
|
41
|
+
}, placeholder: t('Maximum number of votes (from 2 to 10)'), type: 'text', value: max_votes_allowed }))))));
|
|
42
42
|
};
|
|
@@ -35,7 +35,8 @@ export const OptionFieldSet = () => {
|
|
|
35
35
|
options: { index: i, text: e.target.value },
|
|
36
36
|
});
|
|
37
37
|
}, onKeyUp: (event) => {
|
|
38
|
-
|
|
38
|
+
const isFocusedLastOptionField = i === options.length - 1;
|
|
39
|
+
if (event.key === 'Enter' && !isFocusedLastOptionField) {
|
|
39
40
|
const nextInputId = options[i + 1].id;
|
|
40
41
|
document.getElementById(nextInputId)?.focus();
|
|
41
42
|
}
|
|
@@ -10,7 +10,7 @@ export const PollCreationDialogControls = ({ close, }) => {
|
|
|
10
10
|
React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--cancel', onClick: () => {
|
|
11
11
|
messageComposer.pollComposer.initState();
|
|
12
12
|
close();
|
|
13
|
-
} }, t('Cancel')),
|
|
13
|
+
}, type: 'button' }, t('Cancel')),
|
|
14
14
|
React.createElement("button", { className: 'str-chat__dialog__controls-button str-chat__dialog__controls-button--submit', disabled: !canCreatePoll, onClick: () => {
|
|
15
15
|
messageComposer
|
|
16
16
|
.createPoll()
|
|
@@ -20,7 +20,7 @@ export const UploadButton = forwardRef(function UploadButton({ onFileChange, res
|
|
|
20
20
|
export const FileInput = UploadButton;
|
|
21
21
|
export const UploadFileInput = forwardRef(function UploadFileInput({ className, onFileChange: onFileChangeCustom, ...props }, ref) {
|
|
22
22
|
const { t } = useTranslationContext('UploadFileInput');
|
|
23
|
-
const { cooldownRemaining } = useMessageInputContext();
|
|
23
|
+
const { cooldownRemaining, textareaRef } = useMessageInputContext();
|
|
24
24
|
const messageComposer = useMessageComposer();
|
|
25
25
|
const { attachmentManager } = messageComposer;
|
|
26
26
|
const { isUploadEnabled } = useAttachmentManagerState();
|
|
@@ -28,7 +28,8 @@ export const UploadFileInput = forwardRef(function UploadFileInput({ className,
|
|
|
28
28
|
const id = useMemo(() => nanoid(), []);
|
|
29
29
|
const onFileChange = useCallback((files) => {
|
|
30
30
|
attachmentManager.uploadFiles(files);
|
|
31
|
+
textareaRef.current?.focus();
|
|
31
32
|
onFileChangeCustom?.(files);
|
|
32
|
-
}, [onFileChangeCustom, attachmentManager]);
|
|
33
|
+
}, [onFileChangeCustom, attachmentManager, textareaRef]);
|
|
33
34
|
return (React.createElement(FileInput, { accept: acceptedFiles?.join(','), "aria-label": t('aria/File upload'), "data-testid": 'file-input', disabled: !isUploadEnabled || !!cooldownRemaining, id: id, multiple: maxNumberOfFilesPerMessage > 1, ...props, className: clsx('str-chat__file-input', className), onFileChange: onFileChange, ref: ref }));
|
|
34
35
|
});
|
|
@@ -17,6 +17,12 @@ const searchSourceStateSelector = (state) => ({
|
|
|
17
17
|
const configStateSelector = (state) => ({
|
|
18
18
|
enabled: state.text.enabled,
|
|
19
19
|
});
|
|
20
|
+
const messageComposerStateSelector = (state) => ({
|
|
21
|
+
quotedMessage: state.quotedMessage,
|
|
22
|
+
});
|
|
23
|
+
const attachmentManagerStateSelector = (state) => ({
|
|
24
|
+
attachments: state.attachments,
|
|
25
|
+
});
|
|
20
26
|
/**
|
|
21
27
|
* isComposing prevents double submissions in Korean and other languages.
|
|
22
28
|
* starting point for a read:
|
|
@@ -27,7 +33,7 @@ const defaultShouldSubmit = (event) => event.key === 'Enter' && !event.shiftKey
|
|
|
27
33
|
export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, containerClassName, listClassName, maxRows: maxRowsProp, minRows: minRowsProp, onBlur, onChange, onKeyDown, onScroll, onSelect, placeholder: placeholderProp, shouldSubmit: shouldSubmitProp, ...restTextareaProps }) => {
|
|
28
34
|
const { t } = useTranslationContext();
|
|
29
35
|
const { AutocompleteSuggestionList = DefaultSuggestionList } = useComponentContext();
|
|
30
|
-
const { additionalTextareaProps, cooldownRemaining, handleSubmit, maxRows: maxRowsContext, minRows: minRowsContext, onPaste, shouldSubmit: shouldSubmitContext, textareaRef, } = useMessageInputContext();
|
|
36
|
+
const { additionalTextareaProps, cooldownRemaining, focus, handleSubmit, maxRows: maxRowsContext, minRows: minRowsContext, onPaste, shouldSubmit: shouldSubmitContext, textareaRef, } = useMessageInputContext();
|
|
31
37
|
const maxRows = maxRowsProp ?? maxRowsContext ?? 1;
|
|
32
38
|
const minRows = minRowsProp ?? minRowsContext;
|
|
33
39
|
const placeholder = placeholderProp ?? additionalTextareaProps?.placeholder;
|
|
@@ -36,6 +42,8 @@ export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, co
|
|
|
36
42
|
const { textComposer } = messageComposer;
|
|
37
43
|
const { selection, suggestions, text } = useStateStore(textComposer.state, textComposerStateSelector);
|
|
38
44
|
const { enabled } = useStateStore(messageComposer.configState, configStateSelector);
|
|
45
|
+
const { quotedMessage } = useStateStore(messageComposer.state, messageComposerStateSelector);
|
|
46
|
+
const { attachments } = useStateStore(messageComposer.attachmentManager.state, attachmentManagerStateSelector);
|
|
39
47
|
const { isLoadingItems } = useStateStore(suggestions?.searchSource.state, searchSourceStateSelector) ?? {};
|
|
40
48
|
const containerRef = useRef(null);
|
|
41
49
|
const [focusedItemIndex, setFocusedItemIndex] = useState(0);
|
|
@@ -149,11 +157,30 @@ export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, co
|
|
|
149
157
|
setFocusedItemIndex(0);
|
|
150
158
|
}
|
|
151
159
|
}, [textComposer.suggestions]);
|
|
160
|
+
useEffect(() => {
|
|
161
|
+
const textareaIsFocused = textareaRef.current?.matches(':focus');
|
|
162
|
+
if (!textareaRef.current || textareaIsFocused || !focus)
|
|
163
|
+
return;
|
|
164
|
+
textareaRef.current.focus();
|
|
165
|
+
}, [attachments, focus, quotedMessage, textareaRef]);
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
/**
|
|
168
|
+
* The textarea value has to be overridden outside the render cycle so that the events like compositionend can be triggered.
|
|
169
|
+
* If we have overridden the value during the component rendering, the compositionend event would not be triggered, and
|
|
170
|
+
* it would not be possible to type composed characters (ô).
|
|
171
|
+
* On the other hand, just removing the value override via prop (value={text}) would not allow us to change the text based on
|
|
172
|
+
* middleware results (e.g. replace characters with emojis)
|
|
173
|
+
*/
|
|
174
|
+
const textarea = textareaRef.current;
|
|
175
|
+
if (!textarea)
|
|
176
|
+
return;
|
|
177
|
+
textarea.value = text;
|
|
178
|
+
}, [textareaRef, text]);
|
|
152
179
|
return (React.createElement("div", { className: clsx('rta', 'str-chat__textarea str-chat__message-textarea-react-host', containerClassName, {
|
|
153
180
|
['rta--loading']: isLoadingItems,
|
|
154
181
|
}), ref: containerRef },
|
|
155
182
|
React.createElement(Textarea, { ...additionalTextareaProps, ...restTextareaProps, "aria-label": cooldownRemaining ? t('Slow Mode ON') : placeholder, className: clsx('rta__textarea', 'str-chat__textarea__textarea str-chat__message-textarea', className), "data-testid": 'message-input', disabled: !enabled || !!cooldownRemaining, maxRows: maxRows, minRows: minRows, onBlur: onBlur, onChange: changeHandler, onCompositionEnd: onCompositionEnd, onCompositionStart: onCompositionStart, onKeyDown: keyDownHandler, onPaste: onPaste, onScroll: scrollHandler, onSelect: setSelectionDebounced, placeholder: placeholder || t('Type your message'), ref: (ref) => {
|
|
156
183
|
textareaRef.current = ref;
|
|
157
|
-
}
|
|
184
|
+
} }),
|
|
158
185
|
!isComposing && (React.createElement(AutocompleteSuggestionList, { className: listClassName, closeOnClickOutside: closeSuggestionsOnClickOutside, focusedItemIndex: focusedItemIndex, setFocusedItemIndex: setFocusedItemIndex }))));
|
|
159
186
|
};
|