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