@cognigy/webchat 3.51.0 → 3.52.0
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/OSS_LICENSES.txt +14 -14
- package/dist/webchat.esm.js +1467 -331
- package/dist/webchat.js +52 -52
- package/package.json +5 -3
package/dist/webchat.esm.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* webchat.esm.js v3.
|
|
3
|
-
* https://github.com/Cognigy/Webchat/tree/v3.
|
|
4
|
-
* https://github.com/Cognigy/Webchat/tree/v3.
|
|
2
|
+
* webchat.esm.js v3.52.0
|
|
3
|
+
* https://github.com/Cognigy/Webchat/tree/v3.52.0
|
|
4
|
+
* https://github.com/Cognigy/Webchat/tree/v3.52.0/OSS_LICENSES.txt
|
|
5
5
|
*/
|
|
6
6
|
import * as __WEBPACK_EXTERNAL_MODULE_react__ from "react";
|
|
7
7
|
import * as __WEBPACK_EXTERNAL_MODULE_react_dom_7dac9eee__ from "react-dom";
|
|
@@ -35778,21 +35778,22 @@ const Modal = ({
|
|
|
35778
35778
|
/* harmony import */ var react_transition_group__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(66492);
|
|
35779
35779
|
/* harmony import */ var _presentational_TeaserMessage__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(49888);
|
|
35780
35780
|
/* harmony import */ var _presentational_TeaserMessageAnnouncer__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(59359);
|
|
35781
|
-
/* harmony import */ var
|
|
35782
|
-
/* harmony import */ var
|
|
35783
|
-
/* harmony import */ var
|
|
35784
|
-
/* harmony import */ var
|
|
35785
|
-
/* harmony import */ var
|
|
35786
|
-
/* harmony import */ var
|
|
35787
|
-
/* harmony import */ var
|
|
35788
|
-
/* harmony import */ var
|
|
35789
|
-
/* harmony import */ var
|
|
35790
|
-
/* harmony import */ var
|
|
35791
|
-
/* harmony import */ var
|
|
35792
|
-
/* harmony import */ var
|
|
35793
|
-
/* harmony import */ var
|
|
35794
|
-
/* harmony import */ var
|
|
35795
|
-
/* harmony import */ var
|
|
35781
|
+
/* harmony import */ var _presentational_UnreadMessagesAnnouncer__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(1715);
|
|
35782
|
+
/* harmony import */ var _functional_xapp_overlay_XAppOverlay__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(12381);
|
|
35783
|
+
/* harmony import */ var _utils_sourceMapping__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(25091);
|
|
35784
|
+
/* harmony import */ var _plugins_speech_output__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(83679);
|
|
35785
|
+
/* harmony import */ var _utils_filter_out_control_commands__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(70562);
|
|
35786
|
+
/* harmony import */ var _utils_isMobile__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(40794);
|
|
35787
|
+
/* harmony import */ var _webchat_helper_handleMarkdown__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(71621);
|
|
35788
|
+
/* harmony import */ var _presentational_previous_conversations_DeleteAllConversations__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(5196);
|
|
35789
|
+
/* harmony import */ var _presentational_ScreenReaderLiveRegion__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(41426);
|
|
35790
|
+
/* harmony import */ var _presentational_StatusLiveRegion__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(63813);
|
|
35791
|
+
/* harmony import */ var _presentational_FreezeOnExit__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(97475);
|
|
35792
|
+
/* harmony import */ var _presentational_HomeScreenAnnouncer__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(55115);
|
|
35793
|
+
/* harmony import */ var _utils_ai_agent_notice__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(39191);
|
|
35794
|
+
/* harmony import */ var _utils_screen_visibility__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(48245);
|
|
35795
|
+
/* harmony import */ var classnames__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(14176);
|
|
35796
|
+
/* harmony import */ var classnames__WEBPACK_IMPORTED_MODULE_58___default = /*#__PURE__*/__webpack_require__.n(classnames__WEBPACK_IMPORTED_MODULE_58__);
|
|
35796
35797
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
35797
35798
|
|
|
35798
35799
|
|
|
@@ -35850,6 +35851,7 @@ function _extends() { return _extends = Object.assign ? Object.assign.bind() : f
|
|
|
35850
35851
|
|
|
35851
35852
|
|
|
35852
35853
|
|
|
35854
|
+
|
|
35853
35855
|
|
|
35854
35856
|
|
|
35855
35857
|
const stylisPlugins = [(0,_utils_css__WEBPACK_IMPORTED_MODULE_8__/* .isolate */ .az)("[data-cognigy-webchat-root]")];
|
|
@@ -35954,7 +35956,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
35954
35956
|
deleteConversationsModalState: false,
|
|
35955
35957
|
liveContent: {},
|
|
35956
35958
|
isMobile: false,
|
|
35957
|
-
noticeSession:
|
|
35959
|
+
noticeSession: _utils_ai_agent_notice__WEBPACK_IMPORTED_MODULE_56__/* .INITIAL_NOTICE_SESSION */ .j_
|
|
35958
35960
|
};
|
|
35959
35961
|
unreadTitleIndicatorInterval = null;
|
|
35960
35962
|
originalTitle = window.document.title;
|
|
@@ -35965,6 +35967,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
35965
35967
|
engagementMessageTimeout = null;
|
|
35966
35968
|
ratingFocusTimeout = null;
|
|
35967
35969
|
homeScreenExitFocusTimeout = null;
|
|
35970
|
+
xAppOverlayCloseFocusTimeout = null;
|
|
35968
35971
|
|
|
35969
35972
|
// Disarms the pending home-screen exit focus fallback. Also registered as
|
|
35970
35973
|
// a capture-phase document pointerdown listener while the fallback is
|
|
@@ -36002,9 +36005,9 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36002
36005
|
const secondaryColor = props?.config?.settings?.colors?.secondaryColor;
|
|
36003
36006
|
const chatInterfaceColor = props?.config?.settings?.colors?.chatInterfaceColor;
|
|
36004
36007
|
const sourceColorMapping = props?.config?.settings?.widgetSettings?.sourceColorMapping;
|
|
36005
|
-
const overrideBotMessageColor = (0,
|
|
36006
|
-
const overrideUserMessageColor = (0,
|
|
36007
|
-
const overrideAgentMessageColor = (0,
|
|
36008
|
+
const overrideBotMessageColor = (0,_utils_sourceMapping__WEBPACK_IMPORTED_MODULE_46__/* .getSourceBackgroundColor */ .N)(sourceColorMapping?.bot, props?.config?.settings?.colors);
|
|
36009
|
+
const overrideUserMessageColor = (0,_utils_sourceMapping__WEBPACK_IMPORTED_MODULE_46__/* .getSourceBackgroundColor */ .N)(sourceColorMapping?.user, props?.config?.settings?.colors);
|
|
36010
|
+
const overrideAgentMessageColor = (0,_utils_sourceMapping__WEBPACK_IMPORTED_MODULE_46__/* .getSourceBackgroundColor */ .N)(sourceColorMapping?.agent, props?.config?.settings?.colors);
|
|
36008
36011
|
const overrideBotMessageBorderColor = sourceColorMapping?.bot === "user" ? "transparent" : null;
|
|
36009
36012
|
const overrideUserMessageBorderColor = sourceColorMapping?.user === "bot" ? state.theme.black80 : null;
|
|
36010
36013
|
const overrideAgentMessageBorderColor = sourceColorMapping?.agent === "user" ? "transparent" : null;
|
|
@@ -36108,14 +36111,14 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36108
36111
|
evaluateNoticeSession(prevConversationsSnapshot) {
|
|
36109
36112
|
const id = this.props.currentSession || "";
|
|
36110
36113
|
this.setState(prev => ({
|
|
36111
|
-
noticeSession: (0,
|
|
36114
|
+
noticeSession: (0,_utils_ai_agent_notice__WEBPACK_IMPORTED_MODULE_56__/* .computeNoticeSession */ .Kr)(prev.noticeSession, id, prevConversationsSnapshot, this.props.hasRestoredPersistedHistory, this.props.willRestorePersistedConversation)
|
|
36112
36115
|
}));
|
|
36113
36116
|
}
|
|
36114
36117
|
handleNoticeIntroAnnounced = introKey => {
|
|
36115
36118
|
this.announcedNoticeKeys.add(introKey);
|
|
36116
36119
|
};
|
|
36117
36120
|
getNoticeIntro() {
|
|
36118
|
-
const text = (0,
|
|
36121
|
+
const text = (0,_utils_ai_agent_notice__WEBPACK_IMPORTED_MODULE_56__/* .getAIAgentNoticeIntroText */ .bu)({
|
|
36119
36122
|
behavior: this.props.config.settings.behavior,
|
|
36120
36123
|
showDisconnectOverlay: this.showDisconnectOverlay,
|
|
36121
36124
|
noticeSession: this.state.noticeSession,
|
|
@@ -36131,12 +36134,12 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36131
36134
|
componentDidMount() {
|
|
36132
36135
|
const defaultMessagePlugins = [];
|
|
36133
36136
|
if (this.props.ttsActive) {
|
|
36134
|
-
defaultMessagePlugins.push(
|
|
36137
|
+
defaultMessagePlugins.push(_plugins_speech_output__WEBPACK_IMPORTED_MODULE_47__/* ["default"] */ .A);
|
|
36135
36138
|
}
|
|
36136
36139
|
this.setState({
|
|
36137
36140
|
inputPlugins: [...(this.props.inputPlugins || []), _plugins_input_base__WEBPACK_IMPORTED_MODULE_11__/* ["default"] */ .A],
|
|
36138
36141
|
messagePlugins: [...(this.props.messagePlugins || []), ...defaultMessagePlugins],
|
|
36139
|
-
isMobile: (0,
|
|
36142
|
+
isMobile: (0,_utils_isMobile__WEBPACK_IMPORTED_MODULE_49__/* .isMobileViewport */ .$)()
|
|
36140
36143
|
});
|
|
36141
36144
|
this.setupIconAnimationInterval();
|
|
36142
36145
|
|
|
@@ -36208,13 +36211,53 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36208
36211
|
}
|
|
36209
36212
|
}, 450);
|
|
36210
36213
|
}
|
|
36214
|
+
|
|
36215
|
+
// Closing the xApp overlay unmounts the dialog together with whatever
|
|
36216
|
+
// was focused in it (its close button, or the frame), and the screen it
|
|
36217
|
+
// covered re-mounts — so focus cannot be "restored" to a surviving
|
|
36218
|
+
// element and would drop to document.body (SC 2.4.3). The chat screen
|
|
36219
|
+
// re-focuses itself via the message input's 200ms autofocus; when that
|
|
36220
|
+
// is disabled (`disableInputAutofocus`), or for any other view, this
|
|
36221
|
+
// late check moves focus to the re-mounted header title, mirroring the
|
|
36222
|
+
// home-screen exit fallback above. It never overrides focus that
|
|
36223
|
+
// landed anywhere else.
|
|
36224
|
+
if (prevProps.isXAppOverlayOpen && !this.props.isXAppOverlayOpen) {
|
|
36225
|
+
if (this.xAppOverlayCloseFocusTimeout) clearTimeout(this.xAppOverlayCloseFocusTimeout);
|
|
36226
|
+
this.xAppOverlayCloseFocusTimeout = setTimeout(() => {
|
|
36227
|
+
this.xAppOverlayCloseFocusTimeout = null;
|
|
36228
|
+
const webchatWindowEl = this.webchatWindowRef?.current;
|
|
36229
|
+
if (!webchatWindowEl) return;
|
|
36230
|
+
const active = document.activeElement;
|
|
36231
|
+
if (active === document.body || active === null) {
|
|
36232
|
+
(webchatWindowEl.querySelector(".webchat-header-bar .webchat-header-title") ?? (0,_utils_find_focusable__WEBPACK_IMPORTED_MODULE_23__/* ["default"] */ .A)(webchatWindowEl).firstFocusable)?.focus();
|
|
36233
|
+
}
|
|
36234
|
+
}, 450);
|
|
36235
|
+
}
|
|
36211
36236
|
if (prevProps.currentSession !== this.props.currentSession) {
|
|
36212
36237
|
this.evaluateNoticeSession(prevProps.prevConversations);
|
|
36213
36238
|
}
|
|
36239
|
+
|
|
36240
|
+
// The persisted-conversation prediction is read from storage under a
|
|
36241
|
+
// key that contains the endpoint config's URLToken, so it only
|
|
36242
|
+
// becomes meaningful once that config has loaded — after this
|
|
36243
|
+
// component has already mounted. Re-evaluating on the flip is in
|
|
36244
|
+
// time for every case the notice can observe, and normally with
|
|
36245
|
+
// room to spare: `open()` waits for the config before showing
|
|
36246
|
+
// anything, and even if it gives up waiting (~1s by default, then
|
|
36247
|
+
// it opens regardless) the flip still lands within the intro's own
|
|
36248
|
+
// 600ms delay and its effect cleanup cancels the pending timer. A
|
|
36249
|
+
// config slower than roughly the sum of the two would let the
|
|
36250
|
+
// notice announce before the prediction arrives — the remaining
|
|
36251
|
+
// gap, and the reason the notice is not gated on the connect on
|
|
36252
|
+
// top of this. No session id exists yet at this point, so current
|
|
36253
|
+
// props are the pre-change snapshot.
|
|
36254
|
+
if (prevProps.willRestorePersistedConversation !== this.props.willRestorePersistedConversation && !this.props.currentSession) {
|
|
36255
|
+
this.evaluateNoticeSession(this.props.prevConversations);
|
|
36256
|
+
}
|
|
36214
36257
|
if (prevProps.ttsActive !== this.props.ttsActive || prevProps.inputPlugins !== this.props.inputPlugins || prevProps.messagePlugins !== this.props.messagePlugins) {
|
|
36215
36258
|
const defaultMessagePlugins = [];
|
|
36216
36259
|
if (this.props.ttsActive) {
|
|
36217
|
-
defaultMessagePlugins.push(
|
|
36260
|
+
defaultMessagePlugins.push(_plugins_speech_output__WEBPACK_IMPORTED_MODULE_47__/* ["default"] */ .A);
|
|
36218
36261
|
}
|
|
36219
36262
|
this.setState({
|
|
36220
36263
|
inputPlugins: [...(this.props.inputPlugins || []), _plugins_input_base__WEBPACK_IMPORTED_MODULE_11__/* ["default"] */ .A],
|
|
@@ -36264,7 +36307,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36264
36307
|
// TODO: Add markdown rendering if there is valid markdown and renderMarkdown is enabled
|
|
36265
36308
|
// adding react-markdown is blocked by updating React to v18
|
|
36266
36309
|
if (!this.props.config.settings.widgetSettings.disableTeaserMarkdownRemoval) {
|
|
36267
|
-
lastUnseenMessageText = (0,
|
|
36310
|
+
lastUnseenMessageText = (0,_webchat_helper_handleMarkdown__WEBPACK_IMPORTED_MODULE_50__/* .removeMarkdownChars */ .$)(lastUnseenMessageText);
|
|
36268
36311
|
}
|
|
36269
36312
|
this.setState({
|
|
36270
36313
|
lastUnseenMessageText
|
|
@@ -36324,6 +36367,10 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36324
36367
|
|
|
36325
36368
|
// also removes the fallback's document pointerdown listener
|
|
36326
36369
|
this.cancelHomeScreenExitFocusFallback();
|
|
36370
|
+
if (this.xAppOverlayCloseFocusTimeout) {
|
|
36371
|
+
clearTimeout(this.xAppOverlayCloseFocusTimeout);
|
|
36372
|
+
this.xAppOverlayCloseFocusTimeout = null;
|
|
36373
|
+
}
|
|
36327
36374
|
|
|
36328
36375
|
// Teardown icon animation interval
|
|
36329
36376
|
if (this.iconAnimationIntervalHandle) {
|
|
@@ -36333,7 +36380,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36333
36380
|
window.removeEventListener("resize", this.handleResize);
|
|
36334
36381
|
}
|
|
36335
36382
|
handleResize() {
|
|
36336
|
-
const isMobile = (0,
|
|
36383
|
+
const isMobile = (0,_utils_isMobile__WEBPACK_IMPORTED_MODULE_49__/* .isMobileViewport */ .$)();
|
|
36337
36384
|
if (isMobile !== this.state.isMobile) {
|
|
36338
36385
|
this.setState({
|
|
36339
36386
|
isMobile
|
|
@@ -36467,7 +36514,10 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36467
36514
|
// behind it is inert, so this trap would agree with it only by virtue
|
|
36468
36515
|
// of getKeyboardFocusableElements skipping inert subtrees. Excluded
|
|
36469
36516
|
// explicitly instead of relying on that coupling.
|
|
36470
|
-
!this.showDisconnectOverlay
|
|
36517
|
+
!this.showDisconnectOverlay &&
|
|
36518
|
+
// The xApp overlay dialog traps focus with its own focus guards
|
|
36519
|
+
// (Tab presses inside its cross-origin frame are invisible here).
|
|
36520
|
+
!this.props.isXAppOverlayOpen) {
|
|
36471
36521
|
// Get the first and last focusable elements within the webchat window and add focus
|
|
36472
36522
|
const webchatWindowEl = this.webchatWindowRef?.current;
|
|
36473
36523
|
const {
|
|
@@ -36743,7 +36793,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36743
36793
|
// while the webchat is open, so no `open` check is needed here.
|
|
36744
36794
|
const {
|
|
36745
36795
|
showHomeScreenView
|
|
36746
|
-
} = (0,
|
|
36796
|
+
} = (0,_utils_screen_visibility__WEBPACK_IMPORTED_MODULE_57__/* .getScreenVisibility */ .p)(this.props, isInforming);
|
|
36747
36797
|
const openChatAriaLabel = () => {
|
|
36748
36798
|
if (open) return config.settings.customTranslations?.ariaLabels?.closeChat ?? "Close chat";
|
|
36749
36799
|
const openChat = config.settings.customTranslations?.ariaLabels?.openChat ?? "Open chat";
|
|
@@ -36818,7 +36868,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36818
36868
|
el.removeAttribute("inert");
|
|
36819
36869
|
}
|
|
36820
36870
|
}
|
|
36821
|
-
}, !fullscreenMessage ? this.renderRegularLayout(isInforming) : this.renderFullscreenMessageLayout()), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(
|
|
36871
|
+
}, !fullscreenMessage ? this.renderRegularLayout(isInforming) : this.renderFullscreenMessageLayout()), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_StatusLiveRegion__WEBPACK_IMPORTED_MODULE_53__/* .StatusLiveRegion */ .t, null), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_HomeScreenAnnouncer__WEBPACK_IMPORTED_MODULE_55__/* ["default"] */ .A, {
|
|
36822
36872
|
active: showHomeScreenView,
|
|
36823
36873
|
label: config.settings.customTranslations?.ariaLabels?.homeScreen ?? "Chat window home screen"
|
|
36824
36874
|
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_DisconnectOverlay__WEBPACK_IMPORTED_MODULE_20__/* ["default"] */ .A, {
|
|
@@ -36828,7 +36878,12 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36828
36878
|
isConnecting: !!this.props.connecting,
|
|
36829
36879
|
onClose: handleClose,
|
|
36830
36880
|
config: config
|
|
36831
|
-
})),
|
|
36881
|
+
})), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_UnreadMessagesAnnouncer__WEBPACK_IMPORTED_MODULE_44__/* ["default"] */ .A, {
|
|
36882
|
+
active: config.settings.unreadMessages.enableBadge || config.settings.unreadMessages.enableIndicator,
|
|
36883
|
+
count: unseenMessages.length,
|
|
36884
|
+
singularText: config.settings.customTranslations?.ariaLabels?.unreadMessageSingularText ?? "One unread message in chat.",
|
|
36885
|
+
pluralText: config.settings.customTranslations?.ariaLabels?.unreadMessagePluralText ?? "unread messages in chat."
|
|
36886
|
+
}), !disableToggleButton && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement("div", null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_TeaserMessageAnnouncer__WEBPACK_IMPORTED_MODULE_43__/* ["default"] */ .A, {
|
|
36832
36887
|
active: !!lastUnseenMessageText && !open,
|
|
36833
36888
|
text: lastUnseenMessageText,
|
|
36834
36889
|
label: config.settings.customTranslations?.ariaLabels?.newMessagePreview ?? "New message preview"
|
|
@@ -36862,7 +36917,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36862
36917
|
onClick: this.handleFabClick
|
|
36863
36918
|
}, webchatToggleProps, {
|
|
36864
36919
|
type: "button",
|
|
36865
|
-
className:
|
|
36920
|
+
className: classnames__WEBPACK_IMPORTED_MODULE_58___default()("webchat-toggle-button burst", config.settings?.layout?.iconAnimation),
|
|
36866
36921
|
id: "webchatWindowToggleButton",
|
|
36867
36922
|
"aria-label": openChatAriaLabel(),
|
|
36868
36923
|
ref: this.chatToggleButtonRef,
|
|
@@ -36871,10 +36926,17 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
36871
36926
|
}
|
|
36872
36927
|
}), open ? /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_assets_collapse_20px_svg__WEBPACK_IMPORTED_MODULE_19__/* ["default"] */ .A, null) : /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_ChatIcon__WEBPACK_IMPORTED_MODULE_18__/* ["default"] */ .A, {
|
|
36873
36928
|
config: config
|
|
36874
|
-
}), config.settings.unreadMessages.enableBadge ?
|
|
36929
|
+
}), config.settings.unreadMessages.enableBadge ?
|
|
36930
|
+
/*#__PURE__*/
|
|
36931
|
+
// The count is already in the button's
|
|
36932
|
+
// accessible name (and announced live by
|
|
36933
|
+
// UnreadMessagesAnnouncer); the badge is a
|
|
36934
|
+
// visual duplicate. aria-label is prohibited
|
|
36935
|
+
// on a generic <span>.
|
|
36936
|
+
react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_Badge__WEBPACK_IMPORTED_MODULE_21__/* ["default"] */ .A, {
|
|
36875
36937
|
_content: unseenMessages.length,
|
|
36876
36938
|
className: "webchat-unread-message-badge",
|
|
36877
|
-
"aria-
|
|
36939
|
+
"aria-hidden": "true"
|
|
36878
36940
|
}) : null))))))));
|
|
36879
36941
|
}
|
|
36880
36942
|
renderRegularLayout(isInforming) {
|
|
@@ -37000,7 +37062,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
37000
37062
|
onAcceptTerms: handleAcceptTerms,
|
|
37001
37063
|
isHomeScreenEnabled: isHomeScreenEnabled
|
|
37002
37064
|
});
|
|
37003
|
-
if (isXAppOverlayOpen) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(
|
|
37065
|
+
if (isXAppOverlayOpen) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_functional_xapp_overlay_XAppOverlay__WEBPACK_IMPORTED_MODULE_45__/* ["default"] */ .A, null);
|
|
37004
37066
|
if (showPrevConversations) return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_previous_conversations_ConversationsList__WEBPACK_IMPORTED_MODULE_33__/* .PrevConversationsList */ .K, {
|
|
37005
37067
|
conversations: this.props.prevConversations,
|
|
37006
37068
|
onSetShowPrevConversations: onSetShowPrevConversations,
|
|
@@ -37039,7 +37101,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
37039
37101
|
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement("h3", {
|
|
37040
37102
|
className: "sr-only",
|
|
37041
37103
|
id: "webchatChatHistoryHeading"
|
|
37042
|
-
}, config.settings.customTranslations?.ariaLabels?.chatHistory ?? "Chat history"), this.renderHistory()), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(
|
|
37104
|
+
}, config.settings.customTranslations?.ariaLabels?.chatHistory ?? "Chat history"), this.renderHistory()), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_ScreenReaderLiveRegion__WEBPACK_IMPORTED_MODULE_52__/* ["default"] */ .A, {
|
|
37043
37105
|
liveContent: this.state.liveContent,
|
|
37044
37106
|
intro: this.getNoticeIntro(),
|
|
37045
37107
|
onIntroAnnounced: this.handleNoticeIntroAnnounced
|
|
@@ -37070,7 +37132,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
37070
37132
|
const {
|
|
37071
37133
|
showEnabledHomeScreen,
|
|
37072
37134
|
showChatScreen
|
|
37073
|
-
} = (0,
|
|
37135
|
+
} = (0,_utils_screen_visibility__WEBPACK_IMPORTED_MODULE_57__/* .getScreenVisibility */ .p)(this.props, isInforming);
|
|
37074
37136
|
const isChatOptionsButtonVisible = config.settings.chatOptions.enabled && showChatScreen;
|
|
37075
37137
|
const hideBackButton = showChatScreen && !isHomeScreenEnabled;
|
|
37076
37138
|
const showDeleteAllConversationButton = !!((config.settings.privacyNotice.enabled && this.props.hasAcceptedTerms || !config.settings.privacyNotice.enabled) && config.settings.homeScreen.previousConversations.enableDeleteAllConversations && showPrevConversations && Object.keys(this.props.prevConversations).length);
|
|
@@ -37142,9 +37204,9 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
37142
37204
|
className: "webchat-regular-layout-content",
|
|
37143
37205
|
"aria-hidden": isRegularLayoutActiveView ? undefined : true,
|
|
37144
37206
|
inert: isRegularLayoutActiveView ? undefined : ""
|
|
37145
|
-
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(
|
|
37207
|
+
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_FreezeOnExit__WEBPACK_IMPORTED_MODULE_54__/* ["default"] */ .A, {
|
|
37146
37208
|
active: isRegularLayoutActiveView
|
|
37147
|
-
}, getRegularLayoutContent()), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(
|
|
37209
|
+
}, getRegularLayoutContent()), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_presentational_previous_conversations_DeleteAllConversations__WEBPACK_IMPORTED_MODULE_51__/* ["default"] */ .A, {
|
|
37148
37210
|
config: config,
|
|
37149
37211
|
isOpen: this.state.showDeleteAllConversationsModal,
|
|
37150
37212
|
onOpenChange: (open, confirmDelete) => {
|
|
@@ -37207,7 +37269,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
37207
37269
|
|
|
37208
37270
|
// Find privacy message and remove it from the messages list (these message types are not displayed in the chat log).
|
|
37209
37271
|
// If we do not remove, it will cause the collatation of the first user message.
|
|
37210
|
-
const messagesExcludingPrivacyMessage = (0,
|
|
37272
|
+
const messagesExcludingPrivacyMessage = (0,_utils_filter_out_control_commands__WEBPACK_IMPORTED_MODULE_48__/* ["default"] */ .A)(messages, ["acceptPrivacyPolicy"]);
|
|
37211
37273
|
|
|
37212
37274
|
// Filter messages based on progressive rendering settings
|
|
37213
37275
|
const visibleMessages = progressiveMessageRendering ? messagesExcludingPrivacyMessage.filter(message => {
|
|
@@ -37228,7 +37290,7 @@ class WebchatUI extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
37228
37290
|
variant: "body-regular",
|
|
37229
37291
|
component: "div",
|
|
37230
37292
|
className: "webchat-log-ai-agent-notice-text"
|
|
37231
|
-
}, AIAgentNoticeText ||
|
|
37293
|
+
}, AIAgentNoticeText || _utils_ai_agent_notice__WEBPACK_IMPORTED_MODULE_56__/* .DEFAULT_AI_AGENT_NOTICE_TEXT */ .S5), visibleMessages.map((message, index) => {
|
|
37232
37294
|
// Lookahead if there is a user reply that includes text or attachments
|
|
37233
37295
|
const hasReply = visibleMessages.slice(index + 1).some(message => {
|
|
37234
37296
|
const isUser = message.source === "user";
|
|
@@ -37421,6 +37483,7 @@ const MobileScrollLock = ({
|
|
|
37421
37483
|
/* harmony import */ var _webchat_helper_useSelector__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(3862);
|
|
37422
37484
|
/* harmony import */ var _presentational_Header__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(55922);
|
|
37423
37485
|
/* harmony import */ var _webchat_store_messages_message_middleware__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(20570);
|
|
37486
|
+
/* harmony import */ var _utils_find_focusable__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(41098);
|
|
37424
37487
|
|
|
37425
37488
|
|
|
37426
37489
|
|
|
@@ -37430,12 +37493,18 @@ const MobileScrollLock = ({
|
|
|
37430
37493
|
|
|
37431
37494
|
|
|
37432
37495
|
|
|
37496
|
+
|
|
37497
|
+
const TITLE_ID = "webchatXAppOverlayTitle";
|
|
37498
|
+
const FOCUS_GUARD_ATTRIBUTE = "data-xapp-overlay-focus-guard";
|
|
37433
37499
|
const Root = _emotion_styled__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A.div(() => ({
|
|
37434
37500
|
display: "flex",
|
|
37435
37501
|
flexDirection: "column",
|
|
37436
37502
|
height: "100%",
|
|
37437
37503
|
width: "100%",
|
|
37438
37504
|
animation: "fade-in-xapp .25s ease-out",
|
|
37505
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
37506
|
+
animation: "none"
|
|
37507
|
+
},
|
|
37439
37508
|
"@keyframes fade-in-xapp": {
|
|
37440
37509
|
from: {
|
|
37441
37510
|
transform: "scale(.875)",
|
|
@@ -37453,6 +37522,9 @@ const Iframe = _emotion_styled__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A.
|
|
|
37453
37522
|
height: "100%",
|
|
37454
37523
|
width: "100%",
|
|
37455
37524
|
animation: "fade-in-xapp-2 .325s ease-in",
|
|
37525
|
+
"@media (prefers-reduced-motion: reduce)": {
|
|
37526
|
+
animation: "none"
|
|
37527
|
+
},
|
|
37456
37528
|
"@keyframes fade-in-xapp-2": {
|
|
37457
37529
|
from: {
|
|
37458
37530
|
opacity: 0.87,
|
|
@@ -37466,6 +37538,21 @@ const Iframe = _emotion_styled__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A.
|
|
|
37466
37538
|
}
|
|
37467
37539
|
}
|
|
37468
37540
|
}));
|
|
37541
|
+
|
|
37542
|
+
// Visually hidden but reachable by sequential focus navigation. Not
|
|
37543
|
+
// `aria-hidden`: a focusable aria-hidden node is an axe violation
|
|
37544
|
+
// (aria-hidden-focus) — and focus never rests here anyway, the onFocus
|
|
37545
|
+
// handler redirects it immediately.
|
|
37546
|
+
const FocusGuard = _emotion_styled__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .A.div(() => ({
|
|
37547
|
+
position: "absolute",
|
|
37548
|
+
width: 1,
|
|
37549
|
+
height: 1,
|
|
37550
|
+
margin: -1,
|
|
37551
|
+
padding: 0,
|
|
37552
|
+
overflow: "hidden",
|
|
37553
|
+
clip: "rect(0, 0, 0, 0)",
|
|
37554
|
+
outline: "none"
|
|
37555
|
+
}));
|
|
37469
37556
|
const xAppOverlay = () => {
|
|
37470
37557
|
const url = (0,_webchat_helper_useSelector__WEBPACK_IMPORTED_MODULE_6__/* .useSelector */ .d)(state => state.xAppOverlay.currentUrl) || "";
|
|
37471
37558
|
const {
|
|
@@ -37475,7 +37562,17 @@ const xAppOverlay = () => {
|
|
|
37475
37562
|
sendEventOnCloseIconClick,
|
|
37476
37563
|
showCloseIcon
|
|
37477
37564
|
} = (0,_webchat_store_xapp_overlay_hooks__WEBPACK_IMPORTED_MODULE_5__/* .useOverlaySettingsByUrl */ .Z)(url);
|
|
37565
|
+
const ariaLabels = (0,_webchat_helper_useSelector__WEBPACK_IMPORTED_MODULE_6__/* .useSelector */ .d)(state => state.config.settings.customTranslations?.ariaLabels);
|
|
37566
|
+
// A whitespace-only screen title has no accessible text: it would render
|
|
37567
|
+
// an empty heading and name the dialog after it. Treat it as absent.
|
|
37568
|
+
const title = screenTitle?.trim() ?? "";
|
|
37569
|
+
// Names the dialog and its frame when the xApp has no screen title.
|
|
37570
|
+
const fallbackName = ariaLabels?.xAppOverlay ?? "Embedded app";
|
|
37571
|
+
const closeButtonAriaLabel = ariaLabels?.closeDialog ?? "Close dialog";
|
|
37478
37572
|
const dispatch = (0,react_redux__WEBPACK_IMPORTED_MODULE_0__/* .useDispatch */ .wA)();
|
|
37573
|
+
const rootRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
|
|
37574
|
+
const closeButtonRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
|
|
37575
|
+
const iframeRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
|
|
37479
37576
|
const handleClose = () => {
|
|
37480
37577
|
if (closeOnSubmit) {
|
|
37481
37578
|
dispatch((0,_webchat_store_xapp_overlay_slice__WEBPACK_IMPORTED_MODULE_4__/* .closeOverlay */ .L9)());
|
|
@@ -37492,7 +37589,20 @@ const xAppOverlay = () => {
|
|
|
37492
37589
|
dispatch((0,_webchat_store_xapp_overlay_slice__WEBPACK_IMPORTED_MODULE_4__/* .closeOverlay */ .L9)());
|
|
37493
37590
|
};
|
|
37494
37591
|
const handleSubmit = event => {
|
|
37495
|
-
|
|
37592
|
+
// WCH-SI10-004: compare canonical origins, not raw URL strings.
|
|
37593
|
+
// The previous url.startsWith(event.origin) check was semantically backwards —
|
|
37594
|
+
// a domain that is a string-prefix of url (e.g. "https://xapp.cognigy.a" for
|
|
37595
|
+
// "https://xapp.cognigy.ai/form") could pass the check despite being a different
|
|
37596
|
+
// origin. new URL(url).origin extracts the canonical scheme+host+port for an
|
|
37597
|
+
// exact match, which is the correct cross-origin security boundary.
|
|
37598
|
+
let urlOrigin;
|
|
37599
|
+
try {
|
|
37600
|
+
urlOrigin = new URL(url).origin;
|
|
37601
|
+
} catch {
|
|
37602
|
+
// url is empty or not a valid absolute URL — reject all postMessages.
|
|
37603
|
+
return;
|
|
37604
|
+
}
|
|
37605
|
+
if (urlOrigin !== event.origin) {
|
|
37496
37606
|
return;
|
|
37497
37607
|
}
|
|
37498
37608
|
if (event.data.type !== "x-app-submit") {
|
|
@@ -37518,8 +37628,12 @@ const xAppOverlay = () => {
|
|
|
37518
37628
|
handleClose();
|
|
37519
37629
|
};
|
|
37520
37630
|
|
|
37521
|
-
// We
|
|
37631
|
+
// We receive a MessageEvent from the xApp iframe when submission happens.
|
|
37522
37632
|
// https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent
|
|
37633
|
+
// url and feedbackMessage are included in deps so the handler is re-registered
|
|
37634
|
+
// whenever either changes — both affect the handleSubmit closure and can change
|
|
37635
|
+
// independently (e.g. a subsequent RECEIVE_MESSAGE can update overlay settings
|
|
37636
|
+
// for the same URL, which would change feedbackMessage without changing url).
|
|
37523
37637
|
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => {
|
|
37524
37638
|
function unsubscribe() {
|
|
37525
37639
|
window.removeEventListener("message", handleSubmit);
|
|
@@ -37528,19 +37642,88 @@ const xAppOverlay = () => {
|
|
|
37528
37642
|
return () => {
|
|
37529
37643
|
unsubscribe();
|
|
37530
37644
|
};
|
|
37531
|
-
}, [closeOnSubmit]);
|
|
37532
|
-
|
|
37645
|
+
}, [closeOnSubmit, url, feedbackMessage]);
|
|
37646
|
+
|
|
37647
|
+
// APG modal dialog: move focus into the dialog once on open — to the
|
|
37648
|
+
// close button (the first control, as Modal does), else the frame itself.
|
|
37649
|
+
// Deliberately NOT the title: the dialog is named by that heading, so
|
|
37650
|
+
// focusing it makes NVDA read the same text as dialog name, as focused
|
|
37651
|
+
// heading, and again on its browse-mode pass. Deferred like
|
|
37652
|
+
// Modal.initialFocusRef: focusing in the same task that inserted the
|
|
37653
|
+
// subtree is not announced by VoiceOver (CGY-3274).
|
|
37654
|
+
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => {
|
|
37655
|
+
const focusTimer = window.setTimeout(() => {
|
|
37656
|
+
const root = rootRef.current;
|
|
37657
|
+
if (!root || root.closest("[inert]")) return;
|
|
37658
|
+
(closeButtonRef.current ?? iframeRef.current)?.focus();
|
|
37659
|
+
}, 200);
|
|
37660
|
+
return () => window.clearTimeout(focusTimer);
|
|
37661
|
+
}, []);
|
|
37662
|
+
|
|
37663
|
+
// Escape dismisses the dialog exactly like the close icon (same side
|
|
37664
|
+
// effects), and only when the xApp is dismissable — without a close icon
|
|
37665
|
+
// the overlay is a blocking step that ends on submission inside the app.
|
|
37666
|
+
// Keys pressed inside the cross-origin frame never reach this document.
|
|
37667
|
+
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => {
|
|
37668
|
+
if (!showCloseIcon) return;
|
|
37669
|
+
const handleKeyDown = event => {
|
|
37670
|
+
if (event.key !== "Escape") return;
|
|
37671
|
+
// Another modal surface (e.g. the disconnect overlay) is stacked on
|
|
37672
|
+
// top — leave Esc to that surface.
|
|
37673
|
+
if (rootRef.current?.closest("[inert]")) return;
|
|
37674
|
+
handleCloseIconClick();
|
|
37675
|
+
};
|
|
37676
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
37677
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
37678
|
+
}, [showCloseIcon, sendEventOnCloseIconClick]);
|
|
37679
|
+
|
|
37680
|
+
// Focus trap. A keydown-based trap (Modal) cannot see Tab presses inside
|
|
37681
|
+
// the cross-origin frame, so the dialog is bracketed by two focus guards
|
|
37682
|
+
// instead: tabbing out of the frame lands on a guard, which sends focus
|
|
37683
|
+
// back to the opposite edge of the dialog. Arriving at a guard from
|
|
37684
|
+
// outside the dialog (e.g. Tab from the host page) enters at the nearest
|
|
37685
|
+
// edge instead of wrapping. `relatedTarget` is null when focus comes from
|
|
37686
|
+
// the frame's document, which is the wrap case.
|
|
37687
|
+
const handleFocusGuard = edge => event => {
|
|
37688
|
+
const root = rootRef.current;
|
|
37689
|
+
if (!root) return;
|
|
37690
|
+
const focusable = (0,_utils_find_focusable__WEBPACK_IMPORTED_MODULE_9__/* ["default"] */ .A)(root).focusable.filter(el => !el.hasAttribute(FOCUS_GUARD_ATTRIBUTE));
|
|
37691
|
+
const first = focusable[0];
|
|
37692
|
+
const last = focusable[focusable.length - 1];
|
|
37693
|
+
const relatedTarget = event.relatedTarget;
|
|
37694
|
+
const cameFromInside = !relatedTarget || root.contains(relatedTarget);
|
|
37695
|
+
const target = edge === "start" ? cameFromInside ? last : first : cameFromInside ? first : last;
|
|
37696
|
+
target?.focus();
|
|
37697
|
+
};
|
|
37698
|
+
const showHeader = title || showCloseIcon;
|
|
37533
37699
|
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["default"].createElement(Root, {
|
|
37534
|
-
|
|
37700
|
+
ref: rootRef,
|
|
37535
37701
|
role: "dialog",
|
|
37536
|
-
"aria-modal": "true"
|
|
37537
|
-
|
|
37538
|
-
title:
|
|
37702
|
+
"aria-modal": "true",
|
|
37703
|
+
"aria-labelledby": title ? TITLE_ID : undefined,
|
|
37704
|
+
"aria-label": title ? undefined : fallbackName,
|
|
37705
|
+
className: "webchat-xapp-overlay-root",
|
|
37706
|
+
"data-xapp-overlay": true
|
|
37707
|
+
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["default"].createElement(FocusGuard, {
|
|
37708
|
+
tabIndex: 0,
|
|
37709
|
+
onFocus: handleFocusGuard("start"),
|
|
37710
|
+
[FOCUS_GUARD_ATTRIBUTE]: "start"
|
|
37711
|
+
}), showHeader && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["default"].createElement(_presentational_Header__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .A, {
|
|
37712
|
+
title: title,
|
|
37713
|
+
titleId: TITLE_ID,
|
|
37539
37714
|
hideBackButton: true,
|
|
37540
|
-
onClose: showCloseIcon ? handleCloseIconClick : undefined
|
|
37715
|
+
onClose: showCloseIcon ? handleCloseIconClick : undefined,
|
|
37716
|
+
closeButtonAriaLabel: closeButtonAriaLabel,
|
|
37717
|
+
closeButtonRef: closeButtonRef
|
|
37541
37718
|
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["default"].createElement(Iframe, {
|
|
37719
|
+
ref: iframeRef,
|
|
37542
37720
|
src: url,
|
|
37543
|
-
|
|
37721
|
+
title: title || fallbackName,
|
|
37722
|
+
allow: " accelerometer; ambient-light-sensor; autoplay; battery; bluetooth; camera; cross-origin-isolated; display-capture; document-domain; encrypted-media; execution-while-not-rendered; execution-while-out-of-viewport; fullscreen; gamepad; geolocation; gyroscope; hid; idle-detection; interest-cohort; local-fonts; magnetometer; microphone; midi; otp-credentials; payment; picture-in-picture; publickey-credentials-get; screen-wake-lock; serial; speaker-selection; usb; web-share; xr-spatial-tracking"
|
|
37723
|
+
}), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_1__["default"].createElement(FocusGuard, {
|
|
37724
|
+
tabIndex: 0,
|
|
37725
|
+
onFocus: handleFocusGuard("end"),
|
|
37726
|
+
[FOCUS_GUARD_ATTRIBUTE]: "end"
|
|
37544
37727
|
}));
|
|
37545
37728
|
};
|
|
37546
37729
|
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (xAppOverlay);
|
|
@@ -38165,6 +38348,10 @@ const InputPluginRenderer = ({
|
|
|
38165
38348
|
/* harmony import */ var react_responsive__WEBPACK_IMPORTED_MODULE_9___default = /*#__PURE__*/__webpack_require__.n(react_responsive__WEBPACK_IMPORTED_MODULE_9__);
|
|
38166
38349
|
/* harmony import */ var _menu_PersistentMenu__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(71117);
|
|
38167
38350
|
/* harmony import */ var _FloatingLabel__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(93277);
|
|
38351
|
+
/* harmony import */ var _presentational_Notifications__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(82833);
|
|
38352
|
+
/* harmony import */ var _utils_speech_recognition__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(44120);
|
|
38353
|
+
|
|
38354
|
+
|
|
38168
38355
|
|
|
38169
38356
|
|
|
38170
38357
|
|
|
@@ -38314,46 +38501,66 @@ const InputContainer = _emotion_styled__WEBPACK_IMPORTED_MODULE_1__/* ["default"
|
|
|
38314
38501
|
const Label = (0,_emotion_styled__WEBPACK_IMPORTED_MODULE_1__/* ["default"] */ .A)(_FloatingLabel__WEBPACK_IMPORTED_MODULE_11__/* ["default"] */ .A)({
|
|
38315
38502
|
padding: "8px 2px"
|
|
38316
38503
|
});
|
|
38317
|
-
const getSpeechRecognition = () => {
|
|
38318
|
-
try {
|
|
38319
|
-
return new SpeechRecognition();
|
|
38320
|
-
} catch (e) {}
|
|
38321
|
-
try {
|
|
38322
|
-
return new webkitSpeechRecognition();
|
|
38323
|
-
} catch (e) {}
|
|
38324
|
-
return null;
|
|
38325
|
-
};
|
|
38326
38504
|
const combineStrings = (str1, str2) => {
|
|
38327
38505
|
if (!str1) return str2;
|
|
38328
38506
|
if (!str2) return str1;
|
|
38329
38507
|
return str1 + " " + str2;
|
|
38330
38508
|
};
|
|
38331
|
-
let webchatSpeechTimeoutRef = null;
|
|
38332
38509
|
class BaseInput extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureComponent {
|
|
38333
38510
|
constructor(props) {
|
|
38334
38511
|
super(props);
|
|
38335
|
-
const speechRecognition = getSpeechRecognition();
|
|
38336
|
-
if (speechRecognition) {
|
|
38337
|
-
speechRecognition.continuous = true;
|
|
38338
|
-
speechRecognition.interimResults = true;
|
|
38339
|
-
speechRecognition.onresult = this.handleSpeechResult;
|
|
38340
|
-
if (props.config.settings.widgetSettings.STTLanguage) {
|
|
38341
|
-
speechRecognition.lang = props.config.settings.widgetSettings.STTLanguage;
|
|
38342
|
-
}
|
|
38343
|
-
}
|
|
38344
38512
|
this.state = {
|
|
38345
38513
|
text: "",
|
|
38346
|
-
speechRecognition,
|
|
38347
38514
|
speechResult: "",
|
|
38348
|
-
isFinalResult: false,
|
|
38349
38515
|
isMenuOpen: false,
|
|
38350
38516
|
selectionStart: 0,
|
|
38351
38517
|
selectionEnd: 0
|
|
38352
38518
|
};
|
|
38519
|
+
this.configureSpeechRecognition();
|
|
38353
38520
|
}
|
|
38354
38521
|
inputRef = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createRef();
|
|
38355
38522
|
menuRef = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createRef();
|
|
38356
38523
|
fileInputRef = /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createRef();
|
|
38524
|
+
|
|
38525
|
+
/**
|
|
38526
|
+
* The live recognizer, or null where the browser has no Web Speech API.
|
|
38527
|
+
*
|
|
38528
|
+
* Deliberately NOT component state: it is a mutable host object whose
|
|
38529
|
+
* identity never changes, and keeping it in state invited
|
|
38530
|
+
* `setState({ speechRecognition: { ...recognizer, lang } })` to apply the
|
|
38531
|
+
* language. Native properties live on the prototype, so that spread
|
|
38532
|
+
* copied nothing but `lang` — `start`, `stop` and every event handler
|
|
38533
|
+
* disappeared, and the next click threw "start is not a function"
|
|
38534
|
+
* (CGY-37417).
|
|
38535
|
+
*/
|
|
38536
|
+
speechRecognition = (0,_utils_speech_recognition__WEBPACK_IMPORTED_MODULE_13__/* .createSpeechRecognition */ .oC)();
|
|
38537
|
+
|
|
38538
|
+
/** The warm-up or idle stop timer, whichever phase recognition is in. */
|
|
38539
|
+
speechTimeout = null;
|
|
38540
|
+
|
|
38541
|
+
/** Whether the engine has produced any transcript since `start()`. */
|
|
38542
|
+
hasTranscript = false;
|
|
38543
|
+
|
|
38544
|
+
/**
|
|
38545
|
+
* Set while we are the ones ending recognition, so `onend` can tell an
|
|
38546
|
+
* engine-initiated end (which has to reconcile the UI) from our own.
|
|
38547
|
+
*/
|
|
38548
|
+
endingDeliberately = false;
|
|
38549
|
+
|
|
38550
|
+
/**
|
|
38551
|
+
* Set on the submit path, where a transcript arriving afterwards belongs
|
|
38552
|
+
* to the message that was already sent. `abort()` should prevent one, but
|
|
38553
|
+
* a result already dispatched can still be delivered, so the handler
|
|
38554
|
+
* checks this too.
|
|
38555
|
+
*/
|
|
38556
|
+
discardResults = false;
|
|
38557
|
+
|
|
38558
|
+
/**
|
|
38559
|
+
* A start() the engine rejected because a previous stop() had not reached
|
|
38560
|
+
* `onend` yet. Retried once from `onend`, so a stop-then-start inside that
|
|
38561
|
+
* window isn't silently dropped.
|
|
38562
|
+
*/
|
|
38563
|
+
restartPending = false;
|
|
38357
38564
|
componentDidMount() {
|
|
38358
38565
|
// Global handler to modify the input text
|
|
38359
38566
|
window.WebChatInputTextCallback = text => {
|
|
@@ -38362,98 +38569,311 @@ class BaseInput extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38362
38569
|
});
|
|
38363
38570
|
};
|
|
38364
38571
|
setTimeout(() => {
|
|
38572
|
+
// Don't pull focus away from a modal dialog the widget has opened in
|
|
38573
|
+
// the meantime (date picker, xApp overlay, Modal). Scoped to the
|
|
38574
|
+
// widget root: a dialog elsewhere on the host page (cookie banner, a
|
|
38575
|
+
// modal that embeds the launcher) must not suppress the autofocus.
|
|
38576
|
+
const root = this.inputRef.current?.closest("[data-cognigy-webchat-root]");
|
|
38577
|
+
const hasOpenDialog = !!root?.querySelector('[role="dialog"][aria-modal="true"], dialog[open]');
|
|
38578
|
+
if (hasOpenDialog) return;
|
|
38365
38579
|
if (!this.props.config.settings.widgetSettings.disableInputAutofocus) {
|
|
38366
38580
|
this.inputRef.current?.focus?.();
|
|
38367
38581
|
}
|
|
38368
38582
|
}, 200);
|
|
38369
38583
|
}
|
|
38370
38584
|
componentDidUpdate() {
|
|
38585
|
+
this.applySTTLanguage();
|
|
38586
|
+
}
|
|
38587
|
+
componentWillUnmount() {
|
|
38588
|
+
this.clearSpeechTimeout();
|
|
38589
|
+
|
|
38590
|
+
// The input unmounts on every screen change (back to the home screen,
|
|
38591
|
+
// into the previous-conversations list). Without releasing the engine
|
|
38592
|
+
// here the microphone stays open — browser recording indicator and
|
|
38593
|
+
// all — with nothing left to receive the transcript.
|
|
38594
|
+
if (this.speechRecognition) {
|
|
38595
|
+
this.endingDeliberately = true;
|
|
38596
|
+
this.speechRecognition.onresult = null;
|
|
38597
|
+
this.speechRecognition.onerror = null;
|
|
38598
|
+
this.speechRecognition.onend = null;
|
|
38599
|
+
try {
|
|
38600
|
+
// abort(), not stop(): a final transcript can no longer be
|
|
38601
|
+
// delivered anywhere, so don't wait for one.
|
|
38602
|
+
this.speechRecognition.abort();
|
|
38603
|
+
} catch {
|
|
38604
|
+
// Never started, or already finished.
|
|
38605
|
+
}
|
|
38606
|
+
}
|
|
38607
|
+
|
|
38608
|
+
// `sttActive` lives in the store, which outlives this component —
|
|
38609
|
+
// leaving it set would show an active mic button on the next visit to
|
|
38610
|
+
// the chat screen with nothing actually listening.
|
|
38611
|
+
if (this.props.sttActive) this.props.onSetSTTActive(false);
|
|
38612
|
+
}
|
|
38613
|
+
configureSpeechRecognition() {
|
|
38614
|
+
const recognition = this.speechRecognition;
|
|
38615
|
+
if (!recognition) return;
|
|
38616
|
+
recognition.continuous = true;
|
|
38617
|
+
recognition.interimResults = true;
|
|
38618
|
+
recognition.onresult = this.handleSpeechResult;
|
|
38619
|
+
recognition.onerror = this.handleSpeechError;
|
|
38620
|
+
recognition.onend = this.handleSpeechEnd;
|
|
38621
|
+
this.applySTTLanguage();
|
|
38622
|
+
}
|
|
38623
|
+
|
|
38624
|
+
/**
|
|
38625
|
+
* Applies the configured STT language to the live recognizer.
|
|
38626
|
+
*
|
|
38627
|
+
* Runs on every update because the endpoint config is fetched
|
|
38628
|
+
* asynchronously, so `STTLanguage` is typically absent when this
|
|
38629
|
+
* component first mounts. While it is unset we leave the engine's own
|
|
38630
|
+
* default (which follows the page's `lang`) rather than forcing a locale,
|
|
38631
|
+
* so an unconfigured German page keeps transcribing German.
|
|
38632
|
+
*/
|
|
38633
|
+
applySTTLanguage() {
|
|
38371
38634
|
const sttLanguage = this.props.config.settings.widgetSettings.STTLanguage;
|
|
38372
|
-
if (this.
|
|
38373
|
-
this.
|
|
38374
|
-
speechRecognition: {
|
|
38375
|
-
...this.state.speechRecognition,
|
|
38376
|
-
lang: sttLanguage
|
|
38377
|
-
}
|
|
38378
|
-
});
|
|
38635
|
+
if (this.speechRecognition && sttLanguage && this.speechRecognition.lang !== sttLanguage) {
|
|
38636
|
+
this.speechRecognition.lang = sttLanguage;
|
|
38379
38637
|
}
|
|
38380
38638
|
}
|
|
38381
|
-
|
|
38382
|
-
if (
|
|
38383
|
-
clearTimeout(
|
|
38639
|
+
clearSpeechTimeout() {
|
|
38640
|
+
if (this.speechTimeout) {
|
|
38641
|
+
clearTimeout(this.speechTimeout);
|
|
38642
|
+
this.speechTimeout = null;
|
|
38384
38643
|
}
|
|
38385
38644
|
}
|
|
38645
|
+
|
|
38646
|
+
/**
|
|
38647
|
+
* Arms the single stop timer. `delay` is the warm-up budget before the
|
|
38648
|
+
* first transcript and the idle budget after it — the two differ by an
|
|
38649
|
+
* order of magnitude, see the constants' rationale.
|
|
38650
|
+
*/
|
|
38651
|
+
armSpeechTimeout(delay) {
|
|
38652
|
+
this.clearSpeechTimeout();
|
|
38653
|
+
this.speechTimeout = setTimeout(() => {
|
|
38654
|
+
this.speechTimeout = null;
|
|
38655
|
+
const recognizedNothing = !this.hasTranscript;
|
|
38656
|
+
|
|
38657
|
+
// A dictation that produced text ended normally, and the user's
|
|
38658
|
+
// next move is to edit or send it, so focus goes to the input.
|
|
38659
|
+
// Having heard nothing is a failure like any other: leave focus
|
|
38660
|
+
// wherever the user put it (SC 3.2.1).
|
|
38661
|
+
this.endSpeech({
|
|
38662
|
+
restoreFocus: !recognizedNothing
|
|
38663
|
+
});
|
|
38664
|
+
|
|
38665
|
+
// The whole warm-up budget spent without a single word: the
|
|
38666
|
+
// engine never reached its speech service, or the user never
|
|
38667
|
+
// spoke. Either way, say so — this path used to stop the
|
|
38668
|
+
// microphone silently (CGY-37417).
|
|
38669
|
+
if (recognizedNothing) this.notifySpeechFailure("no-transcript");
|
|
38670
|
+
}, delay);
|
|
38671
|
+
}
|
|
38672
|
+
notifySpeechFailure(failure) {
|
|
38673
|
+
(0,_presentational_Notifications__WEBPACK_IMPORTED_MODULE_12__/* .createErrorNotification */ .gi)((0,_utils_speech_recognition__WEBPACK_IMPORTED_MODULE_13__/* .getSpeechRecognitionErrorMessage */ .JO)(failure, this.props.config.settings.customTranslations));
|
|
38674
|
+
}
|
|
38386
38675
|
handleSpeechResult = e => {
|
|
38676
|
+
// A result that raced the abort() on the submit path. The message it
|
|
38677
|
+
// belongs to has already been sent and `text` cleared, so committing
|
|
38678
|
+
// it would refill the input with what the user just sent — and their
|
|
38679
|
+
// next Enter would send it a second time.
|
|
38680
|
+
if (this.discardResults) return;
|
|
38387
38681
|
const result = e.results[e.resultIndex];
|
|
38388
|
-
const {
|
|
38389
|
-
isFinal
|
|
38390
|
-
} = result;
|
|
38391
38682
|
const {
|
|
38392
38683
|
transcript
|
|
38393
38684
|
} = result[0];
|
|
38394
38685
|
|
|
38395
|
-
//
|
|
38396
|
-
|
|
38397
|
-
|
|
38686
|
+
// Edge emits placeholder results — `isFinal` with an empty transcript
|
|
38687
|
+
// — when speech starts and again on stop(); Chrome never does.
|
|
38688
|
+
// Committing one would wipe the interim transcript on screen, and
|
|
38689
|
+
// counting one as "the engine is producing output" would start the
|
|
38690
|
+
// short idle countdown seconds before the first real word arrives.
|
|
38691
|
+
if (!transcript) return;
|
|
38692
|
+
this.hasTranscript = true;
|
|
38693
|
+
|
|
38694
|
+
// Only while listening: stop() lets one last result land afterwards,
|
|
38695
|
+
// and re-arming then would stop recognition and steal focus back to
|
|
38696
|
+
// the input seconds after the user had moved on.
|
|
38697
|
+
if (this.props.sttActive) this.armSpeechTimeout(_utils_speech_recognition__WEBPACK_IMPORTED_MODULE_13__/* .TRANSCRIPT_IDLE_TIMEOUT_MS */ .r0);
|
|
38698
|
+
|
|
38699
|
+
// Interim results are shown next to the typed text but not committed;
|
|
38700
|
+
// only a final result becomes part of the message.
|
|
38701
|
+
if (result.isFinal) {
|
|
38702
|
+
this.setState(({
|
|
38703
|
+
text
|
|
38704
|
+
}) => ({
|
|
38705
|
+
speechResult: "",
|
|
38706
|
+
text: combineStrings(text, transcript)
|
|
38707
|
+
}));
|
|
38708
|
+
return;
|
|
38398
38709
|
}
|
|
38399
|
-
webchatSpeechTimeoutRef = setTimeout(() => {
|
|
38400
|
-
this.handleCancelSpeech();
|
|
38401
|
-
}, 3000);
|
|
38402
38710
|
this.setState({
|
|
38403
|
-
speechResult: transcript
|
|
38404
|
-
|
|
38711
|
+
speechResult: transcript
|
|
38712
|
+
});
|
|
38713
|
+
};
|
|
38714
|
+
handleSpeechError = event => {
|
|
38715
|
+
// `aborted` is just the engine acknowledging our own stop()/abort().
|
|
38716
|
+
if (event.error === "aborted") return;
|
|
38717
|
+
if (event.error === "language-not-supported" || event.error === "bad-grammar") {
|
|
38718
|
+
// Misconfiguration the end user cannot act on: they get the
|
|
38719
|
+
// generic message, the operator gets the detail.
|
|
38720
|
+
console.warn(`[cognigy-webchat] speech recognition rejected the configured STT language "${this.speechRecognition?.lang}" (${event.error})`);
|
|
38721
|
+
}
|
|
38722
|
+
|
|
38723
|
+
// A failure is not a user-initiated stop: leave focus alone. The
|
|
38724
|
+
// button's `aria-pressed` change and the notification convey it.
|
|
38725
|
+
this.endSpeech({
|
|
38726
|
+
restoreFocus: false
|
|
38727
|
+
});
|
|
38728
|
+
this.notifySpeechFailure(event.error);
|
|
38729
|
+
};
|
|
38730
|
+
handleSpeechEnd = () => {
|
|
38731
|
+
const wasDeliberate = this.endingDeliberately;
|
|
38732
|
+
this.endingDeliberately = false;
|
|
38733
|
+
|
|
38734
|
+
// The engine is finished, so anything still held as an interim never
|
|
38735
|
+
// got a final result. Commit it rather than let the user's visible
|
|
38736
|
+
// words vanish. A real final would already have moved it into `text`
|
|
38737
|
+
// and emptied this, so there is nothing to duplicate.
|
|
38738
|
+
this.commitPendingTranscript();
|
|
38739
|
+
|
|
38740
|
+
// The user pressed the button again while the engine was still
|
|
38741
|
+
// winding down from the previous stop, so `start()` was rejected.
|
|
38742
|
+
// Honour it now that the engine has actually ended — once, because
|
|
38743
|
+
// only a rejected start sets the flag.
|
|
38744
|
+
if (this.restartPending) {
|
|
38745
|
+
this.restartPending = false;
|
|
38746
|
+
this.startSpeech();
|
|
38747
|
+
return;
|
|
38748
|
+
}
|
|
38749
|
+
if (wasDeliberate) return;
|
|
38750
|
+
|
|
38751
|
+
// The engine ended on its own — its service dropped the connection,
|
|
38752
|
+
// or it decided the utterance was over despite `continuous`.
|
|
38753
|
+
// Reconcile, so the button can never claim to be listening when
|
|
38754
|
+
// nothing is (SC 4.1.2).
|
|
38755
|
+
if (!this.props.sttActive) return;
|
|
38756
|
+
const recognizedNothing = !this.hasTranscript;
|
|
38757
|
+
this.endSpeech({
|
|
38758
|
+
restoreFocus: false
|
|
38405
38759
|
});
|
|
38406
38760
|
|
|
38407
|
-
//
|
|
38408
|
-
|
|
38409
|
-
|
|
38410
|
-
|
|
38411
|
-
text: combineStrings(this.state.text, transcript)
|
|
38412
|
-
});
|
|
38413
|
-
}
|
|
38761
|
+
// Ending with nothing to show for it is exactly the silent failure
|
|
38762
|
+
// this change exists to remove, so say so. `onerror` cannot reach
|
|
38763
|
+
// here — it ends recognition itself, which marks the end deliberate.
|
|
38764
|
+
if (recognizedNothing) this.notifySpeechFailure("no-transcript");
|
|
38414
38765
|
};
|
|
38766
|
+
|
|
38767
|
+
/**
|
|
38768
|
+
* Moves an un-finalized transcript into the message text. No-op when the
|
|
38769
|
+
* engine already finalized it (or when the submit path discarded it).
|
|
38770
|
+
*/
|
|
38771
|
+
commitPendingTranscript() {
|
|
38772
|
+
this.setState(({
|
|
38773
|
+
text,
|
|
38774
|
+
speechResult
|
|
38775
|
+
}) => speechResult ? {
|
|
38776
|
+
text: combineStrings(text, speechResult),
|
|
38777
|
+
speechResult: ""
|
|
38778
|
+
} : null);
|
|
38779
|
+
}
|
|
38780
|
+
|
|
38781
|
+
/** The user pressed the microphone button to stop dictating. */
|
|
38415
38782
|
handleCancelSpeech = () => {
|
|
38416
|
-
|
|
38417
|
-
|
|
38418
|
-
}
|
|
38419
|
-
|
|
38420
|
-
|
|
38421
|
-
|
|
38783
|
+
this.endSpeech({
|
|
38784
|
+
restoreFocus: true
|
|
38785
|
+
});
|
|
38786
|
+
};
|
|
38787
|
+
|
|
38788
|
+
/**
|
|
38789
|
+
* Ends recognition and reconciles the UI.
|
|
38790
|
+
*
|
|
38791
|
+
* `discardPending` is for the submit path only: the message has been sent
|
|
38792
|
+
* and `text` cleared, so a transcript arriving afterwards has to be
|
|
38793
|
+
* thrown away rather than committed. Everywhere else a late final is
|
|
38794
|
+
* welcome — it is the engine's better-punctuated version of the interim
|
|
38795
|
+
* that was on screen, and dropping it would lose the user's words.
|
|
38796
|
+
*
|
|
38797
|
+
* `restoreFocus` is true only when the user themselves stopped: they are
|
|
38798
|
+
* likely to carry on typing. On an error or a timeout the stop was not
|
|
38799
|
+
* requested, and moving focus then would take it from wherever the user
|
|
38800
|
+
* had put it (SC 3.2.1).
|
|
38801
|
+
*/
|
|
38802
|
+
endSpeech({
|
|
38803
|
+
discardPending = false,
|
|
38804
|
+
restoreFocus
|
|
38805
|
+
}) {
|
|
38806
|
+
this.clearSpeechTimeout();
|
|
38807
|
+
this.endingDeliberately = true;
|
|
38808
|
+
this.discardResults = discardPending;
|
|
38809
|
+
try {
|
|
38810
|
+
if (discardPending) {
|
|
38811
|
+
// abort() asks the engine not to return a result at all,
|
|
38812
|
+
// unlike stop(), which finalizes what it heard.
|
|
38813
|
+
this.speechRecognition?.abort();
|
|
38814
|
+
} else {
|
|
38815
|
+
this.speechRecognition?.stop();
|
|
38816
|
+
}
|
|
38817
|
+
} catch {
|
|
38818
|
+
// Not currently running.
|
|
38422
38819
|
}
|
|
38423
38820
|
this.props.onSetSTTActive(false);
|
|
38424
|
-
|
|
38425
|
-
|
|
38426
|
-
|
|
38821
|
+
|
|
38822
|
+
// The interim transcript deliberately stays on screen. The engine may
|
|
38823
|
+
// still upgrade it to a final; if it never does, `onend` commits it.
|
|
38824
|
+
// Clearing it here would lose the words the user watched appear —
|
|
38825
|
+
// Edge answers stop() with an empty placeholder final, which carries
|
|
38826
|
+
// no text to put back. Only the submit path clears it, because that
|
|
38827
|
+
// text is already on its way.
|
|
38828
|
+
if (discardPending) this.setState({
|
|
38829
|
+
speechResult: ""
|
|
38427
38830
|
});
|
|
38428
|
-
if (this.inputRef.current) {
|
|
38831
|
+
if (restoreFocus && this.inputRef.current) {
|
|
38429
38832
|
this.inputRef.current.focus();
|
|
38430
38833
|
}
|
|
38431
|
-
}
|
|
38834
|
+
}
|
|
38432
38835
|
isSTTSupported() {
|
|
38433
|
-
return !!this.
|
|
38836
|
+
return !!this.speechRecognition;
|
|
38434
38837
|
}
|
|
38435
38838
|
toggleSTT = e => {
|
|
38436
38839
|
e.preventDefault();
|
|
38437
|
-
|
|
38438
|
-
speechRecognition
|
|
38439
|
-
} = this.state;
|
|
38440
|
-
const {
|
|
38441
|
-
sttActive,
|
|
38442
|
-
onSetSTTActive
|
|
38443
|
-
} = this.props;
|
|
38444
|
-
if (sttActive) {
|
|
38840
|
+
if (this.props.sttActive) {
|
|
38445
38841
|
this.handleCancelSpeech();
|
|
38446
|
-
|
|
38447
|
-
speechRecognition.start();
|
|
38448
|
-
if (webchatSpeechTimeoutRef) {
|
|
38449
|
-
clearTimeout(webchatSpeechTimeoutRef);
|
|
38450
|
-
}
|
|
38451
|
-
webchatSpeechTimeoutRef = setTimeout(() => {
|
|
38452
|
-
this.handleCancelSpeech();
|
|
38453
|
-
}, 3000);
|
|
38842
|
+
return;
|
|
38454
38843
|
}
|
|
38455
|
-
|
|
38844
|
+
this.startSpeech();
|
|
38456
38845
|
};
|
|
38846
|
+
startSpeech() {
|
|
38847
|
+
const recognition = this.speechRecognition;
|
|
38848
|
+
if (!recognition) return;
|
|
38849
|
+
try {
|
|
38850
|
+
recognition.start();
|
|
38851
|
+
} catch (error) {
|
|
38852
|
+
if (error?.name === "InvalidStateError") {
|
|
38853
|
+
// The engine is still winding down from a previous stop()
|
|
38854
|
+
// whose `onend` hasn't arrived. Claiming to listen here would
|
|
38855
|
+
// leave the button on with nothing running, so wait for the
|
|
38856
|
+
// end and start then.
|
|
38857
|
+
this.restartPending = true;
|
|
38858
|
+
return;
|
|
38859
|
+
}
|
|
38860
|
+
|
|
38861
|
+
// The engine never started listening, so the button must not
|
|
38862
|
+
// claim that it did.
|
|
38863
|
+
this.notifySpeechFailure("start-failed");
|
|
38864
|
+
return;
|
|
38865
|
+
}
|
|
38866
|
+
|
|
38867
|
+
// Only now that a session is really running: a rejected start above
|
|
38868
|
+
// leaves the previous session's flags in place, so a stale result
|
|
38869
|
+
// still in flight from an aborted submit stays discarded instead of
|
|
38870
|
+
// refilling the input with the message that was just sent.
|
|
38871
|
+
this.hasTranscript = false;
|
|
38872
|
+
this.endingDeliberately = false;
|
|
38873
|
+
this.discardResults = false;
|
|
38874
|
+
this.armSpeechTimeout(_utils_speech_recognition__WEBPACK_IMPORTED_MODULE_13__/* .FIRST_TRANSCRIPT_TIMEOUT_MS */ .rt);
|
|
38875
|
+
this.props.onSetSTTActive(true);
|
|
38876
|
+
}
|
|
38457
38877
|
handleChangeTextValue = e => {
|
|
38458
38878
|
this.setState({
|
|
38459
38879
|
text: e.target.value
|
|
@@ -38469,16 +38889,39 @@ class BaseInput extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38469
38889
|
} = this.state;
|
|
38470
38890
|
const {
|
|
38471
38891
|
sttActive,
|
|
38472
|
-
fileList
|
|
38892
|
+
fileList,
|
|
38893
|
+
fileUploadError
|
|
38473
38894
|
} = this.props;
|
|
38474
|
-
|
|
38895
|
+
|
|
38896
|
+
// Exactly what the field shows, which is `text` plus any transcript
|
|
38897
|
+
// not yet finalized — including after recognition stopped, while the
|
|
38898
|
+
// interim waits for `onend` to commit it.
|
|
38899
|
+
const messageText = combineStrings(text, speechResult);
|
|
38475
38900
|
if (sttActive) {
|
|
38476
|
-
|
|
38477
|
-
|
|
38478
|
-
|
|
38479
|
-
this.
|
|
38901
|
+
// Discard whatever the engine still owes us: it belongs to this
|
|
38902
|
+
// message, which is on its way. handleSubmit restores focus to
|
|
38903
|
+
// the input itself once the message is sent.
|
|
38904
|
+
this.endSpeech({
|
|
38905
|
+
discardPending: true,
|
|
38906
|
+
restoreFocus: false
|
|
38907
|
+
});
|
|
38480
38908
|
}
|
|
38481
|
-
|
|
38909
|
+
|
|
38910
|
+
/**
|
|
38911
|
+
* Enter reaches `handleSubmit` straight from `handleInputKeyDown`, which does
|
|
38912
|
+
* not consult the Send button — so both of the button's `disabled` conditions
|
|
38913
|
+
* have to be repeated here or Enter bypasses them.
|
|
38914
|
+
*
|
|
38915
|
+
* `fileList` is always an array (it defaults to `[]`), so emptiness has to be
|
|
38916
|
+
* checked by length: `!fileList` would never be true.
|
|
38917
|
+
*
|
|
38918
|
+
* The text half checks `messageText` rather than the button's `state.text`
|
|
38919
|
+
* on purpose: while dictating, the pending transcript is shown in the field
|
|
38920
|
+
* but is not in `text` yet, and a dictation the user never typed a word of
|
|
38921
|
+
* must still submit (CGY-37417).
|
|
38922
|
+
*/
|
|
38923
|
+
if (fileUploadError) return;
|
|
38924
|
+
if (!messageText && !fileList?.length) return;
|
|
38482
38925
|
const attachments = [];
|
|
38483
38926
|
fileList.forEach(fileItem => {
|
|
38484
38927
|
if (fileItem.uploadFileMeta) {
|
|
@@ -38495,7 +38938,8 @@ class BaseInput extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38495
38938
|
}
|
|
38496
38939
|
this.props.onSetFileList([]);
|
|
38497
38940
|
this.setState({
|
|
38498
|
-
text: ""
|
|
38941
|
+
text: "",
|
|
38942
|
+
speechResult: ""
|
|
38499
38943
|
}, () => {
|
|
38500
38944
|
this.props.onSendMessage(messageText, data, {
|
|
38501
38945
|
collate: true
|
|
@@ -38657,8 +39101,15 @@ class BaseInput extends react__WEBPACK_IMPORTED_MODULE_0__["default"].PureCompon
|
|
|
38657
39101
|
} : undefined
|
|
38658
39102
|
}));
|
|
38659
39103
|
}), props.config.settings.behavior.enableSTT && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(SpeechButton, {
|
|
39104
|
+
type: "button",
|
|
38660
39105
|
className: classnames__WEBPACK_IMPORTED_MODULE_2___default()("webchat-input-button-speech", sttActive && "webchat-input-button-speech-active"),
|
|
38661
|
-
"aria-label": customTranslations?.ariaLabels?.speechToText ?? "Speech to text"
|
|
39106
|
+
"aria-label": customTranslations?.ariaLabels?.speechToText ?? "Speech to text"
|
|
39107
|
+
// APG toggle button: the "listening"
|
|
39108
|
+
// state is otherwise conveyed only by
|
|
39109
|
+
// the animated background, which is
|
|
39110
|
+
// aria-hidden (SC 4.1.2).
|
|
39111
|
+
,
|
|
39112
|
+
"aria-pressed": sttActive,
|
|
38662
39113
|
id: "webchatInputMessageSpeechButton",
|
|
38663
39114
|
onClick: this.toggleSTT,
|
|
38664
39115
|
disabled: !this.isSTTSupported()
|
|
@@ -38999,6 +39450,7 @@ const PreviewUploadedFiles = () => {
|
|
|
38999
39450
|
key: index,
|
|
39000
39451
|
id: `filePreview${index}`
|
|
39001
39452
|
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(UploadedFilePreview, null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(RemoveFileButton, {
|
|
39453
|
+
type: "button",
|
|
39002
39454
|
onClick: () => onRemoveFileButtonClick(index),
|
|
39003
39455
|
onFocus: () => {
|
|
39004
39456
|
if (removeFileButtonRefs.current[index]) {
|
|
@@ -39979,6 +40431,8 @@ const Header = props => {
|
|
|
39979
40431
|
showChatScreen,
|
|
39980
40432
|
autoFocusScreenTitle,
|
|
39981
40433
|
onDeleteAllConversations,
|
|
40434
|
+
titleId = "webchatHeaderTitle",
|
|
40435
|
+
closeButtonAriaLabel,
|
|
39982
40436
|
...rest
|
|
39983
40437
|
} = props;
|
|
39984
40438
|
const ariaLabels = (0,_webchat_helper_useSelector__WEBPACK_IMPORTED_MODULE_13__/* .useSelector */ .d)(state => state.config.settings.customTranslations?.ariaLabels);
|
|
@@ -40023,10 +40477,10 @@ const Header = props => {
|
|
|
40023
40477
|
title: "Cognigy.AI Logo",
|
|
40024
40478
|
role: "img",
|
|
40025
40479
|
className: classnames__WEBPACK_IMPORTED_MODULE_8___default()("webchat-header-cognigy-logo")
|
|
40026
|
-
})), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(HeaderText, {
|
|
40480
|
+
})), title?.trim() && /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(HeaderText, {
|
|
40027
40481
|
ref: headerTextRef,
|
|
40028
40482
|
variant: "h2-semibold",
|
|
40029
|
-
id:
|
|
40483
|
+
id: titleId,
|
|
40030
40484
|
className: "webchat-header-title",
|
|
40031
40485
|
margin: 0,
|
|
40032
40486
|
tabIndex: -1
|
|
@@ -40058,7 +40512,7 @@ const Header = props => {
|
|
|
40058
40512
|
"data-header-close-button": true,
|
|
40059
40513
|
onClick: handleCloseClick,
|
|
40060
40514
|
className: "webchat-header-close-button",
|
|
40061
|
-
"aria-label": ariaLabels?.closeChat ?? "Close chat",
|
|
40515
|
+
"aria-label": closeButtonAriaLabel ?? ariaLabels?.closeChat ?? "Close chat",
|
|
40062
40516
|
ref: closeButtonRef
|
|
40063
40517
|
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_assets_close_16px_svg__WEBPACK_IMPORTED_MODULE_3__/* ["default"] */ .A, null)))), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_Notifications__WEBPACK_IMPORTED_MODULE_7__/* ["default"] */ .Ay, null));
|
|
40064
40518
|
};
|
|
@@ -40646,7 +41100,8 @@ MultilineInput.displayName = "MultilineInput";
|
|
|
40646
41100
|
|
|
40647
41101
|
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
40648
41102
|
/* harmony export */ Ay: () => (__WEBPACK_DEFAULT_EXPORT__),
|
|
40649
|
-
/* harmony export */ UI: () => (/* binding */ createNotification)
|
|
41103
|
+
/* harmony export */ UI: () => (/* binding */ createNotification),
|
|
41104
|
+
/* harmony export */ gi: () => (/* binding */ createErrorNotification)
|
|
40650
41105
|
/* harmony export */ });
|
|
40651
41106
|
/* unused harmony export createPersistentNotification */
|
|
40652
41107
|
/* unused harmony import specifier */ var toast;
|
|
@@ -40662,6 +41117,25 @@ const Notifications = () => {
|
|
|
40662
41117
|
gutter: 1,
|
|
40663
41118
|
toastOptions: {
|
|
40664
41119
|
duration: 1500,
|
|
41120
|
+
// Failures stay up much longer than confirmations: the user has
|
|
41121
|
+
// to read a remedy and act on it, not just register that
|
|
41122
|
+
// something worked. The longest of these ("Microphone access
|
|
41123
|
+
// is blocked. Allow it in your browser settings…") is ~95
|
|
41124
|
+
// characters, which needs well over 5s to notice and read
|
|
41125
|
+
// (SC 2.2.1).
|
|
41126
|
+
// red40 on red10 is 5.27:1, mirroring the vetted green pairing
|
|
41127
|
+
// below (SC 1.4.3).
|
|
41128
|
+
error: {
|
|
41129
|
+
duration: 12000,
|
|
41130
|
+
style: {
|
|
41131
|
+
backgroundColor: theme.red10,
|
|
41132
|
+
color: theme.red40
|
|
41133
|
+
},
|
|
41134
|
+
iconTheme: {
|
|
41135
|
+
primary: theme.red40,
|
|
41136
|
+
secondary: theme.red10
|
|
41137
|
+
}
|
|
41138
|
+
},
|
|
40665
41139
|
style: {
|
|
40666
41140
|
backgroundColor: theme.green10,
|
|
40667
41141
|
borderRadius: 0,
|
|
@@ -40713,6 +41187,20 @@ function createNotification(message, options = {}) {
|
|
|
40713
41187
|
});
|
|
40714
41188
|
}
|
|
40715
41189
|
|
|
41190
|
+
/**
|
|
41191
|
+
* Same as `createNotification`, styled as a failure and shown for longer.
|
|
41192
|
+
* Use for anything the user needs to act on (e.g. a dictation that could not
|
|
41193
|
+
* be started) — the message is mirrored into <StatusLiveRegion> like every
|
|
41194
|
+
* other toast, so it reaches screen readers too (SC 3.3.1 / 4.1.3).
|
|
41195
|
+
*/
|
|
41196
|
+
function createErrorNotification(message, options = {}) {
|
|
41197
|
+
react_hot_toast__WEBPACK_IMPORTED_MODULE_2__/* ["default"] */ .Ay.error(message, {
|
|
41198
|
+
...options,
|
|
41199
|
+
className: "webchat-toast-notification webchat-toast-notification-error",
|
|
41200
|
+
ariaProps: silencedAriaProps
|
|
41201
|
+
});
|
|
41202
|
+
}
|
|
41203
|
+
|
|
40716
41204
|
/**
|
|
40717
41205
|
* Creates a persistent notification.
|
|
40718
41206
|
* Can be dismissed by calling the returned function.
|
|
@@ -41716,6 +42204,68 @@ const UnreadMessagePreview = _emotion_styled__WEBPACK_IMPORTED_MODULE_0__/* ["de
|
|
|
41716
42204
|
|
|
41717
42205
|
/***/ },
|
|
41718
42206
|
|
|
42207
|
+
/***/ 1715
|
|
42208
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
42209
|
+
|
|
42210
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
42211
|
+
/* harmony export */ A: () => (__WEBPACK_DEFAULT_EXPORT__)
|
|
42212
|
+
/* harmony export */ });
|
|
42213
|
+
/* harmony import */ var react__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(51649);
|
|
42214
|
+
/* harmony import */ var _SrOnlyLiveRegion__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(65766);
|
|
42215
|
+
|
|
42216
|
+
|
|
42217
|
+
let unreadAnnouncementCounter = 0;
|
|
42218
|
+
|
|
42219
|
+
/**
|
|
42220
|
+
* Announces the unread-message count when it changes (WCAG 4.1.3 Status
|
|
42221
|
+
* Messages, technique ARIA22, CGY-3163). Sighted users get this count from
|
|
42222
|
+
* the badge on the toggle button and from the page title indicator; neither
|
|
42223
|
+
* reaches a screen reader on its own — the badge only changes the toggle
|
|
42224
|
+
* button's accessible name (read on focus, never live) and browsers do not
|
|
42225
|
+
* announce `document.title` changes at all.
|
|
42226
|
+
*
|
|
42227
|
+
* Unread messages only accumulate while the chat window is closed or the
|
|
42228
|
+
* page is hidden, so <StatusLiveRegion> (inside the open window) cannot
|
|
42229
|
+
* carry this. The region is mounted for the page lifetime — next to the
|
|
42230
|
+
* toggle button, independent of it — so it pre-exists the first count
|
|
42231
|
+
* change (a live region only announces changes to a node already in the
|
|
42232
|
+
* accessibility tree).
|
|
42233
|
+
*
|
|
42234
|
+
* role="status" (polite), not role="alert": a new-message notice is
|
|
42235
|
+
* advisory and must not interrupt what the user is doing on the host page.
|
|
42236
|
+
*
|
|
42237
|
+
* `active` mirrors the visual indicators (badge or title indicator
|
|
42238
|
+
* enabled): when neither is shown, screen-reader users are not notified
|
|
42239
|
+
* either. Clearing the count (chat opened, page visible again) empties the
|
|
42240
|
+
* region silently so users browsing the page later don't find stale text.
|
|
42241
|
+
*/
|
|
42242
|
+
const UnreadMessagesAnnouncer = ({
|
|
42243
|
+
active,
|
|
42244
|
+
count,
|
|
42245
|
+
singularText,
|
|
42246
|
+
pluralText
|
|
42247
|
+
}) => {
|
|
42248
|
+
const [message, setMessage] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
|
42249
|
+
(0,react__WEBPACK_IMPORTED_MODULE_0__.useEffect)(() => {
|
|
42250
|
+
if (!active || count === 0) {
|
|
42251
|
+
setMessage(null);
|
|
42252
|
+
return;
|
|
42253
|
+
}
|
|
42254
|
+
setMessage({
|
|
42255
|
+
id: `webchatUnreadMessages-${++unreadAnnouncementCounter}`,
|
|
42256
|
+
text: count === 1 ? singularText : `${count} ${pluralText}`
|
|
42257
|
+
});
|
|
42258
|
+
}, [active, count, singularText, pluralText]);
|
|
42259
|
+
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0__["default"].createElement(_SrOnlyLiveRegion__WEBPACK_IMPORTED_MODULE_1__/* .SrOnlyLiveRegion */ .Y, {
|
|
42260
|
+
id: "webchatUnreadMessagesLiveRegion",
|
|
42261
|
+
role: "status",
|
|
42262
|
+
message: message
|
|
42263
|
+
});
|
|
42264
|
+
};
|
|
42265
|
+
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (UnreadMessagesAnnouncer);
|
|
42266
|
+
|
|
42267
|
+
/***/ },
|
|
42268
|
+
|
|
41719
42269
|
/***/ 31961
|
|
41720
42270
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
41721
42271
|
|
|
@@ -43196,9 +43746,18 @@ const INITIAL_NOTICE_SESSION = {
|
|
|
43196
43746
|
* only — later restores (reopening a previous conversation) are already
|
|
43197
43747
|
* covered by the snapshot lookup, and the flag must not silence a
|
|
43198
43748
|
* brand-new conversation started afterwards.
|
|
43199
|
-
|
|
43200
|
-
|
|
43201
|
-
|
|
43749
|
+
*
|
|
43750
|
+
* `willRestorePersistedConversation` is that same page-reload case seen
|
|
43751
|
+
* BEFORE the connect: the restore only happens when the connect
|
|
43752
|
+
* resolves, which can take longer than the notice's 600ms announce
|
|
43753
|
+
* delay, and by then an announcement can no longer be taken back. It is
|
|
43754
|
+
* read from storage through the very helpers the restore uses (see
|
|
43755
|
+
* `hasPersistedConversationForInitialSession`), so it cannot disagree
|
|
43756
|
+
* with the restore that follows. Same first-connect-only scope as the
|
|
43757
|
+
* flag above, for the same reason.
|
|
43758
|
+
*/
|
|
43759
|
+
function computeNoticeSession(prev, currentSessionId, prevConversationsSnapshot, hasRestoredPersistedHistory, willRestorePersistedConversation) {
|
|
43760
|
+
const isRestoredFirstConnect = prev.id === "" && (!!hasRestoredPersistedHistory || !!willRestorePersistedConversation);
|
|
43202
43761
|
const isNew = !prevConversationsSnapshot?.[currentSessionId] && !isRestoredFirstConnect;
|
|
43203
43762
|
const isFirstConnectOfNewConversation = prev.id === "" && prev.isNew && isNew;
|
|
43204
43763
|
return {
|
|
@@ -43216,6 +43775,12 @@ function computeNoticeSession(prev, currentSessionId, prevConversationsSnapshot,
|
|
|
43216
43775
|
* reconnect the socket and open it — its dialog and focus utterances
|
|
43217
43776
|
* would cancel the notice; on close, the intro becomes pending again
|
|
43218
43777
|
* and is announced after the "Connection restored" utterances).
|
|
43778
|
+
*
|
|
43779
|
+
* Eligibility only — WHEN an eligible notice is spoken is the live
|
|
43780
|
+
* region's business (it defers the intro past the screen's focus
|
|
43781
|
+
* utterances). That deferral is why the verdict must be final before the
|
|
43782
|
+
* first connect resolves rather than after it, which is what
|
|
43783
|
+
* `willRestorePersistedConversation` in `computeNoticeSession` provides.
|
|
43219
43784
|
*/
|
|
43220
43785
|
function getAIAgentNoticeIntroText(args) {
|
|
43221
43786
|
const {
|
|
@@ -43343,8 +43908,10 @@ const getMessagesListWithoutControlCommands = (messages, types = EXCLUDE_CC_TYPE
|
|
|
43343
43908
|
* @returns {Array}
|
|
43344
43909
|
*/
|
|
43345
43910
|
const getKeyboardFocusableElements = element => {
|
|
43346
|
-
// Get all interactive elements in given element
|
|
43347
|
-
|
|
43911
|
+
// Get all interactive elements in given element. An <iframe> is a
|
|
43912
|
+
// sequential-focus stop in every browser (Tab enters the frame's
|
|
43913
|
+
// document), so it counts as focusable — e.g. the xApp overlay's frame.
|
|
43914
|
+
const interactiveEls = element?.querySelectorAll('a[href], button, input, textarea, select, details, iframe, [tabindex]:not([tabindex="-1"])');
|
|
43348
43915
|
const interactiveElsArray = interactiveEls && Array.from(interactiveEls);
|
|
43349
43916
|
|
|
43350
43917
|
// Two lists are derived from the interactive elements:
|
|
@@ -43687,6 +44254,113 @@ function getSourceBackgroundColor(mappedValue, colors) {
|
|
|
43687
44254
|
|
|
43688
44255
|
/***/ },
|
|
43689
44256
|
|
|
44257
|
+
/***/ 44120
|
|
44258
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
44259
|
+
|
|
44260
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
44261
|
+
/* harmony export */ JO: () => (/* binding */ getSpeechRecognitionErrorMessage),
|
|
44262
|
+
/* harmony export */ oC: () => (/* binding */ createSpeechRecognition),
|
|
44263
|
+
/* harmony export */ r0: () => (/* binding */ TRANSCRIPT_IDLE_TIMEOUT_MS),
|
|
44264
|
+
/* harmony export */ rt: () => (/* binding */ FIRST_TRANSCRIPT_TIMEOUT_MS)
|
|
44265
|
+
/* harmony export */ });
|
|
44266
|
+
/**
|
|
44267
|
+
* Web Speech API plumbing for the message input's speech-to-text button.
|
|
44268
|
+
*
|
|
44269
|
+
* The spec says nothing about latency, and engines differ by seconds — which
|
|
44270
|
+
* is what CGY-37417 turned out to be. Measured on macOS with a real
|
|
44271
|
+
* microphone, one spoken sentence, `continuous`/`interimResults` both on:
|
|
44272
|
+
*
|
|
44273
|
+
* | event | Chrome 152 | Edge 152 |
|
|
44274
|
+
* | --------------------------- | ---------- | ------------- |
|
|
44275
|
+
* | `audiostart` after `start()`| 0.55 s | 0.52 s |
|
|
44276
|
+
* | `speechstart` -> 1st result | 0.00 s | 0.32 - 4.25 s |
|
|
44277
|
+
* | `start()` -> 1st transcript | 1.37 s | 5.02 - 6.65 s |
|
|
44278
|
+
*
|
|
44279
|
+
* Chrome streams an interim result in the same tick as `speechstart`; Edge
|
|
44280
|
+
* buffers whole phrases against its cloud service and can take over six
|
|
44281
|
+
* seconds to hand back the first word. Edge also emits *placeholder* results
|
|
44282
|
+
* — `isFinal` with an empty transcript — when speech starts and again on
|
|
44283
|
+
* `stop()`, which Chrome never does. Any timing budget here has to hold for
|
|
44284
|
+
* the slow engine, and any result handler has to tolerate empty transcripts.
|
|
44285
|
+
*/
|
|
44286
|
+
|
|
44287
|
+
/**
|
|
44288
|
+
* How long to wait after `start()` for the engine's first transcript before
|
|
44289
|
+
* giving up and telling the user nothing was heard.
|
|
44290
|
+
*
|
|
44291
|
+
* This has to cover the permission prompt (on first use the clock is already
|
|
44292
|
+
* running while the user hunts for the browser's "Allow" button), the
|
|
44293
|
+
* engine's connection to its speech service, the user pausing to collect
|
|
44294
|
+
* their thoughts, and then the engine's own recognition latency — 6.65 s of
|
|
44295
|
+
* it on Edge. The previous budget was 3 s counted from `start()`, so on Edge
|
|
44296
|
+
* recognition was routinely cancelled before it had produced a single word,
|
|
44297
|
+
* and the failure was silent (CGY-37417).
|
|
44298
|
+
*/
|
|
44299
|
+
const FIRST_TRANSCRIPT_TIMEOUT_MS = 15000;
|
|
44300
|
+
|
|
44301
|
+
/**
|
|
44302
|
+
* How long to keep listening after the last transcript before auto-stopping,
|
|
44303
|
+
* so the user doesn't have to press the button a second time.
|
|
44304
|
+
*
|
|
44305
|
+
* Only armed once the engine has actually produced a transcript, which is
|
|
44306
|
+
* what makes 3 s safe: while speech is flowing, consecutive results arrive
|
|
44307
|
+
* ~0.2 - 0.7 s apart on both engines. Before the first transcript,
|
|
44308
|
+
* FIRST_TRANSCRIPT_TIMEOUT_MS applies instead.
|
|
44309
|
+
*/
|
|
44310
|
+
const TRANSCRIPT_IDLE_TIMEOUT_MS = 3000;
|
|
44311
|
+
|
|
44312
|
+
/** Reasons recognition can fail, beyond the spec's own error codes. */
|
|
44313
|
+
|
|
44314
|
+
/**
|
|
44315
|
+
* Creates a recognizer, or `null` where the browser has no Web Speech API
|
|
44316
|
+
* (Firefox, and WebViews that drop it). Callers treat `null` as
|
|
44317
|
+
* "speech-to-text unsupported" and disable the button.
|
|
44318
|
+
*/
|
|
44319
|
+
const createSpeechRecognition = () => {
|
|
44320
|
+
const Recognition = typeof SpeechRecognition !== "undefined" ? SpeechRecognition : typeof webkitSpeechRecognition !== "undefined" ? webkitSpeechRecognition : null;
|
|
44321
|
+
if (!Recognition) return null;
|
|
44322
|
+
try {
|
|
44323
|
+
return new Recognition();
|
|
44324
|
+
} catch {
|
|
44325
|
+
// Constructing can still throw where the API is present but disabled
|
|
44326
|
+
// by policy or an unsupported embedding context.
|
|
44327
|
+
return null;
|
|
44328
|
+
}
|
|
44329
|
+
};
|
|
44330
|
+
|
|
44331
|
+
/**
|
|
44332
|
+
* Maps a failure onto the message shown to the user, so a failed dictation
|
|
44333
|
+
* says what went wrong and what to do instead of failing silently
|
|
44334
|
+
* (SC 3.3.1 Error Identification).
|
|
44335
|
+
*/
|
|
44336
|
+
const getSpeechRecognitionErrorMessage = (failure, customTranslations) => {
|
|
44337
|
+
switch (failure) {
|
|
44338
|
+
// The user (or a policy) blocked the microphone. Only they can undo
|
|
44339
|
+
// it, and only in browser settings — the prompt won't come back.
|
|
44340
|
+
// NOT `service-not-allowed`, which per the spec means the recognition
|
|
44341
|
+
// *service* was refused and says nothing about microphone access;
|
|
44342
|
+
// sending those users to their microphone settings would be a wrong
|
|
44343
|
+
// remedy, so it falls through to the generic message below.
|
|
44344
|
+
case "not-allowed":
|
|
44345
|
+
return customTranslations?.speech_recognition_not_allowed ?? "Microphone access is blocked. Allow it in your browser settings to use speech input.";
|
|
44346
|
+
case "audio-capture":
|
|
44347
|
+
return customTranslations?.speech_recognition_no_microphone ?? "No microphone was found. Connect one to use speech input.";
|
|
44348
|
+
case "no-speech":
|
|
44349
|
+
case "no-transcript":
|
|
44350
|
+
return customTranslations?.speech_recognition_no_speech ?? "No speech was detected. Please try again.";
|
|
44351
|
+
|
|
44352
|
+
// "network" is the engine's speech service being unreachable;
|
|
44353
|
+
// "service-not-allowed" is that service being refused to this page;
|
|
44354
|
+
// "language-not-supported" and "bad-grammar" are misconfiguration the
|
|
44355
|
+
// end user cannot act on (both are logged for the operator instead).
|
|
44356
|
+
// None of these have a remedy the user can apply beyond typing.
|
|
44357
|
+
default:
|
|
44358
|
+
return customTranslations?.speech_recognition_error ?? "Speech input is currently unavailable. Please type your message instead.";
|
|
44359
|
+
}
|
|
44360
|
+
};
|
|
44361
|
+
|
|
44362
|
+
/***/ },
|
|
44363
|
+
|
|
43690
44364
|
/***/ 61395
|
|
43691
44365
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
43692
44366
|
|
|
@@ -43704,6 +44378,8 @@ function getSourceBackgroundColor(mappedValue, colors) {
|
|
|
43704
44378
|
/* harmony import */ var _store_input_input_reducer__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(16967);
|
|
43705
44379
|
/* harmony import */ var _store_xapp_overlay_slice__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(97114);
|
|
43706
44380
|
/* harmony import */ var _store_messages_message_reducer__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(22284);
|
|
44381
|
+
/* harmony import */ var _store_options_persisted_conversation__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(57589);
|
|
44382
|
+
|
|
43707
44383
|
|
|
43708
44384
|
|
|
43709
44385
|
|
|
@@ -43773,6 +44449,7 @@ const ConnectedWebchatUI = (0,react_redux__WEBPACK_IMPORTED_MODULE_1__/* .connec
|
|
|
43773
44449
|
unseenMessages,
|
|
43774
44450
|
prevConversations,
|
|
43775
44451
|
hasRestoredPersistedHistory,
|
|
44452
|
+
willRestorePersistedConversation: (0,_store_options_persisted_conversation__WEBPACK_IMPORTED_MODULE_11__/* .hasPersistedConversationForInitialSession */ ._)(state),
|
|
43776
44453
|
open,
|
|
43777
44454
|
typingIndicator: typing,
|
|
43778
44455
|
inputMode,
|
|
@@ -44520,8 +45197,33 @@ function setHasAcceptedTermsInStorage(browserStorage, userId) {
|
|
|
44520
45197
|
/* harmony import */ var _store_store__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(6236);
|
|
44521
45198
|
|
|
44522
45199
|
|
|
44523
|
-
|
|
44524
|
-
|
|
45200
|
+
|
|
45201
|
+
// Tags removed from the previous allow-list to align with DOMPurify's secure defaults
|
|
45202
|
+
// (WCH-SI10-001). Each tag enables a distinct attack vector:
|
|
45203
|
+
// applet — Java applet execution
|
|
45204
|
+
// base — rewrites all relative URLs on the host page
|
|
45205
|
+
// body / html / head — structural document elements; no legitimate use in sanitised fragments
|
|
45206
|
+
// embed — loads arbitrary external content / plugins
|
|
45207
|
+
// form — posts user data to attacker-controlled URLs
|
|
45208
|
+
// frame / frameset / noframes — clickjacking and legacy frame injection
|
|
45209
|
+
// iframe — inline HTML documents; srcdoc = direct XSS vector
|
|
45210
|
+
// link — loads external stylesheets
|
|
45211
|
+
// meta — HTTP redirects and CSP bypass via http-equiv
|
|
45212
|
+
// object — loads Flash, PDFs, and arbitrary external content
|
|
45213
|
+
// style — CSS injection and attribute-value exfiltration
|
|
45214
|
+
// These tags are blocked by default when no custom tag list is configured.
|
|
45215
|
+
// Tenants can supply a replacement list via widgetSettings.customAllowedHtmlTags,
|
|
45216
|
+
// but dangerous tags are always stripped from that list before it is applied (PR #309).
|
|
45217
|
+
const allowedHtmlTags = ["a", "abbr", "acronym", "address", "area", "article", "aside", "audio", "b", "basefont", "bdi", "bdo", "big", "blockquote", "br", "button", "canvas", "caption", "center", "cite", "code", "col", "colgroup", "data", "datalist", "dd", "del", "details", "dfn", "dialog", "dir", "div", "dl", "dt", "em", "fieldset", "figcaption", "figure", "font", "footer", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hr", "i", "img", "input", "ins", "kbd", "label", "legend", "li", "main", "map", "mark", "meter", "nav", "ol", "optgroup", "option", "output", "p", "param", "picture", "pre", "progress", "q", "rp", "rt", "ruby", "s", "samp", "section", "select", "small", "source", "span", "strike", "strong", "sub", "summary", "sup", "svg", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "tt", "u", "ul", "var", "video", "wbr"];
|
|
45218
|
+
|
|
45219
|
+
// Attributes that are always blocked regardless of caller configuration (WCH-SI10-001).
|
|
45220
|
+
// Removed from the previous allow-list:
|
|
45221
|
+
// action / formaction — form submission to attacker-controlled URLs
|
|
45222
|
+
// sandbox — giving content control over its own sandbox policy
|
|
45223
|
+
// srcdoc — inline HTML document in an iframe (direct XSS vector)
|
|
45224
|
+
// style — inline CSS injection and attribute-value exfiltration
|
|
45225
|
+
// target — controls navigation target (_blank without rel is risky)
|
|
45226
|
+
const allowedHtmlAttributes = ["accept", "accept-charset", "accesskey", "align", "alt", "autocomplete", "autofocus", "autoplay", "bgcolor", "border", "charset", "checked", "cite", "class", "color", "cols", "colspan", "content", "contenteditable", "controls", "coords", "data", "data-*", "datetime", "default", "dir", "dirname", "disabled", "download", "draggable", "dropzone", "enctype", "for", "form", "headers", "height", "hidden", "high", "href", "hreflang", "http-equiv", "id", "ismap", "kind", "label", "lang", "list", "loop", "low", "max", "maxlength", "media", "method", "min", "multiple", "muted", "name", "novalidate", "open", "optimum", "pattern", "placeholder", "poster", "preload", "readonly", "rel", "required", "reversed", "rows", "rowspan", "scope", "selected", "shape", "size", "sizes", "span", "spellcheck", "src", "srclang", "srcset", "start", "step", "tabindex", "title", "translate", "type", "usemap", "value", "width", "wrap"];
|
|
44525
45227
|
const config = {
|
|
44526
45228
|
ALLOWED_TAGS: allowedHtmlTags,
|
|
44527
45229
|
ALLOWED_ATTR: allowedHtmlAttributes
|
|
@@ -46497,6 +47199,124 @@ const getOptionsKey = ({
|
|
|
46497
47199
|
|
|
46498
47200
|
/***/ },
|
|
46499
47201
|
|
|
47202
|
+
/***/ 57589
|
|
47203
|
+
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
47204
|
+
|
|
47205
|
+
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
|
47206
|
+
/* harmony export */ _: () => (/* binding */ hasPersistedConversationForInitialSession)
|
|
47207
|
+
/* harmony export */ });
|
|
47208
|
+
/* harmony import */ var _options__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(48242);
|
|
47209
|
+
/* harmony import */ var _helper_storage__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(62473);
|
|
47210
|
+
|
|
47211
|
+
|
|
47212
|
+
|
|
47213
|
+
/**
|
|
47214
|
+
* Whether browser storage already holds a non-empty conversation for the
|
|
47215
|
+
* session this page load will connect with — i.e. whether the first
|
|
47216
|
+
* connect is going to restore a conversation rather than start one.
|
|
47217
|
+
*
|
|
47218
|
+
* This answers, BEFORE the socket connects, the question the AI-agent
|
|
47219
|
+
* notice needs answered (CGY-3519): a restored conversation is a
|
|
47220
|
+
* continuation and must not be announced. Waiting for the connect to
|
|
47221
|
+
* tell us instead is not an option — it can take longer than the
|
|
47222
|
+
* notice's 600ms announce delay, and an announcement cannot be taken
|
|
47223
|
+
* back.
|
|
47224
|
+
*
|
|
47225
|
+
* It deliberately mirrors the restore in `optionsMiddleware`'s
|
|
47226
|
+
* `SET_OPTIONS` handler: same `getStorage`, same `getOptionsKey`, same
|
|
47227
|
+
* "non-empty messages" condition that `RESET_STATE` uses to set
|
|
47228
|
+
* `hasRestoredPersistedHistory`. Reading the same inputs through the
|
|
47229
|
+
* same helpers is what keeps the prediction and the restore aligned —
|
|
47230
|
+
* including when they agree on "no": if the endpoint config (and with it
|
|
47231
|
+
* `URLToken`) has not loaded yet, the key misses here exactly as it
|
|
47232
|
+
* misses there, and nothing is restored either. The one case where they
|
|
47233
|
+
* part company is benign: a message sent before the first connect is
|
|
47234
|
+
* dispatched ahead of `setOptions`, so `RESET_STATE` finds a non-empty
|
|
47235
|
+
* history and skips the restore while this still says "continued" — the
|
|
47236
|
+
* session IS a continuation for the backend, so staying silent remains
|
|
47237
|
+
* the right outcome.
|
|
47238
|
+
*
|
|
47239
|
+
* `config.initialSessionId` is the pinned `sessionId` from the embedding
|
|
47240
|
+
* options. Without one, the socket client mints a fresh session id per
|
|
47241
|
+
* page load, which no stored entry can match, so there is nothing to
|
|
47242
|
+
* predict.
|
|
47243
|
+
*/
|
|
47244
|
+
// mapStateToProps runs on every dispatch and a persisted conversation can
|
|
47245
|
+
// be sizeable, so this must not sit on the hot path: the parse is cached
|
|
47246
|
+
// against the raw string it was derived from, and the read stops
|
|
47247
|
+
// altogether once the answer is spent (see below). Until then `getItem`
|
|
47248
|
+
// runs on every call, which is what keeps the answer current — the key
|
|
47249
|
+
// changes once the endpoint config supplies its URLToken.
|
|
47250
|
+
let lastRead = null;
|
|
47251
|
+
const hasPersistedConversationForInitialSession = state => {
|
|
47252
|
+
const sessionId = state.config.initialSessionId;
|
|
47253
|
+
if (!sessionId) return false;
|
|
47254
|
+
|
|
47255
|
+
// This page load ended up on a session other than the pinned one, so
|
|
47256
|
+
// nothing stored under the pinned key is being restored into it and
|
|
47257
|
+
// there is nothing to predict. "Start new conversation" from the
|
|
47258
|
+
// conversations list does this before the first connect
|
|
47259
|
+
// (`SWITCH_SESSION` with no id mints a fresh session id), and without
|
|
47260
|
+
// this guard that brand-new conversation would inherit the pinned
|
|
47261
|
+
// session's "continued" verdict and never be announced — the mirror
|
|
47262
|
+
// image of the bug this predicate exists for. A REopened previous
|
|
47263
|
+
// conversation also lands here, and is correctly silenced by the
|
|
47264
|
+
// `prevConversations` snapshot instead.
|
|
47265
|
+
if (state.options.sessionId && state.options.sessionId !== sessionId) return false;
|
|
47266
|
+
const {
|
|
47267
|
+
disableLocalStorage,
|
|
47268
|
+
useSessionStorage
|
|
47269
|
+
} = state.config.settings.embeddingConfiguration;
|
|
47270
|
+
try {
|
|
47271
|
+
const browserStorage = (0,_helper_storage__WEBPACK_IMPORTED_MODULE_1__/* .getStorage */ .c)({
|
|
47272
|
+
disableLocalStorage,
|
|
47273
|
+
useSessionStorage
|
|
47274
|
+
});
|
|
47275
|
+
if (!browserStorage) return false;
|
|
47276
|
+
|
|
47277
|
+
// `channel` is not part of the storage key; it is only in the type.
|
|
47278
|
+
const key = (0,_options__WEBPACK_IMPORTED_MODULE_0__/* .getOptionsKey */ .W)({
|
|
47279
|
+
userId: state.options.userId,
|
|
47280
|
+
sessionId,
|
|
47281
|
+
channel: state.options.channel
|
|
47282
|
+
}, state.config);
|
|
47283
|
+
|
|
47284
|
+
// Once the first connect has assigned a session id the answer is
|
|
47285
|
+
// spent: `computeNoticeSession` consults it only while its own
|
|
47286
|
+
// `prev.id` is still "". The key is final well before that (userId
|
|
47287
|
+
// is dispatched on mount, URLToken with the config, and the connect
|
|
47288
|
+
// waits for the config), so the cache is populated by then and every
|
|
47289
|
+
// later dispatch is served without touching storage.
|
|
47290
|
+
if (state.options.sessionId && lastRead?.key === key) return lastRead.result;
|
|
47291
|
+
const persistedString = browserStorage.getItem(key);
|
|
47292
|
+
if (lastRead && lastRead.key === key && lastRead.raw === persistedString) {
|
|
47293
|
+
return lastRead.result;
|
|
47294
|
+
}
|
|
47295
|
+
let result = false;
|
|
47296
|
+
if (persistedString) {
|
|
47297
|
+
try {
|
|
47298
|
+
const messages = JSON.parse(persistedString)?.messages;
|
|
47299
|
+
result = Array.isArray(messages) && messages.length > 0;
|
|
47300
|
+
} catch {
|
|
47301
|
+
result = false;
|
|
47302
|
+
}
|
|
47303
|
+
}
|
|
47304
|
+
lastRead = {
|
|
47305
|
+
key,
|
|
47306
|
+
raw: persistedString,
|
|
47307
|
+
result
|
|
47308
|
+
};
|
|
47309
|
+
return result;
|
|
47310
|
+
} catch {
|
|
47311
|
+
// Restricted embeddings can throw on merely touching storage. The
|
|
47312
|
+
// restore reads it the same way, so nothing would be restored there
|
|
47313
|
+
// either — "no persisted conversation" is the truthful answer.
|
|
47314
|
+
return false;
|
|
47315
|
+
}
|
|
47316
|
+
};
|
|
47317
|
+
|
|
47318
|
+
/***/ },
|
|
47319
|
+
|
|
46500
47320
|
/***/ 92101
|
|
46501
47321
|
(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
|
|
46502
47322
|
|
|
@@ -52417,7 +53237,7 @@ function deconstructPacket(packet) {
|
|
|
52417
53237
|
buffers: buffers
|
|
52418
53238
|
};
|
|
52419
53239
|
}
|
|
52420
|
-
function _deconstructPacket(data, buffers) {
|
|
53240
|
+
function _deconstructPacket(data, buffers, toJSON) {
|
|
52421
53241
|
if (!data) return data;
|
|
52422
53242
|
if ((0, is_binary_js_1.isBinary)(data)) {
|
|
52423
53243
|
const placeholder = {
|
|
@@ -52433,6 +53253,9 @@ function _deconstructPacket(data, buffers) {
|
|
|
52433
53253
|
}
|
|
52434
53254
|
return newData;
|
|
52435
53255
|
} else if (typeof data === "object" && !(data instanceof Date)) {
|
|
53256
|
+
if (data.toJSON && typeof data.toJSON === "function" && !toJSON) {
|
|
53257
|
+
return _deconstructPacket(data.toJSON(), buffers, true);
|
|
53258
|
+
}
|
|
52436
53259
|
const newData = {};
|
|
52437
53260
|
for (const key in data) {
|
|
52438
53261
|
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
@@ -52486,6 +53309,11 @@ function _reconstructPacket(data, buffers) {
|
|
|
52486
53309
|
|
|
52487
53310
|
|
|
52488
53311
|
|
|
53312
|
+
var __importDefault = this && this.__importDefault || function (mod) {
|
|
53313
|
+
return mod && mod.__esModule ? mod : {
|
|
53314
|
+
"default": mod
|
|
53315
|
+
};
|
|
53316
|
+
};
|
|
52489
53317
|
Object.defineProperty(exports, "__esModule", ({
|
|
52490
53318
|
value: true
|
|
52491
53319
|
}));
|
|
@@ -52494,7 +53322,7 @@ exports.isPacketValid = isPacketValid;
|
|
|
52494
53322
|
const component_emitter_1 = __webpack_require__(40368);
|
|
52495
53323
|
const binary_js_1 = __webpack_require__(56156);
|
|
52496
53324
|
const is_binary_js_1 = __webpack_require__(10003);
|
|
52497
|
-
const debug_1 = __webpack_require__(30887); // debug()
|
|
53325
|
+
const debug_1 = __importDefault(__webpack_require__(30887)); // debug()
|
|
52498
53326
|
const debug = (0, debug_1.default)("socket.io-parser"); // debug()
|
|
52499
53327
|
/**
|
|
52500
53328
|
* These strings must not be used as event names, as they have a special meaning.
|
|
@@ -52634,10 +53462,6 @@ class Decoder extends component_emitter_1.Emitter {
|
|
|
52634
53462
|
packet.type = isBinaryEvent ? PacketType.EVENT : PacketType.ACK;
|
|
52635
53463
|
// binary packet's json
|
|
52636
53464
|
this.reconstructor = new BinaryReconstructor(packet);
|
|
52637
|
-
// no attachments, labeled binary but no binary data to follow
|
|
52638
|
-
if (packet.attachments === 0) {
|
|
52639
|
-
super.emitReserved("decoded", packet);
|
|
52640
|
-
}
|
|
52641
53465
|
} else {
|
|
52642
53466
|
// non-binary full packet
|
|
52643
53467
|
super.emitReserved("decoded", packet);
|
|
@@ -52682,7 +53506,7 @@ class Decoder extends component_emitter_1.Emitter {
|
|
|
52682
53506
|
throw new Error("Illegal attachments");
|
|
52683
53507
|
}
|
|
52684
53508
|
const n = Number(buf);
|
|
52685
|
-
if (!isInteger(n) || n <
|
|
53509
|
+
if (!isInteger(n) || n < 1) {
|
|
52686
53510
|
throw new Error("Illegal attachments");
|
|
52687
53511
|
} else if (n > this.opts.maxAttachments) {
|
|
52688
53512
|
throw new Error("too many attachments");
|
|
@@ -56919,19 +57743,19 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
56919
57743
|
._mainButtonWrapper_zhv80_144 {
|
|
56920
57744
|
padding: 16px;
|
|
56921
57745
|
}
|
|
56922
|
-
.
|
|
57746
|
+
._slideItem_fa7bu_1 {
|
|
56923
57747
|
position: relative;
|
|
56924
57748
|
width: 206px;
|
|
56925
57749
|
/* height: 150px; */
|
|
56926
57750
|
overflow: hidden;
|
|
56927
57751
|
}
|
|
56928
57752
|
|
|
56929
|
-
.
|
|
57753
|
+
._slideItem_fa7bu_1 ._top_fa7bu_8 {
|
|
56930
57754
|
position: relative;
|
|
56931
57755
|
border-radius: var(--cc-bubble-border-radius);
|
|
56932
57756
|
}
|
|
56933
57757
|
|
|
56934
|
-
.
|
|
57758
|
+
._slideItem_fa7bu_1 ._bottom_fa7bu_13 {
|
|
56935
57759
|
border-bottom-left-radius: var(--cc-bubble-border-radius);
|
|
56936
57760
|
border-bottom-right-radius: var(--cc-bubble-border-radius);
|
|
56937
57761
|
border: 1px solid var(--cc-black-80);
|
|
@@ -56942,7 +57766,7 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
56942
57766
|
padding: 8px;
|
|
56943
57767
|
}
|
|
56944
57768
|
|
|
56945
|
-
.
|
|
57769
|
+
._slideItem_fa7bu_1 ._top_fa7bu_8 img {
|
|
56946
57770
|
aspect-ratio: 206/150;
|
|
56947
57771
|
object-fit: cover;
|
|
56948
57772
|
object-position: left;
|
|
@@ -56952,7 +57776,7 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
56952
57776
|
outline: none;
|
|
56953
57777
|
}
|
|
56954
57778
|
|
|
56955
|
-
.
|
|
57779
|
+
._slideItem_fa7bu_1 ._brokenImage_fa7bu_34 {
|
|
56956
57780
|
aspect-ratio: 206/150;
|
|
56957
57781
|
width: 100%;
|
|
56958
57782
|
display: block;
|
|
@@ -56961,9 +57785,9 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
56961
57785
|
background-color: var(--cc-black-80);
|
|
56962
57786
|
}
|
|
56963
57787
|
|
|
56964
|
-
.
|
|
56965
|
-
.
|
|
56966
|
-
.
|
|
57788
|
+
._slideItem_fa7bu_1 ._hasExtraInfo_fa7bu_43,
|
|
57789
|
+
._slideItem_fa7bu_1 ._hasExtraInfo_fa7bu_43 img,
|
|
57790
|
+
._slideItem_fa7bu_1 ._brokenImage_fa7bu_34 {
|
|
56967
57791
|
border-bottom-left-radius: 0px;
|
|
56968
57792
|
border-bottom-right-radius: 0px;
|
|
56969
57793
|
}
|
|
@@ -56972,7 +57796,7 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
56972
57796
|
** Default (legacy) layout: the title overlays the bottom of the image.
|
|
56973
57797
|
** Used when \`galleryCardTitleBelowImage\` is not enabled.
|
|
56974
57798
|
*/
|
|
56975
|
-
.
|
|
57799
|
+
._slideItem_fa7bu_1 ._top_fa7bu_8 h4 {
|
|
56976
57800
|
position: absolute;
|
|
56977
57801
|
margin: 0px;
|
|
56978
57802
|
margin-inline-start: 8px;
|
|
@@ -56980,32 +57804,69 @@ article ._wrapper_zhv80_1 ._list_zhv80_8 ._listItemRoot_zhv80_8 {
|
|
|
56980
57804
|
color: var(--cc-white);
|
|
56981
57805
|
}
|
|
56982
57806
|
|
|
57807
|
+
/*
|
|
57808
|
+
** A card whose image is missing or failed to load shows the grey
|
|
57809
|
+
** \`.brokenImage\` placeholder instead. White text on it is 1.6:1, so the
|
|
57810
|
+
** overlaid title switches to dark text there (WCAG 1.4.3 Contrast Minimum,
|
|
57811
|
+
** CGY-37634).
|
|
57812
|
+
*/
|
|
57813
|
+
._slideItem_fa7bu_1 ._top_fa7bu_8:has(._brokenImage_fa7bu_34) h4 {
|
|
57814
|
+
color: var(--cc-black-10);
|
|
57815
|
+
}
|
|
57816
|
+
|
|
56983
57817
|
/*
|
|
56984
57818
|
** Opt-in layout (galleryCardTitleBelowImage): the title moves into the content
|
|
56985
57819
|
** block beneath the image, so it shares the subtitle's flush margins/padding.
|
|
56986
57820
|
*/
|
|
56987
|
-
.
|
|
56988
|
-
.
|
|
57821
|
+
._slideItem_fa7bu_1 ._bottom_fa7bu_13 p,
|
|
57822
|
+
._slideItem_fa7bu_1 ._bottom_fa7bu_13 h4 {
|
|
56989
57823
|
padding: 0px;
|
|
56990
57824
|
margin: 0px;
|
|
56991
57825
|
}
|
|
56992
57826
|
|
|
56993
|
-
.
|
|
57827
|
+
._slideItem_fa7bu_1 ._bottom_fa7bu_13 ._buttonListItem_fa7bu_82 {
|
|
56994
57828
|
width: 100%;
|
|
56995
57829
|
}
|
|
56996
57830
|
|
|
57831
|
+
/*
|
|
57832
|
+
** default_action link (CGY-37634): wraps the card text inside the content
|
|
57833
|
+
** block (keeping the block's column gap), or sits on the image area when the
|
|
57834
|
+
** block has no text. Buttons are never inside it.
|
|
57835
|
+
*/
|
|
57836
|
+
._slideItem_fa7bu_1 ._link_fa7bu_91 {
|
|
57837
|
+
display: flex;
|
|
57838
|
+
flex-direction: column;
|
|
57839
|
+
gap: 8px;
|
|
57840
|
+
}
|
|
57841
|
+
._slideItem_fa7bu_1 [role="link"] {
|
|
57842
|
+
cursor: pointer;
|
|
57843
|
+
}
|
|
57844
|
+
._slideItem_fa7bu_1 [role="link"]:focus-visible {
|
|
57845
|
+
outline: 2px solid var(--cc-primary-color-focus);
|
|
57846
|
+
outline-offset: 2px;
|
|
57847
|
+
}
|
|
57848
|
+
/* .slideItem is overflow:hidden and the image area fills it edge to edge, so
|
|
57849
|
+
the focus ring is drawn inside the card; an outset ring there is clipped
|
|
57850
|
+
away entirely (WCAG 2.4.7 Focus Visible). The text link sits inside the
|
|
57851
|
+
content block's 8px padding, so its outset ring is never clipped. */
|
|
57852
|
+
._slideItem_fa7bu_1 ._top_fa7bu_8[role="link"]:focus-visible {
|
|
57853
|
+
outline-offset: -2px;
|
|
57854
|
+
}
|
|
57855
|
+
|
|
56997
57856
|
/*
|
|
56998
57857
|
** SWIPER MAIN
|
|
56999
57858
|
** The following styles are a porting from the original swiper/css and related modules.
|
|
57000
57859
|
** The idea is to integrate with modules CSS in order to increase the specificity
|
|
57001
57860
|
** and avoid conflicts on consumer apps
|
|
57002
57861
|
*/
|
|
57003
|
-
article .swiper.
|
|
57862
|
+
article .swiper._wrapper_fa7bu_117 {
|
|
57004
57863
|
margin-left: -20px;
|
|
57005
57864
|
margin-right: -20px;
|
|
57006
57865
|
padding-left: 20px;
|
|
57007
57866
|
padding-right: 20px;
|
|
57008
|
-
|
|
57867
|
+
/* The pagination row is in normal flow below the slides (see
|
|
57868
|
+
.swiper-pagination); 2px clearance under it, matching the gap above it. */
|
|
57869
|
+
padding-bottom: 2px;
|
|
57009
57870
|
padding-top: 0px;
|
|
57010
57871
|
position: relative;
|
|
57011
57872
|
overflow: hidden;
|
|
@@ -57014,7 +57875,7 @@ article .swiper._wrapper_ldua9_82 {
|
|
|
57014
57875
|
z-index: 1;
|
|
57015
57876
|
display: block;
|
|
57016
57877
|
}
|
|
57017
|
-
article .swiper.
|
|
57878
|
+
article .swiper._wrapper_fa7bu_117 .swiper-wrapper {
|
|
57018
57879
|
position: relative;
|
|
57019
57880
|
width: 100%;
|
|
57020
57881
|
/* height: 100%; */
|
|
@@ -57024,15 +57885,15 @@ article .swiper._wrapper_ldua9_82 .swiper-wrapper {
|
|
|
57024
57885
|
transition-timing-function: initial;
|
|
57025
57886
|
box-sizing: content-box;
|
|
57026
57887
|
}
|
|
57027
|
-
article .swiper.
|
|
57028
|
-
article .swiper.
|
|
57029
|
-
article .swiper.
|
|
57888
|
+
article .swiper._wrapper_fa7bu_117 .swiper-android .swiper-slide,
|
|
57889
|
+
article .swiper._wrapper_fa7bu_117 .swiper-ios .swiper-slide,
|
|
57890
|
+
article .swiper._wrapper_fa7bu_117 .swiper-wrapper {
|
|
57030
57891
|
transform: translate3d(0px, 0, 0);
|
|
57031
57892
|
}
|
|
57032
|
-
article .swiper.
|
|
57893
|
+
article .swiper._wrapper_fa7bu_117 .swiper-horizontal {
|
|
57033
57894
|
touch-action: pan-y;
|
|
57034
57895
|
}
|
|
57035
|
-
article .swiper.
|
|
57896
|
+
article .swiper._wrapper_fa7bu_117 .swiper-slide {
|
|
57036
57897
|
flex-shrink: 0;
|
|
57037
57898
|
width: 100%;
|
|
57038
57899
|
height: 100%;
|
|
@@ -57040,15 +57901,15 @@ article .swiper._wrapper_ldua9_82 .swiper-slide {
|
|
|
57040
57901
|
transition-property: transform;
|
|
57041
57902
|
display: block;
|
|
57042
57903
|
}
|
|
57043
|
-
article .swiper.
|
|
57904
|
+
article .swiper._wrapper_fa7bu_117 .swiper-slide-invisible-blank {
|
|
57044
57905
|
visibility: hidden;
|
|
57045
57906
|
}
|
|
57046
57907
|
|
|
57047
57908
|
/*
|
|
57048
57909
|
** SWIPER PAGINATION
|
|
57049
57910
|
*/
|
|
57050
|
-
article .swiper.
|
|
57051
|
-
article .swiper.
|
|
57911
|
+
article .swiper._wrapper_fa7bu_117 .gallery-button-prev,
|
|
57912
|
+
article .swiper._wrapper_fa7bu_117 .gallery-button-next {
|
|
57052
57913
|
position: absolute;
|
|
57053
57914
|
top: calc(150px / 2 - 8px);
|
|
57054
57915
|
z-index: 10;
|
|
@@ -57061,68 +57922,104 @@ article .swiper._wrapper_ldua9_82 .gallery-button-next {
|
|
|
57061
57922
|
display: flex;
|
|
57062
57923
|
align-items: center;
|
|
57063
57924
|
}
|
|
57064
|
-
article .swiper.
|
|
57925
|
+
article .swiper._wrapper_fa7bu_117 .gallery-button-prev {
|
|
57065
57926
|
left: 20px;
|
|
57066
57927
|
}
|
|
57067
|
-
article .swiper.
|
|
57928
|
+
article .swiper._wrapper_fa7bu_117 .gallery-button-next {
|
|
57068
57929
|
right: 20px;
|
|
57069
57930
|
transform: rotate(180deg);
|
|
57070
57931
|
}
|
|
57071
|
-
article .swiper.
|
|
57932
|
+
article .swiper._wrapper_fa7bu_117 .gallery-button-prev:dir(rtl) {
|
|
57072
57933
|
left: unset;
|
|
57073
57934
|
right: 20px;
|
|
57074
57935
|
transform: rotate(180deg);
|
|
57075
57936
|
}
|
|
57076
|
-
article .swiper.
|
|
57937
|
+
article .swiper._wrapper_fa7bu_117 .gallery-button-next:dir(rtl) {
|
|
57077
57938
|
right: unset;
|
|
57078
57939
|
left: 20px;
|
|
57079
57940
|
transform: rotate(0deg);
|
|
57080
57941
|
}
|
|
57081
|
-
article .swiper.
|
|
57942
|
+
article .swiper._wrapper_fa7bu_117 .swiper-button-disabled {
|
|
57082
57943
|
opacity: 0;
|
|
57083
57944
|
}
|
|
57084
57945
|
|
|
57085
57946
|
/*
|
|
57086
57947
|
** SWIPER NAVIGATION
|
|
57087
57948
|
*/
|
|
57088
|
-
|
|
57089
|
-
|
|
57949
|
+
/*
|
|
57950
|
+
** The pagination row is in normal flow beneath the slides. Bullets are fixed
|
|
57951
|
+
** 24px targets (WCAG 2.5.8) and wrap onto further rows when more than fit in
|
|
57952
|
+
** one; in flow, extra rows grow the gallery downwards rather than overlapping
|
|
57953
|
+
** the cards. A single row adds 28px: 2px gap + 24px row + 2px container
|
|
57954
|
+
** padding.
|
|
57955
|
+
*/
|
|
57956
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination {
|
|
57957
|
+
position: relative;
|
|
57090
57958
|
text-align: center;
|
|
57091
57959
|
transition: 300ms opacity;
|
|
57092
57960
|
transform: translate3d(0, 0, 0);
|
|
57093
57961
|
z-index: 10;
|
|
57094
57962
|
}
|
|
57095
|
-
article .swiper.
|
|
57963
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination.swiper-pagination-hidden {
|
|
57096
57964
|
opacity: 0;
|
|
57097
57965
|
}
|
|
57098
|
-
article .swiper.
|
|
57099
|
-
article .swiper.
|
|
57966
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-disabled > .swiper-pagination,
|
|
57967
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination.swiper-pagination-disabled {
|
|
57100
57968
|
display: none !important;
|
|
57101
57969
|
}
|
|
57102
|
-
article .swiper.
|
|
57103
|
-
article .swiper.
|
|
57104
|
-
|
|
57105
|
-
left: 0;
|
|
57970
|
+
article .swiper._wrapper_fa7bu_117 .swiper-horizontal > .swiper-pagination-bullets,
|
|
57971
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullets.swiper-pagination-horizontal {
|
|
57972
|
+
margin-top: 2px;
|
|
57106
57973
|
width: 100%;
|
|
57974
|
+
display: flex;
|
|
57975
|
+
flex-wrap: wrap;
|
|
57976
|
+
justify-content: center;
|
|
57977
|
+
align-items: center;
|
|
57107
57978
|
}
|
|
57108
|
-
|
|
57979
|
+
/*
|
|
57980
|
+
** Each bullet is a 24×24px pointer/focus target (WCAG 2.5.8 Target Size
|
|
57981
|
+
** Minimum, CGY-37634). The visible 6px dot is drawn by ::before, so the look
|
|
57982
|
+
** is unchanged while the hit area grows around it; focus styling therefore
|
|
57983
|
+
** targets ::before too.
|
|
57984
|
+
*/
|
|
57985
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullet {
|
|
57986
|
+
/* Fixed-size flex item: the 24px target never shrinks when the row is
|
|
57987
|
+
full; overflowing bullets wrap (see .swiper-pagination). */
|
|
57988
|
+
flex: 0 0 24px;
|
|
57989
|
+
width: 24px;
|
|
57990
|
+
height: 24px;
|
|
57991
|
+
display: inline-flex;
|
|
57992
|
+
align-items: center;
|
|
57993
|
+
justify-content: center;
|
|
57994
|
+
/* No border-radius here: browsers clip pointer hit-testing to the rounded
|
|
57995
|
+
shape, which would shrink the target back below 24×24. */
|
|
57996
|
+
background: transparent;
|
|
57997
|
+
opacity: 1;
|
|
57998
|
+
}
|
|
57999
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullet::before {
|
|
58000
|
+
content: "";
|
|
57109
58001
|
width: 6px;
|
|
57110
58002
|
height: 6px;
|
|
57111
|
-
display: inline-block;
|
|
57112
58003
|
border-radius: 50%;
|
|
57113
|
-
background: var(--cc-black-50);
|
|
57114
|
-
|
|
58004
|
+
background-color: var(--cc-black-50);
|
|
58005
|
+
}
|
|
58006
|
+
/* Declared before the focus rules (and lower in specificity) so a focused
|
|
58007
|
+
active bullet always shows the focus colour, not the active black. */
|
|
58008
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullet-active::before {
|
|
58009
|
+
background-color: black;
|
|
57115
58010
|
}
|
|
57116
|
-
article .swiper.
|
|
58011
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullet:focus {
|
|
58012
|
+
outline: none;
|
|
58013
|
+
}
|
|
58014
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullet:focus::before {
|
|
57117
58015
|
background-color: var(--cc-primary-color-focus);
|
|
57118
58016
|
box-shadow: 0 0 0 4px var(--cc-primary-color-opacity-10);
|
|
57119
|
-
outline: none;
|
|
57120
58017
|
}
|
|
57121
|
-
article .swiper.
|
|
58018
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullet:focus-visible::before {
|
|
57122
58019
|
outline: 2px solid var(--cc-primary-color-focus);
|
|
57123
58020
|
outline-offset: 2px;
|
|
57124
58021
|
}
|
|
57125
|
-
article .swiper.
|
|
58022
|
+
article .swiper._wrapper_fa7bu_117 button.swiper-pagination-bullet {
|
|
57126
58023
|
border: none;
|
|
57127
58024
|
margin: 0;
|
|
57128
58025
|
padding: 0;
|
|
@@ -57130,23 +58027,20 @@ article .swiper._wrapper_ldua9_82 button.swiper-pagination-bullet {
|
|
|
57130
58027
|
-webkit-appearance: none;
|
|
57131
58028
|
appearance: none;
|
|
57132
58029
|
}
|
|
57133
|
-
article .swiper.
|
|
58030
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-clickable .swiper-pagination-bullet {
|
|
57134
58031
|
cursor: pointer;
|
|
57135
58032
|
}
|
|
57136
|
-
article .swiper.
|
|
58033
|
+
article .swiper._wrapper_fa7bu_117 .swiper-pagination-bullet:only-child {
|
|
57137
58034
|
display: none !important;
|
|
57138
58035
|
}
|
|
57139
|
-
article .swiper._wrapper_ldua9_82 .swiper-pagination-bullet-active {
|
|
57140
|
-
opacity: 1;
|
|
57141
|
-
background-color: black;
|
|
57142
|
-
}
|
|
57143
58036
|
article
|
|
57144
|
-
.swiper.
|
|
58037
|
+
.swiper._wrapper_fa7bu_117
|
|
57145
58038
|
.swiper-horizontal > .swiper-pagination-bullets .swiper-pagination-bullet,
|
|
57146
58039
|
article
|
|
57147
|
-
.swiper.
|
|
58040
|
+
.swiper._wrapper_fa7bu_117
|
|
57148
58041
|
.swiper-pagination-horizontal.swiper-pagination-bullets .swiper-pagination-bullet {
|
|
57149
|
-
|
|
58042
|
+
/* Adjacent 24px targets touch but never overlap (WCAG 2.5.8). */
|
|
58043
|
+
margin: 0;
|
|
57150
58044
|
}
|
|
57151
58045
|
._wrapper_1h4l4_1 {
|
|
57152
58046
|
background-color: white;
|
|
@@ -59322,6 +60216,14 @@ const replaceUrlsWithHTMLanchorElem = text2 => {
|
|
|
59322
60216
|
});
|
|
59323
60217
|
return enhancedText;
|
|
59324
60218
|
};
|
|
60219
|
+
const htmlToPlainText = html2 => {
|
|
60220
|
+
if (!html2) return "";
|
|
60221
|
+
if (typeof DOMParser !== "undefined") {
|
|
60222
|
+
const doc = new DOMParser().parseFromString(html2, "text/html");
|
|
60223
|
+
return (doc.body?.textContent ?? "").trim();
|
|
60224
|
+
}
|
|
60225
|
+
return html2.replace(/<[^>]*>/g, "").trim();
|
|
60226
|
+
};
|
|
59325
60227
|
const getFocusableElements = element2 => {
|
|
59326
60228
|
const interactiveEls = element2?.querySelectorAll('a[href], button, input, textarea, select, details,[tabindex]:not([tabindex="-1"])');
|
|
59327
60229
|
const interactiveElsArray = interactiveEls && Array.from(interactiveEls);
|
|
@@ -59886,7 +60788,7 @@ function createDOMPurify() {
|
|
|
59886
60788
|
NodeFilter: NodeFilter2,
|
|
59887
60789
|
NamedNodeMap = window2.NamedNodeMap || window2.MozNamedAttrMap,
|
|
59888
60790
|
HTMLFormElement,
|
|
59889
|
-
DOMParser,
|
|
60791
|
+
DOMParser: DOMParser2,
|
|
59890
60792
|
trustedTypes: trustedTypes2
|
|
59891
60793
|
} = window2;
|
|
59892
60794
|
const ElementPrototype = Element2.prototype;
|
|
@@ -60248,7 +61150,7 @@ function createDOMPurify() {
|
|
|
60248
61150
|
const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
|
|
60249
61151
|
if (NAMESPACE === HTML_NAMESPACE) {
|
|
60250
61152
|
try {
|
|
60251
|
-
doc = new
|
|
61153
|
+
doc = new DOMParser2().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
|
|
60252
61154
|
} catch (_) {}
|
|
60253
61155
|
}
|
|
60254
61156
|
if (!doc || !doc.documentElement) {
|
|
@@ -68977,7 +69879,7 @@ function linkReference$1(state, node2) {
|
|
|
68977
69879
|
state.patch(node2, result);
|
|
68978
69880
|
return state.applyData(node2, result);
|
|
68979
69881
|
}
|
|
68980
|
-
function link$
|
|
69882
|
+
function link$2(state, node2) {
|
|
68981
69883
|
const properties = {
|
|
68982
69884
|
href: normalizeUri(node2.url)
|
|
68983
69885
|
};
|
|
@@ -69280,7 +70182,7 @@ const handlers = {
|
|
|
69280
70182
|
image: image$1,
|
|
69281
70183
|
inlineCode: inlineCode$1,
|
|
69282
70184
|
linkReference: linkReference$1,
|
|
69283
|
-
link: link$
|
|
70185
|
+
link: link$2,
|
|
69284
70186
|
listItem: listItem$1,
|
|
69285
70187
|
list: list$3,
|
|
69286
70188
|
paragraph: paragraph$2,
|
|
@@ -82801,8 +83703,8 @@ function formatLinkAsAutolink(node2, state) {
|
|
|
82801
83703
|
// references don’t work), space, or angle brackets…
|
|
82802
83704
|
!/[\0- <>\u007F]/.test(node2.url));
|
|
82803
83705
|
}
|
|
82804
|
-
link.peek = linkPeek;
|
|
82805
|
-
function link(node2, _, state, info) {
|
|
83706
|
+
link$1.peek = linkPeek;
|
|
83707
|
+
function link$1(node2, _, state, info) {
|
|
82806
83708
|
const quote = checkQuote(state);
|
|
82807
83709
|
const suffix = quote === '"' ? "Quote" : "Apostrophe";
|
|
82808
83710
|
const tracker = state.createTracker(info);
|
|
@@ -83108,7 +84010,7 @@ const handle = {
|
|
|
83108
84010
|
image,
|
|
83109
84011
|
imageReference,
|
|
83110
84012
|
inlineCode,
|
|
83111
|
-
link,
|
|
84013
|
+
link: link$1,
|
|
83112
84014
|
linkReference,
|
|
83113
84015
|
list: list$2,
|
|
83114
84016
|
listItem,
|
|
@@ -84975,25 +85877,15 @@ const LightboxHeader = () => {
|
|
|
84975
85877
|
const {
|
|
84976
85878
|
config: config2
|
|
84977
85879
|
} = useMessageContext();
|
|
84978
|
-
const
|
|
85880
|
+
const downloadButton2 = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
|
|
84979
85881
|
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => {
|
|
84980
85882
|
setTimeout(() => {
|
|
84981
|
-
|
|
85883
|
+
downloadButton2.current?.focus();
|
|
84982
85884
|
}, 100);
|
|
84983
85885
|
}, []);
|
|
84984
85886
|
const handleDownload = () => {
|
|
84985
85887
|
window.open(url, "_blank");
|
|
84986
85888
|
};
|
|
84987
|
-
const handleKeyDownload = event => {
|
|
84988
|
-
event.key === "Enter" && handleDownload();
|
|
84989
|
-
};
|
|
84990
|
-
const handleKeyClose = event => {
|
|
84991
|
-
if (event.key === "Tab" || event.shiftKey) {
|
|
84992
|
-
firstButton.current?.focus();
|
|
84993
|
-
event.preventDefault();
|
|
84994
|
-
}
|
|
84995
|
-
event.code === "Enter" && onClose && onClose();
|
|
84996
|
-
};
|
|
84997
85889
|
const downloadFullsizeImageLabel = config2?.settings.customTranslations?.ariaLabels?.downloadFullsizeImage || "Download full-size image";
|
|
84998
85890
|
const closeFullsizeImageModalLabel = config2?.settings.customTranslations?.ariaLabels?.closeFullsizeImageModal || "Close full-size image viewer";
|
|
84999
85891
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
@@ -85004,15 +85896,13 @@ const LightboxHeader = () => {
|
|
|
85004
85896
|
}), /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
85005
85897
|
className: classes$h.iconsGroup,
|
|
85006
85898
|
children: [/* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
|
|
85007
|
-
ref:
|
|
85899
|
+
ref: downloadButton2,
|
|
85008
85900
|
onClick: handleDownload,
|
|
85009
|
-
onKeyDown: handleKeyDownload,
|
|
85010
85901
|
"aria-label": downloadFullsizeImageLabel,
|
|
85011
85902
|
className: classes$h.icon,
|
|
85012
85903
|
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(SvgDownload, {})
|
|
85013
85904
|
}), /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("button", {
|
|
85014
85905
|
onClick: onClose,
|
|
85015
|
-
onKeyDown: handleKeyClose,
|
|
85016
85906
|
"aria-label": closeFullsizeImageModalLabel,
|
|
85017
85907
|
className: classes$h.icon,
|
|
85018
85908
|
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(SvgClose, {})
|
|
@@ -85029,12 +85919,34 @@ const Lightbox = () => {
|
|
|
85029
85919
|
const {
|
|
85030
85920
|
config: config2
|
|
85031
85921
|
} = useMessageContext();
|
|
85922
|
+
const dialogRef = (0,react__WEBPACK_IMPORTED_MODULE_1__.useRef)(null);
|
|
85032
85923
|
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => {
|
|
85033
|
-
const
|
|
85034
|
-
event.
|
|
85924
|
+
const handleKeyDown = event => {
|
|
85925
|
+
if (event.key === "Escape" || event.code === "Escape") {
|
|
85926
|
+
onClose && onClose();
|
|
85927
|
+
return;
|
|
85928
|
+
}
|
|
85929
|
+
if (event.key !== "Tab" || !dialogRef.current) return;
|
|
85930
|
+
const {
|
|
85931
|
+
firstFocusable,
|
|
85932
|
+
lastFocusable
|
|
85933
|
+
} = getFocusableElements(dialogRef.current);
|
|
85934
|
+
if (!firstFocusable || !lastFocusable) return;
|
|
85935
|
+
const active = document.activeElement;
|
|
85936
|
+
const focusInsideDialog = !!active && dialogRef.current.contains(active);
|
|
85937
|
+
if (!focusInsideDialog) {
|
|
85938
|
+
event.preventDefault();
|
|
85939
|
+
(event.shiftKey ? lastFocusable : firstFocusable).focus();
|
|
85940
|
+
} else if (event.shiftKey && active === firstFocusable) {
|
|
85941
|
+
event.preventDefault();
|
|
85942
|
+
lastFocusable.focus();
|
|
85943
|
+
} else if (!event.shiftKey && active === lastFocusable) {
|
|
85944
|
+
event.preventDefault();
|
|
85945
|
+
firstFocusable.focus();
|
|
85946
|
+
}
|
|
85035
85947
|
};
|
|
85036
|
-
window.addEventListener("keydown",
|
|
85037
|
-
return () => window.removeEventListener("keydown",
|
|
85948
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
85949
|
+
return () => window.removeEventListener("keydown", handleKeyDown);
|
|
85038
85950
|
}, [onClose]);
|
|
85039
85951
|
const handleOnClickBackdrop = event => {
|
|
85040
85952
|
event.preventDefault();
|
|
@@ -85049,7 +85961,9 @@ const Lightbox = () => {
|
|
|
85049
85961
|
};
|
|
85050
85962
|
const lightboxLabel = config2?.settings.customTranslations?.ariaLabels?.fullSizeImageViewerTitle || "Full-size image viewer";
|
|
85051
85963
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
85964
|
+
ref: dialogRef,
|
|
85052
85965
|
role: "dialog",
|
|
85966
|
+
"aria-modal": "true",
|
|
85053
85967
|
"aria-label": lightboxLabel,
|
|
85054
85968
|
className: classes$h.wrapper,
|
|
85055
85969
|
children: [/* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
|
|
@@ -85058,7 +85972,7 @@ const Lightbox = () => {
|
|
|
85058
85972
|
children: /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
85059
85973
|
className: classes$h.fullImage,
|
|
85060
85974
|
"data-test": "image-lightbox",
|
|
85061
|
-
alt: altText,
|
|
85975
|
+
alt: altText || "",
|
|
85062
85976
|
src: url,
|
|
85063
85977
|
onClick: handleOnClickImage,
|
|
85064
85978
|
onTouchMove: handleOnSwipeImage
|
|
@@ -92668,13 +93582,14 @@ const List = () => {
|
|
|
92668
93582
|
})]
|
|
92669
93583
|
});
|
|
92670
93584
|
};
|
|
92671
|
-
const slideItem = "
|
|
92672
|
-
const top = "
|
|
92673
|
-
const bottom = "
|
|
92674
|
-
const brokenImage = "
|
|
92675
|
-
const hasExtraInfo = "
|
|
92676
|
-
const buttonListItem = "
|
|
92677
|
-
const
|
|
93585
|
+
const slideItem = "_slideItem_fa7bu_1";
|
|
93586
|
+
const top = "_top_fa7bu_8";
|
|
93587
|
+
const bottom = "_bottom_fa7bu_13";
|
|
93588
|
+
const brokenImage = "_brokenImage_fa7bu_34";
|
|
93589
|
+
const hasExtraInfo = "_hasExtraInfo_fa7bu_43";
|
|
93590
|
+
const buttonListItem = "_buttonListItem_fa7bu_82";
|
|
93591
|
+
const link = "_link_fa7bu_91";
|
|
93592
|
+
const wrapper$1 = "_wrapper_fa7bu_117";
|
|
92678
93593
|
const classes$8 = {
|
|
92679
93594
|
slideItem,
|
|
92680
93595
|
top,
|
|
@@ -92682,6 +93597,7 @@ const classes$8 = {
|
|
|
92682
93597
|
brokenImage,
|
|
92683
93598
|
hasExtraInfo,
|
|
92684
93599
|
buttonListItem,
|
|
93600
|
+
link,
|
|
92685
93601
|
wrapper: wrapper$1
|
|
92686
93602
|
};
|
|
92687
93603
|
const getGalleryContent = (elements, isSanitizeEnabled, customAllowedHtmlTags) => {
|
|
@@ -98448,6 +99364,18 @@ const SwiperSlide = /* @__PURE__ */(0,react__WEBPACK_IMPORTED_MODULE_1__.forward
|
|
|
98448
99364
|
})));
|
|
98449
99365
|
});
|
|
98450
99366
|
SwiperSlide.displayName = "SwiperSlide";
|
|
99367
|
+
const getHostname = url => {
|
|
99368
|
+
try {
|
|
99369
|
+
const {
|
|
99370
|
+
protocol,
|
|
99371
|
+
hostname
|
|
99372
|
+
} = new URL(url);
|
|
99373
|
+
return protocol === "http:" || protocol === "https:" ? hostname : "";
|
|
99374
|
+
} catch {
|
|
99375
|
+
return "";
|
|
99376
|
+
}
|
|
99377
|
+
};
|
|
99378
|
+
const NESTED_CONTROL_SELECTOR = "a[href], button, input, select, textarea, summary";
|
|
98451
99379
|
const GalleryItem = props2 => {
|
|
98452
99380
|
const {
|
|
98453
99381
|
slide: slide2,
|
|
@@ -98468,7 +99396,6 @@ const GalleryItem = props2 => {
|
|
|
98468
99396
|
messageParams,
|
|
98469
99397
|
"data-message-id": dataMessageId
|
|
98470
99398
|
} = useMessageContext();
|
|
98471
|
-
const hasExtraInfo2 = subtitle || buttons2 && buttons2?.length > 0;
|
|
98472
99399
|
const titleBelowImage = !!config2?.settings?.layout?.galleryCardTitleBelowImage;
|
|
98473
99400
|
const [isImageBroken, setImageBroken] = (0,react__WEBPACK_IMPORTED_MODULE_1__.useState)(false);
|
|
98474
99401
|
const {
|
|
@@ -98476,22 +99403,25 @@ const GalleryItem = props2 => {
|
|
|
98476
99403
|
} = useSanitize();
|
|
98477
99404
|
const titleHtml = processHTML(title || "");
|
|
98478
99405
|
const subtitleHtml = processHTML(subtitle || "");
|
|
98479
|
-
const hasTitle = !!titleHtml;
|
|
99406
|
+
const hasTitle = !!titleHtml.trim();
|
|
99407
|
+
const hasSubtitle = !!subtitleHtml.trim();
|
|
99408
|
+
const hasButtons = !!buttons2 && buttons2.length > 0;
|
|
99409
|
+
const hasExtraInfo2 = hasSubtitle || hasButtons;
|
|
98480
99410
|
const showContentBlock = titleBelowImage && hasTitle || hasExtraInfo2;
|
|
98481
99411
|
const titleId = useRandomId("webchatCarouselTemplateTitle");
|
|
98482
99412
|
const subtitleId = useRandomId("webchatCarouselTemplateSubtitle");
|
|
98483
99413
|
const shouldBeDisabled = messageParams?.isConversationEnded;
|
|
98484
99414
|
const opensInNewTab = config2?.settings.customTranslations?.ariaLabels?.opensInNewTab ?? "Opens in new tab";
|
|
98485
|
-
const
|
|
98486
|
-
|
|
98487
|
-
const
|
|
98488
|
-
if (
|
|
98489
|
-
|
|
98490
|
-
|
|
99415
|
+
const linkUrl = default_action?.url ? config2?.settings?.layout?.disableUrlButtonSanitization ? default_action.url : distExports.sanitizeUrl(default_action.url) : "";
|
|
99416
|
+
const handleClick = event => {
|
|
99417
|
+
const target = event.target;
|
|
99418
|
+
if (target !== event.currentTarget && target.closest(NESTED_CONTROL_SELECTOR)) return;
|
|
99419
|
+
if (!linkUrl || linkUrl === "about:blank") return;
|
|
99420
|
+
window.open(linkUrl);
|
|
98491
99421
|
};
|
|
98492
99422
|
const handleKeyDown = event => {
|
|
98493
99423
|
if (default_action && event.key === "Enter") {
|
|
98494
|
-
handleClick();
|
|
99424
|
+
handleClick(event);
|
|
98495
99425
|
}
|
|
98496
99426
|
};
|
|
98497
99427
|
const titleElement = /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(Typography, {
|
|
@@ -98503,35 +99433,54 @@ const GalleryItem = props2 => {
|
|
|
98503
99433
|
className: "webchat-carousel-template-title",
|
|
98504
99434
|
id: titleId
|
|
98505
99435
|
});
|
|
99436
|
+
const subtitleElement = hasSubtitle && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(Typography, {
|
|
99437
|
+
variant: "body-regular",
|
|
99438
|
+
dangerouslySetInnerHTML: {
|
|
99439
|
+
__html: subtitleHtml
|
|
99440
|
+
},
|
|
99441
|
+
id: subtitleId,
|
|
99442
|
+
className: "webchat-carousel-template-subtitle"
|
|
99443
|
+
});
|
|
99444
|
+
const hasBlockText = hasSubtitle || titleBelowImage && hasTitle;
|
|
99445
|
+
const linkTarget = linkUrl && linkUrl !== "about:blank" ? hasBlockText ? "text" : "top" : null;
|
|
99446
|
+
const titleText = linkTarget ? htmlToPlainText(titleHtml) : "";
|
|
99447
|
+
const subtitleText = linkTarget ? htmlToPlainText(subtitleHtml) : "";
|
|
99448
|
+
const linkName = titleText || subtitleText || image_alt_text || getHostname(linkUrl);
|
|
99449
|
+
const linkLabel = [linkName, opensInNewTab].filter(Boolean).join(". ");
|
|
99450
|
+
const linkDescribedBy = hasSubtitle && linkName !== subtitleText ? subtitleId : void 0;
|
|
99451
|
+
const cardLink = (className, children) => /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
|
|
99452
|
+
className,
|
|
99453
|
+
role: "link",
|
|
99454
|
+
tabIndex: 0,
|
|
99455
|
+
onClick: handleClick,
|
|
99456
|
+
onKeyDown: handleKeyDown,
|
|
99457
|
+
"aria-label": linkLabel,
|
|
99458
|
+
"aria-describedby": linkDescribedBy,
|
|
99459
|
+
children
|
|
99460
|
+
});
|
|
99461
|
+
const topClassName = classnames$1(classes$8.top, showContentBlock && classes$8.hasExtraInfo);
|
|
99462
|
+
const imageArea = /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
|
|
99463
|
+
children: [!titleBelowImage && hasTitle && titleElement, isImageBroken ? /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("span", {
|
|
99464
|
+
className: classes$8.brokenImage
|
|
99465
|
+
}) : /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
99466
|
+
src: image_url,
|
|
99467
|
+
alt: image_alt_text || "",
|
|
99468
|
+
className: classes$8.slideImage,
|
|
99469
|
+
onError: () => setImageBroken(true)
|
|
99470
|
+
})]
|
|
99471
|
+
});
|
|
99472
|
+
const blockText = /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)(react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.Fragment, {
|
|
99473
|
+
children: [titleBelowImage && hasTitle && titleElement, subtitleElement]
|
|
99474
|
+
});
|
|
98506
99475
|
return /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
98507
99476
|
className: classnames$1("webchat-carousel-template-frame", classes$8.slideItem),
|
|
98508
|
-
children: [/* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.
|
|
98509
|
-
className:
|
|
98510
|
-
children:
|
|
98511
|
-
className: classes$8.brokenImage
|
|
98512
|
-
}) : /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("img", {
|
|
98513
|
-
src: image_url,
|
|
98514
|
-
alt: image_alt_text || "",
|
|
98515
|
-
className: classes$8.slideImage,
|
|
98516
|
-
onError: () => setImageBroken(true)
|
|
98517
|
-
})]
|
|
99477
|
+
children: [linkTarget === "top" ? cardLink(topClassName, imageArea) : /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)("div", {
|
|
99478
|
+
className: topClassName,
|
|
99479
|
+
children: imageArea
|
|
98518
99480
|
}), showContentBlock && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsxs)("div", {
|
|
98519
99481
|
className: classnames$1("webchat-carousel-template-content", classes$8.bottom),
|
|
98520
|
-
onClick: handleClick,
|
|
98521
|
-
onKeyDown: handleKeyDown,
|
|
98522
|
-
role: default_action?.url ? "link" : void 0,
|
|
98523
99482
|
id: contentId,
|
|
98524
|
-
|
|
98525
|
-
"aria-labelledby": default_action?.url && hasTitle ? titleId : void 0,
|
|
98526
|
-
"aria-label": default_action?.url ? `${titleHtml}. ${opensInNewTab}` : void 0,
|
|
98527
|
-
children: [titleBelowImage && hasTitle && titleElement, subtitle && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(Typography, {
|
|
98528
|
-
variant: "body-regular",
|
|
98529
|
-
dangerouslySetInnerHTML: {
|
|
98530
|
-
__html: subtitleHtml
|
|
98531
|
-
},
|
|
98532
|
-
id: subtitleId,
|
|
98533
|
-
className: "webchat-carousel-template-subtitle"
|
|
98534
|
-
}), buttons2 && buttons2?.length > 0 && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(ActionButtons, {
|
|
99483
|
+
children: [linkTarget === "text" ? cardLink(classnames$1("webchat-carousel-template-link", classes$8.link), blockText) : blockText, hasButtons && /* @__PURE__ */(0,react_jsx_runtime__WEBPACK_IMPORTED_MODULE_0__.jsx)(ActionButtons, {
|
|
98535
99484
|
dataMessageId,
|
|
98536
99485
|
buttonClassName: classnames$1(buttonClasses.primaryButton, buttonClasses.actionButton, "webchat-carousel-template-button"),
|
|
98537
99486
|
buttonListItemClassName: classes$8.buttonListItem,
|
|
@@ -98563,22 +99512,24 @@ const Gallery = () => {
|
|
|
98563
99512
|
const carouselContentId = useRandomId("webchatCarouselContentButton");
|
|
98564
99513
|
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => {
|
|
98565
99514
|
const chatHistory = document.getElementById("webchatChatHistoryWrapperLiveLogPanel");
|
|
98566
|
-
const
|
|
98567
|
-
const
|
|
99515
|
+
const messageRoot = dataMessageId ? document.querySelector(`[data-message-id="${dataMessageId.replace(/["\\]/g, "\\$&")}"]`) : null;
|
|
99516
|
+
const firstCard = messageRoot?.querySelector(".webchat-carousel-template-frame") ?? document.getElementById(`${carouselContentId}-0`)?.closest(".webchat-carousel-template-frame");
|
|
99517
|
+
const firstLink = firstCard?.querySelector('[role="link"]');
|
|
99518
|
+
const firstButton = firstCard?.getElementsByTagName("button")?.[0];
|
|
98568
99519
|
if (!config2?.settings?.widgetSettings?.enableAutoFocus) return;
|
|
98569
99520
|
if (!chatHistory?.contains(document.activeElement)) return;
|
|
98570
|
-
if (
|
|
99521
|
+
if (firstLink) {
|
|
98571
99522
|
setTimeout(() => {
|
|
98572
|
-
|
|
99523
|
+
firstLink.focus();
|
|
98573
99524
|
}, 200);
|
|
98574
99525
|
} else if (firstButton) {
|
|
98575
99526
|
setTimeout(() => {
|
|
98576
99527
|
firstButton?.focus();
|
|
98577
99528
|
}, 200);
|
|
98578
99529
|
}
|
|
98579
|
-
}, [carouselContentId, config2?.settings?.widgetSettings?.enableAutoFocus]);
|
|
99530
|
+
}, [carouselContentId, dataMessageId, config2?.settings?.widgetSettings?.enableAutoFocus]);
|
|
98580
99531
|
(0,react__WEBPACK_IMPORTED_MODULE_1__.useEffect)(() => {
|
|
98581
|
-
const galleryMessage = document.querySelector(`[data-message-id="${dataMessageId}"]`);
|
|
99532
|
+
const galleryMessage = dataMessageId ? document.querySelector(`[data-message-id="${dataMessageId.replace(/["\\]/g, "\\$&")}"]`) : null;
|
|
98582
99533
|
const swiperWrapper = galleryMessage?.querySelector(".swiper-wrapper");
|
|
98583
99534
|
if (swiperWrapper) {
|
|
98584
99535
|
swiperWrapper.removeAttribute("aria-live");
|
|
@@ -117355,12 +118306,23 @@ function requireL10n() {
|
|
|
117355
118306
|
}
|
|
117356
118307
|
var l10nExports = requireL10n();
|
|
117357
118308
|
const l10n = /* @__PURE__ */getDefaultExportFromCjs(l10nExports);
|
|
118309
|
+
let instanceSeq = 0;
|
|
118310
|
+
function visuallyHide(element2) {
|
|
118311
|
+
element2.style.position = "absolute";
|
|
118312
|
+
element2.style.width = "1px";
|
|
118313
|
+
element2.style.height = "1px";
|
|
118314
|
+
element2.style.overflow = "hidden";
|
|
118315
|
+
element2.style.clip = "rect(0, 0, 0, 0)";
|
|
118316
|
+
element2.style.whiteSpace = "nowrap";
|
|
118317
|
+
}
|
|
117358
118318
|
function customElements(pluginConfig) {
|
|
117359
118319
|
return function (fp) {
|
|
117360
118320
|
const {
|
|
117361
118321
|
arrowIcon: arrowIcon2,
|
|
117362
|
-
customTranslations
|
|
118322
|
+
customTranslations,
|
|
118323
|
+
localeId
|
|
117363
118324
|
} = pluginConfig;
|
|
118325
|
+
const instanceId = ++instanceSeq;
|
|
117364
118326
|
const navKeydownHandlers = /* @__PURE__ */new WeakMap();
|
|
117365
118327
|
const observers = [];
|
|
117366
118328
|
const timeouts = [];
|
|
@@ -117372,12 +118334,7 @@ function customElements(pluginConfig) {
|
|
|
117372
118334
|
liveRegion.setAttribute("aria-live", "assertive");
|
|
117373
118335
|
liveRegion.setAttribute("aria-atomic", "true");
|
|
117374
118336
|
liveRegion.setAttribute("role", "alert");
|
|
117375
|
-
liveRegion
|
|
117376
|
-
liveRegion.style.width = "1px";
|
|
117377
|
-
liveRegion.style.height = "1px";
|
|
117378
|
-
liveRegion.style.overflow = "hidden";
|
|
117379
|
-
liveRegion.style.clip = "rect(0, 0, 0, 0)";
|
|
117380
|
-
liveRegion.style.whiteSpace = "nowrap";
|
|
118337
|
+
visuallyHide(liveRegion);
|
|
117381
118338
|
fp.calendarContainer.appendChild(liveRegion);
|
|
117382
118339
|
}
|
|
117383
118340
|
function announce(message2) {
|
|
@@ -117520,51 +118477,150 @@ function customElements(pluginConfig) {
|
|
|
117520
118477
|
}
|
|
117521
118478
|
}
|
|
117522
118479
|
const GRID_COLS = 7;
|
|
118480
|
+
const hasWeekNumbers = () => !!fp?.config?.weekNumbers;
|
|
118481
|
+
const colOffset = () => hasWeekNumbers() ? 1 : 0;
|
|
118482
|
+
const weekHeaderId = rowIndex => `webchat-datepicker-${instanceId}-week-${rowIndex}`;
|
|
118483
|
+
const weekNumberLabel = () => customTranslations?.ariaLabels?.datePickerWeekNumber || "Week";
|
|
118484
|
+
const dayCellId = index2 => `webchat-datepicker-${instanceId}-day-${index2}`;
|
|
117523
118485
|
function setDateSelectAlly() {
|
|
117524
118486
|
fp?.calendarContainer?.setAttribute("tabindex", "-1");
|
|
117525
|
-
const
|
|
118487
|
+
const rContainer = fp?.calendarContainer?.querySelector(".flatpickr-rContainer");
|
|
118488
|
+
const innerContainer = fp?.calendarContainer?.querySelector(".flatpickr-innerContainer");
|
|
118489
|
+
const grid = hasWeekNumbers() ? innerContainer : rContainer;
|
|
118490
|
+
const layoutWrapper = hasWeekNumbers() ? rContainer : innerContainer;
|
|
117526
118491
|
if (grid) {
|
|
117527
118492
|
grid.setAttribute("role", "grid");
|
|
117528
118493
|
grid.setAttribute("aria-label", customTranslations?.ariaLabels?.datePickerGridLabel || "Calendar");
|
|
117529
118494
|
grid.setAttribute("aria-description", customTranslations?.ariaLabels?.datePickerGridDescription || "Use arrow keys to navigate through dates");
|
|
117530
|
-
grid.setAttribute("aria-colcount", String(GRID_COLS));
|
|
118495
|
+
grid.setAttribute("aria-colcount", String(GRID_COLS + colOffset()));
|
|
117531
118496
|
grid.setAttribute("aria-rowcount", "7");
|
|
117532
118497
|
}
|
|
117533
|
-
|
|
117534
|
-
|
|
117535
|
-
|
|
117536
|
-
|
|
117537
|
-
const
|
|
117538
|
-
if (
|
|
117539
|
-
|
|
117540
|
-
|
|
117541
|
-
}
|
|
117542
|
-
|
|
118498
|
+
layoutWrapper?.removeAttribute("tabindex");
|
|
118499
|
+
layoutWrapper?.removeAttribute("aria-activedescendant");
|
|
118500
|
+
layoutWrapper?.setAttribute("role", "presentation");
|
|
118501
|
+
if (hasWeekNumbers()) fp?.weekWrapper?.setAttribute("role", "presentation");
|
|
118502
|
+
const weekdayRow = fp?.calendarContainer?.querySelector(".flatpickr-weekdays");
|
|
118503
|
+
if (weekdayRow) {
|
|
118504
|
+
weekdayRow.setAttribute("role", "row");
|
|
118505
|
+
weekdayRow.setAttribute("aria-rowindex", "1");
|
|
118506
|
+
}
|
|
118507
|
+
weekdayRow?.querySelector(".flatpickr-weekdaycontainer")?.setAttribute("role", "presentation");
|
|
118508
|
+
const weekdaySpans = fp?.calendarContainer?.querySelectorAll(".flatpickr-weekdaycontainer .flatpickr-weekday");
|
|
117543
118509
|
weekdaySpans?.forEach((span, index2) => {
|
|
117544
118510
|
span.setAttribute("role", "columnheader");
|
|
117545
118511
|
span.setAttribute("abbr", span.textContent?.trim() || "");
|
|
117546
|
-
span.setAttribute("aria-colindex", String(index2 % GRID_COLS + 1));
|
|
118512
|
+
span.setAttribute("aria-colindex", String(index2 % GRID_COLS + 1 + colOffset()));
|
|
118513
|
+
});
|
|
118514
|
+
const visualWeekHeader = fp?.weekWrapper?.querySelector(".flatpickr-weekday");
|
|
118515
|
+
if (visualWeekHeader && weekdayRow) {
|
|
118516
|
+
visualWeekHeader.setAttribute("aria-hidden", "true");
|
|
118517
|
+
let weekHeader = weekdayRow.querySelector(":scope > [data-week-header]");
|
|
118518
|
+
if (!weekHeader) {
|
|
118519
|
+
weekHeader = document.createElement("span");
|
|
118520
|
+
weekHeader.dataset.weekHeader = "true";
|
|
118521
|
+
visuallyHide(weekHeader);
|
|
118522
|
+
weekdayRow.prepend(weekHeader);
|
|
118523
|
+
}
|
|
118524
|
+
weekHeader.id = weekHeaderId(1);
|
|
118525
|
+
weekHeader.setAttribute("role", "columnheader");
|
|
118526
|
+
weekHeader.setAttribute("abbr", visualWeekHeader.textContent?.trim() || "");
|
|
118527
|
+
weekHeader.setAttribute("aria-colindex", "1");
|
|
118528
|
+
weekHeader.textContent = weekNumberLabel();
|
|
118529
|
+
}
|
|
118530
|
+
}
|
|
118531
|
+
function syncWeekNumbers() {
|
|
118532
|
+
if (!hasWeekNumbers() || !fp?.weekNumbers) return;
|
|
118533
|
+
fp.weekNumbers.setAttribute("role", "presentation");
|
|
118534
|
+
const weekCells = Array.from(fp.weekNumbers.querySelectorAll(":scope > .flatpickr-day"));
|
|
118535
|
+
weekCells.forEach((cell, i) => {
|
|
118536
|
+
const rowIndex = i + 2;
|
|
118537
|
+
cell.id = weekHeaderId(rowIndex);
|
|
118538
|
+
cell.setAttribute("role", "rowheader");
|
|
118539
|
+
cell.setAttribute("aria-rowindex", String(rowIndex));
|
|
118540
|
+
cell.setAttribute("aria-colindex", "1");
|
|
118541
|
+
cell.setAttribute("aria-label", `${weekNumberLabel()} ${cell.textContent?.trim()}`);
|
|
117547
118542
|
});
|
|
117548
118543
|
}
|
|
117549
118544
|
function applyDayGridIndices() {
|
|
117550
118545
|
const dayContainer = fp?.calendarContainer?.querySelector(".dayContainer");
|
|
117551
118546
|
if (!dayContainer) return;
|
|
117552
|
-
dayContainer.setAttribute("role", "
|
|
118547
|
+
dayContainer.setAttribute("role", "presentation");
|
|
117553
118548
|
const dayCells = Array.from(dayContainer.querySelectorAll(":scope > .flatpickr-day"));
|
|
117554
118549
|
dayCells.forEach((cell, i) => {
|
|
117555
|
-
cell.
|
|
117556
|
-
|
|
117557
|
-
|
|
117558
|
-
|
|
117559
|
-
|
|
117560
|
-
|
|
117561
|
-
|
|
117562
|
-
day.setAttribute("aria-selected", "true");
|
|
118550
|
+
cell.id = dayCellId(i);
|
|
118551
|
+
const rowIndex = Math.floor(i / GRID_COLS) + 2;
|
|
118552
|
+
cell.setAttribute("aria-rowindex", String(rowIndex));
|
|
118553
|
+
cell.setAttribute("aria-colindex", String(i % GRID_COLS + 1 + colOffset()));
|
|
118554
|
+
const weekHeader = hasWeekNumbers() ? fp?.weekNumbers?.querySelector(`[aria-rowindex="${rowIndex}"]`) : null;
|
|
118555
|
+
if (weekHeader?.id) {
|
|
118556
|
+
cell.setAttribute("aria-describedby", weekHeader.id);
|
|
117563
118557
|
} else {
|
|
117564
|
-
|
|
118558
|
+
cell.removeAttribute("aria-describedby");
|
|
117565
118559
|
}
|
|
117566
118560
|
});
|
|
117567
118561
|
}
|
|
118562
|
+
function syncGridRows() {
|
|
118563
|
+
const daysContainer = fp?.calendarContainer?.querySelector(".flatpickr-days");
|
|
118564
|
+
const dayContainer = daysContainer?.querySelector(":scope > .dayContainer");
|
|
118565
|
+
if (!daysContainer || !dayContainer) return;
|
|
118566
|
+
daysContainer.setAttribute("role", "rowgroup");
|
|
118567
|
+
const dayCells = Array.from(dayContainer.querySelectorAll(":scope > .flatpickr-day"));
|
|
118568
|
+
const rowCount = Math.ceil(dayCells.length / GRID_COLS);
|
|
118569
|
+
for (let r = 0; r < rowCount; r++) {
|
|
118570
|
+
const rowIndex = r + 2;
|
|
118571
|
+
let row = daysContainer.querySelector(`:scope > [data-grid-row="${rowIndex}"]`);
|
|
118572
|
+
if (!row) {
|
|
118573
|
+
row = document.createElement("div");
|
|
118574
|
+
row.dataset.gridRow = String(rowIndex);
|
|
118575
|
+
row.setAttribute("role", "row");
|
|
118576
|
+
visuallyHide(row);
|
|
118577
|
+
daysContainer.appendChild(row);
|
|
118578
|
+
}
|
|
118579
|
+
row.setAttribute("aria-rowindex", String(rowIndex));
|
|
118580
|
+
const owned = dayCells.slice(r * GRID_COLS, (r + 1) * GRID_COLS).map(c => c.id);
|
|
118581
|
+
const weekHeader = hasWeekNumbers() ? fp?.weekNumbers?.querySelector(`[aria-rowindex="${rowIndex}"]`) : null;
|
|
118582
|
+
if (weekHeader?.id) owned.unshift(weekHeader.id);
|
|
118583
|
+
row.setAttribute("aria-owns", owned.join(" "));
|
|
118584
|
+
}
|
|
118585
|
+
}
|
|
118586
|
+
function isSelectedCell(cell) {
|
|
118587
|
+
return cell.classList.contains("selected") || cell.classList.contains("inRange") || cell.classList.contains("startRange") || cell.classList.contains("endRange");
|
|
118588
|
+
}
|
|
118589
|
+
const pendingLabelRestore = /* @__PURE__ */new Set();
|
|
118590
|
+
function syncDayCellState(cell) {
|
|
118591
|
+
if (isSelectedCell(cell)) {
|
|
118592
|
+
cell.setAttribute("aria-selected", "true");
|
|
118593
|
+
} else {
|
|
118594
|
+
cell.removeAttribute("aria-selected");
|
|
118595
|
+
}
|
|
118596
|
+
if (pendingLabelRestore.has(cell)) return;
|
|
118597
|
+
const date = cellDate(cell);
|
|
118598
|
+
if (date) cell.setAttribute("aria-label", getDayLabel(cell, date));
|
|
118599
|
+
}
|
|
118600
|
+
function syncAllDayCells() {
|
|
118601
|
+
getDayCells().forEach(syncDayCellState);
|
|
118602
|
+
}
|
|
118603
|
+
function observeDayCellClasses() {
|
|
118604
|
+
const daysContainer = fp?.calendarContainer?.querySelector(".flatpickr-days");
|
|
118605
|
+
if (!daysContainer || daysContainer.dataset.classObserved === "true") return;
|
|
118606
|
+
daysContainer.dataset.classObserved = "true";
|
|
118607
|
+
const observer = new MutationObserver(records => {
|
|
118608
|
+
const touched = /* @__PURE__ */new Set();
|
|
118609
|
+
records.forEach(record => {
|
|
118610
|
+
const cell = record.target;
|
|
118611
|
+
if (cell.classList?.contains("flatpickr-day") && cell.closest(".dayContainer")) {
|
|
118612
|
+
touched.add(cell);
|
|
118613
|
+
}
|
|
118614
|
+
});
|
|
118615
|
+
touched.forEach(syncDayCellState);
|
|
118616
|
+
});
|
|
118617
|
+
observer.observe(daysContainer, {
|
|
118618
|
+
attributes: true,
|
|
118619
|
+
attributeFilter: ["class"],
|
|
118620
|
+
subtree: true
|
|
118621
|
+
});
|
|
118622
|
+
observers.push(observer);
|
|
118623
|
+
}
|
|
117568
118624
|
function restoreRovingDay() {
|
|
117569
118625
|
const existing = fp?.calendarContainer?.querySelector(".dayContainer .flatpickr-day[tabindex='0']");
|
|
117570
118626
|
setActiveDay(existing || getInitialActiveDay(), {
|
|
@@ -117572,8 +118628,10 @@ function customElements(pluginConfig) {
|
|
|
117572
118628
|
});
|
|
117573
118629
|
}
|
|
117574
118630
|
function refreshGridAfterRebuild() {
|
|
118631
|
+
syncWeekNumbers();
|
|
117575
118632
|
applyDayGridIndices();
|
|
117576
|
-
|
|
118633
|
+
syncGridRows();
|
|
118634
|
+
syncAllDayCells();
|
|
117577
118635
|
restoreRovingDay();
|
|
117578
118636
|
}
|
|
117579
118637
|
function observeDayGrid() {
|
|
@@ -117656,6 +118714,10 @@ function customElements(pluginConfig) {
|
|
|
117656
118714
|
yearInput.setAttribute("tabindex", "0");
|
|
117657
118715
|
}
|
|
117658
118716
|
}
|
|
118717
|
+
function setValueInputAlly() {
|
|
118718
|
+
const headingId = fp?.calendarContainer?.closest('[role="dialog"]')?.getAttribute("aria-labelledby");
|
|
118719
|
+
if (headingId) fp?.input?.setAttribute("aria-labelledby", headingId);
|
|
118720
|
+
}
|
|
117659
118721
|
function setTimeAlly() {
|
|
117660
118722
|
fp?.calendarContainer?.setAttribute("aria-labelledby", "webchatDatePickerHeaderLabel");
|
|
117661
118723
|
if (fp?.config?.enableTime) {
|
|
@@ -117665,17 +118727,95 @@ function customElements(pluginConfig) {
|
|
|
117665
118727
|
minutesField?.setAttribute("tabIndex", "0");
|
|
117666
118728
|
const amPmField = fp?.timeContainer?.getElementsByClassName("flatpickr-am-pm")?.[0];
|
|
117667
118729
|
amPmField?.setAttribute("tabIndex", "0");
|
|
117668
|
-
amPmField?.setAttribute("role", "button");
|
|
117669
118730
|
}
|
|
117670
118731
|
}
|
|
118732
|
+
function getAmPmValue() {
|
|
118733
|
+
return Array.from(fp?.amPM?.childNodes || []).filter(node2 => node2.nodeType === Node.TEXT_NODE).map(node2 => node2.textContent || "").join("").trim();
|
|
118734
|
+
}
|
|
118735
|
+
function setAmPmAlly() {
|
|
118736
|
+
const amPm = fp?.amPM;
|
|
118737
|
+
if (!amPm || amPm.dataset.a11yBound === "true") return;
|
|
118738
|
+
amPm.dataset.a11yBound = "true";
|
|
118739
|
+
const [am, pm] = fp.l10n.amPM;
|
|
118740
|
+
amPm.setAttribute("role", "spinbutton");
|
|
118741
|
+
amPm.setAttribute("aria-label", customTranslations?.ariaLabels?.datePickerAmPm || "AM/PM");
|
|
118742
|
+
amPm.removeAttribute("title");
|
|
118743
|
+
amPm.setAttribute("aria-valuemin", "0");
|
|
118744
|
+
amPm.setAttribute("aria-valuemax", "1");
|
|
118745
|
+
const syncValue = () => {
|
|
118746
|
+
const value = getAmPmValue();
|
|
118747
|
+
if (!value) return;
|
|
118748
|
+
amPm.setAttribute("aria-valuenow", value === am ? "1" : "0");
|
|
118749
|
+
amPm.setAttribute("aria-valuetext", value);
|
|
118750
|
+
};
|
|
118751
|
+
syncValue();
|
|
118752
|
+
const toggle = () => {
|
|
118753
|
+
amPm.click();
|
|
118754
|
+
syncValue();
|
|
118755
|
+
};
|
|
118756
|
+
const setTo = target => {
|
|
118757
|
+
if (getAmPmValue() !== target) toggle();
|
|
118758
|
+
};
|
|
118759
|
+
amPm.addEventListener("keydown", event => {
|
|
118760
|
+
const actions = {
|
|
118761
|
+
ArrowUp: () => setTo(am),
|
|
118762
|
+
ArrowDown: () => setTo(pm),
|
|
118763
|
+
// APG spinbutton: Home = minimum (aria-valuenow 0 = PM), End = maximum (1 = AM).
|
|
118764
|
+
Home: () => setTo(pm),
|
|
118765
|
+
End: () => setTo(am),
|
|
118766
|
+
Enter: toggle,
|
|
118767
|
+
" ": toggle,
|
|
118768
|
+
Spacebar: toggle
|
|
118769
|
+
};
|
|
118770
|
+
const action = actions[event.key];
|
|
118771
|
+
if (!action) return;
|
|
118772
|
+
event.preventDefault();
|
|
118773
|
+
event.stopPropagation();
|
|
118774
|
+
action();
|
|
118775
|
+
});
|
|
118776
|
+
const observer = new MutationObserver(syncValue);
|
|
118777
|
+
observer.observe(amPm, {
|
|
118778
|
+
childList: true,
|
|
118779
|
+
characterData: true,
|
|
118780
|
+
subtree: true
|
|
118781
|
+
});
|
|
118782
|
+
observers.push(observer);
|
|
118783
|
+
}
|
|
117671
118784
|
function getInMonthCells() {
|
|
117672
118785
|
return getDayCells().filter(c => !c.classList.contains("prevMonthDay") && !c.classList.contains("nextMonthDay"));
|
|
117673
118786
|
}
|
|
117674
118787
|
function cellDate(cell) {
|
|
117675
118788
|
return cell.dateObj;
|
|
117676
118789
|
}
|
|
117677
|
-
|
|
117678
|
-
|
|
118790
|
+
const dateFormatter = (() => {
|
|
118791
|
+
if (!localeId) return null;
|
|
118792
|
+
try {
|
|
118793
|
+
return new Intl.DateTimeFormat(localeId.replace("_", "-"), {
|
|
118794
|
+
weekday: "long",
|
|
118795
|
+
year: "numeric",
|
|
118796
|
+
month: "long",
|
|
118797
|
+
day: "numeric"
|
|
118798
|
+
});
|
|
118799
|
+
} catch {
|
|
118800
|
+
return null;
|
|
118801
|
+
}
|
|
118802
|
+
})();
|
|
118803
|
+
function formatSpokenDate(date) {
|
|
118804
|
+
if (dateFormatter) return dateFormatter.format(date);
|
|
118805
|
+
const weekday = fp.l10n.weekdays.longhand[date.getDay()];
|
|
118806
|
+
const month = fp.l10n.months.longhand[date.getMonth()];
|
|
118807
|
+
return `${weekday}, ${month} ${date.getDate()}, ${date.getFullYear()}`;
|
|
118808
|
+
}
|
|
118809
|
+
function getDayLabel(cell, date) {
|
|
118810
|
+
const labels = customTranslations?.ariaLabels;
|
|
118811
|
+
const parts = [formatSpokenDate(date)];
|
|
118812
|
+
if (cell.classList.contains("startRange")) {
|
|
118813
|
+
parts.push(labels?.datePickerRangeStart || "start of range");
|
|
118814
|
+
}
|
|
118815
|
+
if (cell.classList.contains("endRange")) {
|
|
118816
|
+
parts.push(labels?.datePickerRangeEnd || "end of range");
|
|
118817
|
+
}
|
|
118818
|
+
return parts.join(", ");
|
|
117679
118819
|
}
|
|
117680
118820
|
function sameYMD(a, b) {
|
|
117681
118821
|
return !!a && a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
|
|
@@ -117828,11 +118968,12 @@ function customElements(pluginConfig) {
|
|
|
117828
118968
|
day.setAttribute("tabindex", day === target ? "0" : "-1");
|
|
117829
118969
|
});
|
|
117830
118970
|
}
|
|
117831
|
-
|
|
117832
|
-
|
|
118971
|
+
if (target.getAttribute("aria-label") && !pendingLabelRestore.has(target)) {
|
|
118972
|
+
pendingLabelRestore.add(target);
|
|
117833
118973
|
target.setAttribute("aria-label", "");
|
|
117834
118974
|
setTimeout(() => {
|
|
117835
|
-
|
|
118975
|
+
pendingLabelRestore.delete(target);
|
|
118976
|
+
if (target.isConnected) syncDayCellState(target);
|
|
117836
118977
|
}, 0);
|
|
117837
118978
|
}
|
|
117838
118979
|
});
|
|
@@ -117846,7 +118987,7 @@ function customElements(pluginConfig) {
|
|
|
117846
118987
|
liveRegion = null;
|
|
117847
118988
|
}
|
|
117848
118989
|
return {
|
|
117849
|
-
onReady: [upsertTimeArrows, buildTimeArrows, setDateSelectAlly, setTimeAlly, setMonthSelectAlly, setYearSelectAlly, setNavButtonsAlly, observeNavButtons, observeMonthSelector, createLiveRegion, setGridKeyNavigation, syncRovingTabindex,
|
|
118990
|
+
onReady: [upsertTimeArrows, buildTimeArrows, setDateSelectAlly, setValueInputAlly, setTimeAlly, setMonthSelectAlly, setYearSelectAlly, setNavButtonsAlly, observeNavButtons, observeMonthSelector, createLiveRegion, setAmPmAlly, setGridKeyNavigation, syncRovingTabindex, observeDayCellClasses,
|
|
117850
118991
|
// Apply grid row roles + the initial roving-focus day, and keep them correct
|
|
117851
118992
|
// across every flatpickr rebuild. Does not steal focus — the dialog focuses its
|
|
117852
118993
|
// heading on open; the first Tab into the grid lands on the focusable day.
|
|
@@ -117864,13 +119005,7 @@ function customElements(pluginConfig) {
|
|
|
117864
119005
|
dayElem.innerHTML = `<span class='dayInner'>${dayElem.innerHTML}</span>`;
|
|
117865
119006
|
dayElem.setAttribute("role", "gridcell");
|
|
117866
119007
|
dayElem.setAttribute("tabindex", "-1");
|
|
117867
|
-
|
|
117868
|
-
dayElem.setAttribute("aria-selected", "true");
|
|
117869
|
-
} else {
|
|
117870
|
-
dayElem.removeAttribute("aria-selected");
|
|
117871
|
-
}
|
|
117872
|
-
const date = cellDate(dayElem);
|
|
117873
|
-
if (date) dayElem.setAttribute("aria-label", getDayLabel(date));
|
|
119008
|
+
syncDayCellState(dayElem);
|
|
117874
119009
|
}, handleWeekNumbers],
|
|
117875
119010
|
onValueUpdate: [upsertTimeArrows,
|
|
117876
119011
|
// Selecting a date rebuilds the day grid synchronously (flatpickr calls buildDays()
|
|
@@ -117972,7 +119107,8 @@ const getOptionsFromMessage = (message2, customTranslations) => {
|
|
|
117972
119107
|
formatDate: !data.dateFormat ? date => moment(date).locale(momentLocaleId).format(dateFormatLocalString) : void 0,
|
|
117973
119108
|
plugins: [customElements({
|
|
117974
119109
|
arrowIcon,
|
|
117975
|
-
customTranslations: customTranslations || {}
|
|
119110
|
+
customTranslations: customTranslations || {},
|
|
119111
|
+
localeId: momentLocaleId
|
|
117976
119112
|
})]
|
|
117977
119113
|
};
|
|
117978
119114
|
const enable_disable = Array.isArray(data?.enable_disable) && data.enable_disable.length > 0 ? data.enable_disable : null;
|