botframework-webchat-component 4.15.0 → 4.15.2-main.20220413.d89850f

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 (125) hide show
  1. package/lib/Activity/CarouselLayout.js +3 -3
  2. package/lib/Activity/Speak.d.ts +2 -2
  3. package/lib/Activity/Speak.d.ts.map +1 -1
  4. package/lib/Activity/Speak.js +14 -7
  5. package/lib/Activity/StackedLayout.d.ts +5 -3
  6. package/lib/Activity/StackedLayout.d.ts.map +1 -1
  7. package/lib/Activity/StackedLayout.js +15 -20
  8. package/lib/Attachment/FileContent.js +2 -2
  9. package/lib/BasicSendBox.d.ts.map +1 -1
  10. package/lib/BasicSendBox.js +7 -8
  11. package/lib/BasicToast.js +1 -1
  12. package/lib/BasicToaster.js +1 -1
  13. package/lib/BasicTranscript.d.ts.map +1 -1
  14. package/lib/BasicTranscript.js +30 -15
  15. package/lib/BasicTypingIndicator.js +1 -1
  16. package/lib/Composer.js +3 -3
  17. package/lib/ConnectivityStatus/Connecting.js +1 -1
  18. package/lib/Dictation.js +1 -1
  19. package/lib/Middleware/Activity/createCoreMiddleware.d.ts.map +1 -1
  20. package/lib/Middleware/Activity/createCoreMiddleware.js +9 -12
  21. package/lib/Middleware/ActivityStatus/SendStatus/SendFailedRetry.js +1 -1
  22. package/lib/Middleware/ActivityStatus/SendStatus/SendStatus.d.ts +2 -2
  23. package/lib/Middleware/ActivityStatus/SendStatus/SendStatus.d.ts.map +1 -1
  24. package/lib/Middleware/ActivityStatus/SendStatus/SendStatus.js +4 -3
  25. package/lib/Middleware/ActivityStatus/Timestamp.d.ts +2 -2
  26. package/lib/Middleware/ActivityStatus/Timestamp.d.ts.map +1 -1
  27. package/lib/Middleware/ActivityStatus/Timestamp.js +4 -2
  28. package/lib/Middleware/ActivityStatus/createSendStatusMiddleware.js +2 -2
  29. package/lib/ReactWebChat.js +2 -2
  30. package/lib/ScreenReaderActivity.js +1 -1
  31. package/lib/ScreenReaderText.js +1 -1
  32. package/lib/SendBox/AutoResizeTextArea.js +1 -1
  33. package/lib/SendBox/IconButton.js +1 -1
  34. package/lib/SendBox/MicrophoneButton.js +1 -1
  35. package/lib/SendBox/SendButton.js +1 -1
  36. package/lib/SendBox/SuggestedAction.js +2 -2
  37. package/lib/SendBox/SuggestedActions.js +2 -2
  38. package/lib/SendBox/TextBox.js +3 -3
  39. package/lib/SendBox/UploadButton.js +3 -3
  40. package/lib/Styles/StyleSet/Bubble.js +2 -2
  41. package/lib/Styles/StyleSet/CarouselFilmStrip.js +2 -2
  42. package/lib/Styles/StyleSet/CarouselFilmStripAttachment.js +2 -2
  43. package/lib/Styles/createStyleSet.d.ts +1 -1
  44. package/lib/Styles/createStyleSet.js +2 -2
  45. package/lib/Transcript/ActivityRow.d.ts +2 -2
  46. package/lib/Transcript/ActivityRow.d.ts.map +1 -1
  47. package/lib/Transcript/ActivityRow.js +4 -2
  48. package/lib/Transcript/ActivityTextAlt.js +1 -1
  49. package/lib/Transcript/FocusTrap.js +1 -1
  50. package/lib/Transcript/KeyboardHelp.js +1 -1
  51. package/lib/Transcript/LiveRegionTranscript.d.ts.map +1 -1
  52. package/lib/Transcript/LiveRegionTranscript.js +22 -7
  53. package/lib/Transcript/useActivityAccessibleName.d.ts +2 -2
  54. package/lib/Transcript/useActivityAccessibleName.d.ts.map +1 -1
  55. package/lib/Transcript/useActivityAccessibleName.js +2 -2
  56. package/lib/Transcript/useTypistNames.d.ts +3 -0
  57. package/lib/Transcript/useTypistNames.d.ts.map +1 -0
  58. package/lib/Transcript/useTypistNames.js +61 -0
  59. package/lib/Utils/AccessKeySink/Surface.js +1 -1
  60. package/lib/Utils/AccessibleButton.js +1 -1
  61. package/lib/Utils/AccessibleInputText.js +1 -1
  62. package/lib/Utils/AccessibleTextArea.js +1 -1
  63. package/lib/Utils/CroppedImage.js +1 -1
  64. package/lib/Utils/FocusRedirector.js +1 -1
  65. package/lib/Utils/InlineMarkdown.js +3 -3
  66. package/lib/Utils/TypeFocusSink/FocusBox.js +1 -1
  67. package/lib/Utils/addTargetBlankToHyperlinksMarkdown.js +2 -2
  68. package/lib/Utils/downscaleImageToDataURL/index.js +1 -1
  69. package/lib/Utils/getActivityUniqueId.js +1 -1
  70. package/lib/connectToWebChat.js +2 -2
  71. package/lib/hooks/internal/BypassSpeechSynthesisPonyfill.js +3 -3
  72. package/lib/hooks/internal/useMemoize.d.ts +1 -1
  73. package/lib/hooks/internal/useMemoize.d.ts.map +1 -1
  74. package/lib/hooks/internal/useMemoize.js +1 -1
  75. package/lib/hooks/useObserveTranscriptFocus.d.ts +2 -2
  76. package/lib/hooks/useObserveTranscriptFocus.d.ts.map +1 -1
  77. package/lib/hooks/useObserveTranscriptFocus.js +1 -1
  78. package/lib/hooks/useSendFiles.d.ts.map +1 -1
  79. package/lib/hooks/useSendFiles.js +4 -4
  80. package/lib/index.d.ts +3 -3
  81. package/lib/index.js +5 -5
  82. package/lib/providers/ActivityTree/ActivityTreeComposer.d.ts.map +1 -1
  83. package/lib/providers/ActivityTree/ActivityTreeComposer.js +2 -2
  84. package/lib/providers/ActivityTree/private/types.d.ts +2 -2
  85. package/lib/providers/ActivityTree/private/types.d.ts.map +1 -1
  86. package/lib/providers/ActivityTree/private/useActivitiesWithRenderer.d.ts +2 -2
  87. package/lib/providers/ActivityTree/private/useActivitiesWithRenderer.d.ts.map +1 -1
  88. package/lib/providers/ActivityTree/private/useActivitiesWithRenderer.js +1 -1
  89. package/lib/providers/ActivityTree/private/useActivityTreeWithRenderer.d.ts.map +1 -1
  90. package/lib/providers/ActivityTree/private/useActivityTreeWithRenderer.js +1 -1
  91. package/lib/providers/LiveRegionTwin/LiveRegionTwinComposer.d.ts +2 -0
  92. package/lib/providers/LiveRegionTwin/LiveRegionTwinComposer.d.ts.map +1 -1
  93. package/lib/providers/LiveRegionTwin/LiveRegionTwinComposer.js +10 -6
  94. package/lib/providers/LiveRegionTwin/private/LiveRegionTwinContainer.d.ts +1 -0
  95. package/lib/providers/LiveRegionTwin/private/LiveRegionTwinContainer.d.ts.map +1 -1
  96. package/lib/providers/LiveRegionTwin/private/LiveRegionTwinContainer.js +22 -8
  97. package/lib/providers/TranscriptFocus/TranscriptFocusComposer.js +1 -1
  98. package/package.json +24 -20
  99. package/src/Activity/Speak.tsx +21 -18
  100. package/src/Activity/StackedLayout.tsx +20 -26
  101. package/src/Attachment/FileContent.tsx +1 -1
  102. package/src/BasicSendBox.tsx +3 -2
  103. package/src/BasicTranscript.tsx +29 -13
  104. package/src/Middleware/Activity/createCoreMiddleware.tsx +5 -10
  105. package/src/Middleware/ActivityStatus/SendStatus/SendStatus.tsx +5 -3
  106. package/src/Middleware/ActivityStatus/Timestamp.tsx +5 -3
  107. package/src/SendBox/SuggestedAction.tsx +1 -1
  108. package/src/SendBox/SuggestedActions.tsx +1 -1
  109. package/src/Transcript/ActivityRow.tsx +4 -5
  110. package/src/Transcript/ActivityTextAlt.tsx +2 -3
  111. package/src/Transcript/LiveRegionTranscript.tsx +27 -12
  112. package/src/Transcript/useActivityAccessibleName.ts +3 -4
  113. package/src/Transcript/useTypistNames.ts +37 -0
  114. package/src/Utils/getActivityUniqueId.ts +2 -2
  115. package/src/hooks/internal/useMemoize.ts +6 -6
  116. package/src/hooks/useObserveTranscriptFocus.ts +2 -2
  117. package/src/hooks/useSendFiles.ts +7 -5
  118. package/src/providers/ActivityTree/ActivityTreeComposer.tsx +2 -2
  119. package/src/providers/ActivityTree/private/types.ts +2 -2
  120. package/src/providers/ActivityTree/private/useActivitiesWithRenderer.ts +4 -6
  121. package/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts +6 -11
  122. package/src/providers/LiveRegionTwin/LiveRegionTwinComposer.tsx +10 -3
  123. package/src/providers/LiveRegionTwin/private/LiveRegionTwinContainer.tsx +22 -6
  124. package/lib/Middleware/GroupActivities/createCoreMiddleware.js +0 -69
  125. package/src/Middleware/GroupActivities/createCoreMiddleware.js +0 -57
@@ -1,7 +1,8 @@
1
- import { Constants, DirectLineActivity } from 'botframework-webchat-core';
1
+ import { Constants } from 'botframework-webchat-core';
2
2
  import { hooks } from 'botframework-webchat-api';
3
3
  import PropTypes from 'prop-types';
4
4
  import React, { FC, useCallback } from 'react';
5
+ import type { WebChatActivity } from 'botframework-webchat-core';
5
6
 
6
7
  import connectToWebChat from '../../../connectToWebChat';
7
8
  import ScreenReaderText from '../../../ScreenReaderText';
@@ -33,7 +34,7 @@ const connectSendStatus = (...selectors) =>
33
34
  );
34
35
 
35
36
  type SendStatusProps = {
36
- activity: DirectLineActivity;
37
+ activity: WebChatActivity;
37
38
  sendState: 'sending' | 'send failed' | 'sent';
38
39
  };
39
40
 
@@ -72,9 +73,10 @@ const SendStatus: FC<SendStatusProps> = ({ activity, sendState }) => {
72
73
  };
73
74
 
74
75
  SendStatus.propTypes = {
76
+ // PropTypes cannot fully capture TypeScript types.
77
+ // @ts-ignore
75
78
  activity: PropTypes.shape({
76
79
  channelData: PropTypes.shape({
77
- clientTimestamp: PropTypes.string,
78
80
  state: PropTypes.string
79
81
  })
80
82
  }).isRequired,
@@ -1,8 +1,8 @@
1
- import { DirectLineActivity } from 'botframework-webchat-core';
2
1
  import { hooks } from 'botframework-webchat-api';
3
2
  import classNames from 'classnames';
4
3
  import PropTypes from 'prop-types';
5
4
  import React, { FC } from 'react';
5
+ import type { WebChatActivity } from 'botframework-webchat-core';
6
6
 
7
7
  import AbsoluteTime from './AbsoluteTime';
8
8
  import RelativeTime from './RelativeTime';
@@ -11,7 +11,7 @@ import useStyleSet from '../../hooks/useStyleSet';
11
11
  const { useStyleOptions } = hooks;
12
12
 
13
13
  type TimestampProps = {
14
- activity: DirectLineActivity;
14
+ activity: WebChatActivity;
15
15
  className?: string;
16
16
  };
17
17
 
@@ -40,8 +40,10 @@ Timestamp.defaultProps = {
40
40
  };
41
41
 
42
42
  Timestamp.propTypes = {
43
+ // PropTypes cannot fully capture TypeScript types.
44
+ // @ts-ignore
43
45
  activity: PropTypes.shape({
44
- timestamp: PropTypes.string.isRequired
46
+ timestamp: PropTypes.string
45
47
  }).isRequired,
46
48
  className: PropTypes.string
47
49
  };
@@ -2,6 +2,7 @@ import { hooks } from 'botframework-webchat-api';
2
2
  import classNames from 'classnames';
3
3
  import PropTypes from 'prop-types';
4
4
  import React, { MouseEventHandler, useCallback, useRef, VFC } from 'react';
5
+ import type { DirectLineCardAction } from 'botframework-webchat-core';
5
6
 
6
7
  import AccessibleButton from '../Utils/AccessibleButton';
7
8
  import connectToWebChat from '../connectToWebChat';
@@ -13,7 +14,6 @@ import useScrollToEnd from '../hooks/useScrollToEnd';
13
14
  import useSuggestedActionsAccessKey from '../hooks/internal/useSuggestedActionsAccessKey';
14
15
  import useStyleSet from '../hooks/useStyleSet';
15
16
  import useStyleToEmotionObject from '../hooks/internal/useStyleToEmotionObject';
16
- import { DirectLineCardAction } from 'botframework-webchat-core';
17
17
 
18
18
  const { useDirection, useDisabled, usePerformCardAction, useStyleOptions, useSuggestedActions } = hooks;
19
19
 
@@ -1,11 +1,11 @@
1
1
  /* eslint react/no-array-index-key: "off" */
2
2
 
3
- import { DirectLineCardAction } from 'botframework-webchat-core';
4
3
  import { hooks } from 'botframework-webchat-api';
5
4
  import BasicFilm, { createBasicStyleSet as createBasicStyleSetForReactFilm } from 'react-film';
6
5
  import classNames from 'classnames';
7
6
  import PropTypes from 'prop-types';
8
7
  import React, { FC, useMemo, useRef } from 'react';
8
+ import type { DirectLineCardAction } from 'botframework-webchat-core';
9
9
 
10
10
  import connectToWebChat from '../connectToWebChat';
11
11
  import ScreenReaderText from '../ScreenReaderText';
@@ -2,9 +2,8 @@ import { hooks } from 'botframework-webchat-api';
2
2
  import classNames from 'classnames';
3
3
  import PropTypes from 'prop-types';
4
4
  import React, { forwardRef, useCallback, useRef } from 'react';
5
-
6
- import type { DirectLineActivity } from 'botframework-webchat-core';
7
5
  import type { MouseEventHandler, PropsWithChildren } from 'react';
6
+ import type { WebChatActivity } from 'botframework-webchat-core';
8
7
 
9
8
  import { android } from '../Utils/detectBrowser';
10
9
  import FocusTrap from './FocusTrap';
@@ -18,9 +17,7 @@ import useValueRef from '../hooks/internal/useValueRef';
18
17
 
19
18
  const { useActivityKeysByRead, useGetHasAcknowledgedByActivityKey, useGetKeyByActivity } = hooks;
20
19
 
21
- type ActivityRowProps = PropsWithChildren<{
22
- activity: DirectLineActivity;
23
- }>;
20
+ type ActivityRowProps = PropsWithChildren<{ activity: WebChatActivity }>;
24
21
 
25
22
  const ActivityRow = forwardRef<HTMLLIElement, ActivityRowProps>(({ activity, children }, ref) => {
26
23
  const [activeDescendantId] = useActiveDescendantId();
@@ -112,6 +109,8 @@ ActivityRow.defaultProps = {
112
109
  };
113
110
 
114
111
  ActivityRow.propTypes = {
112
+ // PropTypes cannot fully capture TypeScript type.
113
+ // @ts-ignore
115
114
  activity: PropTypes.shape({
116
115
  channelData: PropTypes.shape({
117
116
  speak: PropTypes.bool,
@@ -1,14 +1,13 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import React from 'react';
3
-
4
- import type { DirectLineActivity } from 'botframework-webchat-core';
5
3
  import type { RefObject, VFC } from 'react';
4
+ import type { WebChatActivity } from 'botframework-webchat-core';
6
5
 
7
6
  import ScreenReaderText from '../ScreenReaderText';
8
7
  import useActivityAccessibleName from './useActivityAccessibleName';
9
8
 
10
9
  type ActivityTextAltProps = {
11
- activity: DirectLineActivity;
10
+ activity: WebChatActivity;
12
11
  bodyRef: RefObject<HTMLDivElement>;
13
12
  id: string;
14
13
  };
@@ -1,10 +1,10 @@
1
1
  import { hooks } from 'botframework-webchat-api';
2
+ import classNames from 'classnames';
2
3
  import PropTypes from 'prop-types';
3
4
  import random from 'math-random';
4
5
  import React, { useEffect, useMemo, useRef } from 'react';
5
-
6
- import type { DirectLineActivity } from 'botframework-webchat-core';
7
6
  import type { FC, RefObject, VFC } from 'react';
7
+ import type { WebChatActivity } from 'botframework-webchat-core';
8
8
 
9
9
  import LiveRegionTwinComposer from '../providers/LiveRegionTwin/LiveRegionTwinComposer';
10
10
  import ScreenReaderActivity from '../ScreenReaderActivity';
@@ -12,15 +12,15 @@ import tabbableElements from '../Utils/tabbableElements';
12
12
  import useActivityTreeWithRenderer from '../providers/ActivityTree/useActivityTreeWithRenderer';
13
13
  import useQueueStaticElement from '../providers/LiveRegionTwin/useQueueStaticElement';
14
14
  import useStyleToEmotionObject from '../hooks/internal/useStyleToEmotionObject';
15
+ import useTypistNames from './useTypistNames';
15
16
 
16
17
  import type { ActivityElementMap } from './types';
17
- import classNames from 'classnames';
18
18
 
19
19
  const { useGetKeyByActivity, useLocalizer, useStyleOptions } = hooks;
20
20
 
21
21
  const ROOT_STYLE = {
22
22
  '&.webchat__live-region-transcript': {
23
- '& .webchat__live-region-transcript__interactive_note': {
23
+ '& .webchat__live-region-transcript__interactive-note, & .webchat__live-region-transcript__text-element': {
24
24
  color: 'transparent',
25
25
  height: 1,
26
26
  overflow: 'hidden',
@@ -37,9 +37,12 @@ const ROOT_STYLE = {
37
37
  *
38
38
  * Presentational activity, will be rendered visually but not going through screen reader.
39
39
  */
40
- function isPresentational(activity: DirectLineActivity): boolean {
41
- const { channelData }: { attachments?: []; channelData?: { messageBack?: { displayText?: string } }; text?: string } =
42
- activity;
40
+ function isPresentational(activity: WebChatActivity): boolean {
41
+ if (activity.type !== 'message') {
42
+ return;
43
+ }
44
+
45
+ const { channelData } = activity;
43
46
 
44
47
  // "Fallback text" includes both message text and narratives for attachments.
45
48
  // Emptying out "fallback text" essentially mute for both message and attachments.
@@ -53,7 +56,7 @@ function isPresentational(activity: DirectLineActivity): boolean {
53
56
  return !(channelData?.messageBack?.displayText || activity.text || activity.attachments?.length);
54
57
  }
55
58
 
56
- type RenderingActivities = Map<string, DirectLineActivity>;
59
+ type RenderingActivities = Map<string, WebChatActivity>;
57
60
 
58
61
  type LiveRegionTranscriptCoreProps = {
59
62
  activityElementMapRef: RefObject<ActivityElementMap>;
@@ -61,19 +64,26 @@ type LiveRegionTranscriptCoreProps = {
61
64
 
62
65
  const LiveRegionTranscriptCore: FC<LiveRegionTranscriptCoreProps> = ({ activityElementMapRef }) => {
63
66
  const [flattenedActivityTree] = useActivityTreeWithRenderer({ flat: true });
67
+ const [typistNames] = useTypistNames();
64
68
  const getKeyByActivity = useGetKeyByActivity();
65
69
  const localize = useLocalizer();
66
70
  const queueStaticElement = useQueueStaticElement();
67
71
 
68
72
  const liveRegionInteractiveLabelAlt = localize('TRANSCRIPT_LIVE_REGION_INTERACTIVE_LABEL_ALT');
69
73
  const liveRegionInteractiveWithLinkLabelAlt = localize('TRANSCRIPT_LIVE_REGION_INTERACTIVE_WITH_LINKS_LABEL_ALT');
74
+ const typingIndicator =
75
+ !!typistNames.length &&
76
+ localize(
77
+ typistNames.length > 1 ? 'TYPING_INDICATOR_MULTIPLE_TEXT' : 'TYPING_INDICATOR_SINGLE_TEXT',
78
+ typistNames[0]
79
+ );
70
80
 
71
81
  const renderingActivities = useMemo<Readonly<RenderingActivities>>(
72
82
  () =>
73
83
  Object.freeze(
74
84
  flattenedActivityTree.reduce<RenderingActivities>(
75
85
  (intermediate, { activity }) => intermediate.set(getKeyByActivity(activity), activity),
76
- new Map<string, DirectLineActivity>()
86
+ new Map<string, WebChatActivity>()
77
87
  )
78
88
  ),
79
89
  [flattenedActivityTree, getKeyByActivity]
@@ -83,7 +93,7 @@ const LiveRegionTranscriptCore: FC<LiveRegionTranscriptCoreProps> = ({ activityE
83
93
 
84
94
  useEffect(() => {
85
95
  const { current: prevRenderingActivities } = prevRenderingActivitiesRef;
86
- const appendedActivities: { activity: DirectLineActivity; key: string }[] = [];
96
+ const appendedActivities: { activity: WebChatActivity; key: string }[] = [];
87
97
 
88
98
  // Bottom-up, find activities which are recently appended (i.e. new activity will have a new key).
89
99
  // We only consider new activities added to the bottom of the chat history.
@@ -109,7 +119,7 @@ const LiveRegionTranscriptCore: FC<LiveRegionTranscriptCoreProps> = ({ activityE
109
119
 
110
120
  if (hasNewLink || hasNewWidget) {
111
121
  // eslint-disable-next-line no-magic-numbers
112
- const labelId = `webchat__live-region-transcript__interactive_note--${random().toString(36).substr(2, 5)}`;
122
+ const labelId = `webchat__live-region-transcript__interactive-note--${random().toString(36).substr(2, 5)}`;
113
123
 
114
124
  queueStaticElement(
115
125
  // Inside ARIA live region:
@@ -123,7 +133,7 @@ const LiveRegionTranscriptCore: FC<LiveRegionTranscriptCoreProps> = ({ activityE
123
133
  <div
124
134
  aria-atomic="true"
125
135
  aria-labelledby={labelId}
126
- className="webchat__live-region-transcript__interactive_note"
136
+ className="webchat__live-region-transcript__interactive-note"
127
137
  role="note"
128
138
  >
129
139
  {/* "id" is required for "aria-activedescendant" */}
@@ -144,6 +154,10 @@ const LiveRegionTranscriptCore: FC<LiveRegionTranscriptCoreProps> = ({ activityE
144
154
  renderingActivities
145
155
  ]);
146
156
 
157
+ useEffect(() => {
158
+ typingIndicator && queueStaticElement(typingIndicator);
159
+ }, [queueStaticElement, typingIndicator]);
160
+
147
161
  return null;
148
162
  };
149
163
 
@@ -164,6 +178,7 @@ const LiveRegionTranscript: VFC<LiveRegionTranscriptProps> = ({ activityElementM
164
178
  className={classNames('webchat__live-region-transcript', rootClassName)}
165
179
  fadeAfter={internalLiveRegionFadeAfter}
166
180
  role="log"
181
+ textElementClassName="webchat__live-region-transcript__text-element"
167
182
  >
168
183
  <LiveRegionTranscriptCore activityElementMapRef={activityElementMapRef} />
169
184
  </LiveRegionTwinComposer>
@@ -1,8 +1,7 @@
1
1
  import { hooks } from 'botframework-webchat-api';
2
2
  import { useEffect, useMemo, useState } from 'react';
3
-
4
- import type { DirectLineActivity } from 'botframework-webchat-core';
5
3
  import type { RefObject } from 'react';
4
+ import type { WebChatActivity } from 'botframework-webchat-core';
6
5
 
7
6
  import activityAltText from '../Utils/activityAltText';
8
7
  import tabbableElements from '../Utils/tabbableElements';
@@ -24,13 +23,13 @@ const ACTIVITY_NUM_ATTACHMENTS_ALT_IDS = {
24
23
  two: 'ACTIVITY_NUM_ATTACHMENTS_TWO_ALT'
25
24
  };
26
25
 
27
- export default function useActivityAccessibleName(activity: DirectLineActivity, bodyRef: RefObject<HTMLElement>) {
26
+ export default function useActivityAccessibleName(activity: WebChatActivity, bodyRef: RefObject<HTMLElement>) {
28
27
  const [{ initials: botInitials }] = useAvatarForBot();
29
28
  const [interactiveType, setInteractiveType] = useState<InteractiveType | false>(false);
30
29
  const fromSelf = activity.from?.role === 'user';
31
30
  const localize = useLocalizer();
32
31
  const localizeWithPlural = useLocalizer({ plural: true });
33
- const numAttachments = activity.attachments?.length || 0;
32
+ const numAttachments = activity.type === 'message' ? activity.attachments?.length || 0 : 0;
34
33
  const renderMarkdownAsHTML = useRenderMarkdownAsHTML();
35
34
 
36
35
  const activityInteractiveAlt = localize('ACTIVITY_INTERACTIVE_LABEL_ALT'); // "Click to interact."
@@ -0,0 +1,37 @@
1
+ import { hooks } from 'botframework-webchat-api';
2
+ import { useEffect, useRef } from 'react';
3
+
4
+ const { useActiveTyping } = hooks;
5
+
6
+ function arrayEquals<T>(x: readonly T[], y: readonly T[]): boolean {
7
+ return x.length === y.length && x.every((value, index) => y[+index] === value);
8
+ }
9
+
10
+ /** Gets names of users who are actively typing, sorted by the time they started typing. */
11
+ export default function useTypistNames(): readonly [readonly string[]] {
12
+ const [activeTyping] = useActiveTyping();
13
+ const prevTypistNamesStateRef = useRef<readonly [readonly string[]]>(
14
+ Object.freeze([Object.freeze([] as string[])]) as [readonly string[]]
15
+ );
16
+
17
+ const activeTypingFromOthersValues = Object.values(activeTyping).filter(({ role }) => role !== 'user');
18
+
19
+ // Sort the list by the first typist.
20
+ const sortedActiveTypingFromOthersValues = activeTypingFromOthersValues.sort(({ at: x }, { at: y }) => x - y);
21
+
22
+ const typistNamesState: readonly [readonly string[]] = Object.freeze([
23
+ Object.freeze(sortedActiveTypingFromOthersValues.map(({ name }) => name))
24
+ ]) as readonly [readonly string[]];
25
+
26
+ const { current: prevTypistNamesState } = prevTypistNamesStateRef;
27
+
28
+ const nextTypistNamesState = arrayEquals(typistNamesState[0], prevTypistNamesState[0])
29
+ ? prevTypistNamesState
30
+ : typistNamesState;
31
+
32
+ useEffect(() => {
33
+ prevTypistNamesStateRef.current = nextTypistNamesState;
34
+ }, [prevTypistNamesStateRef, nextTypistNamesState]);
35
+
36
+ return nextTypistNamesState;
37
+ }
@@ -1,5 +1,5 @@
1
- import { DirectLineActivity } from 'botframework-webchat-core';
1
+ import type { WebChatActivity } from 'botframework-webchat-core';
2
2
 
3
- export default function getActivityUniqueId(activity: DirectLineActivity): string {
3
+ export default function getActivityUniqueId(activity: WebChatActivity): string {
4
4
  return activity?.channelData?.clientActivityID || activity?.id;
5
5
  }
@@ -12,9 +12,9 @@ type Fn<TArgs, TResult> = (...args: TArgs[]) => TResult;
12
12
  * @param {(fn: Fn<TArgs, TIntermediate>) => TFinal} callback - When called, this function should execute the memoizing function.
13
13
  * @param {DependencyList[]} deps - Dependencies to detect for chagnes.
14
14
  */
15
- export default function useMemoize<TArgs extends [], TIntermediate, TFinal>(
16
- fn: Fn<TArgs, TIntermediate>,
17
- callback: (fn: Fn<TArgs, TIntermediate>) => TFinal,
15
+ export default function useMemoize<TIntermediate, TFinal>(
16
+ fn: Fn<unknown, TIntermediate>,
17
+ callback: (fn: Fn<unknown, TIntermediate>) => TFinal,
18
18
  deps: DependencyList[]
19
19
  ): TFinal {
20
20
  if (typeof fn !== 'function') {
@@ -26,10 +26,10 @@ export default function useMemoize<TArgs extends [], TIntermediate, TFinal>(
26
26
  }
27
27
 
28
28
  const memoizedFn = useMemo(() => {
29
- let cache: Cache<TArgs, TIntermediate>[] = [];
29
+ let cache: Cache<unknown, TIntermediate>[] = [];
30
30
 
31
- return (run: (fn: Fn<TArgs, TIntermediate>) => TFinal) => {
32
- const nextCache: Cache<TArgs, TIntermediate>[] = [];
31
+ return (run: (fn: Fn<unknown, TIntermediate>) => TFinal) => {
32
+ const nextCache: Cache<unknown, TIntermediate>[] = [];
33
33
  const result = run((...args) => {
34
34
  const { result } = [...cache, ...nextCache].find(
35
35
  ({ args: cachedArgs }) =>
@@ -1,10 +1,10 @@
1
- import { DirectLineActivity } from 'botframework-webchat-core';
2
1
  import { useEffect } from 'react';
2
+ import type { WebChatActivity } from 'botframework-webchat-core';
3
3
 
4
4
  import useWebChatUIContext from './internal/useWebChatUIContext';
5
5
 
6
6
  export default function useObserveTranscriptFocus(
7
- observer: (event: { activity: DirectLineActivity }) => void,
7
+ observer: (event: { activity: WebChatActivity }) => void,
8
8
  deps: any[]
9
9
  ): void {
10
10
  if (typeof observer !== 'function') {
@@ -1,6 +1,3 @@
1
- /* eslint no-magic-numbers: ["error", { "ignore": [0, 1024] }] */
2
-
3
- import { DirectLineAttachment } from 'botframework-webchat-core';
4
1
  import { hooks } from 'botframework-webchat-api';
5
2
  import { useCallback } from 'react';
6
3
 
@@ -41,8 +38,13 @@ export default function useSendFiles(): (files: File[]) => void {
41
38
  // TODO: [P3] We need to find revokeObjectURL on the UI side
42
39
  // Redux store should not know about the browser environment
43
40
  // One fix is to use ArrayBuffer instead of object URL, but that would requires change to DirectLineJS
44
- const attachments: DirectLineAttachment[] = await Promise.all(
45
- [].map.call(files, async file => {
41
+ const attachments: {
42
+ name: string;
43
+ size: number;
44
+ url: string;
45
+ thumbnail?: string;
46
+ }[] = await Promise.all(
47
+ Array.from(files).map(async file => {
46
48
  let thumbnail;
47
49
 
48
50
  if (downscaleImageToDataURL && enableUploadThumbnail && canMakeThumbnail(file)) {
@@ -2,8 +2,8 @@ import { hooks } from 'botframework-webchat-api';
2
2
  import React, { useMemo } from 'react';
3
3
 
4
4
  import type { ActivityComponentFactory } from 'botframework-webchat-api';
5
- import type { DirectLineActivity } from 'botframework-webchat-core';
6
5
  import type { FC, PropsWithChildren } from 'react';
6
+ import type { WebChatActivity } from 'botframework-webchat-core';
7
7
 
8
8
  import { ActivityWithRenderer, ReadonlyActivityTree } from './private/types';
9
9
  import ActivityTreeContext from './private/Context';
@@ -25,7 +25,7 @@ const ActivityTreeComposer: FC<ActivityTreeComposerProps> = ({ children }) => {
25
25
  throw new Error('botframework-webchat internal: <ActivityTreeComposer> should not be nested.');
26
26
  }
27
27
 
28
- const [activities]: [DirectLineActivity[]] = useActivities();
28
+ const [activities]: [WebChatActivity[]] = useActivities();
29
29
  const createActivityRenderer: ActivityComponentFactory = useCreateActivityRenderer();
30
30
 
31
31
  const activitiesWithRenderer = useActivitiesWithRenderer(activities, createActivityRenderer);
@@ -1,8 +1,8 @@
1
1
  import type { ActivityComponentFactory } from 'botframework-webchat-api';
2
- import type { DirectLineActivity } from 'botframework-webchat-core';
2
+ import type { WebChatActivity } from 'botframework-webchat-core';
3
3
 
4
4
  type ActivityWithRenderer = {
5
- activity: DirectLineActivity;
5
+ activity: WebChatActivity;
6
6
  renderActivity: Exclude<ReturnType<ActivityComponentFactory>, false>;
7
7
  };
8
8
 
@@ -1,17 +1,15 @@
1
1
  import { useCallback, useRef } from 'react';
2
-
3
- import type { DirectLineActivity } from 'botframework-webchat-core';
2
+ import type { WebChatActivity } from 'botframework-webchat-core';
4
3
 
5
4
  import useMemoize from '../../../hooks/internal/useMemoize';
6
-
7
5
  import type { ActivityWithRenderer } from './types';
8
6
 
9
7
  export default function useActivitiesWithRenderer(
10
- activities: readonly DirectLineActivity[],
8
+ activities: readonly WebChatActivity[],
11
9
  createActivityRenderer
12
10
  ): readonly ActivityWithRenderer[] {
13
11
  const createActivityRendererWithLiteralArgs = useCallback(
14
- (activity: DirectLineActivity, nextVisibleActivity: DirectLineActivity) =>
12
+ (activity: WebChatActivity, nextVisibleActivity: WebChatActivity) =>
15
13
  createActivityRenderer({ activity, nextVisibleActivity }),
16
14
  [createActivityRenderer]
17
15
  );
@@ -27,7 +25,7 @@ export default function useActivitiesWithRenderer(
27
25
  // useMemoize() allows any number of memoization.
28
26
 
29
27
  const activitiesWithRenderer: ActivityWithRenderer[] = [];
30
- let nextVisibleActivity: DirectLineActivity;
28
+ let nextVisibleActivity: WebChatActivity;
31
29
 
32
30
  for (let index = activities.length - 1; index >= 0; index--) {
33
31
  const activity = activities[+index];
@@ -1,19 +1,14 @@
1
1
  import { hooks } from 'botframework-webchat-api';
2
2
  import { useMemo } from 'react';
3
-
4
- import type { DirectLineActivity } from 'botframework-webchat-core';
3
+ import type { WebChatActivity } from 'botframework-webchat-core';
5
4
 
6
5
  import intersectionOf from '../../../Utils/intersectionOf';
7
6
  import removeInline from '../../../Utils/removeInline';
8
-
9
7
  import type { ActivityWithRenderer, ReadonlyActivityTree } from './types';
10
8
 
11
9
  const { useGroupActivities } = hooks;
12
10
 
13
- function validateAllEntriesTagged(
14
- entries: readonly ActivityWithRenderer[],
15
- bins: readonly (readonly ActivityWithRenderer[])[]
16
- ): boolean {
11
+ function validateAllEntriesTagged<T>(entries: readonly T[], bins: readonly (readonly T[])[]): boolean {
17
12
  return entries.every(entry => bins.some(bin => bin.includes(entry)));
18
13
  }
19
14
 
@@ -48,8 +43,8 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]):
48
43
  // Both arrays should contains all activities.
49
44
 
50
45
  const { entriesBySender, entriesByStatus } = useMemo<{
51
- entriesBySender: readonly (readonly DirectLineActivity[])[];
52
- entriesByStatus: readonly (readonly DirectLineActivity[])[];
46
+ entriesBySender: readonly (readonly ActivityWithRenderer[])[];
47
+ entriesByStatus: readonly (readonly ActivityWithRenderer[])[];
53
48
  }>(() => {
54
49
  const visibleActivities = entries.map(({ activity }) => activity);
55
50
 
@@ -57,8 +52,8 @@ function useActivityTreeWithRenderer(entries: readonly ActivityWithRenderer[]):
57
52
  sender: activitiesBySender,
58
53
  status: activitiesByStatus
59
54
  }: {
60
- sender: readonly (readonly DirectLineActivity[])[];
61
- status: readonly (readonly DirectLineActivity[])[];
55
+ sender: readonly (readonly WebChatActivity[])[];
56
+ status: readonly (readonly WebChatActivity[])[];
62
57
  } = groupActivities({
63
58
  activities: visibleActivities
64
59
  });
@@ -36,6 +36,9 @@ type LiveRegionTwinComposerProps = PropsWithChildren<{
36
36
 
37
37
  /** Optional "role" attribute for the live region twin container. */
38
38
  role?: string;
39
+
40
+ /** Optional "className" attribute for static text element. */
41
+ textElementClassName?: string;
39
42
  }>;
40
43
 
41
44
  /**
@@ -56,7 +59,8 @@ const LiveRegionTwinComposer: FC<LiveRegionTwinComposerProps> = ({
56
59
  children,
57
60
  className,
58
61
  fadeAfter = DEFAULT_FADE_AFTER,
59
- role
62
+ role,
63
+ textElementClassName
60
64
  }) => {
61
65
  const [staticElementEntries, setStaticElementEntries] = useState<StaticElementEntry[]>([]);
62
66
  const fadeAfterRef = useValueRef(fadeAfter);
@@ -125,6 +129,7 @@ const LiveRegionTwinComposer: FC<LiveRegionTwinComposerProps> = ({
125
129
  aria-roledescription={ariaRoleDescription}
126
130
  className={className}
127
131
  role={role}
132
+ textElementClassName={textElementClassName}
128
133
  />
129
134
  {children}
130
135
  </LiveRegionTwinContext.Provider>
@@ -138,7 +143,8 @@ LiveRegionTwinComposer.defaultProps = {
138
143
  children: undefined,
139
144
  className: undefined,
140
145
  fadeAfter: DEFAULT_FADE_AFTER,
141
- role: undefined
146
+ role: undefined,
147
+ textElementClassName: undefined
142
148
  };
143
149
 
144
150
  LiveRegionTwinComposer.propTypes = {
@@ -148,7 +154,8 @@ LiveRegionTwinComposer.propTypes = {
148
154
  children: PropTypes.any,
149
155
  className: PropTypes.string,
150
156
  fadeAfter: PropTypes.number,
151
- role: PropTypes.string
157
+ role: PropTypes.string,
158
+ textElementClassName: PropTypes.string
152
159
  };
153
160
 
154
161
  export default LiveRegionTwinComposer;
@@ -12,6 +12,7 @@ type LiveRegionTwinContainerProps = {
12
12
  'aria-roledescription'?: string;
13
13
  className?: string;
14
14
  role?: string;
15
+ textElementClassName?: string;
15
16
  };
16
17
 
17
18
  // This container is marked as private because we assume there is only one instance under the <LiveRegionTwinContext>.
@@ -20,7 +21,8 @@ const LiveRegionTwinContainer: VFC<LiveRegionTwinContainerProps> = ({
20
21
  'aria-live': ariaLive,
21
22
  'aria-roledescription': ariaRoleDescription,
22
23
  className,
23
- role
24
+ role,
25
+ textElementClassName
24
26
  }) => {
25
27
  const [staticElementEntries] = useStaticElementEntries();
26
28
 
@@ -37,9 +39,21 @@ const LiveRegionTwinContainer: VFC<LiveRegionTwinContainerProps> = ({
37
39
  className={className}
38
40
  role={role}
39
41
  >
40
- {staticElementEntries.map(({ element, key }) =>
41
- typeof element === 'string' ? <div key={key}>{element}</div> : <Fragment key={key}>{element}</Fragment>
42
- )}
42
+ {staticElementEntries.map(({ element, key }) => {
43
+ if (typeof element === 'string') {
44
+ const id = `webchat__live-region-twin__text-element-${key}`;
45
+
46
+ return (
47
+ <div aria-atomic={true} aria-labelledby={id} className={textElementClassName} key={key}>
48
+ {/* "aria-labelledby" requires the use of "id" attribute. */}
49
+ {/* eslint-disable-next-line react/forbid-dom-props */}
50
+ <p id={id}>{element}</p>
51
+ </div>
52
+ );
53
+ }
54
+
55
+ return <Fragment key={key}>{element}</Fragment>;
56
+ })}
43
57
  </div>
44
58
  );
45
59
  };
@@ -48,7 +62,8 @@ LiveRegionTwinContainer.defaultProps = {
48
62
  'aria-label': undefined,
49
63
  'aria-roledescription': undefined,
50
64
  className: undefined,
51
- role: undefined
65
+ role: undefined,
66
+ textElementClassName: undefined
52
67
  };
53
68
 
54
69
  LiveRegionTwinContainer.propTypes = {
@@ -58,7 +73,8 @@ LiveRegionTwinContainer.propTypes = {
58
73
  'aria-live': PropTypes.oneOf(['assertive', 'polite']).isRequired,
59
74
  'aria-roledescription': PropTypes.string,
60
75
  className: PropTypes.string,
61
- role: PropTypes.string
76
+ role: PropTypes.string,
77
+ textElementClassName: PropTypes.string
62
78
  };
63
79
 
64
80
  export default LiveRegionTwinContainer;