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.
Files changed (97) hide show
  1. package/.eslintrc.yml +5 -109
  2. package/.prettierrc.yml +1 -1
  3. package/README.md +1 -1
  4. package/babel.config.json +2 -2
  5. package/babel.sanitize-html.config.json +10 -0
  6. package/lib/AddFullBundle.d.ts.map +1 -1
  7. package/lib/AddFullBundle.js +1 -2
  8. package/lib/FullComposer.js +2 -2
  9. package/lib/FullReactWebChat.js +1 -1
  10. package/lib/adaptiveCards/AdaptiveCardsComposer.d.ts.map +1 -1
  11. package/lib/adaptiveCards/AdaptiveCardsComposer.js +6 -2
  12. package/lib/adaptiveCards/AdaptiveCardsContext.d.ts +0 -1
  13. package/lib/adaptiveCards/AdaptiveCardsContext.d.ts.map +1 -1
  14. package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.d.ts +7 -7
  15. package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.d.ts.map +1 -1
  16. package/lib/adaptiveCards/Attachment/AdaptiveCardBuilder.js +10 -3
  17. package/lib/adaptiveCards/Attachment/AdaptiveCardContent.js +8 -4
  18. package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.d.ts +9 -22
  19. package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.d.ts.map +1 -1
  20. package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.js +175 -95
  21. package/lib/adaptiveCards/Attachment/AnimationCardContent.js +1 -1
  22. package/lib/adaptiveCards/Attachment/AudioCardContent.js +1 -1
  23. package/lib/adaptiveCards/Attachment/CommonCard.js +7 -3
  24. package/lib/adaptiveCards/Attachment/HeroCardContent.js +7 -3
  25. package/lib/adaptiveCards/Attachment/OAuthCardContent.js +7 -3
  26. package/lib/adaptiveCards/Attachment/ReceiptCardContent.js +7 -3
  27. package/lib/adaptiveCards/Attachment/SignInCardContent.js +1 -1
  28. package/lib/adaptiveCards/Attachment/ThumbnailCardContent.js +7 -3
  29. package/lib/adaptiveCards/Attachment/VideoCardContent.js +1 -1
  30. package/lib/adaptiveCards/AttachmentForScreenReader/AdaptiveCardAttachment.js +7 -3
  31. package/lib/adaptiveCards/AttachmentForScreenReader/RichCardAttachment.js +1 -1
  32. package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.d.ts +3 -0
  33. package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.d.ts.map +1 -1
  34. package/lib/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.js +8 -1
  35. package/lib/adaptiveCards/Styles/adaptiveCardHostConfig.js +2 -2
  36. package/lib/adaptiveCards/Styles/createAdaptiveCardsStyleSet.js +1 -1
  37. package/lib/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.d.ts.map +1 -1
  38. package/lib/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.js +1 -1
  39. package/lib/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.d.ts.map +1 -1
  40. package/lib/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.js +1 -3
  41. package/lib/adaptiveCards/hooks/internal/useParseAdaptiveCardJSON.js +1 -1
  42. package/lib/adaptiveCards/hooks/useAdaptiveCardsHostConfig.js +1 -1
  43. package/lib/adaptiveCards/hooks/useAdaptiveCardsPackage.js +1 -1
  44. package/lib/adaptiveCards/normalizeStyleOptions.js +1 -1
  45. package/lib/addVersion.js +2 -2
  46. package/lib/createCognitiveServicesSpeechServicesPonyfillFactory.js +1 -1
  47. package/lib/createFullStyleSet.d.ts +324 -55
  48. package/lib/createFullStyleSet.d.ts.map +1 -1
  49. package/lib/createFullStyleSet.js +1 -1
  50. package/lib/fullBundleDefaultStyleOptions.js +1 -1
  51. package/lib/hooks/useStyleOptions.js +1 -1
  52. package/lib/index-es5.d.ts +1 -21
  53. package/lib/index-es5.d.ts.map +1 -1
  54. package/lib/index-es5.js +3 -43
  55. package/lib/index-minimal.js +27 -21
  56. package/lib/index.d.ts +10 -1
  57. package/lib/index.d.ts.map +1 -1
  58. package/lib/index.js +10 -7
  59. package/lib/polyfill.d.ts +23 -0
  60. package/lib/polyfill.d.ts.map +1 -0
  61. package/lib/polyfill.js +46 -0
  62. package/lib/renderMarkdown.d.ts.map +1 -1
  63. package/lib/renderMarkdown.js +34 -6
  64. package/lib/speech/CustomAudioInputStream.d.ts.map +1 -1
  65. package/lib/speech/CustomAudioInputStream.js +40 -15
  66. package/lib/speech/createAudioConfig.d.ts.map +1 -1
  67. package/lib/speech/createAudioConfig.js +9 -3
  68. package/lib/speech/createMicrophoneAudioConfigAndAudioContext.d.ts.map +1 -1
  69. package/lib/speech/createMicrophoneAudioConfigAndAudioContext.js +6 -1
  70. package/lib/speech/getUserMedia.js +1 -1
  71. package/lib/useComposerProps.js +1 -1
  72. package/package.json +35 -41
  73. package/src/AddFullBundle.tsx +0 -1
  74. package/src/__tests__/createDirectLine.spec.js +2 -0
  75. package/src/__tests__/renderMarkdown.spec.js +1 -1
  76. package/src/__tests__/versionTag.es5.spec.js +1 -0
  77. package/src/__tests__/versionTag.full.spec.js +1 -0
  78. package/src/__tests__/versionTag.minimal.spec.js +1 -0
  79. package/src/adaptiveCards/AdaptiveCardsComposer.tsx +4 -3
  80. package/src/adaptiveCards/Attachment/AdaptiveCardBuilder.ts +16 -12
  81. package/src/adaptiveCards/Attachment/AdaptiveCardRenderer.tsx +226 -96
  82. package/src/adaptiveCards/Styles/StyleSet/AdaptiveCardRenderer.ts +8 -0
  83. package/src/adaptiveCards/Styles/createAdaptiveCardsStyleSet.spec.js +2 -0
  84. package/src/adaptiveCards/createAdaptiveCardsAttachmentForScreenReaderMiddleware.tsx +14 -12
  85. package/src/adaptiveCards/createAdaptiveCardsAttachmentMiddleware.tsx +26 -25
  86. package/src/adaptiveCards/hooks/useAdaptiveCardsHostConfig.ts +4 -4
  87. package/src/createCognitiveServicesSpeechServicesPonyfillFactory.spec.js +5 -5
  88. package/src/index-es5.ts +3 -26
  89. package/src/polyfill.ts +29 -0
  90. package/src/renderMarkdown.ts +40 -4
  91. package/src/speech/CustomAudioInputStream.ts +39 -10
  92. package/src/speech/createAudioConfig.spec.js +1 -1
  93. package/src/speech/createAudioConfig.ts +9 -6
  94. package/src/speech/createMicrophoneAudioConfigAndAudioContext.ts +1 -3
  95. package/src/useComposerProps.ts +4 -4
  96. package/webpack.config.js +24 -4
  97. package/.eslintignore +0 -1
@@ -1,3 +1,4 @@
1
+ /** @jest-environment jsdom */
1
2
  /* globals process */
2
3
 
3
4
  process.env.npm_package_version = '0.0.0-test';
@@ -16,9 +16,10 @@ const AdaptiveCardsComposer: FC<AdaptiveCardsComposerProps> = ({
16
16
  adaptiveCardsPackage,
17
17
  children
18
18
  }) => {
19
- const patchedAdaptiveCardsPackage = useMemo(() => adaptiveCardsPackage || defaultAdaptiveCardsPackage, [
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 { CardAction } from 'botframework-directlinejs';
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: boolean;
25
- cardAction: CardAction;
24
+ __isBotFrameworkCardAction: true;
25
+ cardAction: DirectLineCardAction;
26
26
  }
27
27
 
28
- function addCardAction(cardAction: CardAction, includesOAuthButtons?: boolean) {
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?: CardAction) {
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
- textblock[prop] = template[prop];
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: CardAction[], includesOAuthButtons?: boolean) {
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?: CardAction, altText?: string) {
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?: CardAction[];
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, { useCallback, useEffect, useLayoutEffect, useRef, useState } from '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
- function addClass(element, className) {
18
- const classNames = new Set(element.className.split(' '));
34
+ type UndoFunction = (() => void) | undefined;
35
+
36
+ function bunchUndos(...fns: UndoFunction[]): UndoFunction {
37
+ return () => fns.forEach(fn => fn?.());
38
+ }
19
39
 
20
- if (!classNames.has(className)) {
21
- classNames.add(className);
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
- element.className = Array.from(classNames).join(' ');
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
- function addPersistentClassWithUndo(element, className) {
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
- function setAttributeWithUndo(element, qualifiedName, nextValue) {
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
- const disabledHandler = event => {
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
- function addEventListenerOnceWithUndo(element, name, handler) {
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
- function addEventListenerWithUndo(element, name, handler) {
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
- function disableElementWithUndo(element) {
95
- const undoStack = [];
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 () => undoStack.forEach(undo => undo && undo());
212
+ return bunchUndos(...undoStack);
132
213
  }
133
214
 
134
- function disableInputElementsWithUndo(element: HTMLElement, observeSubtree = true) {
135
- const undoStack = [].map.call(element.querySelectorAll('button, input, select, textarea'), element =>
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 () => undoStack.forEach(undo => undo && undo());
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.removeAttribute(qualifiedName);
271
+ element?.removeAttribute(qualifiedName);
190
272
  } else {
191
- element.setAttribute(qualifiedName, value);
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(element: HTMLElement, qualifiedName: string, value: false | string): () => void {
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.parentElement;
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
- ): (() => void) | undefined {
247
- if (!selectedActionElements.length) {
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 => !containsClassName(actionElement, 'ac-pushButton'))) {
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 () => undoStack.forEach(undo => undo());
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): () => void {
308
- // These hacks should be done in Adaptive Cards library instead.
309
- const undoStack: (() => void)[] = [];
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.push(
313
- ...Array.from(element.querySelectorAll('.ac-actionSet[role="menubar"] [role="button"]')).map(actionButton =>
314
- setAttributeWithUndo(actionButton, 'role', 'menuitem')
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
- const focusable = getFocusableElements(element)[activeElementIndex];
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
- input.checked = value;
368
- } else {
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, ({ checked, tagName, type, value }) => {
379
- if (tagName === 'INPUT' && (type === 'checkbox' || type === 'radio')) {
380
- return checked;
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
- const AdaptiveCardRenderer = ({ actionPerformedClassName, adaptiveCard, disabled: disabledFromProps, tapAction }) => {
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, target, type } = event;
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 => !~actionsPerformed.indexOf(action) && setActionsPerformed([...actionsPerformed, 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: action.url
587
+ value
473
588
  });
474
589
  } else if (actionTypeName === 'Action.Submit') {
475
- if (typeof action.data !== 'undefined') {
476
- const { data: actionData } = action;
590
+ const { data } = action as SubmitAction as {
591
+ data: string | BotFrameworkCardAction;
592
+ };
477
593
 
478
- if (actionData && actionData.__isBotFrameworkCardAction) {
479
- const { cardAction } = actionData;
480
- const { displayText, text, type, value } = cardAction;
481
-
482
- performCardAction({ displayText, text, type, value });
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
- type: typeof action.data === 'string' ? 'imBack' : 'postBack',
486
- value: action.data
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
- adaptiveCard.constructor.onProcessMarkdown = (text, result) => {
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
  })