@azure/communication-react 1.10.1-alpha-202311230013 → 1.10.1-alpha-202311240012

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 (65) hide show
  1. package/dist/communication-react.d.ts +17 -14
  2. package/dist/dist-cjs/communication-react/index.js +1871 -1759
  3. package/dist/dist-cjs/communication-react/index.js.map +1 -1
  4. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js +1 -1
  5. package/dist/dist-esm/acs-ui-common/src/telemetryVersion.js.map +1 -1
  6. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponent.js +2 -2
  7. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponent.js.map +1 -1
  8. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponentAsMessageBubble.js +1 -1
  9. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponentAsMessageBubble.js.map +1 -1
  10. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponentWrapper.d.ts +46 -0
  11. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponentWrapper.js +53 -0
  12. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageComponentWrapper.js.map +1 -0
  13. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageContent.d.ts +1 -1
  14. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageContent.js +16 -10
  15. package/dist/dist-esm/react-components/src/components/ChatMessage/ChatMessageContent.js.map +1 -1
  16. package/dist/dist-esm/react-components/src/components/ChatMessage/DefaultSystemMessage.d.ts +6 -0
  17. package/dist/dist-esm/react-components/src/components/ChatMessage/DefaultSystemMessage.js +38 -0
  18. package/dist/dist-esm/react-components/src/components/ChatMessage/DefaultSystemMessage.js.map +1 -0
  19. package/dist/dist-esm/react-components/src/components/ChatMessage/FluentChatMessageComponentWrapper.d.ts +23 -0
  20. package/dist/dist-esm/react-components/src/components/ChatMessage/FluentChatMessageComponentWrapper.js +183 -0
  21. package/dist/dist-esm/react-components/src/components/ChatMessage/FluentChatMessageComponentWrapper.js.map +1 -0
  22. package/dist/dist-esm/react-components/src/components/MessageThread.d.ts +5 -7
  23. package/dist/dist-esm/react-components/src/components/MessageThread.js +92 -247
  24. package/dist/dist-esm/react-components/src/components/MessageThread.js.map +1 -1
  25. package/dist/dist-esm/react-components/src/components/VideoGallery/DefaultLayout.js +1 -3
  26. package/dist/dist-esm/react-components/src/components/VideoGallery/DefaultLayout.js.map +1 -1
  27. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideoLayout.js +3 -2
  28. package/dist/dist-esm/react-components/src/components/VideoGallery/FloatingLocalVideoLayout.js.map +1 -1
  29. package/dist/dist-esm/react-components/src/components/VideoGallery/OverflowGallery.d.ts +0 -2
  30. package/dist/dist-esm/react-components/src/components/VideoGallery/OverflowGallery.js +5 -4
  31. package/dist/dist-esm/react-components/src/components/VideoGallery/OverflowGallery.js.map +1 -1
  32. package/dist/dist-esm/react-components/src/components/VideoGallery/SpeakerVideoLayout.js +3 -2
  33. package/dist/dist-esm/react-components/src/components/VideoGallery/SpeakerVideoLayout.js.map +1 -1
  34. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.d.ts +4 -7
  35. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.js +12 -4
  36. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/AzureCommunicationCallAdapter.js.map +1 -1
  37. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/CallAdapter.d.ts +14 -5
  38. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/CallAdapter.js.map +1 -1
  39. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/CallingSoundSubscriber.d.ts +3 -3
  40. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/CallingSoundSubscriber.js +10 -13
  41. package/dist/dist-esm/react-composites/src/composites/CallComposite/adapter/CallingSoundSubscriber.js.map +1 -1
  42. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/SidePane/SidePane.js +1 -1
  43. package/dist/dist-esm/react-composites/src/composites/CallComposite/components/SidePane/SidePane.js.map +1 -1
  44. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/LocalVideoTileSelector.d.ts +1 -0
  45. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/activeVideoBackgroundEffectSelector.d.ts +1 -0
  46. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/callStatusSelector.d.ts +1 -0
  47. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/capabilitiesChangedInfoAndRoleSelector.d.ts +1 -0
  48. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/complianceBannerSelector.d.ts +1 -0
  49. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/deviceCountSelector.d.ts +1 -0
  50. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/devicePermissionSelector.d.ts +1 -0
  51. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/dominantRemoteParticipantSelector.d.ts +1 -0
  52. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/lobbySelector.d.ts +1 -0
  53. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/localAndRemotePIPSelector.d.ts +1 -0
  54. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/localPreviewSelector.d.ts +1 -0
  55. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/localVideoStreamSelector.d.ts +1 -0
  56. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/mediaGallerySelector.d.ts +2 -0
  57. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/mutedNotificationSelector.d.ts +1 -0
  58. package/dist/dist-esm/react-composites/src/composites/CallComposite/selectors/networkReconnectTileSelector.d.ts +1 -0
  59. package/dist/dist-esm/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.js +6 -8
  60. package/dist/dist-esm/react-composites/src/composites/CallWithChatComposite/CallWithChatComposite.js.map +1 -1
  61. package/dist/dist-esm/react-composites/src/composites/ChatComposite/ChatComposite.js +3 -13
  62. package/dist/dist-esm/react-composites/src/composites/ChatComposite/ChatComposite.js.map +1 -1
  63. package/dist/dist-esm/react-composites/src/composites/ChatComposite/ChatScreen.js +10 -4
  64. package/dist/dist-esm/react-composites/src/composites/ChatComposite/ChatScreen.js.map +1 -1
  65. package/package.json +1 -1
@@ -18,10 +18,10 @@ var reactFileTypeIcons = require('@fluentui/react-file-type-icons');
18
18
  var uuid = require('uuid');
19
19
  var reactChat = require('@fluentui-contrib/react-chat');
20
20
  var reactComponents = require('@fluentui/react-components');
21
+ var react$1 = require('@griffel/react');
21
22
  var htmlToReact = require('html-to-react');
22
23
  var Linkify = require('react-linkify');
23
24
  var DOMPurify = require('dompurify');
24
- var react$1 = require('@griffel/react');
25
25
  var reactHooks = require('@fluentui/react-hooks');
26
26
  var reactUseDraggableScroll = require('react-use-draggable-scroll');
27
27
  var reactWindowProvider = require('@fluentui/react-window-provider');
@@ -177,7 +177,7 @@ const _isValidIdentifier = (identifier) => {
177
177
  // Copyright (c) Microsoft Corporation.
178
178
  // Licensed under the MIT License.
179
179
  // GENERATED FILE. DO NOT EDIT MANUALLY.
180
- var telemetryVersion = '1.10.1-alpha-202311230013';
180
+ var telemetryVersion = '1.10.1-alpha-202311240012';
181
181
 
182
182
  // Copyright (c) Microsoft Corporation.
183
183
  /**
@@ -9000,1642 +9000,1905 @@ const delay = (delay) => {
9000
9000
 
9001
9001
  // Copyright (c) Microsoft Corporation.
9002
9002
  /**
9003
- * @private
9004
- */
9005
- const systemMessageIconStyle = react.mergeStyles({
9006
- marginInlineEnd: '0.688rem'
9007
- });
9008
-
9009
- // Copyright (c) Microsoft Corporation.
9010
- /**
9011
- * @private
9003
+ * A utility hook for providing the width of a parent element.
9004
+ * Returns updated width if parent/window resizes.
9005
+ * @param containerRef - Ref of a parent element whose width will be returned.
9006
+ * @internal
9012
9007
  */
9013
- const SystemMessage = (props) => {
9014
- const { iconName, content } = props;
9015
- const Icon = React__default["default"].createElement(react.FontIcon, { iconName: iconName, className: react.mergeStyles(systemMessageIconStyle) });
9016
- return (React__default["default"].createElement(react.Stack, { horizontal: true, className: react.mergeStyles(props === null || props === void 0 ? void 0 : props.containerStyle), tabIndex: 0 },
9017
- Icon,
9018
- React__default["default"].createElement(react.Text, { style: { wordBreak: 'break-word' }, role: "status", title: content, variant: 'small' }, content)));
9008
+ const _useContainerWidth = (containerRef) => {
9009
+ const [width, setWidth] = React.useState(undefined);
9010
+ const observer = React.useRef(new ResizeObserver((entries) => {
9011
+ const { width } = entries[0].contentRect;
9012
+ setWidth(width);
9013
+ }));
9014
+ React.useEffect(() => {
9015
+ if (containerRef.current) {
9016
+ observer.current.observe(containerRef.current);
9017
+ }
9018
+ const currentObserver = observer.current;
9019
+ return () => {
9020
+ currentObserver.disconnect();
9021
+ };
9022
+ }, [containerRef, observer]);
9023
+ return width;
9019
9024
  };
9020
-
9021
- // Copyright (c) Microsoft Corporation.
9022
9025
  /**
9023
- * @private
9026
+ * A utility hook for providing the height of a parent element.
9027
+ * Returns updated height if parent/window resizes.
9028
+ * @param containerRef - Ref of a parent element whose height will be returned.
9029
+ * @internal
9024
9030
  */
9025
- const editBoxStyle = react.mergeStyles({
9026
- marginTop: '0.0875rem',
9027
- marginBottom: '0.0875rem'
9028
- });
9031
+ const _useContainerHeight = (containerRef) => {
9032
+ const [height, setHeight] = React.useState(undefined);
9033
+ const observer = React.useRef(new ResizeObserver((entries) => {
9034
+ const { height } = entries[0].contentRect;
9035
+ setHeight(height);
9036
+ }));
9037
+ React.useEffect(() => {
9038
+ if (containerRef.current) {
9039
+ observer.current.observe(containerRef.current);
9040
+ }
9041
+ const currentObserver = observer.current;
9042
+ return () => {
9043
+ currentObserver.disconnect();
9044
+ };
9045
+ }, [containerRef, observer]);
9046
+ return height;
9047
+ };
9048
+ const NARROW_WIDTH_REM = 30;
9049
+ const SHORT_HEIGHT_REM = 23.75;
9029
9050
  /**
9030
- * @private
9051
+ * Utility function to determine if container width is narrow
9052
+ * @param containerWidthRem container width in rem
9053
+ * @returns boolean
9031
9054
  */
9032
- const editingButtonStyle = react.mergeStyles({
9033
- margin: '0',
9034
- width: '2.125rem',
9035
- height: '2.125rem',
9036
- padding: '0.375rem 0 0 0'
9037
- });
9055
+ const isNarrowWidth = (containerWidthRem) => containerWidthRem <= _convertRemToPx(NARROW_WIDTH_REM);
9038
9056
  /**
9039
- * @private
9057
+ * Utility function to determine if container width is short
9058
+ * @param containerWidthRem container height in rem
9059
+ * @returns boolean
9040
9060
  */
9041
- const inputBoxIcon = react.mergeStyles({
9042
- margin: 'auto',
9043
- '&:hover svg': {
9044
- stroke: 'currentColor'
9045
- }
9046
- });
9061
+ const isShortHeight = (containerHeightRem) => containerHeightRem <= _convertRemToPx(SHORT_HEIGHT_REM);
9062
+
9063
+ // Copyright (c) Microsoft Corporation.
9064
+ // Licensed under the MIT License.
9047
9065
  /**
9048
9066
  * @private
9049
- */
9050
- const editBoxStyleSet = {
9051
- root: {
9052
- minWidth: '6.25rem',
9053
- maxWidth: '100%'
9054
- }
9067
+ *logic: Looking at message A, how do we know it's read number?
9068
+ * Assumption: if user read the latest message, user has read all messages before that
9069
+ * ReadReceipt behaviour: read receipt is only sent to the last message
9070
+ *
9071
+ * If participant read a message that is sent later than message A, then the participant has read message A
9072
+ * How do we check if the message is sent later than message A?
9073
+ * We compare if the messageID of the last read message is larger than or equal to the message A's id
9074
+ * Because messageID is the creation timestamp of each message
9075
+ * Timestamps are in epoch time so lecixographical ordering is the same as time ordering.
9076
+ *
9077
+ * if MessageId of B is larger than message Id of A, then B is created after A
9078
+ * if the last read message is created after the message A is sent, then user should have read message A as well */
9079
+ var getParticipantsWhoHaveReadMessage = (message, readReceiptsBySenderId) => {
9080
+ return (Object.entries(readReceiptsBySenderId)
9081
+ // Filter to only read receipts that match the message OR the participant has read a different message after this message has been created
9082
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
9083
+ .filter(([_, readReceipt]) => readReceipt.lastReadMessage >= message.messageId)
9084
+ // make sure the person is not removed from chat
9085
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
9086
+ .filter(([_, readReceipt]) => readReceipt.displayName && readReceipt.displayName !== '')
9087
+ // Map properties to useful array
9088
+ .map(([id, readReceipt]) => ({ id, displayName: readReceipt.displayName })));
9055
9089
  };
9056
9090
 
9057
9091
  // Copyright (c) Microsoft Corporation.
9058
- const MINIMUM_TOUCH_TARGET_HEIGHT_REM$1 = 3;
9059
- const errorTextColor = 'var(--errorText)';
9060
- /**
9061
- * @private
9062
- */
9063
- const iconWrapperStyle = (theme, isSubMenuOpen) => ({
9064
- root: {
9065
- margin: _pxToRem(3),
9066
- // Show hover styles when the Edit/Delete menu is showing as this action button is still considered 'active'
9067
- color: isSubMenuOpen ? theme.palette.black : theme.palette.neutralPrimary,
9068
- strokeWidth: isSubMenuOpen ? _pxToRem(0.5) : _pxToRem(0),
9069
- stroke: theme.palette.black,
9070
- ':hover, :focus': {
9071
- color: theme.palette.black,
9072
- strokeWidth: _pxToRem(0.5)
9073
- }
9074
- }
9075
- });
9076
- /**
9077
- * @private
9078
- */
9079
- const chatMessageDateStyle = react.mergeStyles({
9080
- color: reactComponents.tokens.colorNeutralForeground2,
9081
- fontWeight: react.FontWeights.regular,
9082
- fontSize: '0.75rem'
9083
- });
9092
+ // Licensed under the MIT License.
9093
+ // These color records are required for createV9Theme
9094
+ // For more info, check https://react.fluentui.dev/iframe.html?viewMode=docs&id=concepts-migration-from-v8-components-theme-migration--page#compatible-themes
9084
9095
  /**
9085
9096
  * @private
9086
9097
  */
9087
- const chatMessageAuthorStyle = react.mergeStyles({
9088
- fontWeight: react.FontWeights.semibold,
9089
- fontSize: '0.75rem'
9090
- });
9098
+ const grey = {
9099
+ '0': '#000000',
9100
+ '2': '#050505',
9101
+ '4': '#0a0a0a',
9102
+ '6': '#0f0f0f',
9103
+ '8': '#141414',
9104
+ '10': '#1a1a1a',
9105
+ '12': '#1f1f1f',
9106
+ '14': '#242424',
9107
+ '16': '#292929',
9108
+ '18': '#2e2e2e',
9109
+ '20': '#333333',
9110
+ '22': '#383838',
9111
+ '24': '#3d3d3d',
9112
+ '26': '#424242',
9113
+ '28': '#474747',
9114
+ '30': '#4d4d4d',
9115
+ '32': '#525252',
9116
+ '34': '#575757',
9117
+ '36': '#5c5c5c',
9118
+ '38': '#616161',
9119
+ '40': '#666666',
9120
+ '42': '#6b6b6b',
9121
+ '44': '#707070',
9122
+ '46': '#757575',
9123
+ '48': '#7a7a7a',
9124
+ '50': '#808080',
9125
+ '52': '#858585',
9126
+ '54': '#8a8a8a',
9127
+ '56': '#8f8f8f',
9128
+ '58': '#949494',
9129
+ '60': '#999999',
9130
+ '62': '#9e9e9e',
9131
+ '64': '#a3a3a3',
9132
+ '66': '#a8a8a8',
9133
+ '68': '#adadad',
9134
+ '70': '#b3b3b3',
9135
+ '72': '#b8b8b8',
9136
+ '74': '#bdbdbd',
9137
+ '76': '#c2c2c2',
9138
+ '78': '#c7c7c7',
9139
+ '80': '#cccccc',
9140
+ '82': '#d1d1d1',
9141
+ '84': '#d6d6d6',
9142
+ '86': '#dbdbdb',
9143
+ '88': '#e0e0e0',
9144
+ '90': '#e6e6e6',
9145
+ '92': '#ebebeb',
9146
+ '94': '#f0f0f0',
9147
+ '96': '#f5f5f5',
9148
+ '98': '#fafafa',
9149
+ '100': '#ffffff'
9150
+ };
9091
9151
  /**
9092
9152
  * @private
9093
9153
  */
9094
- const chatMessageEditedTagStyle = (theme) => react.mergeStyles({ fontWeight: react.FontWeights.semibold, color: theme.palette.neutralSecondary });
9154
+ const whiteAlpha = {
9155
+ '5': 'rgba(255, 255, 255, 0.05)',
9156
+ '10': 'rgba(255, 255, 255, 0.1)',
9157
+ '20': 'rgba(255, 255, 255, 0.2)',
9158
+ '30': 'rgba(255, 255, 255, 0.3)',
9159
+ '40': 'rgba(255, 255, 255, 0.4)',
9160
+ '50': 'rgba(255, 255, 255, 0.5)',
9161
+ '60': 'rgba(255, 255, 255, 0.6)',
9162
+ '70': 'rgba(255, 255, 255, 0.7)',
9163
+ '80': 'rgba(255, 255, 255, 0.8)',
9164
+ '90': 'rgba(255, 255, 255, 0.9)'
9165
+ };
9095
9166
  /**
9096
9167
  * @private
9097
9168
  */
9098
- const chatMessageFailedTagStyle = (theme) => react.mergeStyles({
9099
- fontWeight: react.FontWeights.regular,
9100
- color: theme.semanticColors.errorText,
9101
- fontSize: theme.fonts.smallPlus.fontSize,
9102
- lineHeight: '1rem'
9103
- });
9169
+ const blackAlpha = {
9170
+ '5': 'rgba(0, 0, 0, 0.05)',
9171
+ '10': 'rgba(0, 0, 0, 0.1)',
9172
+ '20': 'rgba(0, 0, 0, 0.2)',
9173
+ '30': 'rgba(0, 0, 0, 0.3)',
9174
+ '40': 'rgba(0, 0, 0, 0.4)',
9175
+ '50': 'rgba(0, 0, 0, 0.5)',
9176
+ '60': 'rgba(0, 0, 0, 0.6)',
9177
+ '70': 'rgba(0, 0, 0, 0.7)',
9178
+ '80': 'rgba(0, 0, 0, 0.8)',
9179
+ '90': 'rgba(0, 0, 0, 0.9)'
9180
+ };
9104
9181
  /**
9105
9182
  * @private
9106
9183
  */
9107
- const editChatMessageFailedTagStyle = react.mergeStyles({
9108
- marginBottom: '0.5rem'
9109
- });
9184
+ const grey10Alpha = {
9185
+ '5': 'rgba(26, 26, 26, 0.05)',
9186
+ '10': 'rgba(26, 26, 26, 0.1)',
9187
+ '20': 'rgba(26, 26, 26, 0.2)',
9188
+ '30': 'rgba(26, 26, 26, 0.3)',
9189
+ '40': 'rgba(26, 26, 26, 0.4)',
9190
+ '50': 'rgba(26, 26, 26, 0.5)',
9191
+ '60': 'rgba(26, 26, 26, 0.6)',
9192
+ '70': 'rgba(26, 26, 26, 0.7)',
9193
+ '80': 'rgba(26, 26, 26, 0.8)',
9194
+ '90': 'rgba(26, 26, 26, 0.9)'
9195
+ };
9110
9196
  /**
9111
9197
  * @private
9112
9198
  */
9113
- const chatMessageFailedTagStackItemStyle = react.mergeStyles({
9114
- alignSelf: 'end'
9115
- });
9116
- /**
9117
- * @private
9199
+ const grey12Alpha = {
9200
+ '5': 'rgba(31, 31, 31, 0.05)',
9201
+ '10': 'rgba(31, 31, 31, 0.1)',
9202
+ '20': 'rgba(31, 31, 31, 0.2)',
9203
+ '30': 'rgba(31, 31, 31, 0.3)',
9204
+ '40': 'rgba(31, 31, 31, 0.4)',
9205
+ '50': 'rgba(31, 31, 31, 0.5)',
9206
+ '60': 'rgba(31, 31, 31, 0.6)',
9207
+ '70': 'rgba(31, 31, 31, 0.7)',
9208
+ '80': 'rgba(31, 31, 31, 0.8)',
9209
+ '90': 'rgba(31, 31, 31, 0.9)'
9210
+ };
9211
+
9212
+ // Copyright (c) Microsoft Corporation.
9213
+ // These mappings are required for createV9Theme
9214
+ // For more info, check https://react.fluentui.dev/iframe.html?viewMode=docs&id=concepts-migration-from-v8-components-theme-migration--page#compatible-themes
9215
+ /**
9216
+ * Creates v9 color tokens from a v8 palette.
9118
9217
  */
9119
- const editChatMessageButtonsStackStyle = react.mergeStyles({
9120
- padding: '0 0.5rem',
9121
- marginTop: '-0.25rem'
9122
- });
9218
+ const mapAliasColors = (palette, inverted) => {
9219
+ return {
9220
+ colorNeutralForeground1: palette.neutralPrimary,
9221
+ colorNeutralForeground1Hover: palette.neutralPrimary,
9222
+ colorNeutralForeground1Pressed: palette.neutralPrimary,
9223
+ colorNeutralForeground1Selected: palette.neutralPrimary,
9224
+ colorNeutralForeground2: palette.neutralSecondary,
9225
+ colorNeutralForeground2Hover: palette.neutralPrimary,
9226
+ colorNeutralForeground2Pressed: palette.neutralPrimary,
9227
+ colorNeutralForeground2Selected: palette.neutralPrimary,
9228
+ colorNeutralForeground2BrandHover: palette.themePrimary,
9229
+ colorNeutralForeground2BrandPressed: palette.themeDarkAlt,
9230
+ colorNeutralForeground2BrandSelected: palette.themePrimary,
9231
+ colorNeutralForeground3: palette.neutralTertiary,
9232
+ colorNeutralForeground3Hover: palette.neutralSecondary,
9233
+ colorNeutralForeground3Pressed: palette.neutralSecondary,
9234
+ colorNeutralForeground3Selected: palette.neutralSecondary,
9235
+ colorNeutralForeground3BrandHover: palette.themePrimary,
9236
+ colorNeutralForeground3BrandPressed: palette.themeDarkAlt,
9237
+ colorNeutralForeground3BrandSelected: palette.themePrimary,
9238
+ colorNeutralForeground4: palette.neutralQuaternary,
9239
+ colorNeutralForegroundDisabled: palette.neutralTertiaryAlt,
9240
+ colorNeutralForegroundInvertedDisabled: whiteAlpha[40],
9241
+ colorBrandForegroundLink: palette.themeDarkAlt,
9242
+ colorBrandForegroundLinkHover: palette.themeDark,
9243
+ colorBrandForegroundLinkPressed: palette.themeDarker,
9244
+ colorBrandForegroundLinkSelected: palette.themeDarkAlt,
9245
+ colorNeutralForeground2Link: palette.neutralSecondary,
9246
+ colorNeutralForeground2LinkHover: palette.neutralPrimary,
9247
+ colorNeutralForeground2LinkPressed: palette.neutralPrimary,
9248
+ colorNeutralForeground2LinkSelected: palette.neutralPrimary,
9249
+ colorCompoundBrandForeground1: palette.themePrimary,
9250
+ colorCompoundBrandForeground1Hover: palette.themeDarkAlt,
9251
+ colorCompoundBrandForeground1Pressed: palette.themeDark,
9252
+ colorBrandForeground1: palette.themePrimary,
9253
+ colorBrandForeground2: palette.themeDarkAlt,
9254
+ colorBrandForeground2Hover: palette.themeDarkAlt,
9255
+ colorBrandForeground2Pressed: palette.themeDarkAlt,
9256
+ colorNeutralForeground1Static: palette.neutralPrimary,
9257
+ colorNeutralForegroundInverted: palette.white,
9258
+ colorNeutralForegroundInvertedHover: palette.white,
9259
+ colorNeutralForegroundInvertedPressed: palette.white,
9260
+ colorNeutralForegroundInvertedSelected: palette.white,
9261
+ colorNeutralForegroundOnBrand: palette.white,
9262
+ colorNeutralForegroundStaticInverted: palette.white,
9263
+ colorNeutralForegroundInvertedLink: palette.white,
9264
+ colorNeutralForegroundInvertedLinkHover: palette.white,
9265
+ colorNeutralForegroundInvertedLinkPressed: palette.white,
9266
+ colorNeutralForegroundInvertedLinkSelected: palette.white,
9267
+ colorNeutralForegroundInverted2: palette.white,
9268
+ colorBrandForegroundInverted: palette.themeSecondary,
9269
+ colorBrandForegroundInvertedHover: palette.themeTertiary,
9270
+ colorBrandForegroundInvertedPressed: palette.themeSecondary,
9271
+ colorBrandForegroundOnLight: palette.themePrimary,
9272
+ colorBrandForegroundOnLightHover: palette.themeDarkAlt,
9273
+ colorBrandForegroundOnLightPressed: palette.themeDark,
9274
+ colorBrandForegroundOnLightSelected: palette.themeDark,
9275
+ colorNeutralBackground1: palette.white,
9276
+ colorNeutralBackground1Hover: palette.neutralLighter,
9277
+ colorNeutralBackground1Pressed: palette.neutralQuaternaryAlt,
9278
+ colorNeutralBackground1Selected: palette.neutralLight,
9279
+ colorNeutralBackground2: palette.neutralLighterAlt,
9280
+ colorNeutralBackground2Hover: palette.neutralLighter,
9281
+ colorNeutralBackground2Pressed: palette.neutralQuaternaryAlt,
9282
+ colorNeutralBackground2Selected: palette.neutralLight,
9283
+ colorNeutralBackground3: palette.neutralLighter,
9284
+ colorNeutralBackground3Hover: palette.neutralLight,
9285
+ colorNeutralBackground3Pressed: palette.neutralQuaternary,
9286
+ colorNeutralBackground3Selected: palette.neutralQuaternaryAlt,
9287
+ colorNeutralBackground4: palette.neutralLighter,
9288
+ colorNeutralBackground4Hover: palette.neutralLighterAlt,
9289
+ colorNeutralBackground4Pressed: palette.neutralLighter,
9290
+ colorNeutralBackground4Selected: palette.white,
9291
+ colorNeutralBackground5: palette.neutralLight,
9292
+ colorNeutralBackground5Hover: palette.neutralLighter,
9293
+ colorNeutralBackground5Pressed: palette.neutralLighter,
9294
+ colorNeutralBackground5Selected: palette.neutralLighterAlt,
9295
+ colorNeutralBackground6: palette.neutralLight,
9296
+ colorNeutralBackgroundStatic: grey[20],
9297
+ colorNeutralBackgroundInverted: palette.neutralSecondary,
9298
+ colorNeutralBackgroundAlpha: inverted ? grey10Alpha[50] : whiteAlpha[50],
9299
+ colorNeutralBackgroundAlpha2: inverted ? grey12Alpha[70] : whiteAlpha[80],
9300
+ colorSubtleBackground: 'transparent',
9301
+ colorSubtleBackgroundHover: palette.neutralLighter,
9302
+ colorSubtleBackgroundPressed: palette.neutralQuaternaryAlt,
9303
+ colorSubtleBackgroundSelected: palette.neutralLight,
9304
+ colorSubtleBackgroundLightAlphaHover: inverted ? whiteAlpha[10] : whiteAlpha[80],
9305
+ colorSubtleBackgroundLightAlphaPressed: inverted ? whiteAlpha[5] : whiteAlpha[50],
9306
+ colorSubtleBackgroundLightAlphaSelected: 'transparent',
9307
+ colorSubtleBackgroundInverted: 'transparent',
9308
+ colorSubtleBackgroundInvertedHover: blackAlpha[10],
9309
+ colorSubtleBackgroundInvertedPressed: blackAlpha[30],
9310
+ colorSubtleBackgroundInvertedSelected: blackAlpha[20],
9311
+ colorTransparentBackground: 'transparent',
9312
+ colorTransparentBackgroundHover: 'transparent',
9313
+ colorTransparentBackgroundPressed: 'transparent',
9314
+ colorTransparentBackgroundSelected: 'transparent',
9315
+ colorNeutralBackgroundDisabled: palette.neutralLighter,
9316
+ colorNeutralBackgroundInvertedDisabled: whiteAlpha[10],
9317
+ colorNeutralStencil1: palette.neutralLight,
9318
+ colorNeutralStencil2: palette.neutralLighterAlt,
9319
+ colorNeutralStencil1Alpha: inverted ? whiteAlpha[10] : blackAlpha[10],
9320
+ colorNeutralStencil2Alpha: inverted ? whiteAlpha[5] : blackAlpha[5],
9321
+ colorBackgroundOverlay: blackAlpha[40],
9322
+ colorScrollbarOverlay: blackAlpha[50],
9323
+ colorBrandBackground: palette.themePrimary,
9324
+ colorBrandBackgroundHover: palette.themeDarkAlt,
9325
+ colorBrandBackgroundPressed: palette.themeDarker,
9326
+ colorBrandBackgroundSelected: palette.themeDark,
9327
+ colorCompoundBrandBackground: palette.themePrimary,
9328
+ colorCompoundBrandBackgroundHover: palette.themeDarkAlt,
9329
+ colorCompoundBrandBackgroundPressed: palette.themeDark,
9330
+ colorBrandBackgroundStatic: palette.themePrimary,
9331
+ colorBrandBackground2: palette.themeLighterAlt,
9332
+ colorBrandBackground2Hover: palette.themeLighterAlt,
9333
+ colorBrandBackground2Pressed: palette.themeLighterAlt,
9334
+ colorBrandBackgroundInverted: palette.white,
9335
+ colorBrandBackgroundInvertedHover: palette.themeLighterAlt,
9336
+ colorBrandBackgroundInvertedPressed: palette.themeLight,
9337
+ colorBrandBackgroundInvertedSelected: palette.themeLighter,
9338
+ colorNeutralStrokeAccessible: palette.neutralSecondary,
9339
+ colorNeutralStrokeAccessibleHover: palette.neutralSecondary,
9340
+ colorNeutralStrokeAccessiblePressed: palette.neutralSecondary,
9341
+ colorNeutralStrokeAccessibleSelected: palette.themePrimary,
9342
+ colorNeutralStroke1: palette.neutralQuaternary,
9343
+ colorNeutralStroke1Hover: palette.neutralTertiaryAlt,
9344
+ colorNeutralStroke1Pressed: palette.neutralTertiaryAlt,
9345
+ colorNeutralStroke1Selected: palette.neutralTertiaryAlt,
9346
+ colorNeutralStroke2: palette.neutralQuaternaryAlt,
9347
+ colorNeutralStroke3: palette.neutralLighter,
9348
+ colorNeutralStrokeSubtle: palette.neutralQuaternaryAlt,
9349
+ colorNeutralStrokeOnBrand: palette.white,
9350
+ colorNeutralStrokeOnBrand2: palette.white,
9351
+ colorNeutralStrokeOnBrand2Hover: palette.white,
9352
+ colorNeutralStrokeOnBrand2Pressed: palette.white,
9353
+ colorNeutralStrokeOnBrand2Selected: palette.white,
9354
+ colorBrandStroke1: palette.themePrimary,
9355
+ colorBrandStroke2: palette.themeLight,
9356
+ colorBrandStroke2Hover: palette.themeLight,
9357
+ colorBrandStroke2Pressed: palette.themeLight,
9358
+ colorBrandStroke2Contrast: palette.themeLight,
9359
+ colorCompoundBrandStroke: palette.themePrimary,
9360
+ colorCompoundBrandStrokeHover: palette.themeDarkAlt,
9361
+ colorCompoundBrandStrokePressed: palette.themeDark,
9362
+ colorNeutralStrokeDisabled: palette.neutralQuaternaryAlt,
9363
+ colorNeutralStrokeInvertedDisabled: whiteAlpha[40],
9364
+ colorTransparentStroke: 'transparent',
9365
+ colorTransparentStrokeInteractive: 'transparent',
9366
+ colorTransparentStrokeDisabled: 'transparent',
9367
+ colorNeutralStrokeAlpha: inverted ? whiteAlpha[10] : blackAlpha[5],
9368
+ colorNeutralStrokeAlpha2: whiteAlpha[20],
9369
+ colorStrokeFocus1: palette.white,
9370
+ colorStrokeFocus2: palette.black,
9371
+ colorNeutralShadowAmbient: 'rgba(0,0,0,0.12)',
9372
+ colorNeutralShadowKey: 'rgba(0,0,0,0.14)',
9373
+ colorNeutralShadowAmbientLighter: 'rgba(0,0,0,0.06)',
9374
+ colorNeutralShadowKeyLighter: 'rgba(0,0,0,0.07)',
9375
+ colorNeutralShadowAmbientDarker: 'rgba(0,0,0,0.20)',
9376
+ colorNeutralShadowKeyDarker: 'rgba(0,0,0,0.24)',
9377
+ colorBrandShadowAmbient: 'rgba(0,0,0,0.30)',
9378
+ colorBrandShadowKey: 'rgba(0,0,0,0.25)'
9379
+ };
9380
+ };
9123
9381
  /**
9124
- * @private
9382
+ * Creates v9 shadow tokens from v8 effects.
9125
9383
  */
9126
- const chatMessageMenuStyle = react.mergeStyles({
9127
- minWidth: '8.5rem',
9128
- height: 'max-content',
9129
- cursor: 'pointer',
9130
- overflow: 'hidden'
9131
- });
9384
+ const mapShadowTokens = (effects) => {
9385
+ return {
9386
+ shadow4: effects.elevation4,
9387
+ shadow8: effects.elevation8,
9388
+ shadow16: effects.elevation16,
9389
+ shadow64: effects.elevation64
9390
+ };
9391
+ };
9132
9392
  /**
9133
- * @private
9393
+ * Creates v9 border radius tokens from v8 effects
9134
9394
  */
9135
- const useChatMessageEditContainerStyles = reactComponents.makeStyles({
9136
- root: {
9137
- paddingTop: '1.25rem' //height of the menu button + marginBottom
9138
- },
9139
- body: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, reactComponents.shorthands.padding(0)), { backgroundColor: 'transparent', boxSizing: 'border-box' }), reactComponents.shorthands.border(`${defaultSendBoxInactiveBorderThicknessREM}rem`, 'solid')), reactComponents.shorthands.borderRadius(reactComponents.tokens.borderRadiusMedium)), reactComponents.shorthands.margin(`${defaultSendBoxActiveBorderThicknessREM - defaultSendBoxInactiveBorderThicknessREM}rem`)), {
9140
- // Width should be updated on hover to include the border width change
9141
- width: `calc(100% - ${defaultSendBoxActiveBorderThicknessREM}rem)`, '&:hover, &:active, &:focus, &:focus-within': Object.assign(Object.assign(Object.assign({}, reactComponents.shorthands.margin('0rem')), reactComponents.shorthands.borderWidth(`${defaultSendBoxActiveBorderThicknessREM}rem`)), { width: '100%' }) }),
9142
- bodyError: Object.assign({}, reactComponents.shorthands.borderColor(errorTextColor)),
9143
- bodyDefault: Object.assign(Object.assign({}, reactComponents.shorthands.borderColor(reactComponents.tokens.colorNeutralStrokeAccessible)), { '&:hover, &:active, &:focus, &:focus-within': Object.assign({}, reactComponents.shorthands.borderColor(reactComponents.tokens.colorCompoundBrandStroke)) })
9144
- });
9395
+ const mapBorderRadiusTokens = (effects) => {
9396
+ return {
9397
+ borderRadiusSmall: effects.roundedCorner2,
9398
+ borderRadiusMedium: effects.roundedCorner4,
9399
+ borderRadiusLarge: effects.roundedCorner6
9400
+ };
9401
+ };
9145
9402
  /**
9146
- * Styles that can be applied to ensure flyout items have the minimum touch target size.
9403
+ * Creates a v9 theme from a v8 theme and base v9 theme.
9404
+ * FluentUI webLightTheme is used in case if no baseThemeV9 is provided.
9147
9405
  *
9148
9406
  * @private
9149
9407
  */
9150
- const menuItemIncreasedSizeStyles = {
9151
- root: {
9152
- height: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9153
- lineHeight: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9154
- maxHeight: 'unset'
9155
- },
9156
- linkContent: {
9157
- height: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9158
- lineHeight: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9159
- maxHeight: 'unset'
9160
- },
9161
- icon: {
9162
- maxHeight: 'unset'
9163
- }
9408
+ const createV9Theme = (themeV8, baseThemeV9) => {
9409
+ const baseTheme = baseThemeV9 !== null && baseThemeV9 !== void 0 ? baseThemeV9 : reactComponents.webLightTheme;
9410
+ return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, baseTheme), mapAliasColors(themeV8.palette, themeV8.isInverted)), mapShadowTokens(themeV8.effects)), mapBorderRadiusTokens(themeV8.effects)), { colorBrandBackground2: themeV8.palette.themeLight, colorBrandBackground2Hover: themeV8.palette.themeLight, colorBrandBackground2Pressed: themeV8.palette.themeLight, colorStatusWarningBackground3: '#D83B01', errorText: themeV8.semanticColors.errorText, colorNeutralStroke1Selected: themeV8.palette.neutralQuaternary, colorNeutralForeground2: themeV8.palette.neutralSecondary, colorBrandForegroundLink: themeV8.palette.themePrimary, colorBrandForegroundLinkHover: themeV8.palette.themeDarker,
9411
+ // Fix for an issue with black borders for iOS that are added with 'after' selector
9412
+ colorStrokeFocus2: 'transparent' });
9164
9413
  };
9414
+
9415
+ // Copyright (c) Microsoft Corporation.
9165
9416
  /**
9166
9417
  * @private
9167
9418
  */
9168
- const menuIconStyleSet = {
9169
- root: {
9170
- height: 'calc(100% - 8px)',
9171
- width: '1.25rem'
9172
- }
9173
- };
9419
+ const useFluentV9Wrapper = reactComponents.makeStyles({
9420
+ body: Object.assign(Object.assign(Object.assign(Object.assign({ height: '100%' }, reactComponents.shorthands.margin(0)), reactComponents.shorthands.overflow('hidden')), reactComponents.shorthands.padding(0)), { width: '100%' })
9421
+ });
9174
9422
  /**
9175
9423
  * @private
9176
9424
  */
9177
- const menuSubIconStyleSet = {
9178
- root: {
9179
- height: 'unset',
9180
- lineHeight: '100%',
9181
- width: '1.25rem'
9182
- }
9425
+ const FluentV9ThemeProvider = (props) => {
9426
+ const { v8Theme, children } = props;
9427
+ const v9Theme = createV9Theme(v8Theme);
9428
+ const dir = v8Theme.rtl ? 'rtl' : 'ltr';
9429
+ return (
9430
+ // TextDirectionProvider is needed to fix issue with direction value update in FluentProvider
9431
+ React__default["default"].createElement(react$1.TextDirectionProvider, { dir: dir },
9432
+ React__default["default"].createElement(FluentProviderWithStylesOverrides, { theme: v9Theme, dir: dir }, children)));
9433
+ };
9434
+ const FluentProviderWithStylesOverrides = (props) => {
9435
+ const classes = useFluentV9Wrapper();
9436
+ return React__default["default"].createElement(reactComponents.FluentProvider, Object.assign({}, props, { className: classes.body }));
9183
9437
  };
9184
9438
 
9185
9439
  // Copyright (c) Microsoft Corporation.
9186
- const MAXIMUM_LENGTH_OF_MESSAGE = 8000;
9187
- const onRenderCancelIcon = (color) => {
9188
- const className = react.mergeStyles(inputBoxIcon, { color });
9189
- return React__default["default"].createElement(react.Icon, { iconName: 'EditBoxCancel', className: className });
9190
- };
9191
- const onRenderSubmitIcon = (color) => {
9192
- const className = react.mergeStyles(inputBoxIcon, { color });
9193
- return React__default["default"].createElement(react.Icon, { iconName: 'EditBoxSubmit', className: className });
9440
+ const offScreenStyle = {
9441
+ border: 0,
9442
+ clip: 'rect(0 0 0 0)',
9443
+ height: '1px',
9444
+ margin: '-1px',
9445
+ overflow: 'hidden',
9446
+ whiteSpace: 'nowrap',
9447
+ padding: 0,
9448
+ width: '1px',
9449
+ position: 'absolute'
9194
9450
  };
9195
- /**
9196
- * @private
9197
- */
9198
- const ChatMessageComponentAsEditBox = (props) => {
9199
- const { onCancel, onSubmit, strings, message } = props;
9200
- /* @conditional-compile-remove(mention) */
9201
- const { mentionLookupOptions } = props;
9202
- const [textValue, setTextValue] = React.useState(message.content || '');
9203
- const [attachedFilesMetadata, setAttachedFilesMetadata] = React__default["default"].useState(getMessageAttachedFilesMetadata(message));
9204
- const editTextFieldRef = React__default["default"].useRef(null);
9205
- const theme = useTheme();
9206
- const messageState = getMessageState(textValue, attachedFilesMetadata !== null && attachedFilesMetadata !== void 0 ? attachedFilesMetadata : []);
9207
- const submitEnabled = messageState === 'OK';
9208
- const editContainerStyles = useChatMessageEditContainerStyles();
9209
- const chatMyMessageStyles = useChatMyMessageStyles();
9451
+ /** @private */
9452
+ const MessageBlock = (props) => (React__default["default"].createElement("div", { style: offScreenStyle, role: "log", "aria-live": props.ariaLive }, props.message ? props.message : ''));
9453
+
9454
+ // Copyright (c) Microsoft Corporation.
9455
+ /** @private */
9456
+ const EMPTY_MESSAGE = { message: '', id: '' };
9457
+ /** @private */
9458
+ const Announcer = (props) => {
9459
+ var _a, _b;
9460
+ const newAssertive = (_a = props.assertive) !== null && _a !== void 0 ? _a : EMPTY_MESSAGE;
9461
+ const oldAssertive = React__default["default"].useRef(EMPTY_MESSAGE);
9462
+ const [activeAssertive1, setActiveAssertive1] = React__default["default"].useState(EMPTY_MESSAGE);
9463
+ const [activeAssertive2, setActiveAssertive2] = React__default["default"].useState(EMPTY_MESSAGE);
9464
+ const alternateAssertive = React__default["default"].useRef(false);
9210
9465
  React.useEffect(() => {
9211
- var _a;
9212
- (_a = editTextFieldRef.current) === null || _a === void 0 ? void 0 : _a.focus();
9213
- }, []);
9214
- const setText = (event, newValue) => {
9215
- setTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
9216
- };
9217
- const textTooLongMessage = messageState === 'too long'
9218
- ? _formatString(strings.editBoxTextLimit, { limitNumber: `${MAXIMUM_LENGTH_OF_MESSAGE}` })
9219
- : undefined;
9220
- const onRenderThemedCancelIcon = React.useCallback((isHover) => onRenderCancelIcon(isHover ? theme.palette.accent : theme.palette.neutralSecondary), [theme.palette.neutralSecondary, theme.palette.accent]);
9221
- const onRenderThemedSubmitIcon = React.useCallback((isHover) => onRenderSubmitIcon(isHover ? theme.palette.accent : theme.palette.neutralSecondary), [theme.palette.neutralSecondary, theme.palette.accent]);
9222
- const editBoxStyles = React.useMemo(() => {
9223
- return react.concatStyleSets(editBoxStyleSet, { textField: { borderColor: theme.palette.themePrimary } });
9224
- }, [theme.palette.themePrimary]);
9225
- const onRenderFileUploads = React.useCallback(() => {
9226
- return (!!attachedFilesMetadata &&
9227
- attachedFilesMetadata.length > 0 && (React__default["default"].createElement("div", { style: { margin: '0.25rem' } },
9228
- React__default["default"].createElement(_FileUploadCards, { activeFileUploads: attachedFilesMetadata === null || attachedFilesMetadata === void 0 ? void 0 : attachedFilesMetadata.map((file) => ({
9229
- id: file.name,
9230
- filename: file.name,
9231
- progress: 1
9232
- })), onCancelFileUpload: (fileId) => {
9233
- setAttachedFilesMetadata(attachedFilesMetadata === null || attachedFilesMetadata === void 0 ? void 0 : attachedFilesMetadata.filter((file) => file.name !== fileId));
9234
- } }))));
9235
- }, [attachedFilesMetadata]);
9236
- const getContent = () => {
9237
- return (React__default["default"].createElement(React__default["default"].Fragment, null,
9238
- React__default["default"].createElement(InputBoxComponent, { "data-ui-id": "edit-box", textFieldRef: editTextFieldRef, inputClassName: editBoxStyle, placeholderText: strings.editBoxPlaceholderText, textValue: textValue, onChange: setText, onKeyDown: (ev) => {
9239
- if (ev.key === 'ArrowUp' || ev.key === 'ArrowDown') {
9240
- ev.stopPropagation();
9241
- }
9242
- }, onEnterKeyDown: () => {
9243
- submitEnabled &&
9244
- onSubmit(textValue, message.metadata, {
9245
- attachedFilesMetadata
9246
- });
9247
- }, supportNewline: false, maxLength: MAXIMUM_LENGTH_OF_MESSAGE, errorMessage: textTooLongMessage, styles: editBoxStyles,
9248
- /* @conditional-compile-remove(mention) */
9249
- mentionLookupOptions: mentionLookupOptions }),
9250
- React__default["default"].createElement(react.Stack, { horizontal: true, horizontalAlign: "end", className: editChatMessageButtonsStackStyle, tokens: { childrenGap: '0.25rem' } },
9251
- message.failureReason && (React__default["default"].createElement(react.Stack.Item, { grow: true, align: "stretch", className: chatMessageFailedTagStackItemStyle },
9252
- React__default["default"].createElement("div", { className: react.mergeStyles(chatMessageFailedTagStyle(theme), editChatMessageFailedTagStyle) }, message.failureReason))),
9253
- React__default["default"].createElement(react.Stack.Item, { align: "end" },
9254
- React__default["default"].createElement(InputBoxButton, { className: editingButtonStyle, ariaLabel: strings.editBoxCancelButton, tooltipContent: strings.editBoxCancelButton, onRenderIcon: onRenderThemedCancelIcon, onClick: () => {
9255
- onCancel && onCancel(message.messageId);
9256
- }, id: 'dismissIconWrapper' })),
9257
- React__default["default"].createElement(react.Stack.Item, { align: "end" },
9258
- React__default["default"].createElement(InputBoxButton, { className: editingButtonStyle, ariaLabel: strings.editBoxSubmitButton, tooltipContent: strings.editBoxSubmitButton, onRenderIcon: onRenderThemedSubmitIcon, onClick: (e) => {
9259
- submitEnabled &&
9260
- onSubmit(textValue, message.metadata, {
9261
- attachedFilesMetadata
9262
- });
9263
- e.stopPropagation();
9264
- }, id: 'submitIconWrapper' }))),
9265
- onRenderFileUploads()));
9266
- };
9267
- const bodyClassName = reactComponents.mergeClasses(editContainerStyles.body, message.failureReason !== undefined ? editContainerStyles.bodyError : editContainerStyles.bodyDefault);
9268
- return (React__default["default"].createElement(reactChat.ChatMyMessage, { root: {
9269
- className: reactComponents.mergeClasses(chatMyMessageStyles.root, editContainerStyles.root)
9270
- }, body: {
9271
- className: bodyClassName
9272
- } }, getContent()));
9466
+ if (oldAssertive.current.message !== (newAssertive === null || newAssertive === void 0 ? void 0 : newAssertive.message) || oldAssertive.current.id !== (newAssertive === null || newAssertive === void 0 ? void 0 : newAssertive.id)) {
9467
+ setActiveAssertive1(alternateAssertive.current ? EMPTY_MESSAGE : newAssertive);
9468
+ setActiveAssertive2(alternateAssertive.current ? newAssertive : EMPTY_MESSAGE);
9469
+ oldAssertive.current = newAssertive;
9470
+ alternateAssertive.current = !alternateAssertive.current;
9471
+ }
9472
+ }, [newAssertive]);
9473
+ const newPolite = (_b = props.polite) !== null && _b !== void 0 ? _b : EMPTY_MESSAGE;
9474
+ const oldPolite = React__default["default"].useRef(EMPTY_MESSAGE);
9475
+ const [activePolite1, setActivePolite1] = React__default["default"].useState(EMPTY_MESSAGE);
9476
+ const [activePolite2, setActivePolite2] = React__default["default"].useState(EMPTY_MESSAGE);
9477
+ const alternatePolite = React__default["default"].useRef(false);
9478
+ React.useEffect(() => {
9479
+ if (oldPolite.current.message !== (newPolite === null || newPolite === void 0 ? void 0 : newPolite.message) || oldPolite.current.id !== (newPolite === null || newPolite === void 0 ? void 0 : newPolite.id)) {
9480
+ setActivePolite1(alternatePolite.current ? EMPTY_MESSAGE : newPolite);
9481
+ setActivePolite2(alternatePolite.current ? newPolite : EMPTY_MESSAGE);
9482
+ oldPolite.current = newPolite;
9483
+ alternatePolite.current = !alternatePolite.current;
9484
+ }
9485
+ }, [newPolite]);
9486
+ return (React__default["default"].createElement("div", null,
9487
+ React__default["default"].createElement(MessageBlock, { ariaLive: "assertive", message: activeAssertive1.message }),
9488
+ React__default["default"].createElement(MessageBlock, { ariaLive: "assertive", message: activeAssertive2.message }),
9489
+ React__default["default"].createElement(MessageBlock, { ariaLive: "polite", message: activePolite1.message }),
9490
+ React__default["default"].createElement(MessageBlock, { ariaLive: "polite", message: activePolite2.message })));
9273
9491
  };
9274
- const isMessageTooLong = (messageText) => messageText.length > MAXIMUM_LENGTH_OF_MESSAGE;
9275
- const isMessageEmpty = (messageText, attachedFilesMetadata) => messageText.trim().length === 0 && attachedFilesMetadata.length === 0;
9276
- const getMessageState = (messageText, attachedFilesMetadata) => isMessageEmpty(messageText, attachedFilesMetadata) ? 'too short' : isMessageTooLong(messageText) ? 'too long' : 'OK';
9277
- // @TODO: Remove when file-sharing feature becomes stable.
9278
- const getMessageAttachedFilesMetadata = (message) => {
9279
- /* @conditional-compile-remove(file-sharing) */
9280
- return message.attachedFilesMetadata;
9492
+
9493
+ // Copyright (c) Microsoft Corporation.
9494
+ /** @private */
9495
+ const LiveAnnouncer = (props) => {
9496
+ const [politeMessage, setPoliteMessage] = React__default["default"].useState(EMPTY_MESSAGE);
9497
+ const [assertiveMessage, setAssertiveMessage] = React__default["default"].useState(EMPTY_MESSAGE);
9498
+ const announcePolite = React.useCallback((message, id) => {
9499
+ setPoliteMessage({ message, id });
9500
+ }, []);
9501
+ const announceAssertive = React.useCallback((message, id) => {
9502
+ setAssertiveMessage({ message, id });
9503
+ }, []);
9504
+ const updateFunctions = React.useMemo(() => ({
9505
+ announcePolite,
9506
+ announceAssertive
9507
+ }), [announceAssertive, announcePolite]);
9508
+ return (React__default["default"].createElement(AnnouncerContext.Provider, { value: updateFunctions },
9509
+ props.children,
9510
+ React__default["default"].createElement(Announcer, { assertive: assertiveMessage, polite: politeMessage })));
9281
9511
  };
9282
9512
 
9283
9513
  // Copyright (c) Microsoft Corporation.
9284
9514
  // Licensed under the MIT License.
9285
9515
  /**
9516
+ * Function to create a React.CSSProperties object from a v8 style object.
9517
+ * This function is still not ideal
9518
+ * as v8Style can use pseudo-class selectors that style objects can't process correctly.
9519
+ *
9286
9520
  * @private
9287
9521
  */
9288
- const formatTimeForChatMessage = (messageDate) => {
9289
- return messageDate.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
9290
- };
9522
+ function createStyleFromV8Style(v8Style) {
9523
+ const result = {};
9524
+ if (v8Style === undefined || v8Style === null || typeof v8Style === 'boolean' || typeof v8Style === 'string') {
9525
+ return undefined;
9526
+ }
9527
+ else if (typeof v8Style === 'object') {
9528
+ // v8Style is a style object
9529
+ for (const record in v8Style) {
9530
+ if (typeof v8Style[record] === 'string') {
9531
+ // v8Style[record] is just a simple style
9532
+ const msSuffix = 'MS';
9533
+ if (record.startsWith(msSuffix)) {
9534
+ // React.CSSProperties uses camelCase for MS properties but v8Style uses PascalCase
9535
+ const newRecord = record.substring(0, msSuffix.length).toLowerCase + record.substring(msSuffix.length);
9536
+ result[newRecord] = v8Style[record];
9537
+ }
9538
+ else {
9539
+ result[record] = v8Style[record];
9540
+ }
9541
+ }
9542
+ else {
9543
+ result[record] = createStyleFromV8Style(v8Style[record]);
9544
+ }
9545
+ }
9546
+ }
9547
+ return result;
9548
+ }
9549
+
9550
+ // Copyright (c) Microsoft Corporation.
9291
9551
  /**
9292
9552
  * @private
9293
9553
  */
9294
- const formatDateForChatMessage = (messageDate) => {
9295
- return messageDate.toLocaleDateString();
9296
- };
9554
+ const editBoxStyle = react.mergeStyles({
9555
+ marginTop: '0.0875rem',
9556
+ marginBottom: '0.0875rem'
9557
+ });
9297
9558
  /**
9298
- * Given a message date object in ISO8601 and a current date object, generates a user friendly timestamp text
9299
- * using the system locale.
9300
- * <time in locale format>.
9301
- * Yesterday <time in locale format>.
9302
- * <dateStrings day of week> <time in locale format>.
9303
- * <date in locale format> <time in locale format>.
9304
- *
9305
- * If message is after yesterday, then only show the time.
9306
- * If message is before yesterday and after day before yesterday, then show 'Yesterday' plus the time.
9307
- * If message is before day before yesterday and within the current week, then show 'Monday/Tuesday/etc' plus the time.
9308
- * - We consider start of the week as Sunday. If current day is Sunday, then any time before that is in previous week.
9309
- * If message is in previous or older weeks, then show date string plus the time.
9310
- *
9311
- * @param messageDate - date of message
9312
- * @param currentDate - date used as offset to create the user friendly timestamp (e.g. to create 'Yesterday' instead of an absolute date)
9313
- *
9314
9559
  * @private
9315
9560
  */
9316
- const formatTimestampForChatMessage = (messageDate, todayDate, dateStrings) => {
9317
- // If message was in the same day timestamp string is just the time like '1:30 p.m.'.
9318
- const startOfDay = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate());
9319
- if (messageDate > startOfDay) {
9320
- return formatTimeForChatMessage(messageDate);
9561
+ const editingButtonStyle = react.mergeStyles({
9562
+ margin: '0',
9563
+ width: '2.125rem',
9564
+ height: '2.125rem',
9565
+ padding: '0.375rem 0 0 0'
9566
+ });
9567
+ /**
9568
+ * @private
9569
+ */
9570
+ const inputBoxIcon = react.mergeStyles({
9571
+ margin: 'auto',
9572
+ '&:hover svg': {
9573
+ stroke: 'currentColor'
9321
9574
  }
9322
- // If message was yesterday then timestamp string is like this 'Yesterday 1:30 p.m.'.
9323
- const yesterdayDate = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate() - 1);
9324
- if (messageDate > yesterdayDate) {
9325
- return dateStrings.yesterday + ' ' + formatTimeForChatMessage(messageDate);
9326
- }
9327
- // If message was before Sunday and today is Sunday (start of week) then timestamp string is like
9328
- // '2021-01-10 1:30 p.m.'.
9329
- const weekDay = todayDate.getDay();
9330
- if (weekDay === 0) {
9331
- return formatDateForChatMessage(messageDate) + ' ' + formatTimeForChatMessage(messageDate);
9332
- }
9333
- // If message was before first day of the week then timestamp string is like Monday 1:30 p.m.
9334
- const firstDayOfTheWeekDate = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate() - weekDay);
9335
- if (messageDate > firstDayOfTheWeekDate) {
9336
- return dayToDayName(messageDate.getDay(), dateStrings) + ' ' + formatTimeForChatMessage(messageDate);
9337
- }
9338
- // If message date is in previous or older weeks then timestamp string is like 2021-01-10 1:30 p.m.
9339
- return formatDateForChatMessage(messageDate) + ' ' + formatTimeForChatMessage(messageDate);
9340
- };
9341
- const dayToDayName = (day, dateStrings) => {
9342
- switch (day) {
9343
- case 0:
9344
- return dateStrings.sunday;
9345
- case 1:
9346
- return dateStrings.monday;
9347
- case 2:
9348
- return dateStrings.tuesday;
9349
- case 3:
9350
- return dateStrings.wednesday;
9351
- case 4:
9352
- return dateStrings.thursday;
9353
- case 5:
9354
- return dateStrings.friday;
9355
- case 6:
9356
- return dateStrings.saturday;
9357
- default:
9358
- throw new Error(`Invalid day [${day}] passed`);
9575
+ });
9576
+ /**
9577
+ * @private
9578
+ */
9579
+ const editBoxStyleSet = {
9580
+ root: {
9581
+ minWidth: '6.25rem',
9582
+ maxWidth: '100%'
9359
9583
  }
9360
9584
  };
9361
9585
 
9362
9586
  // Copyright (c) Microsoft Corporation.
9587
+ const MINIMUM_TOUCH_TARGET_HEIGHT_REM$1 = 3;
9588
+ const errorTextColor = 'var(--errorText)';
9363
9589
  /**
9364
- * Chat message actions flyout that contains actions such as Edit Message, or Remove Message.
9365
- *
9366
9590
  * @private
9367
9591
  */
9368
- const ChatMessageActionFlyout = (props) => {
9369
- var _a, _b;
9370
- const theme = react.useTheme();
9371
- const messageReadByCount = (_a = props.messageReadBy) === null || _a === void 0 ? void 0 : _a.length;
9372
- const sortedMessageReadyByList = [...((_b = props.messageReadBy) !== null && _b !== void 0 ? _b : [])].sort((a, b) => a.displayName.localeCompare(b.displayName));
9373
- const messageReadByList = sortedMessageReadyByList === null || sortedMessageReadyByList === void 0 ? void 0 : sortedMessageReadyByList.map((person) => {
9374
- const personaOptions = {
9375
- hidePersonaDetails: true,
9376
- size: react.PersonaSize.size24,
9377
- text: person.displayName,
9378
- showOverflowTooltip: false,
9379
- styles: {
9380
- root: {
9381
- margin: '0.25rem'
9382
- }
9383
- }
9384
- };
9385
- const { onRenderAvatar } = props;
9386
- return {
9387
- 'data-ui-id': 'chat-composite-message-contextual-menu-read-name-list-item',
9388
- key: person.displayName,
9389
- text: person.displayName,
9390
- itemProps: { styles: props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined },
9391
- onRenderIcon: () => { var _a; return onRenderAvatar ? onRenderAvatar((_a = person.id) !== null && _a !== void 0 ? _a : '', personaOptions) : React__default["default"].createElement(react.Persona, Object.assign({}, personaOptions)); },
9392
- iconProps: {
9393
- styles: menuIconStyleSet
9394
- }
9395
- };
9396
- });
9397
- const menuItems = React.useMemo(() => {
9398
- const items = [
9399
- {
9400
- key: 'Edit',
9401
- 'data-ui-id': 'chat-composite-message-contextual-menu-edit-action',
9402
- text: props.strings.editMessage,
9403
- itemProps: {
9404
- styles: props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined
9405
- },
9406
- iconProps: { iconName: 'MessageEdit', styles: menuIconStyleSet },
9407
- onClick: props.onEditClick
9408
- },
9409
- {
9410
- key: 'Remove',
9411
- text: props.strings.removeMessage,
9412
- itemProps: { styles: props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined },
9413
- iconProps: {
9414
- iconName: 'MessageRemove',
9415
- styles: menuIconStyleSet
9416
- },
9417
- onClick: props.onRemoveClick
9418
- }
9419
- ];
9420
- // only show read by x of x if more than 3 participants in total including myself
9421
- // TODO: change strings.messageReadCount to be required if we can fallback to our own en-us strings for anything that Contoso doesn't provide
9422
- if (props.remoteParticipantsCount &&
9423
- messageReadByCount !== undefined &&
9424
- props.remoteParticipantsCount >= 2 &&
9425
- props.showMessageStatus &&
9426
- props.strings.messageReadCount &&
9427
- props.messageStatus !== 'failed') {
9428
- items.push({
9429
- key: 'Read Count',
9430
- 'data-ui-id': 'chat-composite-message-contextual-menu-read-info',
9431
- text: _formatString(props.strings.messageReadCount, {
9432
- messageReadByCount: `${messageReadByCount}`,
9433
- remoteParticipantsCount: `${props.remoteParticipantsCount}`
9434
- }),
9435
- itemProps: {
9436
- styles: react.concatStyleSets({
9437
- linkContent: {
9438
- color: messageReadByCount > 0 ? theme.palette.neutralPrimary : theme.palette.neutralTertiary
9439
- },
9440
- root: {
9441
- borderTop: `1px solid ${theme.palette.neutralLighter}`
9442
- }
9443
- }, props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined)
9444
- },
9445
- calloutProps: preventUnwantedDismissProps,
9446
- subMenuProps: {
9447
- items: messageReadByList !== null && messageReadByList !== void 0 ? messageReadByList : [],
9448
- calloutProps: preventUnwantedDismissProps,
9449
- styles: react.concatStyleSets({
9450
- root: {
9451
- maxWidth: _pxToRem(320),
9452
- span: {
9453
- overflow: 'hidden',
9454
- textOverflow: 'ellipsis'
9455
- }
9456
- }
9457
- })
9458
- },
9459
- iconProps: {
9460
- iconName: 'MessageSeen',
9461
- styles: {
9462
- root: {
9463
- color: messageReadByCount > 0 ? theme.palette.themeDarkAlt : theme.palette.neutralTertiary
9464
- }
9465
- }
9466
- },
9467
- submenuIconProps: {
9468
- iconName: 'HorizontalGalleryRightButton',
9469
- styles: menuSubIconStyleSet
9470
- },
9471
- disabled: messageReadByCount <= 0
9472
- });
9473
- }
9474
- else if (props.messageStatus === 'failed' && props.strings.resendMessage) {
9475
- items.push({
9476
- key: 'Resend',
9477
- text: props.strings.resendMessage,
9478
- itemProps: {
9479
- styles: react.concatStyleSets({
9480
- linkContent: {
9481
- color: theme.palette.neutralPrimary
9482
- },
9483
- root: {
9484
- borderTop: `1px solid ${theme.palette.neutralLighter}`
9485
- }
9486
- }, props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined)
9487
- },
9488
- calloutProps: preventUnwantedDismissProps,
9489
- iconProps: {
9490
- iconName: 'MessageResend',
9491
- styles: {
9492
- root: {
9493
- color: theme.palette.themeDarkAlt
9494
- }
9495
- }
9496
- },
9497
- onClick: props.onResendClick
9498
- });
9592
+ const iconWrapperStyle = (theme, isSubMenuOpen) => ({
9593
+ root: {
9594
+ margin: _pxToRem(3),
9595
+ // Show hover styles when the Edit/Delete menu is showing as this action button is still considered 'active'
9596
+ color: isSubMenuOpen ? theme.palette.black : theme.palette.neutralPrimary,
9597
+ strokeWidth: isSubMenuOpen ? _pxToRem(0.5) : _pxToRem(0),
9598
+ stroke: theme.palette.black,
9599
+ ':hover, :focus': {
9600
+ color: theme.palette.black,
9601
+ strokeWidth: _pxToRem(0.5)
9499
9602
  }
9500
- return items;
9501
- }, [
9502
- props.strings.editMessage,
9503
- props.strings.removeMessage,
9504
- props.strings.messageReadCount,
9505
- props.strings.resendMessage,
9506
- props.messageStatus,
9507
- props.increaseFlyoutItemSize,
9508
- props.onEditClick,
9509
- props.onRemoveClick,
9510
- props.onResendClick,
9511
- props.remoteParticipantsCount,
9512
- props.showMessageStatus,
9513
- messageReadByCount,
9514
- theme.palette.neutralPrimary,
9515
- theme.palette.neutralTertiary,
9516
- theme.palette.neutralLighter,
9517
- theme.palette.themeDarkAlt,
9518
- messageReadByList
9519
- ]);
9520
- // gap space uses pixels
9521
- return (React__default["default"].createElement(react.ContextualMenu, { alignTargetEdge: true, gapSpace: 2 /*px*/, isBeakVisible: false, items: menuItems, hidden: props.hidden, target: props.target, onDismiss: props.onDismiss, directionalHint: react.DirectionalHint.topRightEdge, className: chatMessageMenuStyle, calloutProps: calloutMenuProps }));
9522
- };
9603
+ }
9604
+ });
9523
9605
  /**
9524
- * Similar to {@link preventDismissOnEvent}, but not prevent dismissing from scrolling, since it is causing bugs in chat thread.
9606
+ * @private
9525
9607
  */
9526
- const preventUnwantedDismissProps = {
9527
- preventDismissOnEvent: (ev) => {
9528
- return ev.type === 'resize';
9529
- }
9530
- };
9531
- const calloutMenuProps = Object.assign(Object.assign({}, preventUnwantedDismissProps), { styles: { root: { marginRight: '3px' } } });
9532
-
9533
- // Copyright (c) Microsoft Corporation.
9608
+ const chatMessageDateStyle = react.mergeStyles({
9609
+ color: reactComponents.tokens.colorNeutralForeground2,
9610
+ fontWeight: react.FontWeights.regular,
9611
+ fontSize: '0.75rem'
9612
+ });
9534
9613
  /**
9535
- * Provides the default implementation for rendering an Mention in a message thread
9536
- * @param mention - The mention to render
9537
- *
9538
9614
  * @private
9539
9615
  */
9540
- const defaultOnMentionRender = (mention) => {
9541
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9542
- const MsftMention = 'msft-mention';
9543
- return React__default["default"].createElement(MsftMention, { id: mention.id }, mention.displayText);
9544
- };
9545
-
9546
- // Copyright (c) Microsoft Corporation.
9547
- /** @private */
9548
- const ChatMessageContent = (props) => {
9549
- switch (props.message.contentType) {
9550
- case 'text':
9551
- return MessageContentAsText(props);
9552
- case 'html':
9553
- return MessageContentAsRichTextHTML(props);
9554
- case 'richtext/html':
9555
- return MessageContentAsRichTextHTML(props);
9556
- default:
9557
- console.warn('unknown message content type');
9558
- return React__default["default"].createElement(React__default["default"].Fragment, null);
9559
- }
9560
- };
9561
- const MessageContentWithLiveAria = (props) => {
9562
- return (React__default["default"].createElement("div", { "data-ui-status": props.message.status, role: "text", "aria-label": props.ariaLabel },
9563
- React__default["default"].createElement(LiveMessage, { message: props.liveMessage, ariaLive: "polite" }),
9564
- props.content));
9565
- };
9566
- const MessageContentAsRichTextHTML = (props) => {
9567
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9568
- React.useEffect(() => {
9569
- var _a;
9570
- const attachments = (_a = props.message.attachedFilesMetadata) === null || _a === void 0 ? void 0 : _a.filter((fileMetadata) => {
9571
- return fileMetadata.attachmentType === 'inlineImage';
9572
- });
9573
- if (props.attachmentsMap && attachments) {
9574
- attachments.forEach((fileMetadata) => {
9575
- if (props.onFetchAttachment && props.attachmentsMap && props.attachmentsMap[fileMetadata.id] === undefined) {
9576
- props.onFetchAttachment([fileMetadata], props.message.messageId);
9577
- return;
9578
- }
9579
- });
9580
- }
9581
- }, [props]);
9582
- return (React__default["default"].createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: generateLiveMessage(props), ariaLabel: messageContentAriaText(props), content: processHtmlToReact(props) }));
9583
- };
9584
- const MessageContentAsText = (props) => {
9585
- return (React__default["default"].createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: generateLiveMessage(props), ariaLabel: messageContentAriaText(props), content: React__default["default"].createElement(Linkify__default["default"], { componentDecorator: (decoratedHref, decoratedText, key) => {
9586
- return (React__default["default"].createElement(react.Link, { target: "_blank", href: decoratedHref, key: key }, decoratedText));
9587
- } }, props.message.content) }));
9588
- };
9589
- /* @conditional-compile-remove(data-loss-prevention) */
9616
+ const chatMessageAuthorStyle = react.mergeStyles({
9617
+ fontWeight: react.FontWeights.semibold,
9618
+ fontSize: '0.75rem'
9619
+ });
9590
9620
  /**
9591
9621
  * @private
9592
9622
  */
9593
- const BlockedMessageContent = (props) => {
9594
- var _a;
9595
- const Icon = React__default["default"].createElement(react.FontIcon, { iconName: 'DataLossPreventionProhibited' });
9596
- const blockedMessage = props.message.warningText === undefined ? props.strings.blockedWarningText : props.message.warningText;
9597
- const blockedMessageLink = props.message.link;
9598
- const blockedMessageLinkText = blockedMessageLink
9599
- ? (_a = props.message.linkText) !== null && _a !== void 0 ? _a : props.strings.blockedWarningLinkText
9600
- : '';
9601
- const liveAuthor = props.message.mine || props.message.senderDisplayName === undefined ? '' : props.message.senderDisplayName;
9602
- const liveBlockedWarningText = `${liveAuthor} ${blockedMessage} ${blockedMessageLinkText}`;
9603
- return (React__default["default"].createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: liveBlockedWarningText, ariaLabel: liveBlockedWarningText, content: React__default["default"].createElement(react.Stack, { horizontal: true, wrap: true },
9604
- Icon,
9605
- blockedMessage && React__default["default"].createElement("p", null, blockedMessage),
9606
- blockedMessageLink && (React__default["default"].createElement(react.Link, { target: '_blank', href: blockedMessageLink }, blockedMessageLinkText))) }));
9607
- };
9608
- // https://stackoverflow.com/questions/28899298/extract-the-text-out-of-html-string-using-javascript
9609
- const extractContent = (s) => {
9610
- const span = document.createElement('span');
9611
- span.innerHTML = s;
9612
- return span.textContent || span.innerText;
9613
- };
9614
- const generateLiveMessage = (props) => {
9615
- const liveAuthor = _formatString(props.strings.liveAuthorIntro, { author: `${props.message.senderDisplayName}` });
9616
- return `${props.message.editedOn ? props.strings.editedTag : ''} ${props.message.mine ? '' : liveAuthor} ${extractContent(props.message.content || '')} `;
9617
- };
9618
- const messageContentAriaText = (props) => {
9619
- // Strip all html tags from the content for aria.
9620
- return props.message.content
9621
- ? props.message.mine
9622
- ? _formatString(props.strings.messageContentMineAriaText, {
9623
- message: DOMPurify__default["default"].sanitize(props.message.content, { ALLOWED_TAGS: [] })
9624
- })
9625
- : _formatString(props.strings.messageContentAriaText, {
9626
- author: `${props.message.senderDisplayName}`,
9627
- message: DOMPurify__default["default"].sanitize(props.message.content, { ALLOWED_TAGS: [] })
9628
- })
9629
- : undefined;
9630
- };
9631
- const processNodeDefinitions = htmlToReact.ProcessNodeDefinitions();
9632
- const htmlToReactParser = htmlToReact.Parser();
9633
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9634
- const processInlineImage = (props) => ({
9635
- // Custom <img> processing
9636
- shouldProcessNode: (node) => {
9637
- var _a;
9638
- function isImageNode(file) {
9639
- return file.attachmentType === 'inlineImage' && file.id === node.attribs.id;
9640
- }
9641
- // Process img node with id in attachments list
9642
- return (node.name &&
9643
- node.name === 'img' &&
9644
- node.attribs &&
9645
- node.attribs.id &&
9646
- ((_a = props.message.attachedFilesMetadata) === null || _a === void 0 ? void 0 : _a.find(isImageNode)));
9623
+ const chatMessageEditedTagStyle = (theme) => react.mergeStyles({ fontWeight: react.FontWeights.semibold, color: theme.palette.neutralSecondary });
9624
+ /**
9625
+ * @private
9626
+ */
9627
+ const chatMessageFailedTagStyle = (theme) => react.mergeStyles({
9628
+ fontWeight: react.FontWeights.regular,
9629
+ color: theme.semanticColors.errorText,
9630
+ fontSize: theme.fonts.smallPlus.fontSize,
9631
+ lineHeight: '1rem'
9632
+ });
9633
+ /**
9634
+ * @private
9635
+ */
9636
+ const editChatMessageFailedTagStyle = react.mergeStyles({
9637
+ marginBottom: '0.5rem'
9638
+ });
9639
+ /**
9640
+ * @private
9641
+ */
9642
+ const chatMessageFailedTagStackItemStyle = react.mergeStyles({
9643
+ alignSelf: 'end'
9644
+ });
9645
+ /**
9646
+ * @private
9647
+ */
9648
+ const editChatMessageButtonsStackStyle = react.mergeStyles({
9649
+ padding: '0 0.5rem',
9650
+ marginTop: '-0.25rem'
9651
+ });
9652
+ /**
9653
+ * @private
9654
+ */
9655
+ const chatMessageMenuStyle = react.mergeStyles({
9656
+ minWidth: '8.5rem',
9657
+ height: 'max-content',
9658
+ cursor: 'pointer',
9659
+ overflow: 'hidden'
9660
+ });
9661
+ /**
9662
+ * @private
9663
+ */
9664
+ const useChatMessageEditContainerStyles = reactComponents.makeStyles({
9665
+ root: {
9666
+ paddingTop: '1.25rem' //height of the menu button + marginBottom
9647
9667
  },
9648
- processNode: (node, children, index) => {
9649
- node.attribs = Object.assign(Object.assign({}, node.attribs), { 'aria-label': node.attribs.name });
9650
- // logic to check id in map/list
9651
- if (props.attachmentsMap && node.attribs.id in props.attachmentsMap) {
9652
- node.attribs = Object.assign(Object.assign({}, node.attribs), { src: props.attachmentsMap[node.attribs.id] });
9653
- }
9654
- /* @conditional-compile-remove(image-gallery) */
9655
- const handleOnClick = () => {
9656
- props.onInlineImageClicked && props.onInlineImageClicked(node.attribs.id);
9657
- };
9658
- /* @conditional-compile-remove(image-gallery) */
9659
- return (React__default["default"].createElement("span", { "data-ui-id": node.attribs.id, onClick: handleOnClick, tabIndex: 0, role: "button", style: {
9660
- cursor: 'pointer'
9661
- }, onKeyDown: (e) => {
9662
- if (e.key === 'Enter') {
9663
- handleOnClick();
9664
- }
9665
- } }, processNodeDefinitions.processDefaultNode(node, children, index)));
9666
- }
9668
+ body: Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, reactComponents.shorthands.padding(0)), { backgroundColor: 'transparent', boxSizing: 'border-box' }), reactComponents.shorthands.border(`${defaultSendBoxInactiveBorderThicknessREM}rem`, 'solid')), reactComponents.shorthands.borderRadius(reactComponents.tokens.borderRadiusMedium)), reactComponents.shorthands.margin(`${defaultSendBoxActiveBorderThicknessREM - defaultSendBoxInactiveBorderThicknessREM}rem`)), {
9669
+ // Width should be updated on hover to include the border width change
9670
+ width: `calc(100% - ${defaultSendBoxActiveBorderThicknessREM}rem)`, '&:hover, &:active, &:focus, &:focus-within': Object.assign(Object.assign(Object.assign({}, reactComponents.shorthands.margin('0rem')), reactComponents.shorthands.borderWidth(`${defaultSendBoxActiveBorderThicknessREM}rem`)), { width: '100%' }) }),
9671
+ bodyError: Object.assign({}, reactComponents.shorthands.borderColor(errorTextColor)),
9672
+ bodyDefault: Object.assign(Object.assign({}, reactComponents.shorthands.borderColor(reactComponents.tokens.colorNeutralStrokeAccessible)), { '&:hover, &:active, &:focus, &:focus-within': Object.assign({}, reactComponents.shorthands.borderColor(reactComponents.tokens.colorCompoundBrandStroke)) })
9667
9673
  });
9668
- /* @conditional-compile-remove(mention) */
9669
- const processMention = (props) => ({
9670
- shouldProcessNode: (node) => {
9671
- var _a;
9672
- if ((_a = props.mentionDisplayOptions) === null || _a === void 0 ? void 0 : _a.onRenderMention) {
9673
- // Override the handling of the <msft-mention> tag in the HTML if there's a custom renderer
9674
- return node.name === 'msft-mention';
9675
- }
9676
- return false;
9674
+ /**
9675
+ * Styles that can be applied to ensure flyout items have the minimum touch target size.
9676
+ *
9677
+ * @private
9678
+ */
9679
+ const menuItemIncreasedSizeStyles = {
9680
+ root: {
9681
+ height: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9682
+ lineHeight: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9683
+ maxHeight: 'unset'
9677
9684
  },
9678
- processNode: (node) => {
9679
- var _a, _b, _c;
9680
- if ((_a = props.mentionDisplayOptions) === null || _a === void 0 ? void 0 : _a.onRenderMention) {
9681
- const { id } = node.attribs;
9682
- const mention = {
9683
- id: id,
9684
- displayText: (_c = (_b = node.children[0]) === null || _b === void 0 ? void 0 : _b.data) !== null && _c !== void 0 ? _c : ''
9685
- };
9686
- return props.mentionDisplayOptions.onRenderMention(mention, defaultOnMentionRender);
9687
- }
9688
- return processNodeDefinitions.processDefaultNode;
9685
+ linkContent: {
9686
+ height: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9687
+ lineHeight: `${MINIMUM_TOUCH_TARGET_HEIGHT_REM$1}rem`,
9688
+ maxHeight: 'unset'
9689
+ },
9690
+ icon: {
9691
+ maxHeight: 'unset'
9689
9692
  }
9690
- });
9691
- const processHtmlToReact = (props) => {
9692
- var _a;
9693
- const steps = [
9694
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9695
- processInlineImage(props),
9696
- /* @conditional-compile-remove(mention) */
9697
- processMention(props),
9698
- {
9699
- // Process everything else in the default way
9700
- shouldProcessNode: htmlToReact.IsValidNodeDefinitions.alwaysValid,
9701
- processNode: processNodeDefinitions.processDefaultNode
9702
- }
9703
- ];
9704
- return htmlToReactParser.parseWithInstructions((_a = props.message.content) !== null && _a !== void 0 ? _a : '', htmlToReact.IsValidNodeDefinitions.alwaysValid, steps);
9705
9693
  };
9706
-
9707
- // Copyright (c) Microsoft Corporation.
9708
9694
  /**
9709
- * Props for the Chat.Message action menu.
9710
- * This is the 3 dots that appear when hovering over one of your own chat messages.
9711
- *
9712
9695
  * @private
9713
9696
  */
9714
- const chatMessageActionMenuProps = (menuProps) => {
9715
- const { ariaLabel, enabled, theme, menuExpanded } = menuProps;
9716
- // Show the action button while the flyout is open (otherwise this will dismiss when the pointer is hovered over the flyout)
9717
- const showActionMenu = enabled || menuExpanded;
9718
- const actionMenuProps = {
9719
- children: (React__default["default"].createElement("div", { tabIndex: showActionMenu ? 0 : undefined, key: "menuButton", "data-ui-id": "chat-composite-message-action-icon", ref: menuProps.menuButtonRef, onClick: showActionMenu ? () => menuProps.onActionButtonClick() : undefined, style: { margin: showActionMenu ? '1px' : 0, minHeight: showActionMenu ? undefined : '30px' }, role: "button", "aria-label": showActionMenu ? ariaLabel : undefined, "aria-haspopup": showActionMenu, "aria-expanded": menuExpanded, onKeyDown: (e) => {
9720
- // simulate <button> tag behavior
9721
- if (showActionMenu && (e.key === 'Enter' || e.key === ' ')) {
9722
- menuProps.onActionButtonClick();
9723
- }
9724
- } }, showActionMenu ? (React__default["default"].createElement(react.Icon, { iconName: "ChatMessageOptions", "aria-label": ariaLabel, styles: iconWrapperStyle(theme, menuExpanded) })) : undefined))
9725
- };
9726
- return actionMenuProps;
9697
+ const menuIconStyleSet = {
9698
+ root: {
9699
+ height: 'calc(100% - 8px)',
9700
+ width: '1.25rem'
9701
+ }
9702
+ };
9703
+ /**
9704
+ * @private
9705
+ */
9706
+ const menuSubIconStyleSet = {
9707
+ root: {
9708
+ height: 'unset',
9709
+ lineHeight: '100%',
9710
+ width: '1.25rem'
9711
+ }
9727
9712
  };
9728
9713
 
9729
9714
  // Copyright (c) Microsoft Corporation.
9730
- // Licensed under the MIT License.
9731
- var __awaiter$y = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
9732
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9733
- return new (P || (P = Promise))(function (resolve, reject) {
9734
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
9735
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9736
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9737
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9738
- });
9715
+ const MAXIMUM_LENGTH_OF_MESSAGE = 8000;
9716
+ const onRenderCancelIcon = (color) => {
9717
+ const className = react.mergeStyles(inputBoxIcon, { color });
9718
+ return React__default["default"].createElement(react.Icon, { iconName: 'EditBoxCancel', className: className });
9739
9719
  };
9740
- const fileDownloadCardsStyle = {
9741
- marginTop: '0.25rem'
9720
+ const onRenderSubmitIcon = (color) => {
9721
+ const className = react.mergeStyles(inputBoxIcon, { color });
9722
+ return React__default["default"].createElement(react.Icon, { iconName: 'EditBoxSubmit', className: className });
9742
9723
  };
9743
- const actionIconStyle = { height: '1rem' };
9744
9724
  /**
9745
- * @internal
9725
+ * @private
9746
9726
  */
9747
- const _FileDownloadCards = (props) => {
9748
- var _a, _b;
9749
- const { userId, fileMetadata } = props;
9750
- const [showSpinner, setShowSpinner] = React.useState(false);
9751
- const localeStrings = useLocaleStringsTrampoline();
9752
- const downloadFileButtonString = React.useMemo(() => () => {
9753
- var _a, _b;
9754
- return (_b = (_a = props.strings) === null || _a === void 0 ? void 0 : _a.downloadFile) !== null && _b !== void 0 ? _b : localeStrings.downloadFile;
9755
- }, [(_a = props.strings) === null || _a === void 0 ? void 0 : _a.downloadFile, localeStrings.downloadFile]);
9756
- const isFileSharingAttachment = React.useCallback((attachment) => {
9757
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9758
- return attachment.attachmentType === 'fileSharing';
9759
- }, []);
9760
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9761
- const isShowDownloadIcon = React.useCallback((attachment) => {
9727
+ const ChatMessageComponentAsEditBox = (props) => {
9728
+ const { onCancel, onSubmit, strings, message } = props;
9729
+ /* @conditional-compile-remove(mention) */
9730
+ const { mentionLookupOptions } = props;
9731
+ const [textValue, setTextValue] = React.useState(message.content || '');
9732
+ const [attachedFilesMetadata, setAttachedFilesMetadata] = React__default["default"].useState(getMessageAttachedFilesMetadata(message));
9733
+ const editTextFieldRef = React__default["default"].useRef(null);
9734
+ const theme = useTheme();
9735
+ const messageState = getMessageState(textValue, attachedFilesMetadata !== null && attachedFilesMetadata !== void 0 ? attachedFilesMetadata : []);
9736
+ const submitEnabled = messageState === 'OK';
9737
+ const editContainerStyles = useChatMessageEditContainerStyles();
9738
+ const chatMyMessageStyles = useChatMyMessageStyles();
9739
+ React.useEffect(() => {
9762
9740
  var _a;
9763
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9764
- return attachment.attachmentType === 'fileSharing' && ((_a = attachment.payload) === null || _a === void 0 ? void 0 : _a.teamsFileAttachment) !== 'true';
9741
+ (_a = editTextFieldRef.current) === null || _a === void 0 ? void 0 : _a.focus();
9765
9742
  }, []);
9766
- const fileCardGroupDescription = React.useMemo(() => () => {
9767
- var _a, _b;
9768
- const fileGroupLocaleString = (_b = (_a = props.strings) === null || _a === void 0 ? void 0 : _a.fileCardGroupMessage) !== null && _b !== void 0 ? _b : localeStrings.fileCardGroupMessage;
9769
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9770
- return _formatString(fileGroupLocaleString, {
9771
- fileCount: `${fileMetadata.filter(isFileSharingAttachment).length}`
9772
- });
9773
- }, [(_b = props.strings) === null || _b === void 0 ? void 0 : _b.fileCardGroupMessage, localeStrings.fileCardGroupMessage, fileMetadata, isFileSharingAttachment]);
9774
- const fileDownloadHandler = React.useCallback((userId, file) => __awaiter$y(void 0, void 0, void 0, function* () {
9775
- if (!props.downloadHandler) {
9776
- window.open(file.url, '_blank', 'noopener,noreferrer');
9777
- }
9778
- else {
9779
- setShowSpinner(true);
9780
- try {
9781
- const response = yield props.downloadHandler(userId, file);
9782
- setShowSpinner(false);
9783
- if (response instanceof URL) {
9784
- window.open(response.toString(), '_blank', 'noopener,noreferrer');
9785
- }
9786
- else {
9787
- props.onDownloadErrorMessage && props.onDownloadErrorMessage(response.errorMessage);
9788
- }
9789
- }
9790
- finally {
9791
- setShowSpinner(false);
9792
- }
9793
- }
9794
- }), [props]);
9795
- if (!fileMetadata ||
9796
- fileMetadata.length === 0 ||
9797
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */ !fileMetadata.some(isFileSharingAttachment)) {
9798
- return React__default["default"].createElement(React__default["default"].Fragment, null);
9799
- }
9800
- return (React__default["default"].createElement("div", { style: fileDownloadCardsStyle, "data-ui-id": "file-download-card-group" },
9801
- React__default["default"].createElement(_FileCardGroup, { ariaLabel: fileCardGroupDescription() }, fileMetadata &&
9802
- fileMetadata
9803
- .filter((attachment) => {
9804
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9805
- return isFileSharingAttachment(attachment);
9806
- })
9807
- .map((file) => (React__default["default"].createElement(react.TooltipHost, { content: downloadFileButtonString(), key: file.name },
9808
- React__default["default"].createElement(_FileCard, { fileName: file.name, key: file.name, fileExtension: file.extension, actionIcon: showSpinner ? (React__default["default"].createElement(react.Spinner, { size: react.SpinnerSize.medium, "aria-live": 'polite', role: 'status' })) : /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9809
- isShowDownloadIcon(file) ? (React__default["default"].createElement(react.IconButton, { className: iconButtonClassName, ariaLabel: downloadFileButtonString() },
9810
- React__default["default"].createElement(DownloadIconTrampoline, null))) : undefined, actionHandler: () => fileDownloadHandler(userId, file) })))))));
9743
+ const setText = (event, newValue) => {
9744
+ setTextValue(newValue !== null && newValue !== void 0 ? newValue : '');
9745
+ };
9746
+ const textTooLongMessage = messageState === 'too long'
9747
+ ? _formatString(strings.editBoxTextLimit, { limitNumber: `${MAXIMUM_LENGTH_OF_MESSAGE}` })
9748
+ : undefined;
9749
+ const onRenderThemedCancelIcon = React.useCallback((isHover) => onRenderCancelIcon(isHover ? theme.palette.accent : theme.palette.neutralSecondary), [theme.palette.neutralSecondary, theme.palette.accent]);
9750
+ const onRenderThemedSubmitIcon = React.useCallback((isHover) => onRenderSubmitIcon(isHover ? theme.palette.accent : theme.palette.neutralSecondary), [theme.palette.neutralSecondary, theme.palette.accent]);
9751
+ const editBoxStyles = React.useMemo(() => {
9752
+ return react.concatStyleSets(editBoxStyleSet, { textField: { borderColor: theme.palette.themePrimary } });
9753
+ }, [theme.palette.themePrimary]);
9754
+ const onRenderFileUploads = React.useCallback(() => {
9755
+ return (!!attachedFilesMetadata &&
9756
+ attachedFilesMetadata.length > 0 && (React__default["default"].createElement("div", { style: { margin: '0.25rem' } },
9757
+ React__default["default"].createElement(_FileUploadCards, { activeFileUploads: attachedFilesMetadata === null || attachedFilesMetadata === void 0 ? void 0 : attachedFilesMetadata.map((file) => ({
9758
+ id: file.name,
9759
+ filename: file.name,
9760
+ progress: 1
9761
+ })), onCancelFileUpload: (fileId) => {
9762
+ setAttachedFilesMetadata(attachedFilesMetadata === null || attachedFilesMetadata === void 0 ? void 0 : attachedFilesMetadata.filter((file) => file.name !== fileId));
9763
+ } }))));
9764
+ }, [attachedFilesMetadata]);
9765
+ const getContent = () => {
9766
+ return (React__default["default"].createElement(React__default["default"].Fragment, null,
9767
+ React__default["default"].createElement(InputBoxComponent, { "data-ui-id": "edit-box", textFieldRef: editTextFieldRef, inputClassName: editBoxStyle, placeholderText: strings.editBoxPlaceholderText, textValue: textValue, onChange: setText, onKeyDown: (ev) => {
9768
+ if (ev.key === 'ArrowUp' || ev.key === 'ArrowDown') {
9769
+ ev.stopPropagation();
9770
+ }
9771
+ }, onEnterKeyDown: () => {
9772
+ submitEnabled &&
9773
+ onSubmit(textValue, message.metadata, {
9774
+ attachedFilesMetadata
9775
+ });
9776
+ }, supportNewline: false, maxLength: MAXIMUM_LENGTH_OF_MESSAGE, errorMessage: textTooLongMessage, styles: editBoxStyles,
9777
+ /* @conditional-compile-remove(mention) */
9778
+ mentionLookupOptions: mentionLookupOptions }),
9779
+ React__default["default"].createElement(react.Stack, { horizontal: true, horizontalAlign: "end", className: editChatMessageButtonsStackStyle, tokens: { childrenGap: '0.25rem' } },
9780
+ message.failureReason && (React__default["default"].createElement(react.Stack.Item, { grow: true, align: "stretch", className: chatMessageFailedTagStackItemStyle },
9781
+ React__default["default"].createElement("div", { className: react.mergeStyles(chatMessageFailedTagStyle(theme), editChatMessageFailedTagStyle) }, message.failureReason))),
9782
+ React__default["default"].createElement(react.Stack.Item, { align: "end" },
9783
+ React__default["default"].createElement(InputBoxButton, { className: editingButtonStyle, ariaLabel: strings.editBoxCancelButton, tooltipContent: strings.editBoxCancelButton, onRenderIcon: onRenderThemedCancelIcon, onClick: () => {
9784
+ onCancel && onCancel(message.messageId);
9785
+ }, id: 'dismissIconWrapper' })),
9786
+ React__default["default"].createElement(react.Stack.Item, { align: "end" },
9787
+ React__default["default"].createElement(InputBoxButton, { className: editingButtonStyle, ariaLabel: strings.editBoxSubmitButton, tooltipContent: strings.editBoxSubmitButton, onRenderIcon: onRenderThemedSubmitIcon, onClick: (e) => {
9788
+ submitEnabled &&
9789
+ onSubmit(textValue, message.metadata, {
9790
+ attachedFilesMetadata
9791
+ });
9792
+ e.stopPropagation();
9793
+ }, id: 'submitIconWrapper' }))),
9794
+ onRenderFileUploads()));
9795
+ };
9796
+ const bodyClassName = reactComponents.mergeClasses(editContainerStyles.body, message.failureReason !== undefined ? editContainerStyles.bodyError : editContainerStyles.bodyDefault);
9797
+ return (React__default["default"].createElement(reactChat.ChatMyMessage, { root: {
9798
+ className: reactComponents.mergeClasses(chatMyMessageStyles.root, editContainerStyles.root)
9799
+ }, body: {
9800
+ className: bodyClassName
9801
+ } }, getContent()));
9802
+ };
9803
+ const isMessageTooLong = (messageText) => messageText.length > MAXIMUM_LENGTH_OF_MESSAGE;
9804
+ const isMessageEmpty = (messageText, attachedFilesMetadata) => messageText.trim().length === 0 && attachedFilesMetadata.length === 0;
9805
+ const getMessageState = (messageText, attachedFilesMetadata) => isMessageEmpty(messageText, attachedFilesMetadata) ? 'too short' : isMessageTooLong(messageText) ? 'too long' : 'OK';
9806
+ // @TODO: Remove when file-sharing feature becomes stable.
9807
+ const getMessageAttachedFilesMetadata = (message) => {
9808
+ /* @conditional-compile-remove(file-sharing) */
9809
+ return message.attachedFilesMetadata;
9811
9810
  };
9811
+
9812
+ // Copyright (c) Microsoft Corporation.
9813
+ // Licensed under the MIT License.
9812
9814
  /**
9813
9815
  * @private
9814
9816
  */
9815
- const DownloadIconTrampoline = () => {
9816
- // @conditional-compile-remove(file-sharing)
9817
- return React__default["default"].createElement(react.Icon, { "data-ui-id": "file-download-card-download-icon", iconName: "DownloadFile", style: actionIconStyle });
9817
+ const formatTimeForChatMessage = (messageDate) => {
9818
+ return messageDate.toLocaleTimeString([], { hour: 'numeric', minute: '2-digit' });
9818
9819
  };
9819
- const useLocaleStringsTrampoline = () => {
9820
- /* @conditional-compile-remove(file-sharing) @conditional-compile-remove(teams-inline-images-and-file-sharing)*/
9821
- return useLocale$1().strings.messageThread;
9820
+ /**
9821
+ * @private
9822
+ */
9823
+ const formatDateForChatMessage = (messageDate) => {
9824
+ return messageDate.toLocaleDateString();
9822
9825
  };
9823
-
9824
- // Copyright (c) Microsoft Corporation.
9825
- // Licensed under the MIT License.
9826
9826
  /**
9827
- * Function to create a React.CSSProperties object from a v8 style object.
9828
- * This function is still not ideal
9829
- * as v8Style can use pseudo-class selectors that style objects can't process correctly.
9827
+ * Given a message date object in ISO8601 and a current date object, generates a user friendly timestamp text
9828
+ * using the system locale.
9829
+ * <time in locale format>.
9830
+ * Yesterday <time in locale format>.
9831
+ * <dateStrings day of week> <time in locale format>.
9832
+ * <date in locale format> <time in locale format>.
9833
+ *
9834
+ * If message is after yesterday, then only show the time.
9835
+ * If message is before yesterday and after day before yesterday, then show 'Yesterday' plus the time.
9836
+ * If message is before day before yesterday and within the current week, then show 'Monday/Tuesday/etc' plus the time.
9837
+ * - We consider start of the week as Sunday. If current day is Sunday, then any time before that is in previous week.
9838
+ * If message is in previous or older weeks, then show date string plus the time.
9839
+ *
9840
+ * @param messageDate - date of message
9841
+ * @param currentDate - date used as offset to create the user friendly timestamp (e.g. to create 'Yesterday' instead of an absolute date)
9830
9842
  *
9831
9843
  * @private
9832
9844
  */
9833
- function createStyleFromV8Style(v8Style) {
9834
- const result = {};
9835
- if (v8Style === undefined || v8Style === null || typeof v8Style === 'boolean' || typeof v8Style === 'string') {
9836
- return undefined;
9845
+ const formatTimestampForChatMessage = (messageDate, todayDate, dateStrings) => {
9846
+ // If message was in the same day timestamp string is just the time like '1:30 p.m.'.
9847
+ const startOfDay = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate());
9848
+ if (messageDate > startOfDay) {
9849
+ return formatTimeForChatMessage(messageDate);
9837
9850
  }
9838
- else if (typeof v8Style === 'object') {
9839
- // v8Style is a style object
9840
- for (const record in v8Style) {
9841
- if (typeof v8Style[record] === 'string') {
9842
- // v8Style[record] is just a simple style
9843
- const msSuffix = 'MS';
9844
- if (record.startsWith(msSuffix)) {
9845
- // React.CSSProperties uses camelCase for MS properties but v8Style uses PascalCase
9846
- const newRecord = record.substring(0, msSuffix.length).toLowerCase + record.substring(msSuffix.length);
9847
- result[newRecord] = v8Style[record];
9848
- }
9849
- else {
9850
- result[record] = v8Style[record];
9851
- }
9852
- }
9853
- else {
9854
- result[record] = createStyleFromV8Style(v8Style[record]);
9855
- }
9856
- }
9851
+ // If message was yesterday then timestamp string is like this 'Yesterday 1:30 p.m.'.
9852
+ const yesterdayDate = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate() - 1);
9853
+ if (messageDate > yesterdayDate) {
9854
+ return dateStrings.yesterday + ' ' + formatTimeForChatMessage(messageDate);
9857
9855
  }
9858
- return result;
9859
- }
9860
-
9861
- // Copyright (c) Microsoft Corporation.
9862
- // Licensed under the MIT License.
9863
- var __awaiter$x = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
9864
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
9865
- return new (P || (P = Promise))(function (resolve, reject) {
9866
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
9867
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
9868
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
9869
- step((generator = generator.apply(thisArg, _arguments || [])).next());
9870
- });
9856
+ // If message was before Sunday and today is Sunday (start of week) then timestamp string is like
9857
+ // '2021-01-10 1:30 p.m.'.
9858
+ const weekDay = todayDate.getDay();
9859
+ if (weekDay === 0) {
9860
+ return formatDateForChatMessage(messageDate) + ' ' + formatTimeForChatMessage(messageDate);
9861
+ }
9862
+ // If message was before first day of the week then timestamp string is like Monday 1:30 p.m.
9863
+ const firstDayOfTheWeekDate = new Date(todayDate.getFullYear(), todayDate.getMonth(), todayDate.getDate() - weekDay);
9864
+ if (messageDate > firstDayOfTheWeekDate) {
9865
+ return dayToDayName(messageDate.getDay(), dateStrings) + ' ' + formatTimeForChatMessage(messageDate);
9866
+ }
9867
+ // If message date is in previous or older weeks then timestamp string is like 2021-01-10 1:30 p.m.
9868
+ return formatDateForChatMessage(messageDate) + ' ' + formatTimeForChatMessage(messageDate);
9871
9869
  };
9872
- const generateDefaultTimestamp = (createdOn, showDate, strings) => {
9873
- const formattedTimestamp = showDate
9874
- ? formatTimestampForChatMessage(createdOn, new Date(), strings)
9875
- : formatTimeForChatMessage(createdOn);
9876
- return formattedTimestamp;
9870
+ const dayToDayName = (day, dateStrings) => {
9871
+ switch (day) {
9872
+ case 0:
9873
+ return dateStrings.sunday;
9874
+ case 1:
9875
+ return dateStrings.monday;
9876
+ case 2:
9877
+ return dateStrings.tuesday;
9878
+ case 3:
9879
+ return dateStrings.wednesday;
9880
+ case 4:
9881
+ return dateStrings.thursday;
9882
+ case 5:
9883
+ return dateStrings.friday;
9884
+ case 6:
9885
+ return dateStrings.saturday;
9886
+ default:
9887
+ throw new Error(`Invalid day [${day}] passed`);
9888
+ }
9877
9889
  };
9878
- // onDisplayDateTimeString from props overwrite onDisplayDateTimeString from locale
9879
- const generateCustomizedTimestamp = (props, createdOn, locale) => {
9880
- /* @conditional-compile-remove(date-time-customization) */
9881
- return props.onDisplayDateTimeString
9882
- ? props.onDisplayDateTimeString(createdOn)
9883
- : locale.onDisplayDateTimeString
9884
- ? locale.onDisplayDateTimeString(createdOn)
9885
- : '';
9886
- };
9887
- /** @private */
9888
- const MessageBubble = (props) => {
9889
- var _a;
9890
- const ids = useIdentifiers();
9891
- const theme = useTheme();
9892
- const locale = useLocale$1();
9893
- const { userId, message, onRemoveClick, onResendClick, disableEditing, showDate, messageContainerStyle, strings, onEditClick, remoteParticipantsCount = 0, onRenderAvatar, showMessageStatus, messageStatus, fileDownloadHandler,
9894
- /* @conditional-compile-remove(image-gallery) */
9895
- onInlineImageClicked, shouldOverlapAvatarAndMessage } = props;
9896
- const defaultTimeStamp = message.createdOn
9897
- ? generateDefaultTimestamp(message.createdOn, showDate, strings)
9898
- : undefined;
9899
- const customTimestamp = message.createdOn ? generateCustomizedTimestamp(props, message.createdOn, locale) : '';
9900
- const formattedTimestamp = customTimestamp || defaultTimeStamp;
9901
- // Track if the action menu was opened by touch - if so we increase the touch targets for the items
9902
- const [wasInteractionByTouch, setWasInteractionByTouch] = React.useState(false);
9903
- // `focused` state is used for show/hide actionMenu
9904
- const [focused, setFocused] = React__default["default"].useState(false);
9905
- // The chat message action flyout should target the Chat.Message action menu if clicked,
9906
- // or target the chat message if opened via touch press.
9907
- // Undefined indicates the flyout menu should not be being shown.
9908
- const messageRef = React.useRef(null);
9909
- const messageActionButtonRef = React.useRef(null);
9910
- const [chatMessageActionFlyoutTarget, setChatMessageActionFlyoutTarget] = React.useState(undefined);
9911
- const chatActionsEnabled = !disableEditing &&
9912
- message.status !== 'sending' &&
9913
- !!message.mine &&
9914
- /* @conditional-compile-remove(data-loss-prevention) */ message.messageType !== 'blocked';
9915
- const [messageReadBy, setMessageReadBy] = React.useState([]);
9916
- const actionMenuProps = chatMessageActionMenuProps({
9917
- ariaLabel: (_a = strings.actionMenuMoreOptions) !== null && _a !== void 0 ? _a : '',
9918
- enabled: chatActionsEnabled,
9919
- menuButtonRef: messageActionButtonRef,
9920
- menuExpanded: chatMessageActionFlyoutTarget === messageActionButtonRef,
9921
- onActionButtonClick: () => {
9922
- if (message.messageType === 'chat') {
9923
- props.onActionButtonClick(message, setMessageReadBy);
9924
- setChatMessageActionFlyoutTarget(messageActionButtonRef);
9925
- }
9926
- },
9927
- theme
9928
- });
9929
- const onActionFlyoutDismiss = React.useCallback(() => {
9930
- // When the flyout dismiss is called, since we control if the action flyout is visible
9931
- // or not we need to set the target to undefined here to actually hide the action flyout
9932
- setChatMessageActionFlyoutTarget(undefined);
9933
- }, [setChatMessageActionFlyoutTarget]);
9934
- const defaultOnRenderFileDownloads = React.useCallback(() => (React__default["default"].createElement(_FileDownloadCards, { userId: userId, fileMetadata: message['attachedFilesMetadata'] || [], downloadHandler: fileDownloadHandler,
9935
- /* @conditional-compile-remove(file-sharing) @conditional-compile-remove(teams-inline-images-and-file-sharing)*/
9936
- strings: { downloadFile: strings.downloadFile, fileCardGroupMessage: strings.fileCardGroupMessage } })), [
9937
- userId,
9938
- message,
9939
- /* @conditional-compile-remove(file-sharing) @conditional-compile-remove(teams-inline-images-and-file-sharing)*/
9940
- strings,
9941
- fileDownloadHandler
9942
- ]);
9943
- const editedOn = 'editedOn' in message ? message.editedOn : undefined;
9944
- const getMessageDetails = React.useCallback(() => {
9945
- if (messageStatus === 'failed') {
9946
- return React__default["default"].createElement("div", { className: chatMessageFailedTagStyle(theme) }, strings.failToSendTag);
9947
- }
9948
- else if (message.messageType === 'chat' && editedOn) {
9949
- return React__default["default"].createElement("div", { className: chatMessageEditedTagStyle(theme) }, strings.editedTag);
9950
- }
9951
- return undefined;
9952
- }, [editedOn, message.messageType, messageStatus, strings.editedTag, strings.failToSendTag, theme]);
9953
- /* @conditional-compile-remove(image-gallery) */
9954
- const handleOnInlineImageClicked = React.useCallback((attachmentId) => __awaiter$x(void 0, void 0, void 0, function* () {
9955
- if (onInlineImageClicked === undefined) {
9956
- return;
9957
- }
9958
- yield onInlineImageClicked(attachmentId, message.messageId);
9959
- }), [message, onInlineImageClicked]);
9960
- const getContent = React.useCallback(() => {
9961
- /* @conditional-compile-remove(data-loss-prevention) */
9962
- if (message.messageType === 'blocked') {
9963
- return (React__default["default"].createElement("div", { tabIndex: 0 },
9964
- React__default["default"].createElement(BlockedMessageContent, { message: message, strings: strings })));
9965
- }
9966
- return (React__default["default"].createElement("div", { tabIndex: 0, className: "ui-chat__message__content" },
9967
- React__default["default"].createElement(ChatMessageContent, { message: message, strings: strings,
9968
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9969
- onFetchAttachment: props.onFetchAttachments,
9970
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
9971
- attachmentsMap: props.attachmentsMap,
9972
- /* @conditional-compile-remove(mention) */
9973
- mentionDisplayOptions: props.mentionDisplayOptions,
9974
- /* @conditional-compile-remove(image-gallery) */
9975
- onInlineImageClicked: handleOnInlineImageClicked }),
9976
- props.onRenderFileDownloads ? props.onRenderFileDownloads(userId, message) : defaultOnRenderFileDownloads()));
9977
- }, [
9978
- defaultOnRenderFileDownloads,
9979
- message,
9980
- props,
9981
- strings,
9982
- userId,
9983
- /* @conditional-compile-remove(image-gallery) */
9984
- handleOnInlineImageClicked
9985
- ]);
9986
- const isBlockedMessage = /* @conditional-compile-remove(data-loss-prevention) */ message.messageType === 'blocked';
9987
- const chatMyMessageStyles = useChatMyMessageStyles();
9988
- const chatMessageCommonStyles = useChatMessageCommonStyles();
9989
- const chatMessageStyles = useChatMessageStyles();
9990
- const chatItemMessageContainerClassName = reactComponents.mergeClasses(
9991
- // messageContainerStyle used in className and style prop as style prop can't handle CSS selectors
9992
- chatMessageStyles.body, isBlockedMessage
9993
- ? chatMessageCommonStyles.blocked
9994
- : props.message.status === 'failed'
9995
- ? chatMessageCommonStyles.failed
9996
- : undefined, shouldOverlapAvatarAndMessage ? chatMessageStyles.avatarOverlap : chatMessageStyles.avatarNoOverlap, message.attached === 'top' || message.attached === false
9997
- ? chatMessageStyles.bodyWithAvatar
9998
- : chatMessageStyles.bodyWithoutAvatar, react.mergeStyles(messageContainerStyle));
9999
- const attached = message.attached === true ? 'center' : message.attached === 'bottom' ? 'bottom' : 'top';
10000
- const chatMessage = (React__default["default"].createElement(React__default["default"].Fragment, null,
10001
- React__default["default"].createElement("div", { key: props.message.messageId }, message.mine ? (React__default["default"].createElement(reactChat.ChatMyMessage, { attached: attached, key: props.message.messageId, body: {
10002
- // messageContainerStyle used in className and style prop as style prop can't handle CSS selectors
10003
- className: reactComponents.mergeClasses(chatMyMessageStyles.body, isBlockedMessage
10004
- ? chatMessageCommonStyles.blocked
10005
- : props.message.status === 'failed'
10006
- ? chatMessageCommonStyles.failed
10007
- : undefined, attached !== 'top' ? chatMyMessageStyles.bodyAttached : undefined, react.mergeStyles(messageContainerStyle)),
10008
- style: Object.assign({}, createStyleFromV8Style(messageContainerStyle)),
10009
- ref: messageRef
10010
- }, root: {
10011
- className: chatMyMessageStyles.root,
10012
- onBlur: (e) => {
10013
- // `focused` controls is focused the whole `ChatMessage` or any of its children. When we're navigating
10014
- // with keyboard the focused element will be changed and there is no way to use `:focus` selector
10015
- if (chatMessageActionFlyoutTarget === null || chatMessageActionFlyoutTarget === void 0 ? void 0 : chatMessageActionFlyoutTarget.current) {
10016
- // doesn't dismiss action button if flyout is open, otherwise, narrator's focus will stay on the closed action menu
10017
- return;
10018
- }
10019
- const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget);
10020
- setFocused(shouldPreserveFocusState);
10021
- },
10022
- onFocus: () => {
10023
- // react onFocus is called even when nested component receives focus (i.e. it bubbles)
10024
- // so when focus moves within actionMenu, the `focus` state in chatMessage remains true, and keeps actionMenu visible
10025
- setFocused(true);
10026
- },
10027
- // make body not focusable to remove repetitions from narrators.
10028
- // inner components are already focusable
10029
- role: 'none',
10030
- tabIndex: -1
10031
- }, "data-ui-id": "chat-composite-message", author: React__default["default"].createElement(react.Text, { className: chatMessageDateStyle, tabIndex: 0 }, message.senderDisplayName), timestamp: React__default["default"].createElement(react.Text, { className: chatMessageDateStyle, "data-ui-id": ids.messageTimestamp, tabIndex: 0 }, formattedTimestamp), details: getMessageDetails(), actions: {
10032
- children: actionMenuProps === null || actionMenuProps === void 0 ? void 0 : actionMenuProps.children,
10033
- className: reactComponents.mergeClasses(chatMyMessageStyles.menu,
10034
- // Make actions menu visible when the message is focused or the flyout is shown
10035
- focused || (chatMessageActionFlyoutTarget === null || chatMessageActionFlyoutTarget === void 0 ? void 0 : chatMessageActionFlyoutTarget.current)
10036
- ? chatMyMessageStyles.menuVisible
10037
- : chatMyMessageStyles.menuHidden, attached !== 'top' ? chatMyMessageStyles.menuAttached : undefined)
10038
- }, onTouchStart: () => setWasInteractionByTouch(true), onPointerDown: () => setWasInteractionByTouch(false), onKeyDown: () => setWasInteractionByTouch(false), onClick: () => {
10039
- if (!wasInteractionByTouch) {
10040
- return;
10041
- }
10042
- // If the message was touched via touch we immediately open the menu
10043
- // flyout (when using mouse the 3-dot menu that appears on hover
10044
- // must be clicked to open the flyout).
10045
- // In doing so here we set the target of the flyout to be the message and
10046
- // not the 3-dot menu button to position the flyout correctly.
10047
- setChatMessageActionFlyoutTarget(messageRef);
10048
- if (message.messageType === 'chat') {
10049
- props.onActionButtonClick(message, setMessageReadBy);
10050
- }
10051
- } }, getContent())) : (React__default["default"].createElement(reactChat.ChatMessage, { attached: attached, key: props.message.messageId, root: {
10052
- className: chatMessageStyles.root,
10053
- // make body not focusable to remove repetitions from narrators.
10054
- // inner components are already focusable
10055
- tabIndex: -1,
10056
- role: 'none'
10057
- }, author: React__default["default"].createElement(react.Text, { className: chatMessageAuthorStyle }, message.senderDisplayName), body: {
10058
- className: chatItemMessageContainerClassName,
10059
- style: Object.assign({}, createStyleFromV8Style(messageContainerStyle))
10060
- }, "data-ui-id": "chat-composite-message", timestamp: React__default["default"].createElement(react.Text, { className: chatMessageDateStyle, "data-ui-id": ids.messageTimestamp }, formattedTimestamp) }, getContent()))),
10061
- chatActionsEnabled && (React__default["default"].createElement(ChatMessageActionFlyout, { hidden: !chatMessageActionFlyoutTarget, target: chatMessageActionFlyoutTarget, increaseFlyoutItemSize: wasInteractionByTouch, onDismiss: onActionFlyoutDismiss, onEditClick: onEditClick, onRemoveClick: onRemoveClick, onResendClick: onResendClick, strings: strings, messageReadBy: messageReadBy, messageStatus: messageStatus !== null && messageStatus !== void 0 ? messageStatus : 'failed', remoteParticipantsCount: remoteParticipantsCount, onRenderAvatar: onRenderAvatar, showMessageStatus: showMessageStatus }))));
10062
- return chatMessage;
10063
- };
10064
- /** @private */
10065
- const ChatMessageComponentAsMessageBubble = React__default["default"].memo(MessageBubble);
10066
9890
 
10067
9891
  // Copyright (c) Microsoft Corporation.
10068
- // Licensed under the MIT License.
10069
- var __awaiter$w = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
10070
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10071
- return new (P || (P = Promise))(function (resolve, reject) {
10072
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10073
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10074
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10075
- step((generator = generator.apply(thisArg, _arguments || [])).next());
10076
- });
10077
- };
10078
9892
  /**
9893
+ * Chat message actions flyout that contains actions such as Edit Message, or Remove Message.
9894
+ *
10079
9895
  * @private
10080
9896
  */
10081
- const ChatMessageComponent = (props) => {
9897
+ const ChatMessageActionFlyout = (props) => {
10082
9898
  var _a, _b;
10083
- const [isEditing, setIsEditing] = React.useState(false);
10084
- const onEditClick = React.useCallback(() => setIsEditing(true), [setIsEditing]);
10085
- const { onDeleteMessage, onSendMessage, message } = props;
10086
- const clientMessageId = 'clientMessageId' in message ? message.clientMessageId : undefined;
10087
- const content = 'content' in message ? message.content : undefined;
10088
- const onRemoveClick = React.useCallback(() => {
10089
- if (onDeleteMessage && message.messageId) {
10090
- onDeleteMessage(message.messageId);
10091
- }
10092
- // when fail to send, message does not have message id, delete message using clientMessageId
10093
- else if (onDeleteMessage && message.messageType === 'chat' && clientMessageId) {
10094
- onDeleteMessage(clientMessageId);
10095
- }
10096
- }, [onDeleteMessage, message.messageId, message.messageType, clientMessageId]);
10097
- const onResendClick = React.useCallback(() => {
10098
- onDeleteMessage && clientMessageId && onDeleteMessage(clientMessageId);
10099
- onSendMessage && onSendMessage(content !== undefined ? content : '');
10100
- }, [clientMessageId, content, onSendMessage, onDeleteMessage]);
10101
- if (isEditing && message.messageType === 'chat') {
10102
- return (React__default["default"].createElement(ChatMessageComponentAsEditBox, { message: message, strings: props.strings, onSubmit: (text, metadata, options) => __awaiter$w(void 0, void 0, void 0, function* () {
10103
- props.onUpdateMessage &&
10104
- message.messageId &&
10105
- (yield props.onUpdateMessage(message.messageId, text, metadata, options));
10106
- setIsEditing(false);
10107
- }), onCancel: (messageId) => {
10108
- props.onCancelEditMessage && props.onCancelEditMessage(messageId);
10109
- setIsEditing(false);
10110
- },
10111
- /* @conditional-compile-remove(mention) */
10112
- mentionLookupOptions: (_a = props.mentionOptions) === null || _a === void 0 ? void 0 : _a.lookupOptions }));
10113
- }
10114
- else {
10115
- return (React__default["default"].createElement(ChatMessageComponentAsMessageBubble, Object.assign({}, props, { onRemoveClick: onRemoveClick, onEditClick: onEditClick, onResendClick: onResendClick, onRenderAvatar: props.onRenderAvatar,
10116
- /* @conditional-compile-remove(date-time-customization) */
10117
- onDisplayDateTimeString: props.onDisplayDateTimeString, strings: props.strings,
10118
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10119
- onFetchAttachments: props.onFetchAttachments,
10120
- /* @conditional-compile-remove(image-gallery) */
10121
- onInlineImageClicked: props.onInlineImageClicked,
10122
- /* @conditional-compile-remove(image-gallery) */
10123
- attachmentsMap: props.attachmentsMap,
10124
- /* @conditional-compile-remove(mention) */
10125
- mentionDisplayOptions: (_b = props.mentionOptions) === null || _b === void 0 ? void 0 : _b.displayOptions })));
10126
- }
10127
- };
10128
-
10129
- // Copyright (c) Microsoft Corporation.
10130
- /**
10131
- * A utility hook for providing the width of a parent element.
10132
- * Returns updated width if parent/window resizes.
10133
- * @param containerRef - Ref of a parent element whose width will be returned.
10134
- * @internal
10135
- */
10136
- const _useContainerWidth = (containerRef) => {
10137
- const [width, setWidth] = React.useState(undefined);
10138
- const observer = React.useRef(new ResizeObserver((entries) => {
10139
- const { width } = entries[0].contentRect;
10140
- setWidth(width);
10141
- }));
10142
- React.useEffect(() => {
10143
- if (containerRef.current) {
10144
- observer.current.observe(containerRef.current);
10145
- }
10146
- const currentObserver = observer.current;
10147
- return () => {
10148
- currentObserver.disconnect();
9899
+ const theme = react.useTheme();
9900
+ const messageReadByCount = (_a = props.messageReadBy) === null || _a === void 0 ? void 0 : _a.length;
9901
+ const sortedMessageReadyByList = [...((_b = props.messageReadBy) !== null && _b !== void 0 ? _b : [])].sort((a, b) => a.displayName.localeCompare(b.displayName));
9902
+ const messageReadByList = sortedMessageReadyByList === null || sortedMessageReadyByList === void 0 ? void 0 : sortedMessageReadyByList.map((person) => {
9903
+ const personaOptions = {
9904
+ hidePersonaDetails: true,
9905
+ size: react.PersonaSize.size24,
9906
+ text: person.displayName,
9907
+ showOverflowTooltip: false,
9908
+ styles: {
9909
+ root: {
9910
+ margin: '0.25rem'
9911
+ }
9912
+ }
10149
9913
  };
10150
- }, [containerRef, observer]);
10151
- return width;
10152
- };
10153
- /**
10154
- * A utility hook for providing the height of a parent element.
10155
- * Returns updated height if parent/window resizes.
10156
- * @param containerRef - Ref of a parent element whose height will be returned.
10157
- * @internal
10158
- */
10159
- const _useContainerHeight = (containerRef) => {
10160
- const [height, setHeight] = React.useState(undefined);
10161
- const observer = React.useRef(new ResizeObserver((entries) => {
10162
- const { height } = entries[0].contentRect;
10163
- setHeight(height);
10164
- }));
10165
- React.useEffect(() => {
10166
- if (containerRef.current) {
10167
- observer.current.observe(containerRef.current);
10168
- }
10169
- const currentObserver = observer.current;
10170
- return () => {
10171
- currentObserver.disconnect();
9914
+ const { onRenderAvatar } = props;
9915
+ return {
9916
+ 'data-ui-id': 'chat-composite-message-contextual-menu-read-name-list-item',
9917
+ key: person.displayName,
9918
+ text: person.displayName,
9919
+ itemProps: { styles: props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined },
9920
+ onRenderIcon: () => { var _a; return onRenderAvatar ? onRenderAvatar((_a = person.id) !== null && _a !== void 0 ? _a : '', personaOptions) : React__default["default"].createElement(react.Persona, Object.assign({}, personaOptions)); },
9921
+ iconProps: {
9922
+ styles: menuIconStyleSet
9923
+ }
10172
9924
  };
10173
- }, [containerRef, observer]);
10174
- return height;
9925
+ });
9926
+ const menuItems = React.useMemo(() => {
9927
+ const items = [
9928
+ {
9929
+ key: 'Edit',
9930
+ 'data-ui-id': 'chat-composite-message-contextual-menu-edit-action',
9931
+ text: props.strings.editMessage,
9932
+ itemProps: {
9933
+ styles: props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined
9934
+ },
9935
+ iconProps: { iconName: 'MessageEdit', styles: menuIconStyleSet },
9936
+ onClick: props.onEditClick
9937
+ },
9938
+ {
9939
+ key: 'Remove',
9940
+ text: props.strings.removeMessage,
9941
+ itemProps: { styles: props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined },
9942
+ iconProps: {
9943
+ iconName: 'MessageRemove',
9944
+ styles: menuIconStyleSet
9945
+ },
9946
+ onClick: props.onRemoveClick
9947
+ }
9948
+ ];
9949
+ // only show read by x of x if more than 3 participants in total including myself
9950
+ // TODO: change strings.messageReadCount to be required if we can fallback to our own en-us strings for anything that Contoso doesn't provide
9951
+ if (props.remoteParticipantsCount &&
9952
+ messageReadByCount !== undefined &&
9953
+ props.remoteParticipantsCount >= 2 &&
9954
+ props.showMessageStatus &&
9955
+ props.strings.messageReadCount &&
9956
+ props.messageStatus !== 'failed') {
9957
+ items.push({
9958
+ key: 'Read Count',
9959
+ 'data-ui-id': 'chat-composite-message-contextual-menu-read-info',
9960
+ text: _formatString(props.strings.messageReadCount, {
9961
+ messageReadByCount: `${messageReadByCount}`,
9962
+ remoteParticipantsCount: `${props.remoteParticipantsCount}`
9963
+ }),
9964
+ itemProps: {
9965
+ styles: react.concatStyleSets({
9966
+ linkContent: {
9967
+ color: messageReadByCount > 0 ? theme.palette.neutralPrimary : theme.palette.neutralTertiary
9968
+ },
9969
+ root: {
9970
+ borderTop: `1px solid ${theme.palette.neutralLighter}`
9971
+ }
9972
+ }, props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined)
9973
+ },
9974
+ calloutProps: preventUnwantedDismissProps,
9975
+ subMenuProps: {
9976
+ items: messageReadByList !== null && messageReadByList !== void 0 ? messageReadByList : [],
9977
+ calloutProps: preventUnwantedDismissProps,
9978
+ styles: react.concatStyleSets({
9979
+ root: {
9980
+ maxWidth: _pxToRem(320),
9981
+ span: {
9982
+ overflow: 'hidden',
9983
+ textOverflow: 'ellipsis'
9984
+ }
9985
+ }
9986
+ })
9987
+ },
9988
+ iconProps: {
9989
+ iconName: 'MessageSeen',
9990
+ styles: {
9991
+ root: {
9992
+ color: messageReadByCount > 0 ? theme.palette.themeDarkAlt : theme.palette.neutralTertiary
9993
+ }
9994
+ }
9995
+ },
9996
+ submenuIconProps: {
9997
+ iconName: 'HorizontalGalleryRightButton',
9998
+ styles: menuSubIconStyleSet
9999
+ },
10000
+ disabled: messageReadByCount <= 0
10001
+ });
10002
+ }
10003
+ else if (props.messageStatus === 'failed' && props.strings.resendMessage) {
10004
+ items.push({
10005
+ key: 'Resend',
10006
+ text: props.strings.resendMessage,
10007
+ itemProps: {
10008
+ styles: react.concatStyleSets({
10009
+ linkContent: {
10010
+ color: theme.palette.neutralPrimary
10011
+ },
10012
+ root: {
10013
+ borderTop: `1px solid ${theme.palette.neutralLighter}`
10014
+ }
10015
+ }, props.increaseFlyoutItemSize ? menuItemIncreasedSizeStyles : undefined)
10016
+ },
10017
+ calloutProps: preventUnwantedDismissProps,
10018
+ iconProps: {
10019
+ iconName: 'MessageResend',
10020
+ styles: {
10021
+ root: {
10022
+ color: theme.palette.themeDarkAlt
10023
+ }
10024
+ }
10025
+ },
10026
+ onClick: props.onResendClick
10027
+ });
10028
+ }
10029
+ return items;
10030
+ }, [
10031
+ props.strings.editMessage,
10032
+ props.strings.removeMessage,
10033
+ props.strings.messageReadCount,
10034
+ props.strings.resendMessage,
10035
+ props.messageStatus,
10036
+ props.increaseFlyoutItemSize,
10037
+ props.onEditClick,
10038
+ props.onRemoveClick,
10039
+ props.onResendClick,
10040
+ props.remoteParticipantsCount,
10041
+ props.showMessageStatus,
10042
+ messageReadByCount,
10043
+ theme.palette.neutralPrimary,
10044
+ theme.palette.neutralTertiary,
10045
+ theme.palette.neutralLighter,
10046
+ theme.palette.themeDarkAlt,
10047
+ messageReadByList
10048
+ ]);
10049
+ // gap space uses pixels
10050
+ return (React__default["default"].createElement(react.ContextualMenu, { alignTargetEdge: true, gapSpace: 2 /*px*/, isBeakVisible: false, items: menuItems, hidden: props.hidden, target: props.target, onDismiss: props.onDismiss, directionalHint: react.DirectionalHint.topRightEdge, className: chatMessageMenuStyle, calloutProps: calloutMenuProps }));
10175
10051
  };
10176
- const NARROW_WIDTH_REM = 30;
10177
- const SHORT_HEIGHT_REM = 23.75;
10178
- /**
10179
- * Utility function to determine if container width is narrow
10180
- * @param containerWidthRem container width in rem
10181
- * @returns boolean
10182
- */
10183
- const isNarrowWidth = (containerWidthRem) => containerWidthRem <= _convertRemToPx(NARROW_WIDTH_REM);
10184
10052
  /**
10185
- * Utility function to determine if container width is short
10186
- * @param containerWidthRem container height in rem
10187
- * @returns boolean
10053
+ * Similar to {@link preventDismissOnEvent}, but not prevent dismissing from scrolling, since it is causing bugs in chat thread.
10188
10054
  */
10189
- const isShortHeight = (containerHeightRem) => containerHeightRem <= _convertRemToPx(SHORT_HEIGHT_REM);
10055
+ const preventUnwantedDismissProps = {
10056
+ preventDismissOnEvent: (ev) => {
10057
+ return ev.type === 'resize';
10058
+ }
10059
+ };
10060
+ const calloutMenuProps = Object.assign(Object.assign({}, preventUnwantedDismissProps), { styles: { root: { marginRight: '3px' } } });
10190
10061
 
10191
10062
  // Copyright (c) Microsoft Corporation.
10192
- // Licensed under the MIT License.
10193
10063
  /**
10194
- * @private
10195
- *logic: Looking at message A, how do we know it's read number?
10196
- * Assumption: if user read the latest message, user has read all messages before that
10197
- * ReadReceipt behaviour: read receipt is only sent to the last message
10198
- *
10199
- * If participant read a message that is sent later than message A, then the participant has read message A
10200
- * How do we check if the message is sent later than message A?
10201
- * We compare if the messageID of the last read message is larger than or equal to the message A's id
10202
- * Because messageID is the creation timestamp of each message
10203
- * Timestamps are in epoch time so lecixographical ordering is the same as time ordering.
10064
+ * Provides the default implementation for rendering an Mention in a message thread
10065
+ * @param mention - The mention to render
10204
10066
  *
10205
- * if MessageId of B is larger than message Id of A, then B is created after A
10206
- * if the last read message is created after the message A is sent, then user should have read message A as well */
10207
- var getParticipantsWhoHaveReadMessage = (message, readReceiptsBySenderId) => {
10208
- return (Object.entries(readReceiptsBySenderId)
10209
- // Filter to only read receipts that match the message OR the participant has read a different message after this message has been created
10210
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
10211
- .filter(([_, readReceipt]) => readReceipt.lastReadMessage >= message.messageId)
10212
- // make sure the person is not removed from chat
10213
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
10214
- .filter(([_, readReceipt]) => readReceipt.displayName && readReceipt.displayName !== '')
10215
- // Map properties to useful array
10216
- .map(([id, readReceipt]) => ({ id, displayName: readReceipt.displayName })));
10067
+ * @private
10068
+ */
10069
+ const defaultOnMentionRender = (mention) => {
10070
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
10071
+ const MsftMention = 'msft-mention';
10072
+ return React__default["default"].createElement(MsftMention, { id: mention.id }, mention.displayText);
10217
10073
  };
10218
10074
 
10219
10075
  // Copyright (c) Microsoft Corporation.
10220
- // Licensed under the MIT License.
10221
- // These color records are required for createV9Theme
10222
- // For more info, check https://react.fluentui.dev/iframe.html?viewMode=docs&id=concepts-migration-from-v8-components-theme-migration--page#compatible-themes
10223
- /**
10224
- * @private
10225
- */
10226
- const grey = {
10227
- '0': '#000000',
10228
- '2': '#050505',
10229
- '4': '#0a0a0a',
10230
- '6': '#0f0f0f',
10231
- '8': '#141414',
10232
- '10': '#1a1a1a',
10233
- '12': '#1f1f1f',
10234
- '14': '#242424',
10235
- '16': '#292929',
10236
- '18': '#2e2e2e',
10237
- '20': '#333333',
10238
- '22': '#383838',
10239
- '24': '#3d3d3d',
10240
- '26': '#424242',
10241
- '28': '#474747',
10242
- '30': '#4d4d4d',
10243
- '32': '#525252',
10244
- '34': '#575757',
10245
- '36': '#5c5c5c',
10246
- '38': '#616161',
10247
- '40': '#666666',
10248
- '42': '#6b6b6b',
10249
- '44': '#707070',
10250
- '46': '#757575',
10251
- '48': '#7a7a7a',
10252
- '50': '#808080',
10253
- '52': '#858585',
10254
- '54': '#8a8a8a',
10255
- '56': '#8f8f8f',
10256
- '58': '#949494',
10257
- '60': '#999999',
10258
- '62': '#9e9e9e',
10259
- '64': '#a3a3a3',
10260
- '66': '#a8a8a8',
10261
- '68': '#adadad',
10262
- '70': '#b3b3b3',
10263
- '72': '#b8b8b8',
10264
- '74': '#bdbdbd',
10265
- '76': '#c2c2c2',
10266
- '78': '#c7c7c7',
10267
- '80': '#cccccc',
10268
- '82': '#d1d1d1',
10269
- '84': '#d6d6d6',
10270
- '86': '#dbdbdb',
10271
- '88': '#e0e0e0',
10272
- '90': '#e6e6e6',
10273
- '92': '#ebebeb',
10274
- '94': '#f0f0f0',
10275
- '96': '#f5f5f5',
10276
- '98': '#fafafa',
10277
- '100': '#ffffff'
10076
+ /** @private */
10077
+ const ChatMessageContent = (props) => {
10078
+ switch (props.message.contentType) {
10079
+ case 'text':
10080
+ return MessageContentAsText(props);
10081
+ case 'html':
10082
+ return MessageContentAsRichTextHTML(props);
10083
+ case 'richtext/html':
10084
+ return MessageContentAsRichTextHTML(props);
10085
+ default:
10086
+ console.warn('unknown message content type');
10087
+ return React__default["default"].createElement(React__default["default"].Fragment, null);
10088
+ }
10278
10089
  };
10279
- /**
10280
- * @private
10281
- */
10282
- const whiteAlpha = {
10283
- '5': 'rgba(255, 255, 255, 0.05)',
10284
- '10': 'rgba(255, 255, 255, 0.1)',
10285
- '20': 'rgba(255, 255, 255, 0.2)',
10286
- '30': 'rgba(255, 255, 255, 0.3)',
10287
- '40': 'rgba(255, 255, 255, 0.4)',
10288
- '50': 'rgba(255, 255, 255, 0.5)',
10289
- '60': 'rgba(255, 255, 255, 0.6)',
10290
- '70': 'rgba(255, 255, 255, 0.7)',
10291
- '80': 'rgba(255, 255, 255, 0.8)',
10292
- '90': 'rgba(255, 255, 255, 0.9)'
10090
+ const MessageContentWithLiveAria = (props) => {
10091
+ return (React__default["default"].createElement("div", { "data-ui-status": props.message.status, role: "text", "aria-label": props.ariaLabel },
10092
+ React__default["default"].createElement(LiveMessage, { message: props.liveMessage, ariaLive: "polite" }),
10093
+ props.content));
10293
10094
  };
10294
- /**
10295
- * @private
10296
- */
10297
- const blackAlpha = {
10298
- '5': 'rgba(0, 0, 0, 0.05)',
10299
- '10': 'rgba(0, 0, 0, 0.1)',
10300
- '20': 'rgba(0, 0, 0, 0.2)',
10301
- '30': 'rgba(0, 0, 0, 0.3)',
10302
- '40': 'rgba(0, 0, 0, 0.4)',
10303
- '50': 'rgba(0, 0, 0, 0.5)',
10304
- '60': 'rgba(0, 0, 0, 0.6)',
10305
- '70': 'rgba(0, 0, 0, 0.7)',
10306
- '80': 'rgba(0, 0, 0, 0.8)',
10307
- '90': 'rgba(0, 0, 0, 0.9)'
10095
+ const MessageContentAsRichTextHTML = (props) => {
10096
+ const {
10097
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10098
+ // message is used only in useEffect that is under teams-inline-images-and-file-sharing cc
10099
+ message,
10100
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10101
+ attachmentsMap,
10102
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10103
+ onFetchAttachments } = props;
10104
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10105
+ React.useEffect(() => {
10106
+ var _a;
10107
+ if (!attachmentsMap || !onFetchAttachments) {
10108
+ return;
10109
+ }
10110
+ const attachments = (_a = message.attachedFilesMetadata) === null || _a === void 0 ? void 0 : _a.filter((fileMetadata) => {
10111
+ return fileMetadata.attachmentType === 'inlineImage' && attachmentsMap[fileMetadata.id] === undefined;
10112
+ });
10113
+ if (attachments && attachments.length > 0) {
10114
+ onFetchAttachments(attachments, message.messageId);
10115
+ }
10116
+ }, [message.attachedFilesMetadata, message.messageId, onFetchAttachments, attachmentsMap]);
10117
+ return (React__default["default"].createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: generateLiveMessage(props), ariaLabel: messageContentAriaText(props), content: processHtmlToReact(props) }));
10118
+ };
10119
+ const MessageContentAsText = (props) => {
10120
+ return (React__default["default"].createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: generateLiveMessage(props), ariaLabel: messageContentAriaText(props), content: React__default["default"].createElement(Linkify__default["default"], { componentDecorator: (decoratedHref, decoratedText, key) => {
10121
+ return (React__default["default"].createElement(react.Link, { target: "_blank", href: decoratedHref, key: key }, decoratedText));
10122
+ } }, props.message.content) }));
10308
10123
  };
10124
+ /* @conditional-compile-remove(data-loss-prevention) */
10309
10125
  /**
10310
10126
  * @private
10311
10127
  */
10312
- const grey10Alpha = {
10313
- '5': 'rgba(26, 26, 26, 0.05)',
10314
- '10': 'rgba(26, 26, 26, 0.1)',
10315
- '20': 'rgba(26, 26, 26, 0.2)',
10316
- '30': 'rgba(26, 26, 26, 0.3)',
10317
- '40': 'rgba(26, 26, 26, 0.4)',
10318
- '50': 'rgba(26, 26, 26, 0.5)',
10319
- '60': 'rgba(26, 26, 26, 0.6)',
10320
- '70': 'rgba(26, 26, 26, 0.7)',
10321
- '80': 'rgba(26, 26, 26, 0.8)',
10322
- '90': 'rgba(26, 26, 26, 0.9)'
10128
+ const BlockedMessageContent = (props) => {
10129
+ var _a;
10130
+ const Icon = React__default["default"].createElement(react.FontIcon, { iconName: 'DataLossPreventionProhibited' });
10131
+ const blockedMessage = props.message.warningText === undefined ? props.strings.blockedWarningText : props.message.warningText;
10132
+ const blockedMessageLink = props.message.link;
10133
+ const blockedMessageLinkText = blockedMessageLink
10134
+ ? (_a = props.message.linkText) !== null && _a !== void 0 ? _a : props.strings.blockedWarningLinkText
10135
+ : '';
10136
+ const liveAuthor = props.message.mine || props.message.senderDisplayName === undefined ? '' : props.message.senderDisplayName;
10137
+ const liveBlockedWarningText = `${liveAuthor} ${blockedMessage} ${blockedMessageLinkText}`;
10138
+ return (React__default["default"].createElement(MessageContentWithLiveAria, { message: props.message, liveMessage: liveBlockedWarningText, ariaLabel: liveBlockedWarningText, content: React__default["default"].createElement(react.Stack, { horizontal: true, wrap: true },
10139
+ Icon,
10140
+ blockedMessage && React__default["default"].createElement("p", null, blockedMessage),
10141
+ blockedMessageLink && (React__default["default"].createElement(react.Link, { target: '_blank', href: blockedMessageLink }, blockedMessageLinkText))) }));
10142
+ };
10143
+ // https://stackoverflow.com/questions/28899298/extract-the-text-out-of-html-string-using-javascript
10144
+ const extractContent = (s) => {
10145
+ const span = document.createElement('span');
10146
+ span.innerHTML = s;
10147
+ return span.textContent || span.innerText;
10148
+ };
10149
+ const generateLiveMessage = (props) => {
10150
+ const liveAuthor = _formatString(props.strings.liveAuthorIntro, { author: `${props.message.senderDisplayName}` });
10151
+ return `${props.message.editedOn ? props.strings.editedTag : ''} ${props.message.mine ? '' : liveAuthor} ${extractContent(props.message.content || '')} `;
10152
+ };
10153
+ const messageContentAriaText = (props) => {
10154
+ // Strip all html tags from the content for aria.
10155
+ return props.message.content
10156
+ ? props.message.mine
10157
+ ? _formatString(props.strings.messageContentMineAriaText, {
10158
+ message: DOMPurify__default["default"].sanitize(props.message.content, { ALLOWED_TAGS: [] })
10159
+ })
10160
+ : _formatString(props.strings.messageContentAriaText, {
10161
+ author: `${props.message.senderDisplayName}`,
10162
+ message: DOMPurify__default["default"].sanitize(props.message.content, { ALLOWED_TAGS: [] })
10163
+ })
10164
+ : undefined;
10165
+ };
10166
+ const processNodeDefinitions = htmlToReact.ProcessNodeDefinitions();
10167
+ const htmlToReactParser = htmlToReact.Parser();
10168
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10169
+ const processInlineImage = (props) => ({
10170
+ // Custom <img> processing
10171
+ shouldProcessNode: (node) => {
10172
+ var _a;
10173
+ function isImageNode(file) {
10174
+ return file.attachmentType === 'inlineImage' && file.id === node.attribs.id;
10175
+ }
10176
+ // Process img node with id in attachments list
10177
+ return (node.name &&
10178
+ node.name === 'img' &&
10179
+ node.attribs &&
10180
+ node.attribs.id &&
10181
+ ((_a = props.message.attachedFilesMetadata) === null || _a === void 0 ? void 0 : _a.find(isImageNode)));
10182
+ },
10183
+ processNode: (node, children, index) => {
10184
+ node.attribs = Object.assign(Object.assign({}, node.attribs), { 'aria-label': node.attribs.name });
10185
+ // logic to check id in map/list
10186
+ if (props.attachmentsMap && node.attribs.id in props.attachmentsMap) {
10187
+ node.attribs = Object.assign(Object.assign({}, node.attribs), { src: props.attachmentsMap[node.attribs.id] });
10188
+ }
10189
+ /* @conditional-compile-remove(image-gallery) */
10190
+ const handleOnClick = () => {
10191
+ props.onInlineImageClicked && props.onInlineImageClicked(node.attribs.id);
10192
+ };
10193
+ /* @conditional-compile-remove(image-gallery) */
10194
+ return (React__default["default"].createElement("span", { "data-ui-id": node.attribs.id, onClick: handleOnClick, tabIndex: 0, role: "button", style: {
10195
+ cursor: 'pointer'
10196
+ }, onKeyDown: (e) => {
10197
+ if (e.key === 'Enter') {
10198
+ handleOnClick();
10199
+ }
10200
+ } }, processNodeDefinitions.processDefaultNode(node, children, index)));
10201
+ }
10202
+ });
10203
+ /* @conditional-compile-remove(mention) */
10204
+ const processMention = (props) => ({
10205
+ shouldProcessNode: (node) => {
10206
+ var _a;
10207
+ if ((_a = props.mentionDisplayOptions) === null || _a === void 0 ? void 0 : _a.onRenderMention) {
10208
+ // Override the handling of the <msft-mention> tag in the HTML if there's a custom renderer
10209
+ return node.name === 'msft-mention';
10210
+ }
10211
+ return false;
10212
+ },
10213
+ processNode: (node) => {
10214
+ var _a, _b, _c;
10215
+ if ((_a = props.mentionDisplayOptions) === null || _a === void 0 ? void 0 : _a.onRenderMention) {
10216
+ const { id } = node.attribs;
10217
+ const mention = {
10218
+ id: id,
10219
+ displayText: (_c = (_b = node.children[0]) === null || _b === void 0 ? void 0 : _b.data) !== null && _c !== void 0 ? _c : ''
10220
+ };
10221
+ return props.mentionDisplayOptions.onRenderMention(mention, defaultOnMentionRender);
10222
+ }
10223
+ return processNodeDefinitions.processDefaultNode;
10224
+ }
10225
+ });
10226
+ const processHtmlToReact = (props) => {
10227
+ var _a;
10228
+ const steps = [
10229
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10230
+ processInlineImage(props),
10231
+ /* @conditional-compile-remove(mention) */
10232
+ processMention(props),
10233
+ {
10234
+ // Process everything else in the default way
10235
+ shouldProcessNode: htmlToReact.IsValidNodeDefinitions.alwaysValid,
10236
+ processNode: processNodeDefinitions.processDefaultNode
10237
+ }
10238
+ ];
10239
+ return htmlToReactParser.parseWithInstructions((_a = props.message.content) !== null && _a !== void 0 ? _a : '', htmlToReact.IsValidNodeDefinitions.alwaysValid, steps);
10323
10240
  };
10241
+
10242
+ // Copyright (c) Microsoft Corporation.
10324
10243
  /**
10244
+ * Props for the Chat.Message action menu.
10245
+ * This is the 3 dots that appear when hovering over one of your own chat messages.
10246
+ *
10325
10247
  * @private
10326
10248
  */
10327
- const grey12Alpha = {
10328
- '5': 'rgba(31, 31, 31, 0.05)',
10329
- '10': 'rgba(31, 31, 31, 0.1)',
10330
- '20': 'rgba(31, 31, 31, 0.2)',
10331
- '30': 'rgba(31, 31, 31, 0.3)',
10332
- '40': 'rgba(31, 31, 31, 0.4)',
10333
- '50': 'rgba(31, 31, 31, 0.5)',
10334
- '60': 'rgba(31, 31, 31, 0.6)',
10335
- '70': 'rgba(31, 31, 31, 0.7)',
10336
- '80': 'rgba(31, 31, 31, 0.8)',
10337
- '90': 'rgba(31, 31, 31, 0.9)'
10249
+ const chatMessageActionMenuProps = (menuProps) => {
10250
+ const { ariaLabel, enabled, theme, menuExpanded } = menuProps;
10251
+ // Show the action button while the flyout is open (otherwise this will dismiss when the pointer is hovered over the flyout)
10252
+ const showActionMenu = enabled || menuExpanded;
10253
+ const actionMenuProps = {
10254
+ children: (React__default["default"].createElement("div", { tabIndex: showActionMenu ? 0 : undefined, key: "menuButton", "data-ui-id": "chat-composite-message-action-icon", ref: menuProps.menuButtonRef, onClick: showActionMenu ? () => menuProps.onActionButtonClick() : undefined, style: { margin: showActionMenu ? '1px' : 0, minHeight: showActionMenu ? undefined : '30px' }, role: "button", "aria-label": showActionMenu ? ariaLabel : undefined, "aria-haspopup": showActionMenu, "aria-expanded": menuExpanded, onKeyDown: (e) => {
10255
+ // simulate <button> tag behavior
10256
+ if (showActionMenu && (e.key === 'Enter' || e.key === ' ')) {
10257
+ menuProps.onActionButtonClick();
10258
+ }
10259
+ } }, showActionMenu ? (React__default["default"].createElement(react.Icon, { iconName: "ChatMessageOptions", "aria-label": ariaLabel, styles: iconWrapperStyle(theme, menuExpanded) })) : undefined))
10260
+ };
10261
+ return actionMenuProps;
10338
10262
  };
10339
10263
 
10340
10264
  // Copyright (c) Microsoft Corporation.
10341
- // These mappings are required for createV9Theme
10342
- // For more info, check https://react.fluentui.dev/iframe.html?viewMode=docs&id=concepts-migration-from-v8-components-theme-migration--page#compatible-themes
10343
- /**
10344
- * Creates v9 color tokens from a v8 palette.
10265
+ // Licensed under the MIT License.
10266
+ var __awaiter$y = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
10267
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10268
+ return new (P || (P = Promise))(function (resolve, reject) {
10269
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10270
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10271
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10272
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10273
+ });
10274
+ };
10275
+ const fileDownloadCardsStyle = {
10276
+ marginTop: '0.25rem'
10277
+ };
10278
+ const actionIconStyle = { height: '1rem' };
10279
+ /**
10280
+ * @internal
10345
10281
  */
10346
- const mapAliasColors = (palette, inverted) => {
10347
- return {
10348
- colorNeutralForeground1: palette.neutralPrimary,
10349
- colorNeutralForeground1Hover: palette.neutralPrimary,
10350
- colorNeutralForeground1Pressed: palette.neutralPrimary,
10351
- colorNeutralForeground1Selected: palette.neutralPrimary,
10352
- colorNeutralForeground2: palette.neutralSecondary,
10353
- colorNeutralForeground2Hover: palette.neutralPrimary,
10354
- colorNeutralForeground2Pressed: palette.neutralPrimary,
10355
- colorNeutralForeground2Selected: palette.neutralPrimary,
10356
- colorNeutralForeground2BrandHover: palette.themePrimary,
10357
- colorNeutralForeground2BrandPressed: palette.themeDarkAlt,
10358
- colorNeutralForeground2BrandSelected: palette.themePrimary,
10359
- colorNeutralForeground3: palette.neutralTertiary,
10360
- colorNeutralForeground3Hover: palette.neutralSecondary,
10361
- colorNeutralForeground3Pressed: palette.neutralSecondary,
10362
- colorNeutralForeground3Selected: palette.neutralSecondary,
10363
- colorNeutralForeground3BrandHover: palette.themePrimary,
10364
- colorNeutralForeground3BrandPressed: palette.themeDarkAlt,
10365
- colorNeutralForeground3BrandSelected: palette.themePrimary,
10366
- colorNeutralForeground4: palette.neutralQuaternary,
10367
- colorNeutralForegroundDisabled: palette.neutralTertiaryAlt,
10368
- colorNeutralForegroundInvertedDisabled: whiteAlpha[40],
10369
- colorBrandForegroundLink: palette.themeDarkAlt,
10370
- colorBrandForegroundLinkHover: palette.themeDark,
10371
- colorBrandForegroundLinkPressed: palette.themeDarker,
10372
- colorBrandForegroundLinkSelected: palette.themeDarkAlt,
10373
- colorNeutralForeground2Link: palette.neutralSecondary,
10374
- colorNeutralForeground2LinkHover: palette.neutralPrimary,
10375
- colorNeutralForeground2LinkPressed: palette.neutralPrimary,
10376
- colorNeutralForeground2LinkSelected: palette.neutralPrimary,
10377
- colorCompoundBrandForeground1: palette.themePrimary,
10378
- colorCompoundBrandForeground1Hover: palette.themeDarkAlt,
10379
- colorCompoundBrandForeground1Pressed: palette.themeDark,
10380
- colorBrandForeground1: palette.themePrimary,
10381
- colorBrandForeground2: palette.themeDarkAlt,
10382
- colorBrandForeground2Hover: palette.themeDarkAlt,
10383
- colorBrandForeground2Pressed: palette.themeDarkAlt,
10384
- colorNeutralForeground1Static: palette.neutralPrimary,
10385
- colorNeutralForegroundInverted: palette.white,
10386
- colorNeutralForegroundInvertedHover: palette.white,
10387
- colorNeutralForegroundInvertedPressed: palette.white,
10388
- colorNeutralForegroundInvertedSelected: palette.white,
10389
- colorNeutralForegroundOnBrand: palette.white,
10390
- colorNeutralForegroundStaticInverted: palette.white,
10391
- colorNeutralForegroundInvertedLink: palette.white,
10392
- colorNeutralForegroundInvertedLinkHover: palette.white,
10393
- colorNeutralForegroundInvertedLinkPressed: palette.white,
10394
- colorNeutralForegroundInvertedLinkSelected: palette.white,
10395
- colorNeutralForegroundInverted2: palette.white,
10396
- colorBrandForegroundInverted: palette.themeSecondary,
10397
- colorBrandForegroundInvertedHover: palette.themeTertiary,
10398
- colorBrandForegroundInvertedPressed: palette.themeSecondary,
10399
- colorBrandForegroundOnLight: palette.themePrimary,
10400
- colorBrandForegroundOnLightHover: palette.themeDarkAlt,
10401
- colorBrandForegroundOnLightPressed: palette.themeDark,
10402
- colorBrandForegroundOnLightSelected: palette.themeDark,
10403
- colorNeutralBackground1: palette.white,
10404
- colorNeutralBackground1Hover: palette.neutralLighter,
10405
- colorNeutralBackground1Pressed: palette.neutralQuaternaryAlt,
10406
- colorNeutralBackground1Selected: palette.neutralLight,
10407
- colorNeutralBackground2: palette.neutralLighterAlt,
10408
- colorNeutralBackground2Hover: palette.neutralLighter,
10409
- colorNeutralBackground2Pressed: palette.neutralQuaternaryAlt,
10410
- colorNeutralBackground2Selected: palette.neutralLight,
10411
- colorNeutralBackground3: palette.neutralLighter,
10412
- colorNeutralBackground3Hover: palette.neutralLight,
10413
- colorNeutralBackground3Pressed: palette.neutralQuaternary,
10414
- colorNeutralBackground3Selected: palette.neutralQuaternaryAlt,
10415
- colorNeutralBackground4: palette.neutralLighter,
10416
- colorNeutralBackground4Hover: palette.neutralLighterAlt,
10417
- colorNeutralBackground4Pressed: palette.neutralLighter,
10418
- colorNeutralBackground4Selected: palette.white,
10419
- colorNeutralBackground5: palette.neutralLight,
10420
- colorNeutralBackground5Hover: palette.neutralLighter,
10421
- colorNeutralBackground5Pressed: palette.neutralLighter,
10422
- colorNeutralBackground5Selected: palette.neutralLighterAlt,
10423
- colorNeutralBackground6: palette.neutralLight,
10424
- colorNeutralBackgroundStatic: grey[20],
10425
- colorNeutralBackgroundInverted: palette.neutralSecondary,
10426
- colorNeutralBackgroundAlpha: inverted ? grey10Alpha[50] : whiteAlpha[50],
10427
- colorNeutralBackgroundAlpha2: inverted ? grey12Alpha[70] : whiteAlpha[80],
10428
- colorSubtleBackground: 'transparent',
10429
- colorSubtleBackgroundHover: palette.neutralLighter,
10430
- colorSubtleBackgroundPressed: palette.neutralQuaternaryAlt,
10431
- colorSubtleBackgroundSelected: palette.neutralLight,
10432
- colorSubtleBackgroundLightAlphaHover: inverted ? whiteAlpha[10] : whiteAlpha[80],
10433
- colorSubtleBackgroundLightAlphaPressed: inverted ? whiteAlpha[5] : whiteAlpha[50],
10434
- colorSubtleBackgroundLightAlphaSelected: 'transparent',
10435
- colorSubtleBackgroundInverted: 'transparent',
10436
- colorSubtleBackgroundInvertedHover: blackAlpha[10],
10437
- colorSubtleBackgroundInvertedPressed: blackAlpha[30],
10438
- colorSubtleBackgroundInvertedSelected: blackAlpha[20],
10439
- colorTransparentBackground: 'transparent',
10440
- colorTransparentBackgroundHover: 'transparent',
10441
- colorTransparentBackgroundPressed: 'transparent',
10442
- colorTransparentBackgroundSelected: 'transparent',
10443
- colorNeutralBackgroundDisabled: palette.neutralLighter,
10444
- colorNeutralBackgroundInvertedDisabled: whiteAlpha[10],
10445
- colorNeutralStencil1: palette.neutralLight,
10446
- colorNeutralStencil2: palette.neutralLighterAlt,
10447
- colorNeutralStencil1Alpha: inverted ? whiteAlpha[10] : blackAlpha[10],
10448
- colorNeutralStencil2Alpha: inverted ? whiteAlpha[5] : blackAlpha[5],
10449
- colorBackgroundOverlay: blackAlpha[40],
10450
- colorScrollbarOverlay: blackAlpha[50],
10451
- colorBrandBackground: palette.themePrimary,
10452
- colorBrandBackgroundHover: palette.themeDarkAlt,
10453
- colorBrandBackgroundPressed: palette.themeDarker,
10454
- colorBrandBackgroundSelected: palette.themeDark,
10455
- colorCompoundBrandBackground: palette.themePrimary,
10456
- colorCompoundBrandBackgroundHover: palette.themeDarkAlt,
10457
- colorCompoundBrandBackgroundPressed: palette.themeDark,
10458
- colorBrandBackgroundStatic: palette.themePrimary,
10459
- colorBrandBackground2: palette.themeLighterAlt,
10460
- colorBrandBackground2Hover: palette.themeLighterAlt,
10461
- colorBrandBackground2Pressed: palette.themeLighterAlt,
10462
- colorBrandBackgroundInverted: palette.white,
10463
- colorBrandBackgroundInvertedHover: palette.themeLighterAlt,
10464
- colorBrandBackgroundInvertedPressed: palette.themeLight,
10465
- colorBrandBackgroundInvertedSelected: palette.themeLighter,
10466
- colorNeutralStrokeAccessible: palette.neutralSecondary,
10467
- colorNeutralStrokeAccessibleHover: palette.neutralSecondary,
10468
- colorNeutralStrokeAccessiblePressed: palette.neutralSecondary,
10469
- colorNeutralStrokeAccessibleSelected: palette.themePrimary,
10470
- colorNeutralStroke1: palette.neutralQuaternary,
10471
- colorNeutralStroke1Hover: palette.neutralTertiaryAlt,
10472
- colorNeutralStroke1Pressed: palette.neutralTertiaryAlt,
10473
- colorNeutralStroke1Selected: palette.neutralTertiaryAlt,
10474
- colorNeutralStroke2: palette.neutralQuaternaryAlt,
10475
- colorNeutralStroke3: palette.neutralLighter,
10476
- colorNeutralStrokeSubtle: palette.neutralQuaternaryAlt,
10477
- colorNeutralStrokeOnBrand: palette.white,
10478
- colorNeutralStrokeOnBrand2: palette.white,
10479
- colorNeutralStrokeOnBrand2Hover: palette.white,
10480
- colorNeutralStrokeOnBrand2Pressed: palette.white,
10481
- colorNeutralStrokeOnBrand2Selected: palette.white,
10482
- colorBrandStroke1: palette.themePrimary,
10483
- colorBrandStroke2: palette.themeLight,
10484
- colorBrandStroke2Hover: palette.themeLight,
10485
- colorBrandStroke2Pressed: palette.themeLight,
10486
- colorBrandStroke2Contrast: palette.themeLight,
10487
- colorCompoundBrandStroke: palette.themePrimary,
10488
- colorCompoundBrandStrokeHover: palette.themeDarkAlt,
10489
- colorCompoundBrandStrokePressed: palette.themeDark,
10490
- colorNeutralStrokeDisabled: palette.neutralQuaternaryAlt,
10491
- colorNeutralStrokeInvertedDisabled: whiteAlpha[40],
10492
- colorTransparentStroke: 'transparent',
10493
- colorTransparentStrokeInteractive: 'transparent',
10494
- colorTransparentStrokeDisabled: 'transparent',
10495
- colorNeutralStrokeAlpha: inverted ? whiteAlpha[10] : blackAlpha[5],
10496
- colorNeutralStrokeAlpha2: whiteAlpha[20],
10497
- colorStrokeFocus1: palette.white,
10498
- colorStrokeFocus2: palette.black,
10499
- colorNeutralShadowAmbient: 'rgba(0,0,0,0.12)',
10500
- colorNeutralShadowKey: 'rgba(0,0,0,0.14)',
10501
- colorNeutralShadowAmbientLighter: 'rgba(0,0,0,0.06)',
10502
- colorNeutralShadowKeyLighter: 'rgba(0,0,0,0.07)',
10503
- colorNeutralShadowAmbientDarker: 'rgba(0,0,0,0.20)',
10504
- colorNeutralShadowKeyDarker: 'rgba(0,0,0,0.24)',
10505
- colorBrandShadowAmbient: 'rgba(0,0,0,0.30)',
10506
- colorBrandShadowKey: 'rgba(0,0,0,0.25)'
10507
- };
10282
+ const _FileDownloadCards = (props) => {
10283
+ var _a, _b;
10284
+ const { userId, fileMetadata } = props;
10285
+ const [showSpinner, setShowSpinner] = React.useState(false);
10286
+ const localeStrings = useLocaleStringsTrampoline();
10287
+ const downloadFileButtonString = React.useMemo(() => () => {
10288
+ var _a, _b;
10289
+ return (_b = (_a = props.strings) === null || _a === void 0 ? void 0 : _a.downloadFile) !== null && _b !== void 0 ? _b : localeStrings.downloadFile;
10290
+ }, [(_a = props.strings) === null || _a === void 0 ? void 0 : _a.downloadFile, localeStrings.downloadFile]);
10291
+ const isFileSharingAttachment = React.useCallback((attachment) => {
10292
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10293
+ return attachment.attachmentType === 'fileSharing';
10294
+ }, []);
10295
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10296
+ const isShowDownloadIcon = React.useCallback((attachment) => {
10297
+ var _a;
10298
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10299
+ return attachment.attachmentType === 'fileSharing' && ((_a = attachment.payload) === null || _a === void 0 ? void 0 : _a.teamsFileAttachment) !== 'true';
10300
+ }, []);
10301
+ const fileCardGroupDescription = React.useMemo(() => () => {
10302
+ var _a, _b;
10303
+ const fileGroupLocaleString = (_b = (_a = props.strings) === null || _a === void 0 ? void 0 : _a.fileCardGroupMessage) !== null && _b !== void 0 ? _b : localeStrings.fileCardGroupMessage;
10304
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10305
+ return _formatString(fileGroupLocaleString, {
10306
+ fileCount: `${fileMetadata.filter(isFileSharingAttachment).length}`
10307
+ });
10308
+ }, [(_b = props.strings) === null || _b === void 0 ? void 0 : _b.fileCardGroupMessage, localeStrings.fileCardGroupMessage, fileMetadata, isFileSharingAttachment]);
10309
+ const fileDownloadHandler = React.useCallback((userId, file) => __awaiter$y(void 0, void 0, void 0, function* () {
10310
+ if (!props.downloadHandler) {
10311
+ window.open(file.url, '_blank', 'noopener,noreferrer');
10312
+ }
10313
+ else {
10314
+ setShowSpinner(true);
10315
+ try {
10316
+ const response = yield props.downloadHandler(userId, file);
10317
+ setShowSpinner(false);
10318
+ if (response instanceof URL) {
10319
+ window.open(response.toString(), '_blank', 'noopener,noreferrer');
10320
+ }
10321
+ else {
10322
+ props.onDownloadErrorMessage && props.onDownloadErrorMessage(response.errorMessage);
10323
+ }
10324
+ }
10325
+ finally {
10326
+ setShowSpinner(false);
10327
+ }
10328
+ }
10329
+ }), [props]);
10330
+ if (!fileMetadata ||
10331
+ fileMetadata.length === 0 ||
10332
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */ !fileMetadata.some(isFileSharingAttachment)) {
10333
+ return React__default["default"].createElement(React__default["default"].Fragment, null);
10334
+ }
10335
+ return (React__default["default"].createElement("div", { style: fileDownloadCardsStyle, "data-ui-id": "file-download-card-group" },
10336
+ React__default["default"].createElement(_FileCardGroup, { ariaLabel: fileCardGroupDescription() }, fileMetadata &&
10337
+ fileMetadata
10338
+ .filter((attachment) => {
10339
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10340
+ return isFileSharingAttachment(attachment);
10341
+ })
10342
+ .map((file) => (React__default["default"].createElement(react.TooltipHost, { content: downloadFileButtonString(), key: file.name },
10343
+ React__default["default"].createElement(_FileCard, { fileName: file.name, key: file.name, fileExtension: file.extension, actionIcon: showSpinner ? (React__default["default"].createElement(react.Spinner, { size: react.SpinnerSize.medium, "aria-live": 'polite', role: 'status' })) : /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10344
+ isShowDownloadIcon(file) ? (React__default["default"].createElement(react.IconButton, { className: iconButtonClassName, ariaLabel: downloadFileButtonString() },
10345
+ React__default["default"].createElement(DownloadIconTrampoline, null))) : undefined, actionHandler: () => fileDownloadHandler(userId, file) })))))));
10346
+ };
10347
+ /**
10348
+ * @private
10349
+ */
10350
+ const DownloadIconTrampoline = () => {
10351
+ // @conditional-compile-remove(file-sharing)
10352
+ return React__default["default"].createElement(react.Icon, { "data-ui-id": "file-download-card-download-icon", iconName: "DownloadFile", style: actionIconStyle });
10353
+ };
10354
+ const useLocaleStringsTrampoline = () => {
10355
+ /* @conditional-compile-remove(file-sharing) @conditional-compile-remove(teams-inline-images-and-file-sharing)*/
10356
+ return useLocale$1().strings.messageThread;
10357
+ };
10358
+
10359
+ // Copyright (c) Microsoft Corporation.
10360
+ // Licensed under the MIT License.
10361
+ var __awaiter$x = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
10362
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10363
+ return new (P || (P = Promise))(function (resolve, reject) {
10364
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10365
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10366
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10367
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10368
+ });
10369
+ };
10370
+ const generateDefaultTimestamp = (createdOn, showDate, strings) => {
10371
+ const formattedTimestamp = showDate
10372
+ ? formatTimestampForChatMessage(createdOn, new Date(), strings)
10373
+ : formatTimeForChatMessage(createdOn);
10374
+ return formattedTimestamp;
10375
+ };
10376
+ // onDisplayDateTimeString from props overwrite onDisplayDateTimeString from locale
10377
+ const generateCustomizedTimestamp = (props, createdOn, locale) => {
10378
+ /* @conditional-compile-remove(date-time-customization) */
10379
+ return props.onDisplayDateTimeString
10380
+ ? props.onDisplayDateTimeString(createdOn)
10381
+ : locale.onDisplayDateTimeString
10382
+ ? locale.onDisplayDateTimeString(createdOn)
10383
+ : '';
10384
+ };
10385
+ /** @private */
10386
+ const MessageBubble = (props) => {
10387
+ var _a;
10388
+ const ids = useIdentifiers();
10389
+ const theme = useTheme();
10390
+ const locale = useLocale$1();
10391
+ const { userId, message, onRemoveClick, onResendClick, disableEditing, showDate, messageContainerStyle, strings, onEditClick, remoteParticipantsCount = 0, onRenderAvatar, showMessageStatus, messageStatus, fileDownloadHandler,
10392
+ /* @conditional-compile-remove(image-gallery) */
10393
+ onInlineImageClicked, shouldOverlapAvatarAndMessage } = props;
10394
+ const defaultTimeStamp = message.createdOn
10395
+ ? generateDefaultTimestamp(message.createdOn, showDate, strings)
10396
+ : undefined;
10397
+ const customTimestamp = message.createdOn ? generateCustomizedTimestamp(props, message.createdOn, locale) : '';
10398
+ const formattedTimestamp = customTimestamp || defaultTimeStamp;
10399
+ // Track if the action menu was opened by touch - if so we increase the touch targets for the items
10400
+ const [wasInteractionByTouch, setWasInteractionByTouch] = React.useState(false);
10401
+ // `focused` state is used for show/hide actionMenu
10402
+ const [focused, setFocused] = React__default["default"].useState(false);
10403
+ // The chat message action flyout should target the Chat.Message action menu if clicked,
10404
+ // or target the chat message if opened via touch press.
10405
+ // Undefined indicates the flyout menu should not be being shown.
10406
+ const messageRef = React.useRef(null);
10407
+ const messageActionButtonRef = React.useRef(null);
10408
+ const [chatMessageActionFlyoutTarget, setChatMessageActionFlyoutTarget] = React.useState(undefined);
10409
+ const chatActionsEnabled = !disableEditing &&
10410
+ message.status !== 'sending' &&
10411
+ !!message.mine &&
10412
+ /* @conditional-compile-remove(data-loss-prevention) */ message.messageType !== 'blocked';
10413
+ const [messageReadBy, setMessageReadBy] = React.useState([]);
10414
+ const actionMenuProps = chatMessageActionMenuProps({
10415
+ ariaLabel: (_a = strings.actionMenuMoreOptions) !== null && _a !== void 0 ? _a : '',
10416
+ enabled: chatActionsEnabled,
10417
+ menuButtonRef: messageActionButtonRef,
10418
+ menuExpanded: chatMessageActionFlyoutTarget === messageActionButtonRef,
10419
+ onActionButtonClick: () => {
10420
+ if (message.messageType === 'chat') {
10421
+ props.onActionButtonClick(message, setMessageReadBy);
10422
+ setChatMessageActionFlyoutTarget(messageActionButtonRef);
10423
+ }
10424
+ },
10425
+ theme
10426
+ });
10427
+ const onActionFlyoutDismiss = React.useCallback(() => {
10428
+ // When the flyout dismiss is called, since we control if the action flyout is visible
10429
+ // or not we need to set the target to undefined here to actually hide the action flyout
10430
+ setChatMessageActionFlyoutTarget(undefined);
10431
+ }, [setChatMessageActionFlyoutTarget]);
10432
+ const defaultOnRenderFileDownloads = React.useCallback(() => (React__default["default"].createElement(_FileDownloadCards, { userId: userId, fileMetadata: message['attachedFilesMetadata'] || [], downloadHandler: fileDownloadHandler,
10433
+ /* @conditional-compile-remove(file-sharing) @conditional-compile-remove(teams-inline-images-and-file-sharing)*/
10434
+ strings: { downloadFile: strings.downloadFile, fileCardGroupMessage: strings.fileCardGroupMessage } })), [
10435
+ userId,
10436
+ message,
10437
+ /* @conditional-compile-remove(file-sharing) @conditional-compile-remove(teams-inline-images-and-file-sharing)*/
10438
+ strings,
10439
+ fileDownloadHandler
10440
+ ]);
10441
+ const editedOn = 'editedOn' in message ? message.editedOn : undefined;
10442
+ const getMessageDetails = React.useCallback(() => {
10443
+ if (messageStatus === 'failed') {
10444
+ return React__default["default"].createElement("div", { className: chatMessageFailedTagStyle(theme) }, strings.failToSendTag);
10445
+ }
10446
+ else if (message.messageType === 'chat' && editedOn) {
10447
+ return React__default["default"].createElement("div", { className: chatMessageEditedTagStyle(theme) }, strings.editedTag);
10448
+ }
10449
+ return undefined;
10450
+ }, [editedOn, message.messageType, messageStatus, strings.editedTag, strings.failToSendTag, theme]);
10451
+ /* @conditional-compile-remove(image-gallery) */
10452
+ const handleOnInlineImageClicked = React.useCallback((attachmentId) => __awaiter$x(void 0, void 0, void 0, function* () {
10453
+ if (onInlineImageClicked === undefined) {
10454
+ return;
10455
+ }
10456
+ yield onInlineImageClicked(attachmentId, message.messageId);
10457
+ }), [message, onInlineImageClicked]);
10458
+ const getContent = React.useCallback(() => {
10459
+ /* @conditional-compile-remove(data-loss-prevention) */
10460
+ if (message.messageType === 'blocked') {
10461
+ return (React__default["default"].createElement("div", { tabIndex: 0 },
10462
+ React__default["default"].createElement(BlockedMessageContent, { message: message, strings: strings })));
10463
+ }
10464
+ return (React__default["default"].createElement("div", { tabIndex: 0, className: "ui-chat__message__content" },
10465
+ React__default["default"].createElement(ChatMessageContent, { message: message, strings: strings,
10466
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10467
+ onFetchAttachments: props.onFetchAttachments,
10468
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10469
+ attachmentsMap: props.attachmentsMap,
10470
+ /* @conditional-compile-remove(mention) */
10471
+ mentionDisplayOptions: props.mentionDisplayOptions,
10472
+ /* @conditional-compile-remove(image-gallery) */
10473
+ onInlineImageClicked: handleOnInlineImageClicked }),
10474
+ props.onRenderFileDownloads ? props.onRenderFileDownloads(userId, message) : defaultOnRenderFileDownloads()));
10475
+ }, [
10476
+ defaultOnRenderFileDownloads,
10477
+ message,
10478
+ props,
10479
+ strings,
10480
+ userId,
10481
+ /* @conditional-compile-remove(image-gallery) */
10482
+ handleOnInlineImageClicked
10483
+ ]);
10484
+ const isBlockedMessage = /* @conditional-compile-remove(data-loss-prevention) */ message.messageType === 'blocked';
10485
+ const chatMyMessageStyles = useChatMyMessageStyles();
10486
+ const chatMessageCommonStyles = useChatMessageCommonStyles();
10487
+ const chatMessageStyles = useChatMessageStyles();
10488
+ const chatItemMessageContainerClassName = reactComponents.mergeClasses(
10489
+ // messageContainerStyle used in className and style prop as style prop can't handle CSS selectors
10490
+ chatMessageStyles.body, isBlockedMessage
10491
+ ? chatMessageCommonStyles.blocked
10492
+ : props.message.status === 'failed'
10493
+ ? chatMessageCommonStyles.failed
10494
+ : undefined, shouldOverlapAvatarAndMessage ? chatMessageStyles.avatarOverlap : chatMessageStyles.avatarNoOverlap, message.attached === 'top' || message.attached === false
10495
+ ? chatMessageStyles.bodyWithAvatar
10496
+ : chatMessageStyles.bodyWithoutAvatar, react.mergeStyles(messageContainerStyle));
10497
+ const attached = message.attached === true ? 'center' : message.attached === 'bottom' ? 'bottom' : 'top';
10498
+ const chatMessage = (React__default["default"].createElement(React__default["default"].Fragment, null,
10499
+ React__default["default"].createElement("div", { key: props.message.messageId }, message.mine ? (React__default["default"].createElement(reactChat.ChatMyMessage, { attached: attached, key: props.message.messageId, body: {
10500
+ // messageContainerStyle used in className and style prop as style prop can't handle CSS selectors
10501
+ className: reactComponents.mergeClasses(chatMyMessageStyles.body, isBlockedMessage
10502
+ ? chatMessageCommonStyles.blocked
10503
+ : props.message.status === 'failed'
10504
+ ? chatMessageCommonStyles.failed
10505
+ : undefined, attached !== 'top' ? chatMyMessageStyles.bodyAttached : undefined, react.mergeStyles(messageContainerStyle)),
10506
+ style: Object.assign({}, createStyleFromV8Style(messageContainerStyle)),
10507
+ ref: messageRef
10508
+ }, root: {
10509
+ className: chatMyMessageStyles.root,
10510
+ onBlur: (e) => {
10511
+ // `focused` controls is focused the whole `ChatMessage` or any of its children. When we're navigating
10512
+ // with keyboard the focused element will be changed and there is no way to use `:focus` selector
10513
+ if (chatMessageActionFlyoutTarget === null || chatMessageActionFlyoutTarget === void 0 ? void 0 : chatMessageActionFlyoutTarget.current) {
10514
+ // doesn't dismiss action button if flyout is open, otherwise, narrator's focus will stay on the closed action menu
10515
+ return;
10516
+ }
10517
+ const shouldPreserveFocusState = e.currentTarget.contains(e.relatedTarget);
10518
+ setFocused(shouldPreserveFocusState);
10519
+ },
10520
+ onFocus: () => {
10521
+ // react onFocus is called even when nested component receives focus (i.e. it bubbles)
10522
+ // so when focus moves within actionMenu, the `focus` state in chatMessage remains true, and keeps actionMenu visible
10523
+ setFocused(true);
10524
+ },
10525
+ // make body not focusable to remove repetitions from narrators.
10526
+ // inner components are already focusable
10527
+ role: 'none',
10528
+ tabIndex: -1
10529
+ }, "data-ui-id": "chat-composite-message", author: React__default["default"].createElement(react.Text, { className: chatMessageDateStyle, tabIndex: 0 }, message.senderDisplayName), timestamp: React__default["default"].createElement(react.Text, { className: chatMessageDateStyle, "data-ui-id": ids.messageTimestamp, tabIndex: 0 }, formattedTimestamp), details: getMessageDetails(), actions: {
10530
+ children: actionMenuProps === null || actionMenuProps === void 0 ? void 0 : actionMenuProps.children,
10531
+ className: reactComponents.mergeClasses(chatMyMessageStyles.menu,
10532
+ // Make actions menu visible when the message is focused or the flyout is shown
10533
+ focused || (chatMessageActionFlyoutTarget === null || chatMessageActionFlyoutTarget === void 0 ? void 0 : chatMessageActionFlyoutTarget.current)
10534
+ ? chatMyMessageStyles.menuVisible
10535
+ : chatMyMessageStyles.menuHidden, attached !== 'top' ? chatMyMessageStyles.menuAttached : undefined)
10536
+ }, onTouchStart: () => setWasInteractionByTouch(true), onPointerDown: () => setWasInteractionByTouch(false), onKeyDown: () => setWasInteractionByTouch(false), onClick: () => {
10537
+ if (!wasInteractionByTouch) {
10538
+ return;
10539
+ }
10540
+ // If the message was touched via touch we immediately open the menu
10541
+ // flyout (when using mouse the 3-dot menu that appears on hover
10542
+ // must be clicked to open the flyout).
10543
+ // In doing so here we set the target of the flyout to be the message and
10544
+ // not the 3-dot menu button to position the flyout correctly.
10545
+ setChatMessageActionFlyoutTarget(messageRef);
10546
+ if (message.messageType === 'chat') {
10547
+ props.onActionButtonClick(message, setMessageReadBy);
10548
+ }
10549
+ } }, getContent())) : (React__default["default"].createElement(reactChat.ChatMessage, { attached: attached, key: props.message.messageId, root: {
10550
+ className: chatMessageStyles.root,
10551
+ // make body not focusable to remove repetitions from narrators.
10552
+ // inner components are already focusable
10553
+ tabIndex: -1,
10554
+ role: 'none'
10555
+ }, author: React__default["default"].createElement(react.Text, { className: chatMessageAuthorStyle }, message.senderDisplayName), body: {
10556
+ className: chatItemMessageContainerClassName,
10557
+ style: Object.assign({}, createStyleFromV8Style(messageContainerStyle))
10558
+ }, "data-ui-id": "chat-composite-message", timestamp: React__default["default"].createElement(react.Text, { className: chatMessageDateStyle, "data-ui-id": ids.messageTimestamp }, formattedTimestamp) }, getContent()))),
10559
+ chatActionsEnabled && (React__default["default"].createElement(ChatMessageActionFlyout, { hidden: !chatMessageActionFlyoutTarget, target: chatMessageActionFlyoutTarget, increaseFlyoutItemSize: wasInteractionByTouch, onDismiss: onActionFlyoutDismiss, onEditClick: onEditClick, onRemoveClick: onRemoveClick, onResendClick: onResendClick, strings: strings, messageReadBy: messageReadBy, messageStatus: messageStatus !== null && messageStatus !== void 0 ? messageStatus : 'failed', remoteParticipantsCount: remoteParticipantsCount, onRenderAvatar: onRenderAvatar, showMessageStatus: showMessageStatus }))));
10560
+ return chatMessage;
10508
10561
  };
10509
- /**
10510
- * Creates v9 shadow tokens from v8 effects.
10511
- */
10512
- const mapShadowTokens = (effects) => {
10513
- return {
10514
- shadow4: effects.elevation4,
10515
- shadow8: effects.elevation8,
10516
- shadow16: effects.elevation16,
10517
- shadow64: effects.elevation64
10518
- };
10562
+ /** @private */
10563
+ const ChatMessageComponentAsMessageBubble = React__default["default"].memo(MessageBubble);
10564
+
10565
+ // Copyright (c) Microsoft Corporation.
10566
+ // Licensed under the MIT License.
10567
+ var __awaiter$w = (window && window.__awaiter) || function (thisArg, _arguments, P, generator) {
10568
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
10569
+ return new (P || (P = Promise))(function (resolve, reject) {
10570
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10571
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10572
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
10573
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
10574
+ });
10519
10575
  };
10520
10576
  /**
10521
- * Creates v9 border radius tokens from v8 effects
10577
+ * @private
10522
10578
  */
10523
- const mapBorderRadiusTokens = (effects) => {
10524
- return {
10525
- borderRadiusSmall: effects.roundedCorner2,
10526
- borderRadiusMedium: effects.roundedCorner4,
10527
- borderRadiusLarge: effects.roundedCorner6
10528
- };
10579
+ const ChatMessageComponent = (props) => {
10580
+ var _a, _b;
10581
+ const { onDeleteMessage, onSendMessage, message } = props;
10582
+ const [isEditing, setIsEditing] = React.useState(false);
10583
+ const onEditClick = React.useCallback(() => setIsEditing(true), [setIsEditing]);
10584
+ const clientMessageId = 'clientMessageId' in message ? message.clientMessageId : undefined;
10585
+ const content = 'content' in message ? message.content : undefined;
10586
+ const onRemoveClick = React.useCallback(() => {
10587
+ if (onDeleteMessage && message.messageId) {
10588
+ onDeleteMessage(message.messageId);
10589
+ }
10590
+ // when fail to send, message does not have message id, delete message using clientMessageId
10591
+ else if (onDeleteMessage && message.messageType === 'chat' && clientMessageId) {
10592
+ onDeleteMessage(clientMessageId);
10593
+ }
10594
+ }, [onDeleteMessage, message.messageId, message.messageType, clientMessageId]);
10595
+ const onResendClick = React.useCallback(() => {
10596
+ onDeleteMessage && clientMessageId && onDeleteMessage(clientMessageId);
10597
+ onSendMessage && onSendMessage(content !== undefined ? content : '');
10598
+ }, [clientMessageId, content, onSendMessage, onDeleteMessage]);
10599
+ if (isEditing && message.messageType === 'chat') {
10600
+ return (React__default["default"].createElement(ChatMessageComponentAsEditBox, { message: message, strings: props.strings, onSubmit: (text, metadata, options) => __awaiter$w(void 0, void 0, void 0, function* () {
10601
+ props.onUpdateMessage &&
10602
+ message.messageId &&
10603
+ (yield props.onUpdateMessage(message.messageId, text, metadata, options));
10604
+ setIsEditing(false);
10605
+ }), onCancel: (messageId) => {
10606
+ props.onCancelEditMessage && props.onCancelEditMessage(messageId);
10607
+ setIsEditing(false);
10608
+ },
10609
+ /* @conditional-compile-remove(mention) */
10610
+ mentionLookupOptions: (_a = props.mentionOptions) === null || _a === void 0 ? void 0 : _a.lookupOptions }));
10611
+ }
10612
+ else {
10613
+ return (React__default["default"].createElement(ChatMessageComponentAsMessageBubble, Object.assign({}, props, { onRemoveClick: onRemoveClick, onEditClick: onEditClick, onResendClick: onResendClick, onRenderAvatar: props.onRenderAvatar,
10614
+ /* @conditional-compile-remove(date-time-customization) */
10615
+ onDisplayDateTimeString: props.onDisplayDateTimeString, strings: props.strings,
10616
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10617
+ onFetchAttachments: props.onFetchAttachments,
10618
+ /* @conditional-compile-remove(image-gallery) */
10619
+ onInlineImageClicked: props.onInlineImageClicked,
10620
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10621
+ attachmentsMap: props.attachmentsMap,
10622
+ /* @conditional-compile-remove(mention) */
10623
+ mentionDisplayOptions: (_b = props.mentionOptions) === null || _b === void 0 ? void 0 : _b.displayOptions })));
10624
+ }
10529
10625
  };
10626
+
10627
+ // Copyright (c) Microsoft Corporation.
10530
10628
  /**
10531
- * Creates a v9 theme from a v8 theme and base v9 theme.
10532
- * FluentUI webLightTheme is used in case if no baseThemeV9 is provided.
10629
+ * The component for rendering a chat message using Fluent UI components
10630
+ * and handling default and custom renderers.
10631
+ * This component handles rendering for chat message body, avatar and message status.
10632
+ * The chat message body, avatar and message status should be shown for both default and custom renderers.
10533
10633
  *
10534
10634
  * @private
10535
10635
  */
10536
- const createV9Theme = (themeV8, baseThemeV9) => {
10537
- const baseTheme = baseThemeV9 !== null && baseThemeV9 !== void 0 ? baseThemeV9 : reactComponents.webLightTheme;
10538
- return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, baseTheme), mapAliasColors(themeV8.palette, themeV8.isInverted)), mapShadowTokens(themeV8.effects)), mapBorderRadiusTokens(themeV8.effects)), { colorBrandBackground2: themeV8.palette.themeLight, colorBrandBackground2Hover: themeV8.palette.themeLight, colorBrandBackground2Pressed: themeV8.palette.themeLight, colorStatusWarningBackground3: '#D83B01', errorText: themeV8.semanticColors.errorText, colorNeutralStroke1Selected: themeV8.palette.neutralQuaternary, colorNeutralForeground2: themeV8.palette.neutralSecondary, colorBrandForegroundLink: themeV8.palette.themePrimary, colorBrandForegroundLinkHover: themeV8.palette.themeDarker,
10539
- // Fix for an issue with black borders for iOS that are added with 'after' selector
10540
- colorStrokeFocus2: 'transparent' });
10636
+ const FluentChatMessageComponentWrapper = (props) => {
10637
+ const { message, styles, shouldOverlapAvatarAndMessage, onRenderMessage, onRenderAvatar, showMessageStatus, onRenderMessageStatus, participantCount, readCount, onActionButtonClick,
10638
+ /* @conditional-compile-remove(date-time-customization) */
10639
+ onDisplayDateTimeString,
10640
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10641
+ onFetchInlineAttachment,
10642
+ /* @conditional-compile-remove(image-gallery) */
10643
+ onInlineImageClicked,
10644
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10645
+ inlineAttachments,
10646
+ /* @conditional-compile-remove(mention) */
10647
+ mentionOptions,
10648
+ /* @conditional-compile-remove(file-sharing) */
10649
+ fileDownloadHandler, userId,
10650
+ /* @conditional-compile-remove(file-sharing) */
10651
+ onRenderFileDownloads, defaultStatusRenderer, statusToRender } = props;
10652
+ const chatMessageRenderStyles = useChatMessageRenderStyles();
10653
+ const onRenderFileDownloadsMemo = React.useMemo(() => {
10654
+ /* @conditional-compile-remove(file-sharing) */
10655
+ return onRenderFileDownloads;
10656
+ }, [/* @conditional-compile-remove(file-sharing) */ onRenderFileDownloads]);
10657
+ // To rerender the defaultChatMessageRenderer if app running across days(every new day chat time stamp
10658
+ // needs to be regenerated), the dependency on "new Date().toDateString()"" is added.
10659
+ const defaultChatMessageRenderer = React.useCallback((messageProps) => {
10660
+ var _a;
10661
+ if (messageProps.message.messageType === 'chat' ||
10662
+ /* @conditional-compile-remove(data-loss-prevention) */ messageProps.message.messageType === 'blocked') {
10663
+ return (React__default["default"].createElement(ChatMessageComponent, Object.assign({}, messageProps, {
10664
+ /* @conditional-compile-remove(file-sharing) */
10665
+ onRenderFileDownloads: onRenderFileDownloadsMemo,
10666
+ /* @conditional-compile-remove(file-sharing) */
10667
+ strings: messageProps.strings, message: messageProps.message, userId: userId, remoteParticipantsCount: participantCount ? participantCount - 1 : 0, shouldOverlapAvatarAndMessage: shouldOverlapAvatarAndMessage, onRenderAvatar: onRenderAvatar, showMessageStatus: showMessageStatus, messageStatus: messageProps.message.status, onActionButtonClick: onActionButtonClick,
10668
+ /* @conditional-compile-remove(date-time-customization) */
10669
+ onDisplayDateTimeString: onDisplayDateTimeString,
10670
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10671
+ onFetchAttachments: onFetchInlineAttachment,
10672
+ /* @conditional-compile-remove(image-gallery) */
10673
+ onInlineImageClicked: onInlineImageClicked,
10674
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10675
+ attachmentsMap: (_a = inlineAttachments[messageProps.message.messageId]) !== null && _a !== void 0 ? _a : {},
10676
+ /* @conditional-compile-remove(mention) */
10677
+ mentionOptions: mentionOptions,
10678
+ /* @conditional-compile-remove(file-sharing) */
10679
+ fileDownloadHandler: fileDownloadHandler })));
10680
+ }
10681
+ return React__default["default"].createElement(React__default["default"].Fragment, null);
10682
+ }, [
10683
+ onActionButtonClick,
10684
+ onRenderAvatar,
10685
+ onRenderFileDownloadsMemo,
10686
+ participantCount,
10687
+ shouldOverlapAvatarAndMessage,
10688
+ showMessageStatus,
10689
+ userId,
10690
+ /* @conditional-compile-remove(date-time-customization) */
10691
+ onDisplayDateTimeString,
10692
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10693
+ onFetchInlineAttachment,
10694
+ /* @conditional-compile-remove(image-gallery) */
10695
+ onInlineImageClicked,
10696
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10697
+ inlineAttachments,
10698
+ /* @conditional-compile-remove(mention) */
10699
+ mentionOptions,
10700
+ /* @conditional-compile-remove(file-sharing) */
10701
+ fileDownloadHandler,
10702
+ // eslint-disable-next-line react-hooks/exhaustive-deps
10703
+ new Date().toDateString()
10704
+ ]);
10705
+ const messageRenderer = React.useCallback((messageProps) => {
10706
+ return onRenderMessage === undefined
10707
+ ? defaultChatMessageRenderer(Object.assign({}, messageProps))
10708
+ : onRenderMessage(messageProps, defaultChatMessageRenderer);
10709
+ }, [defaultChatMessageRenderer, onRenderMessage]);
10710
+ const messageStatusRenderer = React.useCallback((onRenderMessageStatus, defaultStatusRenderer, showMessageStatus, participantCount, readCount) => {
10711
+ return showMessageStatus && statusToRender ? (onRenderMessageStatus ? (onRenderMessageStatus({ status: message.status })) : (defaultStatusRenderer(message, participantCount !== null && participantCount !== void 0 ? participantCount : 0, readCount !== null && readCount !== void 0 ? readCount : 0, message.status))) : (React__default["default"].createElement("div", { className: react.mergeStyles(noMessageStatusStyle) }));
10712
+ }, [message, statusToRender]);
10713
+ const shouldShowAvatar = React.useMemo(() => {
10714
+ return message.attached === 'top' || message.attached === false;
10715
+ }, [message.attached]);
10716
+ const attached = React.useMemo(() => {
10717
+ return shouldShowAvatar ? 'top' : 'center';
10718
+ }, [shouldShowAvatar]);
10719
+ const myMessageRootProps = React.useMemo(() => {
10720
+ return {
10721
+ // myChatItemMessageContainer used in className and style prop as style prop can't handle CSS selectors
10722
+ className: reactComponents.mergeClasses(chatMessageRenderStyles.rootMyMessage, chatMessageRenderStyles.rootCommon, react.mergeStyles(styles === null || styles === void 0 ? void 0 : styles.myChatItemMessageContainer)),
10723
+ style: (styles === null || styles === void 0 ? void 0 : styles.myChatItemMessageContainer) !== undefined
10724
+ ? createStyleFromV8Style(styles === null || styles === void 0 ? void 0 : styles.myChatItemMessageContainer)
10725
+ : {},
10726
+ role: 'none'
10727
+ };
10728
+ }, [chatMessageRenderStyles.rootCommon, chatMessageRenderStyles.rootMyMessage, styles === null || styles === void 0 ? void 0 : styles.myChatItemMessageContainer]);
10729
+ const myMessageBodyProps = React.useMemo(() => {
10730
+ return {
10731
+ className: reactComponents.mergeClasses(chatMessageRenderStyles.bodyCommon, chatMessageRenderStyles.bodyMyMessage),
10732
+ // make body not focusable to remove repetitions from narrators.
10733
+ // inner components are already focusable
10734
+ tabIndex: -1,
10735
+ role: 'none'
10736
+ };
10737
+ }, [chatMessageRenderStyles.bodyCommon, chatMessageRenderStyles.bodyMyMessage]);
10738
+ const myMessageStatusIcon = React.useMemo(() => {
10739
+ var _a;
10740
+ return (React__default["default"].createElement("div", { className: react.mergeStyles({ paddingLeft: '0.25rem' }, (styles === null || styles === void 0 ? void 0 : styles.messageStatusContainer) ? styles.messageStatusContainer((_a = message.mine) !== null && _a !== void 0 ? _a : false) : '') }, message.status
10741
+ ? messageStatusRenderer(onRenderMessageStatus, defaultStatusRenderer, showMessageStatus, participantCount, readCount)
10742
+ : undefined));
10743
+ }, [
10744
+ defaultStatusRenderer,
10745
+ message.mine,
10746
+ message.status,
10747
+ messageStatusRenderer,
10748
+ onRenderMessageStatus,
10749
+ participantCount,
10750
+ readCount,
10751
+ showMessageStatus,
10752
+ styles
10753
+ ]);
10754
+ const messageRootProps = React.useMemo(() => {
10755
+ return { className: reactComponents.mergeClasses(chatMessageRenderStyles.rootMessage, chatMessageRenderStyles.rootCommon) };
10756
+ }, [chatMessageRenderStyles.rootCommon, chatMessageRenderStyles.rootMessage]);
10757
+ const messageBodyProps = React.useMemo(() => {
10758
+ return {
10759
+ // chatItemMessageContainer used in className and style prop as style prop can't handle CSS selectors
10760
+ className: reactComponents.mergeClasses(chatMessageRenderStyles.bodyCommon, !shouldShowAvatar ? chatMessageRenderStyles.bodyWithoutAvatar : chatMessageRenderStyles.bodyWithAvatar, shouldOverlapAvatarAndMessage ? chatMessageRenderStyles.avatarOverlap : chatMessageRenderStyles.avatarNoOverlap, react.mergeStyles(styles === null || styles === void 0 ? void 0 : styles.chatItemMessageContainer)),
10761
+ style: (styles === null || styles === void 0 ? void 0 : styles.chatItemMessageContainer) !== undefined ? createStyleFromV8Style(styles === null || styles === void 0 ? void 0 : styles.chatItemMessageContainer) : {},
10762
+ // make body not focusable to remove repetitions from narrators.
10763
+ // inner components are already focusable
10764
+ tabIndex: -1,
10765
+ role: 'none'
10766
+ };
10767
+ }, [
10768
+ chatMessageRenderStyles.avatarNoOverlap,
10769
+ chatMessageRenderStyles.avatarOverlap,
10770
+ chatMessageRenderStyles.bodyCommon,
10771
+ chatMessageRenderStyles.bodyWithAvatar,
10772
+ chatMessageRenderStyles.bodyWithoutAvatar,
10773
+ shouldOverlapAvatarAndMessage,
10774
+ shouldShowAvatar,
10775
+ styles === null || styles === void 0 ? void 0 : styles.chatItemMessageContainer
10776
+ ]);
10777
+ const avatar = React.useMemo(() => {
10778
+ const chatAvatarStyle = shouldShowAvatar ? gutterWithAvatar : gutterWithHiddenAvatar;
10779
+ const personaOptions = {
10780
+ hidePersonaDetails: true,
10781
+ size: react.PersonaSize.size32,
10782
+ text: message.senderDisplayName,
10783
+ showOverflowTooltip: false
10784
+ };
10785
+ return (React__default["default"].createElement("div", { className: react.mergeStyles(chatAvatarStyle) }, onRenderAvatar ? onRenderAvatar === null || onRenderAvatar === void 0 ? void 0 : onRenderAvatar(message.senderId, personaOptions) : React__default["default"].createElement(react.Persona, Object.assign({}, personaOptions))));
10786
+ }, [message.senderDisplayName, message.senderId, onRenderAvatar, shouldShowAvatar]);
10787
+ // Fluent UI message components are used here as for default message renderer,
10788
+ // timestamp and author name should be shown but they aren't shown for custom renderer.
10789
+ // More investigations are needed to check if this can be simplified with states.
10790
+ // Status and avatar should be shown for both custom and default renderers.
10791
+ if (message.mine === true) {
10792
+ return (React__default["default"].createElement("div", null,
10793
+ React__default["default"].createElement(reactChat.ChatMyMessage, { attached: attached, root: myMessageRootProps, body: myMessageBodyProps, statusIcon: myMessageStatusIcon }, messageRenderer(Object.assign({}, props)))));
10794
+ }
10795
+ else {
10796
+ return (React__default["default"].createElement("div", null,
10797
+ React__default["default"].createElement(reactChat.ChatMessage, { attached: attached, root: messageRootProps, body: messageBodyProps, avatar: avatar }, messageRenderer(Object.assign({}, props)))));
10798
+ }
10541
10799
  };
10542
10800
 
10543
10801
  // Copyright (c) Microsoft Corporation.
10544
10802
  /**
10545
10803
  * @private
10546
10804
  */
10547
- const useFluentV9Wrapper = reactComponents.makeStyles({
10548
- body: Object.assign(Object.assign(Object.assign(Object.assign({ height: '100%' }, reactComponents.shorthands.margin(0)), reactComponents.shorthands.overflow('hidden')), reactComponents.shorthands.padding(0)), { width: '100%' })
10805
+ const systemMessageIconStyle = react.mergeStyles({
10806
+ marginInlineEnd: '0.688rem'
10549
10807
  });
10808
+
10809
+ // Copyright (c) Microsoft Corporation.
10550
10810
  /**
10551
10811
  * @private
10552
10812
  */
10553
- const FluentV9ThemeProvider = (props) => {
10554
- const { v8Theme, children } = props;
10555
- const v9Theme = createV9Theme(v8Theme);
10556
- const dir = v8Theme.rtl ? 'rtl' : 'ltr';
10557
- return (
10558
- // TextDirectionProvider is needed to fix issue with direction value update in FluentProvider
10559
- React__default["default"].createElement(react$1.TextDirectionProvider, { dir: dir },
10560
- React__default["default"].createElement(FluentProviderWithStylesOverrides, { theme: v9Theme, dir: dir }, children)));
10561
- };
10562
- const FluentProviderWithStylesOverrides = (props) => {
10563
- const classes = useFluentV9Wrapper();
10564
- return React__default["default"].createElement(reactComponents.FluentProvider, Object.assign({}, props, { className: classes.body }));
10813
+ const SystemMessage = (props) => {
10814
+ const { iconName, content } = props;
10815
+ const Icon = React__default["default"].createElement(react.FontIcon, { iconName: iconName, className: react.mergeStyles(systemMessageIconStyle) });
10816
+ return (React__default["default"].createElement(react.Stack, { horizontal: true, className: react.mergeStyles(props === null || props === void 0 ? void 0 : props.containerStyle), tabIndex: 0 },
10817
+ Icon,
10818
+ React__default["default"].createElement(react.Text, { style: { wordBreak: 'break-word' }, role: "status", title: content, variant: 'small' }, content)));
10565
10819
  };
10566
10820
 
10567
10821
  // Copyright (c) Microsoft Corporation.
10568
- const offScreenStyle = {
10569
- border: 0,
10570
- clip: 'rect(0 0 0 0)',
10571
- height: '1px',
10572
- margin: '-1px',
10573
- overflow: 'hidden',
10574
- whiteSpace: 'nowrap',
10575
- padding: 0,
10576
- width: '1px',
10577
- position: 'absolute'
10822
+ /**
10823
+ * @private
10824
+ */
10825
+ const DefaultSystemMessage = (props) => {
10826
+ var _a;
10827
+ const message = props.message;
10828
+ switch (message.messageType) {
10829
+ case 'system':
10830
+ switch (message.systemMessageType) {
10831
+ case 'content':
10832
+ return (React__default["default"].createElement(SystemMessage, { iconName: (message.iconName ? message.iconName : ''), content: (_a = message.content) !== null && _a !== void 0 ? _a : '', containerStyle: props === null || props === void 0 ? void 0 : props.messageContainerStyle }));
10833
+ case 'participantAdded':
10834
+ case 'participantRemoved':
10835
+ return (React__default["default"].createElement(ParticipantSystemMessageComponent, { message: message, style: props.messageContainerStyle, defaultName: props.strings.noDisplayNameSub }));
10836
+ }
10837
+ }
10838
+ return React__default["default"].createElement(React__default["default"].Fragment, null);
10839
+ };
10840
+ const ParticipantSystemMessageComponent = ({ message, style, defaultName }) => {
10841
+ const { strings } = useLocale$1();
10842
+ const participantsStr = generateParticipantsStr(message.participants, defaultName);
10843
+ const messageSuffix = message.systemMessageType === 'participantAdded'
10844
+ ? strings.messageThread.participantJoined
10845
+ : strings.messageThread.participantLeft;
10846
+ if (participantsStr !== '') {
10847
+ return (React__default["default"].createElement(SystemMessage, { iconName: (message.iconName ? message.iconName : ''), content: `${participantsStr} ${messageSuffix}`, containerStyle: style }));
10848
+ }
10849
+ return React__default["default"].createElement(React__default["default"].Fragment, null);
10578
10850
  };
10579
- /** @private */
10580
- const MessageBlock = (props) => (React__default["default"].createElement("div", { style: offScreenStyle, role: "log", "aria-live": props.ariaLive }, props.message ? props.message : ''));
10851
+ const generateParticipantsStr = (participants, defaultName) => participants
10852
+ .map((participant) => `${!participant.displayName || participant.displayName === '' ? defaultName : participant.displayName}`)
10853
+ .join(', ');
10581
10854
 
10582
10855
  // Copyright (c) Microsoft Corporation.
10583
- /** @private */
10584
- const EMPTY_MESSAGE = { message: '', id: '' };
10585
- /** @private */
10586
- const Announcer = (props) => {
10856
+ /**
10857
+ * The wrapper component to display different types of chat message.
10858
+ *
10859
+ * @private
10860
+ */
10861
+ const ChatMessageComponentWrapper = (props) => {
10587
10862
  var _a, _b;
10588
- const newAssertive = (_a = props.assertive) !== null && _a !== void 0 ? _a : EMPTY_MESSAGE;
10589
- const oldAssertive = React__default["default"].useRef(EMPTY_MESSAGE);
10590
- const [activeAssertive1, setActiveAssertive1] = React__default["default"].useState(EMPTY_MESSAGE);
10591
- const [activeAssertive2, setActiveAssertive2] = React__default["default"].useState(EMPTY_MESSAGE);
10592
- const alternateAssertive = React__default["default"].useRef(false);
10593
- React.useEffect(() => {
10594
- if (oldAssertive.current.message !== (newAssertive === null || newAssertive === void 0 ? void 0 : newAssertive.message) || oldAssertive.current.id !== (newAssertive === null || newAssertive === void 0 ? void 0 : newAssertive.id)) {
10595
- setActiveAssertive1(alternateAssertive.current ? EMPTY_MESSAGE : newAssertive);
10596
- setActiveAssertive2(alternateAssertive.current ? newAssertive : EMPTY_MESSAGE);
10597
- oldAssertive.current = newAssertive;
10598
- alternateAssertive.current = !alternateAssertive.current;
10599
- }
10600
- }, [newAssertive]);
10601
- const newPolite = (_b = props.polite) !== null && _b !== void 0 ? _b : EMPTY_MESSAGE;
10602
- const oldPolite = React__default["default"].useRef(EMPTY_MESSAGE);
10603
- const [activePolite1, setActivePolite1] = React__default["default"].useState(EMPTY_MESSAGE);
10604
- const [activePolite2, setActivePolite2] = React__default["default"].useState(EMPTY_MESSAGE);
10605
- const alternatePolite = React__default["default"].useRef(false);
10606
- React.useEffect(() => {
10607
- if (oldPolite.current.message !== (newPolite === null || newPolite === void 0 ? void 0 : newPolite.message) || oldPolite.current.id !== (newPolite === null || newPolite === void 0 ? void 0 : newPolite.id)) {
10608
- setActivePolite1(alternatePolite.current ? EMPTY_MESSAGE : newPolite);
10609
- setActivePolite2(alternatePolite.current ? newPolite : EMPTY_MESSAGE);
10610
- oldPolite.current = newPolite;
10611
- alternatePolite.current = !alternatePolite.current;
10612
- }
10613
- }, [newPolite]);
10614
- return (React__default["default"].createElement("div", null,
10615
- React__default["default"].createElement(MessageBlock, { ariaLive: "assertive", message: activeAssertive1.message }),
10616
- React__default["default"].createElement(MessageBlock, { ariaLive: "assertive", message: activeAssertive2.message }),
10617
- React__default["default"].createElement(MessageBlock, { ariaLive: "polite", message: activePolite1.message }),
10618
- React__default["default"].createElement(MessageBlock, { ariaLive: "polite", message: activePolite2.message })));
10619
- };
10620
-
10621
- // Copyright (c) Microsoft Corporation.
10622
- /** @private */
10623
- const LiveAnnouncer = (props) => {
10624
- const [politeMessage, setPoliteMessage] = React__default["default"].useState(EMPTY_MESSAGE);
10625
- const [assertiveMessage, setAssertiveMessage] = React__default["default"].useState(EMPTY_MESSAGE);
10626
- const announcePolite = React.useCallback((message, id) => {
10627
- setPoliteMessage({ message, id });
10863
+ const { message, styles, onRenderMessage, key: messageKey } = props;
10864
+ const systemMessageStyle = React.useMemo(() => {
10865
+ return {
10866
+ paddingTop: '0.5rem'
10867
+ };
10628
10868
  }, []);
10629
- const announceAssertive = React.useCallback((message, id) => {
10630
- setAssertiveMessage({ message, id });
10869
+ const customMessageStyle = React.useMemo(() => {
10870
+ return { paddingTop: '1rem', paddingBottom: '0.25rem' };
10631
10871
  }, []);
10632
- const updateFunctions = React.useMemo(() => ({
10633
- announcePolite,
10634
- announceAssertive
10635
- }), [announceAssertive, announcePolite]);
10636
- return (React__default["default"].createElement(AnnouncerContext.Provider, { value: updateFunctions },
10637
- props.children,
10638
- React__default["default"].createElement(Announcer, { assertive: assertiveMessage, polite: politeMessage })));
10872
+ /* @conditional-compile-remove(data-loss-prevention) */
10873
+ // Similar logic as switch statement case 'chat', if statement for conditional compile (merge logic to switch case when stabilize)
10874
+ if (message.messageType === 'blocked') {
10875
+ const myChatMessageStyle = message.status === 'failed'
10876
+ ? (_a = styles === null || styles === void 0 ? void 0 : styles.failedMyChatMessageContainer) !== null && _a !== void 0 ? _a : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer
10877
+ : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer;
10878
+ const blockedMessageStyle = styles === null || styles === void 0 ? void 0 : styles.blockedMessageContainer;
10879
+ const messageContainerStyle = message.mine ? myChatMessageStyle : blockedMessageStyle;
10880
+ return (React__default["default"].createElement(FluentChatMessageComponentWrapper, Object.assign({}, props, { message: message, messageContainerStyle: messageContainerStyle })));
10881
+ }
10882
+ switch (message.messageType) {
10883
+ case 'chat': {
10884
+ const myChatMessageStyle = message.status === 'failed'
10885
+ ? (_b = styles === null || styles === void 0 ? void 0 : styles.failedMyChatMessageContainer) !== null && _b !== void 0 ? _b : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer
10886
+ : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer;
10887
+ const chatMessageStyle = styles === null || styles === void 0 ? void 0 : styles.chatMessageContainer;
10888
+ const messageContainerStyle = message.mine ? myChatMessageStyle : chatMessageStyle;
10889
+ return (React__default["default"].createElement(FluentChatMessageComponentWrapper, Object.assign({}, props, { message: message, messageContainerStyle: messageContainerStyle })));
10890
+ }
10891
+ case 'system': {
10892
+ const messageContainerStyle = styles === null || styles === void 0 ? void 0 : styles.systemMessageContainer;
10893
+ const systemMessageComponent = onRenderMessage === undefined ? (React__default["default"].createElement(DefaultSystemMessage, Object.assign({}, props))) : (onRenderMessage(Object.assign(Object.assign({}, props), { messageContainerStyle }), (props) => React__default["default"].createElement(DefaultSystemMessage, Object.assign({}, props))));
10894
+ return (React__default["default"].createElement("div", { key: messageKey, style: systemMessageStyle }, systemMessageComponent));
10895
+ }
10896
+ default: {
10897
+ // We do not handle custom type message by default, users can handle custom type by using onRenderMessage function.
10898
+ const customMessageComponent = onRenderMessage === undefined ? React__default["default"].createElement(React__default["default"].Fragment, null) : onRenderMessage(Object.assign({}, props));
10899
+ return (React__default["default"].createElement("div", { key: messageKey, style: customMessageStyle }, customMessageComponent));
10900
+ }
10901
+ }
10639
10902
  };
10640
10903
 
10641
10904
  // Copyright (c) Microsoft Corporation.
@@ -10706,38 +10969,37 @@ const DefaultJumpToNewMessageButton = (props) => {
10706
10969
  const { text, onClick } = props;
10707
10970
  return (React__default["default"].createElement(react.PrimaryButton, { className: newMessageButtonStyle, styles: buttonWithIconStyles$1, text: text, onClick: onClick, onRenderIcon: () => React__default["default"].createElement(react.Icon, { iconName: "Down", className: DownIconStyle }) }));
10708
10971
  };
10709
- const generateParticipantsStr = (participants, defaultName) => participants
10710
- .map((participant) => `${!participant.displayName || participant.displayName === '' ? defaultName : participant.displayName}`)
10711
- .join(', ');
10712
- const ParticipantSystemMessageComponent = ({ message, style, defaultName }) => {
10713
- const { strings } = useLocale$1();
10714
- const participantsStr = generateParticipantsStr(message.participants, defaultName);
10715
- const messageSuffix = message.systemMessageType === 'participantAdded'
10716
- ? strings.messageThread.participantJoined
10717
- : strings.messageThread.participantLeft;
10718
- if (participantsStr !== '') {
10719
- return (React__default["default"].createElement(SystemMessage, { iconName: (message.iconName ? message.iconName : ''), content: `${participantsStr} ${messageSuffix}`, containerStyle: style }));
10720
- }
10721
- return React__default["default"].createElement(React__default["default"].Fragment, null);
10722
- };
10723
- const DefaultSystemMessage = (props) => {
10724
- var _a;
10725
- const message = props.message;
10726
- switch (message.messageType) {
10727
- case 'system':
10728
- switch (message.systemMessageType) {
10729
- case 'content':
10730
- return (React__default["default"].createElement(SystemMessage, { iconName: (message.iconName ? message.iconName : ''), content: (_a = message.content) !== null && _a !== void 0 ? _a : '', containerStyle: props === null || props === void 0 ? void 0 : props.messageContainerStyle }));
10731
- case 'participantAdded':
10732
- case 'participantRemoved':
10733
- return (React__default["default"].createElement(ParticipantSystemMessageComponent, { message: message, style: props.messageContainerStyle, defaultName: props.strings.noDisplayNameSub }));
10972
+ const memoizeAllMessages = memoizeFnAll((message, showMessageDate, showMessageStatus, strings, index, onUpdateMessage, onCancelEditMessage, onDeleteMessage, onSendMessage, disableEditing, lastSeenChatMessage, lastSendingChatMessage, lastDeliveredChatMessage) => {
10973
+ let key = message.messageId;
10974
+ let statusToRender = undefined;
10975
+ if (message.messageType === 'chat' ||
10976
+ /* @conditional-compile-remove(data-loss-prevention) */ message.messageType === 'blocked') {
10977
+ if ((!message.messageId || message.messageId === '') && 'clientMessageId' in message) {
10978
+ key = message.clientMessageId;
10979
+ }
10980
+ if (showMessageStatus && message.mine) {
10981
+ switch (message.messageId) {
10982
+ case lastSeenChatMessage: {
10983
+ statusToRender = 'seen';
10984
+ break;
10985
+ }
10986
+ case lastSendingChatMessage: {
10987
+ statusToRender = 'sending';
10988
+ break;
10989
+ }
10990
+ case lastDeliveredChatMessage: {
10991
+ statusToRender = 'delivered';
10992
+ break;
10993
+ }
10734
10994
  }
10995
+ }
10996
+ if (message.mine && message.status === 'failed') {
10997
+ statusToRender = 'failed';
10998
+ }
10735
10999
  }
10736
- return React__default["default"].createElement(React__default["default"].Fragment, null);
10737
- };
10738
- const memoizeAllMessages = memoizeFnAll((_messageKey, message, showMessageDate, showMessageStatus, onRenderAvatar, shouldOverlapAvatarAndMessage, styles, onRenderMessageStatus, defaultStatusRenderer, defaultChatMessageRenderer, strings, theme, chatMessageRenderStyles, _attached, statusToRender, participantCount, readCount, onRenderMessage, onUpdateMessage, onCancelEditMessage, onDeleteMessage, onSendMessage, disableEditing) => {
10739
- var _a, _b;
10740
- const messageProps = {
11000
+ return {
11001
+ key: key !== null && key !== void 0 ? key : 'id_' + index,
11002
+ statusToRender,
10741
11003
  message,
10742
11004
  strings,
10743
11005
  showDate: showMessageDate,
@@ -10745,92 +11007,9 @@ const memoizeAllMessages = memoizeFnAll((_messageKey, message, showMessageDate,
10745
11007
  onCancelEditMessage,
10746
11008
  onDeleteMessage,
10747
11009
  onSendMessage,
10748
- disableEditing
10749
- };
10750
- const chatMessage = (message, messageProps) => {
10751
- var _a;
10752
- const messageStatusRenderer = showMessageStatus && statusToRender
10753
- ? onRenderMessageStatus
10754
- ? (status) => onRenderMessageStatus({ status })
10755
- : (status) => defaultStatusRenderer(message, status, participantCount !== null && participantCount !== void 0 ? participantCount : 0, readCount !== null && readCount !== void 0 ? readCount : 0)
10756
- : () => React__default["default"].createElement("div", { className: react.mergeStyles(noMessageStatusStyle) });
10757
- let chatMessageComponent;
10758
- const shouldShowAvatar = message.attached === 'top' || message.attached === false;
10759
- const attached = shouldShowAvatar ? 'top' : 'center';
10760
- if (message.mine === true) {
10761
- chatMessageComponent = (React__default["default"].createElement(reactChat.ChatMyMessage, { attached: attached, root: {
10762
- // myChatItemMessageContainer used in className and style prop as style prop can't handle CSS selectors
10763
- className: reactComponents.mergeClasses(chatMessageRenderStyles.rootMyMessage, chatMessageRenderStyles.rootCommon, react.mergeStyles(styles === null || styles === void 0 ? void 0 : styles.myChatItemMessageContainer)),
10764
- style: (styles === null || styles === void 0 ? void 0 : styles.myChatItemMessageContainer) !== undefined
10765
- ? createStyleFromV8Style(styles === null || styles === void 0 ? void 0 : styles.myChatItemMessageContainer)
10766
- : {},
10767
- role: 'none'
10768
- }, body: {
10769
- className: reactComponents.mergeClasses(chatMessageRenderStyles.bodyCommon, chatMessageRenderStyles.bodyMyMessage),
10770
- // make body not focusable to remove repetitions from narrators.
10771
- // inner components are already focusable
10772
- tabIndex: -1,
10773
- role: 'none'
10774
- }, statusIcon: React__default["default"].createElement("div", { className: react.mergeStyles({ paddingLeft: '0.25rem' }, (styles === null || styles === void 0 ? void 0 : styles.messageStatusContainer) ? styles.messageStatusContainer((_a = message.mine) !== null && _a !== void 0 ? _a : false) : '') }, message.status ? messageStatusRenderer(message.status) : undefined) }, onRenderMessage === undefined
10775
- ? defaultChatMessageRenderer(Object.assign(Object.assign({}, messageProps), { messageStatusRenderer }))
10776
- : onRenderMessage(messageProps, defaultChatMessageRenderer)));
10777
- }
10778
- else {
10779
- const chatAvatarStyle = shouldShowAvatar ? gutterWithAvatar : gutterWithHiddenAvatar;
10780
- const personaOptions = {
10781
- hidePersonaDetails: true,
10782
- size: react.PersonaSize.size32,
10783
- text: message.senderDisplayName,
10784
- showOverflowTooltip: false
10785
- };
10786
- chatMessageComponent = (React__default["default"].createElement(reactChat.ChatMessage, { attached: attached, root: { className: reactComponents.mergeClasses(chatMessageRenderStyles.rootMessage, chatMessageRenderStyles.rootCommon) }, body: {
10787
- // chatItemMessageContainer used in className and style prop as style prop can't handle CSS selectors
10788
- className: reactComponents.mergeClasses(chatMessageRenderStyles.bodyCommon, !shouldShowAvatar ? chatMessageRenderStyles.bodyWithoutAvatar : chatMessageRenderStyles.bodyWithAvatar, shouldOverlapAvatarAndMessage
10789
- ? chatMessageRenderStyles.avatarOverlap
10790
- : chatMessageRenderStyles.avatarNoOverlap, react.mergeStyles(styles === null || styles === void 0 ? void 0 : styles.chatItemMessageContainer)),
10791
- style: (styles === null || styles === void 0 ? void 0 : styles.chatItemMessageContainer) !== undefined
10792
- ? createStyleFromV8Style(styles === null || styles === void 0 ? void 0 : styles.chatItemMessageContainer)
10793
- : {},
10794
- // make body not focusable to remove repetitions from narrators.
10795
- // inner components are already focusable
10796
- tabIndex: -1,
10797
- role: 'none'
10798
- }, avatar: React__default["default"].createElement("div", { className: react.mergeStyles(chatAvatarStyle) }, onRenderAvatar ? onRenderAvatar === null || onRenderAvatar === void 0 ? void 0 : onRenderAvatar(message.senderId, personaOptions) : React__default["default"].createElement(react.Persona, Object.assign({}, personaOptions))) }, onRenderMessage === undefined
10799
- ? defaultChatMessageRenderer(Object.assign(Object.assign({}, messageProps), { messageStatusRenderer }))
10800
- : onRenderMessage(messageProps, defaultChatMessageRenderer)));
10801
- }
10802
- return React__default["default"].createElement("div", { key: _messageKey }, chatMessageComponent);
11010
+ disableEditing,
11011
+ showMessageStatus
10803
11012
  };
10804
- /* @conditional-compile-remove(data-loss-prevention) */
10805
- // Similar logic as switch statement case 'chat', if statement for conditional compile (merge logic to switch case when stabilize)
10806
- if (message.messageType === 'blocked') {
10807
- const myChatMessageStyle = message.status === 'failed'
10808
- ? (_a = styles === null || styles === void 0 ? void 0 : styles.failedMyChatMessageContainer) !== null && _a !== void 0 ? _a : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer
10809
- : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer;
10810
- const blockedMessageStyle = styles === null || styles === void 0 ? void 0 : styles.blockedMessageContainer;
10811
- messageProps.messageContainerStyle = message.mine ? myChatMessageStyle : blockedMessageStyle;
10812
- return chatMessage(message, messageProps);
10813
- }
10814
- switch (message.messageType) {
10815
- case 'chat': {
10816
- const myChatMessageStyle = message.status === 'failed'
10817
- ? (_b = styles === null || styles === void 0 ? void 0 : styles.failedMyChatMessageContainer) !== null && _b !== void 0 ? _b : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer
10818
- : styles === null || styles === void 0 ? void 0 : styles.myChatMessageContainer;
10819
- const chatMessageStyle = styles === null || styles === void 0 ? void 0 : styles.chatMessageContainer;
10820
- messageProps.messageContainerStyle = message.mine ? myChatMessageStyle : chatMessageStyle;
10821
- return chatMessage(message, messageProps);
10822
- }
10823
- case 'system': {
10824
- messageProps.messageContainerStyle = styles === null || styles === void 0 ? void 0 : styles.systemMessageContainer;
10825
- const systemMessageComponent = onRenderMessage === undefined ? (React__default["default"].createElement(DefaultSystemMessage, Object.assign({}, messageProps))) : (onRenderMessage(messageProps, (props) => React__default["default"].createElement(DefaultSystemMessage, Object.assign({}, props))));
10826
- return (React__default["default"].createElement("div", { key: _messageKey, style: { paddingTop: '0.5rem' } }, systemMessageComponent));
10827
- }
10828
- default: {
10829
- // We do not handle custom type message by default, users can handle custom type by using onRenderMessage function.
10830
- const customMessageComponent = onRenderMessage === undefined ? React__default["default"].createElement(React__default["default"].Fragment, null) : onRenderMessage(messageProps);
10831
- return (React__default["default"].createElement("div", { key: _messageKey, style: { paddingTop: '1rem', paddingBottom: '0.25rem' } }, customMessageComponent));
10832
- }
10833
- }
10834
11013
  });
10835
11014
  const getLastChatMessageIdWithStatus = (messages, status) => {
10836
11015
  for (let i = messages.length - 1; i >= 0; i--) {
@@ -10874,9 +11053,9 @@ const MessageThreadWrapper = (props) => {
10874
11053
  /* @conditional-compile-remove(mention) */
10875
11054
  mentionOptions,
10876
11055
  /* @conditional-compile-remove(image-gallery) */
10877
- onInlineImageClicked } = props;
10878
- const onRenderFileDownloads = onRenderFileDownloadsTrampoline(props);
10879
- const [messages, setMessages] = React.useState([]);
11056
+ onInlineImageClicked,
11057
+ /* @conditional-compile-remove(file-sharing) */
11058
+ onRenderFileDownloads } = props;
10880
11059
  // We need this state to wait for one tick and scroll to bottom after messages have been initialized.
10881
11060
  // Otherwise chatScrollDivRef.current.clientHeight is wrong if we scroll to bottom before messages are initialized.
10882
11061
  const [chatMessagesInitialized, setChatMessagesInitialized] = React.useState(false);
@@ -10895,19 +11074,24 @@ const MessageThreadWrapper = (props) => {
10895
11074
  const [inlineAttachments, setInlineAttachments] = React.useState({});
10896
11075
  /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
10897
11076
  const onFetchInlineAttachment = React.useCallback((attachments, messageId) => __awaiter$v(void 0, void 0, void 0, function* () {
10898
- if (!onFetchAttachments) {
11077
+ if (!onFetchAttachments || attachments.length === 0) {
10899
11078
  return;
10900
11079
  }
10901
11080
  const attachmentDownloadResult = yield onFetchAttachments(attachments);
10902
- const listOfAttachments = inlineAttachments[messageId];
10903
- for (const result of attachmentDownloadResult) {
10904
- const { attachmentId, blobUrl } = result;
10905
- listOfAttachments[attachmentId] = blobUrl;
10906
- }
10907
- if (Object.keys(listOfAttachments).length > 0) {
10908
- setInlineAttachments((prev) => (Object.assign(Object.assign({}, prev), { [messageId]: listOfAttachments })));
11081
+ if (attachmentDownloadResult.length > 0) {
11082
+ setInlineAttachments((prev) => {
11083
+ var _a;
11084
+ // The new state should always be based on the previous one
11085
+ // otherwise there can be issues with renders
11086
+ const listOfAttachments = (_a = prev[messageId]) !== null && _a !== void 0 ? _a : {};
11087
+ for (const result of attachmentDownloadResult) {
11088
+ const { attachmentId, blobUrl } = result;
11089
+ listOfAttachments[attachmentId] = blobUrl;
11090
+ }
11091
+ return Object.assign(Object.assign({}, prev), { [messageId]: listOfAttachments });
11092
+ });
10909
11093
  }
10910
- }), [inlineAttachments, onFetchAttachments]);
11094
+ }), [onFetchAttachments]);
10911
11095
  const isAllChatMessagesLoadedRef = React.useRef(false);
10912
11096
  // isAllChatMessagesLoadedRef needs to be updated every time when a new adapter is set in order to display correct data
10913
11097
  // onLoadPreviousChatMessages is updated when a new adapter is set
@@ -10925,10 +11109,12 @@ const MessageThreadWrapper = (props) => {
10925
11109
  const messageIdSeenByMeRef = React.useRef('');
10926
11110
  const chatScrollDivRef = React.useRef(null);
10927
11111
  const isLoadingChatMessagesRef = React.useRef(false);
11112
+ const messages = React.useMemo(() => {
11113
+ return newMessages;
11114
+ }, [newMessages]);
10928
11115
  const messagesRef = React.useRef(messages);
10929
11116
  const setMessagesRef = (messagesWithAttachedValue) => {
10930
11117
  messagesRef.current = messagesWithAttachedValue;
10931
- setMessages(messagesWithAttachedValue);
10932
11118
  };
10933
11119
  const isAtBottomOfScrollRef = React.useRef(isAtBottomOfScroll);
10934
11120
  const setIsAtBottomOfScrollRef = (isAtBottomOfScrollValue) => {
@@ -11114,52 +11300,7 @@ const MessageThreadWrapper = (props) => {
11114
11300
  }, []);
11115
11301
  const localeStrings = useLocale$1().strings.messageThread;
11116
11302
  const strings = React.useMemo(() => (Object.assign(Object.assign({}, localeStrings), props.strings)), [localeStrings, props.strings]);
11117
- // To rerender the defaultChatMessageRenderer if app running across days(every new day chat time stamp need to be regenerated)
11118
- const defaultChatMessageRenderer = React.useCallback((messageProps) => {
11119
- var _a;
11120
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
11121
- if (inlineAttachments[messageProps.message.messageId] === undefined) {
11122
- setInlineAttachments((prev) => (Object.assign(Object.assign({}, prev), { [messageProps.message.messageId]: {} })));
11123
- }
11124
- if (messageProps.message.messageType === 'chat' ||
11125
- /* @conditional-compile-remove(data-loss-prevention) */ messageProps.message.messageType === 'blocked') {
11126
- return (React__default["default"].createElement(ChatMessageComponent, Object.assign({}, messageProps, { onRenderFileDownloads: onRenderFileDownloads,
11127
- /* @conditional-compile-remove(file-sharing) */
11128
- strings: strings, message: messageProps.message, userId: props.userId, remoteParticipantsCount: participantCount ? participantCount - 1 : 0, shouldOverlapAvatarAndMessage: isNarrow, onRenderAvatar: onRenderAvatar, showMessageStatus: showMessageStatus, messageStatus: messageProps.message.status, onActionButtonClick: onActionButtonClickMemo,
11129
- /* @conditional-compile-remove(date-time-customization) */
11130
- onDisplayDateTimeString: onDisplayDateTimeString,
11131
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
11132
- onFetchAttachments: onFetchInlineAttachment,
11133
- /* @conditional-compile-remove(image-gallery) */
11134
- onInlineImageClicked: onInlineImageClicked,
11135
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
11136
- attachmentsMap: (_a = inlineAttachments[messageProps.message.messageId]) !== null && _a !== void 0 ? _a : {},
11137
- /* @conditional-compile-remove(mention) */
11138
- mentionOptions: mentionOptions })));
11139
- }
11140
- return React__default["default"].createElement(React__default["default"].Fragment, null);
11141
- }, [
11142
- onRenderFileDownloads,
11143
- /* @conditional-compile-remove(file-sharing) */
11144
- strings,
11145
- props.userId,
11146
- participantCount,
11147
- isNarrow,
11148
- onRenderAvatar,
11149
- showMessageStatus,
11150
- onActionButtonClickMemo,
11151
- /* @conditional-compile-remove(date-time-customization) */
11152
- onDisplayDateTimeString,
11153
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
11154
- onFetchInlineAttachment,
11155
- /* @conditional-compile-remove(image-gallery) */
11156
- onInlineImageClicked,
11157
- /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
11158
- inlineAttachments,
11159
- /* @conditional-compile-remove(mention) */
11160
- mentionOptions
11161
- ]);
11162
- const defaultStatusRenderer = React.useCallback((message, status, participantCount, readCount) => {
11303
+ const defaultStatusRenderer = React.useCallback((message, participantCount, readCount, status) => {
11163
11304
  const onToggleToolTip = (isToggled) => {
11164
11305
  if (isToggled && readReceiptsBySenderIdRef.current) {
11165
11306
  setReadCountForHoveredIndicator(getParticipantsWhoHaveReadMessage(message, readReceiptsBySenderIdRef.current).length);
@@ -11173,87 +11314,56 @@ const MessageThreadWrapper = (props) => {
11173
11314
  remoteParticipantsCount: participantCount ? participantCount - 1 : 0 }));
11174
11315
  }, []);
11175
11316
  const theme = useTheme();
11176
- const chatMessageRenderStyles = useChatMessageRenderStyles();
11177
- const messagesToDisplay = React.useMemo(() => memoizeAllMessages((memoizedMessageFn) => {
11178
- return messages.map((message, index) => {
11179
- let key = message.messageId;
11180
- let statusToRender = undefined;
11181
- if (message.messageType === 'chat' ||
11182
- /* @conditional-compile-remove(data-loss-prevention) */ message.messageType === 'blocked') {
11183
- if ((!message.messageId || message.messageId === '') && 'clientMessageId' in message) {
11184
- key = message.clientMessageId;
11185
- }
11186
- if (showMessageStatus && message.mine) {
11187
- switch (message.messageId) {
11188
- case lastSeenChatMessage: {
11189
- statusToRender = 'seen';
11190
- break;
11191
- }
11192
- case lastSendingChatMessage: {
11193
- statusToRender = 'sending';
11194
- break;
11195
- }
11196
- case lastDeliveredChatMessage: {
11197
- statusToRender = 'delivered';
11198
- break;
11199
- }
11200
- }
11201
- }
11202
- if (message.mine && message.status === 'failed') {
11203
- statusToRender = 'failed';
11204
- }
11205
- }
11206
- return memoizedMessageFn(key !== null && key !== void 0 ? key : 'id_' + index, message, showMessageDate, showMessageStatus, onRenderAvatar, isNarrow, styles, onRenderMessageStatus, defaultStatusRenderer, defaultChatMessageRenderer, strings, theme, chatMessageRenderStyles,
11207
- // Temporary solution to make sure we re-render if attach attribute is changed.
11208
- // The proper fix should be in selector.
11209
- message.messageType === 'chat' ||
11210
- /* @conditional-compile-remove(data-loss-prevention) */ message.messageType === 'blocked'
11211
- ? message.attached
11212
- : undefined, statusToRender, participantCount, readCountForHoveredIndicator, onRenderMessage, onUpdateMessage, onCancelEditMessage, onDeleteMessage, onSendMessage, props.disableEditing);
11317
+ const messagesToDisplay = React.useMemo(() => {
11318
+ return memoizeAllMessages((memoizedMessageFn) => {
11319
+ return messages.map((message, index) => {
11320
+ return memoizedMessageFn(message, showMessageDate, showMessageStatus, strings, index, onUpdateMessage, onCancelEditMessage, onDeleteMessage, onSendMessage, props.disableEditing, lastDeliveredChatMessage, lastSeenChatMessage, lastSendingChatMessage);
11321
+ });
11213
11322
  });
11214
- }), [
11323
+ }, [
11324
+ lastDeliveredChatMessage,
11325
+ lastSeenChatMessage,
11326
+ lastSendingChatMessage,
11215
11327
  messages,
11216
- showMessageDate,
11217
- showMessageStatus,
11218
- onRenderAvatar,
11219
- isNarrow,
11220
- styles,
11221
- onRenderMessageStatus,
11222
- defaultStatusRenderer,
11223
- defaultChatMessageRenderer,
11224
- strings,
11225
- theme,
11226
- chatMessageRenderStyles,
11227
- participantCount,
11228
- readCountForHoveredIndicator,
11229
- onRenderMessage,
11230
- onUpdateMessage,
11231
11328
  onCancelEditMessage,
11232
11329
  onDeleteMessage,
11233
11330
  onSendMessage,
11331
+ onUpdateMessage,
11234
11332
  props.disableEditing,
11235
- lastSeenChatMessage,
11236
- lastSendingChatMessage,
11237
- lastDeliveredChatMessage
11333
+ showMessageDate,
11334
+ showMessageStatus,
11335
+ strings
11238
11336
  ]);
11239
11337
  const classes = useChatStyles();
11240
- const chatBody = React.useMemo(() => {
11241
- return (React__default["default"].createElement(LiveAnnouncer, null,
11338
+ return (React__default["default"].createElement("div", { className: react.mergeStyles(messageThreadWrapperContainerStyle), ref: chatThreadRef },
11339
+ existsNewChatMessage && !disableJumpToNewMessageButton && (React__default["default"].createElement("div", { className: react.mergeStyles(newMessageButtonContainerStyle, styles === null || styles === void 0 ? void 0 : styles.newMessageButtonContainer) }, onRenderJumpToNewMessageButton ? (onRenderJumpToNewMessageButton({ text: strings.newMessagesIndicator, onClick: scrollToBottom })) : (React__default["default"].createElement(DefaultJumpToNewMessageButton, { text: strings.newMessagesIndicator, onClick: scrollToBottom })))),
11340
+ React__default["default"].createElement(LiveAnnouncer, null,
11242
11341
  React__default["default"].createElement(FluentV9ThemeProvider, { v8Theme: theme },
11243
11342
  React__default["default"].createElement(reactChat.Chat
11244
11343
  // styles?.chatContainer used in className and style prop as style prop can't handle CSS selectors
11245
11344
  , {
11246
11345
  // styles?.chatContainer used in className and style prop as style prop can't handle CSS selectors
11247
- className: reactComponents.mergeClasses(classes.root, react.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)) }, messagesToDisplay))));
11248
- }, [theme, classes.root, styles === null || styles === void 0 ? void 0 : styles.chatContainer, messagesToDisplay]);
11249
- return (React__default["default"].createElement("div", { className: react.mergeStyles(messageThreadWrapperContainerStyle), ref: chatThreadRef },
11250
- existsNewChatMessage && !disableJumpToNewMessageButton && (React__default["default"].createElement("div", { className: react.mergeStyles(newMessageButtonContainerStyle, styles === null || styles === void 0 ? void 0 : styles.newMessageButtonContainer) }, onRenderJumpToNewMessageButton ? (onRenderJumpToNewMessageButton({ text: strings.newMessagesIndicator, onClick: scrollToBottom })) : (React__default["default"].createElement(DefaultJumpToNewMessageButton, { text: strings.newMessagesIndicator, onClick: scrollToBottom })))),
11251
- chatBody));
11252
- };
11253
- const onRenderFileDownloadsTrampoline = (props) => {
11254
- /* @conditional-compile-remove(file-sharing) */
11255
- return props.onRenderFileDownloads;
11256
- };
11346
+ className: reactComponents.mergeClasses(classes.root, react.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)) }, messagesToDisplay.map((message) => {
11347
+ return (React__default["default"].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,
11348
+ /* @conditional-compile-remove(file-sharing) */
11349
+ fileDownloadHandler: props.fileDownloadHandler,
11350
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
11351
+ onFetchInlineAttachment: onFetchInlineAttachment,
11352
+ /* @conditional-compile-remove(teams-inline-images-and-file-sharing) */
11353
+ inlineAttachments: inlineAttachments,
11354
+ /* @conditional-compile-remove(image-gallery) */
11355
+ onInlineImageClicked: onInlineImageClicked,
11356
+ /* @conditional-compile-remove(date-time-customization) */
11357
+ onDisplayDateTimeString: onDisplayDateTimeString,
11358
+ /* @conditional-compile-remove(mention) */
11359
+ mentionOptions: mentionOptions,
11360
+ /* @conditional-compile-remove(file-sharing) */
11361
+ onRenderFileDownloads: onRenderFileDownloads })));
11362
+ }))))));
11363
+ };
11364
+ const MemoChatMessageComponentWrapper = React__default["default"].memo((obj) => {
11365
+ return React__default["default"].createElement(ChatMessageComponentWrapper, Object.assign({}, obj));
11366
+ });
11257
11367
 
11258
11368
  // Copyright (c) Microsoft Corporation.
11259
11369
  /**
@@ -14122,13 +14232,13 @@ const OverflowGallery = (props) => {
14122
14232
  const scrollableHorizontalGalleryContainerStyles = React.useMemo(() => {
14123
14233
  if (isNarrow && parentWidth) {
14124
14234
  return {
14125
- width: props.layout === 'default'
14126
- ? `${_convertPxToRem(parentWidth) - 1}rem`
14127
- : `${_convertPxToRem(parentWidth) - SMALL_FLOATING_MODAL_SIZE_REM.width - 1}rem`
14235
+ width: shouldFloatLocalVideo
14236
+ ? `${_convertPxToRem(parentWidth) - SMALL_FLOATING_MODAL_SIZE_REM.width - 1}rem`
14237
+ : `${_convertPxToRem(parentWidth) - 1}rem`
14128
14238
  };
14129
14239
  }
14130
14240
  return undefined;
14131
- }, [isNarrow, parentWidth, props.layout]);
14241
+ }, [isNarrow, parentWidth, shouldFloatLocalVideo]);
14132
14242
  /* @conditional-compile-remove(vertical-gallery) */
14133
14243
  if (overflowGalleryPosition === 'verticalRight') {
14134
14244
  return (React__default["default"].createElement(ResponsiveVerticalGallery, { key: "responsive-vertical-gallery", containerStyles: containerStyles, verticalGalleryStyles: galleryStyles, controlBarHeightRem: HORIZONTAL_GALLERY_BUTTON_WIDTH, gapHeightRem: HORIZONTAL_GALLERY_GAP, isShort: isShort, onFetchTilesToRender: onFetchTilesToRender, onChildrenPerPageChange: onChildrenPerPageChange }, overflowGalleryElements ? overflowGalleryElements : [React__default["default"].createElement(React__default["default"].Fragment, null)]));
@@ -14210,9 +14320,7 @@ const DefaultLayout = (props) => {
14210
14320
  /* @conditional-compile-remove(vertical-gallery) */
14211
14321
  overflowGalleryPosition: overflowGalleryPosition, onFetchTilesToRender: setIndexesToRender, onChildrenPerPageChange: (n) => {
14212
14322
  childrenPerPage.current = n;
14213
- },
14214
- /* @conditional-compile-remove(gallery-layouts) */
14215
- layout: 'default', parentWidth: parentWidth }));
14323
+ }, parentWidth: parentWidth }));
14216
14324
  }, [
14217
14325
  isNarrow,
14218
14326
  /* @conditional-compile-remove(vertical-gallery) */ isShort,
@@ -15076,7 +15184,7 @@ const FloatingLocalVideoLayout = (props) => {
15076
15184
  /* @conditional-compile-remove(vertical-gallery) */
15077
15185
  , {
15078
15186
  /* @conditional-compile-remove(vertical-gallery) */
15079
- isShort: isShort, onFetchTilesToRender: setIndexesToRender, isNarrow: isNarrow, shouldFloatLocalVideo: true, overflowGalleryElements: overflowGalleryTiles, horizontalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery,
15187
+ isShort: isShort, onFetchTilesToRender: setIndexesToRender, isNarrow: isNarrow, shouldFloatLocalVideo: !!localVideoComponent, overflowGalleryElements: overflowGalleryTiles, horizontalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery,
15080
15188
  /* @conditional-compile-remove(vertical-gallery) */
15081
15189
  verticalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.verticalGallery,
15082
15190
  /* @conditional-compile-remove(vertical-gallery) */
@@ -15092,7 +15200,8 @@ const FloatingLocalVideoLayout = (props) => {
15092
15200
  /* @conditional-compile-remove(vertical-gallery) */ overflowGalleryPosition,
15093
15201
  setIndexesToRender,
15094
15202
  /* @conditional-compile-remove(vertical-gallery) */ styles === null || styles === void 0 ? void 0 : styles.verticalGallery,
15095
- parentWidth
15203
+ parentWidth,
15204
+ localVideoComponent
15096
15205
  ]);
15097
15206
  return (React__default["default"].createElement(react.Stack, { styles: rootLayoutStyle },
15098
15207
  wrappedLocalVideoComponent,
@@ -15205,7 +15314,7 @@ const SpeakerVideoLayout = (props) => {
15205
15314
  /* @conditional-compile-remove(vertical-gallery) */
15206
15315
  , {
15207
15316
  /* @conditional-compile-remove(vertical-gallery) */
15208
- isShort: isShort, onFetchTilesToRender: setIndexesToRender, isNarrow: isNarrow, shouldFloatLocalVideo: true, overflowGalleryElements: overflowGalleryTiles, horizontalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery,
15317
+ isShort: isShort, onFetchTilesToRender: setIndexesToRender, isNarrow: isNarrow, shouldFloatLocalVideo: !!localVideoComponent, overflowGalleryElements: overflowGalleryTiles, horizontalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.horizontalGallery,
15209
15318
  /* @conditional-compile-remove(vertical-gallery) */
15210
15319
  verticalGalleryStyles: styles === null || styles === void 0 ? void 0 : styles.verticalGallery,
15211
15320
  /* @conditional-compile-remove(vertical-gallery) */
@@ -15221,7 +15330,8 @@ const SpeakerVideoLayout = (props) => {
15221
15330
  /* @conditional-compile-remove(vertical-gallery) */ overflowGalleryPosition,
15222
15331
  setIndexesToRender,
15223
15332
  /* @conditional-compile-remove(vertical-gallery) */ styles === null || styles === void 0 ? void 0 : styles.verticalGallery,
15224
- parentWidth
15333
+ parentWidth,
15334
+ localVideoComponent
15225
15335
  ]);
15226
15336
  return (React__default["default"].createElement(react.Stack, { styles: rootLayoutStyle },
15227
15337
  wrappedLocalVideoComponent,
@@ -22642,9 +22752,15 @@ const ChatScreen = (props) => {
22642
22752
  const onRenderAvatarCallback = React.useCallback((userId, defaultOptions) => {
22643
22753
  return (React__default["default"].createElement(AvatarPersona, Object.assign({ userId: userId, hidePersonaDetails: true }, defaultOptions, { dataProvider: onFetchAvatarPersonaData })));
22644
22754
  }, [onFetchAvatarPersonaData]);
22645
- const messageThreadStyles = Object.assign({}, messageThreadChatCompositeStyles(theme.semanticColors.bodyBackground), styles === null || styles === void 0 ? void 0 : styles.messageThread);
22646
- const typingIndicatorStyles = Object.assign({}, styles === null || styles === void 0 ? void 0 : styles.typingIndicator);
22647
- const sendBoxStyles = Object.assign({}, styles === null || styles === void 0 ? void 0 : styles.sendBox);
22755
+ const messageThreadStyles = React.useMemo(() => {
22756
+ return Object.assign({}, messageThreadChatCompositeStyles(theme.semanticColors.bodyBackground), styles === null || styles === void 0 ? void 0 : styles.messageThread);
22757
+ }, [styles === null || styles === void 0 ? void 0 : styles.messageThread, theme.semanticColors.bodyBackground]);
22758
+ const typingIndicatorStyles = React.useMemo(() => {
22759
+ return Object.assign({}, styles === null || styles === void 0 ? void 0 : styles.typingIndicator);
22760
+ }, [styles === null || styles === void 0 ? void 0 : styles.typingIndicator]);
22761
+ const sendBoxStyles = React.useMemo(() => {
22762
+ return Object.assign({}, styles === null || styles === void 0 ? void 0 : styles.sendBox);
22763
+ }, [styles === null || styles === void 0 ? void 0 : styles.sendBox]);
22648
22764
  const userId = toFlatCommunicationIdentifier(adapter.getState().userId);
22649
22765
  const fileUploadButtonOnChange = React.useCallback((files) => {
22650
22766
  if (!files) {
@@ -22795,21 +22911,12 @@ const ChatScreen = (props) => {
22795
22911
  const ChatComposite = (props) => {
22796
22912
  const { adapter, options, onFetchAvatarPersonaData, onRenderTypingIndicator, onRenderMessage, onFetchParticipantMenuItems } = props;
22797
22913
  const formFactor = props['formFactor'] || 'desktop';
22798
- /**
22799
- * @TODO Remove this function and pass the props directly when file-sharing is promoted to stable.
22800
- * @private
22801
- */
22802
- // eslint-disable-next-line @typescript-eslint/explicit-function-return-type
22803
- const fileSharingOptions = () => {
22804
- /* @conditional-compile-remove(file-sharing) */
22805
- return {
22806
- fileSharing: options === null || options === void 0 ? void 0 : options.fileSharing
22807
- };
22808
- };
22809
22914
  return (React__default["default"].createElement("div", { className: chatScreenContainerStyle },
22810
22915
  React__default["default"].createElement(BaseProvider, Object.assign({}, props),
22811
22916
  React__default["default"].createElement(ChatAdapterProvider, { adapter: adapter },
22812
- React__default["default"].createElement(ChatScreen, Object.assign({ formFactor: formFactor, options: options, onFetchAvatarPersonaData: onFetchAvatarPersonaData, onRenderTypingIndicator: onRenderTypingIndicator, onRenderMessage: onRenderMessage, onFetchParticipantMenuItems: onFetchParticipantMenuItems }, fileSharingOptions()))))));
22917
+ React__default["default"].createElement(ChatScreen, { formFactor: formFactor, options: options, onFetchAvatarPersonaData: onFetchAvatarPersonaData, onRenderTypingIndicator: onRenderTypingIndicator, onRenderMessage: onRenderMessage, onFetchParticipantMenuItems: onFetchParticipantMenuItems,
22918
+ /* @conditional-compile-remove(file-sharing) */
22919
+ fileSharing: options === null || options === void 0 ? void 0 : options.fileSharing })))));
22813
22920
  };
22814
22921
 
22815
22922
  // Copyright (c) Microsoft Corporation.
@@ -26054,7 +26161,7 @@ const SidePane = (props) => {
26054
26161
  props.onPeopleButtonClicked,
26055
26162
  props.disablePeopleButton,
26056
26163
  props.disableChatButton,
26057
- sidePaneRenderer,
26164
+ sidePaneRenderer === null || sidePaneRenderer === void 0 ? void 0 : sidePaneRenderer.id,
26058
26165
  closePane
26059
26166
  ]);
26060
26167
  const HeaderToRender = props.mobileView && (overrideSidePaneId === 'chat' || (sidePaneRenderer === null || sidePaneRenderer === void 0 ? void 0 : sidePaneRenderer.id) === 'people') ? LegacyHeader : Header();
@@ -30117,11 +30224,11 @@ const CALL_REJECTED_CODE = 603;
30117
30224
  * @private
30118
30225
  */
30119
30226
  class CallingSoundSubscriber {
30120
- constructor(call, locator, sounds) {
30227
+ constructor(call, callee, sounds) {
30121
30228
  this.onCallStateChanged = () => {
30122
30229
  this.call.on('stateChanged', () => {
30123
30230
  var _a, _b, _c, _d, _e;
30124
- if (shouldPlayRingingSound(this.call, this.callLocator) && ((_a = this.soundsLoaded) === null || _a === void 0 ? void 0 : _a.callRingingSound)) {
30231
+ if (isPSTNCall(this.call, this.callee) && ((_a = this.soundsLoaded) === null || _a === void 0 ? void 0 : _a.callRingingSound)) {
30125
30232
  this.soundsLoaded.callRingingSound.loop = true;
30126
30233
  this.playSound(this.soundsLoaded.callRingingSound);
30127
30234
  }
@@ -30141,7 +30248,7 @@ class CallingSoundSubscriber {
30141
30248
  });
30142
30249
  };
30143
30250
  this.call = call;
30144
- this.callLocator = locator;
30251
+ this.callee = callee;
30145
30252
  if (sounds) {
30146
30253
  this.soundsLoaded = this.loadSounds(sounds);
30147
30254
  this.subscribeCallSoundEvents();
@@ -30161,17 +30268,17 @@ class CallingSoundSubscriber {
30161
30268
  var _a, _b, _c;
30162
30269
  let callEndedSound;
30163
30270
  if (sounds === null || sounds === void 0 ? void 0 : sounds.callEnded) {
30164
- callEndedSound = new Audio((_a = sounds === null || sounds === void 0 ? void 0 : sounds.callEnded) === null || _a === void 0 ? void 0 : _a.path);
30271
+ callEndedSound = new Audio((_a = sounds === null || sounds === void 0 ? void 0 : sounds.callEnded) === null || _a === void 0 ? void 0 : _a.url);
30165
30272
  callEndedSound.preload = 'auto';
30166
30273
  }
30167
30274
  let callRingingSound;
30168
30275
  if (sounds === null || sounds === void 0 ? void 0 : sounds.callRinging) {
30169
- callRingingSound = new Audio((_b = sounds === null || sounds === void 0 ? void 0 : sounds.callRinging) === null || _b === void 0 ? void 0 : _b.path);
30276
+ callRingingSound = new Audio((_b = sounds === null || sounds === void 0 ? void 0 : sounds.callRinging) === null || _b === void 0 ? void 0 : _b.url);
30170
30277
  callRingingSound.preload = 'auto';
30171
30278
  }
30172
30279
  let callBusySound;
30173
30280
  if (sounds === null || sounds === void 0 ? void 0 : sounds.callBusy) {
30174
- callBusySound = new Audio((_c = sounds === null || sounds === void 0 ? void 0 : sounds.callBusy) === null || _c === void 0 ? void 0 : _c.path);
30281
+ callBusySound = new Audio((_c = sounds === null || sounds === void 0 ? void 0 : sounds.callBusy) === null || _c === void 0 ? void 0 : _c.url);
30175
30282
  callBusySound.preload = 'auto';
30176
30283
  }
30177
30284
  return {
@@ -30190,12 +30297,11 @@ class CallingSoundSubscriber {
30190
30297
  * Helper function to allow the calling sound subscriber to determine when to play the ringing
30191
30298
  * sound when making an outbound call.
30192
30299
  */
30193
- const shouldPlayRingingSound = (call, locator) => {
30300
+ const isPSTNCall = (call, callee) => {
30194
30301
  /* @conditional-compile-remove(calling-sounds) */
30195
- const callee = locator.participantIds;
30196
- /* @conditional-compile-remove(calling-sounds) */
30197
- if (callee.length >= 1 &&
30198
- !communicationCommon.isPhoneNumberIdentifier(fromFlatCommunicationIdentifier(callee[0])) &&
30302
+ if (callee &&
30303
+ callee.length >= 1 &&
30304
+ !communicationCommon.isPhoneNumberIdentifier(callee[0]) &&
30199
30305
  (call.state === 'Ringing' || call.state === 'Connecting')) {
30200
30306
  return true;
30201
30307
  }
@@ -30220,7 +30326,7 @@ class CallContext {
30220
30326
  constructor(clientState, isTeamsCall,
30221
30327
  /* @conditional-compile-remove(rooms) */
30222
30328
  isRoomsCall, options) {
30223
- var _a, _b, _c, _d, _e;
30329
+ var _a, _b, _c, _d;
30224
30330
  this.emitter = new EventEmitter.EventEmitter();
30225
30331
  this.state = {
30226
30332
  isLocalPreviewMicrophoneEnabled: false,
@@ -30228,6 +30334,7 @@ class CallContext {
30228
30334
  displayName: (_a = clientState.callAgent) === null || _a === void 0 ? void 0 : _a.displayName,
30229
30335
  devices: clientState.deviceManager,
30230
30336
  call: undefined,
30337
+ /* @conditional-compile-remove(calling-sounds) */ targetCallees: undefined,
30231
30338
  page: 'configuration',
30232
30339
  latestErrors: clientState.latestErrors,
30233
30340
  isTeamsCall,
@@ -30240,9 +30347,9 @@ class CallContext {
30240
30347
  onResolveVideoEffectDependency: (_c = options === null || options === void 0 ? void 0 : options.videoBackgroundOptions) === null || _c === void 0 ? void 0 : _c.onResolveDependency,
30241
30348
  /* @conditional-compile-remove(video-background-effects) */ selectedVideoBackgroundEffect: undefined,
30242
30349
  cameraStatus: undefined,
30243
- /* @conditional-compile-remove(calling-sounds) */ sounds: (_d = options === null || options === void 0 ? void 0 : options.soundOptions) === null || _d === void 0 ? void 0 : _d.callingSounds
30350
+ /* @conditional-compile-remove(calling-sounds) */ sounds: options === null || options === void 0 ? void 0 : options.callingSounds
30244
30351
  };
30245
- this.emitter.setMaxListeners((_e = options === null || options === void 0 ? void 0 : options.maxListeners) !== null && _e !== void 0 ? _e : 50);
30352
+ this.emitter.setMaxListeners((_d = options === null || options === void 0 ? void 0 : options.maxListeners) !== null && _d !== void 0 ? _d : 50);
30246
30353
  this.bindPublicMethods();
30247
30354
  this.displayNameModifier = (options === null || options === void 0 ? void 0 : options.onFetchProfile)
30248
30355
  ? createProfileStateModifier(options.onFetchProfile, () => {
@@ -30274,6 +30381,10 @@ class CallContext {
30274
30381
  setCurrentCallId(callId) {
30275
30382
  this.callId = callId;
30276
30383
  }
30384
+ /* @conditional-compile-remove(calling-sounds) */
30385
+ setTargetCallee(targetCallees) {
30386
+ this.setState(Object.assign(Object.assign({}, this.state), { targetCallees }));
30387
+ }
30277
30388
  onCallEnded(handler) {
30278
30389
  this.emitter.on('callEnded', handler);
30279
30390
  }
@@ -30888,6 +30999,8 @@ class AzureCommunicationCallAdapter {
30888
30999
  }
30889
31000
  return backendId;
30890
31001
  });
31002
+ /* @conditional-compile-remove(calling-sounds) */
31003
+ this.context.setTargetCallee(idsToAdd);
30891
31004
  const call = this.handlers.onStartCall(idsToAdd, options);
30892
31005
  if (!call) {
30893
31006
  throw new Error('Unable to start call.');
@@ -31026,7 +31139,7 @@ class AzureCommunicationCallAdapter {
31026
31139
  var _a, _b, _c, _d, _e, _f, _g, _h;
31027
31140
  /* @conditional-compile-remove(calling-sounds) */
31028
31141
  if (this.call) {
31029
- this.callingSoundSubscriber = new CallingSoundSubscriber(this.call, this.locator, this.getState().sounds);
31142
+ this.callingSoundSubscriber = new CallingSoundSubscriber(this.call, this.getState().targetCallees, this.getState().sounds);
31030
31143
  }
31031
31144
  (_a = this.call) === null || _a === void 0 ? void 0 : _a.on('remoteParticipantsUpdated', this.onRemoteParticipantsUpdated.bind(this));
31032
31145
  (_b = this.call) === null || _b === void 0 ? void 0 : _b.on('isMutedChanged', this.isMyMutedChanged.bind(this));
@@ -31149,6 +31262,7 @@ class AzureCommunicationCallAdapter {
31149
31262
  if (((_a = this.call) === null || _a === void 0 ? void 0 : _a.role) === 'Consumer') {
31150
31263
  (_b = this.call) === null || _b === void 0 ? void 0 : _b.feature(communicationCalling.Features.RaiseHand).lowerHand();
31151
31264
  }
31265
+ this.emitter.emit('roleChanged');
31152
31266
  }
31153
31267
  callIdChanged() {
31154
31268
  var _a;
@@ -31982,10 +32096,8 @@ const CallWithChatScreen = (props) => {
31982
32096
  callWithChatAdapter.offStateChange(updateCallWithChatPage);
31983
32097
  };
31984
32098
  }, [callWithChatAdapter]);
31985
- const chatProps = React.useMemo(() => {
31986
- return {
31987
- adapter: new CallWithChatBackedChatAdapter(callWithChatAdapter)
31988
- };
32099
+ const chatAdapter = React.useMemo(() => {
32100
+ return new CallWithChatBackedChatAdapter(callWithChatAdapter);
31989
32101
  }, [callWithChatAdapter]);
31990
32102
  /** Constant setting of id for the parent stack of the composite */
31991
32103
  const compositeParentDivId = reactHooks.useId('callWithChatCompositeParentDiv-internal');
@@ -32034,7 +32146,7 @@ const CallWithChatScreen = (props) => {
32034
32146
  disabled: chatButtonDisabled
32035
32147
  }
32036
32148
  : undefined, [chatButtonDisabled, mobileView, toggleChat, showChatButton]);
32037
- const unreadChatMessagesCount = useUnreadMessagesTracker(chatProps.adapter, isChatOpen);
32149
+ const unreadChatMessagesCount = useUnreadMessagesTracker(chatAdapter, isChatOpen);
32038
32150
  const customChatButton = React.useCallback((args) => ({
32039
32151
  placement: mobileView ? 'primary' : 'secondary',
32040
32152
  onRenderButton: () => (React__default["default"].createElement(ChatButtonWithUnreadMessagesBadge, { checked: isChatOpen, showLabel: args.displayType !== 'compact', onClick: toggleChat, disabled: chatButtonDisabled, strings: chatButtonStrings, styles: commonButtonStyles, newMessageLabel: callWithChatStrings.chatButtonNewMessageNotificationLabel, unreadChatMessagesCount: unreadChatMessagesCount,
@@ -32108,14 +32220,14 @@ const CallWithChatScreen = (props) => {
32108
32220
  /* @conditional-compile-remove(custom-branding) */
32109
32221
  props.backgroundImage
32110
32222
  ]);
32111
- const onRenderChatContent = React.useCallback(() => (React__default["default"].createElement(ChatComposite, Object.assign({}, chatProps, { fluentTheme: theme, options: {
32223
+ const onRenderChatContent = React.useCallback(() => (React__default["default"].createElement(ChatComposite, { adapter: chatAdapter, fluentTheme: theme, options: {
32112
32224
  topic: false,
32113
32225
  /* @conditional-compile-remove(chat-composite-participant-pane) */
32114
32226
  participantPane: false,
32115
32227
  /* @conditional-compile-remove(file-sharing) */
32116
32228
  fileSharing: props.fileSharing
32117
- }, onFetchAvatarPersonaData: props.onFetchAvatarPersonaData }))), [
32118
- chatProps,
32229
+ }, onFetchAvatarPersonaData: props.onFetchAvatarPersonaData })), [
32230
+ chatAdapter,
32119
32231
  /* @conditional-compile-remove(file-sharing) */ props.fileSharing,
32120
32232
  props.onFetchAvatarPersonaData,
32121
32233
  theme