@microsoft/omnichannel-chat-widget 1.8.4-main.f21df63 → 1.8.4-main.f45c44b

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 (67) hide show
  1. package/README.md +1 -0
  2. package/lib/cjs/common/telemetry/TelemetryConstants.js +1 -0
  3. package/lib/cjs/common/utils.js +92 -16
  4. package/lib/cjs/components/citationpanestateful/CitationPaneStateful.js +8 -1
  5. package/lib/cjs/components/confirmationpanestateful/ConfirmationPaneStateful.js +12 -1
  6. package/lib/cjs/components/emailtranscriptpanestateful/EmailTranscriptPaneStateful.js +36 -13
  7. package/lib/cjs/components/livechatwidget/common/createMarkdown.js +96 -5
  8. package/lib/cjs/components/livechatwidget/common/endChat.js +4 -0
  9. package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +182 -1
  10. package/lib/cjs/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +14 -2
  11. package/lib/cjs/components/postchatloadingpanestateful/PostChatLoadingPaneStateful.js +15 -3
  12. package/lib/cjs/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +1 -0
  13. package/lib/cjs/components/prechatsurveypanestateful/PreChatSurveyPaneStateful.js +64 -2
  14. package/lib/cjs/components/webchatcontainerstateful/WebChatContainerStateful.js +97 -0
  15. package/lib/cjs/components/webchatcontainerstateful/common/DesignerChatAdapter.js +10 -0
  16. package/lib/cjs/components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts.js +3 -0
  17. package/lib/cjs/components/webchatcontainerstateful/common/defaultProps/defaultWebChatContainerStatefulProps.js +2 -1
  18. package/lib/cjs/components/webchatcontainerstateful/common/utils/chatAdapterUtils.js +3 -1
  19. package/lib/cjs/components/webchatcontainerstateful/common/utils/citationA11y.js +195 -0
  20. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +2 -0
  21. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachmentMiddleware.js +2 -1
  22. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +203 -0
  23. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/defaultStyles/defaultTimestampRetryStyles.js +8 -1
  24. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/timestamps/NotDeliveredTimestamp.js +3 -13
  25. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.js +81 -0
  26. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +29 -1
  27. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/localizedStringsBotInitialsMiddleware.js +52 -8
  28. package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/notification/NotificationHandler.js +55 -1
  29. package/lib/cjs/contexts/common/LiveChatWidgetContextInitialState.js +11 -0
  30. package/lib/esm/common/telemetry/TelemetryConstants.js +1 -0
  31. package/lib/esm/common/utils.js +87 -13
  32. package/lib/esm/components/citationpanestateful/CitationPaneStateful.js +8 -1
  33. package/lib/esm/components/confirmationpanestateful/ConfirmationPaneStateful.js +12 -1
  34. package/lib/esm/components/emailtranscriptpanestateful/EmailTranscriptPaneStateful.js +37 -14
  35. package/lib/esm/components/livechatwidget/common/createMarkdown.js +96 -5
  36. package/lib/esm/components/livechatwidget/common/endChat.js +4 -0
  37. package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +182 -1
  38. package/lib/esm/components/livechatwidget/livechatwidgetstateful/LiveChatWidgetStateful.js +15 -3
  39. package/lib/esm/components/postchatloadingpanestateful/PostChatLoadingPaneStateful.js +16 -4
  40. package/lib/esm/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +1 -0
  41. package/lib/esm/components/prechatsurveypanestateful/PreChatSurveyPaneStateful.js +65 -3
  42. package/lib/esm/components/webchatcontainerstateful/WebChatContainerStateful.js +97 -0
  43. package/lib/esm/components/webchatcontainerstateful/common/DesignerChatAdapter.js +10 -0
  44. package/lib/esm/components/webchatcontainerstateful/common/defaultProps/defaultMiddlewareLocalizedTexts.js +3 -0
  45. package/lib/esm/components/webchatcontainerstateful/common/defaultProps/defaultWebChatContainerStatefulProps.js +2 -1
  46. package/lib/esm/components/webchatcontainerstateful/common/utils/chatAdapterUtils.js +3 -1
  47. package/lib/esm/components/webchatcontainerstateful/common/utils/citationA11y.js +188 -0
  48. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +2 -0
  49. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachmentMiddleware.js +2 -1
  50. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +195 -0
  51. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/defaultStyles/defaultTimestampRetryStyles.js +8 -1
  52. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/timestamps/NotDeliveredTimestamp.js +3 -13
  53. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.js +74 -0
  54. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +29 -1
  55. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/localizedStringsBotInitialsMiddleware.js +52 -8
  56. package/lib/esm/components/webchatcontainerstateful/webchatcontroller/notification/NotificationHandler.js +55 -1
  57. package/lib/esm/contexts/common/LiveChatWidgetContextInitialState.js +11 -0
  58. package/lib/types/common/telemetry/TelemetryConstants.d.ts +1 -0
  59. package/lib/types/common/utils.d.ts +3 -1
  60. package/lib/types/components/webchatcontainerstateful/common/utils/chatAdapterUtils.d.ts +1 -1
  61. package/lib/types/components/webchatcontainerstateful/common/utils/citationA11y.d.ts +1 -0
  62. package/lib/types/components/webchatcontainerstateful/interfaces/IWebChatContainerStatefulProps.d.ts +7 -0
  63. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.d.ts +18 -0
  64. package/lib/types/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentSentAnnouncementMiddleware.d.ts +12 -0
  65. package/lib/types/components/webchatcontainerstateful/webchatcontroller/notification/NotificationHandler.d.ts +7 -0
  66. package/lib/types/contexts/common/ILiveChatWidgetLocalizedTexts.d.ts +17 -0
  67. package/package.json +26 -7
package/README.md CHANGED
@@ -311,6 +311,7 @@ BroadcastService.getMessageByEventName("CloseChat").subscribe(async (msg) => {
311
311
  [Create LiveChatWidget with Webpack5 and TypeScript](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/BuildingUsingWebpack5.md)\
312
312
  [Omnichannel Features](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/Features.md)\
313
313
  [How to Add Visual Regression Tests](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/VisualRegressionTestingGuide.md)\
314
+ [Accessibility Tooling](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/accessibility/README.md)\
314
315
  [Security](https://github.com/microsoft/omnichannel-chat-widget/blob/main/SECURITY.md)\
315
316
  [Third Party Cookie Support](https://github.com/microsoft/omnichannel-chat-widget/blob/main/docs/Tpc.md)\
316
317
  [Storybook](https://microsoft.github.io/omnichannel-chat-widget/docs/storybook/)
@@ -202,6 +202,7 @@ exports.TelemetryEvent = TelemetryEvent;
202
202
  TelemetryEvent["QueueOverflowEvent"] = "QueueOverflowEvent";
203
203
  TelemetryEvent["ProcessingHTMLTextMiddlewareFailed"] = "ProcessingHTMLTextMiddlewareFailed";
204
204
  TelemetryEvent["ProcessingSanitizationMiddlewareFailed"] = "ProcessingSanitizationMiddlewareFailed";
205
+ TelemetryEvent["HTMLSanitized"] = "HTMLSanitized";
205
206
  TelemetryEvent["FormatTagsMiddlewareJSONStringifyFailed"] = "FormatTagsMiddlewareJSONStringifyFailed";
206
207
  TelemetryEvent["AttachmentUploadValidatorMiddlewareFailed"] = "AttachmentUploadValidatorMiddlewareFailed";
207
208
  TelemetryEvent["CitationMiddlewareFailed"] = "CitationMiddlewareFailed";
@@ -3,13 +3,13 @@
3
3
  Object.defineProperty(exports, "__esModule", {
4
4
  value: true
5
5
  });
6
- exports.getCustomEventValue = exports.getConversationDetailsCall = exports.getBroadcastChannelName = exports.formatTemplateString = exports.findParentFocusableElementsWithoutChildContainer = exports.findAllFocusableElement = exports.extractPreChatSurveyResponseValues = exports.escapeHtml = exports.debounceLeading = exports.createTimer = exports.createFileAndDownload = exports.checkContactIdError = exports.changeLanguageCodeFormatForWebChat = exports.addDelayInMs = void 0;
6
+ exports.getCustomEventValue = exports.getConversationDetailsCall = exports.getBroadcastChannelName = exports.formatTemplateString = exports.findParentFocusableElementsWithoutChildContainer = exports.findAllFocusableElement = exports.extractPreChatSurveyResponseValues = exports.escapeHtml = exports.debounceLeading = exports.createTimer = exports.createFileAndDownload = exports.checkContactIdError = exports.changeLanguageCodeFormatForWebChat = exports.announceMessageImmediately = exports.addDelayInMs = void 0;
7
7
  exports.getDeviceType = getDeviceType;
8
8
  exports.getWidgetEndChatEventName = exports.getWidgetCacheIdfromProps = exports.getWidgetCacheId = exports.getTimestampHourMinute = exports.getStateFromCache = exports.getLocaleDirection = exports.getIconText = exports.getDomain = void 0;
9
9
  exports.isEndConversationDueToOverflowActivity = isEndConversationDueToOverflowActivity;
10
10
  exports.parseAdaptiveCardPayload = exports.newGuid = exports.isValidCustomEvent = exports.isUndefinedOrEmpty = exports.isThisSessionPopout = exports.isNullOrUndefined = exports.isNullOrEmptyString = void 0;
11
11
  exports.parseBooleanFromConfig = parseBooleanFromConfig;
12
- exports.setTabIndices = exports.setOcUserAgent = exports.setFocusOnSendBox = exports.setFocusOnElement = exports.preventFocusToMoveOutOfElement = exports.parseLowerCaseString = void 0;
12
+ exports.setTabIndices = exports.setOcUserAgent = exports.setFocusOnSendBox = exports.setFocusOnElement = exports.setAriaHiddenForSiblings = exports.preventFocusToMoveOutOfElement = exports.parseLowerCaseString = void 0;
13
13
  var _Constants = require("./Constants");
14
14
  var _TelemetryConstants = require("./telemetry/TelemetryConstants");
15
15
  var _omnichannelChatComponents = require("@microsoft/omnichannel-chat-components");
@@ -85,28 +85,69 @@ exports.findAllFocusableElement = findAllFocusableElement;
85
85
  const preventFocusToMoveOutOfElement = elementId => {
86
86
  const container = document.getElementById(elementId);
87
87
  if (!container) {
88
- return;
88
+ return () => {/* no-op */};
89
89
  }
90
90
  const focusableElements = findAllFocusableElement(container);
91
91
  if (!focusableElements) {
92
- return;
92
+ return () => {/* no-op */};
93
93
  }
94
94
  const firstFocusableElement = focusableElements[0];
95
95
  const lastFocusableElement = focusableElements[focusableElements.length - 1];
96
- firstFocusableElement.onkeydown = e => {
97
- if (e.shiftKey && e.key === _KeyCodes.KeyCodes.TAB) {
98
- e.preventDefault();
99
- lastFocusableElement === null || lastFocusableElement === void 0 ? void 0 : lastFocusableElement.focus();
100
- }
101
- };
102
- lastFocusableElement.onkeydown = e => {
103
- if (!e.shiftKey && e.key === _KeyCodes.KeyCodes.TAB) {
104
- e.preventDefault();
105
- firstFocusableElement === null || firstFocusableElement === void 0 ? void 0 : firstFocusableElement.focus();
106
- }
107
- };
96
+ const cleanups = [];
97
+ if (firstFocusableElement === lastFocusableElement) {
98
+ const handler = e => {
99
+ if (e.key === _KeyCodes.KeyCodes.TAB && !e.shiftKey) {
100
+ e.preventDefault();
101
+ firstFocusableElement.focus();
102
+ } else if (e.key === _KeyCodes.KeyCodes.TAB && e.shiftKey) {
103
+ e.preventDefault();
104
+ firstFocusableElement.focus();
105
+ }
106
+ };
107
+ firstFocusableElement.addEventListener("keydown", handler);
108
+ cleanups.push(() => firstFocusableElement.removeEventListener("keydown", handler));
109
+ } else {
110
+ const firstHandler = e => {
111
+ if (e.shiftKey && e.key === _KeyCodes.KeyCodes.TAB) {
112
+ e.preventDefault();
113
+ lastFocusableElement === null || lastFocusableElement === void 0 ? void 0 : lastFocusableElement.focus();
114
+ }
115
+ };
116
+ firstFocusableElement.addEventListener("keydown", firstHandler);
117
+ cleanups.push(() => firstFocusableElement.removeEventListener("keydown", firstHandler));
118
+ const lastHandler = e => {
119
+ if (!e.shiftKey && e.key === _KeyCodes.KeyCodes.TAB) {
120
+ e.preventDefault();
121
+ firstFocusableElement === null || firstFocusableElement === void 0 ? void 0 : firstFocusableElement.focus();
122
+ }
123
+ };
124
+ lastFocusableElement.addEventListener("keydown", lastHandler);
125
+ cleanups.push(() => lastFocusableElement.removeEventListener("keydown", lastHandler));
126
+ }
127
+ return () => cleanups.forEach(fn => fn());
108
128
  };
109
129
  exports.preventFocusToMoveOutOfElement = preventFocusToMoveOutOfElement;
130
+ const setAriaHiddenForSiblings = (elementId, shouldHide, stateMap) => {
131
+ const element = document.getElementById(elementId);
132
+ if (!(element !== null && element !== void 0 && element.parentElement)) return;
133
+ Array.from(element.parentElement.children).forEach(sibling => {
134
+ if (sibling !== element) {
135
+ if (shouldHide) {
136
+ stateMap.set(sibling, sibling.getAttribute("aria-hidden"));
137
+ sibling.setAttribute("aria-hidden", "true");
138
+ } else if (stateMap.has(sibling)) {
139
+ const original = stateMap.get(sibling);
140
+ if (original === null) {
141
+ sibling.removeAttribute("aria-hidden");
142
+ } else {
143
+ sibling.setAttribute("aria-hidden", original);
144
+ }
145
+ stateMap.delete(sibling);
146
+ }
147
+ }
148
+ });
149
+ };
150
+ exports.setAriaHiddenForSiblings = setAriaHiddenForSiblings;
110
151
  const setFocusOnSendBox = () => {
111
152
  const sendBoxSelector = "textarea[data-id=\"webchat-sendbox-input\"]";
112
153
  setFocusOnElement(sendBoxSelector);
@@ -117,6 +158,41 @@ const setFocusOnElement = selector => {
117
158
  element === null || element === void 0 ? void 0 : element.focus();
118
159
  };
119
160
  exports.setFocusOnElement = setFocusOnElement;
161
+ const IMMEDIATE_ANNOUNCEMENT_REGION_ID = "oc-lcw-immediate-announcement";
162
+
163
+ // Announces a message to screen readers via an aria-live="assertive" region
164
+ // attached to document.body — outside the chat widget's DOM subtree — so the
165
+ // screen reader does not have to traverse chat content to reach it.
166
+ const announceMessageImmediately = message => {
167
+ if (!message || typeof document === "undefined") {
168
+ return;
169
+ }
170
+ let region = document.getElementById(IMMEDIATE_ANNOUNCEMENT_REGION_ID);
171
+ if (!region) {
172
+ region = document.createElement("div");
173
+ region.id = IMMEDIATE_ANNOUNCEMENT_REGION_ID;
174
+ region.setAttribute("aria-live", "assertive");
175
+ region.setAttribute("role", "alert");
176
+ region.setAttribute("aria-atomic", "true");
177
+ region.style.position = "absolute";
178
+ region.style.width = "1px";
179
+ region.style.height = "1px";
180
+ region.style.overflow = "hidden";
181
+ region.style.clip = "rect(0 0 0 0)";
182
+ region.style.clipPath = "inset(50%)";
183
+ region.style.whiteSpace = "nowrap";
184
+ document.body.appendChild(region);
185
+ }
186
+ region.textContent = "";
187
+ // Re-set on the next tick so screen readers detect the change even when
188
+ // the same message is announced twice in a row.
189
+ setTimeout(() => {
190
+ if (region) {
191
+ region.textContent = message;
192
+ }
193
+ }, 50);
194
+ };
195
+ exports.announceMessageImmediately = announceMessageImmediately;
120
196
  const escapeHtml = inputString => {
121
197
  const entityMap = {
122
198
  "<": "&lt;",
@@ -44,7 +44,7 @@ const CitationPaneStateful = props => {
44
44
 
45
45
  // Initial focus pattern (mirrors ConfirmationPaneStateful): focus first focusable element (will re-attempt after visibility becomes true)
46
46
  (0, _react.useEffect)(() => {
47
- (0, _utils.preventFocusToMoveOutOfElement)(controlId);
47
+ const cleanup = (0, _utils.preventFocusToMoveOutOfElement)(controlId);
48
48
  const focusableElements = (0, _utils.findAllFocusableElement)(`#${controlId}`);
49
49
  requestAnimationFrame(() => {
50
50
  if (focusableElements && focusableElements.length > 0 && focusableElements[0]) {
@@ -62,6 +62,13 @@ const CitationPaneStateful = props => {
62
62
  Event: _TelemetryConstants.TelemetryEvent.UXCitationPaneCompleted,
63
63
  ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
64
64
  });
65
+ return () => {
66
+ // internal tracking: restore parent tab indices on unmount so focus
67
+ // can escape the widget even if the pane is dismissed by an
68
+ // external state change rather than a user button click.
69
+ (0, _utils.setTabIndices)(elements, initialTabIndexMap, true);
70
+ cleanup();
71
+ };
65
72
  }, []);
66
73
 
67
74
  // Retry focus once pane is actually visible (isReady) in case initial attempt occurred while wrapper was visibility:hidden
@@ -82,7 +82,7 @@ const ConfirmationPaneStateful = props => {
82
82
 
83
83
  // Move focus to the first button
84
84
  (0, _react.useEffect)(() => {
85
- (0, _utils.preventFocusToMoveOutOfElement)(controlProps.id);
85
+ const cleanup = (0, _utils.preventFocusToMoveOutOfElement)(controlProps.id);
86
86
  const focusableElements = (0, _utils.findAllFocusableElement)(`#${controlProps.id}`);
87
87
  requestAnimationFrame(() => {
88
88
  if (focusableElements && focusableElements.length > 0 && focusableElements[0]) {
@@ -100,6 +100,17 @@ const ConfirmationPaneStateful = props => {
100
100
  Event: _TelemetryConstants.TelemetryEvent.UXConfirmationPaneCompleted,
101
101
  ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
102
102
  });
103
+ return () => {
104
+ // internal tracking: when the confirmation pane unmounts (page reload,
105
+ // parent state change, or any path that bypasses onConfirm /
106
+ // onCancel) the tab indices that were forced to -1 on sibling
107
+ // focusable elements must be restored, otherwise Tab order
108
+ // remains broken and focus can be trapped inside the widget.
109
+ // setTabIndices is a no-op when initialTabIndexMap is already
110
+ // empty (onConfirm / onCancel already restored).
111
+ (0, _utils.setTabIndices)(elements, initialTabIndexMap, true);
112
+ cleanup();
113
+ };
103
114
  }, []);
104
115
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DimLayer.DimLayer, {
105
116
  brightness: (controlProps === null || controlProps === void 0 ? void 0 : controlProps.brightnessValueOnDim) ?? "0.2"
@@ -35,16 +35,23 @@ const EmailTranscriptPaneStateful = props => {
35
35
  const [state, dispatch] = (0, _useChatContextStore.default)();
36
36
  const [facadeChatSDK] = (0, _useFacadeChatSDKStore.default)();
37
37
  const [initialEmail, setInitialEmail] = (0, _react.useState)("");
38
- const closeEmailTranscriptPane = () => {
38
+ // restoreFocus=false is used on the submit path: the notification banner
39
+ // (success or error) takes focus via NotificationHandler.setFocusOnNotificationCloseButton,
40
+ // so an intermediate restore to the chat-widget shell would otherwise cause SRs to
41
+ // announce "Enter <widget>" in between the dialog and the banner.
42
+ const closeEmailTranscriptPane = function () {
43
+ let restoreFocus = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
39
44
  dispatch({
40
45
  type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_SHOW_EMAIL_TRANSCRIPT_PANE,
41
46
  payload: false
42
47
  });
43
- const previousFocusedElementId = state.appStates.previousElementIdOnFocusBeforeModalOpen;
44
- if (previousFocusedElementId) {
45
- (0, _utils.setFocusOnElement)("#" + previousFocusedElementId);
46
- } else {
47
- (0, _utils.setFocusOnSendBox)();
48
+ if (restoreFocus) {
49
+ const previousFocusedElementId = state.appStates.previousElementIdOnFocusBeforeModalOpen;
50
+ if (previousFocusedElementId) {
51
+ (0, _utils.setFocusOnElement)("#" + previousFocusedElementId);
52
+ } else {
53
+ (0, _utils.setFocusOnSendBox)();
54
+ }
48
55
  }
49
56
  dispatch({
50
57
  type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_PREVIOUS_FOCUSED_ELEMENT_ID,
@@ -55,23 +62,31 @@ const EmailTranscriptPaneStateful = props => {
55
62
  const onSend = (0, _react.useCallback)(async email => {
56
63
  var _state$domainStates;
57
64
  const liveChatContext = state === null || state === void 0 ? void 0 : (_state$domainStates = state.domainStates) === null || _state$domainStates === void 0 ? void 0 : _state$domainStates.liveChatContext;
58
- closeEmailTranscriptPane();
65
+ closeEmailTranscriptPane(false);
59
66
  const chatTranscriptBody = {
60
67
  emailAddress: email,
61
68
  attachmentMessage: (props === null || props === void 0 ? void 0 : props.attachmentMessage) ?? "The following attachment was uploaded during the conversation:"
62
69
  };
63
70
  try {
64
- var _state$domainStates2, _state$domainStates2$;
71
+ var _state$domainStates2, _state$domainStates2$, _state$domainStates3, _state$domainStates3$;
65
72
  await (facadeChatSDK === null || facadeChatSDK === void 0 ? void 0 : facadeChatSDK.emailLiveChatTranscript(chatTranscriptBody, {
66
73
  liveChatContext
67
74
  }));
68
- _NotificationHandler.NotificationHandler.notifySuccess(_NotificationScenarios.NotificationScenarios.EmailAddressSaved, (state === null || state === void 0 ? void 0 : (_state$domainStates2 = state.domainStates) === null || _state$domainStates2 === void 0 ? void 0 : (_state$domainStates2$ = _state$domainStates2.middlewareLocalizedTexts) === null || _state$domainStates2$ === void 0 ? void 0 : _state$domainStates2$.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_SUCCESS) ?? (_defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts === null || _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts === void 0 ? void 0 : _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_SUCCESS));
75
+ const successMessage = (state === null || state === void 0 ? void 0 : (_state$domainStates2 = state.domainStates) === null || _state$domainStates2 === void 0 ? void 0 : (_state$domainStates2$ = _state$domainStates2.middlewareLocalizedTexts) === null || _state$domainStates2$ === void 0 ? void 0 : _state$domainStates2$.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_SUCCESS) ?? (_defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts === null || _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts === void 0 ? void 0 : _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_SUCCESS);
76
+ // Announce from a live region at document.body level so the
77
+ // screen reader speaks the confirmation immediately, without
78
+ // traversing the chat transcript on the way to the banner.
79
+ // Prefix with the explicit state word so SR users hear the outcome
80
+ // (visual users already see a success icon on the banner).
81
+ const successSrPrefix = (state === null || state === void 0 ? void 0 : (_state$domainStates3 = state.domainStates) === null || _state$domainStates3 === void 0 ? void 0 : (_state$domainStates3$ = _state$domainStates3.middlewareLocalizedTexts) === null || _state$domainStates3$ === void 0 ? void 0 : _state$domainStates3$.MIDDLEWARE_SR_PREFIX_SUCCESS) ?? _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts.MIDDLEWARE_SR_PREFIX_SUCCESS;
82
+ (0, _utils.announceMessageImmediately)(`${successSrPrefix}${successMessage}`);
83
+ _NotificationHandler.NotificationHandler.notifySuccess(_NotificationScenarios.NotificationScenarios.EmailAddressSaved, successMessage);
69
84
  _TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.INFO, {
70
85
  Event: _TelemetryConstants.TelemetryEvent.EmailTranscriptSent,
71
86
  Description: "Transcript sent to email successfully."
72
87
  });
73
88
  } catch (ex) {
74
- var _state$domainStates3, _state$domainStates3$;
89
+ var _state$domainStates4, _state$domainStates4$, _state$domainStates5, _state$domainStates5$;
75
90
  _TelemetryHelper.TelemetryHelper.logActionEventToAllTelemetry(_TelemetryConstants.LogLevel.ERROR, {
76
91
  Event: _TelemetryConstants.TelemetryEvent.EmailTranscriptFailed,
77
92
  Description: "Email transcript failed.",
@@ -79,8 +94,11 @@ const EmailTranscriptPaneStateful = props => {
79
94
  exception: ex
80
95
  }
81
96
  });
82
- const message = (0, _utils.formatTemplateString)((state === null || state === void 0 ? void 0 : (_state$domainStates3 = state.domainStates) === null || _state$domainStates3 === void 0 ? void 0 : (_state$domainStates3$ = _state$domainStates3.middlewareLocalizedTexts) === null || _state$domainStates3$ === void 0 ? void 0 : _state$domainStates3$.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_ERROR) ?? _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_ERROR, [email]);
83
- _NotificationHandler.NotificationHandler.notifyError(_NotificationScenarios.NotificationScenarios.EmailTranscriptError, (props === null || props === void 0 ? void 0 : props.bannerMessageOnError) ?? message);
97
+ const message = (0, _utils.formatTemplateString)((state === null || state === void 0 ? void 0 : (_state$domainStates4 = state.domainStates) === null || _state$domainStates4 === void 0 ? void 0 : (_state$domainStates4$ = _state$domainStates4.middlewareLocalizedTexts) === null || _state$domainStates4$ === void 0 ? void 0 : _state$domainStates4$.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_ERROR) ?? _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts.MIDDLEWARE_BANNER_FILE_EMAIL_ADDRESS_RECORDED_ERROR, [email]);
98
+ const bannerMessage = (props === null || props === void 0 ? void 0 : props.bannerMessageOnError) ?? message;
99
+ const errorSrPrefix = (state === null || state === void 0 ? void 0 : (_state$domainStates5 = state.domainStates) === null || _state$domainStates5 === void 0 ? void 0 : (_state$domainStates5$ = _state$domainStates5.middlewareLocalizedTexts) === null || _state$domainStates5$ === void 0 ? void 0 : _state$domainStates5$.MIDDLEWARE_SR_PREFIX_ERROR) ?? _defaultMiddlewareLocalizedTexts.defaultMiddlewareLocalizedTexts.MIDDLEWARE_SR_PREFIX_ERROR;
100
+ (0, _utils.announceMessageImmediately)(`${errorSrPrefix}${bannerMessage}`);
101
+ _NotificationHandler.NotificationHandler.notifyError(_NotificationScenarios.NotificationScenarios.EmailTranscriptError, bannerMessage);
84
102
  }
85
103
  }, [props.attachmentMessage, props.bannerMessageOnError, facadeChatSDK, state.domainStates.liveChatContext]);
86
104
  const controlProps = {
@@ -103,7 +121,7 @@ const EmailTranscriptPaneStateful = props => {
103
121
 
104
122
  // Move focus to the first button
105
123
  (0, _react.useEffect)(() => {
106
- (0, _utils.preventFocusToMoveOutOfElement)(controlProps.id);
124
+ const cleanup = (0, _utils.preventFocusToMoveOutOfElement)(controlProps.id);
107
125
  const focusableElements = (0, _utils.findAllFocusableElement)(`#${controlProps.id}`);
108
126
  if (focusableElements) {
109
127
  focusableElements[0].focus();
@@ -118,6 +136,11 @@ const EmailTranscriptPaneStateful = props => {
118
136
  Event: _TelemetryConstants.TelemetryEvent.UXEmailTranscriptPaneCompleted,
119
137
  ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
120
138
  });
139
+ return () => {
140
+ // internal tracking: restore parent tab indices on unmount.
141
+ (0, _utils.setTabIndices)(elements, initialTabIndexMap, true);
142
+ cleanup();
143
+ };
121
144
  }, [initialEmail]);
122
145
  return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DimLayer.DimLayer, {
123
146
  brightness: (controlProps === null || controlProps === void 0 ? void 0 : controlProps.brightnessValueOnDim) ?? "0.2"
@@ -42,10 +42,8 @@ const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineMarkdown
42
42
 
43
43
  markdown.disable(["strikethrough"]);
44
44
 
45
- // Custom plugin to fix numbered list continuity
45
+ // Custom plugin to fix numbered list continuity and merge adjacent markdown links with the same href.
46
46
  markdown.use(function (md) {
47
- const originalRender = md.render.bind(md);
48
- const originalRenderInline = md.renderInline.bind(md);
49
47
  function preprocessText(text) {
50
48
  // Handle numbered lists that come with double line breaks (knowledge article format)
51
49
  // This ensures proper continuous numbering instead of separate lists
@@ -80,16 +78,109 @@ const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineMarkdown
80
78
  return result;
81
79
  }
82
80
 
81
+ // Accessibility fix: when a bot emits content like "[1.](url) [View details](url)",
82
+ // markdown-it renders two sibling links that screen readers announce as two separate
83
+ // focusable links. Merge consecutive markdown link tokens with identical attributes so
84
+ // the number and label form one combined focusable link without discarding metadata.
85
+ md.core.ruler.after("inline", "merge_adjacent_same_href_links", function (state) {
86
+ const sortedAttrs = token => (token.attrs || []).map(attr => `${attr[0]}=${attr[1]}`).sort();
87
+ const hasSameAttributes = (first, second) => {
88
+ const firstAttrs = sortedAttrs(first);
89
+ const secondAttrs = sortedAttrs(second);
90
+ return firstAttrs.length === secondAttrs.length && firstAttrs.every((attr, index) => attr === secondAttrs[index]);
91
+ };
92
+ const collectLink = (children, index) => {
93
+ var _open$attrGet;
94
+ const open = children[index];
95
+ if (!open || open.type !== "link_open") {
96
+ return undefined;
97
+ }
98
+ const href = (_open$attrGet = open.attrGet) === null || _open$attrGet === void 0 ? void 0 : _open$attrGet.call(open, "href");
99
+ if (!href) {
100
+ return undefined;
101
+ }
102
+ let depth = 0;
103
+ for (let currentIndex = index; currentIndex < children.length; currentIndex++) {
104
+ const token = children[currentIndex];
105
+ if (token.type === "link_open") {
106
+ depth++;
107
+ } else if (token.type === "link_close") {
108
+ depth--;
109
+ if (depth === 0) {
110
+ return {
111
+ closeIndex: currentIndex,
112
+ href,
113
+ open
114
+ };
115
+ }
116
+ }
117
+ }
118
+ return undefined;
119
+ };
120
+ const getNextAdjacentLink = (children, startIndex) => {
121
+ const separatorTokens = [];
122
+ let index = startIndex;
123
+ while (index < children.length && children[index].type === "text" && /^\s*$/.test(children[index].content || "")) {
124
+ separatorTokens.push(children[index]);
125
+ index++;
126
+ }
127
+ const link = collectLink(children, index);
128
+ if (!link) {
129
+ return undefined;
130
+ }
131
+ return {
132
+ ...link,
133
+ openIndex: index,
134
+ separatorTokens
135
+ };
136
+ };
137
+ state.tokens.forEach(blockToken => {
138
+ if (blockToken.type !== "inline" || !blockToken.children) {
139
+ return;
140
+ }
141
+ const mergedChildren = [];
142
+ const children = blockToken.children;
143
+ let index = 0;
144
+ while (index < children.length) {
145
+ const firstLink = collectLink(children, index);
146
+ if (!firstLink) {
147
+ mergedChildren.push(children[index]);
148
+ index++;
149
+ continue;
150
+ }
151
+ const linkTokens = [firstLink.open, ...children.slice(index + 1, firstLink.closeIndex)];
152
+ let nextIndex = firstLink.closeIndex + 1;
153
+ let mergedAnyLink = false;
154
+ let nextLink = getNextAdjacentLink(children, nextIndex);
155
+ while (nextLink && nextLink.href === firstLink.href && hasSameAttributes(firstLink.open, nextLink.open)) {
156
+ linkTokens.push(...nextLink.separatorTokens);
157
+ linkTokens.push(...children.slice(nextLink.openIndex + 1, nextLink.closeIndex));
158
+ nextIndex = nextLink.closeIndex + 1;
159
+ mergedAnyLink = true;
160
+ nextLink = getNextAdjacentLink(children, nextIndex);
161
+ }
162
+ if (mergedAnyLink) {
163
+ mergedChildren.push(...linkTokens, children[firstLink.closeIndex]);
164
+ index = nextIndex;
165
+ } else {
166
+ mergedChildren.push(children[index]);
167
+ index++;
168
+ }
169
+ }
170
+ blockToken.children = mergedChildren;
171
+ });
172
+ });
173
+
83
174
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
84
175
  md.render = function (text, env) {
85
176
  const processedText = preprocessText(text);
86
- return originalRender(processedText, env);
177
+ return md.renderer.render(md.parse(processedText, env), md.options, env);
87
178
  };
88
179
 
89
180
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
90
181
  md.renderInline = function (text, env) {
91
182
  const processedText = preprocessText(text);
92
- return originalRenderInline(processedText, env);
183
+ return md.renderer.render(md.parseInline(processedText, env), md.options, env);
93
184
  };
94
185
  });
95
186
 
@@ -346,6 +346,10 @@ const closeChatStateCleanUp = dispatch => {
346
346
  type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_CITATIONS,
347
347
  payload: {}
348
348
  });
349
+ dispatch({
350
+ type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_SHOW_EMAIL_TRANSCRIPT_PANE,
351
+ payload: false
352
+ });
349
353
 
350
354
  // Dismiss the chat disconnect notification banner if it was shown
351
355
  _NotificationHandler.NotificationHandler.dismissNotification(_NotificationScenarios.NotificationScenarios.ChatDisconnect);