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.
- package/lib/Activity/CarouselLayout.js +3 -3
- package/lib/Activity/Speak.d.ts +2 -2
- package/lib/Activity/Speak.d.ts.map +1 -1
- package/lib/Activity/Speak.js +14 -7
- package/lib/Activity/StackedLayout.d.ts +5 -3
- package/lib/Activity/StackedLayout.d.ts.map +1 -1
- package/lib/Activity/StackedLayout.js +15 -20
- package/lib/Attachment/FileContent.js +2 -2
- package/lib/BasicSendBox.d.ts.map +1 -1
- package/lib/BasicSendBox.js +7 -8
- package/lib/BasicToast.js +1 -1
- package/lib/BasicToaster.js +1 -1
- package/lib/BasicTranscript.d.ts.map +1 -1
- package/lib/BasicTranscript.js +30 -15
- package/lib/BasicTypingIndicator.js +1 -1
- package/lib/Composer.js +3 -3
- package/lib/ConnectivityStatus/Connecting.js +1 -1
- package/lib/Dictation.js +1 -1
- package/lib/Middleware/Activity/createCoreMiddleware.d.ts.map +1 -1
- package/lib/Middleware/Activity/createCoreMiddleware.js +9 -12
- package/lib/Middleware/ActivityStatus/SendStatus/SendFailedRetry.js +1 -1
- package/lib/Middleware/ActivityStatus/SendStatus/SendStatus.d.ts +2 -2
- package/lib/Middleware/ActivityStatus/SendStatus/SendStatus.d.ts.map +1 -1
- package/lib/Middleware/ActivityStatus/SendStatus/SendStatus.js +4 -3
- package/lib/Middleware/ActivityStatus/Timestamp.d.ts +2 -2
- package/lib/Middleware/ActivityStatus/Timestamp.d.ts.map +1 -1
- package/lib/Middleware/ActivityStatus/Timestamp.js +4 -2
- package/lib/Middleware/ActivityStatus/createSendStatusMiddleware.js +2 -2
- package/lib/ReactWebChat.js +2 -2
- package/lib/ScreenReaderActivity.js +1 -1
- package/lib/ScreenReaderText.js +1 -1
- package/lib/SendBox/AutoResizeTextArea.js +1 -1
- package/lib/SendBox/IconButton.js +1 -1
- package/lib/SendBox/MicrophoneButton.js +1 -1
- package/lib/SendBox/SendButton.js +1 -1
- package/lib/SendBox/SuggestedAction.js +2 -2
- package/lib/SendBox/SuggestedActions.js +2 -2
- package/lib/SendBox/TextBox.js +3 -3
- package/lib/SendBox/UploadButton.js +3 -3
- package/lib/Styles/StyleSet/Bubble.js +2 -2
- package/lib/Styles/StyleSet/CarouselFilmStrip.js +2 -2
- package/lib/Styles/StyleSet/CarouselFilmStripAttachment.js +2 -2
- package/lib/Styles/createStyleSet.d.ts +1 -1
- package/lib/Styles/createStyleSet.js +2 -2
- package/lib/Transcript/ActivityRow.d.ts +2 -2
- package/lib/Transcript/ActivityRow.d.ts.map +1 -1
- package/lib/Transcript/ActivityRow.js +4 -2
- package/lib/Transcript/ActivityTextAlt.js +1 -1
- package/lib/Transcript/FocusTrap.js +1 -1
- package/lib/Transcript/KeyboardHelp.js +1 -1
- package/lib/Transcript/LiveRegionTranscript.d.ts.map +1 -1
- package/lib/Transcript/LiveRegionTranscript.js +22 -7
- package/lib/Transcript/useActivityAccessibleName.d.ts +2 -2
- package/lib/Transcript/useActivityAccessibleName.d.ts.map +1 -1
- package/lib/Transcript/useActivityAccessibleName.js +2 -2
- package/lib/Transcript/useTypistNames.d.ts +3 -0
- package/lib/Transcript/useTypistNames.d.ts.map +1 -0
- package/lib/Transcript/useTypistNames.js +61 -0
- package/lib/Utils/AccessKeySink/Surface.js +1 -1
- package/lib/Utils/AccessibleButton.js +1 -1
- package/lib/Utils/AccessibleInputText.js +1 -1
- package/lib/Utils/AccessibleTextArea.js +1 -1
- package/lib/Utils/CroppedImage.js +1 -1
- package/lib/Utils/FocusRedirector.js +1 -1
- package/lib/Utils/InlineMarkdown.js +3 -3
- package/lib/Utils/TypeFocusSink/FocusBox.js +1 -1
- package/lib/Utils/addTargetBlankToHyperlinksMarkdown.js +2 -2
- package/lib/Utils/downscaleImageToDataURL/index.js +1 -1
- package/lib/Utils/getActivityUniqueId.js +1 -1
- package/lib/connectToWebChat.js +2 -2
- package/lib/hooks/internal/BypassSpeechSynthesisPonyfill.js +3 -3
- package/lib/hooks/internal/useMemoize.d.ts +1 -1
- package/lib/hooks/internal/useMemoize.d.ts.map +1 -1
- package/lib/hooks/internal/useMemoize.js +1 -1
- package/lib/hooks/useObserveTranscriptFocus.d.ts +2 -2
- package/lib/hooks/useObserveTranscriptFocus.d.ts.map +1 -1
- package/lib/hooks/useObserveTranscriptFocus.js +1 -1
- package/lib/hooks/useSendFiles.d.ts.map +1 -1
- package/lib/hooks/useSendFiles.js +4 -4
- package/lib/index.d.ts +3 -3
- package/lib/index.js +5 -5
- package/lib/providers/ActivityTree/ActivityTreeComposer.d.ts.map +1 -1
- package/lib/providers/ActivityTree/ActivityTreeComposer.js +2 -2
- package/lib/providers/ActivityTree/private/types.d.ts +2 -2
- package/lib/providers/ActivityTree/private/types.d.ts.map +1 -1
- package/lib/providers/ActivityTree/private/useActivitiesWithRenderer.d.ts +2 -2
- package/lib/providers/ActivityTree/private/useActivitiesWithRenderer.d.ts.map +1 -1
- package/lib/providers/ActivityTree/private/useActivitiesWithRenderer.js +1 -1
- package/lib/providers/ActivityTree/private/useActivityTreeWithRenderer.d.ts.map +1 -1
- package/lib/providers/ActivityTree/private/useActivityTreeWithRenderer.js +1 -1
- package/lib/providers/LiveRegionTwin/LiveRegionTwinComposer.d.ts +2 -0
- package/lib/providers/LiveRegionTwin/LiveRegionTwinComposer.d.ts.map +1 -1
- package/lib/providers/LiveRegionTwin/LiveRegionTwinComposer.js +10 -6
- package/lib/providers/LiveRegionTwin/private/LiveRegionTwinContainer.d.ts +1 -0
- package/lib/providers/LiveRegionTwin/private/LiveRegionTwinContainer.d.ts.map +1 -1
- package/lib/providers/LiveRegionTwin/private/LiveRegionTwinContainer.js +22 -8
- package/lib/providers/TranscriptFocus/TranscriptFocusComposer.js +1 -1
- package/package.json +24 -20
- package/src/Activity/Speak.tsx +21 -18
- package/src/Activity/StackedLayout.tsx +20 -26
- package/src/Attachment/FileContent.tsx +1 -1
- package/src/BasicSendBox.tsx +3 -2
- package/src/BasicTranscript.tsx +29 -13
- package/src/Middleware/Activity/createCoreMiddleware.tsx +5 -10
- package/src/Middleware/ActivityStatus/SendStatus/SendStatus.tsx +5 -3
- package/src/Middleware/ActivityStatus/Timestamp.tsx +5 -3
- package/src/SendBox/SuggestedAction.tsx +1 -1
- package/src/SendBox/SuggestedActions.tsx +1 -1
- package/src/Transcript/ActivityRow.tsx +4 -5
- package/src/Transcript/ActivityTextAlt.tsx +2 -3
- package/src/Transcript/LiveRegionTranscript.tsx +27 -12
- package/src/Transcript/useActivityAccessibleName.ts +3 -4
- package/src/Transcript/useTypistNames.ts +37 -0
- package/src/Utils/getActivityUniqueId.ts +2 -2
- package/src/hooks/internal/useMemoize.ts +6 -6
- package/src/hooks/useObserveTranscriptFocus.ts +2 -2
- package/src/hooks/useSendFiles.ts +7 -5
- package/src/providers/ActivityTree/ActivityTreeComposer.tsx +2 -2
- package/src/providers/ActivityTree/private/types.ts +2 -2
- package/src/providers/ActivityTree/private/useActivitiesWithRenderer.ts +4 -6
- package/src/providers/ActivityTree/private/useActivityTreeWithRenderer.ts +6 -11
- package/src/providers/LiveRegionTwin/LiveRegionTwinComposer.tsx +10 -3
- package/src/providers/LiveRegionTwin/private/LiveRegionTwinContainer.tsx +22 -6
- package/lib/Middleware/GroupActivities/createCoreMiddleware.js +0 -69
- package/src/Middleware/GroupActivities/createCoreMiddleware.js +0 -57
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { Constants
|
|
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:
|
|
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:
|
|
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
|
|
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:
|
|
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-
|
|
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:
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
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,
|
|
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:
|
|
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-
|
|
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-
|
|
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:
|
|
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 {
|
|
1
|
+
import type { WebChatActivity } from 'botframework-webchat-core';
|
|
2
2
|
|
|
3
|
-
export default function getActivityUniqueId(activity:
|
|
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<
|
|
16
|
-
fn: Fn<
|
|
17
|
-
callback: (fn: Fn<
|
|
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<
|
|
29
|
+
let cache: Cache<unknown, TIntermediate>[] = [];
|
|
30
30
|
|
|
31
|
-
return (run: (fn: Fn<
|
|
32
|
-
const nextCache: Cache<
|
|
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:
|
|
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:
|
|
45
|
-
|
|
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]: [
|
|
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 {
|
|
2
|
+
import type { WebChatActivity } from 'botframework-webchat-core';
|
|
3
3
|
|
|
4
4
|
type ActivityWithRenderer = {
|
|
5
|
-
activity:
|
|
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
|
|
8
|
+
activities: readonly WebChatActivity[],
|
|
11
9
|
createActivityRenderer
|
|
12
10
|
): readonly ActivityWithRenderer[] {
|
|
13
11
|
const createActivityRendererWithLiteralArgs = useCallback(
|
|
14
|
-
(activity:
|
|
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:
|
|
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
|
|
52
|
-
entriesByStatus: readonly (readonly
|
|
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
|
|
61
|
-
status: readonly (readonly
|
|
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'
|
|
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;
|