@microsoft/omnichannel-chat-widget 1.8.4-main.f6a1732 → 1.8.4-main.fba10aa

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.
@@ -16,13 +16,22 @@ function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "functio
16
16
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
17
17
  let uiTimer;
18
18
  const PostChatLoadingPaneStateful = props => {
19
- var _props$styleProps;
19
+ var _props$controlProps, _props$styleProps;
20
+ const defaultSubtitleText = "Please take a moment to give us feedback about your chat experience. We are loading the survey for you now.";
21
+ const subtitleText = ((_props$controlProps = props.controlProps) === null || _props$controlProps === void 0 ? void 0 : _props$controlProps.subtitleText) ?? defaultSubtitleText;
22
+ const [announcedSubtitleText, setAnnouncedSubtitleText] = (0, _react.useState)("");
20
23
  (0, _react.useEffect)(() => {
21
24
  uiTimer = (0, _utils.createTimer)();
22
25
  _TelemetryHelper.TelemetryHelper.logLoadingEvent(_TelemetryConstants.LogLevel.INFO, {
23
26
  Event: _TelemetryConstants.TelemetryEvent.UXPostChatLoadingPaneStart
24
27
  });
25
28
  }, []);
29
+ (0, _react.useEffect)(() => {
30
+ const timeout = window.setTimeout(() => {
31
+ setAnnouncedSubtitleText(subtitleText);
32
+ }, 0);
33
+ return () => window.clearTimeout(timeout);
34
+ }, [subtitleText]);
26
35
  const [state] = (0, _useChatContextStore.default)();
27
36
  const generalStyleProps = Object.assign({}, _defaultgeneralPostChatLoadingPaneStyleProps.defaultGeneralPostChatLoadingPaneStyleProps, (_props$styleProps = props.styleProps) === null || _props$styleProps === void 0 ? void 0 : _props$styleProps.generalStyleProps);
28
37
  const styleProps = {
@@ -36,8 +45,11 @@ const PostChatLoadingPaneStateful = props => {
36
45
  hideTitle: true,
37
46
  hideSpinner: true,
38
47
  hideSpinnerText: true,
39
- subtitleText: "Please take a moment to give us feedback about your chat experience. We are loading the survey for you now.",
40
- ...props.controlProps
48
+ ...props.controlProps,
49
+ role: "status",
50
+ "aria-live": "polite",
51
+ "aria-atomic": "true",
52
+ subtitleText: announcedSubtitleText
41
53
  };
42
54
 
43
55
  // Move focus to the first button
@@ -67,6 +67,7 @@ const PostChatSurveyPaneStateful = props => {
67
67
  };
68
68
  const controlProps = {
69
69
  id: "oc-lcw-postchatsurvey-pane",
70
+ title: "Post-chat survey",
70
71
  surveyURL: ((_props$controlProps = props.controlProps) === null || _props$controlProps === void 0 ? void 0 : _props$controlProps.surveyURL) ?? surveyInviteLink,
71
72
  ...props.controlProps
72
73
  };
@@ -20,6 +20,34 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de
20
20
  function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
21
21
  function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
22
22
  let uiTimer;
23
+ const getFocusedElementAnnouncement = element => {
24
+ var _element$textContent;
25
+ const ariaLabel = element.getAttribute(_Constants.HtmlAttributeNames.ariaLabel);
26
+ if (ariaLabel) {
27
+ return ariaLabel.trim();
28
+ }
29
+ const ariaLabelledBy = element.getAttribute(_Constants.HtmlAttributeNames.ariaLabelledby);
30
+ if (ariaLabelledBy) {
31
+ const labelledByText = ariaLabelledBy.split(/\s+/).map(id => {
32
+ var _document$getElementB, _document$getElementB2;
33
+ return (_document$getElementB = document.getElementById(id)) === null || _document$getElementB === void 0 ? void 0 : (_document$getElementB2 = _document$getElementB.textContent) === null || _document$getElementB2 === void 0 ? void 0 : _document$getElementB2.trim();
34
+ }).filter(Boolean).join(" ");
35
+ if (labelledByText) {
36
+ return labelledByText;
37
+ }
38
+ }
39
+ if (element.id) {
40
+ const label = Array.from(document.querySelectorAll("label")).find(candidate => candidate.htmlFor === element.id || candidate.getAttribute("for") === element.id);
41
+ if (label !== null && label !== void 0 && label.textContent) {
42
+ return label.textContent.trim();
43
+ }
44
+ }
45
+ const parentLabel = element.closest("label");
46
+ if (parentLabel !== null && parentLabel !== void 0 && parentLabel.textContent) {
47
+ return parentLabel.textContent.trim();
48
+ }
49
+ return ((_element$textContent = element.textContent) === null || _element$textContent === void 0 ? void 0 : _element$textContent.trim()) ?? "";
50
+ };
23
51
 
24
52
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
53
  const PreChatSurveyPaneStateful = props => {
@@ -42,6 +70,8 @@ const PreChatSurveyPaneStateful = props => {
42
70
  surveyProps,
43
71
  initStartChat
44
72
  } = props;
73
+ const [preChatFocusAnnouncement, setPreChatFocusAnnouncement] = (0, _react.useState)("");
74
+ const liveRegionUpdateTimeout = (0, _react.useRef)();
45
75
  const generalStyleProps = Object.assign({}, _defaultGeneralPreChatSurveyPaneStyleProps.defaultGeneralPreChatSurveyPaneStyleProps, surveyProps === null || surveyProps === void 0 ? void 0 : (_surveyProps$stylePro = surveyProps.styleProps) === null || _surveyProps$stylePro === void 0 ? void 0 : _surveyProps$stylePro.generalStyleProps, {
46
76
  display: state.appStates.isMinimized ? "none" : ""
47
77
  });
@@ -128,6 +158,16 @@ const PreChatSurveyPaneStateful = props => {
128
158
  ...(surveyProps === null || surveyProps === void 0 ? void 0 : surveyProps.styleProps),
129
159
  generalStyleProps: generalStyleProps
130
160
  };
161
+ const announceFocusedElement = event => {
162
+ if (liveRegionUpdateTimeout.current) {
163
+ window.clearTimeout(liveRegionUpdateTimeout.current);
164
+ }
165
+ const announcement = getFocusedElementAnnouncement(event.target);
166
+ setPreChatFocusAnnouncement("");
167
+ liveRegionUpdateTimeout.current = window.setTimeout(() => {
168
+ setPreChatFocusAnnouncement(announcement);
169
+ }, 0);
170
+ };
131
171
  (0, _react.useEffect)(() => {
132
172
  // Set Aria-Label Attribute for Inputs
133
173
  const adaptiveCardElements = document.getElementsByClassName(_Constants.HtmlAttributeNames.adaptiveCardClassName);
@@ -175,10 +215,32 @@ const PreChatSurveyPaneStateful = props => {
175
215
  }
176
216
  }
177
217
  }, [state.appStates.isMinimized]);
178
- return /*#__PURE__*/_react.default.createElement(_omnichannelChatComponents.PreChatSurveyPane, {
218
+ (0, _react.useEffect)(() => () => {
219
+ if (liveRegionUpdateTimeout.current) {
220
+ window.clearTimeout(liveRegionUpdateTimeout.current);
221
+ }
222
+ }, []);
223
+ return /*#__PURE__*/_react.default.createElement("div", {
224
+ onFocusCapture: announceFocusedElement
225
+ }, /*#__PURE__*/_react.default.createElement("div", {
226
+ role: "status",
227
+ "aria-live": "polite",
228
+ "aria-atomic": "true",
229
+ style: {
230
+ position: "absolute",
231
+ width: "1px",
232
+ height: "1px",
233
+ margin: "-1px",
234
+ padding: 0,
235
+ border: 0,
236
+ clip: "rect(0, 0, 0, 0)",
237
+ overflow: "hidden",
238
+ whiteSpace: "nowrap"
239
+ }
240
+ }, preChatFocusAnnouncement), /*#__PURE__*/_react.default.createElement(_omnichannelChatComponents.PreChatSurveyPane, {
179
241
  controlProps: controlProps,
180
242
  styleProps: styleProps
181
- });
243
+ }));
182
244
  };
183
245
  exports.PreChatSurveyPaneStateful = PreChatSurveyPaneStateful;
184
246
  var _default = PreChatSurveyPaneStateful;
@@ -32,6 +32,21 @@ const AdaptiveCardAccessibilityWrapper = _ref => {
32
32
  const container = containerRef.current;
33
33
  if (!container) return;
34
34
 
35
+ // dropdown-double-label: compact Input.ChoiceSet renders as a native
36
+ // <select> with both aria-labelledby and a sibling visible <label for>.
37
+ // Only remove aria-labelledby when it points solely at that same visible
38
+ // label; preserve composite labels that include required/error/context text.
39
+ const compactSelects = container.querySelectorAll(".ac-input-container select.ac-input.ac-multichoiceInput[aria-labelledby]");
40
+ compactSelects.forEach(select => {
41
+ const id = select.id;
42
+ if (!id) return;
43
+ const visibleLabel = container.querySelector(`label[for="${CSS.escape(id)}"]:not([aria-hidden='true'])`);
44
+ const labelledBy = (select.getAttribute("aria-labelledby") || "").trim().split(/\s+/);
45
+ if (visibleLabel !== null && visibleLabel !== void 0 && visibleLabel.id && labelledBy.length === 1 && labelledBy[0] === visibleLabel.id) {
46
+ select.removeAttribute("aria-labelledby");
47
+ }
48
+ });
49
+
35
50
  // action-button-toggle / login-button-toggle: submit/login action buttons can be
36
51
  // rendered with toggle-only ARIA that causes screen readers to announce them as
37
52
  // switches instead of plain push buttons. Preserve Action.ToggleVisibility buttons,
@@ -16,6 +16,32 @@ let currentAgentName = _defaultWebChatStyles.defaultWebChatStyles.botAvatarIniti
16
16
 
17
17
  // Optional external updater (React context dispatch wrapper) set at runtime
18
18
  let externalInitialsUpdater;
19
+ const hasTransferTag = activity => {
20
+ var _activity$channelData;
21
+ const tags = [...(Array.isArray(activity === null || activity === void 0 ? void 0 : (_activity$channelData = activity.channelData) === null || _activity$channelData === void 0 ? void 0 : _activity$channelData.tags) ? activity.channelData.tags : []), ...(Array.isArray(activity === null || activity === void 0 ? void 0 : activity.tags) ? activity.tags : [])];
22
+ return tags.some(tag => typeof tag === "string" && tag.toLowerCase().includes("transfer"));
23
+ };
24
+ const isTransferSystemMessage = activity => {
25
+ const text = ((activity === null || activity === void 0 ? void 0 : activity.text) || "").toString();
26
+ return hasTransferTag(activity) || /\btransfer(?:red|ring)?\b/i.test(text);
27
+ };
28
+ const resetCachedAgentName = dispatch => {
29
+ if (currentAgentName !== _defaultWebChatStyles.defaultWebChatStyles.botAvatarInitials || currentAgentInitials !== _defaultWebChatStyles.defaultWebChatStyles.botAvatarInitials) {
30
+ var _externalInitialsUpda;
31
+ currentAgentName = _defaultWebChatStyles.defaultWebChatStyles.botAvatarInitials;
32
+ currentAgentInitials = _defaultWebChatStyles.defaultWebChatStyles.botAvatarInitials;
33
+ (_externalInitialsUpda = externalInitialsUpdater) === null || _externalInitialsUpda === void 0 ? void 0 : _externalInitialsUpda(currentAgentInitials || "");
34
+ _omnichannelChatComponents.BroadcastService.postMessage({
35
+ eventName: "BotAvatarInitialsUpdated",
36
+ payload: {
37
+ initials: currentAgentInitials
38
+ }
39
+ });
40
+ dispatch({
41
+ type: "__BOT_INITIALS_UPDATED__"
42
+ });
43
+ }
44
+ };
19
45
  const localizedStringsBotInitialsMiddleware = onInitialsChange => _ref => {
20
46
  let {
21
47
  dispatch
@@ -29,19 +55,31 @@ const localizedStringsBotInitialsMiddleware = onInitialsChange => _ref => {
29
55
  var _action$payload, _activity$from;
30
56
  const activity = (_action$payload = action.payload) === null || _action$payload === void 0 ? void 0 : _action$payload.activity;
31
57
  if (activity !== null && activity !== void 0 && (_activity$from = activity.from) !== null && _activity$from !== void 0 && _activity$from.name && activity.from.role !== _Constants.Constants.userMessageTag && activity.from.name !== _Constants.Constants.userMessageTag) {
32
- var _activity$channelData, _activity$channelData2, _activity$tags;
58
+ var _activity$channelData2, _activity$channelData3, _activity$tags;
33
59
  const agentName = activity.from.name.trim();
34
- const isSystemMessage = agentName === "__agent__" || agentName.startsWith("__") || ((_activity$channelData = activity.channelData) === null || _activity$channelData === void 0 ? void 0 : (_activity$channelData2 = _activity$channelData.tags) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.includes(_Constants.Constants.systemMessageTag)) || ((_activity$tags = activity.tags) === null || _activity$tags === void 0 ? void 0 : _activity$tags.includes(_Constants.Constants.systemMessageTag));
35
- if (!isSystemMessage && agentName) {
60
+ const isSystemMessage = agentName === "__agent__" || agentName.startsWith("__") || ((_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : (_activity$channelData3 = _activity$channelData2.tags) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.includes(_Constants.Constants.systemMessageTag)) || ((_activity$tags = activity.tags) === null || _activity$tags === void 0 ? void 0 : _activity$tags.includes(_Constants.Constants.systemMessageTag));
61
+ if (isSystemMessage) {
62
+ // transfer-stale-bot-name: when a transfer system message indicates the
63
+ // conversation has been routed to a different agent, the
64
+ // previously-cached agent name must NOT linger. Otherwise the
65
+ // next non-system activity (e.g. typing indicator, welcome
66
+ // message, or the new agent's first turn before their name
67
+ // is observed) is announced to screen readers as the OLD
68
+ // agent. Reset to defaults so the next observed name takes
69
+ // effect cleanly.
70
+ if (isTransferSystemMessage(activity)) {
71
+ resetCachedAgentName(dispatch);
72
+ }
73
+ } else if (agentName) {
36
74
  const newInitials = (0, _utils.getIconText)(agentName) || currentAgentInitials;
37
75
  const hasInitialsChanged = newInitials !== currentAgentInitials;
38
76
  const hasNameChanged = agentName !== currentAgentName;
39
77
  currentAgentName = agentName;
40
78
  if (hasInitialsChanged) {
41
- var _externalInitialsUpda;
79
+ var _externalInitialsUpda2;
42
80
  currentAgentInitials = newInitials;
43
81
  // Notify external React context if provided
44
- (_externalInitialsUpda = externalInitialsUpdater) === null || _externalInitialsUpda === void 0 ? void 0 : _externalInitialsUpda(currentAgentInitials || "");
82
+ (_externalInitialsUpda2 = externalInitialsUpdater) === null || _externalInitialsUpda2 === void 0 ? void 0 : _externalInitialsUpda2(currentAgentInitials || "");
45
83
  // Broadcast (optional) for multi-tab sync without forcing consumers
46
84
  _omnichannelChatComponents.BroadcastService.postMessage({
47
85
  eventName: "BotAvatarInitialsUpdated",
@@ -1,5 +1,5 @@
1
1
  import { LogLevel, TelemetryEvent } from "../../common/telemetry/TelemetryConstants";
2
- import React, { useEffect } from "react";
2
+ import React, { useEffect, useState } from "react";
3
3
  import { createTimer, findAllFocusableElement } from "../../common/utils";
4
4
  import { LoadingPane } from "@microsoft/omnichannel-chat-components";
5
5
  import { TelemetryHelper } from "../../common/telemetry/TelemetryHelper";
@@ -7,13 +7,22 @@ import { defaultGeneralPostChatLoadingPaneStyleProps } from "./common/defaultgen
7
7
  import useChatContextStore from "../../hooks/useChatContextStore";
8
8
  let uiTimer;
9
9
  export const PostChatLoadingPaneStateful = props => {
10
- var _props$styleProps;
10
+ var _props$controlProps, _props$styleProps;
11
+ const defaultSubtitleText = "Please take a moment to give us feedback about your chat experience. We are loading the survey for you now.";
12
+ const subtitleText = ((_props$controlProps = props.controlProps) === null || _props$controlProps === void 0 ? void 0 : _props$controlProps.subtitleText) ?? defaultSubtitleText;
13
+ const [announcedSubtitleText, setAnnouncedSubtitleText] = useState("");
11
14
  useEffect(() => {
12
15
  uiTimer = createTimer();
13
16
  TelemetryHelper.logLoadingEvent(LogLevel.INFO, {
14
17
  Event: TelemetryEvent.UXPostChatLoadingPaneStart
15
18
  });
16
19
  }, []);
20
+ useEffect(() => {
21
+ const timeout = window.setTimeout(() => {
22
+ setAnnouncedSubtitleText(subtitleText);
23
+ }, 0);
24
+ return () => window.clearTimeout(timeout);
25
+ }, [subtitleText]);
17
26
  const [state] = useChatContextStore();
18
27
  const generalStyleProps = Object.assign({}, defaultGeneralPostChatLoadingPaneStyleProps, (_props$styleProps = props.styleProps) === null || _props$styleProps === void 0 ? void 0 : _props$styleProps.generalStyleProps);
19
28
  const styleProps = {
@@ -27,8 +36,11 @@ export const PostChatLoadingPaneStateful = props => {
27
36
  hideTitle: true,
28
37
  hideSpinner: true,
29
38
  hideSpinnerText: true,
30
- subtitleText: "Please take a moment to give us feedback about your chat experience. We are loading the survey for you now.",
31
- ...props.controlProps
39
+ ...props.controlProps,
40
+ role: "status",
41
+ "aria-live": "polite",
42
+ "aria-atomic": "true",
43
+ subtitleText: announcedSubtitleText
32
44
  };
33
45
 
34
46
  // Move focus to the first button
@@ -58,6 +58,7 @@ export const PostChatSurveyPaneStateful = props => {
58
58
  };
59
59
  const controlProps = {
60
60
  id: "oc-lcw-postchatsurvey-pane",
61
+ title: "Post-chat survey",
61
62
  surveyURL: ((_props$controlProps = props.controlProps) === null || _props$controlProps === void 0 ? void 0 : _props$controlProps.surveyURL) ?? surveyInviteLink,
62
63
  ...props.controlProps
63
64
  };
@@ -1,6 +1,6 @@
1
1
  import { HtmlAttributeNames, Regex } from "../../common/Constants";
2
2
  import { ConversationStage, LogLevel, TelemetryEvent } from "../../common/telemetry/TelemetryConstants";
3
- import React, { useEffect } from "react";
3
+ import React, { useEffect, useRef, useState } from "react";
4
4
  import { createTimer, extractPreChatSurveyResponseValues, findAllFocusableElement, getStateFromCache, getWidgetCacheId, isUndefinedOrEmpty, parseAdaptiveCardPayload } from "../../common/utils";
5
5
  import { ConversationState } from "../../contexts/common/ConversationState";
6
6
  import { LiveChatWidgetActionType } from "../../contexts/common/LiveChatWidgetActionType";
@@ -11,6 +11,34 @@ import { defaultGeneralPreChatSurveyPaneStyleProps } from "./common/defaultStyle
11
11
  import { defaultPreChatSurveyLocalizedTexts } from "./common/defaultProps/defaultPreChatSurveyLocalizedTexts";
12
12
  import useChatContextStore from "../../hooks/useChatContextStore";
13
13
  let uiTimer;
14
+ const getFocusedElementAnnouncement = element => {
15
+ var _element$textContent;
16
+ const ariaLabel = element.getAttribute(HtmlAttributeNames.ariaLabel);
17
+ if (ariaLabel) {
18
+ return ariaLabel.trim();
19
+ }
20
+ const ariaLabelledBy = element.getAttribute(HtmlAttributeNames.ariaLabelledby);
21
+ if (ariaLabelledBy) {
22
+ const labelledByText = ariaLabelledBy.split(/\s+/).map(id => {
23
+ var _document$getElementB, _document$getElementB2;
24
+ return (_document$getElementB = document.getElementById(id)) === null || _document$getElementB === void 0 ? void 0 : (_document$getElementB2 = _document$getElementB.textContent) === null || _document$getElementB2 === void 0 ? void 0 : _document$getElementB2.trim();
25
+ }).filter(Boolean).join(" ");
26
+ if (labelledByText) {
27
+ return labelledByText;
28
+ }
29
+ }
30
+ if (element.id) {
31
+ const label = Array.from(document.querySelectorAll("label")).find(candidate => candidate.htmlFor === element.id || candidate.getAttribute("for") === element.id);
32
+ if (label !== null && label !== void 0 && label.textContent) {
33
+ return label.textContent.trim();
34
+ }
35
+ }
36
+ const parentLabel = element.closest("label");
37
+ if (parentLabel !== null && parentLabel !== void 0 && parentLabel.textContent) {
38
+ return parentLabel.textContent.trim();
39
+ }
40
+ return ((_element$textContent = element.textContent) === null || _element$textContent === void 0 ? void 0 : _element$textContent.trim()) ?? "";
41
+ };
14
42
 
15
43
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
44
  export const PreChatSurveyPaneStateful = props => {
@@ -33,6 +61,8 @@ export const PreChatSurveyPaneStateful = props => {
33
61
  surveyProps,
34
62
  initStartChat
35
63
  } = props;
64
+ const [preChatFocusAnnouncement, setPreChatFocusAnnouncement] = useState("");
65
+ const liveRegionUpdateTimeout = useRef();
36
66
  const generalStyleProps = Object.assign({}, defaultGeneralPreChatSurveyPaneStyleProps, surveyProps === null || surveyProps === void 0 ? void 0 : (_surveyProps$stylePro = surveyProps.styleProps) === null || _surveyProps$stylePro === void 0 ? void 0 : _surveyProps$stylePro.generalStyleProps, {
37
67
  display: state.appStates.isMinimized ? "none" : ""
38
68
  });
@@ -119,6 +149,16 @@ export const PreChatSurveyPaneStateful = props => {
119
149
  ...(surveyProps === null || surveyProps === void 0 ? void 0 : surveyProps.styleProps),
120
150
  generalStyleProps: generalStyleProps
121
151
  };
152
+ const announceFocusedElement = event => {
153
+ if (liveRegionUpdateTimeout.current) {
154
+ window.clearTimeout(liveRegionUpdateTimeout.current);
155
+ }
156
+ const announcement = getFocusedElementAnnouncement(event.target);
157
+ setPreChatFocusAnnouncement("");
158
+ liveRegionUpdateTimeout.current = window.setTimeout(() => {
159
+ setPreChatFocusAnnouncement(announcement);
160
+ }, 0);
161
+ };
122
162
  useEffect(() => {
123
163
  // Set Aria-Label Attribute for Inputs
124
164
  const adaptiveCardElements = document.getElementsByClassName(HtmlAttributeNames.adaptiveCardClassName);
@@ -166,9 +206,31 @@ export const PreChatSurveyPaneStateful = props => {
166
206
  }
167
207
  }
168
208
  }, [state.appStates.isMinimized]);
169
- return /*#__PURE__*/React.createElement(PreChatSurveyPane, {
209
+ useEffect(() => () => {
210
+ if (liveRegionUpdateTimeout.current) {
211
+ window.clearTimeout(liveRegionUpdateTimeout.current);
212
+ }
213
+ }, []);
214
+ return /*#__PURE__*/React.createElement("div", {
215
+ onFocusCapture: announceFocusedElement
216
+ }, /*#__PURE__*/React.createElement("div", {
217
+ role: "status",
218
+ "aria-live": "polite",
219
+ "aria-atomic": "true",
220
+ style: {
221
+ position: "absolute",
222
+ width: "1px",
223
+ height: "1px",
224
+ margin: "-1px",
225
+ padding: 0,
226
+ border: 0,
227
+ clip: "rect(0, 0, 0, 0)",
228
+ overflow: "hidden",
229
+ whiteSpace: "nowrap"
230
+ }
231
+ }, preChatFocusAnnouncement), /*#__PURE__*/React.createElement(PreChatSurveyPane, {
170
232
  controlProps: controlProps,
171
233
  styleProps: styleProps
172
- });
234
+ }));
173
235
  };
174
236
  export default PreChatSurveyPaneStateful;
@@ -25,6 +25,21 @@ const AdaptiveCardAccessibilityWrapper = _ref => {
25
25
  const container = containerRef.current;
26
26
  if (!container) return;
27
27
 
28
+ // dropdown-double-label: compact Input.ChoiceSet renders as a native
29
+ // <select> with both aria-labelledby and a sibling visible <label for>.
30
+ // Only remove aria-labelledby when it points solely at that same visible
31
+ // label; preserve composite labels that include required/error/context text.
32
+ const compactSelects = container.querySelectorAll(".ac-input-container select.ac-input.ac-multichoiceInput[aria-labelledby]");
33
+ compactSelects.forEach(select => {
34
+ const id = select.id;
35
+ if (!id) return;
36
+ const visibleLabel = container.querySelector(`label[for="${CSS.escape(id)}"]:not([aria-hidden='true'])`);
37
+ const labelledBy = (select.getAttribute("aria-labelledby") || "").trim().split(/\s+/);
38
+ if (visibleLabel !== null && visibleLabel !== void 0 && visibleLabel.id && labelledBy.length === 1 && labelledBy[0] === visibleLabel.id) {
39
+ select.removeAttribute("aria-labelledby");
40
+ }
41
+ });
42
+
28
43
  // action-button-toggle / login-button-toggle: submit/login action buttons can be
29
44
  // rendered with toggle-only ARIA that causes screen readers to announce them as
30
45
  // switches instead of plain push buttons. Preserve Action.ToggleVisibility buttons,
@@ -10,6 +10,32 @@ let currentAgentName = defaultWebChatStyles.botAvatarInitials;
10
10
 
11
11
  // Optional external updater (React context dispatch wrapper) set at runtime
12
12
  let externalInitialsUpdater;
13
+ const hasTransferTag = activity => {
14
+ var _activity$channelData;
15
+ const tags = [...(Array.isArray(activity === null || activity === void 0 ? void 0 : (_activity$channelData = activity.channelData) === null || _activity$channelData === void 0 ? void 0 : _activity$channelData.tags) ? activity.channelData.tags : []), ...(Array.isArray(activity === null || activity === void 0 ? void 0 : activity.tags) ? activity.tags : [])];
16
+ return tags.some(tag => typeof tag === "string" && tag.toLowerCase().includes("transfer"));
17
+ };
18
+ const isTransferSystemMessage = activity => {
19
+ const text = ((activity === null || activity === void 0 ? void 0 : activity.text) || "").toString();
20
+ return hasTransferTag(activity) || /\btransfer(?:red|ring)?\b/i.test(text);
21
+ };
22
+ const resetCachedAgentName = dispatch => {
23
+ if (currentAgentName !== defaultWebChatStyles.botAvatarInitials || currentAgentInitials !== defaultWebChatStyles.botAvatarInitials) {
24
+ var _externalInitialsUpda;
25
+ currentAgentName = defaultWebChatStyles.botAvatarInitials;
26
+ currentAgentInitials = defaultWebChatStyles.botAvatarInitials;
27
+ (_externalInitialsUpda = externalInitialsUpdater) === null || _externalInitialsUpda === void 0 ? void 0 : _externalInitialsUpda(currentAgentInitials || "");
28
+ BroadcastService.postMessage({
29
+ eventName: "BotAvatarInitialsUpdated",
30
+ payload: {
31
+ initials: currentAgentInitials
32
+ }
33
+ });
34
+ dispatch({
35
+ type: "__BOT_INITIALS_UPDATED__"
36
+ });
37
+ }
38
+ };
13
39
  export const localizedStringsBotInitialsMiddleware = onInitialsChange => _ref => {
14
40
  let {
15
41
  dispatch
@@ -23,19 +49,31 @@ export const localizedStringsBotInitialsMiddleware = onInitialsChange => _ref =>
23
49
  var _action$payload, _activity$from;
24
50
  const activity = (_action$payload = action.payload) === null || _action$payload === void 0 ? void 0 : _action$payload.activity;
25
51
  if (activity !== null && activity !== void 0 && (_activity$from = activity.from) !== null && _activity$from !== void 0 && _activity$from.name && activity.from.role !== Constants.userMessageTag && activity.from.name !== Constants.userMessageTag) {
26
- var _activity$channelData, _activity$channelData2, _activity$tags;
52
+ var _activity$channelData2, _activity$channelData3, _activity$tags;
27
53
  const agentName = activity.from.name.trim();
28
- const isSystemMessage = agentName === "__agent__" || agentName.startsWith("__") || ((_activity$channelData = activity.channelData) === null || _activity$channelData === void 0 ? void 0 : (_activity$channelData2 = _activity$channelData.tags) === null || _activity$channelData2 === void 0 ? void 0 : _activity$channelData2.includes(Constants.systemMessageTag)) || ((_activity$tags = activity.tags) === null || _activity$tags === void 0 ? void 0 : _activity$tags.includes(Constants.systemMessageTag));
29
- if (!isSystemMessage && agentName) {
54
+ const isSystemMessage = agentName === "__agent__" || agentName.startsWith("__") || ((_activity$channelData2 = activity.channelData) === null || _activity$channelData2 === void 0 ? void 0 : (_activity$channelData3 = _activity$channelData2.tags) === null || _activity$channelData3 === void 0 ? void 0 : _activity$channelData3.includes(Constants.systemMessageTag)) || ((_activity$tags = activity.tags) === null || _activity$tags === void 0 ? void 0 : _activity$tags.includes(Constants.systemMessageTag));
55
+ if (isSystemMessage) {
56
+ // transfer-stale-bot-name: when a transfer system message indicates the
57
+ // conversation has been routed to a different agent, the
58
+ // previously-cached agent name must NOT linger. Otherwise the
59
+ // next non-system activity (e.g. typing indicator, welcome
60
+ // message, or the new agent's first turn before their name
61
+ // is observed) is announced to screen readers as the OLD
62
+ // agent. Reset to defaults so the next observed name takes
63
+ // effect cleanly.
64
+ if (isTransferSystemMessage(activity)) {
65
+ resetCachedAgentName(dispatch);
66
+ }
67
+ } else if (agentName) {
30
68
  const newInitials = getIconText(agentName) || currentAgentInitials;
31
69
  const hasInitialsChanged = newInitials !== currentAgentInitials;
32
70
  const hasNameChanged = agentName !== currentAgentName;
33
71
  currentAgentName = agentName;
34
72
  if (hasInitialsChanged) {
35
- var _externalInitialsUpda;
73
+ var _externalInitialsUpda2;
36
74
  currentAgentInitials = newInitials;
37
75
  // Notify external React context if provided
38
- (_externalInitialsUpda = externalInitialsUpdater) === null || _externalInitialsUpda === void 0 ? void 0 : _externalInitialsUpda(currentAgentInitials || "");
76
+ (_externalInitialsUpda2 = externalInitialsUpdater) === null || _externalInitialsUpda2 === void 0 ? void 0 : _externalInitialsUpda2(currentAgentInitials || "");
39
77
  // Broadcast (optional) for multi-tab sync without forcing consumers
40
78
  BroadcastService.postMessage({
41
79
  eventName: "BotAvatarInitialsUpdated",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@microsoft/omnichannel-chat-widget",
3
- "version": "1.8.4-main.f6a1732",
3
+ "version": "1.8.4-main.fba10aa",
4
4
  "description": "Microsoft Omnichannel Chat Widget",
5
5
  "main": "lib/cjs/index.js",
6
6
  "types": "lib/types/index.d.ts",
@@ -84,7 +84,7 @@
84
84
  "react-test-renderer": "^18.3.1",
85
85
  "rimraf": "^6.0.1",
86
86
  "storybook-addon-playwright": "^4.9.2",
87
- "swiper": "^9.0.5",
87
+ "swiper": "^12.1.2",
88
88
  "terser-webpack-plugin": "^4.2.3",
89
89
  "thread-loader": "^2.1.3",
90
90
  "ts-loader": "^9.2.6",
@@ -95,7 +95,7 @@
95
95
  "dependencies": {
96
96
  "@azure/core-tracing": "^1.2.0",
97
97
  "@microsoft/applicationinsights-web": "^3.3.6",
98
- "@microsoft/omnichannel-chat-components": "1.1.17-main.d4c4cb2",
98
+ "@microsoft/omnichannel-chat-components": "1.1.17-main.f21df63",
99
99
  "@microsoft/omnichannel-chat-sdk": "1.11.9-main.da8219b",
100
100
  "@opentelemetry/api": "^1.9.0",
101
101
  "abort-controller": "^3",
@@ -172,7 +172,7 @@
172
172
  "**/lodash": "4.17.23",
173
173
  "**/@babel/runtime-corejs3": "^7.29.0",
174
174
  "**/brace-expansion": "2.0.3",
175
- "**/swiper": "9.4.1"
175
+ "**/swiper": "12.1.2"
176
176
  },
177
177
  "jest": {
178
178
  "verbose": true,