botframework-webchat 4.14.0 → 4.15.1
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/.eslintrc.yml +5 -109
- package/.prettierrc.yml +1 -1
- package/README.md +1 -1
- package/babel.config.json +2 -2
- package/babel.sanitize-html.config.json +10 -0
- package/lib/AddFullBundle.d.ts.map +1 -1
- package/lib/AddFullBundle.js +1 -2
- package/lib/FullComposer.js +2 -2
- package/lib/FullReactWebChat.js +1 -1
- package/lib/adaptiveCards/AdaptiveCardsComposer.d.ts.map +1 -1
- package/lib/adaptiveCards/AdaptiveCardsComposer.js +6 -2
- package/lib/adaptiveCards/AdaptiveCardsContext.d.ts +0 -1
- package/lib/adaptiveCards/AdaptiveCardsContext.d.ts.map +1 -1
- package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.d.ts +7 -7
- package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.d.ts.map +1 -1
- package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.js +10 -3
- package/lib/adaptiveCards/Attachment/AdaptiveCardContent.js +8 -4
- package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.d.ts +9 -22
- package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.d.ts.map +1 -1
- package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.js +175 -95
- package/lib/adaptiveCards/Attachment/AnimationCardContent.js +1 -1
- package/lib/adaptiveCards/Attachment/AudioCardContent.js +1 -1
- package/lib/adaptiveCards/Attachment/CommonCard.js +7 -3
- package/lib/adaptiveCards/Attachment/HeroCardContent.js +7 -3
- package/lib/adaptiveCards/Attachment/OAuthCardContent.js +7 -3
- package/lib/adaptiveCards/Attachment/ReceiptCardContent.js +7 -3
- package/lib/adaptiveCards/Attachment/SignInCardContent.js +1 -1
- package/lib/adaptiveCards/Attachment/ThumbnailCardContent.js +7 -3
- package/lib/adaptiveCards/Attachment/VideoCardContent.js +1 -1
- package/lib/adaptiveCards/AttachmentForScreenReader/AdaptiveCardAttachment.js +7 -3
- package/lib/adaptiveCards/AttachmentForScreenReader/RichCardAttachment.js +1 -1
- package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.d.ts +3 -0
- package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.d.ts.map +1 -1
- package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.js +8 -1
- package/lib/adaptiveCards/Styles/adaptiveCardHostConfig.js +2 -2
- package/lib/adaptiveCards/Styles/createAdaptiveCardsStyleSet.js +1 -1
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.d.ts.map +1 -1
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.js +1 -1
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.d.ts.map +1 -1
- package/lib/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.js +1 -3
- package/lib/adaptiveCards/hooks/internal/useParseAdaptiveCardJSON.js +1 -1
- package/lib/adaptiveCards/hooks/useAdaptiveCardsHostConfig.js +1 -1
- package/lib/adaptiveCards/hooks/useAdaptiveCardsPackage.js +1 -1
- package/lib/adaptiveCards/normalizeStyleOptions.js +1 -1
- package/lib/addVersion.js +2 -2
- package/lib/createCognitiveServicesSpeechServicesPonyfillFactory.js +1 -1
- package/lib/createFullStyleSet.d.ts +324 -55
- package/lib/createFullStyleSet.d.ts.map +1 -1
- package/lib/createFullStyleSet.js +1 -1
- package/lib/fullBundleDefaultStyleOptions.js +1 -1
- package/lib/hooks/useStyleOptions.js +1 -1
- package/lib/index-es5.d.ts +1 -21
- package/lib/index-es5.d.ts.map +1 -1
- package/lib/index-es5.js +3 -43
- package/lib/index-minimal.js +27 -21
- package/lib/index.d.ts +10 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +10 -7
- package/lib/polyfill.d.ts +23 -0
- package/lib/polyfill.d.ts.map +1 -0
- package/lib/polyfill.js +46 -0
- package/lib/renderMarkdown.d.ts.map +1 -1
- package/lib/renderMarkdown.js +34 -6
- package/lib/speech/CustomAudioInputStream.d.ts.map +1 -1
- package/lib/speech/CustomAudioInputStream.js +40 -15
- package/lib/speech/createAudioConfig.d.ts.map +1 -1
- package/lib/speech/createAudioConfig.js +9 -3
- package/lib/speech/createMicrophoneAudioConfigAndAudioContext.d.ts.map +1 -1
- package/lib/speech/createMicrophoneAudioConfigAndAudioContext.js +6 -1
- package/lib/speech/getUserMedia.js +1 -1
- package/lib/useComposerProps.js +1 -1
- package/package.json +35 -41
- package/src/AddFullBundle.tsx +0 -1
- package/src/__tests__/createDirectLine.spec.js +2 -0
- package/src/__tests__/renderMarkdown.spec.js +1 -1
- package/src/__tests__/versionTag.es5.spec.js +1 -0
- package/src/__tests__/versionTag.full.spec.js +1 -0
- package/src/__tests__/versionTag.minimal.spec.js +1 -0
- package/src/adaptiveCards/AdaptiveCardsComposer.tsx +4 -3
- package/src/adaptiveCards/Attachment/AdaptiveCardBuilder.ts +16 -12
- package/src/adaptiveCards/Attachment/AdaptiveCardRenderer.tsx +226 -96
- package/src/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.ts +8 -0
- package/src/adaptiveCards/Styles/createAdaptiveCardsStyleSet.spec.js +2 -0
- package/src/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.tsx +14 -12
- package/src/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.tsx +26 -25
- package/src/adaptiveCards/hooks/useAdaptiveCardsHostConfig.ts +4 -4
- package/src/createCognitiveServicesSpeechServicesPonyfillFactory.spec.js +5 -5
- package/src/index-es5.ts +3 -26
- package/src/polyfill.ts +29 -0
- package/src/renderMarkdown.ts +40 -4
- package/src/speech/CustomAudioInputStream.ts +39 -10
- package/src/speech/createAudioConfig.spec.js +1 -1
- package/src/speech/createAudioConfig.ts +9 -6
- package/src/speech/createMicrophoneAudioConfigAndAudioContext.ts +1 -3
- package/src/useComposerProps.ts +4 -4
- package/webpack.config.js +24 -4
- package/.eslintignore +0 -1
|
@@ -16,9 +16,10 @@ const AdaptiveCardsComposer: FC<AdaptiveCardsComposerProps> = ({
|
|
|
16
16
|
adaptiveCardsPackage,
|
|
17
17
|
children
|
|
18
18
|
}) => {
|
|
19
|
-
const patchedAdaptiveCardsPackage = useMemo(
|
|
20
|
-
adaptiveCardsPackage
|
|
21
|
-
|
|
19
|
+
const patchedAdaptiveCardsPackage = useMemo(
|
|
20
|
+
() => adaptiveCardsPackage || defaultAdaptiveCardsPackage,
|
|
21
|
+
[adaptiveCardsPackage]
|
|
22
|
+
);
|
|
22
23
|
|
|
23
24
|
const adaptiveCardsContext = useMemo(
|
|
24
25
|
() => ({
|
|
@@ -16,16 +16,16 @@ import {
|
|
|
16
16
|
TextWeight
|
|
17
17
|
} from 'adaptivecards';
|
|
18
18
|
|
|
19
|
-
import {
|
|
19
|
+
import { DirectLineCardAction, isForbiddenPropertyName } from 'botframework-webchat-core';
|
|
20
20
|
import AdaptiveCardsPackage from '../../types/AdaptiveCardsPackage';
|
|
21
21
|
import AdaptiveCardsStyleOptions from '../AdaptiveCardsStyleOptions';
|
|
22
22
|
|
|
23
23
|
export interface BotFrameworkCardAction {
|
|
24
|
-
__isBotFrameworkCardAction:
|
|
25
|
-
cardAction:
|
|
24
|
+
__isBotFrameworkCardAction: true;
|
|
25
|
+
cardAction: DirectLineCardAction;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
function addCardAction(cardAction:
|
|
28
|
+
function addCardAction(cardAction: DirectLineCardAction, includesOAuthButtons?: boolean) {
|
|
29
29
|
const { type } = cardAction;
|
|
30
30
|
let action;
|
|
31
31
|
|
|
@@ -42,11 +42,11 @@ function addCardAction(cardAction: CardAction, includesOAuthButtons?: boolean) {
|
|
|
42
42
|
cardAction
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
action.title = cardAction.title;
|
|
45
|
+
action.title = (cardAction as { title: string }).title;
|
|
46
46
|
} else {
|
|
47
47
|
action = new OpenUrlAction();
|
|
48
48
|
|
|
49
|
-
action.title = cardAction.title;
|
|
49
|
+
action.title = (cardAction as { title: string }).title;
|
|
50
50
|
action.url = cardAction.type === 'call' ? `tel:${cardAction.value}` : cardAction.value;
|
|
51
51
|
}
|
|
52
52
|
|
|
@@ -71,7 +71,7 @@ export default class AdaptiveCardBuilder {
|
|
|
71
71
|
this.card.addItem(this.container);
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
addColumnSet(sizes: number[], container: Container = this.container, selectAction?:
|
|
74
|
+
addColumnSet(sizes: number[], container: Container = this.container, selectAction?: DirectLineCardAction) {
|
|
75
75
|
const columnSet = new ColumnSet();
|
|
76
76
|
|
|
77
77
|
columnSet.selectAction = selectAction && addCardAction(selectAction);
|
|
@@ -96,9 +96,12 @@ export default class AdaptiveCardBuilder {
|
|
|
96
96
|
if (typeof text !== 'undefined') {
|
|
97
97
|
const textblock = new TextBlock();
|
|
98
98
|
|
|
99
|
-
// tslint:disable-next-line:forin
|
|
100
99
|
for (const prop in template) {
|
|
101
|
-
|
|
100
|
+
if (!isForbiddenPropertyName(prop)) {
|
|
101
|
+
// Mitigated through denylisting.
|
|
102
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
103
|
+
textblock[prop] = template[prop];
|
|
104
|
+
}
|
|
102
105
|
}
|
|
103
106
|
|
|
104
107
|
textblock.text = text;
|
|
@@ -107,7 +110,7 @@ export default class AdaptiveCardBuilder {
|
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
addButtons(cardActions:
|
|
113
|
+
addButtons(cardActions: DirectLineCardAction[], includesOAuthButtons?: boolean) {
|
|
111
114
|
cardActions &&
|
|
112
115
|
cardActions.forEach(cardAction => {
|
|
113
116
|
this.card.addAction(addCardAction(cardAction, includesOAuthButtons));
|
|
@@ -119,6 +122,7 @@ export default class AdaptiveCardBuilder {
|
|
|
119
122
|
this.addTextBlock(content.title, {
|
|
120
123
|
color: TextColor.Default,
|
|
121
124
|
size: TextSize.Medium,
|
|
125
|
+
style: 'heading',
|
|
122
126
|
weight: TextWeight.Bolder,
|
|
123
127
|
wrap: richCardWrapTitle
|
|
124
128
|
});
|
|
@@ -131,7 +135,7 @@ export default class AdaptiveCardBuilder {
|
|
|
131
135
|
this.addButtons(content.buttons);
|
|
132
136
|
}
|
|
133
137
|
|
|
134
|
-
addImage(url: string, container?: Container, selectAction?:
|
|
138
|
+
addImage(url: string, container?: Container, selectAction?: DirectLineCardAction, altText?: string) {
|
|
135
139
|
container = container || this.container;
|
|
136
140
|
|
|
137
141
|
const image = new Image();
|
|
@@ -146,7 +150,7 @@ export default class AdaptiveCardBuilder {
|
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
export interface ICommonContent {
|
|
149
|
-
buttons?:
|
|
153
|
+
buttons?: DirectLineCardAction[];
|
|
150
154
|
subtitle?: string;
|
|
151
155
|
text?: string;
|
|
152
156
|
title?: string;
|
|
@@ -1,34 +1,65 @@
|
|
|
1
1
|
/* eslint no-magic-numbers: ["error", { "ignore": [-1, 0, 2] }] */
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
Action as AdaptiveCardAction,
|
|
5
|
+
AdaptiveCard,
|
|
6
|
+
IMarkdownProcessingResult,
|
|
7
|
+
OpenUrlAction,
|
|
8
|
+
SubmitAction
|
|
9
|
+
} from 'adaptivecards';
|
|
3
10
|
import { Components, getTabIndex, hooks } from 'botframework-webchat-component';
|
|
11
|
+
import { DirectLineCardAction } from 'botframework-webchat-core';
|
|
4
12
|
import classNames from 'classnames';
|
|
5
13
|
import PropTypes from 'prop-types';
|
|
6
|
-
import React, {
|
|
7
|
-
|
|
14
|
+
import React, {
|
|
15
|
+
KeyboardEventHandler,
|
|
16
|
+
MouseEventHandler,
|
|
17
|
+
useCallback,
|
|
18
|
+
useEffect,
|
|
19
|
+
useLayoutEffect,
|
|
20
|
+
useRef,
|
|
21
|
+
useState,
|
|
22
|
+
VFC
|
|
23
|
+
} from 'react';
|
|
24
|
+
|
|
25
|
+
import { BotFrameworkCardAction } from './AdaptiveCardBuilder';
|
|
8
26
|
import useAdaptiveCardsHostConfig from '../hooks/useAdaptiveCardsHostConfig';
|
|
9
27
|
import useAdaptiveCardsPackage from '../hooks/useAdaptiveCardsPackage';
|
|
10
28
|
|
|
11
29
|
const { ErrorBox } = Components;
|
|
12
30
|
const { useDisabled, useLocalizer, usePerformCardAction, useRenderMarkdownAsHTML, useScrollToEnd, useStyleSet } = hooks;
|
|
13
31
|
|
|
14
|
-
// eslint-disable-next-line no-undef
|
|
15
32
|
const node_env = process.env.node_env || process.env.NODE_ENV;
|
|
16
33
|
|
|
17
|
-
|
|
18
|
-
|
|
34
|
+
type UndoFunction = (() => void) | undefined;
|
|
35
|
+
|
|
36
|
+
function bunchUndos(...fns: UndoFunction[]): UndoFunction {
|
|
37
|
+
return () => fns.forEach(fn => fn?.());
|
|
38
|
+
}
|
|
19
39
|
|
|
20
|
-
|
|
21
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Adds a class to the `HTMLElement`. Returns `true` if the class is added, otherwise, `undefined`.
|
|
42
|
+
*/
|
|
43
|
+
function addClass(element: HTMLElement, className: string): true | undefined {
|
|
44
|
+
const { classList } = element;
|
|
22
45
|
|
|
23
|
-
|
|
46
|
+
if (!classList.contains(className)) {
|
|
47
|
+
classList.add(className);
|
|
24
48
|
|
|
25
49
|
return true;
|
|
26
50
|
}
|
|
27
|
-
|
|
28
|
-
return false;
|
|
29
51
|
}
|
|
30
52
|
|
|
31
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Adds a class to the `HTMLElement` and re-add on mutations.
|
|
55
|
+
*
|
|
56
|
+
* @returns {function} A function, when called, will restore to previous state.
|
|
57
|
+
*/
|
|
58
|
+
function addPersistentClassWithUndo(element: HTMLElement | undefined, className: string): UndoFunction {
|
|
59
|
+
if (!element) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
32
63
|
if (addClass(element, className)) {
|
|
33
64
|
// After we add the class, keep observing the element to make sure the class is not removed.
|
|
34
65
|
const observer = new MutationObserver(() => addClass(element, className));
|
|
@@ -46,11 +77,27 @@ function addPersistentClassWithUndo(element, className) {
|
|
|
46
77
|
}
|
|
47
78
|
}
|
|
48
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Returns `true`, if the object is a plain object and not a class, otherwise, `false`.
|
|
82
|
+
*/
|
|
49
83
|
function isPlainObject(obj) {
|
|
50
84
|
return Object.getPrototypeOf(obj) === Object.prototype;
|
|
51
85
|
}
|
|
52
86
|
|
|
53
|
-
|
|
87
|
+
/**
|
|
88
|
+
* Sets an attribute.
|
|
89
|
+
*
|
|
90
|
+
* @returns {function} A function, when called, will restore to previous state.
|
|
91
|
+
*/
|
|
92
|
+
function setAttributeWithUndo(
|
|
93
|
+
element: HTMLElement | undefined,
|
|
94
|
+
qualifiedName: string,
|
|
95
|
+
nextValue: string
|
|
96
|
+
): UndoFunction {
|
|
97
|
+
if (!element) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
54
101
|
const value = element.getAttribute(qualifiedName);
|
|
55
102
|
|
|
56
103
|
if (value !== nextValue) {
|
|
@@ -60,15 +107,29 @@ function setAttributeWithUndo(element, qualifiedName, nextValue) {
|
|
|
60
107
|
}
|
|
61
108
|
}
|
|
62
109
|
|
|
63
|
-
|
|
110
|
+
/**
|
|
111
|
+
* An event handler for disabling event bubbling and propagation.
|
|
112
|
+
*/
|
|
113
|
+
const disabledHandler = (event: Event) => {
|
|
64
114
|
event.preventDefault();
|
|
65
115
|
event.stopImmediatePropagation();
|
|
66
116
|
event.stopPropagation();
|
|
67
117
|
};
|
|
68
118
|
|
|
69
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Listens to event once. Returns a function, when called, will stop listening.
|
|
121
|
+
*/
|
|
122
|
+
function addEventListenerOnceWithUndo(
|
|
123
|
+
element: HTMLElement | undefined,
|
|
124
|
+
name: string,
|
|
125
|
+
handler: EventListener
|
|
126
|
+
): UndoFunction {
|
|
127
|
+
if (!element) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
70
131
|
/* eslint-disable-next-line prefer-const */
|
|
71
|
-
let detach;
|
|
132
|
+
let detach: () => void;
|
|
72
133
|
const detachingHandler = event => {
|
|
73
134
|
try {
|
|
74
135
|
handler(event);
|
|
@@ -85,14 +146,34 @@ function addEventListenerOnceWithUndo(element, name, handler) {
|
|
|
85
146
|
return detach;
|
|
86
147
|
}
|
|
87
148
|
|
|
88
|
-
|
|
149
|
+
/**
|
|
150
|
+
* Listens to event. Returns a function, when called, will stop listening.
|
|
151
|
+
*/
|
|
152
|
+
function addEventListenerWithUndo(
|
|
153
|
+
element: HTMLElement | undefined,
|
|
154
|
+
name: string,
|
|
155
|
+
handler: EventListener
|
|
156
|
+
): UndoFunction {
|
|
157
|
+
if (!element) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
89
161
|
element.addEventListener(name, handler);
|
|
90
162
|
|
|
91
163
|
return () => element.removeEventListener(name, handler);
|
|
92
164
|
}
|
|
93
165
|
|
|
94
|
-
|
|
95
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Disables an element with undo function.
|
|
168
|
+
*
|
|
169
|
+
* @returns {function} A function, when called, will restore to previous state.
|
|
170
|
+
*/
|
|
171
|
+
function disableElementWithUndo(element: HTMLElement | undefined): UndoFunction {
|
|
172
|
+
if (!element) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const undoStack: UndoFunction[] = [];
|
|
96
177
|
const isActive = element === document.activeElement;
|
|
97
178
|
const tag = element.nodeName.toLowerCase();
|
|
98
179
|
|
|
@@ -128,11 +209,23 @@ function disableElementWithUndo(element) {
|
|
|
128
209
|
break;
|
|
129
210
|
}
|
|
130
211
|
|
|
131
|
-
return (
|
|
212
|
+
return bunchUndos(...undoStack);
|
|
132
213
|
}
|
|
133
214
|
|
|
134
|
-
|
|
135
|
-
|
|
215
|
+
/**
|
|
216
|
+
* Disables all inputtable descendants.
|
|
217
|
+
*
|
|
218
|
+
* @param {HTMLElement | undefined} element Container element to start looking for inputtable descendants.
|
|
219
|
+
* @param {boolean} observeSubtree `true` to applies to all future inputtable descendants, otherwise, `false`.
|
|
220
|
+
*
|
|
221
|
+
* @returns {function} A function, when called, will restore to previous state.
|
|
222
|
+
*/
|
|
223
|
+
function disableInputElementsWithUndo(element: HTMLElement | undefined, observeSubtree = true): UndoFunction {
|
|
224
|
+
if (!element) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const undoStack: (() => void)[] = [].map.call(element.querySelectorAll('button, input, select, textarea'), element =>
|
|
136
229
|
disableElementWithUndo(element)
|
|
137
230
|
);
|
|
138
231
|
|
|
@@ -154,18 +247,7 @@ function disableInputElementsWithUndo(element: HTMLElement, observeSubtree = tru
|
|
|
154
247
|
undoStack.push(() => observer.disconnect());
|
|
155
248
|
}
|
|
156
249
|
|
|
157
|
-
return (
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Checks if an element contains a class.
|
|
162
|
-
*
|
|
163
|
-
* @param {HTMLElement} element - The element to check for the class.
|
|
164
|
-
* @param {string} className - The name of the class to check for.
|
|
165
|
-
* @returns {boolean} `true` if the element contains the class, otherwise, `false`.
|
|
166
|
-
*/
|
|
167
|
-
function containsClassName(element: HTMLElement, className: string): boolean {
|
|
168
|
-
return (element.className || '').split(' ').includes(className);
|
|
250
|
+
return bunchUndos(...undoStack);
|
|
169
251
|
}
|
|
170
252
|
|
|
171
253
|
/**
|
|
@@ -174,7 +256,7 @@ function containsClassName(element: HTMLElement, className: string): boolean {
|
|
|
174
256
|
* @returns {false | string} The value of the attribute. `false` if the attribute was not set.
|
|
175
257
|
*/
|
|
176
258
|
function getAttribute(element: HTMLElement, qualifiedName: string): false | string {
|
|
177
|
-
return element.hasAttribute(qualifiedName) && element.getAttribute(qualifiedName);
|
|
259
|
+
return !!element && element.hasAttribute(qualifiedName) && (element.getAttribute(qualifiedName) || '');
|
|
178
260
|
}
|
|
179
261
|
|
|
180
262
|
/**
|
|
@@ -184,11 +266,11 @@ function getAttribute(element: HTMLElement, qualifiedName: string): false | stri
|
|
|
184
266
|
* @param {string} qualifiedName - The name of the attribute.
|
|
185
267
|
* @param {false | string} value - The value of the attribute. When passing `false`, remove the attribute.
|
|
186
268
|
*/
|
|
187
|
-
function setOrRemoveAttribute(element: HTMLElement, qualifiedName: string, value: false | string): void {
|
|
269
|
+
function setOrRemoveAttribute(element: HTMLElement | undefined, qualifiedName: string, value: false | string): void {
|
|
188
270
|
if (value === false) {
|
|
189
|
-
element
|
|
271
|
+
element?.removeAttribute(qualifiedName);
|
|
190
272
|
} else {
|
|
191
|
-
element
|
|
273
|
+
element?.setAttribute(qualifiedName, value);
|
|
192
274
|
}
|
|
193
275
|
}
|
|
194
276
|
|
|
@@ -201,7 +283,15 @@ function setOrRemoveAttribute(element: HTMLElement, qualifiedName: string, value
|
|
|
201
283
|
*
|
|
202
284
|
* @returns {() => void} The undo function, when called, will undo all manipulations by restoring values recorded at the time of the function call.
|
|
203
285
|
*/
|
|
204
|
-
function setOrRemoveAttributeWithUndo(
|
|
286
|
+
function setOrRemoveAttributeWithUndo(
|
|
287
|
+
element: HTMLElement | undefined,
|
|
288
|
+
qualifiedName: string,
|
|
289
|
+
value: false | string
|
|
290
|
+
): UndoFunction {
|
|
291
|
+
if (!element) {
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
|
|
205
295
|
const prevValue = getAttribute(element, qualifiedName);
|
|
206
296
|
|
|
207
297
|
setOrRemoveAttribute(element, qualifiedName, value);
|
|
@@ -218,14 +308,12 @@ function setOrRemoveAttributeWithUndo(element: HTMLElement, qualifiedName: strin
|
|
|
218
308
|
* @returns {HTMLElement | undefined} The first ancestor that fulfill the predicate, otherwise, `undefined`.
|
|
219
309
|
*/
|
|
220
310
|
function findAncestor(element: HTMLElement, predicate: (ancestor: HTMLElement) => boolean): HTMLElement | undefined {
|
|
221
|
-
let current = element
|
|
311
|
+
let current = element;
|
|
222
312
|
|
|
223
|
-
while (current) {
|
|
313
|
+
while ((current = current.parentElement)) {
|
|
224
314
|
if (predicate.call(element, current)) {
|
|
225
315
|
return current;
|
|
226
316
|
}
|
|
227
|
-
|
|
228
|
-
current = current.parentElement;
|
|
229
317
|
}
|
|
230
318
|
}
|
|
231
319
|
|
|
@@ -241,15 +329,15 @@ function findAncestor(element: HTMLElement, predicate: (ancestor: HTMLElement) =
|
|
|
241
329
|
* @returns {() => void} The undo function, when called, will undo all manipulations by restoring values recorded at the time of the function call.
|
|
242
330
|
*/
|
|
243
331
|
function indicateActionSelectionWithUndo(
|
|
244
|
-
selectedActionElements: HTMLElement[],
|
|
332
|
+
selectedActionElements: HTMLElement[] | undefined,
|
|
245
333
|
actionPerformedClassName?: string
|
|
246
|
-
):
|
|
247
|
-
if (!selectedActionElements
|
|
334
|
+
): UndoFunction {
|
|
335
|
+
if (!selectedActionElements?.length) {
|
|
248
336
|
return;
|
|
249
337
|
}
|
|
250
338
|
|
|
251
339
|
// Verify all input elements are "ac-pushButton", could belongs to ActionSet or "card actions".
|
|
252
|
-
if (selectedActionElements.some(actionElement => !
|
|
340
|
+
if (selectedActionElements.some(actionElement => !actionElement.classList.contains('ac-pushButton'))) {
|
|
253
341
|
console.warn(
|
|
254
342
|
'botframework-webchat: Cannot mark selected action in the card, some elements are not an "ac-pushButton".'
|
|
255
343
|
);
|
|
@@ -296,7 +384,7 @@ function indicateActionSelectionWithUndo(
|
|
|
296
384
|
);
|
|
297
385
|
});
|
|
298
386
|
|
|
299
|
-
return (
|
|
387
|
+
return bunchUndos(...undoStack);
|
|
300
388
|
}
|
|
301
389
|
|
|
302
390
|
/**
|
|
@@ -304,21 +392,21 @@ function indicateActionSelectionWithUndo(
|
|
|
304
392
|
*
|
|
305
393
|
* @returns {() => void} The undo function, when called, will undo all manipulations by restoring values recorded at the time of the function call.
|
|
306
394
|
*/
|
|
307
|
-
function fixAccessibilityIssuesWithUndo(element: HTMLElement):
|
|
308
|
-
|
|
309
|
-
|
|
395
|
+
function fixAccessibilityIssuesWithUndo(element: HTMLElement): UndoFunction {
|
|
396
|
+
if (!element) {
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
310
399
|
|
|
400
|
+
// These hacks should be done in Adaptive Cards library instead.
|
|
311
401
|
// Related to #3949: All action buttons inside role="menubar" should be role="menuitem".
|
|
312
|
-
undoStack.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
)
|
|
316
|
-
);
|
|
402
|
+
const undoStack: UndoFunction[] = Array.from(
|
|
403
|
+
element.querySelectorAll('.ac-actionSet[role="menubar"] [role="button"]') as NodeListOf<HTMLElement>
|
|
404
|
+
).map(actionButton => setAttributeWithUndo(actionButton, 'role', 'menuitem'));
|
|
317
405
|
|
|
318
|
-
return () => undoStack.forEach(undo => undo());
|
|
406
|
+
return () => undoStack.forEach(undo => undo?.());
|
|
319
407
|
}
|
|
320
408
|
|
|
321
|
-
function getFocusableElements(element) {
|
|
409
|
+
function getFocusableElements(element: HTMLElement) {
|
|
322
410
|
return [].filter.call(
|
|
323
411
|
element.querySelectorAll(
|
|
324
412
|
[
|
|
@@ -335,7 +423,7 @@ function getFocusableElements(element) {
|
|
|
335
423
|
'textarea',
|
|
336
424
|
'[tabindex]'
|
|
337
425
|
].join(', ')
|
|
338
|
-
)
|
|
426
|
+
) as NodeListOf<HTMLElement>,
|
|
339
427
|
element => {
|
|
340
428
|
const tabIndex = getTabIndex(element);
|
|
341
429
|
|
|
@@ -344,50 +432,68 @@ function getFocusableElements(element) {
|
|
|
344
432
|
);
|
|
345
433
|
}
|
|
346
434
|
|
|
347
|
-
function restoreActiveElementIndex(element, activeElementIndex) {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
focusable && focusable.focus();
|
|
435
|
+
function restoreActiveElementIndex(element: HTMLElement, activeElementIndex: number) {
|
|
436
|
+
getFocusableElements(element)[+activeElementIndex]?.focus();
|
|
351
437
|
}
|
|
352
438
|
|
|
353
|
-
function saveActiveElementIndex(element) {
|
|
439
|
+
function saveActiveElementIndex(element: HTMLElement) {
|
|
354
440
|
return getFocusableElements(element).indexOf(document.activeElement);
|
|
355
441
|
}
|
|
356
442
|
|
|
357
|
-
function restoreInputValues(element, inputValues) {
|
|
358
|
-
const inputs = element.querySelectorAll('input, select, textarea')
|
|
443
|
+
function restoreInputValues(element: HTMLElement, inputValues: (boolean | string)[]) {
|
|
444
|
+
const inputs = element.querySelectorAll('input, select, textarea') as NodeListOf<
|
|
445
|
+
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
446
|
+
>;
|
|
359
447
|
|
|
360
|
-
[].forEach.call(inputs, (input, index) => {
|
|
361
|
-
const value = inputValues[index];
|
|
448
|
+
[].forEach.call(inputs, (input: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, index: number) => {
|
|
449
|
+
const value = inputValues[+index];
|
|
362
450
|
|
|
363
451
|
if (typeof value !== 'undefined') {
|
|
364
452
|
const { tagName, type } = input;
|
|
365
453
|
|
|
366
454
|
if (tagName === 'INPUT' && (type === 'checkbox' || type === 'radio')) {
|
|
367
|
-
|
|
368
|
-
|
|
455
|
+
if (typeof value === 'boolean') {
|
|
456
|
+
(input as HTMLInputElement).checked = value;
|
|
457
|
+
}
|
|
458
|
+
} else if (typeof value === 'string') {
|
|
369
459
|
input.value = value;
|
|
370
460
|
}
|
|
371
461
|
}
|
|
372
462
|
});
|
|
373
463
|
}
|
|
374
464
|
|
|
375
|
-
function saveInputValues(element) {
|
|
376
|
-
const inputs = element.querySelectorAll('input, select, textarea')
|
|
465
|
+
function saveInputValues(element: HTMLElement): (boolean | string)[] {
|
|
466
|
+
const inputs = element.querySelectorAll('input, select, textarea') as NodeListOf<
|
|
467
|
+
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
|
468
|
+
>;
|
|
377
469
|
|
|
378
|
-
return [].map.call(inputs, (
|
|
379
|
-
|
|
380
|
-
|
|
470
|
+
return [].map.call(inputs, (input: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement) => {
|
|
471
|
+
const { type } = input;
|
|
472
|
+
|
|
473
|
+
if (input.tagName === 'INPUT' && (type === 'checkbox' || type === 'radio')) {
|
|
474
|
+
return (input as HTMLInputElement).checked;
|
|
381
475
|
}
|
|
382
476
|
|
|
383
|
-
return value;
|
|
477
|
+
return input.value;
|
|
384
478
|
});
|
|
385
479
|
}
|
|
386
480
|
|
|
387
|
-
|
|
481
|
+
type AdaptiveCardRendererProps = {
|
|
482
|
+
actionPerformedClassName?: string;
|
|
483
|
+
adaptiveCard: AdaptiveCard;
|
|
484
|
+
disabled?: boolean;
|
|
485
|
+
tapAction?: DirectLineCardAction;
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const AdaptiveCardRenderer: VFC<AdaptiveCardRendererProps> = ({
|
|
489
|
+
actionPerformedClassName,
|
|
490
|
+
adaptiveCard,
|
|
491
|
+
disabled: disabledFromProps,
|
|
492
|
+
tapAction
|
|
493
|
+
}) => {
|
|
388
494
|
const [{ adaptiveCardRenderer: adaptiveCardRendererStyleSet }] = useStyleSet();
|
|
389
495
|
const [{ GlobalSettings, HostConfig }] = useAdaptiveCardsPackage();
|
|
390
|
-
const [actionsPerformed, setActionsPerformed] = useState([]);
|
|
496
|
+
const [actionsPerformed, setActionsPerformed] = useState<AdaptiveCardAction[]>([]);
|
|
391
497
|
const [adaptiveCardsHostConfig] = useAdaptiveCardsHostConfig();
|
|
392
498
|
const [disabledFromComposer] = useDisabled();
|
|
393
499
|
const [errors, setErrors] = useState([]);
|
|
@@ -395,7 +501,7 @@ const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled
|
|
|
395
501
|
const activeElementIndexRef = useRef(-1);
|
|
396
502
|
const adaptiveCardElementRef = useRef<HTMLElement>();
|
|
397
503
|
const contentRef = useRef<HTMLDivElement>();
|
|
398
|
-
const inputValuesRef = useRef([]);
|
|
504
|
+
const inputValuesRef = useRef<(boolean | string)[]>([]);
|
|
399
505
|
const localize = useLocalizer();
|
|
400
506
|
const performCardAction = usePerformCardAction();
|
|
401
507
|
const renderMarkdownAsHTML = useRenderMarkdownAsHTML();
|
|
@@ -406,8 +512,9 @@ const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled
|
|
|
406
512
|
// TODO: [P2] #3199 We should consider using `adaptiveCard.selectAction` instead.
|
|
407
513
|
// The null check for "tapAction" is in "handleClickAndKeyPressForTapAction".
|
|
408
514
|
const handleClickAndKeyPress = useCallback(
|
|
409
|
-
event => {
|
|
410
|
-
const { key,
|
|
515
|
+
(event: KeyboardEvent | MouseEvent): void => {
|
|
516
|
+
const { key, type } = event as KeyboardEvent;
|
|
517
|
+
const target = event.target as HTMLDivElement;
|
|
411
518
|
|
|
412
519
|
// Some items, e.g. tappable text, cannot be disabled thru DOM attributes
|
|
413
520
|
const { current } = contentRef;
|
|
@@ -451,12 +558,13 @@ const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled
|
|
|
451
558
|
const handleClickAndKeyPressForTapAction = !disabled && tapAction ? handleClickAndKeyPress : undefined;
|
|
452
559
|
|
|
453
560
|
const addActionsPerformed = useCallback(
|
|
454
|
-
action
|
|
561
|
+
(action: AdaptiveCardAction): void =>
|
|
562
|
+
!~actionsPerformed.indexOf(action) && setActionsPerformed([...actionsPerformed, action]),
|
|
455
563
|
[actionsPerformed, setActionsPerformed]
|
|
456
564
|
);
|
|
457
565
|
|
|
458
566
|
const handleExecuteAction = useCallback(
|
|
459
|
-
action => {
|
|
567
|
+
(action: AdaptiveCardAction): void => {
|
|
460
568
|
// Some items, e.g. tappable image, cannot be disabled thru DOM attributes
|
|
461
569
|
if (disabled) {
|
|
462
570
|
return;
|
|
@@ -465,25 +573,40 @@ const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled
|
|
|
465
573
|
addActionsPerformed(action);
|
|
466
574
|
|
|
467
575
|
const actionTypeName = action.getJsonTypeName();
|
|
576
|
+
const { iconUrl: image, title } = action;
|
|
468
577
|
|
|
578
|
+
// We cannot use "instanceof" check here, because web devs may bring their own version of Adaptive Cards package.
|
|
579
|
+
// We need to check using "getJsonTypeName()" instead.
|
|
469
580
|
if (actionTypeName === 'Action.OpenUrl') {
|
|
581
|
+
const { url: value } = action as OpenUrlAction;
|
|
582
|
+
|
|
470
583
|
performCardAction({
|
|
584
|
+
image,
|
|
585
|
+
title,
|
|
471
586
|
type: 'openUrl',
|
|
472
|
-
value
|
|
587
|
+
value
|
|
473
588
|
});
|
|
474
589
|
} else if (actionTypeName === 'Action.Submit') {
|
|
475
|
-
|
|
476
|
-
|
|
590
|
+
const { data } = action as SubmitAction as {
|
|
591
|
+
data: string | BotFrameworkCardAction;
|
|
592
|
+
};
|
|
477
593
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
594
|
+
if (typeof data !== 'undefined') {
|
|
595
|
+
if (typeof data === 'string') {
|
|
596
|
+
performCardAction({
|
|
597
|
+
image,
|
|
598
|
+
title,
|
|
599
|
+
type: 'imBack',
|
|
600
|
+
value: data
|
|
601
|
+
});
|
|
602
|
+
} else if (data.__isBotFrameworkCardAction) {
|
|
603
|
+
performCardAction(data.cardAction);
|
|
483
604
|
} else {
|
|
484
605
|
performCardAction({
|
|
485
|
-
|
|
486
|
-
|
|
606
|
+
image,
|
|
607
|
+
title,
|
|
608
|
+
type: 'postBack',
|
|
609
|
+
value: data
|
|
487
610
|
});
|
|
488
611
|
}
|
|
489
612
|
}
|
|
@@ -511,7 +634,9 @@ const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled
|
|
|
511
634
|
// This could be limitations from Adaptive Cards package (not supported as of 1.2.5)
|
|
512
635
|
// Because there could be timing difference between .parse and .render, we could be using wrong Markdown engine
|
|
513
636
|
|
|
514
|
-
|
|
637
|
+
// "onProcessMarkdown" is a static function but we are trying to scope it to the current object instead.
|
|
638
|
+
// eslint-disable-next-line dot-notation
|
|
639
|
+
adaptiveCard.constructor['onProcessMarkdown'] = (text: string, result: IMarkdownProcessingResult) => {
|
|
515
640
|
if (renderMarkdownAsHTML) {
|
|
516
641
|
result.outputHtml = renderMarkdownAsHTML(text);
|
|
517
642
|
result.didProcess = true;
|
|
@@ -533,7 +658,7 @@ const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled
|
|
|
533
658
|
return setErrors(validationEvents.reduce((items, { message }) => [...items, new Error(message)], []));
|
|
534
659
|
}
|
|
535
660
|
|
|
536
|
-
let element;
|
|
661
|
+
let element: HTMLElement;
|
|
537
662
|
|
|
538
663
|
try {
|
|
539
664
|
element = adaptiveCard.render();
|
|
@@ -612,8 +737,8 @@ const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled
|
|
|
612
737
|
) : (
|
|
613
738
|
<div
|
|
614
739
|
className={classNames(adaptiveCardRendererStyleSet + '', 'webchat__adaptive-card-renderer')}
|
|
615
|
-
onClick={handleClickAndKeyPressForTapAction}
|
|
616
|
-
onKeyPress={handleClickAndKeyPressForTapAction}
|
|
740
|
+
onClick={handleClickAndKeyPressForTapAction as unknown as MouseEventHandler<HTMLDivElement>}
|
|
741
|
+
onKeyPress={handleClickAndKeyPressForTapAction as unknown as KeyboardEventHandler<HTMLDivElement>}
|
|
617
742
|
ref={contentRef}
|
|
618
743
|
/>
|
|
619
744
|
);
|
|
@@ -629,7 +754,12 @@ AdaptiveCardRenderer.propTypes = {
|
|
|
629
754
|
actionPerformedClassName: PropTypes.string,
|
|
630
755
|
adaptiveCard: PropTypes.any.isRequired,
|
|
631
756
|
disabled: PropTypes.bool,
|
|
757
|
+
|
|
758
|
+
// TypeScript class is not mappable to PropTypes.func
|
|
759
|
+
// @ts-ignore
|
|
632
760
|
tapAction: PropTypes.shape({
|
|
761
|
+
image: PropTypes.string,
|
|
762
|
+
title: PropTypes.string,
|
|
633
763
|
type: PropTypes.string.isRequired,
|
|
634
764
|
value: PropTypes.string
|
|
635
765
|
})
|