stream-chat-react 13.0.0-rc.2 → 13.0.1
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.d.ts +1 -1
- package/dist/components/Channel/Channel.js +2 -0
- package/dist/components/Chat/hooks/useChat.js +1 -1
- package/dist/components/MessageInput/EditMessageForm.js +1 -1
- package/dist/components/MessageInput/MessageInput.d.ts +1 -3
- package/dist/components/MessageInput/MessageInputFlat.js +2 -2
- package/dist/components/MessageInput/hooks/useCreateMessageInputContext.js +1 -3
- package/dist/components/MessageInput/hooks/useMessageInputControls.d.ts +0 -1
- package/dist/components/MessageInput/hooks/useMessageInputControls.js +3 -4
- package/dist/components/MessageInput/hooks/usePasteHandler.d.ts +1 -1
- package/dist/components/MessageInput/hooks/usePasteHandler.js +4 -4
- package/dist/components/MessageInput/hooks/useSubmitHandler.js +31 -1
- package/dist/components/MessageInput/hooks/{useMessageInputText.d.ts → useTextareaRef.d.ts} +1 -2
- package/dist/components/MessageInput/hooks/useTextareaRef.js +14 -0
- package/dist/components/Poll/PollActions/PollResults/PollResults.js +1 -1
- package/dist/components/Poll/PollCreationDialog/MultipleAnswersField.js +9 -2
- package/dist/components/TextareaComposer/TextareaComposer.d.ts +3 -5
- package/dist/components/TextareaComposer/TextareaComposer.js +15 -13
- package/dist/context/ComponentContext.d.ts +3 -3
- package/dist/css/v2/index.css +1 -1
- package/dist/css/v2/index.layout.css +1 -1
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/i18n/Streami18n.d.ts +1 -0
- package/dist/i18n/de.json +1 -0
- package/dist/i18n/en.json +1 -0
- package/dist/i18n/es.json +1 -0
- package/dist/i18n/fr.json +1 -0
- package/dist/i18n/hi.json +1 -0
- package/dist/i18n/it.json +1 -0
- package/dist/i18n/ja.json +1 -0
- package/dist/i18n/ko.json +2 -1
- package/dist/i18n/nl.json +2 -1
- package/dist/i18n/pt.json +1 -0
- package/dist/i18n/ru.json +1 -0
- package/dist/i18n/tr.json +1 -0
- package/dist/index.browser.cjs +348 -323
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +348 -323
- package/dist/index.node.cjs.map +4 -4
- package/dist/plugins/Emojis/EmojiPicker.js +9 -4
- package/dist/plugins/Emojis/index.browser.cjs +208 -96
- package/dist/plugins/Emojis/index.browser.cjs.map +4 -4
- package/dist/plugins/Emojis/index.node.cjs +208 -96
- package/dist/plugins/Emojis/index.node.cjs.map +4 -4
- package/dist/plugins/Emojis/middleware/textComposerEmojiMiddleware.d.ts +4 -48
- package/dist/plugins/Emojis/middleware/textComposerEmojiMiddleware.js +52 -58
- package/dist/scss/v2/MessageInput/MessageInput-layout.scss +5 -0
- package/package.json +4 -4
- package/dist/components/MessageInput/hooks/useMessageInputText.js +0 -44
|
@@ -5,7 +5,7 @@ import type { OnMentionAction } from './hooks/useMentionsHandlers';
|
|
|
5
5
|
import type { LoadingErrorIndicatorProps } from '../Loading';
|
|
6
6
|
import type { ComponentContextValue } from '../../context';
|
|
7
7
|
import type { ChannelUnreadUiState, GiphyVersions, ImageAttachmentSizeHandler, VideoAttachmentSizeHandler } from '../../types/types';
|
|
8
|
-
type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'SendButton' | 'StartRecordingAudioButton' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
|
|
8
|
+
type ChannelPropsForwardedToComponentContext = Pick<ComponentContextValue, 'Attachment' | 'AttachmentPreviewList' | 'AttachmentSelector' | 'AttachmentSelectorInitiationButtonContents' | 'AudioRecorder' | 'AutocompleteSuggestionItem' | 'AutocompleteSuggestionList' | 'Avatar' | 'BaseImage' | 'CooldownTimer' | 'CustomMessageActionsList' | 'DateSeparator' | 'EditMessageInput' | 'EmojiPicker' | 'emojiSearchIndex' | 'EmptyStateIndicator' | 'FileUploadIcon' | 'GiphyPreviewMessage' | 'HeaderComponent' | 'Input' | 'LinkPreviewList' | 'LoadingIndicator' | 'Message' | 'MessageActions' | 'MessageBouncePrompt' | 'MessageBlocked' | 'MessageDeleted' | 'MessageListNotifications' | 'MessageListMainPanel' | 'MessageNotification' | 'MessageOptions' | 'MessageRepliesCountButton' | 'MessageStatus' | 'MessageSystem' | 'MessageTimestamp' | 'ModalGallery' | 'PinIndicator' | 'PollActions' | 'PollContent' | 'PollCreationDialog' | 'PollHeader' | 'PollOptionSelector' | 'QuotedMessage' | 'QuotedMessagePreview' | 'QuotedPoll' | 'reactionOptions' | 'ReactionSelector' | 'ReactionsList' | 'ReactionsListModal' | 'SendButton' | 'StartRecordingAudioButton' | 'TextareaComposer' | 'ThreadHead' | 'ThreadHeader' | 'ThreadStart' | 'Timestamp' | 'TypingIndicator' | 'UnreadMessagesNotification' | 'UnreadMessagesSeparator' | 'VirtualMessage' | 'StopAIGenerationButton' | 'StreamedMessageText'>;
|
|
9
9
|
export type ChannelProps = ChannelPropsForwardedToComponentContext & {
|
|
10
10
|
/** Custom handler function that runs when the active channel has unread messages and the app is running on a separate browser tab */
|
|
11
11
|
activeUnreadHandler?: (unread: number, documentTitle: string) => void;
|
|
@@ -756,6 +756,7 @@ const ChannelInner = (props) => {
|
|
|
756
756
|
StartRecordingAudioButton: props.StartRecordingAudioButton,
|
|
757
757
|
StopAIGenerationButton: props.StopAIGenerationButton,
|
|
758
758
|
StreamedMessageText: props.StreamedMessageText,
|
|
759
|
+
TextareaComposer: props.TextareaComposer,
|
|
759
760
|
ThreadHead: props.ThreadHead,
|
|
760
761
|
ThreadHeader: props.ThreadHeader,
|
|
761
762
|
ThreadStart: props.ThreadStart,
|
|
@@ -817,6 +818,7 @@ const ChannelInner = (props) => {
|
|
|
817
818
|
props.StartRecordingAudioButton,
|
|
818
819
|
props.StopAIGenerationButton,
|
|
819
820
|
props.StreamedMessageText,
|
|
821
|
+
props.TextareaComposer,
|
|
820
822
|
props.ThreadHead,
|
|
821
823
|
props.ThreadHeader,
|
|
822
824
|
props.ThreadStart,
|
|
@@ -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.0.
|
|
27
|
+
const version = "13.0.1";
|
|
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'
|
|
@@ -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,
|
|
43
|
+
React.createElement(MessageInput, { clearEditingState: clearEditingState, hideSendButton: true, Input: EditMessageInput, ...additionalMessageInputProps })));
|
|
44
44
|
};
|
|
@@ -20,7 +20,7 @@ export type MessageInputProps = {
|
|
|
20
20
|
* Default value is handled via MessageComposer.
|
|
21
21
|
* [Available props](https://www.npmjs.com/package/react-textarea-autosize)
|
|
22
22
|
*/
|
|
23
|
-
additionalTextareaProps?: Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'defaultValue'>;
|
|
23
|
+
additionalTextareaProps?: Omit<React.TextareaHTMLAttributes<HTMLTextAreaElement>, 'defaultValue' | 'style' | 'disabled' | 'value'>;
|
|
24
24
|
/**
|
|
25
25
|
* When enabled, recorded messages won’t be sent immediately.
|
|
26
26
|
* Instead, they will “stack up” with other attachments in the message composer allowing the user to send multiple attachments as part of the same message.
|
|
@@ -36,8 +36,6 @@ export type MessageInputProps = {
|
|
|
36
36
|
emojiSearchIndex?: ComponentContextValue['emojiSearchIndex'];
|
|
37
37
|
/** If true, focuses the text input on component mount */
|
|
38
38
|
focus?: boolean;
|
|
39
|
-
/** If true, expands the text input vertically for new lines */
|
|
40
|
-
grow?: boolean;
|
|
41
39
|
/** Allows to hide MessageInput's send button. */
|
|
42
40
|
hideSendButton?: boolean;
|
|
43
41
|
/** Custom UI component handling how the message input is rendered, defaults to and accepts the same props as [MessageInputFlat](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/MessageInputFlat.tsx) */
|
|
@@ -7,7 +7,7 @@ import { StopAIGenerationButton as DefaultStopAIGenerationButton } from './StopA
|
|
|
7
7
|
import { AudioRecorder as DefaultAudioRecorder, RecordingPermissionDeniedNotification as DefaultRecordingPermissionDeniedNotification, StartRecordingAudioButton as DefaultStartRecordingAudioButton, RecordingPermission, } from '../MediaRecorder';
|
|
8
8
|
import { QuotedMessagePreview as DefaultQuotedMessagePreview, QuotedMessagePreviewHeader, } from './QuotedMessagePreview';
|
|
9
9
|
import { LinkPreviewList as DefaultLinkPreviewList } from './LinkPreviewList';
|
|
10
|
-
import { TextareaComposer } from '../TextareaComposer';
|
|
10
|
+
import { TextareaComposer as DefaultTextareaComposer } from '../TextareaComposer';
|
|
11
11
|
import { AIStates, useAIState } from '../AIStateIndicator';
|
|
12
12
|
import { RecordingAttachmentType } from '../MediaRecorder/classes';
|
|
13
13
|
import { useChatContext } from '../../context/ChatContext';
|
|
@@ -19,7 +19,7 @@ import { WithDragAndDropUpload } from './WithDragAndDropUpload';
|
|
|
19
19
|
export const MessageInputFlat = () => {
|
|
20
20
|
const { message } = useMessageContext();
|
|
21
21
|
const { asyncMessagesMultiSendEnabled, cooldownRemaining, handleSubmit, hideSendButton, recordingController, setCooldownRemaining, } = useMessageInputContext('MessageInputFlat');
|
|
22
|
-
const { AttachmentPreviewList = DefaultAttachmentPreviewList, AttachmentSelector = message ? SimpleAttachmentSelector : DefaultAttachmentSelector, AudioRecorder = DefaultAudioRecorder, CooldownTimer = DefaultCooldownTimer, EmojiPicker, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, StopAIGenerationButton: StopAIGenerationButtonOverride, } = useComponentContext(
|
|
22
|
+
const { AttachmentPreviewList = DefaultAttachmentPreviewList, AttachmentSelector = message ? SimpleAttachmentSelector : DefaultAttachmentSelector, AudioRecorder = DefaultAudioRecorder, CooldownTimer = DefaultCooldownTimer, EmojiPicker, LinkPreviewList = DefaultLinkPreviewList, QuotedMessagePreview = DefaultQuotedMessagePreview, RecordingPermissionDeniedNotification = DefaultRecordingPermissionDeniedNotification, SendButton = DefaultSendButton, StartRecordingAudioButton = DefaultStartRecordingAudioButton, StopAIGenerationButton: StopAIGenerationButtonOverride, TextareaComposer = DefaultTextareaComposer, } = useComponentContext();
|
|
23
23
|
const { channel } = useChatContext('MessageInputFlat');
|
|
24
24
|
const { aiState } = useAIState(channel);
|
|
25
25
|
const stopGenerating = useCallback(() => channel?.stopAIResponse(), [channel]);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo } from 'react';
|
|
2
2
|
export const useCreateMessageInputContext = (value) => {
|
|
3
|
-
const { additionalTextareaProps, asyncMessagesMultiSendEnabled, audioRecordingEnabled, clearEditingState, cooldownInterval, cooldownRemaining, emojiSearchIndex, focus,
|
|
3
|
+
const { additionalTextareaProps, asyncMessagesMultiSendEnabled, audioRecordingEnabled, clearEditingState, cooldownInterval, cooldownRemaining, emojiSearchIndex, focus, handleSubmit, hideSendButton, isThreadInput, maxRows, minRows, onPaste, parent, recordingController, setCooldownRemaining, shouldSubmit, textareaRef, } = value;
|
|
4
4
|
const parentId = parent?.id;
|
|
5
5
|
const messageInputContext = useMemo(() => ({
|
|
6
6
|
additionalTextareaProps,
|
|
@@ -11,10 +11,8 @@ export const useCreateMessageInputContext = (value) => {
|
|
|
11
11
|
cooldownRemaining,
|
|
12
12
|
emojiSearchIndex,
|
|
13
13
|
focus,
|
|
14
|
-
grow,
|
|
15
14
|
handleSubmit,
|
|
16
15
|
hideSendButton,
|
|
17
|
-
insertText,
|
|
18
16
|
isThreadInput,
|
|
19
17
|
maxRows,
|
|
20
18
|
minRows,
|
|
@@ -4,7 +4,6 @@ import type { UpdatedMessage } from 'stream-chat';
|
|
|
4
4
|
import type { MessageInputProps } from '../MessageInput';
|
|
5
5
|
export type MessageInputHookProps = {
|
|
6
6
|
handleSubmit: (event?: React.BaseSyntheticEvent, customMessageData?: Omit<UpdatedMessage, 'mentioned_users'>) => void;
|
|
7
|
-
insertText: (textToInsert: string) => void;
|
|
8
7
|
onPaste: (event: React.ClipboardEvent<HTMLTextAreaElement>) => void;
|
|
9
8
|
recordingController: RecordingController;
|
|
10
9
|
textareaRef: React.MutableRefObject<HTMLTextAreaElement | null | undefined>;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { useTextareaRef } from './useTextareaRef';
|
|
2
2
|
import { useSubmitHandler } from './useSubmitHandler';
|
|
3
3
|
import { usePasteHandler } from './usePasteHandler';
|
|
4
4
|
import { useMediaRecorder } from '../../MediaRecorder/hooks/useMediaRecorder';
|
|
5
5
|
export const useMessageInputControls = (props) => {
|
|
6
6
|
const { asyncMessagesMultiSendEnabled, audioRecordingConfig, audioRecordingEnabled } = props;
|
|
7
|
-
const {
|
|
7
|
+
const { textareaRef } = useTextareaRef(props);
|
|
8
8
|
const { handleSubmit } = useSubmitHandler(props);
|
|
9
9
|
const recordingController = useMediaRecorder({
|
|
10
10
|
asyncMessagesMultiSendEnabled,
|
|
@@ -12,10 +12,9 @@ export const useMessageInputControls = (props) => {
|
|
|
12
12
|
handleSubmit,
|
|
13
13
|
recordingConfig: audioRecordingConfig,
|
|
14
14
|
});
|
|
15
|
-
const { onPaste } = usePasteHandler(
|
|
15
|
+
const { onPaste } = usePasteHandler();
|
|
16
16
|
return {
|
|
17
17
|
handleSubmit,
|
|
18
|
-
insertText,
|
|
19
18
|
onPaste,
|
|
20
19
|
recordingController,
|
|
21
20
|
textareaRef,
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import { useMessageComposer } from './useMessageComposer';
|
|
3
3
|
import { dataTransferItemsToFiles } from '../../ReactFileUtilities';
|
|
4
|
-
export const usePasteHandler = (
|
|
5
|
-
const { attachmentManager } = useMessageComposer();
|
|
4
|
+
export const usePasteHandler = () => {
|
|
5
|
+
const { attachmentManager, textComposer } = useMessageComposer();
|
|
6
6
|
const onPaste = useCallback((clipboardEvent) => {
|
|
7
7
|
(async (event) => {
|
|
8
8
|
const { items } = event.clipboardData;
|
|
@@ -25,12 +25,12 @@ export const usePasteHandler = (insertText) => {
|
|
|
25
25
|
const fileLikes = await dataTransferItemsToFiles(Array.from(items));
|
|
26
26
|
if (plainTextPromise) {
|
|
27
27
|
const pastedText = await plainTextPromise;
|
|
28
|
-
insertText(pastedText);
|
|
28
|
+
textComposer.insertText({ text: pastedText });
|
|
29
29
|
}
|
|
30
30
|
else {
|
|
31
31
|
attachmentManager.uploadFiles(fileLikes);
|
|
32
32
|
}
|
|
33
33
|
})(clipboardEvent);
|
|
34
|
-
}, [attachmentManager,
|
|
34
|
+
}, [attachmentManager, textComposer]);
|
|
35
35
|
return { onPaste };
|
|
36
36
|
};
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
+
import { MessageComposer } from 'stream-chat';
|
|
2
3
|
import { useMessageComposer } from './useMessageComposer';
|
|
3
4
|
import { useChannelActionContext } from '../../../context/ChannelActionContext';
|
|
4
5
|
import { useTranslationContext } from '../../../context/TranslationContext';
|
|
6
|
+
const takeStateSnapshot = (messageComposer) => {
|
|
7
|
+
const textComposerState = messageComposer.textComposer.state.getLatestValue();
|
|
8
|
+
const attachmentManagerState = messageComposer.attachmentManager.state.getLatestValue();
|
|
9
|
+
const linkPreviewsManagerState = messageComposer.linkPreviewsManager.state.getLatestValue();
|
|
10
|
+
const pollComposerState = messageComposer.pollComposer.state.getLatestValue();
|
|
11
|
+
const customDataManagerState = messageComposer.customDataManager.state.getLatestValue();
|
|
12
|
+
const state = messageComposer.state.getLatestValue();
|
|
13
|
+
return () => {
|
|
14
|
+
messageComposer.state.next(state);
|
|
15
|
+
messageComposer.textComposer.state.next(textComposerState);
|
|
16
|
+
messageComposer.attachmentManager.state.next(attachmentManagerState);
|
|
17
|
+
messageComposer.linkPreviewsManager.state.next(linkPreviewsManagerState);
|
|
18
|
+
messageComposer.pollComposer.state.next(pollComposerState);
|
|
19
|
+
messageComposer.customDataManager.state.next(customDataManagerState);
|
|
20
|
+
};
|
|
21
|
+
};
|
|
5
22
|
export const useSubmitHandler = (props) => {
|
|
6
23
|
const { clearEditingState, overrideSubmitHandler } = props;
|
|
7
24
|
const { addNotification, editMessage, sendMessage } = useChannelActionContext('useSubmitHandler');
|
|
@@ -23,7 +40,20 @@ export const useSubmitHandler = (props) => {
|
|
|
23
40
|
}
|
|
24
41
|
}
|
|
25
42
|
else {
|
|
43
|
+
const restoreComposerStateSnapshot = takeStateSnapshot(messageComposer);
|
|
26
44
|
try {
|
|
45
|
+
// FIXME: once MessageComposer has sendMessage method, then the following condition should be encapsulated by it
|
|
46
|
+
// keep attachments, text, quoted message (treat them as draft) ... if sending a poll
|
|
47
|
+
const sentPollMessage = !!message.poll_id;
|
|
48
|
+
if (sentPollMessage) {
|
|
49
|
+
messageComposer.state.partialNext({
|
|
50
|
+
id: MessageComposer.generateId(),
|
|
51
|
+
pollId: null,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
messageComposer.clear();
|
|
56
|
+
}
|
|
27
57
|
// todo: get rid of overrideSubmitHandler once MessageComposer supports submission flow
|
|
28
58
|
if (overrideSubmitHandler) {
|
|
29
59
|
await overrideSubmitHandler({
|
|
@@ -36,11 +66,11 @@ export const useSubmitHandler = (props) => {
|
|
|
36
66
|
else {
|
|
37
67
|
await sendMessage({ localMessage, message, options: sendOptions });
|
|
38
68
|
}
|
|
39
|
-
messageComposer.clear();
|
|
40
69
|
if (messageComposer.config.text.publishTypingEvents)
|
|
41
70
|
await messageComposer.channel.stopTyping();
|
|
42
71
|
}
|
|
43
72
|
catch (err) {
|
|
73
|
+
restoreComposerStateSnapshot();
|
|
44
74
|
addNotification(t('Send message request failed'), 'error');
|
|
45
75
|
}
|
|
46
76
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import type { MessageInputProps } from '../MessageInput';
|
|
3
|
-
export declare const
|
|
4
|
-
insertText: (textToInsert: string) => void;
|
|
3
|
+
export declare const useTextareaRef: (props: MessageInputProps) => {
|
|
5
4
|
textareaRef: import("react").RefObject<HTMLTextAreaElement | undefined>;
|
|
6
5
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
export const useTextareaRef = (props) => {
|
|
3
|
+
const { focus } = props;
|
|
4
|
+
const textareaRef = useRef(undefined);
|
|
5
|
+
// Focus
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
if (focus && textareaRef.current) {
|
|
8
|
+
textareaRef.current.focus();
|
|
9
|
+
}
|
|
10
|
+
}, [focus]);
|
|
11
|
+
return {
|
|
12
|
+
textareaRef,
|
|
13
|
+
};
|
|
14
|
+
};
|
|
@@ -7,7 +7,7 @@ import { useStateStore } from '../../../../store';
|
|
|
7
7
|
import { usePollContext, useTranslationContext } from '../../../../context';
|
|
8
8
|
const pollStateSelector = (nextValue) => ({
|
|
9
9
|
name: nextValue.name,
|
|
10
|
-
options: nextValue.options,
|
|
10
|
+
options: [...nextValue.options],
|
|
11
11
|
vote_counts_by_option: nextValue.vote_counts_by_option,
|
|
12
12
|
});
|
|
13
13
|
export const PollResults = ({ close }) => {
|
|
@@ -28,8 +28,15 @@ export const MultipleAnswersField = () => {
|
|
|
28
28
|
React.createElement("input", { id: 'max_votes_allowed', onBlur: () => {
|
|
29
29
|
pollComposer.handleFieldBlur('max_votes_allowed');
|
|
30
30
|
}, onChange: (e) => {
|
|
31
|
+
const nativeFieldValidation = !e.target.validity.valid
|
|
32
|
+
? {
|
|
33
|
+
max_votes_allowed: t('Only numbers are allowed'),
|
|
34
|
+
}
|
|
35
|
+
: undefined;
|
|
31
36
|
pollComposer.updateFields({
|
|
32
|
-
max_votes_allowed:
|
|
33
|
-
|
|
37
|
+
max_votes_allowed: !nativeFieldValidation
|
|
38
|
+
? e.target.value
|
|
39
|
+
: pollComposer.max_votes_allowed,
|
|
40
|
+
}, nativeFieldValidation);
|
|
34
41
|
}, placeholder: t('Maximum number of votes (from 2 to 10)'), type: 'number', value: max_votes_allowed }))))));
|
|
35
42
|
};
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import type { TextareaHTMLAttributes } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
export type
|
|
3
|
+
export type TextareaComposerProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'style' | 'defaultValue' | 'disabled' | 'value'> & {
|
|
4
4
|
closeSuggestionsOnClickOutside?: boolean;
|
|
5
5
|
containerClassName?: string;
|
|
6
|
-
dropdownClassName?: string;
|
|
7
|
-
grow?: boolean;
|
|
8
|
-
itemClassName?: string;
|
|
9
6
|
listClassName?: string;
|
|
10
7
|
maxRows?: number;
|
|
8
|
+
minRows?: number;
|
|
11
9
|
shouldSubmit?: (event: React.KeyboardEvent<HTMLTextAreaElement>) => boolean;
|
|
12
10
|
};
|
|
13
|
-
export declare const TextareaComposer: ({ className, closeSuggestionsOnClickOutside, containerClassName,
|
|
11
|
+
export declare const TextareaComposer: ({ className, closeSuggestionsOnClickOutside, containerClassName, listClassName, maxRows: maxRowsProp, minRows: minRowsProp, onBlur, onChange, onKeyDown, onScroll, onSelect, placeholder: placeholderProp, shouldSubmit: shouldSubmitProp, ...restTextareaProps }: TextareaComposerProps) => React.JSX.Element;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import debounce from 'lodash.debounce';
|
|
1
2
|
import clsx from 'clsx';
|
|
3
|
+
import { useMemo } from 'react';
|
|
2
4
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
5
|
import Textarea from 'react-textarea-autosize';
|
|
4
6
|
import { useMessageComposer } from '../MessageInput';
|
|
@@ -24,16 +26,12 @@ const configStateSelector = (state) => ({
|
|
|
24
26
|
* In the long term, the fix should happen by handling keypress, but changing this has unknown implications.
|
|
25
27
|
*/
|
|
26
28
|
const defaultShouldSubmit = (event) => event.key === 'Enter' && !event.shiftKey && !event.nativeEvent.isComposing;
|
|
27
|
-
export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, containerClassName,
|
|
28
|
-
// dropdownClassName, // todo: X find a different way to prevent prop drilling
|
|
29
|
-
grow: growProp,
|
|
30
|
-
// itemClassName, // todo: X find a different way to prevent prop drilling
|
|
31
|
-
listClassName, maxRows: maxRowsProp = 1, onBlur, onChange, onKeyDown, onScroll, placeholder: placeholderProp, shouldSubmit: shouldSubmitProp, ...restProps }) => {
|
|
29
|
+
export const TextareaComposer = ({ className, closeSuggestionsOnClickOutside, containerClassName, listClassName, maxRows: maxRowsProp, minRows: minRowsProp, onBlur, onChange, onKeyDown, onScroll, onSelect, placeholder: placeholderProp, shouldSubmit: shouldSubmitProp, ...restTextareaProps }) => {
|
|
32
30
|
const { t } = useTranslationContext();
|
|
33
31
|
const { AutocompleteSuggestionList = DefaultSuggestionList } = useComponentContext();
|
|
34
|
-
const { additionalTextareaProps, cooldownRemaining,
|
|
35
|
-
const
|
|
36
|
-
const
|
|
32
|
+
const { additionalTextareaProps, cooldownRemaining, handleSubmit, maxRows: maxRowsContext, minRows: minRowsContext, onPaste, shouldSubmit: shouldSubmitContext, textareaRef, } = useMessageInputContext();
|
|
33
|
+
const maxRows = maxRowsProp ?? maxRowsContext ?? 1;
|
|
34
|
+
const minRows = minRowsProp ?? minRowsContext;
|
|
37
35
|
const placeholder = placeholderProp ?? additionalTextareaProps?.placeholder;
|
|
38
36
|
const shouldSubmit = shouldSubmitProp ?? shouldSubmitContext ?? defaultShouldSubmit;
|
|
39
37
|
const messageComposer = useMessageComposer();
|
|
@@ -70,16 +68,13 @@ listClassName, maxRows: maxRowsProp = 1, onBlur, onChange, onKeyDown, onScroll,
|
|
|
70
68
|
onKeyDown(event);
|
|
71
69
|
return;
|
|
72
70
|
}
|
|
73
|
-
if (event.key === 'Enter') {
|
|
74
|
-
// allow next line only on Shift + Enter. Enter is reserved for submission.
|
|
75
|
-
event.preventDefault();
|
|
76
|
-
}
|
|
77
71
|
if (textComposer.suggestions &&
|
|
78
72
|
textComposer.suggestions.searchSource.items?.length) {
|
|
79
73
|
if (event.key === 'Escape')
|
|
80
74
|
return textComposer.closeSuggestions();
|
|
81
75
|
const loadedItems = textComposer.suggestions.searchSource.items;
|
|
82
76
|
if (event.key === 'Enter') {
|
|
77
|
+
event.preventDefault();
|
|
83
78
|
textComposer.handleSelect(loadedItems[focusedItemIndex]);
|
|
84
79
|
}
|
|
85
80
|
if (event.key === 'ArrowUp') {
|
|
@@ -130,6 +125,13 @@ listClassName, maxRows: maxRowsProp = 1, onBlur, onChange, onKeyDown, onScroll,
|
|
|
130
125
|
textComposer.closeSuggestions();
|
|
131
126
|
}
|
|
132
127
|
}, [onScroll, textComposer]);
|
|
128
|
+
const setSelectionDebounced = useMemo(() => debounce((e) => {
|
|
129
|
+
onSelect?.(e);
|
|
130
|
+
textComposer.setSelection({
|
|
131
|
+
end: e.target.selectionEnd,
|
|
132
|
+
start: e.target.selectionStart,
|
|
133
|
+
});
|
|
134
|
+
}, 100, { leading: false, trailing: true }), [onSelect, textComposer]);
|
|
133
135
|
useEffect(() => {
|
|
134
136
|
// FIXME: find the real reason for cursor being set to the end on each change
|
|
135
137
|
// This is a workaround to prevent the cursor from jumping
|
|
@@ -148,7 +150,7 @@ listClassName, maxRows: maxRowsProp = 1, onBlur, onChange, onKeyDown, onScroll,
|
|
|
148
150
|
return (React.createElement("div", { className: clsx('rta', 'str-chat__textarea str-chat__message-textarea-react-host', containerClassName, {
|
|
149
151
|
['rta--loading']: isLoadingItems,
|
|
150
152
|
}), ref: containerRef },
|
|
151
|
-
React.createElement(Textarea, { ...
|
|
153
|
+
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) => {
|
|
152
154
|
textareaRef.current = ref;
|
|
153
155
|
}, value: text }),
|
|
154
156
|
!isComposing && (React.createElement(AutocompleteSuggestionList, { className: listClassName, closeOnClickOutside: closeSuggestionsOnClickOutside, focusedItemIndex: focusedItemIndex, setFocusedItemIndex: setFocusedItemIndex }))));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PropsWithChildren } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import type { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, ChannelPreviewActionButtonsProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListModalProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, StreamedMessageTextProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
|
|
3
|
+
import type { AttachmentPreviewListProps, AttachmentProps, AvatarProps, BaseImageProps, ChannelPreviewActionButtonsProps, CooldownTimerProps, CustomMessageActionsListProps, DateSeparatorProps, EmojiSearchIndex, EmptyStateIndicatorProps, EventComponentProps, FixedHeightMessageProps, GiphyPreviewMessageProps, LoadingIndicatorProps, MessageBouncePromptProps, MessageDeletedProps, MessageInputProps, MessageListNotificationsProps, MessageNotificationProps, MessageOptionsProps, MessageProps, MessageRepliesCountButtonProps, MessageStatusProps, MessageTimestampProps, MessageUIComponentProps, ModalGalleryProps, PinIndicatorProps, PollCreationDialogProps, PollOptionSelectorProps, QuotedMessagePreviewProps, ReactionOptions, ReactionSelectorProps, ReactionsListModalProps, ReactionsListProps, RecordingPermissionDeniedNotificationProps, SendButtonProps, StartRecordingAudioButtonProps, StreamedMessageTextProps, TextareaComposerProps, ThreadHeaderProps, ThreadListItemProps, ThreadListItemUIProps, TimestampProps, TypingIndicatorProps, UnreadMessagesNotificationProps, UnreadMessagesSeparatorProps } from '../components';
|
|
4
4
|
import type { SuggestionItemProps, SuggestionListProps } from '../components/TextareaComposer';
|
|
5
5
|
import type { SearchProps, SearchResultsPresearchProps, SearchSourceResultListProps } from '../experimental';
|
|
6
6
|
import type { PropsWithChildrenOnly, UnknownType } from '../types/types';
|
|
@@ -141,6 +141,8 @@ export type ComponentContextValue = {
|
|
|
141
141
|
StartRecordingAudioButton?: React.ComponentType<StartRecordingAudioButtonProps>;
|
|
142
142
|
StopAIGenerationButton?: React.ComponentType<StopAIGenerationButtonProps> | null;
|
|
143
143
|
StreamedMessageText?: React.ComponentType<StreamedMessageTextProps>;
|
|
144
|
+
/** Custom UI component to handle message text input, defaults to and accepts same props as [TextareaComposer](https://github.com/GetStream/stream-chat-react/blob/master/src/components/TextareaComposer/TextareaComposer.tsx) */
|
|
145
|
+
TextareaComposer?: React.ComponentType<TextareaComposerProps>;
|
|
144
146
|
/** Custom UI component that displays thread's parent or other message at the top of the `MessageList`, defaults to and accepts same props as [MessageSimple](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Message/MessageSimple.tsx) */
|
|
145
147
|
ThreadHead?: React.ComponentType<MessageProps>;
|
|
146
148
|
/** Custom UI component to display the header of a `Thread`, defaults to and accepts same props as: [DefaultThreadHeader](https://github.com/GetStream/stream-chat-react/blob/master/src/components/Thread/Thread.tsx) */
|
|
@@ -155,8 +157,6 @@ export type ComponentContextValue = {
|
|
|
155
157
|
ThreadStart?: React.ComponentType;
|
|
156
158
|
/** Custom UI component to display a date used in timestamps. It's used internally by the default `MessageTimestamp`, and to display a timestamp for edited messages. */
|
|
157
159
|
Timestamp?: React.ComponentType<TimestampProps>;
|
|
158
|
-
/** Optional context provider that lets you override the default autocomplete triggers, defaults to: [DefaultTriggerProvider](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageInput/DefaultTriggerProvider.tsx) */
|
|
159
|
-
TriggerProvider?: React.ComponentType;
|
|
160
160
|
/** Custom UI component for the typing indicator, defaults to and accepts same props as: [TypingIndicator](https://github.com/GetStream/stream-chat-react/blob/master/src/components/TypingIndicator/TypingIndicator.tsx) */
|
|
161
161
|
TypingIndicator?: React.ComponentType<TypingIndicatorProps>;
|
|
162
162
|
/** Custom UI component that indicates a user is viewing unread messages. It disappears once the user scrolls to UnreadMessagesSeparator. Defaults to and accepts same props as: [UnreadMessagesNotification](https://github.com/GetStream/stream-chat-react/blob/master/src/components/MessageList/UnreadMessagesNotification.tsx) */
|