@morgan-and-morgan/intake-agent 1.0.0-alpha.16 → 1.0.0-alpha.18

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/README.md CHANGED
@@ -6,6 +6,7 @@ Load the widget with a script tag, pinning an exact version:
6
6
  <script
7
7
  src="https://cdn.jsdelivr.net/npm/@morgan-and-morgan/intake-agent@<version>/dist/mm-intake-widget.js"
8
8
  data-chat-url="https://example.com/chat"
9
+ data-prompt-delay-seconds="30"
9
10
  ></script>
10
11
  ```
11
12
 
@@ -34,17 +35,28 @@ major. Every release is tagged `intake-agent-vx.y.z`.
34
35
  | --- | --- | --- |
35
36
  | `data-chat-url` | Release environment URL | Chat iframe target |
36
37
  | `data-auto-open` | `false` | Open the chat immediately after mount |
38
+ | `data-prompt-delay-seconds` | `30` | Seconds after mount before showing the consultation modal |
39
+
40
+ Set the delay on the script tag before it loads. Non-negative numbers are
41
+ accepted, including fractions; `0` shows the modal immediately. Missing, empty,
42
+ invalid, negative, or overflowing values (above 2147483.647 seconds) use 30 seconds.
43
+ The value is read locally and adds no configuration request. Changing the
44
+ attribute after the script loads does not reschedule the modal. Existing popup
45
+ suppression and dismissal cooldowns still apply.
37
46
 
38
47
  The consultation modal reads `window.drupalSettings.mxPopup`, falling back to
39
48
  `window.mxChatbotSettings.mxPopup` when the Drupal field is absent. Only the
40
49
  boolean `false` suppresses the modal, including calls to `showPrompt()`.
41
50
  Missing settings preserve the usual display rules. Settings are read when the
42
- modal is about to appear; the launcher and chat remain available.
51
+ modal is about to appear; the launcher and chat remain available. Set suppression
52
+ before loading the embed when using a zero-second delay, so the first display
53
+ check sees the intended setting.
43
54
 
44
55
  ## Consultation prompt design
45
56
 
46
57
  The responsive prompt follows the [desktop and mobile Figma layouts](https://www.figma.com/design/0SxWl42gdhx08KFe2ldpd8/MM-Design-System-v4.0?node-id=21250-169311).
47
- Portrait, avatar, status dots, and icons are exported from those layers.
58
+ The portrait and avatar use cropped, optimized original photos; CSS applies the Figma framing.
59
+ Status dots and icons are exported from their layers.
48
60
  The fonts are the existing website's [Kuunari Bold Compressed](https://www.forthepeople.com/themes/custom/morgan/fonts/Kuunari/kuunari-bold-compressed.woff2)
49
61
  and [Nunito Sans](https://www.forthepeople.com/themes/custom/morgan/fonts/NunitoSans/NunitoSans.woff2).
50
62
  On forthepeople.com, the prompt loads these fonts from the host site to respect
@@ -52,5 +64,6 @@ its font security policy. Other hosts use the bundled copies.
52
64
  Keep prompt assets in `public/` and `apps/expo/public/intake-widget/` identical;
53
65
  the asset tests check both the CDN and portal copies.
54
66
 
55
- Retain `consultation-prompt.webp` at both asset origins for older deployed widget
56
- versions. The redesigned prompt does not request it.
67
+ Retain `consultation-prompt.webp`, `consultation-team.png`, and
68
+ `consultation-john-morgan.png` at both asset origins for older deployed widget
69
+ versions. The current prompt requests the separate WebP photos instead.
Binary file
@@ -1,5 +1,17 @@
1
1
  (function() {
2
2
  "use strict";
3
+ const DEFAULT_PROMPT_DELAY_MS = 3e4;
4
+ const MAX_PROMPT_DELAY_MS = 2147483647;
5
+ const readPromptDelayMs = (seconds) => {
6
+ if (seconds === void 0 || seconds.trim() === "") {
7
+ return DEFAULT_PROMPT_DELAY_MS;
8
+ }
9
+ const delayMs = Number(seconds) * 1e3;
10
+ if (!Number.isFinite(delayMs) || delayMs < 0 || delayMs > MAX_PROMPT_DELAY_MS) {
11
+ return DEFAULT_PROMPT_DELAY_MS;
12
+ }
13
+ return delayMs;
14
+ };
3
15
  const readLoaderConfig = (script, baseUri) => {
4
16
  var _a;
5
17
  const scriptUrl = new URL(script.src, baseUri);
@@ -9,7 +21,8 @@
9
21
  assetBaseUrl: new URL(".", scriptUrl).href,
10
22
  autoOpen: script.dataset.autoOpen === "true",
11
23
  chatOrigin: new URL(chatUrl).origin,
12
- chatUrl
24
+ chatUrl,
25
+ promptDelayMs: readPromptDelayMs(script.dataset.promptDelaySeconds)
13
26
  };
14
27
  };
15
28
  const ATTRIBUTION_FIELD_MAX_LENGTH = {
@@ -298,12 +311,16 @@
298
311
  return dispose;
299
312
  };
300
313
  const PROMPT_STYLES = [
314
+ // Initial dialog focus announces the popup without drawing a ring around its backdrop.
315
+ ".mm-prompt-backdrop:focus{outline:none}",
301
316
  `.mm-prompt-backdrop{--mm-prompt-blue:${designSystemColors["primary-800"]};--mm-prompt-dark:${designSystemColors["primary-900"]};--mm-prompt-yellow:${designSystemColors["secondary-500"]};--mm-prompt-yellow-end:${designSystemColors["secondary-700"]};--mm-prompt-text:${designSystemColors["primary-100"]};position:fixed;inset:0;z-index:2147483002;display:flex;flex-direction:column;align-items:center;padding:12px;overflow-y:auto;overscroll-behavior:contain;touch-action:pan-y pinch-zoom;background:rgba(0,0,0,.5);animation:mm-prompt-backdrop-in .25s cubic-bezier(.23,1,.32,1) both;font-family:"MMIntakeNunitoSans",var(--mm-font);font-variation-settings:"YTLC" 500,"wdth" 100;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}`,
302
317
  // Auto margins center the card when it fits and keep its top reachable when it overflows.
303
318
  ".mm-prompt-card{position:relative;isolation:isolate;display:flex;flex:none;flex-direction:column;margin-block:auto;width:600px;max-width:100%;padding:16px 16px 32px;border-radius:16px;background:var(--mm-prompt-blue);color:var(--mm-white);box-shadow:0 4px 5px rgba(0,0,0,.1),0 7px 10px rgba(0,0,0,.07),0 2px 16px rgba(0,0,0,.08);animation:mm-prompt-card-in .25s cubic-bezier(.23,1,.32,1) both}",
304
319
  ".mm-prompt-status{position:relative;z-index:1;display:flex;align-items:center;gap:13px;align-self:flex-start;max-width:calc(100% - 52px);padding:8px 40px 8px 8px;border-radius:999px;background:var(--mm-prompt-dark);color:var(--mm-prompt-text)}",
305
320
  ".mm-prompt-avatar{position:relative;flex:none;width:56px;height:56px}",
306
- ".mm-prompt-team{display:block;width:100%;height:100%;border-radius:50%}",
321
+ // Crop the original at (310, 234, 454, 454), then preserve Figma's avatar framing in CSS.
322
+ ".mm-prompt-team-crop{position:absolute;inset:0;overflow:hidden;border-radius:50%;transform:scaleX(-1)}",
323
+ ".mm-prompt-team{position:absolute;display:block;left:-3.26787%;top:-0.29881%;width:103.65294%;height:103.65298%;max-width:none;transform:rotate(-1.62035deg);transform-origin:0 0}",
307
324
  // The exported indicator bounds include Figma's 2px outside stroke.
308
325
  ".mm-prompt-online{position:absolute;right:-2px;top:40.17px;display:block;line-height:0}",
309
326
  ".mm-prompt-online img{display:block}",
@@ -313,8 +330,9 @@
313
330
  '.mm-prompt-title{position:relative;z-index:1;flex:1;min-width:0;height:232px;display:flex;flex-direction:column;justify-content:flex-end;margin:0;font-family:"MMIntakeKuunari",Impact,sans-serif;font-size:72px;font-weight:700;line-height:64px;letter-spacing:-2px;text-transform:uppercase}',
314
331
  ".mm-prompt-title-line{display:block;white-space:nowrap}",
315
332
  ".mm-prompt-mobile-break{display:none}",
316
- ".mm-prompt-portrait{position:relative;flex:none;width:270px;height:304px}",
317
- ".mm-prompt-image{display:block;width:100%;height:100%;pointer-events:none}",
333
+ // Figma node 21250:169707 crops the transparent source without baking in the card background.
334
+ ".mm-prompt-portrait{position:relative;flex:none;width:270px;height:304px;overflow:hidden}",
335
+ ".mm-prompt-image{position:absolute;display:block;left:-40.22%;top:-3.95%;width:181.72%;height:107.91%;max-width:none;pointer-events:none}",
318
336
  `.mm-prompt-cta{position:relative;display:flex;align-items:center;justify-content:center;gap:16px;height:56px;margin:0 16px;border-radius:8px;background:linear-gradient(to bottom,var(--mm-prompt-yellow) 41.827%,var(--mm-prompt-yellow-end));box-shadow:0 2px 4px rgba(0,0,0,.05),0 4px 3px rgba(0,0,0,.04),0 1px 2px rgba(0,0,0,.03);color:${designSystemColors["base-black"]};font-size:20px;font-weight:700;line-height:24px}`,
319
337
  ".mm-prompt-cta-label{min-width:90px}",
320
338
  // Preserve the Figma text frames in the SVG exports so centering retains their alignment.
@@ -334,11 +352,11 @@
334
352
  const promptMarkup = (assetUrl) => [
335
353
  '<div class="mm-prompt-card">',
336
354
  '<div class="mm-prompt-status"><span class="mm-prompt-avatar" aria-hidden="true">',
337
- `<img loading="lazy" class="mm-prompt-team" src="${assetUrl("consultation-team.png")}" alt="" width="56" height="56" decoding="async" />`,
355
+ `<span class="mm-prompt-team-crop"><img loading="lazy" class="mm-prompt-team" src="${assetUrl("consultation-team.webp")}" alt="" width="360" height="360" decoding="async" /></span>`,
338
356
  `<picture class="mm-prompt-online"><source media="(max-width:623px)" srcset="${assetUrl("consultation-online-mobile.svg")}" /><img loading="lazy" src="${assetUrl("consultation-online-desktop.svg")}" alt="" /></picture>`,
339
357
  '</span><p class="mm-prompt-status-text">Our team is <br class="mm-prompt-mobile-break" />available 24/7</p></div>',
340
358
  '<div class="mm-prompt-hero"><h2 class="mm-prompt-title"><span class="mm-prompt-title-line">Click <br class="mm-prompt-mobile-break" />to start</span><span class="mm-prompt-title-line">your free</span><span class="mm-prompt-title-line">consultation</span></h2>',
341
- `<div class="mm-prompt-portrait" aria-hidden="true"><img loading="lazy" class="mm-prompt-image" src="${assetUrl("consultation-john-morgan.png")}" alt="" width="270" height="304" decoding="async" /></div></div>`,
359
+ `<div class="mm-prompt-portrait" aria-hidden="true"><img loading="lazy" class="mm-prompt-image" src="${assetUrl("consultation-john-morgan.webp")}" alt="" width="1500" height="1000" decoding="async" /></div></div>`,
342
360
  `<span class="mm-prompt-cta" aria-hidden="true"><img loading="lazy" class="mm-prompt-chat-icon" src="${assetUrl("consultation-chat.svg")}" alt="" width="24" height="29" /><span class="mm-prompt-cta-label">Chat now</span></span>`,
343
361
  '<button class="mm-prompt-open" type="button" aria-label="Chat now about a free consultation"></button>',
344
362
  `<button class="mm-prompt-close" type="button" aria-label="Dismiss consultation prompt"><img class="mm-prompt-close-icon" src="${assetUrl("consultation-close.svg")}" alt="" width="18" height="29" /><span class="mm-prompt-close-contrast" aria-hidden="true">×</span></button>`,
@@ -412,7 +430,7 @@
412
430
  '<section class="mm-chat" role="dialog" aria-label="Morgan and Morgan intake chat" inert hidden>',
413
431
  '<div class="mm-frame-wrap"><div class="mm-loading" role="status" aria-label="Loading secure chat"><span class="mm-dot"></span><span class="mm-dot"></span><span class="mm-dot"></span></div><iframe class="mm-frame mm-chat-frame" title="Morgan and Morgan intake chat" referrerpolicy="strict-origin-when-cross-origin"></iframe></div>',
414
432
  "</section>",
415
- '<section class="mm-prompt-backdrop" role="dialog" aria-modal="true" aria-label="Start your free consultation" inert hidden>',
433
+ '<section class="mm-prompt-backdrop" role="dialog" aria-modal="true" aria-label="Start your free consultation" tabindex="-1" inert hidden>',
416
434
  promptMarkup(assetUrl),
417
435
  "</section>"
418
436
  ].join("");
@@ -438,7 +456,6 @@
438
456
  };
439
457
  };
440
458
  const PANEL_ANIMATION_MS = 180;
441
- const PROMPT_DELAY_MS = 5e3;
442
459
  const PROMPT_COOLDOWN_MS = 24 * 60 * 60 * 1e3;
443
460
  const PROMPT_COOLDOWN_KEY = "mm-intake-widget-prompt-dismissed-at";
444
461
  const PROMPT_SESSION_KEY = "mm-intake-widget-prompt-dismissed";
@@ -480,12 +497,13 @@
480
497
  }
481
498
  };
482
499
  const mountIntakeWidget = (config, onDestroy) => {
500
+ var _a;
483
501
  const mount = document.createElement("div");
484
502
  mount.dataset.mmIntakeWidget = "";
485
503
  const shadow = mount.attachShadow({ mode: "open" });
486
504
  const assetUrl = (fileName) => {
487
- var _a;
488
- return new URL(fileName, (_a = config.assetBaseUrl) != null ? _a : `${config.chatOrigin}/intake-widget/`).href;
505
+ var _a2;
506
+ return new URL(fileName, (_a2 = config.assetBaseUrl) != null ? _a2 : `${config.chatOrigin}/intake-widget/`).href;
489
507
  };
490
508
  const elements = renderWidgetShell(shadow, assetUrl);
491
509
  document.body.append(mount);
@@ -511,8 +529,8 @@
511
529
  elements.launcher.setAttribute("aria-label", isOpen ? "Close intake chat" : "Open intake chat");
512
530
  };
513
531
  const postToChat = (message) => {
514
- var _a;
515
- (_a = elements.chatFrame.contentWindow) == null ? void 0 : _a.postMessage(message, config.chatOrigin);
532
+ var _a2;
533
+ (_a2 = elements.chatFrame.contentWindow) == null ? void 0 : _a2.postMessage(message, config.chatOrigin);
516
534
  };
517
535
  const postHostReady = () => {
518
536
  const currentUrl = globalThis.location.href;
@@ -540,10 +558,10 @@
540
558
  postToChat(createEmbedProtocolMessage(EMBED_MESSAGE_TYPE.FOCUS_COMPOSER));
541
559
  };
542
560
  const answerAuthTokenRequest = async () => {
543
- var _a;
561
+ var _a2;
544
562
  let token;
545
563
  try {
546
- token = await ((_a = config.getAuthToken) == null ? void 0 : _a.call(config));
564
+ token = await ((_a2 = config.getAuthToken) == null ? void 0 : _a2.call(config));
547
565
  } catch {
548
566
  token = void 0;
549
567
  }
@@ -575,18 +593,18 @@
575
593
  }
576
594
  };
577
595
  const showPrompt = () => {
578
- var _a, _b, _c;
596
+ var _a2, _b, _c;
579
597
  globalThis.clearTimeout(promptTimer);
580
598
  promptTimer = void 0;
581
599
  const host = globalThis;
582
- const mxPopup = (_c = (_a = host.drupalSettings) == null ? void 0 : _a.mxPopup) != null ? _c : (_b = host.mxChatbotSettings) == null ? void 0 : _b.mxPopup;
600
+ const mxPopup = (_c = (_a2 = host.drupalSettings) == null ? void 0 : _a2.mxPopup) != null ? _c : (_b = host.mxChatbotSettings) == null ? void 0 : _b.mxPopup;
583
601
  if (mxPopup === false || hasPromptCooldown() || elements.chatDialog.dataset.state === "open") {
584
602
  return;
585
603
  }
586
604
  promptReturnFocus = document.activeElement instanceof HTMLElement && document.activeElement !== mount ? document.activeElement : null;
587
605
  elements.promptBackdrop.hidden = false;
588
606
  elements.promptBackdrop.removeAttribute("inert");
589
- elements.promptClose.focus();
607
+ elements.promptBackdrop.focus({ preventScroll: true });
590
608
  };
591
609
  const syncViewport = () => {
592
610
  const viewport = globalThis.visualViewport;
@@ -641,7 +659,7 @@
641
659
  }
642
660
  };
643
661
  const restoreHostChrome = () => {
644
- var _a, _b;
662
+ var _a2, _b;
645
663
  if (themeColorBackup === null) {
646
664
  return;
647
665
  }
@@ -652,7 +670,7 @@
652
670
  createdThemeColor = null;
653
671
  themeColorBackup = null;
654
672
  for (const [index, root] of hostRoots.entries()) {
655
- root.style.backgroundColor = (_a = hostBackgroundColorBackup == null ? void 0 : hostBackgroundColorBackup[index]) != null ? _a : "";
673
+ root.style.backgroundColor = (_a2 = hostBackgroundColorBackup == null ? void 0 : hostBackgroundColorBackup[index]) != null ? _a2 : "";
656
674
  root.style.overflow = (_b = hostOverflowBackup == null ? void 0 : hostOverflowBackup[index]) != null ? _b : "";
657
675
  }
658
676
  hostBackgroundColorBackup = null;
@@ -678,16 +696,16 @@
678
696
  syncHostChrome();
679
697
  }
680
698
  const startViewportTracking = () => {
681
- var _a, _b;
682
- (_a = globalThis.visualViewport) == null ? void 0 : _a.addEventListener("resize", syncViewport);
699
+ var _a2, _b;
700
+ (_a2 = globalThis.visualViewport) == null ? void 0 : _a2.addEventListener("resize", syncViewport);
683
701
  (_b = globalThis.visualViewport) == null ? void 0 : _b.addEventListener("scroll", syncViewport);
684
702
  fullPagePanelQuery == null ? void 0 : fullPagePanelQuery.addEventListener("change", syncHostChrome);
685
703
  syncViewport();
686
704
  syncHostChrome();
687
705
  };
688
706
  const stopViewportTracking = () => {
689
- var _a, _b;
690
- (_a = globalThis.visualViewport) == null ? void 0 : _a.removeEventListener("resize", syncViewport);
707
+ var _a2, _b;
708
+ (_a2 = globalThis.visualViewport) == null ? void 0 : _a2.removeEventListener("resize", syncViewport);
691
709
  (_b = globalThis.visualViewport) == null ? void 0 : _b.removeEventListener("scroll", syncViewport);
692
710
  fullPagePanelQuery == null ? void 0 : fullPagePanelQuery.removeEventListener("change", syncHostChrome);
693
711
  setKeyboardOpen(false);
@@ -724,7 +742,7 @@
724
742
  }
725
743
  };
726
744
  const closeChat = () => {
727
- var _a;
745
+ var _a2;
728
746
  elements.launcher.focus();
729
747
  writeOpenState(false);
730
748
  const wasOpen = elements.chatDialog.dataset.state === "open";
@@ -737,7 +755,7 @@
737
755
  if (!wasOpen) {
738
756
  return;
739
757
  }
740
- if (((_a = matchMediaQuery("(prefers-reduced-motion: reduce)")) == null ? void 0 : _a.matches) === true) {
758
+ if (((_a2 = matchMediaQuery("(prefers-reduced-motion: reduce)")) == null ? void 0 : _a2.matches) === true) {
741
759
  finishChatClose();
742
760
  return;
743
761
  }
@@ -791,14 +809,9 @@
791
809
  };
792
810
  const handleKeydown = (event) => {
793
811
  if (event.key === "Tab" && elements.promptBackdrop.hidden === false) {
794
- const { activeElement } = shadow;
795
- if (!event.shiftKey && activeElement === elements.promptClose) {
796
- event.preventDefault();
797
- elements.promptOpen.focus();
798
- } else if (event.shiftKey && activeElement === elements.promptOpen) {
799
- event.preventDefault();
800
- elements.promptClose.focus();
801
- }
812
+ event.preventDefault();
813
+ const next = shadow.activeElement === elements.promptClose ? elements.promptOpen : elements.promptClose;
814
+ next.focus();
802
815
  return;
803
816
  }
804
817
  if (event.key !== "Escape") {
@@ -854,7 +867,7 @@
854
867
  if (returnedFromSigning || readOpenState()) {
855
868
  openChat();
856
869
  } else if (!hasPromptCooldown()) {
857
- promptTimer = globalThis.setTimeout(showPrompt, PROMPT_DELAY_MS);
870
+ promptTimer = globalThis.setTimeout(showPrompt, (_a = config.promptDelayMs) != null ? _a : DEFAULT_PROMPT_DELAY_MS);
858
871
  }
859
872
  return Object.freeze({
860
873
  close: closeChat,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morgan-and-morgan/intake-agent",
3
- "version": "1.0.0-alpha.16",
3
+ "version": "1.0.0-alpha.18",
4
4
  "description": "Embeddable chat widget loader.",
5
5
  "license": "UNLICENSED",
6
6
  "files": [
@@ -34,6 +34,6 @@
34
34
  "@typescript/native-preview": "catalog:",
35
35
  "lucide-react-native": "^0.562.0",
36
36
  "typescript": "~5.9.3",
37
- "vite": "^7.3.6"
37
+ "vite": "^7.3.7"
38
38
  }
39
39
  }