botframework-webchat 4.15.3-main.20220706.ecf53f4 → 4.15.3-main.20220720.8c4b995

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/dist/webchat-es5.js +1 -1
  2. package/dist/webchat-minimal.js +1 -1
  3. package/dist/webchat.js +1 -1
  4. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.d.ts +2 -0
  5. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.d.ts.map +1 -0
  6. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.js +25 -0
  7. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/findDOMNodeOwner.d.ts +3 -0
  8. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/findDOMNodeOwner.d.ts.map +1 -0
  9. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/findDOMNodeOwner.js +32 -0
  10. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useAdaptiveCardModEffect.d.ts +13 -0
  11. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useAdaptiveCardModEffect.d.ts.map +1 -0
  12. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useAdaptiveCardModEffect.js +132 -0
  13. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useLazyRef.d.ts +3 -0
  14. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useLazyRef.d.ts.map +1 -0
  15. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useLazyRef.js +21 -0
  16. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/usePrevious.d.ts +2 -0
  17. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/usePrevious.d.ts.map +1 -0
  18. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/usePrevious.js +18 -0
  19. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useValueRef.d.ts +3 -0
  20. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useValueRef.d.ts.map +1 -0
  21. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/private/useValueRef.js +15 -0
  22. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActionSetShouldNotBeMenuBarModEffect.d.ts +16 -0
  23. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActionSetShouldNotBeMenuBarModEffect.d.ts.map +1 -0
  24. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActionSetShouldNotBeMenuBarModEffect.js +45 -0
  25. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActionShouldBePushButtonModEffect.d.ts +15 -0
  26. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActionShouldBePushButtonModEffect.d.ts.map +1 -0
  27. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActionShouldBePushButtonModEffect.js +93 -0
  28. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActiveElementModEffect.d.ts +6 -0
  29. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActiveElementModEffect.d.ts.map +1 -0
  30. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useActiveElementModEffect.js +44 -0
  31. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useDisabledModEffect.d.ts +9 -0
  32. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useDisabledModEffect.d.ts.map +1 -0
  33. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/useDisabledModEffect.js +50 -0
  34. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/usePersistValuesModEffect.d.ts +6 -0
  35. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/usePersistValuesModEffect.d.ts.map +1 -0
  36. package/lib/adaptiveCards/Attachment/AdaptiveCardHacks/usePersistValuesModEffect.js +103 -0
  37. package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.d.ts.map +1 -1
  38. package/lib/adaptiveCards/Attachment/AdaptiveCardRenderer.js +97 -557
  39. package/lib/adaptiveCards/Attachment/private/renderAdaptiveCard.d.ts +15 -0
  40. package/lib/adaptiveCards/Attachment/private/renderAdaptiveCard.d.ts.map +1 -0
  41. package/lib/adaptiveCards/Attachment/private/renderAdaptiveCard.js +79 -0
  42. package/lib/adaptiveCards/DOMManipulationWithUndo/addEventListenerWithUndo.d.ts +6 -0
  43. package/lib/adaptiveCards/DOMManipulationWithUndo/addEventListenerWithUndo.d.ts.map +1 -0
  44. package/lib/adaptiveCards/DOMManipulationWithUndo/addEventListenerWithUndo.js +25 -0
  45. package/lib/adaptiveCards/DOMManipulationWithUndo/bunchUndos.d.ts +3 -0
  46. package/lib/adaptiveCards/DOMManipulationWithUndo/bunchUndos.d.ts.map +1 -0
  47. package/lib/adaptiveCards/DOMManipulationWithUndo/bunchUndos.js +23 -0
  48. package/lib/adaptiveCards/DOMManipulationWithUndo/durableAddClassWithUndo.d.ts +8 -0
  49. package/lib/adaptiveCards/DOMManipulationWithUndo/durableAddClassWithUndo.d.ts.map +1 -0
  50. package/lib/adaptiveCards/DOMManipulationWithUndo/durableAddClassWithUndo.js +38 -0
  51. package/lib/adaptiveCards/DOMManipulationWithUndo/durableDisableInputElementAccessiblyWithUndo.d.ts +22 -0
  52. package/lib/adaptiveCards/DOMManipulationWithUndo/durableDisableInputElementAccessiblyWithUndo.d.ts.map +1 -0
  53. package/lib/adaptiveCards/DOMManipulationWithUndo/durableDisableInputElementAccessiblyWithUndo.js +96 -0
  54. package/lib/adaptiveCards/DOMManipulationWithUndo/private/addClass.d.ts +5 -0
  55. package/lib/adaptiveCards/DOMManipulationWithUndo/private/addClass.d.ts.map +1 -0
  56. package/lib/adaptiveCards/DOMManipulationWithUndo/private/addClass.js +19 -0
  57. package/lib/adaptiveCards/DOMManipulationWithUndo/private/getAttributeOrFalse.d.ts +7 -0
  58. package/lib/adaptiveCards/DOMManipulationWithUndo/private/getAttributeOrFalse.d.ts.map +1 -0
  59. package/lib/adaptiveCards/DOMManipulationWithUndo/private/getAttributeOrFalse.js +16 -0
  60. package/lib/adaptiveCards/DOMManipulationWithUndo/private/noOp.d.ts +3 -0
  61. package/lib/adaptiveCards/DOMManipulationWithUndo/private/noOp.d.ts.map +1 -0
  62. package/lib/adaptiveCards/DOMManipulationWithUndo/private/noOp.js +14 -0
  63. package/lib/adaptiveCards/DOMManipulationWithUndo/private/setOrRemoveAttributeIfFalse.d.ts +9 -0
  64. package/lib/adaptiveCards/DOMManipulationWithUndo/private/setOrRemoveAttributeIfFalse.d.ts.map +1 -0
  65. package/lib/adaptiveCards/DOMManipulationWithUndo/private/setOrRemoveAttributeIfFalse.js +22 -0
  66. package/lib/adaptiveCards/DOMManipulationWithUndo/setOrRemoveAttributeIfFalseWithUndo.d.ts +12 -0
  67. package/lib/adaptiveCards/DOMManipulationWithUndo/setOrRemoveAttributeIfFalseWithUndo.d.ts.map +1 -0
  68. package/lib/adaptiveCards/DOMManipulationWithUndo/setOrRemoveAttributeIfFalseWithUndo.js +41 -0
  69. package/lib/adaptiveCards/DOMManipulationWithUndo/types/UndoFunction.d.ts +3 -0
  70. package/lib/adaptiveCards/DOMManipulationWithUndo/types/UndoFunction.d.ts.map +1 -0
  71. package/lib/adaptiveCards/DOMManipulationWithUndo/types/UndoFunction.js +2 -0
  72. package/lib/addVersion.js +1 -1
  73. package/lib/createFullStyleSet.d.ts +2 -2
  74. package/package.json +7 -7
  75. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/closest.ts +17 -0
  76. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/findDOMNodeOwner.ts +25 -0
  77. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/useAdaptiveCardModEffect.ts +93 -0
  78. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/useLazyRef.ts +15 -0
  79. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/usePrevious.ts +12 -0
  80. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/private/useValueRef.ts +11 -0
  81. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/useActionSetShouldNotBeMenuBarModEffect.ts +39 -0
  82. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/useActionShouldBePushButtonModEffect.ts +105 -0
  83. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/useActiveElementModEffect.ts +35 -0
  84. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/useDisabledModEffect.ts +45 -0
  85. package/src/adaptiveCards/Attachment/AdaptiveCardHacks/usePersistValuesModEffect.ts +110 -0
  86. package/src/adaptiveCards/Attachment/AdaptiveCardRenderer.tsx +83 -582
  87. package/src/adaptiveCards/Attachment/private/renderAdaptiveCard.ts +75 -0
  88. package/src/adaptiveCards/DOMManipulationWithUndo/addEventListenerWithUndo.ts +21 -0
  89. package/src/adaptiveCards/DOMManipulationWithUndo/bunchUndos.tsx +12 -0
  90. package/src/adaptiveCards/DOMManipulationWithUndo/durableAddClassWithUndo.ts +28 -0
  91. package/src/adaptiveCards/DOMManipulationWithUndo/durableDisableInputElementAccessiblyWithUndo.ts +84 -0
  92. package/src/adaptiveCards/DOMManipulationWithUndo/private/addClass.tsx +13 -0
  93. package/src/adaptiveCards/DOMManipulationWithUndo/private/getAttributeOrFalse.ts +8 -0
  94. package/src/adaptiveCards/DOMManipulationWithUndo/private/noOp.ts +5 -0
  95. package/src/adaptiveCards/DOMManipulationWithUndo/private/setOrRemoveAttributeIfFalse.ts +18 -0
  96. package/src/adaptiveCards/DOMManipulationWithUndo/setOrRemoveAttributeIfFalseWithUndo.ts +34 -0
  97. package/src/adaptiveCards/DOMManipulationWithUndo/types/UndoFunction.ts +3 -0
@@ -1,12 +1,6 @@
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
+ import { Action as AdaptiveCardAction, AdaptiveCard, OpenUrlAction, SubmitAction } from 'adaptivecards';
10
4
  import { Components, getTabIndex, hooks } from 'botframework-webchat-component';
11
5
  import classNames from 'classnames';
12
6
  import PropTypes from 'prop-types';
@@ -14,470 +8,29 @@ import React, {
14
8
  KeyboardEventHandler,
15
9
  MouseEventHandler,
16
10
  useCallback,
17
- useEffect,
18
11
  useLayoutEffect,
12
+ useMemo,
19
13
  useRef,
20
- useState,
21
14
  VFC
22
15
  } from 'react';
23
16
  import type { DirectLineCardAction } from 'botframework-webchat-core';
24
17
 
25
18
  import { BotFrameworkCardAction } from './AdaptiveCardBuilder';
19
+ import renderAdaptiveCard from './private/renderAdaptiveCard';
20
+ import useActionSetShouldNotBeMenuBarModEffect from './AdaptiveCardHacks/useActionSetShouldNotBeMenuBarModEffect';
21
+ import useActionShouldBePushButtonModEffect from './AdaptiveCardHacks/useActionShouldBePushButtonModEffect';
22
+ import useActiveElementModEffect from './AdaptiveCardHacks/useActiveElementModEffect';
26
23
  import useAdaptiveCardsHostConfig from '../hooks/useAdaptiveCardsHostConfig';
27
24
  import useAdaptiveCardsPackage from '../hooks/useAdaptiveCardsPackage';
25
+ import useDisabledModEffect from './AdaptiveCardHacks/useDisabledModEffect';
26
+ import usePersistValuesModEffect from './AdaptiveCardHacks/usePersistValuesModEffect';
27
+ import useValueRef from './AdaptiveCardHacks/private/useValueRef';
28
28
 
29
29
  const { ErrorBox } = Components;
30
30
  const { useDisabled, useLocalizer, usePerformCardAction, useRenderMarkdownAsHTML, useScrollToEnd, useStyleSet } = hooks;
31
31
 
32
32
  const node_env = process.env.node_env || process.env.NODE_ENV;
33
33
 
34
- type UndoFunction = (() => void) | undefined;
35
-
36
- function bunchUndos(...fns: UndoFunction[]): UndoFunction {
37
- return () => fns.forEach(fn => fn?.());
38
- }
39
-
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;
45
-
46
- if (!classList.contains(className)) {
47
- classList.add(className);
48
-
49
- return true;
50
- }
51
- }
52
-
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
-
63
- if (addClass(element, className)) {
64
- // After we add the class, keep observing the element to make sure the class is not removed.
65
- const observer = new MutationObserver(() => addClass(element, className));
66
-
67
- observer.observe(element, { attributes: true, attributeFilter: ['class'] });
68
-
69
- return () => {
70
- const classNames = new Set(element.className.split(' '));
71
-
72
- classNames.delete(className);
73
-
74
- element.className = Array.from(classNames).join(' ');
75
- observer.disconnect();
76
- };
77
- }
78
- }
79
-
80
- /**
81
- * Returns `true`, if the object is a plain object and not a class, otherwise, `false`.
82
- */
83
- function isPlainObject(obj) {
84
- return Object.getPrototypeOf(obj) === Object.prototype;
85
- }
86
-
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
-
101
- const value = element.getAttribute(qualifiedName);
102
-
103
- if (value !== nextValue) {
104
- element.setAttribute(qualifiedName, nextValue);
105
-
106
- return () => (value ? element.setAttribute(qualifiedName, value) : element.removeAttribute(qualifiedName));
107
- }
108
- }
109
-
110
- /**
111
- * An event handler for disabling event bubbling and propagation.
112
- */
113
- const disabledHandler = (event: Event) => {
114
- event.preventDefault();
115
- event.stopImmediatePropagation();
116
- event.stopPropagation();
117
- };
118
-
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
-
131
- /* eslint-disable-next-line prefer-const */
132
- let detach: () => void;
133
- const detachingHandler = event => {
134
- try {
135
- handler(event);
136
- } finally {
137
- // IE11 does not support { once: true }, so we need to detach manually.
138
- detach();
139
- }
140
- };
141
-
142
- detach = () => element.removeEventListener(name, detachingHandler);
143
-
144
- element.addEventListener(name, detachingHandler, { once: true });
145
-
146
- return detach;
147
- }
148
-
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
-
161
- element.addEventListener(name, handler);
162
-
163
- return () => element.removeEventListener(name, handler);
164
- }
165
-
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[] = [];
177
- const isActive = element === document.activeElement;
178
- const tag = element.nodeName.toLowerCase();
179
-
180
- /* eslint-disable-next-line default-case */
181
- switch (tag) {
182
- case 'button':
183
- case 'input':
184
- case 'select':
185
- case 'textarea':
186
- undoStack.push(setAttributeWithUndo(element, 'aria-disabled', 'true'));
187
-
188
- if (isActive) {
189
- undoStack.push(
190
- addEventListenerOnceWithUndo(element, 'blur', () =>
191
- undoStack.push(setAttributeWithUndo(element, 'disabled', 'disabled'))
192
- )
193
- );
194
- } else {
195
- undoStack.push(setAttributeWithUndo(element, 'disabled', 'disabled'));
196
- }
197
-
198
- if (tag === 'input' || tag === 'textarea') {
199
- undoStack.push(addEventListenerWithUndo(element, 'click', disabledHandler));
200
- undoStack.push(setAttributeWithUndo(element, 'readonly', 'readonly'));
201
- } else if (tag === 'select') {
202
- undoStack.push(
203
- ...[].map.call(element.querySelectorAll('option'), option =>
204
- setAttributeWithUndo(option, 'disabled', 'disabled')
205
- )
206
- );
207
- }
208
-
209
- break;
210
- }
211
-
212
- return bunchUndos(...undoStack);
213
- }
214
-
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 =>
229
- disableElementWithUndo(element)
230
- );
231
-
232
- const tag = element.nodeName.toLowerCase();
233
-
234
- // Only set tabindex="-1" on focusable element. Otherwise, we will make <div> focusable by mouse.
235
- (tag === 'a' || tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea') &&
236
- undoStack.push(setAttributeWithUndo(element, 'tabindex', '-1'));
237
-
238
- if (observeSubtree) {
239
- const observer = new MutationObserver(mutations =>
240
- mutations.forEach(({ addedNodes }) =>
241
- undoStack.push(...[].map.call(addedNodes, addedNode => disableInputElementsWithUndo(addedNode, false)))
242
- )
243
- );
244
-
245
- observer.observe(element, { childList: true, subtree: true });
246
-
247
- undoStack.push(() => observer.disconnect());
248
- }
249
-
250
- return bunchUndos(...undoStack);
251
- }
252
-
253
- /**
254
- * Gets the value of an attribute from an element.
255
- *
256
- * @returns {false | string} The value of the attribute. `false` if the attribute was not set.
257
- */
258
- function getAttribute(element: HTMLElement, qualifiedName: string): false | string {
259
- return !!element && element.hasAttribute(qualifiedName) && (element.getAttribute(qualifiedName) || '');
260
- }
261
-
262
- /**
263
- * Sets or removes an attribute from an element.
264
- *
265
- * @param {HTMLElement} element - The element to set or remove attribute from.
266
- * @param {string} qualifiedName - The name of the attribute.
267
- * @param {false | string} value - The value of the attribute. When passing `false`, remove the attribute.
268
- */
269
- function setOrRemoveAttribute(element: HTMLElement | undefined, qualifiedName: string, value: false | string): void {
270
- if (value === false) {
271
- element?.removeAttribute(qualifiedName);
272
- } else {
273
- element?.setAttribute(qualifiedName, value);
274
- }
275
- }
276
-
277
- /**
278
- * Sets or removes an attribute from an element with an undo function.
279
- *
280
- * @param {HTMLElement} element - The element to set or remove attribute from.
281
- * @param {string} qualifiedName - The name of the attribute.
282
- * @param {false | string} value - The value of the attribute. When passing `false`, remove the attribute.
283
- *
284
- * @returns {() => void} The undo function, when called, will undo all manipulations by restoring values recorded at the time of the function call.
285
- */
286
- function setOrRemoveAttributeWithUndo(
287
- element: HTMLElement | undefined,
288
- qualifiedName: string,
289
- value: false | string
290
- ): UndoFunction {
291
- if (!element) {
292
- return;
293
- }
294
-
295
- const prevValue = getAttribute(element, qualifiedName);
296
-
297
- setOrRemoveAttribute(element, qualifiedName, value);
298
-
299
- return () => setOrRemoveAttribute(element, qualifiedName, prevValue);
300
- }
301
-
302
- /**
303
- * Finds the first ancestor that fulfill the predicate.
304
- *
305
- * @param {HTMLElement} element - The starting element. This element will not be checked against the predicate.
306
- * @param {(ancestor: HTMLElement) => boolean} predicate - The predicate to fulfill.
307
- *
308
- * @returns {HTMLElement | undefined} The first ancestor that fulfill the predicate, otherwise, `undefined`.
309
- */
310
- function findAncestor(element: HTMLElement, predicate: (ancestor: HTMLElement) => boolean): HTMLElement | undefined {
311
- let current = element;
312
-
313
- while ((current = current.parentElement)) {
314
- if (predicate.call(element, current)) {
315
- return current;
316
- }
317
- }
318
- }
319
-
320
- /**
321
- * Indicates the action selected by performing a series of manipulations, with undo:
322
- *
323
- * - Accessibility: set `aria-pressed` to `true`
324
- * - Applies `styleOptions.actionPerformedClassName`
325
- *
326
- * @param {HTMLElement[]} selectedActionElements - An array of elements that are representing the action and is selected.
327
- * @param {string?} actionPerformedClassName - The name of the class to apply to all elements.
328
- *
329
- * @returns {() => void} The undo function, when called, will undo all manipulations by restoring values recorded at the time of the function call.
330
- */
331
- function indicateActionSelectionWithUndo(
332
- selectedActionElements: HTMLElement[] | undefined,
333
- actionPerformedClassName?: string
334
- ): UndoFunction {
335
- if (!selectedActionElements?.length) {
336
- return;
337
- }
338
-
339
- // Verify all input elements are "ac-pushButton", could belongs to ActionSet or "card actions".
340
- if (selectedActionElements.some(actionElement => !actionElement.classList.contains('ac-pushButton'))) {
341
- console.warn(
342
- 'botframework-webchat: Cannot mark selected action in the card, some elements are not an "ac-pushButton".'
343
- );
344
-
345
- return;
346
- }
347
-
348
- // A distinct set of action set containers which has selections, excluding containers without actions.
349
- // Multiple submission in an Adaptive Card is still a vague area and TBD.
350
- // We might want to disable the whole card, just buttons in same container, or do nothing (today).
351
- const actionSetElements = new Set<HTMLElement>();
352
-
353
- selectedActionElements.forEach(selectedActionElement => {
354
- const actionSetElement = findAncestor(
355
- selectedActionElement,
356
- ancestor => ancestor.getAttribute('role') === 'menubar'
357
- );
358
-
359
- actionSetElement && actionSetElements.add(actionSetElement);
360
- });
361
-
362
- const undoStack: (() => void)[] = [];
363
-
364
- actionSetElements.forEach(actionSetElement => {
365
- // Remove "role" from every "ac-actionSet" container.
366
- undoStack.push(setOrRemoveAttributeWithUndo(actionSetElement, 'role', false));
367
-
368
- // Modify "role" of every actions in the container.
369
- Array.from(actionSetElement.querySelectorAll('.ac-pushButton') as NodeListOf<HTMLElement>).forEach(
370
- actionElement => {
371
- if (selectedActionElements.includes(actionElement)) {
372
- // Add "aria-pressed" and set "role" attribute to "button" (which is required by "aria-pressed").
373
- undoStack.push(setOrRemoveAttributeWithUndo(actionElement, 'aria-pressed', 'true'));
374
- undoStack.push(setOrRemoveAttributeWithUndo(actionElement, 'role', 'button'));
375
-
376
- // Highlight actions by applying `styleOptions.actionPerformedClassName`.
377
- actionPerformedClassName &&
378
- undoStack.push(addPersistentClassWithUndo(actionElement, actionPerformedClassName));
379
- } else {
380
- // We removed "role=menubar" from the container, we must remove "role=menuitem" from unselected actions.
381
- undoStack.push(setOrRemoveAttributeWithUndo(actionElement, 'role', false));
382
- }
383
- }
384
- );
385
- });
386
-
387
- return bunchUndos(...undoStack);
388
- }
389
-
390
- /**
391
- * Fixes accessibility issues from Adaptive Card, with undo.
392
- *
393
- * @returns {() => void} The undo function, when called, will undo all manipulations by restoring values recorded at the time of the function call.
394
- */
395
- function fixAccessibilityIssuesWithUndo(element: HTMLElement): UndoFunction {
396
- if (!element) {
397
- return;
398
- }
399
-
400
- // These hacks should be done in Adaptive Cards library instead.
401
- // Related to #3949: All action buttons inside role="menubar" should be role="menuitem".
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'));
405
-
406
- return () => undoStack.forEach(undo => undo?.());
407
- }
408
-
409
- function getFocusableElements(element: HTMLElement) {
410
- return [].filter.call(
411
- element.querySelectorAll(
412
- [
413
- 'a',
414
- 'body',
415
- 'button',
416
- 'frame',
417
- 'iframe',
418
- 'img',
419
- 'input',
420
- 'isindex',
421
- 'object',
422
- 'select',
423
- 'textarea',
424
- '[tabindex]'
425
- ].join(', ')
426
- ) as NodeListOf<HTMLElement>,
427
- element => {
428
- const tabIndex = getTabIndex(element);
429
-
430
- return typeof tabIndex === 'number' && tabIndex >= 0;
431
- }
432
- );
433
- }
434
-
435
- function restoreActiveElementIndex(element: HTMLElement, activeElementIndex: number) {
436
- getFocusableElements(element)[+activeElementIndex]?.focus();
437
- }
438
-
439
- function saveActiveElementIndex(element: HTMLElement) {
440
- return getFocusableElements(element).indexOf(document.activeElement);
441
- }
442
-
443
- function restoreInputValues(element: HTMLElement, inputValues: (boolean | string)[]) {
444
- const inputs = element.querySelectorAll('input, select, textarea') as NodeListOf<
445
- HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
446
- >;
447
-
448
- [].forEach.call(inputs, (input: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement, index: number) => {
449
- const value = inputValues[+index];
450
-
451
- if (typeof value !== 'undefined') {
452
- const { tagName, type } = input;
453
-
454
- if (tagName === 'INPUT' && (type === 'checkbox' || type === 'radio')) {
455
- if (typeof value === 'boolean') {
456
- (input as HTMLInputElement).checked = value;
457
- }
458
- } else if (typeof value === 'string') {
459
- input.value = value;
460
- }
461
- }
462
- });
463
- }
464
-
465
- function saveInputValues(element: HTMLElement): (boolean | string)[] {
466
- const inputs = element.querySelectorAll('input, select, textarea') as NodeListOf<
467
- HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
468
- >;
469
-
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;
475
- }
476
-
477
- return input.value;
478
- });
479
- }
480
-
481
34
  type AdaptiveCardRendererProps = {
482
35
  actionPerformedClassName?: string;
483
36
  adaptiveCard: AdaptiveCard;
@@ -493,26 +46,23 @@ const AdaptiveCardRenderer: VFC<AdaptiveCardRendererProps> = ({
493
46
  }) => {
494
47
  const [{ adaptiveCardRenderer: adaptiveCardRendererStyleSet }] = useStyleSet();
495
48
  const [{ GlobalSettings, HostConfig }] = useAdaptiveCardsPackage();
496
- const [actionsPerformed, setActionsPerformed] = useState<AdaptiveCardAction[]>([]);
497
49
  const [adaptiveCardsHostConfig] = useAdaptiveCardsHostConfig();
498
50
  const [disabledFromComposer] = useDisabled();
499
- const [errors, setErrors] = useState([]);
500
- const [lastRender, setLastRender] = useState(0);
501
- const activeElementIndexRef = useRef(-1);
502
- const adaptiveCardElementRef = useRef<HTMLElement>();
503
51
  const contentRef = useRef<HTMLDivElement>();
504
- const inputValuesRef = useRef<(boolean | string)[]>([]);
505
52
  const localize = useLocalizer();
506
53
  const performCardAction = usePerformCardAction();
507
54
  const renderMarkdownAsHTML = useRenderMarkdownAsHTML();
508
55
  const scrollToEnd = useScrollToEnd();
509
56
 
510
57
  const disabled = disabledFromComposer || disabledFromProps;
58
+ const tapActionRef = useValueRef(tapAction);
59
+
60
+ const disabledRef = useValueRef(disabled);
511
61
 
512
62
  // TODO: [P2] #3199 We should consider using `adaptiveCard.selectAction` instead.
513
63
  // The null check for "tapAction" is in "handleClickAndKeyPressForTapAction".
514
- const handleClickAndKeyPress = useCallback(
515
- (event: KeyboardEvent | MouseEvent): void => {
64
+ const handleClickAndKeyPress = useCallback<KeyboardEventHandler<HTMLDivElement> | MouseEventHandler<HTMLDivElement>>(
65
+ (event): void => {
516
66
  const { key, type } = event as KeyboardEvent;
517
67
  const target = event.target as HTMLDivElement;
518
68
 
@@ -548,30 +98,22 @@ const AdaptiveCardRenderer: VFC<AdaptiveCardRendererProps> = ({
548
98
  }
549
99
  }
550
100
 
551
- performCardAction(tapAction);
101
+ performCardAction(tapActionRef.current);
552
102
  scrollToEnd();
553
103
  },
554
- [contentRef, performCardAction, scrollToEnd, tapAction]
104
+ [contentRef, performCardAction, scrollToEnd, tapActionRef]
555
105
  );
556
106
 
557
107
  // Only listen to event if it is not disabled and have "tapAction" prop.
558
108
  const handleClickAndKeyPressForTapAction = !disabled && tapAction ? handleClickAndKeyPress : undefined;
559
109
 
560
- const addActionsPerformed = useCallback(
561
- (action: AdaptiveCardAction): void =>
562
- !~actionsPerformed.indexOf(action) && setActionsPerformed([...actionsPerformed, action]),
563
- [actionsPerformed, setActionsPerformed]
564
- );
565
-
566
110
  const handleExecuteAction = useCallback(
567
111
  (action: AdaptiveCardAction): void => {
568
112
  // Some items, e.g. tappable image, cannot be disabled thru DOM attributes
569
- if (disabled) {
113
+ if (disabledRef.current) {
570
114
  return;
571
115
  }
572
116
 
573
- addActionsPerformed(action);
574
-
575
117
  const actionTypeName = action.getJsonTypeName();
576
118
  const { iconUrl: image, title } = action;
577
119
 
@@ -617,128 +159,87 @@ const AdaptiveCardRenderer: VFC<AdaptiveCardRendererProps> = ({
617
159
  console.error(action);
618
160
  }
619
161
  },
620
- [addActionsPerformed, disabled, performCardAction, scrollToEnd]
162
+ [disabledRef, performCardAction, scrollToEnd]
621
163
  );
622
164
 
623
- useLayoutEffect(() => {
624
- const { current } = contentRef;
625
-
626
- if (!current || !adaptiveCard) {
627
- activeElementIndexRef.current = -1;
628
- inputValuesRef.current = [];
629
- }
630
-
631
- // Currently, the only way to set the Markdown engine is to set it thru static member of AdaptiveCard class
632
-
633
- // TODO: [P3] Checks if we could make the "renderMarkdownAsHTML" per card
634
- // This could be limitations from Adaptive Cards package (not supported as of 1.2.5)
635
- // Because there could be timing difference between .parse and .render, we could be using wrong Markdown engine
636
-
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) => {
640
- if (renderMarkdownAsHTML) {
641
- result.outputHtml = renderMarkdownAsHTML(text);
642
- result.didProcess = true;
643
- }
644
- };
645
-
646
- if (adaptiveCardsHostConfig) {
647
- adaptiveCard.hostConfig = isPlainObject(adaptiveCardsHostConfig)
648
- ? new HostConfig(adaptiveCardsHostConfig)
649
- : adaptiveCardsHostConfig;
650
- }
651
-
652
- // For accessibility issue #1340, `tabindex="0"` must not be set for the root container if it is not interactive.
653
- GlobalSettings.setTabIndexAtCardRoot = !!tapAction;
654
-
655
- const { validationEvents } = adaptiveCard.validateProperties();
656
-
657
- if (validationEvents.length) {
658
- return setErrors(validationEvents.reduce((items, { message }) => [...items, new Error(message)], []));
659
- }
660
-
661
- let element: HTMLElement;
662
-
663
- try {
664
- element = adaptiveCard.render();
665
- } catch (error) {
666
- return setErrors([error]);
667
- }
668
-
669
- if (!element) {
670
- return setErrors([new Error('Adaptive Card rendered as empty element')]);
671
- }
672
-
673
- // Clear errors on next render
674
- setErrors([]);
675
-
676
- restoreInputValues(element, inputValuesRef.current);
677
-
678
- current.appendChild(element);
679
- adaptiveCardElementRef.current = element;
680
-
681
- // Focus can only be restored after the DOM is attached.
682
- restoreActiveElementIndex(element, activeElementIndexRef.current);
683
-
684
- setLastRender(Date.now());
685
-
686
- return () => {
687
- activeElementIndexRef.current = saveActiveElementIndex(element);
688
- inputValuesRef.current = saveInputValues(element);
689
-
690
- current.removeChild(adaptiveCardElementRef.current);
691
-
692
- adaptiveCardElementRef.current = undefined;
693
- };
165
+ // For accessibility issue #1340, `tabindex="0"` must not be set for the root container if it is not interactive.
166
+ const setTabIndexAtCardRoot = !!tapAction;
167
+
168
+ const [applyActionShouldBePushButtonMod, undoActionShouldBePushButtonMod] =
169
+ useActionShouldBePushButtonModEffect(adaptiveCard);
170
+ const [applyActionSetShouldNotBeMenuBarMod, undoActionSetShouldNotBeMenuBarMod] =
171
+ useActionSetShouldNotBeMenuBarModEffect(adaptiveCard);
172
+ const [applyActiveElementMod, undoActiveElementMod] = useActiveElementModEffect(adaptiveCard);
173
+ const [applyDisabledMod, undoDisabledMod] = useDisabledModEffect(adaptiveCard);
174
+ const [applyPersistValuesMod, undoPersistValuesMod] = usePersistValuesModEffect(adaptiveCard);
175
+
176
+ const { element, errors }: { element?: HTMLElement; errors?: Error[] } = useMemo(() => {
177
+ undoActionShouldBePushButtonMod();
178
+ undoActionSetShouldNotBeMenuBarMod();
179
+ undoActiveElementMod();
180
+ undoDisabledMod();
181
+ undoPersistValuesMod();
182
+
183
+ return renderAdaptiveCard(adaptiveCard, {
184
+ adaptiveCardsHostConfig,
185
+ adaptiveCardsPackage: { GlobalSettings, HostConfig },
186
+ renderMarkdownAsHTML,
187
+ setTabIndexAtCardRoot
188
+ });
694
189
  }, [
695
190
  adaptiveCard,
696
191
  adaptiveCardsHostConfig,
697
- contentRef,
698
192
  GlobalSettings,
699
193
  HostConfig,
700
194
  renderMarkdownAsHTML,
701
- setErrors,
702
- tapAction
195
+ setTabIndexAtCardRoot,
196
+ undoActionShouldBePushButtonMod,
197
+ undoActionSetShouldNotBeMenuBarMod,
198
+ undoActiveElementMod,
199
+ undoDisabledMod,
200
+ undoPersistValuesMod
703
201
  ]);
704
202
 
705
- useEffect(() => {
706
- // Set onExecuteAction without causing unnecessary re-render.
707
- adaptiveCard.onExecuteAction = disabled ? undefined : handleExecuteAction;
708
- }, [adaptiveCard, disabled, handleExecuteAction]);
709
-
710
- useEffect(() => fixAccessibilityIssuesWithUndo(adaptiveCardElementRef.current), [adaptiveCardElementRef, lastRender]);
711
-
712
- useEffect(() => {
713
- // If the Adaptive Card get re-rendered, re-disable elements as needed.
714
- if (disabled) {
715
- return disableInputElementsWithUndo(adaptiveCardElementRef.current);
716
- }
717
- }, [adaptiveCardElementRef, disabled, lastRender]);
718
-
719
- useEffect(() => {
720
- // If the Adaptive Card changed, reset all actions performed.
721
- setActionsPerformed([]);
722
- }, [adaptiveCard]);
723
-
724
- useEffect(
725
- () =>
726
- indicateActionSelectionWithUndo(
727
- // Actions that do not have "renderedElement" means it is the Adaptive Card itself, such as "selectAction" (AC) or "tapAction" (rich cards).
728
- // We do not need to mark the whole card as performed.
729
- actionsPerformed.map(({ renderedElement }) => renderedElement).filter(renderedElement => renderedElement),
730
- actionPerformedClassName
731
- ),
732
- [actionsPerformed, actionPerformedClassName, lastRender]
733
- );
203
+ useMemo(() => {
204
+ adaptiveCard.onExecuteAction = handleExecuteAction;
205
+ }, [adaptiveCard, handleExecuteAction]);
206
+
207
+ useLayoutEffect(() => {
208
+ const { current } = contentRef;
209
+
210
+ current?.appendChild(element);
211
+
212
+ return () => {
213
+ current?.removeChild(element);
214
+ };
215
+ }, [contentRef, element]);
216
+
217
+ // Apply all mods regardless whether the element changed or not.
218
+ // This is because we have undoed mods when we call the `useXXXModEffect` hook.
219
+ useLayoutEffect(() => {
220
+ applyActionShouldBePushButtonMod(element, actionPerformedClassName);
221
+ applyActionSetShouldNotBeMenuBarMod(element);
222
+ applyActiveElementMod(element);
223
+ applyDisabledMod(element, disabled);
224
+ applyPersistValuesMod(element);
225
+ }, [
226
+ actionPerformedClassName,
227
+ applyActionShouldBePushButtonMod,
228
+ applyActionSetShouldNotBeMenuBarMod,
229
+ applyActiveElementMod,
230
+ applyDisabledMod,
231
+ applyPersistValuesMod,
232
+ disabled,
233
+ element
234
+ ]);
734
235
 
735
- return errors.length ? (
236
+ return errors?.length ? (
736
237
  node_env === 'development' && <ErrorBox error={errors[0]} type={localize('ADAPTIVE_CARD_ERROR_BOX_TITLE_RENDER')} />
737
238
  ) : (
738
239
  <div
739
240
  className={classNames(adaptiveCardRendererStyleSet + '', 'webchat__adaptive-card-renderer')}
740
- onClick={handleClickAndKeyPressForTapAction as unknown as MouseEventHandler<HTMLDivElement>}
741
- onKeyPress={handleClickAndKeyPressForTapAction as unknown as KeyboardEventHandler<HTMLDivElement>}
241
+ onClick={handleClickAndKeyPressForTapAction as MouseEventHandler<HTMLDivElement>}
242
+ onKeyPress={handleClickAndKeyPressForTapAction as KeyboardEventHandler<HTMLDivElement>}
742
243
  ref={contentRef}
743
244
  />
744
245
  );