@@ -50,6 +50,7 @@ import {
5050 ChatMessageComponentWrapper,
5151 ChatMessageComponentWrapperProps
5252} from './ChatMessage/ChatMessageComponentWrapper';
53+ import { Announcer } from './Announcer';
5354
5455const isMessageSame = (first: ChatMessage, second: ChatMessage): boolean => {
5556 return (
@@ -211,6 +212,8 @@ export interface MessageThreadStrings {
211212 editBoxSubmitButton: string;
212213 /** String for action menu indicating there are more options */
213214 actionMenuMoreOptions?: string;
215+ /** Aria label to announce when a message is deleted */
216+ messageDeletedAnnouncementAriaLabel: string;
214217 /* @conditional-compile-remove(file-sharing) */
215218 /** String for download file button in file card */
216219 downloadFile: string;
@@ -715,6 +718,33 @@ export const MessageThreadWrapper = (props: MessageThreadProps): JSX.Element =>
715718 [onFetchAttachments]
716719 );
717720
721+ const localeStrings = useLocale().strings.messageThread;
722+ const strings = useMemo(() => ({ ...localeStrings, ...props.strings }), [localeStrings, props.strings]);
723+
724+ // id for the latest deleted message
725+ const [latestDeletedMessageId, setLatestDeletedMessageId] = useState<string | undefined>(undefined);
726+ // this value is used to check if a message is deleted for the previous value of messages array
727+ const previousMessagesRef = useRef<Message[]>([]);
728+ // an aria label for Narrator to notify when a message is deleted
729+ const [deletedMessageAriaLabel, setDeletedMessageAriaLabel] = useState<string | undefined>(undefined);
730+
731+ const onDeleteMessageCallback = useCallback(
732+ async (messageId: string): Promise<void> => {
733+ if (!onDeleteMessage) {
734+ return;
735+ }
736+ try {
737+ await onDeleteMessage(messageId);
738+ // reset deleted message label in case if there was a value already (messages are deleted 1 after another)
739+ setDeletedMessageAriaLabel(undefined);
740+ setLatestDeletedMessageId(messageId);
741+ } catch (e) {
742+ console.log('onDeleteMessage failed: messageId', messageId, 'error', e);
743+ }
744+ },
745+ [onDeleteMessage]
746+ );
747+
718748 const isAllChatMessagesLoadedRef = useRef(false);
719749 // isAllChatMessagesLoadedRef needs to be updated every time when a new adapter is set in order to display correct data
720750 // onLoadPreviousChatMessages is updated when a new adapter is set
@@ -741,6 +771,27 @@ export const MessageThreadWrapper = (props: MessageThreadProps): JSX.Element =>
741771 return newMessages;
742772 }, [newMessages]);
743773
774+ useEffect(() => {
775+ if (latestDeletedMessageId === undefined) {
776+ setDeletedMessageAriaLabel(undefined);
777+ } else {
778+ if (!previousMessagesRef.current.find((message) => message.messageId === latestDeletedMessageId)) {
779+ // the message is deleted in previousMessagesRef
780+ // no need to update deletedMessageAriaLabel
781+ setDeletedMessageAriaLabel(undefined);
782+ } else if (!messages.find((message) => message.messageId === latestDeletedMessageId)) {
783+ // the message is deleted in messages array but still exists in previousMessagesRef
784+ // update deletedMessageAriaLabel
785+ setDeletedMessageAriaLabel(strings.messageDeletedAnnouncementAriaLabel);
786+ } else {
787+ // the message exists in both arrays
788+ // no need to update deletedMessageAriaLabel
789+ setDeletedMessageAriaLabel(undefined);
790+ }
791+ }
792+ previousMessagesRef.current = messages;
793+ }, [latestDeletedMessageId, messages, strings.messageDeletedAnnouncementAriaLabel]);
794+
744795 const messagesRef = useRef(messages);
745796 const setMessagesRef = (messagesWithAttachedValue: Message[]): void => {
746797 messagesRef.current = messagesWithAttachedValue;
@@ -961,9 +1012,6 @@ export const MessageThreadWrapper = (props: MessageThreadProps): JSX.Element =>
9611012 []
9621013 );
9631014
964- const localeStrings = useLocale().strings.messageThread;
965- const strings = useMemo(() => ({ ...localeStrings, ...props.strings }), [localeStrings, props.strings]);
966-
9671015 const defaultStatusRenderer = useCallback(
9681016 (
9691017 message: ChatMessage | /* @conditional-compile-remove(data-loss-prevention) */ BlockedMessage,
@@ -1006,7 +1054,7 @@ export const MessageThreadWrapper = (props: MessageThreadProps): JSX.Element =>
10061054 index,
10071055 onUpdateMessage,
10081056 onCancelEditMessage,
1009- onDeleteMessage ,
1057+ onDeleteMessageCallback ,
10101058 onSendMessage,
10111059 props.disableEditing,
10121060 lastDeliveredChatMessage,
@@ -1021,7 +1069,7 @@ export const MessageThreadWrapper = (props: MessageThreadProps): JSX.Element =>
10211069 lastSendingChatMessage,
10221070 messages,
10231071 onCancelEditMessage,
1024- onDeleteMessage ,
1072+ onDeleteMessageCallback ,
10251073 onSendMessage,
10261074 onUpdateMessage,
10271075 props.disableEditing,
@@ -1048,6 +1096,7 @@ export const MessageThreadWrapper = (props: MessageThreadProps): JSX.Element =>
10481096 )}
10491097 <LiveAnnouncer>
10501098 <FluentV9ThemeProvider v8Theme={theme}>
1099+ <Announcer announcementString={deletedMessageAriaLabel} ariaLive={'assertive'} />
10511100 <Chat
10521101 // styles?.chatContainer used in className and style prop as style prop can't handle CSS selectors
10531102 className={mergeClasses(classes.root, mergeStyles(styles?.chatContainer))}
0 commit comments