UNPKG

communication-react-19

Version:

React library for building modern communication user experiences utilizing Azure Communication Services (React 19 compatible fork)

553 lines 32.1 kB
// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { Icon, mergeStyles, PrimaryButton } from '@fluentui/react'; import { Chat } from '@fluentui-contrib/react-chat'; import { mergeClasses, useArrowNavigationGroup } from '@fluentui/react-components'; import { DownIconStyle, newMessageButtonContainerStyle, messageThreadContainerStyle, messageThreadWrapperContainerStyle, useChatStyles, buttonWithIconStyles, newMessageButtonStyle } from './styles/MessageThread.styles'; import { delay } from './utils/delay'; import { memoizeFnAll } from "../../../acs-ui-common/src"; import { useLocale } from '../localization/LocalizationProvider'; import { isNarrowWidth, _useContainerWidth } from './utils/responsive'; import getParticipantsWhoHaveReadMessage from './utils/getParticipantsWhoHaveReadMessage'; import { useTheme } from '../theming'; import { FluentV9ThemeProvider } from './../theming/FluentV9ThemeProvider'; import LiveAnnouncer from './Announcer/LiveAnnouncer'; import { createStyleFromV8Style } from './styles/v8StyleShim'; import { ChatMessageComponentWrapper } from './ChatMessage/ChatMessageComponentWrapper'; import { MessageStatusIndicatorInternal } from './MessageStatusIndicatorInternal'; import { Announcer } from './Announcer'; /* @conditional-compile-remove(rich-text-editor) */ import { loadChatMessageComponentAsRichTextEditBox } from './ChatMessage/MyMessageComponents/ChatMessageComponentAsEditBoxPicker'; const isMessageSame = (first, second) => { return (first.messageId === second.messageId && first.content === second.content && first.contentType === second.contentType && JSON.stringify(first.createdOn) === JSON.stringify(second.createdOn) && first.senderId === second.senderId && first.senderDisplayName === second.senderDisplayName && JSON.stringify(first.editedOn) === JSON.stringify(second.editedOn)); }; /** * Get the latest message from the message array. * * @param messages */ const getLatestChatMessage = (messages) => { for (let i = messages.length - 1; i >= 0; i--) { const message = messages[i]; if ((message === null || message === void 0 ? void 0 : message.messageType) === 'chat' && !!message.createdOn) { return message; } } return undefined; }; /** * Compare latestMessageFromPreviousMessages & latestMessageFromNewMessages to see if the new message is not from * current user. */ const isThereNewMessageNotFromCurrentUser = (userId, latestMessageFromPreviousMessages, latestMessageFromNewMessages) => { if (latestMessageFromNewMessages === undefined) { return false; } if (latestMessageFromPreviousMessages === undefined) { return latestMessageFromNewMessages.senderId !== userId; } return (!isMessageSame(latestMessageFromNewMessages, latestMessageFromPreviousMessages) && latestMessageFromNewMessages.senderId !== userId); }; /** * Returns true if the current user sent the latest message and false otherwise. It will ignore messages that have no * sender, messages that have failed to send, and messages from the current user that is marked as SEEN. This is meant * as an indirect way to detect if user is at bottom of the chat when the component updates with new messages. If we * updated this component due to current user sending a message we want to then call scrollToBottom. */ const didUserSendTheLatestMessage = (userId, latestMessageFromPreviousMessages, latestMessageFromNewMessages) => { if (latestMessageFromNewMessages === undefined) { return false; } if (latestMessageFromPreviousMessages === undefined) { return latestMessageFromNewMessages.senderId === userId; } return (!isMessageSame(latestMessageFromNewMessages, latestMessageFromPreviousMessages) && latestMessageFromNewMessages.senderId === userId); }; const DefaultJumpToNewMessageButton = (props) => { const { text, onClick } = props; return (React.createElement(PrimaryButton, { className: newMessageButtonStyle, styles: buttonWithIconStyles, text: text, onClick: onClick, onRenderIcon: () => React.createElement(Icon, { iconName: "Down", className: DownIconStyle }) })); }; const memoizeAllMessages = memoizeFnAll((message, showMessageDate, showMessageStatus, strings, index, onUpdateMessage, onCancelEditMessage, onDeleteMessage, onSendMessage, disableEditing, lastSeenChatMessage, lastSendingChatMessage, lastDeliveredChatMessage) => { let key = message.messageId; let statusToRender = undefined; if (message.messageType === 'chat' || /* @conditional-compile-remove(data-loss-prevention) */ message.messageType === 'blocked') { if ((!message.messageId || message.messageId === '') && 'clientMessageId' in message) { key = message.clientMessageId; } if (showMessageStatus && message.mine) { switch (message.messageId) { case lastSeenChatMessage: { statusToRender = 'seen'; break; } case lastSendingChatMessage: { statusToRender = 'sending'; break; } case lastDeliveredChatMessage: { statusToRender = 'delivered'; break; } } } if (message.mine && message.status === 'failed') { statusToRender = 'failed'; } } return { key: key !== null && key !== void 0 ? key : 'id_' + index, statusToRender, message, strings, showDate: showMessageDate, onUpdateMessage, onCancelEditMessage, onDeleteMessage, onSendMessage, disableEditing, showMessageStatus }; }); const getLastChatMessageIdWithStatus = (messages, status) => { for (let i = messages.length - 1; i >= 0; i--) { const message = messages[i]; if ((message === null || message === void 0 ? void 0 : message.messageType) === 'chat' && message.status === status && message.mine) { return message.messageId; } } return undefined; }; const getLastChatMessageForCurrentUser = (messages) => { for (let i = messages.length - 1; i >= 0; i--) { const message = messages[i]; if ((message === null || message === void 0 ? void 0 : message.messageType) === 'chat' && message.mine) { return message; } } return undefined; }; /** * `MessageThread` allows you to easily create a component for rendering chat messages, handling scrolling behavior of new/old messages and customizing icons & controls inside the chat thread. * @param props - of type MessageThreadProps * * Users will need to provide at least chat messages and userId to render the `MessageThread` component. * Users can also customize `MessageThread` by passing in their own Avatar, `MessageStatusIndicator` icon, `JumpToNewMessageButton`, `LoadPreviousMessagesButton` and the behavior of these controls. * * `MessageThread` internally uses the `Chat` component from `@fluentui-contrib/chat`. You can checkout the details about these components [here](https://microsoft.github.io/fluentui-contrib/react-chat/). * * @public */ export const MessageThread = (props) => { var _a; const theme = useTheme(); const chatBody = useMemo(() => { return (React.createElement(FluentV9ThemeProvider, { v8Theme: theme }, React.createElement(MessageThreadWrapper, Object.assign({}, props)))); }, [theme, props]); return React.createElement("div", { className: mergeStyles(messageThreadContainerStyle, (_a = props.styles) === null || _a === void 0 ? void 0 : _a.root) }, chatBody); }; /** * @private */ export const MessageThreadWrapper = (props) => { var _a; const { messages: newMessages, userId, participantCount, readReceiptsBySenderId, styles, disableJumpToNewMessageButton = false, showMessageDate = false, showMessageStatus = false, numberOfChatMessagesToReload = 5, onMessageSeen, onRenderMessageStatus, onRenderAvatar, onLoadPreviousChatMessages, onRenderJumpToNewMessageButton, onRenderMessage, onUpdateMessage, onCancelEditMessage, onDeleteMessage, onSendMessage, /* @conditional-compile-remove(date-time-customization) */ onDisplayDateTimeString, /* @conditional-compile-remove(mention) */ mentionOptions, inlineImageOptions, /* @conditional-compile-remove(file-sharing-acs) */ attachmentOptions, /* @conditional-compile-remove(file-sharing-acs) */ onRenderAttachmentDownloads, /* @conditional-compile-remove(rich-text-editor) */ richTextEditorOptions } = props; // We need this state to wait for one tick and scroll to bottom after messages have been initialized. // Otherwise chatScrollDivRef.current.clientHeight is wrong if we scroll to bottom before messages are initialized. const [chatMessagesInitialized, setChatMessagesInitialized] = useState(false); const [isAtBottomOfScroll, setIsAtBottomOfScroll] = useState(true); const [forceUpdate, setForceUpdate] = useState(0); // Used to decide if should auto scroll to bottom or show "new message" button const [latestPreviousChatMessage, setLatestPreviousChatMessage] = useState(undefined); const [latestCurrentChatMessage, setLatestCurrentChatMessage] = useState(undefined); const [existsNewChatMessage, setExistsNewChatMessage] = useState(false); const [lastSeenChatMessage, setLastSeenChatMessage] = useState(undefined); const [lastDeliveredChatMessage, setLastDeliveredChatMessage] = useState(undefined); const [lastSendingChatMessage, setLastSendingChatMessage] = useState(undefined); // readCount and participantCount will only need to be updated on-fly when user hover on an indicator const [readCountForHoveredIndicator, setReadCountForHoveredIndicator] = useState(undefined); const localeStrings = useLocale().strings.messageThread; const strings = useMemo(() => (Object.assign(Object.assign({}, localeStrings), props.strings)), [localeStrings, props.strings]); // it is required to use useState for messages // as the scrolling logic requires re - render at a specific point in time const [messages, setMessages] = useState([]); // id for the latest deleted message const [latestDeletedMessageId, setLatestDeletedMessageId] = useState(undefined); // this value is used to check if a message is deleted for the previous value of messages array const previousMessagesRef = useRef([]); // an aria label for Narrator to notify when a message is deleted const [deletedMessageAriaLabel, setDeletedMessageAriaLabel] = useState(undefined); /* @conditional-compile-remove(rich-text-editor) */ useEffect(() => { // if rich text editor is enabled, the rich text editor component should be loaded early for good UX if (richTextEditorOptions !== undefined) { // this line is needed to load the Rooster JS dependencies early in the lifecycle // when the rich text editor is enabled loadChatMessageComponentAsRichTextEditBox(); } }, [richTextEditorOptions]); const onDeleteMessageCallback = useCallback((messageId) => __awaiter(void 0, void 0, void 0, function* () { if (!onDeleteMessage) { return; } try { // reset deleted message label in case if there was a value already (messages are deleted 1 after another) setDeletedMessageAriaLabel(undefined); setLatestDeletedMessageId(messageId); lastChatMessageStatus.current = 'deleted'; // we should set up latestDeletedMessageId before the onDeleteMessage call // as otherwise in very rare cases the messages array can be updated before latestDeletedMessageId yield onDeleteMessage(messageId); } catch (e) { console.log('onDeleteMessage failed: messageId', messageId, 'error', e); } }), [onDeleteMessage]); const isAllChatMessagesLoadedRef = useRef(false); // isAllChatMessagesLoadedRef needs to be updated every time when a new adapter is set in order to display correct data // onLoadPreviousChatMessages is updated when a new adapter is set useEffect(() => { if (onLoadPreviousChatMessages) { isAllChatMessagesLoadedRef.current = false; } }, [onLoadPreviousChatMessages]); const previousTopRef = useRef(-1); const previousHeightRef = useRef(-1); const messageIdSeenByMeRef = useRef(''); const chatScrollDivRef = useRef(null); const isLoadingChatMessagesRef = useRef(false); useEffect(() => { if (latestDeletedMessageId === undefined) { setDeletedMessageAriaLabel(undefined); } else { if (!previousMessagesRef.current.find((message) => message.messageId === latestDeletedMessageId)) { // the message is deleted in previousMessagesRef // no need to update deletedMessageAriaLabel setDeletedMessageAriaLabel(undefined); } else if (!messages.find((message) => message.messageId === latestDeletedMessageId)) { // the message is deleted in messages array but still exists in previousMessagesRef // update deletedMessageAriaLabel setDeletedMessageAriaLabel(strings.messageDeletedAnnouncementAriaLabel); } else { // the message exists in both arrays // no need to update deletedMessageAriaLabel setDeletedMessageAriaLabel(undefined); } } previousMessagesRef.current = messages; }, [latestDeletedMessageId, messages, strings.messageDeletedAnnouncementAriaLabel]); const messagesRef = useRef(messages); const setMessagesRef = (messagesWithAttachedValue) => { messagesRef.current = messagesWithAttachedValue; setMessages(messagesWithAttachedValue); }; const isAtBottomOfScrollRef = useRef(isAtBottomOfScroll); const setIsAtBottomOfScrollRef = (isAtBottomOfScrollValue) => { isAtBottomOfScrollRef.current = isAtBottomOfScrollValue; setIsAtBottomOfScroll(isAtBottomOfScrollValue); }; const chatMessagesInitializedRef = useRef(chatMessagesInitialized); const setChatMessagesInitializedRef = (chatMessagesInitialized) => { chatMessagesInitializedRef.current = chatMessagesInitialized; setChatMessagesInitialized(chatMessagesInitialized); }; const chatThreadRef = useRef(null); // When the chat thread is narrow, we perform space optimizations such as overlapping // the avatar on top of the chat message and moving the chat accept/reject edit buttons // to a new line const chatThreadWidth = _useContainerWidth(chatThreadRef); const isNarrow = chatThreadWidth ? isNarrowWidth(chatThreadWidth) : false; /** * ClientHeight controls the number of messages to render. However ClientHeight will not be initialized after the * first render (not sure but I guess Fluent is updating it in hook which is after render maybe?) so we need to * trigger a re-render until ClientHeight is initialized. This force re-render should only happen once. */ const clientHeight = (_a = chatThreadRef.current) === null || _a === void 0 ? void 0 : _a.clientHeight; // we try to only send those message status if user is scrolled to the bottom. const sendMessageStatusIfAtBottom = useCallback(() => __awaiter(void 0, void 0, void 0, function* () { if (!isAtBottomOfScrollRef.current || !document.hasFocus() || !messagesRef.current || messagesRef.current.length === 0 || !showMessageStatus) { return; } const messagesWithId = messagesRef.current.filter((message) => { return message.messageType === 'chat' && !message.mine && !!message.messageId; }); if (messagesWithId.length === 0) { return; } const lastMessage = messagesWithId[messagesWithId.length - 1]; try { if (onMessageSeen && lastMessage && lastMessage.messageId && lastMessage.messageId !== messageIdSeenByMeRef.current) { yield onMessageSeen(lastMessage.messageId); messageIdSeenByMeRef.current = lastMessage.messageId; } } catch (e) { console.log('onMessageSeen Error', lastMessage, e); } }), [showMessageStatus, onMessageSeen]); const scrollToBottom = useCallback(() => { if (chatScrollDivRef.current) { chatScrollDivRef.current.scrollTop = chatScrollDivRef.current.scrollHeight; } setExistsNewChatMessage(false); setIsAtBottomOfScrollRef(true); sendMessageStatusIfAtBottom(); }, [sendMessageStatusIfAtBottom]); const handleScrollToTheBottom = useCallback(() => { if (!chatScrollDivRef.current) { return; } const atBottom = Math.ceil(chatScrollDivRef.current.scrollTop) >= chatScrollDivRef.current.scrollHeight - chatScrollDivRef.current.clientHeight; if (atBottom) { sendMessageStatusIfAtBottom(); if (!isAtBottomOfScrollRef.current) { scrollToBottom(); } } setIsAtBottomOfScrollRef(atBottom); }, [scrollToBottom, sendMessageStatusIfAtBottom]); // Infinite scrolling + threadInitialize function const fetchNewMessageWhenAtTop = useCallback(() => __awaiter(void 0, void 0, void 0, function* () { if (!isLoadingChatMessagesRef.current) { if (onLoadPreviousChatMessages) { isLoadingChatMessagesRef.current = true; try { // Fetch message until scrollTop reach the threshold for fetching new message while (!isAllChatMessagesLoadedRef.current && chatScrollDivRef.current && chatScrollDivRef.current.scrollTop <= 500) { isAllChatMessagesLoadedRef.current = yield onLoadPreviousChatMessages(numberOfChatMessagesToReload); yield delay(200); } } finally { // Set isLoadingChatMessagesRef to false after messages are fetched isLoadingChatMessagesRef.current = false; } } } }), [numberOfChatMessagesToReload, onLoadPreviousChatMessages]); // The below 2 of useEffects are design for fixing infinite scrolling problem // Scrolling element will behave differently when scrollTop = 0(it sticks at the top) // we need to get previousTop before it prepend contents // Execute order [newMessage useEffect] => get previousTop => dom update => [messages useEffect] useEffect(() => { if (!chatScrollDivRef.current) { return; } previousTopRef.current = chatScrollDivRef.current.scrollTop; previousHeightRef.current = chatScrollDivRef.current.scrollHeight; }, [newMessages]); useEffect(() => { if (!chatScrollDivRef.current) { return; } chatScrollDivRef.current.scrollTop = chatScrollDivRef.current.scrollHeight - (previousHeightRef.current - previousTopRef.current); }, [messages]); // Fetch more messages to make the scroll bar appear, infinity scroll is then handled in the handleScroll function. useEffect(() => { fetchNewMessageWhenAtTop(); }, [fetchNewMessageWhenAtTop]); /** * One time run useEffects. Sets up listeners when component is mounted and tears down listeners when component * unmounts unless these function changed */ useEffect(() => { window && window.addEventListener('click', sendMessageStatusIfAtBottom); window && window.addEventListener('focus', sendMessageStatusIfAtBottom); return () => { window && window.removeEventListener('click', sendMessageStatusIfAtBottom); window && window.removeEventListener('focus', sendMessageStatusIfAtBottom); }; }, [sendMessageStatusIfAtBottom]); useEffect(() => { const chatScrollDiv = chatScrollDivRef.current; chatScrollDiv === null || chatScrollDiv === void 0 ? void 0 : chatScrollDiv.addEventListener('scroll', handleScrollToTheBottom); chatScrollDiv === null || chatScrollDiv === void 0 ? void 0 : chatScrollDiv.addEventListener('scroll', fetchNewMessageWhenAtTop); return () => { chatScrollDiv === null || chatScrollDiv === void 0 ? void 0 : chatScrollDiv.removeEventListener('scroll', handleScrollToTheBottom); chatScrollDiv === null || chatScrollDiv === void 0 ? void 0 : chatScrollDiv.removeEventListener('scroll', fetchNewMessageWhenAtTop); }; }, [fetchNewMessageWhenAtTop, handleScrollToTheBottom]); useEffect(() => { if (clientHeight === undefined) { setForceUpdate(forceUpdate + 1); return; } // Only scroll to bottom if isAtBottomOfScrollRef is true isAtBottomOfScrollRef.current && scrollToBottom(); }, [clientHeight, forceUpdate, scrollToBottom, chatMessagesInitialized]); useEffect(() => { var _a; const newStatus = (_a = getLastChatMessageForCurrentUser(newMessages)) === null || _a === void 0 ? void 0 : _a.status; if (newStatus !== undefined) { if (lastChatMessageStatus.current === 'deleted' && newStatus === 'sending') { // enforce message life cycle // message status should always be [ sending -> delivered -> seen (optional) -> deleted ] or [sending -> failed -> deleted] // not any other way around // therefore, if current message status is deleted, we should only update it if newStatus is sending lastChatMessageStatus.current = newStatus; } else if (lastChatMessageStatus.current !== 'deleted') { lastChatMessageStatus.current = newStatus; } } // The hook should depend on newMessages not on messages as otherwise it will skip the sending status for a first message }, [newMessages]); /** * This needs to run to update latestPreviousChatMessage & latestCurrentChatMessage. * These two states are used to manipulate scrollbar */ useEffect(() => { setLatestPreviousChatMessage(getLatestChatMessage(messagesRef.current)); setLatestCurrentChatMessage(getLatestChatMessage(newMessages)); setMessagesRef(newMessages); !chatMessagesInitializedRef.current && setChatMessagesInitializedRef(true); setLastDeliveredChatMessage(getLastChatMessageIdWithStatus(newMessages, 'delivered')); setLastSeenChatMessage(getLastChatMessageIdWithStatus(newMessages, 'seen')); setLastSendingChatMessage(getLastChatMessageIdWithStatus(newMessages, 'sending')); }, [newMessages]); /** * This needs to run after messages are rendered so we can manipulate the scroll bar. */ useEffect(() => { // If user just sent the latest message then we assume we can move user to bottom of scroll. if (isThereNewMessageNotFromCurrentUser(userId, latestPreviousChatMessage, latestCurrentChatMessage) && !isAtBottomOfScrollRef.current) { setExistsNewChatMessage(true); } else if (didUserSendTheLatestMessage(userId, latestPreviousChatMessage, latestCurrentChatMessage) || isAtBottomOfScrollRef.current) { scrollToBottom(); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [messages]); const lastChatMessageStatus = useRef(undefined); const participantCountRef = useRef(participantCount); const readReceiptsBySenderIdRef = useRef(readReceiptsBySenderId); participantCountRef.current = participantCount; readReceiptsBySenderIdRef.current = readReceiptsBySenderId; const onActionButtonClickMemo = useCallback((message, setMessageReadBy) => { if (participantCountRef.current && participantCountRef.current - 1 > 1 && readReceiptsBySenderIdRef.current) { setMessageReadBy(getParticipantsWhoHaveReadMessage(message, readReceiptsBySenderIdRef.current)); } }, []); const defaultStatusRenderer = useCallback((message, participantCount, readCount, status) => { // we should only announce label if the message status isn't deleted // because after message is deleted, we now need to render statusIndicator for previous messages // and their status has been announced already and we should not announce them again const shouldAnnounce = lastChatMessageStatus.current !== 'deleted'; const onToggleToolTip = (isToggled) => { if (isToggled && readReceiptsBySenderIdRef.current) { setReadCountForHoveredIndicator(getParticipantsWhoHaveReadMessage(message, readReceiptsBySenderIdRef.current).length); } else { setReadCountForHoveredIndicator(undefined); } }; return (React.createElement(MessageStatusIndicatorInternal, { status: status, readCount: readCount, onToggleToolTip: onToggleToolTip, // -1 because participant count does not include myself remoteParticipantsCount: participantCount ? participantCount - 1 : 0, shouldAnnounce: shouldAnnounce })); }, []); const theme = useTheme(); const messagesToDisplay = useMemo(() => { return memoizeAllMessages((memoizedMessageFn) => { return messages.map((message, index) => { return memoizedMessageFn(message, showMessageDate, showMessageStatus, strings, index, onUpdateMessage, onCancelEditMessage, onDeleteMessageCallback, onSendMessage, props.disableEditing, lastDeliveredChatMessage, lastSeenChatMessage, lastSendingChatMessage); }); }); }, [ lastDeliveredChatMessage, lastSeenChatMessage, lastSendingChatMessage, messages, onCancelEditMessage, onDeleteMessageCallback, onSendMessage, onUpdateMessage, props.disableEditing, showMessageDate, showMessageStatus, strings ]); const classes = useChatStyles(); const chatArrowNavigationAttributes = useArrowNavigationGroup({ axis: 'vertical', memorizeCurrent: false }); return (React.createElement("div", { className: mergeStyles(messageThreadWrapperContainerStyle), ref: chatThreadRef }, existsNewChatMessage && !disableJumpToNewMessageButton && (React.createElement("div", { className: mergeStyles(newMessageButtonContainerStyle, styles === null || styles === void 0 ? void 0 : styles.newMessageButtonContainer) }, onRenderJumpToNewMessageButton ? (onRenderJumpToNewMessageButton({ text: strings.newMessagesIndicator, onClick: scrollToBottom })) : (React.createElement(DefaultJumpToNewMessageButton, { text: strings.newMessagesIndicator, onClick: scrollToBottom })))), React.createElement(LiveAnnouncer, null, React.createElement(FluentV9ThemeProvider, { v8Theme: theme }, React.createElement(Chat // styles?.chatContainer used in className and style prop as style prop can't handle CSS selectors , Object.assign({ // styles?.chatContainer used in className and style prop as style prop can't handle CSS selectors className: mergeClasses(classes.root, mergeStyles(styles === null || styles === void 0 ? void 0 : styles.chatContainer)), ref: chatScrollDivRef, style: Object.assign({}, createStyleFromV8Style(styles === null || styles === void 0 ? void 0 : styles.chatContainer)) }, chatArrowNavigationAttributes), latestDeletedMessageId && (React.createElement(Announcer, { key: latestDeletedMessageId, announcementString: deletedMessageAriaLabel, ariaLive: 'polite' })), messagesToDisplay.map((message) => { var _a; return (React.createElement(MemoChatMessageComponentWrapper, Object.assign({}, message, { userId: userId, key: message.key, styles: styles, shouldOverlapAvatarAndMessage: isNarrow, strings: strings, onRenderAvatar: onRenderAvatar, onRenderMessage: onRenderMessage, onRenderMessageStatus: onRenderMessageStatus, defaultStatusRenderer: defaultStatusRenderer, onActionButtonClick: onActionButtonClickMemo, readCount: readCountForHoveredIndicator, participantCount: participantCount, /* @conditional-compile-remove(file-sharing-acs) */ actionsForAttachment: (_a = attachmentOptions === null || attachmentOptions === void 0 ? void 0 : attachmentOptions.downloadOptions) === null || _a === void 0 ? void 0 : _a.actionsForAttachment, inlineImageOptions: inlineImageOptions, /* @conditional-compile-remove(date-time-customization) */ onDisplayDateTimeString: onDisplayDateTimeString, /* @conditional-compile-remove(mention) */ mentionOptions: mentionOptions, /* @conditional-compile-remove(file-sharing-acs) */ onRenderAttachmentDownloads: onRenderAttachmentDownloads, /* @conditional-compile-remove(rich-text-editor) */ isRichTextEditorEnabled: !!richTextEditorOptions, /* @conditional-compile-remove(rich-text-editor-image-upload) */ onPaste: richTextEditorOptions === null || richTextEditorOptions === void 0 ? void 0 : richTextEditorOptions.onPaste, /* @conditional-compile-remove(rich-text-editor-image-upload) */ onInsertInlineImage: richTextEditorOptions === null || richTextEditorOptions === void 0 ? void 0 : richTextEditorOptions.onInsertInlineImage, /* @conditional-compile-remove(rich-text-editor-image-upload) */ inlineImagesWithProgress: (richTextEditorOptions === null || richTextEditorOptions === void 0 ? void 0 : richTextEditorOptions.messagesInlineImagesWithProgress) && (richTextEditorOptions === null || richTextEditorOptions === void 0 ? void 0 : richTextEditorOptions.messagesInlineImagesWithProgress[message.message.messageId]), /* @conditional-compile-remove(rich-text-editor-image-upload) */ onRemoveInlineImage: richTextEditorOptions === null || richTextEditorOptions === void 0 ? void 0 : richTextEditorOptions.onRemoveInlineImage }))); })))))); }; const MemoChatMessageComponentWrapper = React.memo((obj) => { return React.createElement(ChatMessageComponentWrapper, Object.assign({}, obj)); }); //# sourceMappingURL=MessageThread.js.map