@xapp/chat-widget 1.103.0 → 1.104.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/dist/index.css +1 -1
- package/dist/index.d.ts +0 -0
- package/dist/index.es.js +207 -22
- package/dist/index.es.js.map +1 -1
- package/dist/index.js +207 -22
- package/dist/index.js.map +1 -1
- package/dist/utils/chatContinuation.d.ts +0 -0
- package/dist/xapp-chat-widget.css +1 -1
- package/dist/xapp-chat-widget.js +1 -1
- package/dist/xapp-chat-widget.js.map +1 -1
- package/package.json +2 -2
package/dist/index.es.js
CHANGED
|
@@ -2,6 +2,7 @@ import React, { useCallback, useState, useEffect, useRef, useLayoutEffect, creat
|
|
|
2
2
|
export { default as React } from 'react';
|
|
3
3
|
import require$$0, { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
4
4
|
import { useSelector, useDispatch, Provider } from 'react-redux';
|
|
5
|
+
import { createPortal } from 'react-dom';
|
|
5
6
|
|
|
6
7
|
var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
|
|
7
8
|
|
|
@@ -2398,6 +2399,16 @@ function stripChatContinueParam(win) {
|
|
|
2398
2399
|
function hasConversation(chats) {
|
|
2399
2400
|
return !!(chats === null || chats === void 0 ? void 0 : chats.some(function (chat) { return chat.type === "chat.msg"; }));
|
|
2400
2401
|
}
|
|
2402
|
+
/**
|
|
2403
|
+
* True once the visitor has said something themselves.
|
|
2404
|
+
*
|
|
2405
|
+
* Stricter than `hasConversation`, which counts the greeting the assistant posts before anyone
|
|
2406
|
+
* has engaged. Visitor messages carry a `visitor:` nick, the same marker MessageList uses to
|
|
2407
|
+
* decide which side of the thread a message belongs on.
|
|
2408
|
+
*/
|
|
2409
|
+
function hasVisitorMessage(chats) {
|
|
2410
|
+
return !!(chats === null || chats === void 0 ? void 0 : chats.some(function (chat) { var _a, _b; return chat.type === "chat.msg" && !!((_b = (_a = chat.user) === null || _a === void 0 ? void 0 : _a.nick) === null || _b === void 0 ? void 0 : _b.startsWith("visitor:")); }));
|
|
2411
|
+
}
|
|
2401
2412
|
/**
|
|
2402
2413
|
* The CTA to show, given whether there is a conversation to go back to.
|
|
2403
2414
|
*
|
|
@@ -37099,6 +37110,22 @@ var StaticMessagesChatWidgetContainer = function (props) {
|
|
|
37099
37110
|
* After this delay of inactivity, the "stop typing" event will be sent.
|
|
37100
37111
|
*/
|
|
37101
37112
|
var TYPING_INDICATOR_DEBOUNCE_MS = 2000;
|
|
37113
|
+
/**
|
|
37114
|
+
* Reparenting has to happen before the browser paints, or the chat is missing for a frame on
|
|
37115
|
+
* every crossing. useLayoutEffect warns when a component is rendered on a server, where there is
|
|
37116
|
+
* nothing to paint and nothing to move, so fall back there.
|
|
37117
|
+
*/
|
|
37118
|
+
var useBeforePaintEffect = typeof window === "undefined" ? useEffect : useLayoutEffect;
|
|
37119
|
+
/**
|
|
37120
|
+
* Narrowest viewport a pop-out is allowed on by default. Below this a floating window covers
|
|
37121
|
+
* most of the screen, which is worse than letting the chat scroll away with the page.
|
|
37122
|
+
*/
|
|
37123
|
+
var DEFAULT_POP_OUT_MIN_WIDTH = 640;
|
|
37124
|
+
/**
|
|
37125
|
+
* How long to keep correcting the message list's scroll after the chat moves. Text rewraps at
|
|
37126
|
+
* the new width, so the content height settles over a frame or two rather than immediately.
|
|
37127
|
+
*/
|
|
37128
|
+
var SCROLL_SETTLE_MS = 250;
|
|
37102
37129
|
/**
|
|
37103
37130
|
* Check if we're in development mode. Uses typeof to prevent TypeScript
|
|
37104
37131
|
* errors after Rollup replaces "production" with a literal string.
|
|
@@ -37270,7 +37297,7 @@ var ChatWidgetConnected = function (props) {
|
|
|
37270
37297
|
* Exported for use by StaticChatWidgetContainer to avoid infinite loops.
|
|
37271
37298
|
*/
|
|
37272
37299
|
var ChatWidgetUI = function (props) {
|
|
37273
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14;
|
|
37300
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20;
|
|
37274
37301
|
var innerDispatch = useChatDispatch();
|
|
37275
37302
|
var dispatch = useChatServerDispatch();
|
|
37276
37303
|
// Publish the visual viewport's geometry so the panel can size around the soft keyboard.
|
|
@@ -37295,23 +37322,176 @@ var ChatWidgetUI = function (props) {
|
|
|
37295
37322
|
var arrivedViaChatLink = useArrivedViaChatLink(previewMode);
|
|
37296
37323
|
var conversationOnScreen = hasConversation(chatState.chats);
|
|
37297
37324
|
var chatButtonCta = useMemo(function () { var _a; return resolveCta((_a = props.config) === null || _a === void 0 ? void 0 : _a.cta, { hasConversation: conversationOnScreen, arrivedViaChatLink: arrivedViaChatLink }); }, [(_b = props.config) === null || _b === void 0 ? void 0 : _b.cta, conversationOnScreen, arrivedViaChatLink]);
|
|
37325
|
+
// Pop-out: an embedded chat follows the visitor down the page rather than scrolling away
|
|
37326
|
+
// mid-conversation. Same component, same store - it is moved, not reopened - so the
|
|
37327
|
+
// conversation continues instead of restarting in a second widget.
|
|
37328
|
+
var popOutEnabled = dockedMode && ((_c = props.config.popOut) === null || _c === void 0 ? void 0 : _c.enabled) === true;
|
|
37329
|
+
var popOutMinWidth = (_e = (_d = props.config.popOut) === null || _d === void 0 ? void 0 : _d.minWidth) !== null && _e !== void 0 ? _e : DEFAULT_POP_OUT_MIN_WIDTH;
|
|
37330
|
+
var anchorRef = useRef(null);
|
|
37331
|
+
var _21 = useState(false), anchorOffScreen = _21[0], setAnchorOffScreen = _21[1];
|
|
37332
|
+
// A returning visitor's chat often sits below the fold at load. Following them before they
|
|
37333
|
+
// have even reached it would put a floating window in front of someone who never scrolled
|
|
37334
|
+
// past anything, so the chat has to have been on screen once first.
|
|
37335
|
+
var _22 = useState(false), anchorSeen = _22[0], setAnchorSeen = _22[1];
|
|
37336
|
+
// Set when the visitor puts the floating window away: it stays put until their own
|
|
37337
|
+
// scrolling brings the chat's place on the page back into view.
|
|
37338
|
+
var _23 = useState(false), popOutDismissed = _23[0], setPopOutDismissed = _23[1];
|
|
37339
|
+
// On a phone the chat offers its entry point rather than covering the page; this is set when
|
|
37340
|
+
// the visitor taps that and asks for the conversation.
|
|
37341
|
+
var _24 = useState(false), mobileOpened = _24[0], setMobileOpened = _24[1];
|
|
37342
|
+
// Tracked rather than read at render: a visitor who narrows the window or rotates a tablet
|
|
37343
|
+
// while the chat is popped out would otherwise keep a floating window over most of a phone
|
|
37344
|
+
// screen until some unrelated re-render happened along.
|
|
37345
|
+
var _25 = useState(typeof window === "undefined" ? 0 : window.innerWidth), viewportWidth = _25[0], setViewportWidth = _25[1];
|
|
37346
|
+
useEffect(function () {
|
|
37347
|
+
if (!popOutEnabled || typeof window === "undefined") {
|
|
37348
|
+
return undefined;
|
|
37349
|
+
}
|
|
37350
|
+
var handleResize = function () { return setViewportWidth(window.innerWidth); };
|
|
37351
|
+
handleResize();
|
|
37352
|
+
window.addEventListener("resize", handleResize);
|
|
37353
|
+
return function () { return window.removeEventListener("resize", handleResize); };
|
|
37354
|
+
}, [popOutEnabled]);
|
|
37355
|
+
useEffect(function () {
|
|
37356
|
+
if (!popOutEnabled || typeof IntersectionObserver === "undefined" || !anchorRef.current) {
|
|
37357
|
+
return undefined;
|
|
37358
|
+
}
|
|
37359
|
+
var observer = new IntersectionObserver(function (entries) {
|
|
37360
|
+
var entry = entries[entries.length - 1];
|
|
37361
|
+
if (!entry) {
|
|
37362
|
+
return;
|
|
37363
|
+
}
|
|
37364
|
+
setAnchorOffScreen(!entry.isIntersecting);
|
|
37365
|
+
if (entry.isIntersecting) {
|
|
37366
|
+
setAnchorSeen(true);
|
|
37367
|
+
setPopOutDismissed(false);
|
|
37368
|
+
// Back at the chat itself, so the phone entry point starts over.
|
|
37369
|
+
setMobileOpened(false);
|
|
37370
|
+
}
|
|
37371
|
+
});
|
|
37372
|
+
observer.observe(anchorRef.current);
|
|
37373
|
+
return function () { return observer.disconnect(); };
|
|
37374
|
+
}, [popOutEnabled]);
|
|
37375
|
+
// With `after: "message"` the chat only follows a visitor who has engaged - the assistant's
|
|
37376
|
+
// greeting arrives before anyone types, so it does not count. A floating window also covers
|
|
37377
|
+
// most of a phone screen, so narrow screens stay on the page either way.
|
|
37378
|
+
var engaged = ((_f = props.config.popOut) === null || _f === void 0 ? void 0 : _f.after) !== "message" || hasVisitorMessage(chatState.chats);
|
|
37379
|
+
var following = popOutEnabled && anchorSeen && anchorOffScreen && !popOutDismissed && engaged;
|
|
37380
|
+
var wideEnough = viewportWidth >= popOutMinWidth;
|
|
37381
|
+
var mobileEntry = (_h = (_g = props.config.popOut) === null || _g === void 0 ? void 0 : _g.mobile) !== null && _h !== void 0 ? _h : "auto";
|
|
37382
|
+
/**
|
|
37383
|
+
* Too narrow for a floating window: the chat stays put and offers its entry point instead,
|
|
37384
|
+
* so a visitor reading on still has a way back to the conversation.
|
|
37385
|
+
*/
|
|
37386
|
+
var collapsedOnMobile = following && !wideEnough && mobileEntry !== "none" && !mobileOpened;
|
|
37387
|
+
var poppedOut = following && (wideEnough || mobileOpened);
|
|
37388
|
+
function handlePopOutDismiss() {
|
|
37389
|
+
// On a phone, putting the window away goes back to the entry point rather than leaving
|
|
37390
|
+
// the visitor with nothing: the conversation is still off-screen up the page.
|
|
37391
|
+
if (wideEnough) {
|
|
37392
|
+
setPopOutDismissed(true);
|
|
37393
|
+
return;
|
|
37394
|
+
}
|
|
37395
|
+
setMobileOpened(false);
|
|
37396
|
+
}
|
|
37397
|
+
function handleMobileOpen() {
|
|
37398
|
+
setMobileOpened(true);
|
|
37399
|
+
}
|
|
37400
|
+
// The chat always renders into this one node, and the node is what moves between the page
|
|
37401
|
+
// and the corner. Swapping the JSX between an element and a portal instead would hand React
|
|
37402
|
+
// a different fiber type in the same slot, remounting the whole chat - and with it the
|
|
37403
|
+
// visitor's half-typed message and the message list's scroll position - on every crossing.
|
|
37404
|
+
var popOutHost = useMemo(function () { return (popOutEnabled && typeof document !== "undefined" ? document.createElement("div") : null); }, [popOutEnabled]);
|
|
37405
|
+
// Where the visitor is reading, recorded as they scroll. Measuring this at move time does
|
|
37406
|
+
// not work: React has already applied the new size by then, so the height is the new one
|
|
37407
|
+
// while the scroll position is still the old one, and the mix is meaningless.
|
|
37408
|
+
var readingRef = useRef({ fromBottom: 0, latest: true });
|
|
37409
|
+
useEffect(function () {
|
|
37410
|
+
if (!popOutHost) {
|
|
37411
|
+
return undefined;
|
|
37412
|
+
}
|
|
37413
|
+
var onScroll = function (event) {
|
|
37414
|
+
var el = event.target;
|
|
37415
|
+
if (!(el instanceof HTMLElement) || !el.classList.contains("message-list-container")) {
|
|
37416
|
+
return;
|
|
37417
|
+
}
|
|
37418
|
+
var fromBottom = el.scrollHeight - el.clientHeight - el.scrollTop;
|
|
37419
|
+
readingRef.current = {
|
|
37420
|
+
fromBottom: fromBottom,
|
|
37421
|
+
// Within a screenful of the newest message is "following the conversation", and
|
|
37422
|
+
// that is where they go back to. The list also smooth-scrolls after each
|
|
37423
|
+
// message, so the last position it reports can be mid-animation.
|
|
37424
|
+
latest: fromBottom <= el.clientHeight,
|
|
37425
|
+
};
|
|
37426
|
+
};
|
|
37427
|
+
// Scroll events do not bubble, but they do capture.
|
|
37428
|
+
popOutHost.addEventListener("scroll", onScroll, true);
|
|
37429
|
+
return function () { return popOutHost.removeEventListener("scroll", onScroll, true); };
|
|
37430
|
+
}, [popOutHost]);
|
|
37431
|
+
useBeforePaintEffect(function () {
|
|
37432
|
+
if (!popOutHost) {
|
|
37433
|
+
return undefined;
|
|
37434
|
+
}
|
|
37435
|
+
popOutHost.className = "widget-portal";
|
|
37436
|
+
var target = poppedOut ? document.body : anchorRef.current;
|
|
37437
|
+
if (target && popOutHost.parentElement !== target) {
|
|
37438
|
+
// Moving a node blurs whatever was focused inside it, which would drop the visitor
|
|
37439
|
+
// out of the message box mid-sentence, so put the caret back afterwards.
|
|
37440
|
+
var focused = document.activeElement;
|
|
37441
|
+
var refocus = focused instanceof HTMLElement && popOutHost.contains(focused) ? focused : undefined;
|
|
37442
|
+
// The embedded chat and the floating window are different heights, so a scroll
|
|
37443
|
+
// position kept in pixels lands somewhere else in the thread - usually leaving the
|
|
37444
|
+
// newest message below the fold, which reads as the conversation having been lost.
|
|
37445
|
+
// Distance from the bottom is what the visitor actually chose: at the newest message
|
|
37446
|
+
// they stay there, and reading back stays that far back.
|
|
37447
|
+
var messages_1 = popOutHost.querySelector(".message-list-container");
|
|
37448
|
+
// appendChild moves the node, children and all, without React re-rendering them.
|
|
37449
|
+
target.appendChild(popOutHost);
|
|
37450
|
+
// Read afresh each time rather than closing over the position from before the move:
|
|
37451
|
+
// the delayed corrections below run up to a quarter of a second later, and a visitor
|
|
37452
|
+
// who scrolls back through the thread in that window must not be snapped forward
|
|
37453
|
+
// again. Their scrolling updates the ref, so the later passes simply hold their place.
|
|
37454
|
+
var restoreScroll = function () {
|
|
37455
|
+
if (!messages_1) {
|
|
37456
|
+
return;
|
|
37457
|
+
}
|
|
37458
|
+
var _a = readingRef.current, fromBottom = _a.fromBottom, latest = _a.latest;
|
|
37459
|
+
var bottom = messages_1.scrollHeight - messages_1.clientHeight;
|
|
37460
|
+
messages_1.scrollTop = latest ? bottom : Math.max(0, bottom - fromBottom);
|
|
37461
|
+
};
|
|
37462
|
+
restoreScroll();
|
|
37463
|
+
refocus === null || refocus === void 0 ? void 0 : refocus.focus({ preventScroll: true });
|
|
37464
|
+
// The thread rewraps at the new width, so the content keeps growing for a frame or
|
|
37465
|
+
// two after the move. Restoring only once leaves the newest message under the fold,
|
|
37466
|
+
// which is what a visitor reads as "my conversation is gone".
|
|
37467
|
+
var frame_1 = requestAnimationFrame(restoreScroll);
|
|
37468
|
+
var settled_1 = window.setTimeout(restoreScroll, SCROLL_SETTLE_MS);
|
|
37469
|
+
return function () {
|
|
37470
|
+
cancelAnimationFrame(frame_1);
|
|
37471
|
+
window.clearTimeout(settled_1);
|
|
37472
|
+
};
|
|
37473
|
+
}
|
|
37474
|
+
return undefined;
|
|
37475
|
+
}, [popOutHost, poppedOut]);
|
|
37476
|
+
useEffect(function () { return function () { return popOutHost === null || popOutHost === void 0 ? void 0 : popOutHost.remove(); }; }, [popOutHost]);
|
|
37298
37477
|
// Only an embedded widget may drop its header. In normal mode the header holds the
|
|
37299
37478
|
// minimize and close buttons, so hiding it would leave a floating window with no way out.
|
|
37300
|
-
|
|
37479
|
+
// While popped out the header is what lets the visitor put the window away, so it stays.
|
|
37480
|
+
var headerHidden = ((_j = props.config.header) === null || _j === void 0 ? void 0 : _j.hidden) === true && (dockedMode || staticMode) && !poppedOut;
|
|
37301
37481
|
// The header is the only place a HEADER_LEFT / HEADER_RIGHT menu button renders. Without it
|
|
37302
37482
|
// the menu would be unreachable, so hand the button to the footer instead.
|
|
37303
37483
|
var menuConfig = useMemo(function () {
|
|
37304
37484
|
return headerHidden && props.config.menu && props.config.menu.menuButtonLocation !== "FOOTER"
|
|
37305
37485
|
? __assign$1(__assign$1({}, props.config.menu), { menuButtonLocation: "FOOTER" }) : props.config.menu;
|
|
37306
37486
|
}, [headerHidden, props.config.menu]);
|
|
37307
|
-
var canRefresh = (
|
|
37487
|
+
var canRefresh = (_l = (_k = props.config.header) === null || _k === void 0 ? void 0 : _k.actions) === null || _l === void 0 ? void 0 : _l.refresh;
|
|
37308
37488
|
// can't minimize in docked mode, static mode, or preview mode.
|
|
37309
|
-
var canMinimize = !dockedMode && !staticMode && !previewMode && ((
|
|
37310
|
-
log("docked: ".concat(dockedMode, " static: ").concat(staticMode, " preview: ").concat(previewMode, " minimized: ").concat((
|
|
37489
|
+
var canMinimize = !dockedMode && !staticMode && !previewMode && ((_o = (_m = props.config.header) === null || _m === void 0 ? void 0 : _m.actions) === null || _o === void 0 ? void 0 : _o.minimize);
|
|
37490
|
+
log("docked: ".concat(dockedMode, " static: ").concat(staticMode, " preview: ").concat(previewMode, " minimized: ").concat((_q = (_p = props.config.header) === null || _p === void 0 ? void 0 : _p.actions) === null || _q === void 0 ? void 0 : _q.minimize));
|
|
37311
37491
|
var canCancel;
|
|
37312
37492
|
// To preserve legacy behavior, cancel needs a little more checks
|
|
37313
|
-
if (typeof ((
|
|
37314
|
-
canCancel = !dockedMode && !staticMode && !previewMode && ((
|
|
37493
|
+
if (typeof ((_s = (_r = props.config.header) === null || _r === void 0 ? void 0 : _r.actions) === null || _s === void 0 ? void 0 : _s.cancel) === "boolean") {
|
|
37494
|
+
canCancel = !dockedMode && !staticMode && !previewMode && ((_u = (_t = props.config.header) === null || _t === void 0 ? void 0 : _t.actions) === null || _u === void 0 ? void 0 : _u.cancel);
|
|
37315
37495
|
}
|
|
37316
37496
|
else {
|
|
37317
37497
|
canCancel = !dockedMode && !staticMode && !previewMode;
|
|
@@ -37322,16 +37502,16 @@ var ChatWidgetUI = function (props) {
|
|
|
37322
37502
|
}
|
|
37323
37503
|
// Our state - pull from storage
|
|
37324
37504
|
// Parse autoOpenOnWidth once for use in both initial state and useEffect
|
|
37325
|
-
var autoOpenOnWidthValue = ((
|
|
37505
|
+
var autoOpenOnWidthValue = ((_v = props.config) === null || _v === void 0 ? void 0 : _v.autoOpenOnWidth)
|
|
37326
37506
|
? parseInt(props.config.autoOpenOnWidth, 10)
|
|
37327
37507
|
: NaN;
|
|
37328
37508
|
var autoOpenOnWidthMatch = !isNaN(autoOpenOnWidthValue) && window.innerWidth >= autoOpenOnWidthValue;
|
|
37329
37509
|
// When autoOpenOnWidth is configured, use width check instead of persisted visible state
|
|
37330
37510
|
// to prevent greeting requests on viewports below the threshold
|
|
37331
|
-
var
|
|
37511
|
+
var _26 = useState((!canMinimize && !canCancel) ||
|
|
37332
37512
|
// !!get("visible") ||
|
|
37333
|
-
(!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible =
|
|
37334
|
-
var
|
|
37513
|
+
(!isNaN(autoOpenOnWidthValue) ? autoOpenOnWidthMatch : chatState.visuals.visible)), visible = _26[0], setVisibleState = _26[1];
|
|
37514
|
+
var _27 = useState(false); _27[0]; var setTypingState = _27[1]; // false initially - state kept for potential external observers
|
|
37335
37515
|
// Ref to track typing state for use in timeout callbacks
|
|
37336
37516
|
var typingRef = useRef(false);
|
|
37337
37517
|
// Timeout ref for debouncing "stop typing" events
|
|
@@ -37339,11 +37519,11 @@ var ChatWidgetUI = function (props) {
|
|
|
37339
37519
|
// Track whether autoOpenOnWidth has already fired so it doesn't re-open after user closes
|
|
37340
37520
|
var autoOpenFiredRef = useRef(autoOpenOnWidthMatch);
|
|
37341
37521
|
var chatServer = useContext(ChatServerContext);
|
|
37342
|
-
var patternsConfig = (
|
|
37522
|
+
var patternsConfig = (_x = (_w = props.config) === null || _w === void 0 ? void 0 : _w.autoOpenOnPattern) === null || _x === void 0 ? void 0 : _x.patterns;
|
|
37343
37523
|
var currentUrl = window.location.href;
|
|
37344
37524
|
var patternExist = patternsConfig && patternsConfig.length > 0;
|
|
37345
37525
|
var patternMatches = patternsConfig === null || patternsConfig === void 0 ? void 0 : patternsConfig.some(function (pattern) { return currentUrl.includes(pattern); });
|
|
37346
|
-
var configWidth = (
|
|
37526
|
+
var configWidth = (_z = (_y = props.config) === null || _y === void 0 ? void 0 : _y.autoOpenOnPattern) === null || _z === void 0 ? void 0 : _z.minimumWidth;
|
|
37347
37527
|
var currentWidth = window.innerWidth;
|
|
37348
37528
|
var setVisible = useCallback(function (newVisible) {
|
|
37349
37529
|
if (staticMode || previewMode) {
|
|
@@ -37378,7 +37558,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37378
37558
|
};
|
|
37379
37559
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
37380
37560
|
}, []);
|
|
37381
|
-
var
|
|
37561
|
+
var _28 = useState(!document.hidden), isTabVisible = _28[0], setIsTabVisible = _28[1];
|
|
37382
37562
|
useEffect(function () {
|
|
37383
37563
|
var handleVisibilityChange = function () {
|
|
37384
37564
|
setIsTabVisible(!document.hidden);
|
|
@@ -37602,8 +37782,8 @@ var ChatWidgetUI = function (props) {
|
|
|
37602
37782
|
}));
|
|
37603
37783
|
}
|
|
37604
37784
|
// Action Bar state and handlers
|
|
37605
|
-
var
|
|
37606
|
-
var
|
|
37785
|
+
var _29 = useState(false), formModalOpen = _29[0], setFormModalOpen = _29[1];
|
|
37786
|
+
var _30 = useState(null), activeFormConfig = _30[0], setActiveFormConfig = _30[1];
|
|
37607
37787
|
var handleFormButtonClick = useCallback(function (formButton) {
|
|
37608
37788
|
// If form-widget is configured, use its openForm API
|
|
37609
37789
|
// The form-widget script should already be pre-loaded
|
|
@@ -37670,7 +37850,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37670
37850
|
var messages = chatState && chatState.chats;
|
|
37671
37851
|
log("Rendering - accountStatus: \"".concat(chatState.accountStatus, "\", connectionStatus: \"").concat(chatState.connection.connectionStatus, "\""));
|
|
37672
37852
|
var config = props.config, onConnectionStatusChange = props.onConnectionStatusChange;
|
|
37673
|
-
var actionBarEnabled = ((
|
|
37853
|
+
var actionBarEnabled = ((_0 = config === null || config === void 0 ? void 0 : config.actionBar) === null || _0 === void 0 ? void 0 : _0.enabled) === true;
|
|
37674
37854
|
// The host page declares this; the widget cannot work it out. On the standalone
|
|
37675
37855
|
// contact/booking page the form already *is* the page, which makes every button that
|
|
37676
37856
|
// only opens one redundant. XappMedia/chat-widget#1626.
|
|
@@ -37694,7 +37874,7 @@ var ChatWidgetUI = function (props) {
|
|
|
37694
37874
|
onConnectionStatusChange(connectionStatus);
|
|
37695
37875
|
}
|
|
37696
37876
|
}, [connectionStatus, onConnectionStatusChange]);
|
|
37697
|
-
useExternalScript((
|
|
37877
|
+
useExternalScript((_1 = props.config) === null || _1 === void 0 ? void 0 : _1.middlewareUrl);
|
|
37698
37878
|
// Only reach for form.xapp.ai when something on this page can still open the form.
|
|
37699
37879
|
//
|
|
37700
37880
|
// Loading it is not free: the script mounts a second form-widget instance whose wrapper
|
|
@@ -37734,18 +37914,23 @@ var ChatWidgetUI = function (props) {
|
|
|
37734
37914
|
};
|
|
37735
37915
|
}, [actionBarVisible]);
|
|
37736
37916
|
// This is a pseudo agent. It represent's the widget (shown in the header avatar for instance)
|
|
37737
|
-
var widgetAgent = ((
|
|
37917
|
+
var widgetAgent = ((_2 = chatState.agents["agent:robot"]) === null || _2 === void 0 ? void 0 : _2.user) ||
|
|
37738
37918
|
(config === null || config === void 0 ? void 0 : config.agent) || {
|
|
37739
37919
|
nick: "agent:robot",
|
|
37740
37920
|
avatarPath: config.avatarUrl,
|
|
37741
37921
|
display_name: "Agent",
|
|
37742
37922
|
};
|
|
37743
|
-
|
|
37923
|
+
var widgetContainer = (jsxs("div", { className: "widget-container ".concat(modeClass, " ").concat(getVisibilityClass()).concat(actionBarVisible ? " widget-container--with-action-bar" : "").concat(poppedOut ? " widget-container--popped" : "").concat(collapsedOnMobile ? " widget-container--collapsed" : ""), children: [jsx(WidgetStylesheet, { theme: config === null || config === void 0 ? void 0 : config.theme }), !headerHidden && (jsx(ChatHeader, { accountStatus: chatState.accountStatus, refreshOnClick: handleRestartClick, minimizeOnClick: poppedOut ? handlePopOutDismiss : handleMinimizeClick, cancelOnClick: handleCancelClick, agent: widgetAgent, canRefresh: canRefresh, canMinimize: poppedOut || canMinimize, canCancel: canCancel, config: config === null || config === void 0 ? void 0 : config.header, menuConfig: config.menu, debugMode: (_5 = (_4 = (_3 = props.config) === null || _3 === void 0 ? void 0 : _3.connection) === null || _4 === void 0 ? void 0 : _4.mcp) === null || _5 === void 0 ? void 0 : _5.debugMode, onSubmit: handleOnSubmit })), jsx(MessageList, { visible: visible, queuePosition: chatState.queuePosition, isChatting: chatState.isChatting, isOffline: isOffline, messages: messages, agents: chatState.agents, agent: config === null || config === void 0 ? void 0 : config.agent, lastRatingRequestTimestamp: chatState.lastRatingRequestTimestamp, hasRating: chatState.hasRating, visitorId: chatState.visitorId, hasWsButton: !!chatState.wsButton, messageMiddleware: props.messageMiddleware, textTypingStatusEnabled: (_7 = (_6 = props.config) === null || _6 === void 0 ? void 0 : _6.typingStatus) === null || _7 === void 0 ? void 0 : _7.textTypingStatusEnabled, disableAutoScroll: props.disableAutoScroll, onSend: handleSendMessage, onWrite: handleWriteMessage, onOpenUrl: handleOpenUrl, minimizeOnClick: handleMinimizeClick }), jsx("div", { className: "xa-spinner-container ".concat(visible && connectionStatus === "pending" ? "visible" : ""), children: jsx("div", { className: "xa-spinner" }) }), connectionStatus === "offline" && jsx(ServerOffline, {}), chatState.wsButton && visible && (jsx(WsButton, { button: chatState.wsButton, onPress: handleWsButtonPress })), jsx(ChatFooter, { isAdmin: config === null || config === void 0 ? void 0 : config.isAdmin, isChatting: chatState.isChatting, placeholder: (_8 = config === null || config === void 0 ? void 0 : config.input) === null || _8 === void 0 ? void 0 : _8.placeholder, sendButtonIcon: (_10 = (_9 = config === null || config === void 0 ? void 0 : config.footer) === null || _9 === void 0 ? void 0 : _9.sendButton) === null || _10 === void 0 ? void 0 : _10.icon, sendButtonIconHover: (_12 = (_11 = config === null || config === void 0 ? void 0 : config.footer) === null || _11 === void 0 ? void 0 : _11.sendButton) === null || _12 === void 0 ? void 0 : _12.iconHover, sendButtonIconDisabled: (_14 = (_13 = config === null || config === void 0 ? void 0 : config.footer) === null || _13 === void 0 ? void 0 : _13.sendButton) === null || _14 === void 0 ? void 0 : _14.iconDisabled, visible: visible, hasWsButton: !!chatState.wsButton, menuConfig: menuConfig, footerConfig: (_15 = props.config) === null || _15 === void 0 ? void 0 : _15.footer, inputConfig: (_16 = props.config) === null || _16 === void 0 ? void 0 : _16.input, disabled: previewMode, onChange: handleOnChange, onSubmit: handleOnSubmit, onFileUpload: handleFileUpload }), jsx("div", { className: "restartModal", ref: modalRef, onClick: handleRestartModalCloseClick, children: jsx(ModalContent, { onClose: handleRestartModalCloseClick, onReset: handleReset }) })] }));
|
|
37924
|
+
return (jsxs(Fragment, { children: [popOutEnabled ? (
|
|
37925
|
+
// The anchor keeps the chat's place in the page, so the layout does not jump when
|
|
37926
|
+
// the chat pops out. The chat itself lives in popOutHost, which the effect above
|
|
37927
|
+
// moves between this anchor and the body.
|
|
37928
|
+
jsx("div", { className: "widget-anchor", ref: anchorRef, children: popOutHost && createPortal(widgetContainer, popOutHost) })) : (widgetContainer), actionBarVisible && config.actionBar && !(collapsedOnMobile && mobileEntry === "button") ? (jsxs(Fragment, { children: [jsx(ActionBar, { config: __assign$1(__assign$1({}, config.actionBar), {
|
|
37744
37929
|
// Use actionBar.cta if set, otherwise fall back to widget-level cta
|
|
37745
|
-
cta: resolveCta((
|
|
37930
|
+
cta: resolveCta((_17 = config.actionBar.cta) !== null && _17 !== void 0 ? _17 : config.cta, {
|
|
37746
37931
|
hasConversation: conversationOnScreen,
|
|
37747
37932
|
arrivedViaChatLink: arrivedViaChatLink,
|
|
37748
|
-
}) }), visible: visible, chatDisabled: config.disabled, formDisabled: isFormPage, hasUserInteracted: (
|
|
37933
|
+
}) }), visible: collapsedOnMobile ? false : visible, chatDisabled: config.disabled, formDisabled: isFormPage, hasUserInteracted: (_18 = chatState.visuals) === null || _18 === void 0 ? void 0 : _18.hasInteracted, onChatClick: collapsedOnMobile ? handleMobileOpen : chatButtonOnClick, onChatMinimize: handleMinimizeClick, onFormClick: handleFormButtonClick, onCtaDismiss: handleCtaDismiss }), formModalOpen && activeFormConfig && (jsx(FormModal, { config: activeFormConfig, widgetEnv: config, onClose: handleFormModalClose }))] })) : (jsx(ChatButton, { addClass: visible && !collapsedOnMobile ? "visible" : "", onClick: collapsedOnMobile ? handleMobileOpen : chatButtonOnClick, config: chatButtonCta, imageUrl: (_19 = config === null || config === void 0 ? void 0 : config.chatButton) === null || _19 === void 0 ? void 0 : _19.imageUrl, visible: collapsedOnMobile ? false : visible, hasInteracted: (_20 = chatState.visuals) === null || _20 === void 0 ? void 0 : _20.hasInteracted, onCtaDismiss: handleCtaDismiss })), jsx(ErrorOverlay, { enableErrorOverlay: config === null || config === void 0 ? void 0 : config.enableErrorOverlay })] }));
|
|
37749
37934
|
};
|
|
37750
37935
|
/**
|
|
37751
37936
|
* Top-level wrapper that dispatches between preview mode and connected mode.
|