stream-chat-react 13.0.0-rc.1 → 13.0.0
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/{useMessageInputText.d.ts → useTextareaRef.d.ts} +1 -2
- package/dist/components/MessageInput/hooks/useTextareaRef.js +14 -0
- package/dist/components/TextareaComposer/SuggestionList/CommandItem.d.ts +2 -8
- package/dist/components/TextareaComposer/SuggestionList/SuggestionList.d.ts +6 -8
- package/dist/components/TextareaComposer/SuggestionList/SuggestionList.js +7 -6
- package/dist/components/TextareaComposer/SuggestionList/SuggestionListItem.d.ts +10 -10
- package/dist/components/TextareaComposer/SuggestionList/SuggestionListItem.js +1 -2
- package/dist/components/TextareaComposer/TextareaComposer.d.ts +3 -5
- package/dist/components/TextareaComposer/TextareaComposer.js +13 -8
- package/dist/context/ComponentContext.d.ts +3 -3
- package/dist/experimental/index.browser.cjs.map +2 -2
- package/dist/experimental/index.node.cjs.map +2 -2
- package/dist/index.browser.cjs +75 -93
- package/dist/index.browser.cjs.map +4 -4
- package/dist/index.node.cjs +75 -93
- 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/package.json +3 -3
- 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.0
|
|
27
|
+
const version = "13.0.0";
|
|
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,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
|
+
};
|
|
@@ -1,13 +1,7 @@
|
|
|
1
1
|
import type { PropsWithChildren } from 'react';
|
|
2
2
|
import React from 'react';
|
|
3
|
+
import type { CommandResponse } from 'stream-chat';
|
|
3
4
|
export type CommandItemProps = {
|
|
4
|
-
entity:
|
|
5
|
-
/** Arguments of command */
|
|
6
|
-
args?: string;
|
|
7
|
-
/** Description of command */
|
|
8
|
-
description?: string;
|
|
9
|
-
/** Name of the command */
|
|
10
|
-
name?: string;
|
|
11
|
-
};
|
|
5
|
+
entity: CommandResponse;
|
|
12
6
|
};
|
|
13
7
|
export declare const CommandItem: (props: PropsWithChildren<CommandItemProps>) => React.JSX.Element;
|
|
@@ -1,16 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { SuggestionListItemComponentProps } from './SuggestionListItem';
|
|
3
|
+
type SuggestionTrigger = '/' | ':' | '@' | string;
|
|
3
4
|
export type SuggestionListProps = Partial<{
|
|
4
|
-
|
|
5
|
+
suggestionItemComponents: Record<SuggestionTrigger, React.ComponentType<SuggestionListItemComponentProps>>;
|
|
5
6
|
className?: string;
|
|
6
7
|
closeOnClickOutside?: boolean;
|
|
7
8
|
containerClassName?: string;
|
|
8
9
|
focusedItemIndex: number;
|
|
9
10
|
setFocusedItemIndex: (index: number) => void;
|
|
10
11
|
}>;
|
|
11
|
-
export declare const defaultComponents:
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
'@': ({ Avatar, entity }: import("./UserItem").UserItemProps) => React.JSX.Element | null;
|
|
15
|
-
};
|
|
16
|
-
export declare const SuggestionList: ({ className, closeOnClickOutside, containerClassName, focusedItemIndex, setFocusedItemIndex, }: SuggestionListProps) => React.JSX.Element | null;
|
|
12
|
+
export declare const defaultComponents: Record<SuggestionTrigger, React.ComponentType<SuggestionListItemComponentProps>>;
|
|
13
|
+
export declare const SuggestionList: ({ className, closeOnClickOutside, containerClassName, focusedItemIndex, setFocusedItemIndex, suggestionItemComponents, }: SuggestionListProps) => React.JSX.Element | null;
|
|
14
|
+
export {};
|
|
@@ -15,19 +15,20 @@ const searchSourceStateSelector = (nextValue) => ({
|
|
|
15
15
|
items: nextValue.items ?? [],
|
|
16
16
|
});
|
|
17
17
|
export const defaultComponents = {
|
|
18
|
-
'/': CommandItem,
|
|
19
|
-
':': EmoticonItem,
|
|
20
|
-
'@': UserItem,
|
|
18
|
+
'/': (props) => (React.createElement(CommandItem, { entity: props.entity })),
|
|
19
|
+
':': (props) => (React.createElement(EmoticonItem, { entity: props.entity })),
|
|
20
|
+
'@': (props) => (React.createElement(UserItem, { entity: props.entity })),
|
|
21
21
|
};
|
|
22
|
-
export const SuggestionList = ({ className, closeOnClickOutside = true, containerClassName, focusedItemIndex, setFocusedItemIndex, }) => {
|
|
22
|
+
export const SuggestionList = ({ className, closeOnClickOutside = true, containerClassName, focusedItemIndex, setFocusedItemIndex, suggestionItemComponents = defaultComponents, }) => {
|
|
23
23
|
const { AutocompleteSuggestionItem = DefaultSuggestionListItem } = useComponentContext();
|
|
24
24
|
const messageComposer = useMessageComposer();
|
|
25
25
|
const { textComposer } = messageComposer;
|
|
26
26
|
const { suggestions } = useStateStore(textComposer.state, textComposerStateSelector);
|
|
27
27
|
const { items } = useStateStore(suggestions?.searchSource.state, searchSourceStateSelector) ?? {};
|
|
28
28
|
const [container, setContainer] = useState(null);
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const component = suggestions?.trigger
|
|
30
|
+
? suggestionItemComponents[suggestions?.trigger]
|
|
31
|
+
: undefined;
|
|
31
32
|
useEffect(() => {
|
|
32
33
|
if (!closeOnClickOutside || !suggestions || !container)
|
|
33
34
|
return;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export type
|
|
7
|
-
export type
|
|
2
|
+
import type { TextComposerSuggestion } from 'stream-chat';
|
|
3
|
+
import type { UserItemProps } from './UserItem';
|
|
4
|
+
import type { CommandItemProps } from './CommandItem';
|
|
5
|
+
import type { EmoticonItemProps } from './EmoticonItem';
|
|
6
|
+
export type DefaultSuggestionListItemEntity = UserItemProps['entity'] | CommandItemProps['entity'] | EmoticonItemProps['entity'];
|
|
7
|
+
export type SuggestionListItemComponentProps = {
|
|
8
|
+
entity: DefaultSuggestionListItemEntity | unknown;
|
|
9
|
+
focused: boolean;
|
|
10
|
+
};
|
|
8
11
|
export type SuggestionItemProps = {
|
|
9
|
-
component: React.ComponentType<
|
|
10
|
-
entity: SuggestionItem;
|
|
11
|
-
focused: boolean;
|
|
12
|
-
}>;
|
|
12
|
+
component: React.ComponentType<SuggestionListItemComponentProps>;
|
|
13
13
|
item: TextComposerSuggestion;
|
|
14
14
|
focused: boolean;
|
|
15
15
|
className?: string;
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import clsx from 'clsx';
|
|
2
|
-
import { useLayoutEffect } from 'react';
|
|
3
|
-
import React, { useCallback, useRef } from 'react';
|
|
2
|
+
import React, { useCallback, useLayoutEffect, useRef } from 'react';
|
|
4
3
|
import { useMessageComposer } from '../../MessageInput';
|
|
5
4
|
export const SuggestionListItem = React.forwardRef(function SuggestionListItem({ className, component: Component, focused, item, onMouseEnter }, innerRef) {
|
|
6
5
|
const { textComposer } = useMessageComposer();
|
|
@@ -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 = 1, 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 grow = growProp ?? growContext;
|
|
32
|
+
const { additionalTextareaProps, cooldownRemaining, handleSubmit, maxRows: maxRowsContext, minRows: minRowsContext, onPaste, shouldSubmit: shouldSubmitContext, textareaRef, } = useMessageInputContext();
|
|
36
33
|
const maxRows = maxRowsProp ?? maxRowsContext;
|
|
34
|
+
const minRows = minRowsProp ?? minRowsContext;
|
|
37
35
|
const placeholder = placeholderProp ?? additionalTextareaProps?.placeholder;
|
|
38
36
|
const shouldSubmit = shouldSubmitProp ?? shouldSubmitContext ?? defaultShouldSubmit;
|
|
39
37
|
const messageComposer = useMessageComposer();
|
|
@@ -130,6 +128,13 @@ listClassName, maxRows: maxRowsProp = 1, onBlur, onChange, onKeyDown, onScroll,
|
|
|
130
128
|
textComposer.closeSuggestions();
|
|
131
129
|
}
|
|
132
130
|
}, [onScroll, textComposer]);
|
|
131
|
+
const setSelectionDebounced = useMemo(() => debounce((e) => {
|
|
132
|
+
onSelect?.(e);
|
|
133
|
+
textComposer.setSelection({
|
|
134
|
+
end: e.target.selectionEnd,
|
|
135
|
+
start: e.target.selectionStart,
|
|
136
|
+
});
|
|
137
|
+
}, 100, { leading: false, trailing: true }), [onSelect, textComposer]);
|
|
133
138
|
useEffect(() => {
|
|
134
139
|
// FIXME: find the real reason for cursor being set to the end on each change
|
|
135
140
|
// This is a workaround to prevent the cursor from jumping
|
|
@@ -148,7 +153,7 @@ listClassName, maxRows: maxRowsProp = 1, onBlur, onChange, onKeyDown, onScroll,
|
|
|
148
153
|
return (React.createElement("div", { className: clsx('rta', 'str-chat__textarea str-chat__message-textarea-react-host', containerClassName, {
|
|
149
154
|
['rta--loading']: isLoadingItems,
|
|
150
155
|
}), ref: containerRef },
|
|
151
|
-
React.createElement(Textarea, { ...
|
|
156
|
+
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
157
|
textareaRef.current = ref;
|
|
153
158
|
}, value: text }),
|
|
154
159
|
!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) */
|