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.
Files changed (36) hide show
  1. package/dist/components/Channel/Channel.d.ts +1 -1
  2. package/dist/components/Channel/Channel.js +2 -0
  3. package/dist/components/Chat/hooks/useChat.js +1 -1
  4. package/dist/components/MessageInput/EditMessageForm.js +1 -1
  5. package/dist/components/MessageInput/MessageInput.d.ts +1 -3
  6. package/dist/components/MessageInput/MessageInputFlat.js +2 -2
  7. package/dist/components/MessageInput/hooks/useCreateMessageInputContext.js +1 -3
  8. package/dist/components/MessageInput/hooks/useMessageInputControls.d.ts +0 -1
  9. package/dist/components/MessageInput/hooks/useMessageInputControls.js +3 -4
  10. package/dist/components/MessageInput/hooks/usePasteHandler.d.ts +1 -1
  11. package/dist/components/MessageInput/hooks/usePasteHandler.js +4 -4
  12. package/dist/components/MessageInput/hooks/{useMessageInputText.d.ts → useTextareaRef.d.ts} +1 -2
  13. package/dist/components/MessageInput/hooks/useTextareaRef.js +14 -0
  14. package/dist/components/TextareaComposer/SuggestionList/CommandItem.d.ts +2 -8
  15. package/dist/components/TextareaComposer/SuggestionList/SuggestionList.d.ts +6 -8
  16. package/dist/components/TextareaComposer/SuggestionList/SuggestionList.js +7 -6
  17. package/dist/components/TextareaComposer/SuggestionList/SuggestionListItem.d.ts +10 -10
  18. package/dist/components/TextareaComposer/SuggestionList/SuggestionListItem.js +1 -2
  19. package/dist/components/TextareaComposer/TextareaComposer.d.ts +3 -5
  20. package/dist/components/TextareaComposer/TextareaComposer.js +13 -8
  21. package/dist/context/ComponentContext.d.ts +3 -3
  22. package/dist/experimental/index.browser.cjs.map +2 -2
  23. package/dist/experimental/index.node.cjs.map +2 -2
  24. package/dist/index.browser.cjs +75 -93
  25. package/dist/index.browser.cjs.map +4 -4
  26. package/dist/index.node.cjs +75 -93
  27. package/dist/index.node.cjs.map +4 -4
  28. package/dist/plugins/Emojis/EmojiPicker.js +9 -4
  29. package/dist/plugins/Emojis/index.browser.cjs +208 -96
  30. package/dist/plugins/Emojis/index.browser.cjs.map +4 -4
  31. package/dist/plugins/Emojis/index.node.cjs +208 -96
  32. package/dist/plugins/Emojis/index.node.cjs.map +4 -4
  33. package/dist/plugins/Emojis/middleware/textComposerEmojiMiddleware.d.ts +4 -48
  34. package/dist/plugins/Emojis/middleware/textComposerEmojiMiddleware.js +52 -58
  35. package/package.json +3 -3
  36. 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-rc.1";
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, grow: true, hideSendButton: true, Input: EditMessageInput, ...additionalMessageInputProps })));
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('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, 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, grow, handleSubmit, hideSendButton, insertText, isThreadInput, maxRows, minRows, onPaste, parent, recordingController, setCooldownRemaining, shouldSubmit, textareaRef, } = value;
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 { useMessageInputText } from './useMessageInputText';
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 { insertText, textareaRef } = useMessageInputText(props);
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(insertText);
15
+ const { onPaste } = usePasteHandler();
16
16
  return {
17
17
  handleSubmit,
18
- insertText,
19
18
  onPaste,
20
19
  recordingController,
21
20
  textareaRef,
@@ -1,3 +1,3 @@
1
- export declare const usePasteHandler: (insertText: (textToInsert: string) => void) => {
1
+ export declare const usePasteHandler: () => {
2
2
  onPaste: (clipboardEvent: React.ClipboardEvent<HTMLTextAreaElement>) => void;
3
3
  };
@@ -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 = (insertText) => {
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, insertText]);
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 useMessageInputText: (props: MessageInputProps) => {
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 { SuggestionItemProps } from './SuggestionListItem';
2
+ import type { SuggestionListItemComponentProps } from './SuggestionListItem';
3
+ type SuggestionTrigger = '/' | ':' | '@' | string;
3
4
  export type SuggestionListProps = Partial<{
4
- SuggestionItem: React.ComponentType<SuggestionItemProps>;
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
- '/': (props: React.PropsWithChildren<import("./CommandItem").CommandItemProps>) => React.JSX.Element;
13
- ':': (props: import("./EmoticonItem").EmoticonItemProps) => React.JSX.Element | null;
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
- // @ts-expect-error component type mismatch
30
- const component = suggestions?.trigger && defaultComponents[suggestions?.trigger];
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 { CommandResponse, TextComposerSuggestion, UserResponse } from 'stream-chat';
3
- import type { EmojiSearchIndexResult } from '../../MessageInput';
4
- export type SuggestionCommand = CommandResponse;
5
- export type SuggestionUser = UserResponse;
6
- export type SuggestionEmoji = EmojiSearchIndexResult;
7
- export type SuggestionItem = SuggestionUser | SuggestionCommand | SuggestionEmoji;
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 TextComposerProps = Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, 'style' | 'defaultValue' | 'disabled'> & {
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, grow: growProp, listClassName, maxRows: maxRowsProp, onBlur, onChange, onKeyDown, onScroll, placeholder: placeholderProp, shouldSubmit: shouldSubmitProp, ...restProps }: TextComposerProps) => React.JSX.Element;
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, grow: growContext, handleSubmit, maxRows: maxRowsContext, onPaste, shouldSubmit: shouldSubmitContext, textareaRef, } = useMessageInputContext();
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, { ...restProps, "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: grow ? maxRows : 1, onBlur: onBlur, onChange: changeHandler, onCompositionEnd: onCompositionEnd, onCompositionStart: onCompositionStart, onKeyDown: keyDownHandler, onPaste: onPaste, onScroll: scrollHandler, placeholder: placeholder || t('Type your message'), ref: (ref) => {
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) */