@microsoft/omnichannel-chat-widget 1.8.4-main.6b93408 → 1.8.4-main.7e15cb1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/cjs/common/Constants.js +0 -1
- package/lib/cjs/common/telemetry/TelemetryManager.js +6 -1
- package/lib/cjs/common/telemetry/sanitizeSasInPayload.js +82 -0
- package/lib/cjs/components/citationpanestateful/CitationPaneStateful.js +7 -1
- package/lib/cjs/components/confirmationpanestateful/ConfirmationPaneStateful.js +11 -1
- package/lib/cjs/components/emailtranscriptpanestateful/EmailTranscriptPaneStateful.js +5 -1
- package/lib/cjs/components/livechatwidget/common/createMarkdown.js +4 -2
- package/lib/cjs/components/livechatwidget/common/initWebChatComposer.js +16 -12
- package/lib/cjs/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +1 -0
- package/lib/cjs/components/prechatsurveypanestateful/PreChatSurveyPaneStateful.js +88 -4
- package/lib/cjs/components/prechatsurveypanestateful/common/defaultStyles/defaultGeneralPreChatSurveyPaneStyleProps.js +13 -3
- package/lib/cjs/components/webchatcontainerstateful/WebChatContainerStateful.js +47 -17
- package/lib/cjs/components/webchatcontainerstateful/common/DesignerChatAdapter.js +10 -0
- package/lib/cjs/components/webchatcontainerstateful/common/defaultProps/defaultWebChatContainerStatefulProps.js +2 -1
- package/lib/cjs/components/webchatcontainerstateful/common/utils/chatAdapterUtils.js +3 -1
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +11 -14
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +50 -0
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +4 -8
- package/lib/cjs/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/localizedStringsBotInitialsMiddleware.js +43 -5
- package/lib/cjs/contexts/common/LiveChatWidgetContextInitialState.js +11 -0
- package/lib/esm/common/Constants.js +0 -1
- package/lib/esm/common/telemetry/TelemetryManager.js +6 -1
- package/lib/esm/common/telemetry/sanitizeSasInPayload.js +75 -0
- package/lib/esm/components/citationpanestateful/CitationPaneStateful.js +7 -1
- package/lib/esm/components/confirmationpanestateful/ConfirmationPaneStateful.js +11 -1
- package/lib/esm/components/emailtranscriptpanestateful/EmailTranscriptPaneStateful.js +5 -1
- package/lib/esm/components/livechatwidget/common/createMarkdown.js +4 -2
- package/lib/esm/components/livechatwidget/common/initWebChatComposer.js +16 -12
- package/lib/esm/components/postchatsurveypanestateful/PostChatSurveyPaneStateful.js +1 -0
- package/lib/esm/components/prechatsurveypanestateful/PreChatSurveyPaneStateful.js +89 -5
- package/lib/esm/components/prechatsurveypanestateful/common/defaultStyles/defaultGeneralPreChatSurveyPaneStyleProps.js +13 -3
- package/lib/esm/components/webchatcontainerstateful/WebChatContainerStateful.js +48 -18
- package/lib/esm/components/webchatcontainerstateful/common/DesignerChatAdapter.js +10 -0
- package/lib/esm/components/webchatcontainerstateful/common/defaultProps/defaultWebChatContainerStatefulProps.js +2 -1
- package/lib/esm/components/webchatcontainerstateful/common/utils/chatAdapterUtils.js +3 -1
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/activityMiddleware.js +11 -14
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/renderingmiddlewares/attachments/AdaptiveCardAccessibilityWrapper.js +50 -0
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/attachmentUploadValidatorMiddleware.js +5 -9
- package/lib/esm/components/webchatcontainerstateful/webchatcontroller/middlewares/storemiddlewares/localizedStringsBotInitialsMiddleware.js +43 -5
- package/lib/esm/contexts/common/LiveChatWidgetContextInitialState.js +11 -0
- package/lib/types/common/Constants.d.ts +0 -1
- package/lib/types/common/telemetry/sanitizeSasInPayload.d.ts +24 -0
- package/lib/types/components/webchatcontainerstateful/common/utils/chatAdapterUtils.d.ts +1 -1
- package/lib/types/components/webchatcontainerstateful/interfaces/IWebChatContainerStatefulProps.d.ts +7 -0
- package/package.json +5 -5
|
@@ -158,7 +158,6 @@ let HtmlIdNames = /*#__PURE__*/_createClass(function HtmlIdNames() {
|
|
|
158
158
|
});
|
|
159
159
|
exports.HtmlIdNames = HtmlIdNames;
|
|
160
160
|
_defineProperty(HtmlIdNames, "MSLiveChatWidget", "MSLiveChatWidget");
|
|
161
|
-
_defineProperty(HtmlIdNames, "fileSentAnnouncementRegionId", "ms_lcw_file_sent_announcement");
|
|
162
161
|
let HtmlClassNames = /*#__PURE__*/_createClass(function HtmlClassNames() {
|
|
163
162
|
_classCallCheck(this, HtmlClassNames);
|
|
164
163
|
});
|
|
@@ -11,6 +11,7 @@ var _appInsightsLogger = require("./loggers/appInsightsLogger");
|
|
|
11
11
|
var _ariaTelemetryLogger = require("./loggers/ariaTelemetryLogger");
|
|
12
12
|
var _consoleLogger = require("./loggers/consoleLogger");
|
|
13
13
|
var _defaultAriaConfig = require("./defaultConfigs/defaultAriaConfig");
|
|
14
|
+
var _sanitizeSasInPayload = require("./sanitizeSasInPayload");
|
|
14
15
|
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); } }
|
|
15
16
|
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); Object.defineProperty(Constructor, "prototype", { writable: false }); return Constructor; }
|
|
16
17
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
|
@@ -81,11 +82,15 @@ const RegisterLoggers = () => {
|
|
|
81
82
|
};
|
|
82
83
|
};
|
|
83
84
|
const logTelemetry = telemetryEvent => {
|
|
85
|
+
// Redact Azure SAS credentials in any URL embedded in the payload (e.g.
|
|
86
|
+
// customer-supplied blob asset URLs in widget customization props) before
|
|
87
|
+
// it fans out to console, Aria, AppInsights, or any custom logger.
|
|
88
|
+
const sanitizedPayload = (0, _sanitizeSasInPayload.sanitizeSasInPayload)(telemetryEvent === null || telemetryEvent === void 0 ? void 0 : telemetryEvent.payload);
|
|
84
89
|
loggers.map(logger => {
|
|
85
90
|
var _payload, _telemetryInput$paylo;
|
|
86
91
|
const logLevel = telemetryEvent.logLevel ?? _TelemetryConstants.LogLevel.INFO;
|
|
87
92
|
const scenarioType = ((_payload = telemetryEvent.payload) === null || _payload === void 0 ? void 0 : _payload.scenarioType) ?? _TelemetryConstants.ScenarioType.UNDEFINED;
|
|
88
|
-
const telemetryInput = parseInput(
|
|
93
|
+
const telemetryInput = parseInput(sanitizedPayload, scenarioType);
|
|
89
94
|
telemetryInput.telemetryInfo = {
|
|
90
95
|
telemetryInfo: _TelemetryHelper.TelemetryHelper.buildTelemetryEvent(logLevel, telemetryInput)
|
|
91
96
|
};
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.sanitizeSasInPayload = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* Redacts the values of Azure Shared Access Signature (SAS) query parameters in any
|
|
9
|
+
* URL embedded in a telemetry payload, so customer-supplied SAS URLs (e.g. blob asset
|
|
10
|
+
* URLs in widget customization props) cannot leak credentials through Aria/AppInsights.
|
|
11
|
+
*
|
|
12
|
+
* Trigger: any URL whose query string contains `sig=`. When detected, every well-known
|
|
13
|
+
* SAS parameter value in that query is replaced with `[REDACTED]`. Other URLs and other
|
|
14
|
+
* substrings of the payload pass through unchanged.
|
|
15
|
+
*
|
|
16
|
+
* Coverage of SAS parameter names:
|
|
17
|
+
* - Service/Blob SAS: sig, sv, se, st, sp, spr, sr, sip
|
|
18
|
+
* - Account SAS: ss, srt
|
|
19
|
+
* - User-delegation key SAS: skoid, sktid, skt, ske, sks, skv, saoid, suoid, sce, sdd
|
|
20
|
+
* - Response header overrides: rscc, rscd, rsce, rscl, rsct
|
|
21
|
+
*
|
|
22
|
+
* Reference: https://learn.microsoft.com/en-us/rest/api/storageservices/create-service-sas
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const SAS_PARAM_NAMES = new Set(["sig", "sv", "se", "st", "sp", "spr", "sr", "sip", "ss", "srt", "skoid", "sktid", "skt", "ske", "sks", "skv", "saoid", "suoid", "sce", "sdd", "rscc", "rscd", "rsce", "rscl", "rsct"]);
|
|
26
|
+
const REDACTED_VALUE = "[REDACTED]";
|
|
27
|
+
|
|
28
|
+
// Matches any http(s) URL substring up to whitespace / closing quote / angle-bracket / paren.
|
|
29
|
+
const URL_REGEX = /\bhttps?:\/\/[^\s"'<>)]+/gi;
|
|
30
|
+
const redactQueryIfSas = url => {
|
|
31
|
+
const queryIdx = url.indexOf("?");
|
|
32
|
+
if (queryIdx < 0) {
|
|
33
|
+
return url;
|
|
34
|
+
}
|
|
35
|
+
// Separate the fragment (#...) before processing the query — fragments come
|
|
36
|
+
// after the query string in URL syntax and must not be folded into the last
|
|
37
|
+
// query parameter's value when we split on `&`.
|
|
38
|
+
const afterQuestionMark = url.slice(queryIdx + 1);
|
|
39
|
+
const fragIdx = afterQuestionMark.indexOf("#");
|
|
40
|
+
const query = fragIdx < 0 ? afterQuestionMark : afterQuestionMark.slice(0, fragIdx);
|
|
41
|
+
const fragment = fragIdx < 0 ? "" : afterQuestionMark.slice(fragIdx); // includes the leading '#'
|
|
42
|
+
if (!/(^|&)sig=/i.test(query)) {
|
|
43
|
+
// No SAS signature present — leave the URL alone so legitimate non-SAS query
|
|
44
|
+
// strings (e.g. ?utm_source=...) are not mangled.
|
|
45
|
+
return url;
|
|
46
|
+
}
|
|
47
|
+
const base = url.slice(0, queryIdx);
|
|
48
|
+
const sanitizedQuery = query.split("&").map(pair => {
|
|
49
|
+
const eqIdx = pair.indexOf("=");
|
|
50
|
+
if (eqIdx < 0) {
|
|
51
|
+
return pair;
|
|
52
|
+
}
|
|
53
|
+
const name = pair.slice(0, eqIdx).toLowerCase();
|
|
54
|
+
return SAS_PARAM_NAMES.has(name) ? `${pair.slice(0, eqIdx)}=${REDACTED_VALUE}` : pair;
|
|
55
|
+
}).join("&");
|
|
56
|
+
return `${base}?${sanitizedQuery}${fragment}`;
|
|
57
|
+
};
|
|
58
|
+
const sanitizeString = s => s.replace(URL_REGEX, redactQueryIfSas);
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Deep-walk a value and redact SAS credentials in any string it contains.
|
|
62
|
+
* Returns a new object/array (originals are not mutated); non-string leaves are
|
|
63
|
+
* passed through. Tree-shaped, JSON-like values only — not designed for class
|
|
64
|
+
* instances, prototype-bound objects, or cyclic graphs.
|
|
65
|
+
*/
|
|
66
|
+
const sanitizeSasInPayload = value => {
|
|
67
|
+
if (typeof value === "string") {
|
|
68
|
+
return sanitizeString(value);
|
|
69
|
+
}
|
|
70
|
+
if (Array.isArray(value)) {
|
|
71
|
+
return value.map(sanitizeSasInPayload);
|
|
72
|
+
}
|
|
73
|
+
if (value !== null && typeof value === "object") {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const [k, v] of Object.entries(value)) {
|
|
76
|
+
out[k] = sanitizeSasInPayload(v);
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
};
|
|
82
|
+
exports.sanitizeSasInPayload = sanitizeSasInPayload;
|
|
@@ -62,7 +62,13 @@ const CitationPaneStateful = props => {
|
|
|
62
62
|
Event: _TelemetryConstants.TelemetryEvent.UXCitationPaneCompleted,
|
|
63
63
|
ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
|
|
64
64
|
});
|
|
65
|
-
return
|
|
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
|
+
};
|
|
66
72
|
}, []);
|
|
67
73
|
|
|
68
74
|
// Retry focus once pane is actually visible (isReady) in case initial attempt occurred while wrapper was visibility:hidden
|
|
@@ -100,7 +100,17 @@ const ConfirmationPaneStateful = props => {
|
|
|
100
100
|
Event: _TelemetryConstants.TelemetryEvent.UXConfirmationPaneCompleted,
|
|
101
101
|
ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
|
|
102
102
|
});
|
|
103
|
-
return
|
|
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
|
+
};
|
|
104
114
|
}, []);
|
|
105
115
|
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DimLayer.DimLayer, {
|
|
106
116
|
brightness: (controlProps === null || controlProps === void 0 ? void 0 : controlProps.brightnessValueOnDim) ?? "0.2"
|
|
@@ -136,7 +136,11 @@ const EmailTranscriptPaneStateful = props => {
|
|
|
136
136
|
Event: _TelemetryConstants.TelemetryEvent.UXEmailTranscriptPaneCompleted,
|
|
137
137
|
ElapsedTimeInMilliseconds: uiTimer.milliSecondsElapsed
|
|
138
138
|
});
|
|
139
|
-
return
|
|
139
|
+
return () => {
|
|
140
|
+
// internal tracking: restore parent tab indices on unmount.
|
|
141
|
+
(0, _utils.setTabIndices)(elements, initialTabIndexMap, true);
|
|
142
|
+
cleanup();
|
|
143
|
+
};
|
|
140
144
|
}, [initialEmail]);
|
|
141
145
|
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement(_DimLayer.DimLayer, {
|
|
142
146
|
brightness: (controlProps === null || controlProps === void 0 ? void 0 : controlProps.brightnessValueOnDim) ?? "0.2"
|
|
@@ -173,14 +173,16 @@ const createMarkdown = (disableMarkdownMessageFormatting, disableNewLineMarkdown
|
|
|
173
173
|
|
|
174
174
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
175
175
|
md.render = function (text, env) {
|
|
176
|
+
const safeEnv = env ?? {};
|
|
176
177
|
const processedText = preprocessText(text);
|
|
177
|
-
return md.renderer.render(md.parse(processedText,
|
|
178
|
+
return md.renderer.render(md.parse(processedText, safeEnv), md.options, safeEnv);
|
|
178
179
|
};
|
|
179
180
|
|
|
180
181
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
181
182
|
md.renderInline = function (text, env) {
|
|
183
|
+
const safeEnv = env ?? {};
|
|
182
184
|
const processedText = preprocessText(text);
|
|
183
|
-
return md.renderer.render(md.parseInline(processedText,
|
|
185
|
+
return md.renderer.render(md.parseInline(processedText, safeEnv), md.options, safeEnv);
|
|
184
186
|
};
|
|
185
187
|
});
|
|
186
188
|
|
|
@@ -304,24 +304,28 @@ const initWebChatComposer = (props, state, dispatch, facadeChatSDK, endChat) =>
|
|
|
304
304
|
const conversationId = (state === null || state === void 0 ? void 0 : (_state$domainStates2 = state.domainStates) === null || _state$domainStates2 === void 0 ? void 0 : (_state$domainStates2$ = _state$domainStates2.chatToken) === null || _state$domainStates2$ === void 0 ? void 0 : _state$domainStates2$.chatId) || "unknown";
|
|
305
305
|
_TelemetryHelper.TelemetryHelper.logActionEvent(_TelemetryConstants.LogLevel.INFO, {
|
|
306
306
|
Event: _TelemetryConstants.TelemetryEvent.HTMLSanitized,
|
|
307
|
-
Description:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
307
|
+
Description: JSON.stringify({
|
|
308
|
+
message: "HTML content would be sanitized by stricter allowlist (monitor-only)",
|
|
309
|
+
removedTags: uniqueTags,
|
|
310
|
+
removedAttributes: uniqueAttrs,
|
|
311
|
+
phase: "Monitor",
|
|
312
|
+
organizationId: orgId,
|
|
313
|
+
conversationId: conversationId
|
|
314
|
+
}),
|
|
315
|
+
ElapsedTimeInMilliseconds: executionTimeMs
|
|
316
316
|
});
|
|
317
317
|
|
|
318
318
|
// Log to console in development for debugging
|
|
319
319
|
if (process.env.NODE_ENV === "development") {
|
|
320
|
-
console.warn("[Monitor] Stricter HTML sanitization
|
|
320
|
+
console.warn("[Monitor] Stricter HTML sanitization telemetry:", {
|
|
321
|
+
description: JSON.parse(JSON.stringify({
|
|
322
|
+
message: "HTML content would be sanitized by stricter allowlist (monitor-only)",
|
|
323
|
+
removedTags: uniqueTags,
|
|
324
|
+
removedAttributes: uniqueAttrs,
|
|
325
|
+
phase: "Monitor"
|
|
326
|
+
})),
|
|
321
327
|
orgId,
|
|
322
328
|
conversationId,
|
|
323
|
-
removedTags: uniqueTags,
|
|
324
|
-
removedAttributes: uniqueAttrs,
|
|
325
329
|
executionTimeMs
|
|
326
330
|
});
|
|
327
331
|
}
|
|
@@ -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,54 @@ const PreChatSurveyPaneStateful = props => {
|
|
|
175
215
|
}
|
|
176
216
|
}
|
|
177
217
|
}, [state.appStates.isMinimized]);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
218
|
+
(0, _react.useEffect)(() => () => {
|
|
219
|
+
if (liveRegionUpdateTimeout.current) {
|
|
220
|
+
window.clearTimeout(liveRegionUpdateTimeout.current);
|
|
221
|
+
}
|
|
222
|
+
}, []);
|
|
223
|
+
return (
|
|
224
|
+
/*#__PURE__*/
|
|
225
|
+
// Bug 6525143 / Bug 6423684: the LiveChatWidget container is a centered
|
|
226
|
+
// flex column (HeaderStateful first, this pane second) with
|
|
227
|
+
// justifyContent: center and clipped overflow. Sizing this focus-capture
|
|
228
|
+
// wrapper to height: 100% makes it 100% of the ENTIRE container rather than
|
|
229
|
+
// the space remaining below the header, so header + pane overflow the
|
|
230
|
+
// container and the centered column splits the overflow top & bottom -
|
|
231
|
+
// clipping the header's top edge. Instead, fill only the remaining space
|
|
232
|
+
// with flex (flex: 1 1 auto + minHeight: 0) and lay out as a column so the
|
|
233
|
+
// header stays fully visible while the inner pane (a flex child that grows
|
|
234
|
+
// via flexGrow/flexBasis - see defaultGeneralPreChatSurveyPaneStyleProps)
|
|
235
|
+
// fills the leftover area and scrolls, instead of collapsing to its
|
|
236
|
+
// intrinsic content size.
|
|
237
|
+
_react.default.createElement("div", {
|
|
238
|
+
onFocusCapture: announceFocusedElement,
|
|
239
|
+
style: {
|
|
240
|
+
width: "100%",
|
|
241
|
+
flex: "1 1 auto",
|
|
242
|
+
minHeight: 0,
|
|
243
|
+
display: "flex",
|
|
244
|
+
flexDirection: "column"
|
|
245
|
+
}
|
|
246
|
+
}, /*#__PURE__*/_react.default.createElement("div", {
|
|
247
|
+
role: "status",
|
|
248
|
+
"aria-live": "polite",
|
|
249
|
+
"aria-atomic": "true",
|
|
250
|
+
style: {
|
|
251
|
+
position: "absolute",
|
|
252
|
+
width: "1px",
|
|
253
|
+
height: "1px",
|
|
254
|
+
margin: "-1px",
|
|
255
|
+
padding: 0,
|
|
256
|
+
border: 0,
|
|
257
|
+
clip: "rect(0, 0, 0, 0)",
|
|
258
|
+
overflow: "hidden",
|
|
259
|
+
whiteSpace: "nowrap"
|
|
260
|
+
}
|
|
261
|
+
}, preChatFocusAnnouncement), /*#__PURE__*/_react.default.createElement(_omnichannelChatComponents.PreChatSurveyPane, {
|
|
262
|
+
controlProps: controlProps,
|
|
263
|
+
styleProps: styleProps
|
|
264
|
+
}))
|
|
265
|
+
);
|
|
182
266
|
};
|
|
183
267
|
exports.PreChatSurveyPaneStateful = PreChatSurveyPaneStateful;
|
|
184
268
|
var _default = PreChatSurveyPaneStateful;
|
|
@@ -11,8 +11,18 @@ const defaultGeneralPreChatSurveyPaneStyleProps = {
|
|
|
11
11
|
backgroundColor: "#FFFFFF",
|
|
12
12
|
borderColor: "#F1F1F1",
|
|
13
13
|
overflowY: "auto",
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
// Bug 6525143 / Bug 6423684: the focus-capture wrapper is a flex column sized to
|
|
15
|
+
// the space remaining below the header (flex: 1 1 auto). Fill that space via flex
|
|
16
|
+
// (flexGrow: 1 / flexBasis: 0 / minHeight: 0) instead of height: 100%, which
|
|
17
|
+
// resolved against the whole centered #oc-lcw container and pushed the header out
|
|
18
|
+
// (the clip). minHeight: 0 keeps the pane shrinkable so its overflowY scroll
|
|
19
|
+
// engages for tall surveys rather than growing the column and clipping the header.
|
|
20
|
+
// Note: the shared PreChatSurveyPane default (chat-components) still merges
|
|
21
|
+
// height: "inherit" underneath these props; under this flex parent that resolves
|
|
22
|
+
// to auto and is overridden by the flex main-size, so the pane still fills via flex.
|
|
23
|
+
flexGrow: 1,
|
|
24
|
+
flexBasis: 0,
|
|
25
|
+
minHeight: 0,
|
|
26
|
+
width: "inherit"
|
|
17
27
|
};
|
|
18
28
|
exports.defaultGeneralPreChatSurveyPaneStyleProps = defaultGeneralPreChatSurveyPaneStyleProps;
|
|
@@ -232,6 +232,48 @@ const WebChatContainerStateful = props => {
|
|
|
232
232
|
chatHistoryElement.setAttribute(_Constants.HtmlAttributeNames.ariaLabel, webChatContainerProps.webChatHistoryMobileAccessibilityLabel);
|
|
233
233
|
}
|
|
234
234
|
}
|
|
235
|
+
|
|
236
|
+
// internal tracking: WebChats BasicToaster renders a `role="log"` container
|
|
237
|
+
// with no accessible name. When the toaster is empty (typical idle
|
|
238
|
+
// state) screen readers traversing the page announce it as "blank".
|
|
239
|
+
// Inject an aria-label so the region has a meaningful name; the
|
|
240
|
+
// consumer can override via the new
|
|
241
|
+
// `webChatNotificationRegionAccessibilityLabel` prop. The toaster
|
|
242
|
+
// may not be rendered yet when this effect first runs, so observe
|
|
243
|
+
// for it via a MutationObserver scoped to the widget root (or, if
|
|
244
|
+
// not yet mounted, the widget container's parent) so we never fire
|
|
245
|
+
// on host-page mutations outside the widget subtree.
|
|
246
|
+
const toasterLabel = (webChatContainerProps === null || webChatContainerProps === void 0 ? void 0 : webChatContainerProps.webChatNotificationRegionAccessibilityLabel) ?? _defaultWebChatContainerStatefulProps.defaultWebChatContainerStatefulProps.webChatNotificationRegionAccessibilityLabel ?? "Chat notifications";
|
|
247
|
+
const labelToaster = root => {
|
|
248
|
+
const toaster = root.querySelector(".webchat__toaster[role='log']");
|
|
249
|
+
if (toaster && !toaster.getAttribute(_Constants.HtmlAttributeNames.ariaLabel)) {
|
|
250
|
+
toaster.setAttribute(_Constants.HtmlAttributeNames.ariaLabel, toasterLabel);
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
return false;
|
|
254
|
+
};
|
|
255
|
+
// Prefer the widget root; if it is not yet in the DOM, fall back to
|
|
256
|
+
// `document` for the *initial* lookup only, then observe the most
|
|
257
|
+
// specific available scope (widget root if present, otherwise the
|
|
258
|
+
// widget's parent / document.body as a last resort). The observer
|
|
259
|
+
// re-resolves the widget root on every callback so we tighten scope
|
|
260
|
+
// as soon as it mounts.
|
|
261
|
+
let toasterObserver;
|
|
262
|
+
const resolveScope = () => document.getElementById("ms_lcw_webchat_root") ?? document.body;
|
|
263
|
+
if (!labelToaster(resolveScope())) {
|
|
264
|
+
toasterObserver = new MutationObserver(() => {
|
|
265
|
+
const scope = resolveScope();
|
|
266
|
+
if (labelToaster(scope)) {
|
|
267
|
+
var _toasterObserver;
|
|
268
|
+
(_toasterObserver = toasterObserver) === null || _toasterObserver === void 0 ? void 0 : _toasterObserver.disconnect();
|
|
269
|
+
toasterObserver = undefined;
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
toasterObserver.observe(resolveScope(), {
|
|
273
|
+
childList: true,
|
|
274
|
+
subtree: true
|
|
275
|
+
});
|
|
276
|
+
}
|
|
235
277
|
dispatch({
|
|
236
278
|
type: _LiveChatWidgetActionType.LiveChatWidgetActionType.SET_RENDERING_MIDDLEWARE_PROPS,
|
|
237
279
|
payload: webChatContainerProps === null || webChatContainerProps === void 0 ? void 0 : webChatContainerProps.renderingMiddlewareProps
|
|
@@ -256,6 +298,10 @@ const WebChatContainerStateful = props => {
|
|
|
256
298
|
}
|
|
257
299
|
}
|
|
258
300
|
}
|
|
301
|
+
return () => {
|
|
302
|
+
var _toasterObserver2;
|
|
303
|
+
(_toasterObserver2 = toasterObserver) === null || _toasterObserver2 === void 0 ? void 0 : _toasterObserver2.disconnect();
|
|
304
|
+
};
|
|
259
305
|
}, []);
|
|
260
306
|
(0, _react2.useEffect)(() => {
|
|
261
307
|
var _props$webChatContain6, _props$webChatContain7;
|
|
@@ -496,23 +542,7 @@ const WebChatContainerStateful = props => {
|
|
|
496
542
|
height: "100%",
|
|
497
543
|
width: "100%"
|
|
498
544
|
}
|
|
499
|
-
}, shouldLoadPersistentHistoryMessages && /*#__PURE__*/_react2.default.createElement(_WebChatEventSubscribers.default, null), /*#__PURE__*/_react2.default.createElement(BasicWebChat, null))), /*#__PURE__*/_react2.default.createElement(
|
|
500
|
-
id: _Constants.HtmlIdNames.fileSentAnnouncementRegionId,
|
|
501
|
-
role: "alert",
|
|
502
|
-
"aria-live": "assertive",
|
|
503
|
-
"aria-atomic": "true",
|
|
504
|
-
style: {
|
|
505
|
-
position: "absolute",
|
|
506
|
-
width: "1px",
|
|
507
|
-
height: "1px",
|
|
508
|
-
padding: "0",
|
|
509
|
-
margin: "-1px",
|
|
510
|
-
overflow: "hidden",
|
|
511
|
-
clip: "rect(0, 0, 0, 0)",
|
|
512
|
-
whiteSpace: "nowrap",
|
|
513
|
-
border: "0"
|
|
514
|
-
}
|
|
515
|
-
}), citationPaneOpen && /*#__PURE__*/_react2.default.createElement(_CitationPaneStateful.default, {
|
|
545
|
+
}, shouldLoadPersistentHistoryMessages && /*#__PURE__*/_react2.default.createElement(_WebChatEventSubscribers.default, null), /*#__PURE__*/_react2.default.createElement(BasicWebChat, null))), citationPaneOpen && /*#__PURE__*/_react2.default.createElement(_CitationPaneStateful.default, {
|
|
516
546
|
id: ((_props$citationPanePr = props.citationPaneProps) === null || _props$citationPanePr === void 0 ? void 0 : _props$citationPanePr.id) || _Constants.HtmlAttributeNames.ocwCitationPaneClassName,
|
|
517
547
|
title: ((_props$citationPanePr2 = props.citationPaneProps) === null || _props$citationPanePr2 === void 0 ? void 0 : _props$citationPanePr2.title) || _Constants.HtmlAttributeNames.ocwCitationPaneTitle,
|
|
518
548
|
contentHtml: citationPaneText,
|
|
@@ -59,6 +59,16 @@ let DesignerChatAdapter = /*#__PURE__*/function (_MockAdapter) {
|
|
|
59
59
|
if (msg.text) {
|
|
60
60
|
if (msg.suggestedActions) {
|
|
61
61
|
(0, _chatAdapterUtils.postAgentSuggestedActionsActivity)(this.activityObserver, msg.text, msg.suggestedActions, index * 100);
|
|
62
|
+
} else if (msg.from && msg.from.role) {
|
|
63
|
+
// Route caller-supplied `from` through the shared
|
|
64
|
+
// postBotMessageActivity helper so the activity shape
|
|
65
|
+
// (id, channelData.tags, timestamp, type) matches every
|
|
66
|
+
// other designer-mode path and downstream middleware
|
|
67
|
+
// (localizedStringsBotInitialsMiddleware, telemetry,
|
|
68
|
+
// attachment plumbing) behaves identically. The override
|
|
69
|
+
// narrows only the fields the caller cares about (e.g.
|
|
70
|
+
// from.name / from.role) — base botUser fields remain.
|
|
71
|
+
(0, _chatAdapterUtils.postBotMessageActivity)(this.activityObserver, msg.text, undefined, index * 100, msg.from);
|
|
62
72
|
} else {
|
|
63
73
|
(0, _chatAdapterUtils.postBotMessageActivity)(this.activityObserver, msg.text, undefined, index * 100);
|
|
64
74
|
}
|
|
@@ -20,6 +20,7 @@ const defaultWebChatContainerStatefulProps = {
|
|
|
20
20
|
honorsTargetInHTMLLinks: false,
|
|
21
21
|
hyperlinkTextOverride: false,
|
|
22
22
|
directLine: new _mockadapter.default(),
|
|
23
|
-
adaptiveCardStyles: _defaultAdaptiveCardStyles.defaultAdaptiveCardStyles
|
|
23
|
+
adaptiveCardStyles: _defaultAdaptiveCardStyles.defaultAdaptiveCardStyles,
|
|
24
|
+
webChatNotificationRegionAccessibilityLabel: "Chat notifications"
|
|
24
25
|
};
|
|
25
26
|
exports.defaultWebChatContainerStatefulProps = defaultWebChatContainerStatefulProps;
|
|
@@ -44,11 +44,13 @@ exports.postEchoActivity = postEchoActivity;
|
|
|
44
44
|
const postBotMessageActivity = function (activityObserver, text) {
|
|
45
45
|
let tags = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : "";
|
|
46
46
|
let delay = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1000;
|
|
47
|
+
let fromOverride = arguments.length > 4 ? arguments[4] : undefined;
|
|
47
48
|
setTimeout(() => {
|
|
48
49
|
activityObserver === null || activityObserver === void 0 ? void 0 : activityObserver.next({
|
|
49
50
|
id: (0, _omnichannelChatSdk.uuidv4)(),
|
|
50
51
|
from: {
|
|
51
|
-
...botUser
|
|
52
|
+
...botUser,
|
|
53
|
+
...(fromOverride ?? {})
|
|
52
54
|
},
|
|
53
55
|
text,
|
|
54
56
|
type: "message",
|
|
@@ -32,13 +32,9 @@ function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj &&
|
|
|
32
32
|
|
|
33
33
|
const loggedSystemMessages = new Array();
|
|
34
34
|
let lastRenderedAt = 0; // Track last rendered receivedAt timestamp for deduplication
|
|
35
|
-
// Cache original (pre-render) text per activity ID to prevent exponential <br> growth.
|
|
36
|
-
// BotFramework WebChat can mutate card.activity.text between renders (e.g. on minimize/open),
|
|
37
|
-
// so we always render from the original markdown source, not the possibly-HTML-mutated text.
|
|
38
|
-
const originalSystemMessageTexts = new Map();
|
|
39
35
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
40
36
|
const handleSystemMessage = (next, args, card, renderMarkdown, systemMessageStyleProps) => {
|
|
41
|
-
var _card$activity, _card$activity$channe, _card$activity$channe2, _card$activity2, _card$activity2$chann, _card$activity3, _card$activity3$chann, _card$activity3$chann2, _card$activity4, _card$activity4$chann, _card$activity5, _card$activity5$chann, _card$nextVisibleActi, _card$nextVisibleActi2, _card$activity6, _card$activity6$chann, _card$activity7, _card$nextVisibleActi3, _card$activity8
|
|
37
|
+
var _card$activity, _card$activity$channe, _card$activity$channe2, _card$activity2, _card$activity2$chann, _card$activity3, _card$activity3$chann, _card$activity3$chann2, _card$activity4, _card$activity4$chann, _card$activity5, _card$activity5$chann, _card$nextVisibleActi, _card$nextVisibleActi2, _card$activity6, _card$activity6$chann, _card$activity7, _card$nextVisibleActi3, _card$activity8;
|
|
42
38
|
const systemMessageStyles = {
|
|
43
39
|
..._defaultSystemMessageStyles.defaultSystemMessageStyles,
|
|
44
40
|
...systemMessageStyleProps
|
|
@@ -60,14 +56,16 @@ const handleSystemMessage = (next, args, card, renderMarkdown, systemMessageStyl
|
|
|
60
56
|
if ((_card$activity5 = card.activity) !== null && _card$activity5 !== void 0 && (_card$activity5$chann = _card$activity5.channelData) !== null && _card$activity5$chann !== void 0 && _card$activity5$chann.clientmessageid && ((_card$nextVisibleActi = card.nextVisibleActivity) === null || _card$nextVisibleActi === void 0 ? void 0 : (_card$nextVisibleActi2 = _card$nextVisibleActi.channelData) === null || _card$nextVisibleActi2 === void 0 ? void 0 : _card$nextVisibleActi2.clientmessageid) === ((_card$activity6 = card.activity) === null || _card$activity6 === void 0 ? void 0 : (_card$activity6$chann = _card$activity6.channelData) === null || _card$activity6$chann === void 0 ? void 0 : _card$activity6$chann.clientmessageid) || (_card$activity7 = card.activity) !== null && _card$activity7 !== void 0 && _card$activity7.messageid && ((_card$nextVisibleActi3 = card.nextVisibleActivity) === null || _card$nextVisibleActi3 === void 0 ? void 0 : _card$nextVisibleActi3.messageid) === ((_card$activity8 = card.activity) === null || _card$activity8 === void 0 ? void 0 : _card$activity8.messageid)) {
|
|
61
57
|
return () => false;
|
|
62
58
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
|
|
60
|
+
// Render latest text on every pass. We deliberately do NOT cache the pre-render text
|
|
61
|
+
// (removed post-PR #895), because:
|
|
62
|
+
// 1. card.activity.text is no longer mutated (renderedHtml is a local variable), so
|
|
63
|
+
// exponential <br> growth from repeated markdown rendering is not possible.
|
|
64
|
+
// 2. Caching the first-seen text prevents edited system messages (same id, updated
|
|
65
|
+
// text/version) from ever re-rendering with the latest content.
|
|
68
66
|
// renderMarkdown sanitizes the HTML using DOMPurify with allowlist-based configuration
|
|
69
67
|
// See initWebChatComposer.ts for sanitization details
|
|
70
|
-
const renderedHtml = renderMarkdown(
|
|
68
|
+
const renderedHtml = renderMarkdown(card.activity.text);
|
|
71
69
|
// eslint-disable-next-line react/display-name
|
|
72
70
|
return () => /*#__PURE__*/_react.default.createElement("div", {
|
|
73
71
|
key: card.activity.id,
|
|
@@ -108,12 +106,12 @@ const createActivityMiddleware = (renderMarkdown, systemMessageStyleProps, userM
|
|
|
108
106
|
return handleSystemMessage(next, args, card, renderMarkdown, systemMessageStyleProps);
|
|
109
107
|
}
|
|
110
108
|
if (isTagIncluded(card, _Constants.Constants.persistentChatHistoryMessagePullTriggerTag)) {
|
|
111
|
-
var _card$
|
|
109
|
+
var _card$activity9, _card$activity9$chann, _card$activity9$chann2;
|
|
112
110
|
// Safety check: if this is a new chat session and flag is false, auto-correct it
|
|
113
111
|
if (!_LazyLoadActivity.LazyLoadHandler.hasMoreHistoryAvailable) {
|
|
114
112
|
_LazyLoadActivity.LazyLoadHandler.setHasMoreHistoryAvailable(true);
|
|
115
113
|
}
|
|
116
|
-
const receivedAt = card === null || card === void 0 ? void 0 : (_card$
|
|
114
|
+
const receivedAt = card === null || card === void 0 ? void 0 : (_card$activity9 = card.activity) === null || _card$activity9 === void 0 ? void 0 : (_card$activity9$chann = _card$activity9.channelData) === null || _card$activity9$chann === void 0 ? void 0 : (_card$activity9$chann2 = _card$activity9$chann.webChat) === null || _card$activity9$chann2 === void 0 ? void 0 : _card$activity9$chann2.receivedAt;
|
|
117
115
|
if (receivedAt < lastRenderedAt) {
|
|
118
116
|
card.activity = null;
|
|
119
117
|
return () => false;
|
|
@@ -174,6 +172,5 @@ exports.createActivityMiddleware = createActivityMiddleware;
|
|
|
174
172
|
const resetActivityMiddlewareCache = () => {
|
|
175
173
|
loggedSystemMessages.length = 0;
|
|
176
174
|
lastRenderedAt = 0;
|
|
177
|
-
originalSystemMessageTexts.clear();
|
|
178
175
|
};
|
|
179
176
|
exports.resetActivityMiddlewareCache = resetActivityMiddlewareCache;
|
|
@@ -32,6 +32,56 @@ 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
|
+
|
|
50
|
+
// internal tracking: non-radio inputs (Input.Text including
|
|
51
|
+
// isMultiline=true which renders as <textarea>, Input.Date,
|
|
52
|
+
// Input.Number, Input.Toggle, Input.ChoiceSet multi-select
|
|
53
|
+
// checkbox) rendered by adaptivecards can have THREE accessible-
|
|
54
|
+
// name sources resolving to the same string: a visible
|
|
55
|
+
// <label for>, aria-label, AND aria-labelledby. TalkBack walks
|
|
56
|
+
// each accessibility node independently and reads the duplicated
|
|
57
|
+
// name. When a visible <label for> exists and names the control,
|
|
58
|
+
// strip the redundant aria-label / aria-labelledby on the input
|
|
59
|
+
// so only one announceable source remains.
|
|
60
|
+
const labelledInputs = container.querySelectorAll(".ac-input-container input.ac-input[id]:not([type='radio']), .ac-input-container textarea.ac-input[id]");
|
|
61
|
+
labelledInputs.forEach(input => {
|
|
62
|
+
const id = input.id;
|
|
63
|
+
if (!id) return;
|
|
64
|
+
const visibleLabel = container.querySelector(`label[for="${CSS.escape(id)}"]:not([aria-hidden='true'])`);
|
|
65
|
+
if (!visibleLabel) return;
|
|
66
|
+
const labelText = (visibleLabel.textContent || "").trim();
|
|
67
|
+
if (!labelText) return;
|
|
68
|
+
const ariaLabel = input.getAttribute("aria-label");
|
|
69
|
+
if (ariaLabel && ariaLabel.trim() === labelText) {
|
|
70
|
+
input.removeAttribute("aria-label");
|
|
71
|
+
}
|
|
72
|
+
const labelledBy = (input.getAttribute("aria-labelledby") || "").trim();
|
|
73
|
+
if (labelledBy) {
|
|
74
|
+
const ids = labelledBy.split(/\s+/);
|
|
75
|
+
const resolved = ids.map(rid => {
|
|
76
|
+
var _document$getElementB;
|
|
77
|
+
return (((_document$getElementB = document.getElementById(rid)) === null || _document$getElementB === void 0 ? void 0 : _document$getElementB.textContent) || "").trim();
|
|
78
|
+
}).filter(Boolean).join(" ");
|
|
79
|
+
if (resolved === labelText) {
|
|
80
|
+
input.removeAttribute("aria-labelledby");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
35
85
|
// action-button-toggle / login-button-toggle: submit/login action buttons can be
|
|
36
86
|
// rendered with toggle-only ARIA that causes screen readers to announce them as
|
|
37
87
|
// switches instead of plain push buttons. Preserve Action.ToggleVisibility buttons,
|