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
JavaScript
// 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