@xapp/chat-widget 1.96.0 → 1.96.2

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
@@ -391,8 +391,19 @@ var ActionBarCta = function (_a) {
391
391
  }, children: [require$$0.jsx("span", { className: "xapp-action-bar-cta__message", children: message }), dismissible && (require$$0.jsx("button", { className: "xapp-action-bar-cta__dismiss", onClick: handleDismiss, "aria-label": "Dismiss", type: "button", children: require$$0.jsx("svg", { width: "14", height: "14", viewBox: "0 0 14 14", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: require$$0.jsx("path", { d: "M13 1L1 13M1 1L13 13", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }) }) }))] }));
392
392
  };
393
393
 
394
- var DEFAULT_MOBILE_BREAKPOINT = 768;
395
- var RESIZE_DEBOUNCE_MS = 150;
394
+ /**
395
+ * The same query the stylesheets use, as a string rather than a number.
396
+ *
397
+ * A bare width could not express this. A phone on its side is 740-863px wide, so any
398
+ * width-only threshold either excludes landscape phones or catches tablets - #1592 moved
399
+ * the stylesheets to `(max-width: 640px), (max-height: 480px) and (pointer: coarse)` for
400
+ * exactly that reason, and a JS constant of 640 or 768 cannot mirror it either way.
401
+ *
402
+ * Sharing the query text is what keeps the two halves from drifting: matchMedia evaluates
403
+ * the identical condition the CSS does, so there is one definition of "phone" and it lives
404
+ * in `_mixins.scss`. Keep this in step with the `mobile()` mixin. XappMedia/chat-widget#1594.
405
+ */
406
+ var MOBILE_MEDIA_QUERY = "(max-width: 640px), (max-height: 480px) and (pointer: coarse)";
396
407
  /**
397
408
  * ChatWidget.scss sizes the chat panel against this variable. Nothing used to set it, so it always
398
409
  * fell back to the hardcoded 80px in the SCSS - and the real mobile bar is taller than that once
@@ -405,33 +416,43 @@ var RESIZE_DEBOUNCE_MS = 150;
405
416
  */
406
417
  var ACTION_BAR_HEIGHT_VAR = "--xapp-action-bar-height";
407
418
  var ActionBar = function (_a) {
408
- var _b, _c, _d;
409
- var config = _a.config, visible = _a.visible, chatDisabled = _a.chatDisabled, _e = _a.hasUserInteracted, hasUserInteracted = _e === void 0 ? false : _e, onChatClick = _a.onChatClick, onChatMinimize = _a.onChatMinimize, onFormClick = _a.onFormClick, onCtaDismiss = _a.onCtaDismiss;
410
- var _f = React.useState(false), isMobile = _f[0], setIsMobile = _f[1];
411
- var mobileBreakpoint = (_b = config.mobileBreakpoint) !== null && _b !== void 0 ? _b : DEFAULT_MOBILE_BREAKPOINT;
412
- var position = (_c = config.position) !== null && _c !== void 0 ? _c : "right";
413
- var resizeTimeoutRef = React.useRef(null);
419
+ var _b, _c;
420
+ var config = _a.config, visible = _a.visible, chatDisabled = _a.chatDisabled, _d = _a.hasUserInteracted, hasUserInteracted = _d === void 0 ? false : _d, onChatClick = _a.onChatClick, onChatMinimize = _a.onChatMinimize, onFormClick = _a.onFormClick, onCtaDismiss = _a.onCtaDismiss;
421
+ var _e = React.useState(false), isMobile = _e[0], setIsMobile = _e[1];
422
+ // An explicit override can only be a width; without one we ask the same question the
423
+ // stylesheets ask, so the two cannot disagree. A survey of production on 2026-08-19
424
+ // found the override set by nobody - zero of 971 bundles, against 18 configured
425
+ // ActionBars - so in practice every bar takes the shared query.
426
+ // `!= null`, not a truthy check: 0 is a legitimate override meaning "never mobile", and
427
+ // treating it as unset would hand back the default responsive query - the opposite of
428
+ // what was asked for.
429
+ var mediaQuery = config.mobileBreakpoint != null
430
+ ? "(max-width: ".concat(config.mobileBreakpoint, "px)")
431
+ : MOBILE_MEDIA_QUERY;
432
+ var position = (_b = config.position) !== null && _b !== void 0 ? _b : "right";
414
433
  var barRef = React.useRef(null);
415
- // Debounced resize handler
416
- var handleResize = React.useCallback(function () {
417
- if (resizeTimeoutRef.current) {
418
- window.clearTimeout(resizeTimeoutRef.current);
419
- }
420
- resizeTimeoutRef.current = window.setTimeout(function () {
421
- setIsMobile(window.innerWidth <= mobileBreakpoint);
422
- }, RESIZE_DEBOUNCE_MS);
423
- }, [mobileBreakpoint]);
424
434
  React.useEffect(function () {
425
- // Initial check (immediate, not debounced)
426
- setIsMobile(window.innerWidth <= mobileBreakpoint);
427
- window.addEventListener("resize", handleResize);
428
- return function () {
429
- window.removeEventListener("resize", handleResize);
430
- if (resizeTimeoutRef.current) {
431
- window.clearTimeout(resizeTimeoutRef.current);
432
- }
435
+ // matchMedia fires only when the answer actually changes, so there is nothing to
436
+ // debounce - unlike a resize listener, which fires continuously through a drag.
437
+ // Guarded because jsdom does not implement matchMedia, and a widget that throws on
438
+ // mount in a consumer's test suite is a worse failure than one that assumes desktop.
439
+ if (typeof window.matchMedia !== "function") {
440
+ return undefined;
441
+ }
442
+ var query = window.matchMedia(mediaQuery);
443
+ setIsMobile(query.matches);
444
+ var onChange = function (event) {
445
+ setIsMobile(event.matches);
433
446
  };
434
- }, [mobileBreakpoint, handleResize]);
447
+ // addListener is the pre-2021 Safari spelling. Still worth carrying: this widget
448
+ // runs on whatever browser a customer's visitor brings.
449
+ if (typeof query.addEventListener === "function") {
450
+ query.addEventListener("change", onChange);
451
+ return function () { return query.removeEventListener("change", onChange); };
452
+ }
453
+ query.addListener(onChange);
454
+ return function () { return query.removeListener(onChange); };
455
+ }, [mediaQuery]);
435
456
  // Publish the bar's real height so the chat panel can size around it.
436
457
  //
437
458
  // useLayoutEffect, not useEffect: this commits before paint, so the panel never gets a
@@ -484,7 +505,7 @@ var ActionBar = function (_a) {
484
505
  // - Chat must not be visible (user hasn't opened it)
485
506
  // - Chat must not be disabled
486
507
  var hasChatButton = config.buttons.some(lib.isActionBarChatButton);
487
- var showCta = ((_d = config.cta) === null || _d === void 0 ? void 0 : _d.message) && !visible && !chatDisabled && hasChatButton;
508
+ var showCta = ((_c = config.cta) === null || _c === void 0 ? void 0 : _c.message) && !visible && !chatDisabled && hasChatButton;
488
509
  var handleCtaClick = function () {
489
510
  onChatClick();
490
511
  };