@v-tilt/browser 1.12.0 → 1.13.1

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.
Files changed (109) hide show
  1. package/dist/all-external-dependencies.js.map +1 -1
  2. package/dist/array.chat.js +2 -0
  3. package/dist/array.chat.js.map +1 -0
  4. package/dist/array.chat.no-external.js +2 -0
  5. package/dist/array.chat.no-external.js.map +1 -0
  6. package/dist/array.full.chat.js +2 -0
  7. package/dist/array.full.chat.js.map +1 -0
  8. package/dist/array.full.chat.no-external.js +2 -0
  9. package/dist/array.full.chat.no-external.js.map +1 -0
  10. package/dist/array.full.js +1 -1
  11. package/dist/array.full.js.map +1 -1
  12. package/dist/array.full.no-external.js +2 -0
  13. package/dist/array.full.no-external.js.map +1 -0
  14. package/dist/array.js +1 -1
  15. package/dist/array.js.map +1 -1
  16. package/dist/array.no-external.js +1 -1
  17. package/dist/array.no-external.js.map +1 -1
  18. package/dist/chat.js +1 -1
  19. package/dist/chat.js.map +1 -1
  20. package/dist/entrypoints/all-external-dependencies.d.ts +10 -3
  21. package/dist/entrypoints/array.chat.d.ts +10 -0
  22. package/dist/entrypoints/array.chat.no-external.d.ts +6 -0
  23. package/dist/entrypoints/array.full.chat.d.ts +13 -0
  24. package/dist/entrypoints/array.full.chat.no-external.d.ts +7 -0
  25. package/dist/entrypoints/array.full.d.ts +5 -9
  26. package/dist/entrypoints/array.full.no-external.d.ts +12 -0
  27. package/dist/entrypoints/module.chat.es.d.ts +7 -0
  28. package/dist/entrypoints/module.full.chat.es.d.ts +12 -0
  29. package/dist/entrypoints/module.full.es.d.ts +12 -0
  30. package/dist/entrypoints/module.no-external.es.d.ts +1 -0
  31. package/dist/extensions/chat/bubble-drag.d.ts +20 -5
  32. package/dist/extensions/chat/chat-wrapper.d.ts +8 -2
  33. package/dist/extensions/chat/chat.d.ts +21 -221
  34. package/dist/extensions/chat/controller/__tests__/fakes/ably-realtime-fake.d.ts +84 -0
  35. package/dist/extensions/chat/controller/ably-client.d.ts +160 -0
  36. package/dist/extensions/chat/controller/ably-handlers.d.ts +95 -0
  37. package/dist/extensions/chat/controller/ably-token.d.ts +67 -0
  38. package/dist/extensions/chat/controller/chat-controller.d.ts +194 -0
  39. package/dist/extensions/chat/controller/message-delivery-status.d.ts +6 -0
  40. package/dist/extensions/chat/controller/message-order.d.ts +12 -0
  41. package/dist/extensions/chat/controller/message-stream.d.ts +49 -0
  42. package/dist/extensions/chat/lib/bubble-offset.d.ts +18 -0
  43. package/dist/extensions/chat/lib/merge-chat-config.d.ts +3 -0
  44. package/dist/extensions/chat/normalize-send-content.d.ts +2 -0
  45. package/dist/extensions/chat/outbox/message-delivery.d.ts +17 -0
  46. package/dist/extensions/chat/outbox/message-outbox.d.ts +57 -0
  47. package/dist/extensions/chat/store/chat-store.d.ts +122 -0
  48. package/dist/extensions/chat/types.d.ts +1 -19
  49. package/dist/extensions/chat/ui/ChannelItem.d.ts +12 -0
  50. package/dist/extensions/chat/ui/ChannelListView.d.ts +14 -0
  51. package/dist/extensions/chat/ui/ChatBubble.d.ts +14 -0
  52. package/dist/extensions/chat/ui/ChatHeader.d.ts +13 -0
  53. package/dist/extensions/chat/ui/ChatPanel.d.ts +14 -0
  54. package/dist/extensions/chat/ui/ChatRoot.d.ts +31 -0
  55. package/dist/extensions/chat/ui/ClosedBanner.d.ts +7 -0
  56. package/dist/extensions/chat/ui/ConnectionBanner.d.ts +32 -0
  57. package/dist/extensions/chat/ui/ConversationView.d.ts +14 -0
  58. package/dist/extensions/chat/ui/MessageBubble.d.ts +25 -0
  59. package/dist/extensions/chat/ui/MessageInput.d.ts +14 -0
  60. package/dist/extensions/chat/ui/MessageList.d.ts +19 -0
  61. package/dist/extensions/chat/ui/NewMessagesPill.d.ts +13 -0
  62. package/dist/extensions/chat/ui/Skeletons.d.ts +14 -0
  63. package/dist/extensions/chat/ui/TypingBubble.d.ts +23 -0
  64. package/dist/extensions/chat/ui/TypingIndicator.d.ts +12 -0
  65. package/dist/extensions/chat/ui/WidgetSlot.d.ts +20 -0
  66. package/dist/extensions/chat/ui/hooks/use-auto-mark-read.d.ts +18 -0
  67. package/dist/extensions/chat/ui/hooks/use-scroll-preservation.d.ts +33 -0
  68. package/dist/extensions/chat/ui/icons.d.ts +18 -0
  69. package/dist/extensions/chat/ui/message-render.d.ts +18 -0
  70. package/dist/extensions/chat/ui/shadow-styles.d.ts +13 -0
  71. package/dist/extensions/chat/ui/utils/mobile-body-scroll-lock.d.ts +14 -0
  72. package/dist/extensions/chat/widget-registry.d.ts +21 -5
  73. package/dist/extensions/chat/widgets/collect-email.d.ts +4 -2
  74. package/dist/extensions/chat/widgets/escalate-to-human.d.ts +2 -1
  75. package/dist/external-scripts-loader.js +1 -1
  76. package/dist/external-scripts-loader.js.map +1 -1
  77. package/dist/feature.d.ts +1 -0
  78. package/dist/lib/merge-vtilt-config.d.ts +9 -0
  79. package/dist/main.js +1 -1
  80. package/dist/main.js.map +1 -1
  81. package/dist/module.chat.d.ts +1788 -0
  82. package/dist/module.chat.js +2 -0
  83. package/dist/module.chat.js.map +1 -0
  84. package/dist/module.d.ts +48 -3
  85. package/dist/module.full.chat.d.ts +1792 -0
  86. package/dist/module.full.chat.js +2 -0
  87. package/dist/module.full.chat.js.map +1 -0
  88. package/dist/module.full.d.ts +1793 -0
  89. package/dist/module.full.js +2 -0
  90. package/dist/module.full.js.map +1 -0
  91. package/dist/module.js +1 -1
  92. package/dist/module.js.map +1 -1
  93. package/dist/module.no-external.d.ts +48 -3
  94. package/dist/module.no-external.js +1 -1
  95. package/dist/module.no-external.js.map +1 -1
  96. package/dist/recorder.js.map +1 -1
  97. package/dist/snippet-stub-methods.d.ts +14 -0
  98. package/dist/utils/globals.d.ts +53 -27
  99. package/dist/utils/index.d.ts +8 -0
  100. package/dist/vtilt.d.ts +6 -1
  101. package/dist/web-vitals.js.map +1 -1
  102. package/package.json +4 -1
  103. package/dist/extensions/chat/chat-styles.d.ts +0 -27
  104. package/dist/extensions/chat/message-content-styles.d.ts +0 -1
  105. package/dist/extensions/chat/message-html.d.ts +0 -6
  106. package/dist/extensions/chat/message-markdown.d.ts +0 -8
  107. package/dist/extensions/ga4-proxy.d.ts +0 -59
  108. package/dist/utils/type-utils.d.ts +0 -4
  109. package/dist/web-vitals.d.ts +0 -81
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Single CSS string injected into the chat widget's Shadow DOM.
3
+ *
4
+ * Theme variables (`--vtilt-primary`, `--vtilt-font`, `--vtilt-radius`) are
5
+ * set on `:host` from the ChatStore's theme signal, so component classes can
6
+ * stay theme-agnostic. The widget lives inside a Shadow DOM so styles never
7
+ * leak out to (or in from) the host page.
8
+ *
9
+ * Keep list / code-block / markdown rules in sync with
10
+ * app/src/shared/styles/chat-message-content.css — the dashboard reuses the
11
+ * same `.vtilt-md` / `.vtilt-user-md` class names for inbox previews.
12
+ */
13
+ export declare const SHADOW_STYLES = "\n/* Load Inter so the widget looks like vTilt on every host page. The @import\n sits at the very top of the stylesheet (required by the spec) and only\n fetches the variable-axis subset we need. If the host's CSP blocks Google\n Fonts the font-family fallback chain below keeps the widget legible. */\n@import url(\"https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap\");\n\n:host {\n --vtilt-primary: #7B68EE;\n --vtilt-primary-shadow: rgba(123, 104, 238, 0.4);\n --vtilt-font: \"Inter var\", \"Inter\", -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif;\n --vtilt-radius: 16px;\n --vtilt-text: #000000;\n --vtilt-text-muted: #666666;\n --vtilt-text-subtle: #888888;\n --vtilt-bg: #ffffff;\n --vtilt-bg-list: #FAFAFA;\n --vtilt-border: #E5E5E5;\n --vtilt-border-light: #EEEEEE;\n --vtilt-skeleton: #E8E8EA;\n --vtilt-online: #16A34A;\n\n all: initial;\n font-family: var(--vtilt-font);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n color: var(--vtilt-text);\n}\n\n*, *::before, *::after {\n box-sizing: border-box;\n margin: 0;\n padding: 0;\n}\n\nbutton {\n font-family: inherit;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n touch-action: manipulation;\n}\n\n/* =========================================================================\n * Container + bubble + panel\n * ========================================================================= */\n\n/* The host element gets 'all: initial' inline (set in ChatRoot.tsx) so the\n host page can't position or style it. Inline styles win over ':host' rules\n in the cascade, so the host's effective font-family / color end up at the\n UA default \u2014 and shadow children inherit from there. We re-establish the\n widget typography on .vt-container (which lives *inside* the shadow tree)\n so every descendant inherits Inter + the themed text color, regardless of\n what the host page or the host element's inline reset look like. */\n.vt-container {\n position: fixed;\n bottom: var(--vt-bubble-offset-bottom, 20px);\n /* Pin the container to the bubble's intrinsic size so it keeps the\n * same bounding box even after the bubble itself goes display:none on\n * open. Without this the container collapses to 0x0 and the absolutely\n * positioned panel anchors at that zero-size point \u2014 which means the\n * opened panel no longer overlaps the bubble's last visible footprint\n * for any non-default anchor (e.g. a bubble dragged to the bottom-left\n * with data-position=\"bottom-right\" would see the panel jump 60px away\n * from where the bubble had been). With a fixed container size, the\n * panel's [data-panel-anchor] corner always coincides with the matching\n * corner of the bubble, regardless of drag offset. */\n width: 60px;\n height: 60px;\n z-index: 999999;\n font-family: var(--vtilt-font);\n font-size: 14px;\n line-height: 1.4;\n color: var(--vtilt-text);\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.vt-container[data-position=\"bottom-right\"] {\n right: var(--vt-bubble-offset-right, 20px);\n}\n.vt-container[data-position=\"bottom-left\"] {\n left: var(--vt-bubble-offset-left, 20px);\n}\n.vt-container[data-hidden=\"true\"] { display: none; }\n\n.vt-bubble {\n width: 60px;\n height: 60px;\n border-radius: 50%;\n background: var(--vtilt-primary);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 16px var(--vtilt-primary-shadow);\n transition: transform 0.15s ease, box-shadow 0.15s ease;\n position: relative;\n border: none;\n}\n.vt-bubble:hover { transform: scale(1.05); box-shadow: 0 6px 20px var(--vtilt-primary-shadow); }\n.vt-bubble:active { transform: scale(0.95); }\n.vt-container[data-open=\"true\"] .vt-bubble { display: none; }\n.vt-container[data-draggable=\"true\"] .vt-bubble:hover,\n.vt-container[data-draggable=\"true\"] .vt-bubble:active { transform: none; }\n\n.vt-bubble-badge {\n display: none;\n position: absolute;\n top: -4px;\n right: -4px;\n background: #E53935;\n color: white;\n font-size: 11px;\n font-weight: 700;\n min-width: 20px;\n height: 20px;\n border-radius: 10px;\n align-items: center;\n justify-content: center;\n padding: 0 6px;\n border: 2px solid white;\n}\n.vt-bubble-badge[data-visible=\"true\"] { display: flex; }\n\n/* Panel position is driven by [data-panel-anchor] on the container.\n * The chat widget recomputes the anchor from the bubble's actual viewport\n * position (see usePanelAnchor in ui/ChatRoot.tsx) so the panel always\n * extends into whichever side has more room \u2014 even after the bubble has\n * been dragged across the viewport. The default anchor (no attribute) is\n * \"bottom-right\" so the very first paint, before JS has computed the\n * anchor, matches the most common default position.\n *\n * Per-anchor max-width / max-height are exposed as the CSS variables\n * --vt-panel-max-w and --vt-panel-max-h. JS sets them to the available\n * room from the chosen anchor (capped by the panel's intrinsic 380x600\n * design size), so on small or oddly-shaped viewports the panel never\n * overruns the opposite edge. The min(...) wrapper falls back to the\n * original \"viewport minus 40px\" formula when the variables aren't set. */\n.vt-panel {\n display: none;\n flex-direction: column;\n position: absolute;\n bottom: 0;\n right: 0;\n width: 380px;\n height: 600px;\n /* Effective size = min(intrinsic, room-from-anchor, viewport-minus-margin).\n * The viewport term is a backstop: while the chat is open we deliberately\n * freeze the per-anchor variable, so a viewport resize during open could\n * otherwise leave a stale value that overruns the opposite edge. */\n max-width: min(380px, var(--vt-panel-max-w, calc(100vw - 40px)), calc(100vw - 40px));\n max-height: min(600px, var(--vt-panel-max-h, calc(100vh - 40px)), calc(100vh - 40px));\n max-height: min(600px, var(--vt-panel-max-h, calc(100dvh - 40px)), calc(100dvh - 40px));\n background: var(--vtilt-bg);\n border-radius: var(--vtilt-radius);\n box-shadow: 0 5px 40px rgba(0, 0, 0, 0.16);\n overflow: hidden;\n}\n.vt-container[data-panel-anchor=\"bottom-left\"] .vt-panel { right: auto; left: 0; }\n.vt-container[data-panel-anchor=\"top-right\"] .vt-panel { bottom: auto; top: 0; }\n.vt-container[data-panel-anchor=\"top-left\"] .vt-panel { bottom: auto; top: 0; right: auto; left: 0; }\n.vt-container[data-open=\"true\"] .vt-panel { display: flex; }\n.vt-container[data-open=\"true\"] .vt-panel.vt-opening {\n animation: vt-open 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n /* The animation scales from 0.4 to 1; transform-origin decides which\n * corner of the panel that scale grows from. JS sets --vt-panel-origin\n * to the same corner usePanelAnchor picked for [data-panel-anchor], so\n * the panel always appears to \"pop\" out of the bubble. The fallback\n * matches the default bottom-right anchor used before JS computes a\n * direction (or in environments where the variable isn't set). */\n transform-origin: var(--vt-panel-origin, bottom right);\n}\n\n@keyframes vt-open {\n from { opacity: 0; transform: scale(0.4); }\n to { opacity: 1; transform: scale(1); }\n}\n\n/* =========================================================================\n * Header\n * ========================================================================= */\n\n.vt-header {\n background: var(--vtilt-bg);\n border-bottom: 1px solid var(--vtilt-border);\n padding: 18px 16px;\n padding-top: max(18px, env(safe-area-inset-top, 18px));\n display: flex;\n align-items: center;\n min-height: 60px;\n flex-shrink: 0;\n gap: 12px;\n}\n.vt-header[data-view=\"list\"] {\n justify-content: space-between;\n}\n.vt-header[data-view=\"conversation\"] {\n padding: 12px 16px;\n padding-top: max(12px, env(safe-area-inset-top, 12px));\n}\n\n.vt-header-title { font-weight: 600; font-size: 17px; color: var(--vtilt-text); }\n.vt-header-info { flex: 1; min-width: 0; }\n.vt-header-name { font-weight: 600; font-size: 16px; color: var(--vtilt-text); }\n.vt-header-status {\n font-size: 13px;\n color: var(--vtilt-online);\n display: flex;\n align-items: center;\n gap: 5px;\n margin-top: 1px;\n}\n.vt-header-status::before {\n content: \"\";\n width: 7px;\n height: 7px;\n background: var(--vtilt-online);\n border-radius: 50%;\n}\n\n.vt-iconbtn {\n background: transparent;\n border: none;\n color: var(--vtilt-text-muted);\n padding: 6px;\n border-radius: 4px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n}\n.vt-iconbtn:hover { color: var(--vtilt-text); background: #F0F0F0; }\n.vt-header-back { margin-left: -6px; }\n.vt-header-close { margin-right: -6px; }\n\n.vt-header-avatar {\n width: 44px;\n height: 44px;\n border-radius: 50%;\n background: var(--vtilt-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: white;\n}\n.vt-header-avatar[data-mode=\"support\"] { background: #DEDEDE; color: #666; }\n\n/* =========================================================================\n * Content area (channel list, conversation)\n * ========================================================================= */\n\n.vt-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n overflow: hidden;\n min-height: 0;\n background: var(--vtilt-bg);\n}\n\n/* Channel list ----------------------------------------------------------- */\n\n.vt-list-root {\n flex: 1;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n}\n\n.vt-list-empty {\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 48px 24px;\n text-align: center;\n}\n.vt-list-empty-icon {\n width: 72px;\n height: 72px;\n margin-bottom: 24px;\n background: var(--vtilt-primary);\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n.vt-list-empty-title {\n font-size: 18px;\n font-weight: 600;\n color: var(--vtilt-text);\n margin-bottom: 12px;\n line-height: 1.4;\n max-width: 280px;\n}\n.vt-list-empty-subtitle {\n font-size: 14px;\n color: var(--vtilt-text-muted);\n margin-bottom: 28px;\n}\n\n.vt-channel-item {\n padding: 14px 16px;\n cursor: pointer;\n display: flex;\n align-items: center;\n gap: 12px;\n background: var(--vtilt-bg);\n border: none;\n width: 100%;\n text-align: left;\n border-bottom: 1px solid var(--vtilt-border-light);\n transition: background 0.1s ease;\n font: inherit;\n color: inherit;\n}\n.vt-channel-item:hover { background: #F5F5F5; }\n.vt-channel-item:active { background: #EBEBEB; }\n\n.vt-channel-avatar {\n width: 48px;\n height: 48px;\n border-radius: 50%;\n background: var(--vtilt-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: white;\n}\n.vt-channel-avatar[data-mode=\"support\"] { background: #DEDEDE; color: #666; }\n\n.vt-channel-body { flex: 1; min-width: 0; }\n.vt-channel-row {\n display: flex;\n justify-content: space-between;\n align-items: center;\n gap: 8px;\n margin-bottom: 4px;\n}\n.vt-channel-name {\n font-weight: 500;\n font-size: 15px;\n color: var(--vtilt-text);\n line-height: 1.2;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n.vt-channel-name[data-unread=\"true\"] { font-weight: 600; }\n.vt-channel-time {\n font-size: 13px;\n color: var(--vtilt-text-subtle);\n white-space: nowrap;\n flex-shrink: 0;\n}\n.vt-channel-preview {\n font-size: 14px;\n color: var(--vtilt-text-subtle);\n font-weight: 400;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n flex: 1;\n min-width: 0;\n line-height: 1.4;\n}\n.vt-channel-preview[data-unread=\"true\"] { color: #333; }\n.vt-channel-unread {\n min-width: 20px;\n height: 20px;\n padding: 0 6px;\n background: var(--vtilt-primary);\n border-radius: 10px;\n flex-shrink: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 600;\n color: #FFFFFF;\n line-height: 1;\n}\n\n.vt-list-footer {\n padding: 16px;\n padding-bottom: max(16px, env(safe-area-inset-bottom, 16px));\n border-top: 1px solid var(--vtilt-border);\n}\n\n.vt-new-channel-btn {\n width: 100%;\n background: var(--vtilt-primary);\n color: white;\n border: none;\n border-radius: 100px;\n padding: 14px 24px;\n font-weight: 500;\n font-size: 15px;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 10px;\n box-shadow: 0 2px 8px var(--vtilt-primary-shadow);\n transition: opacity 0.1s ease;\n}\n.vt-new-channel-btn:hover { opacity: 0.9; }\n.vt-new-channel-btn:active { opacity: 0.8; }\n\n/* Conversation ----------------------------------------------------------- */\n\n.vt-messages-wrapper {\n position: relative;\n flex: 1;\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n\n.vt-messages {\n flex: 1;\n overflow-y: auto;\n -webkit-overflow-scrolling: touch;\n padding: 20px 16px 24px 16px;\n display: flex;\n flex-direction: column;\n gap: 12px;\n min-height: 0;\n background: var(--vtilt-bg-list);\n}\n\n.vt-message {\n display: flex;\n flex-direction: column;\n animation: vt-message-in 0.22s ease-out both;\n}\n.vt-message[data-direction=\"in\"] { align-items: flex-start; }\n.vt-message[data-direction=\"out\"] { align-items: flex-end; }\n\n.vt-message-row {\n display: flex;\n gap: 10px;\n align-items: flex-end;\n max-width: 85%;\n}\n.vt-message[data-direction=\"out\"] .vt-message-row {\n flex-direction: row-reverse;\n}\n\n.vt-message-avatar {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: var(--vtilt-primary);\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n color: white;\n}\n.vt-message-avatar[data-mode=\"support\"] { background: #DEDEDE; color: #666; }\n\n.vt-message-bubble {\n padding: 10px 14px;\n background: var(--vtilt-bg);\n border-radius: 20px 20px 20px 4px;\n font-size: 14px;\n line-height: 1.4;\n color: var(--vtilt-text);\n word-wrap: break-word;\n overflow-wrap: anywhere;\n min-width: 0;\n}\n.vt-message[data-direction=\"out\"] .vt-message-bubble {\n background: var(--vtilt-primary);\n color: white;\n border-radius: 20px 20px 4px 20px;\n}\n\n.vt-message-meta {\n font-size: 12px;\n color: var(--vtilt-text-subtle);\n margin-top: 6px;\n}\n.vt-message[data-direction=\"in\"] .vt-message-meta { margin-left: 46px; }\n.vt-message[data-direction=\"out\"] .vt-message-meta { margin-right: 4px; }\n\n.vt-message-retry {\n font: inherit;\n font-size: inherit;\n color: var(--vtilt-error, #dc2626);\n background: none;\n border: none;\n padding: 0;\n cursor: pointer;\n text-decoration: underline;\n}\n\n@keyframes vt-message-in {\n from { opacity: 0; transform: translateY(4px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n/* Markdown content rules (parity with chat-message-content.css) */\n.vt-md {\n max-width: 100%;\n overflow-wrap: anywhere;\n word-break: break-word;\n font-size: inherit;\n line-height: 1.45;\n}\n.vt-md p { margin: 0 0 0.5em 0; }\n.vt-md p:last-child { margin-bottom: 0; }\n.vt-md :is(b, strong) { font-weight: 600; }\n.vt-md :is(i, em) { font-style: italic; }\n.vt-md u { text-decoration: underline; }\n.vt-md :is(h1, h2, h3, h4, h5, h6) {\n font-size: 1em;\n font-weight: 600;\n margin: 0.65em 0 0.35em 0;\n line-height: 1.3;\n}\n.vt-md :is(h1, h2, h3):first-child { margin-top: 0.15em; }\n.vt-md :is(ul, ol) {\n list-style: none;\n margin: 0.5em 0;\n padding: 0 0 0 0.35em;\n}\n.vt-md li {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: flex-start;\n gap: 0.35em;\n margin: 0.2em 0;\n padding: 0;\n line-height: 1.45;\n}\n.vt-md ul > li::before {\n content: \"\u2022\";\n display: inline-block;\n flex: 0 0 auto;\n line-height: 1.45;\n}\n.vt-md ul ul > li::before { content: \"\u25E6\"; }\n.vt-md ol { counter-reset: vt-ol; }\n.vt-md ol > li { counter-increment: vt-ol; }\n.vt-md ol > li::before {\n content: counter(vt-ol) \".\";\n display: inline-block;\n flex: 0 0 auto;\n min-width: 1.1em;\n line-height: 1.45;\n}\n.vt-md ol ol { counter-reset: vt-ol; }\n.vt-md li > :is(ul, ol) {\n flex: 1 0 100%;\n width: 100%;\n margin: 0.25em 0 0 0;\n padding-left: 0.75em;\n}\n.vt-md code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.9em;\n background: rgba(0, 0, 0, 0.06);\n padding: 0.1em 0.35em;\n border-radius: 4px;\n}\n.vt-md pre {\n margin: 0.5em 0;\n padding: 0.5em 0.65em;\n border-radius: 6px;\n background: rgba(0, 0, 0, 0.06);\n overflow-x: auto;\n white-space: pre-wrap;\n}\n.vt-md pre code { background: none; padding: 0; }\n.vt-md blockquote {\n margin: 0.5em 0;\n padding-left: 0.75em;\n border-left: 3px solid rgba(0, 0, 0, 0.15);\n}\n.vt-md a { color: inherit; text-decoration: underline; }\n\n/* Brand-colored \"out\" bubble inverts code/pre/quote backdrop. */\n.vt-user-md code { background: rgba(255, 255, 255, 0.2); }\n.vt-user-md pre { background: rgba(255, 255, 255, 0.15); }\n.vt-user-md pre code { background: none; }\n.vt-user-md blockquote { border-left-color: rgba(255, 255, 255, 0.35); }\n\n/* Typing bubble + new-messages pill -------------------------------------\n *\n * The typing indicator is no longer a separate strip above the composer\n * - it is an inline .vt-message rendered as the last child of the\n * message list, reusing the avatar + bubble shape so the dots morph\n * into the real message when it arrives. The only typing-specific\n * style is the [data-typing] modifier that switches the bubble layout\n * to inline-flex so the three dots sit horizontally instead of\n * stacking. The dot animation rules are reused by TypingBubble. */\n\n.vt-message-bubble[data-typing=\"true\"] {\n display: inline-flex;\n align-items: center;\n gap: 5px;\n /* Critical: do NOT override the base .vt-message-bubble padding here.\n * The row uses align-items: flex-end, so a bubble shorter than a\n * single-line text bubble would bottom-align with the avatar and\n * visually sit lower than where the real message will appear. We\n * inherit 10px 14px from the base, then enforce a min content\n * height of one text line (14px font-size * 1.4 line-height) so the\n * total bubble height matches a single-line text bubble exactly.\n * When the dots morph into text the bubble stays put \u2014 no jolt. */\n min-height: 1.4em;\n}\n.vt-typing-dot {\n width: 6px;\n height: 6px;\n background: #B5B5B5;\n border-radius: 50%;\n animation: vt-typing-bounce 1.2s infinite;\n}\n.vt-typing-dot:nth-child(2) { animation-delay: 0.2s; }\n.vt-typing-dot:nth-child(3) { animation-delay: 0.4s; }\n@keyframes vt-typing-bounce {\n 0%, 60%, 100% { opacity: 0.35; transform: translateY(0); }\n 30% { opacity: 1; transform: translateY(-2px); }\n}\n\n.vt-new-pill {\n position: absolute;\n left: 50%;\n transform: translateX(-50%);\n bottom: 12px;\n background: var(--vtilt-primary);\n color: white;\n padding: 6px 14px;\n border-radius: 999px;\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n border: none;\n box-shadow: 0 2px 8px rgba(0,0,0,0.15);\n display: none;\n align-items: center;\n gap: 6px;\n z-index: 5;\n transition: opacity 0.15s ease;\n}\n.vt-new-pill[data-visible=\"true\"] { display: flex; }\n.vt-new-pill:hover { opacity: 0.92; }\n\n/* Closed banner + connection banner ------------------------------------- */\n\n.vt-closed-banner {\n padding: 12px 16px;\n padding-bottom: max(12px, env(safe-area-inset-bottom, 12px));\n border-top: 1px solid var(--vtilt-border);\n text-align: center;\n background: var(--vtilt-bg-list);\n flex-shrink: 0;\n font-size: 14px;\n color: var(--vtilt-text-subtle);\n}\n\n/* Connection banner.\n *\n * Two visual modes:\n * - data-kind=\"reconnecting\": slim pill. Pulsing dot + \"Reconnecting\"\n * label. Slate background \u2014 informational, not alarming.\n * - data-kind=\"failed\": actionable card. Warning icon + \"Connection\n * lost\" + Retry button. Red because the user must act.\n */\n.vt-conn-banner {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 6px 12px;\n border-top: 1px solid var(--vtilt-border);\n font-size: 12px;\n font-weight: 500;\n flex-shrink: 0;\n}\n.vt-conn-banner[data-kind=\"reconnecting\"] {\n background: #F8FAFC;\n color: #475569;\n justify-content: center;\n}\n.vt-conn-banner[data-kind=\"failed\"] {\n background: #FEF2F2;\n color: #991B1B;\n}\n.vt-conn-banner-icon {\n display: inline-flex;\n flex-shrink: 0;\n color: #DC2626;\n}\n.vt-conn-banner-text {\n flex: 1;\n}\n.vt-conn-banner[data-kind=\"reconnecting\"] .vt-conn-banner-text {\n flex: 0 0 auto;\n}\n.vt-conn-banner-dot {\n display: inline-block;\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: #F59E0B;\n flex-shrink: 0;\n animation: vt-conn-pulse 1.4s ease-in-out infinite;\n}\n@keyframes vt-conn-pulse {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.4; transform: scale(0.85); }\n}\n.vt-conn-banner-action {\n appearance: none;\n border: 1px solid #DC2626;\n background: transparent;\n color: #DC2626;\n font: inherit;\n font-weight: 600;\n font-size: 11px;\n padding: 3px 10px;\n border-radius: 999px;\n cursor: pointer;\n flex-shrink: 0;\n transition: background 0.12s ease, color 0.12s ease;\n}\n.vt-conn-banner-action:hover,\n.vt-conn-banner-action:focus-visible {\n background: #DC2626;\n color: #FFFFFF;\n outline: none;\n}\n@media (prefers-reduced-motion: reduce) {\n .vt-conn-banner-dot {\n animation: none;\n }\n}\n\n/* Input ----------------------------------------------------------------- */\n\n.vt-input-container {\n padding: 8px 12px;\n padding-bottom: max(8px, env(safe-area-inset-bottom, 8px));\n border-top: 1px solid var(--vtilt-border);\n flex-shrink: 0;\n background: var(--vtilt-bg);\n}\n.vt-input-box {\n position: relative;\n border: 1px solid #DDD;\n border-radius: 12px;\n background: var(--vtilt-bg);\n transition: border-color 0.15s ease, box-shadow 0.15s ease;\n}\n.vt-input-box:focus-within {\n border-color: var(--vtilt-primary);\n box-shadow: 0 0 0 2px rgba(123, 104, 238, 0.12);\n}\n.vt-input-textarea {\n width: 100%;\n box-sizing: border-box;\n border: none;\n border-radius: 0;\n padding: 12px 48px 12px 12px;\n font-size: 16px;\n line-height: 1.4;\n outline: none;\n background: transparent;\n color: var(--vtilt-text);\n resize: none;\n overflow-y: auto;\n overflow-x: hidden;\n min-height: 60px;\n max-height: 120px;\n font-family: inherit;\n}\n.vt-input-textarea::placeholder { color: #999; }\n.vt-input-textarea:disabled { color: #BBB; cursor: not-allowed; }\n\n.vt-input-send {\n position: absolute;\n right: 4px;\n bottom: 4px;\n background: #E5E5E5;\n color: #999;\n border: none;\n border-radius: 50%;\n padding: 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: background-color 0.15s ease, color 0.15s ease, opacity 0.15s ease;\n opacity: 0.6;\n}\n.vt-input-send[data-active=\"true\"] {\n background: var(--vtilt-primary);\n color: white;\n cursor: pointer;\n opacity: 1;\n}\n.vt-input-send[data-active=\"true\"]:hover { opacity: 0.9; }\n.vt-input-send[data-active=\"true\"]:active { opacity: 0.8; transform: scale(0.95); }\n.vt-input-send:disabled { cursor: not-allowed; }\n\n.vt-input-container[data-pending=\"true\"] .vt-input-box {\n background: var(--vtilt-bg-list);\n border-color: var(--vtilt-border-light);\n}\n\n/* Skeletons ------------------------------------------------------------- */\n\n@keyframes vt-skeleton-pulse {\n 0%, 100% { opacity: 1; }\n 50% { opacity: 0.55; }\n}\n.vt-skeleton {\n background: var(--vtilt-skeleton);\n border-radius: 6px;\n animation: vt-skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n.vt-skeleton-circle {\n background: var(--vtilt-skeleton);\n border-radius: 50%;\n animation: vt-skeleton-pulse 2.2s cubic-bezier(0.4, 0, 0.6, 1) infinite;\n}\n\n/* Built-in widgets ------------------------------------------------------ */\n\n.vt-widget { margin-top: 10px; }\n.vt-widget p { margin: 0 0 10px 0; font-size: 14px; color: #333; }\n.vt-widget-input {\n font-family: inherit;\n font-size: 14px;\n padding: 8px 10px;\n border: 1px solid #DDD;\n border-radius: 8px;\n width: 100%;\n margin-bottom: 8px;\n}\n.vt-widget-input:focus { outline: none; border-color: var(--vtilt-primary); }\n.vt-widget-btn {\n padding: 10px 16px;\n background: var(--vtilt-primary);\n color: white;\n border: none;\n border-radius: 12px;\n font-size: 14px;\n font-weight: 600;\n cursor: pointer;\n}\n.vt-widget-btn[data-style=\"outline\"] {\n background: transparent;\n color: var(--vtilt-primary);\n border: 2px solid var(--vtilt-primary);\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n.vt-widget-btn:disabled { opacity: 0.6; cursor: not-allowed; }\n.vt-widget-thanks { font-size: 13px; color: var(--vtilt-online); margin: 0; }\n\n/* Mobile fullscreen ----------------------------------------------------\n *\n * On viewports <=480px the open panel covers the whole device, regardless\n * of where the bubble was dragged. The rules live in a media query (not\n * a JS-driven data attribute) so they remain correct on viewport resize\n * \u2014 rotation, browser-window resize, devtools toggling \u2014 without needing\n * a re-render to update an attribute.\n *\n * \"transform: none !important\" is required because bubble-drag.ts sets\n * an inline \"transform: translate3d(...)\" on the container. Per the CSS\n * cascade, author !important declarations win over inline normal styles,\n * so the open panel snaps back to (0, 0) and fills the viewport even\n * after the user has dragged the bubble away from its anchor corner.\n */\n\n@media (max-width: 480px) {\n .vt-bubble { width: 56px; height: 56px; }\n .vt-container { width: 56px; height: 56px; }\n\n .vt-container[data-open=\"true\"] {\n top: 0; left: 0; right: 0; bottom: 0;\n /* Stretch the container from a 56x56 box back to the viewport so the\n * fullscreen panel inside has room to fill the screen. */\n width: auto; height: auto;\n transform: none !important;\n }\n .vt-container[data-open=\"true\"] .vt-panel {\n position: absolute;\n top: 0; left: 0; right: 0; bottom: 0;\n width: 100%;\n max-width: none;\n height: 100%;\n max-height: none;\n border-radius: 0;\n box-shadow: none;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n * { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }\n}\n";
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Pure transition logic for iOS/mobile body scroll lock when the chat panel
3
+ * opens fullscreen. Extracted for unit tests.
4
+ */
5
+ export interface MobileBodyScrollTransition {
6
+ nextWasOpen: boolean;
7
+ shouldLock: boolean;
8
+ shouldUnlock: boolean;
9
+ }
10
+ /**
11
+ * Given the previous and next panel open state, decide whether to lock or
12
+ * unlock the host document scroll.
13
+ */
14
+ export declare function mobileBodyScrollTransition(wasOpen: boolean, open: boolean, isMobileViewport: boolean): MobileBodyScrollTransition;
@@ -1,7 +1,12 @@
1
1
  /**
2
2
  * Chat Widget Registry - Extensible widget system for AI-triggered UI.
3
- * Tool calls from the AI are mapped to registered widgets; each widget provides render() and onAction().
3
+ *
4
+ * Tool calls from the AI are mapped to registered widgets; each widget
5
+ * provides render() and onAction(). As of the Preact rewrite, `render`
6
+ * returns a Preact VNode (JSX) instead of an HTML string — the WidgetSlot
7
+ * component mounts the returned VNode inside the message bubble.
4
8
  */
9
+ import type { VNode } from "preact";
5
10
  export interface WidgetContext {
6
11
  channelId: string;
7
12
  distinctId: string;
@@ -13,12 +18,17 @@ export interface WidgetContext {
13
18
  messageId: string;
14
19
  /** Build a full URL for a given endpoint path, respecting api_host */
15
20
  buildEndpointUrl(path: string): string;
21
+ /**
22
+ * Dispatch an action for this widget. Provided by the WidgetSlot — calls
23
+ * `definition.onAction(action, data, context)` and, on `{ success: true }`,
24
+ * marks the widget as submitted in message metadata so the next render
25
+ * shows the confirmation UI and (where applicable) triggers an AI follow-up.
26
+ */
27
+ dispatch(action: string, data: Record<string, unknown>): Promise<void>;
16
28
  }
17
29
  export interface WidgetActionResult {
18
30
  /** Whether the action succeeded — drives metadata-based re-render */
19
31
  success?: boolean;
20
- /** @deprecated Use metadata-driven rendering instead. Kept for custom widget compat. */
21
- replaceHTML?: string;
22
32
  }
23
33
  export interface ChatWidgetDefinition {
24
34
  /** Unique type identifier, e.g. "collect_email", "schedule_meeting" */
@@ -30,8 +40,14 @@ export interface ChatWidgetDefinition {
30
40
  type: string;
31
41
  description: string;
32
42
  }>;
33
- /** Returns an HTML string to render inline in the chat bubble */
34
- render(params: Record<string, unknown>, context: WidgetContext): string;
43
+ /**
44
+ * Returns a Preact VNode rendered inline inside the chat bubble.
45
+ *
46
+ * The component should call `ctx.dispatch(action, data)` to submit —
47
+ * WidgetSlot wires that into `onAction` + the controller's submission
48
+ * pipeline so widgets only need to render their form.
49
+ */
50
+ render(params: Record<string, unknown>, context: WidgetContext): VNode;
35
51
  /** Called when the widget form is submitted or button clicked */
36
52
  onAction(action: string, data: Record<string, unknown>, context: WidgetContext): Promise<WidgetActionResult>;
37
53
  }
@@ -1,6 +1,8 @@
1
1
  /**
2
- * Built-in collect_email widget: renders name + email inputs, POSTs to widget actions API.
3
- * UI state is metadata-driven submitted state is rendered by _renderWidgets().
2
+ * Built-in collect_email widget: renders name + email inputs, POSTs to
3
+ * widget actions API on submit. Submission UI is metadata-driven
4
+ * WidgetSlot renders the confirmation once the controller flips
5
+ * `submitted: true` in the message's metadata.
4
6
  */
5
7
  import type { ChatWidgetDefinition } from "../widget-registry";
6
8
  export declare const collectEmailWidget: ChatWidgetDefinition;
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Built-in escalate_to_human widget: shows a "Connect with a human" button.
3
- * UI state is metadata-driven — submitted state is rendered by _renderWidgets().
3
+ * UI state is metadata-driven — WidgetSlot renders the confirmation once
4
+ * the controller flips `submitted: true` in the message's metadata.
4
5
  */
5
6
  import type { ChatWidgetDefinition } from "../widget-registry";
6
7
  export declare const escalateToHumanWidget: ChatWidgetDefinition;
@@ -1,2 +1,2 @@
1
- !function(r){"use strict";var n="undefined"!=typeof window?window:void 0,e="undefined"!=typeof globalThis?globalThis:n,o=null==e?void 0:e.navigator,i=null==e?void 0:e.document;null==e||e.location,null==e||e.fetch,n&&i&&i.createElement,null==e||e.XMLHttpRequest,null==e||e.AbortController,null==o||o.userAgent;var t=null!=n?n:{},l={none:0,error:1,warn:2,info:3,debug:4},a="[vTilt]",u=new Set(["none","error","warn","info","debug"]);function d(){return"undefined"!=typeof console}function s(r){var n=r.length>0&&"string"==typeof r[0]?r[0]:"",e=n?r.slice(1):r;return[n?a+":"+n:a,...e]}var v="warn",f=!1;var c=null;function w(){return c||(c={setLevel(r,n){void 0===n&&(n=!1),v=r,n&&(f=!0)},setLevelFromRemote(r){f||r&&u.has(r)&&(v=r)},getLevel:()=>v,isLockedByInit:()=>f,debug(){if(!(l[v]<l.debug)&&d()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.log(...s(n))}},info(){if(!(l[v]<l.info)&&d()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.log(...s(n))}},warn(){if(!(l[v]<l.warn)&&d()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.warn(...s(n))}},error(){if(!(l[v]<l.error)&&d()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.error(...s(n))}}}),c}var p="external-scripts",g=(r,n,e)=>{if(r.getConfig().disable_external_dependency_loading)return w().warn(p,n+" was requested but loading of external scripts is disabled."),e("Loading of external scripts is disabled");var o=null==i?void 0:i.querySelectorAll("script");if(o)for(var t,l=function(){if(o[a].src===n){var r=o[a];return r.__vtilt_loading_callback_fired?{v:e()}:(r.addEventListener("load",n=>{r.__vtilt_loading_callback_fired=!0,e(void 0,n)}),r.onerror=r=>e(r),{v:void 0})}},a=0;a<o.length;a++)if(t=l())return t.v;var u=()=>{var r;if(!i)return e("document not found");var o=i.createElement("script");o.type="text/javascript",o.crossOrigin="anonymous",o.src=n,o.onload=r=>{o.__vtilt_loading_callback_fired=!0,e(void 0,r)},o.onerror=r=>e(r);var t=i.querySelectorAll("body > script");t.length>0?null===(r=t[0].parentNode)||void 0===r||r.insertBefore(o,t[0]):i.body.appendChild(o)};(null==i?void 0:i.body)?u():null==i||i.addEventListener("DOMContentLoaded",u)},y=(r,n)=>{var e=r.getConfig();return e.script_host?e.script_host.replace(/\/+$/gm,"")+"/"+n+".js":e.api_host?e.api_host.replace(/\/+$/gm,"")+"/dist/"+n+".js":(w().error(p,"cannot load "+n+".js: api_host is required"),"")};t.__VTiltExtensions__=t.__VTiltExtensions__||{},t.__VTiltExtensions__.loadExternalDependency=(r,n,e)=>{var o=y(r,n);g(r,o,e)},r.getExtensionUrl=y,r.loadScript=g}({});
1
+ !function(r){"use strict";var n="undefined"!=typeof window?window:void 0,e="undefined"!=typeof globalThis?globalThis:n,o=null==e?void 0:e.navigator,i=null==e?void 0:e.document;null==e||e.location,null==e||e.fetch,n&&i&&i.createElement,null==e||e.XMLHttpRequest,null==e||e.AbortController,null==o||o.userAgent;var t=null!=n?n:{},a={none:0,error:1,warn:2,info:3,debug:4},l="[vTilt]",u=new Set(["none","error","warn","info","debug"]);function s(){return"undefined"!=typeof console}function d(r){var n=r.length>0&&"string"==typeof r[0]?r[0]:"",e=n?r.slice(1):r;return[n?l+":"+n:l,...e]}var v="warn",f=!1;var c=null;function w(){return c||(c={setLevel(r,n){void 0===n&&(n=!1),v=r,n&&(f=!0)},setLevelFromRemote(r){f||r&&u.has(r)&&(v=r)},getLevel:()=>v,isLockedByInit:()=>f,debug(){if(!(a[v]<a.debug)&&s()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.log(...d(n))}},info(){if(!(a[v]<a.info)&&s()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.log(...d(n))}},warn(){if(!(a[v]<a.warn)&&s()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.warn(...d(n))}},error(){if(!(a[v]<a.error)&&s()){for(var r=arguments.length,n=new Array(r),e=0;e<r;e++)n[e]=arguments[e];console.error(...d(n))}}}),c}var p="external-scripts",g=(r,n,e)=>{if(r.getConfig().disable_external_dependency_loading)return w().warn(p,n+" was requested but loading of external scripts is disabled."),e("Loading of external scripts is disabled");var o=null==i?void 0:i.querySelectorAll("script");if(o)for(var t,a=function(){if(o[l].src===n){var r=o[l];return r.__vtilt_loading_callback_fired?{v:e()}:(r.addEventListener("load",n=>{r.__vtilt_loading_callback_fired=!0,e(void 0,n)}),r.onerror=r=>e(r),{v:void 0})}},l=0;l<o.length;l++)if(t=a())return t.v;var u=()=>{var r;if(!i)return e("document not found");var o=i.createElement("script");o.type="text/javascript",o.crossOrigin="anonymous",o.src=n,o.onload=r=>{o.__vtilt_loading_callback_fired=!0,e(void 0,r)},o.onerror=r=>e(r);var t=i.querySelectorAll("body > script");t.length>0?null===(r=t[0].parentNode)||void 0===r||r.insertBefore(o,t[0]):i.body.appendChild(o)};(null==i?void 0:i.body)?u():null==i||i.addEventListener("DOMContentLoaded",u)},y=(r,n)=>{var e=r.getConfig();return e.script_host?e.script_host.replace(/\/+$/gm,"")+"/"+n+".js":e.api_host?e.api_host.replace(/\/+$/gm,"")+"/dist/"+n+".js":(w().error(p,"cannot load "+n+".js: api_host is required"),"")};t.__VTiltExtensions__=t.__VTiltExtensions__||{},t.__VTiltExtensions__.loadExternalDependency=(r,n,e)=>{if(function(r){var n=t.__VTiltExtensions__;if(!n)return!1;switch(r){case"chat":return!!n.initChat;case"recorder":return!!n.initSessionRecording;case"web-vitals":return!!n.webVitalsCallbacks;default:return!1}}(n))e();else{var o=y(r,n);g(r,o,e)}},r.getExtensionUrl=y,r.loadScript=g}({});
2
2
  //# sourceMappingURL=external-scripts-loader.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"external-scripts-loader.js","sources":["../src/utils/globals.ts","../src/utils/logger.ts","../src/entrypoints/external-scripts-loader.ts"],"sourcesContent":[null,null,null],"names":["win","window","undefined","global","globalThis","navigator","document","location","fetch","createElement","XMLHttpRequest","AbortController","userAgent","assignableWindow","LEVEL_PRIORITY","none","error","warn","info","debug","PREFIX","VALID_LEVELS","Set","hasConsole","console","formatArgs","args","scope","length","rest","slice","currentLevel","lockedByInit","singleton","getLogger","setLevel","level","lockFromInit","setLevelFromRemote","has","getLevel","isLockedByInit","_len","arguments","Array","_key","log","_len2","_key2","_len3","_key3","_len4","_key4","LOG_SCOPE","loadScript","vtilt","url","callback","getConfig","disable_external_dependency_loading","existingScripts","querySelectorAll","_ret","_loop","i","src","alreadyExistingScriptTag","__vtilt_loading_callback_fired","v","addEventListener","event","onerror","addScript","scriptTag","type","crossOrigin","onload","scripts","_a","parentNode","insertBefore","body","appendChild","getExtensionUrl","kind","config","script_host","replace","api_host","__VTiltExtensions__","loadExternalDependency"],"mappings":"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"}
1
+ {"version":3,"file":"external-scripts-loader.js","sources":["../src/utils/globals.ts","../src/utils/logger.ts","../src/entrypoints/external-scripts-loader.ts"],"sourcesContent":[null,null,null],"names":["win","window","undefined","global","globalThis","navigator","document","location","fetch","createElement","XMLHttpRequest","AbortController","userAgent","assignableWindow","LEVEL_PRIORITY","none","error","warn","info","debug","PREFIX","VALID_LEVELS","Set","hasConsole","console","formatArgs","args","scope","length","rest","slice","currentLevel","lockedByInit","singleton","getLogger","setLevel","level","lockFromInit","setLevelFromRemote","has","getLevel","isLockedByInit","_len","arguments","Array","_key","log","_len2","_key2","_len3","_key3","_len4","_key4","LOG_SCOPE","loadScript","vtilt","url","callback","getConfig","disable_external_dependency_loading","existingScripts","querySelectorAll","_ret","_loop","i","src","alreadyExistingScriptTag","__vtilt_loading_callback_fired","v","addEventListener","event","onerror","addScript","scriptTag","type","crossOrigin","onload","scripts","_a","parentNode","insertBefore","body","appendChild","getExtensionUrl","kind","config","script_host","replace","api_host","__VTiltExtensions__","loadExternalDependency","ext","initChat","initSessionRecording","webVitalsCallbacks","isExtensionAlreadyLoaded"],"mappings":"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"}
package/dist/feature.d.ts CHANGED
@@ -75,6 +75,7 @@ export declare abstract class LazyFeature<TConfig extends object, TLoaded> imple
75
75
  protected _loaded: TLoaded | undefined;
76
76
  protected _isStarted: boolean;
77
77
  protected _isLoading: boolean;
78
+ private _loadCallbacks;
78
79
  private _apiReady;
79
80
  private _deferredCalls;
80
81
  abstract readonly name: string;
@@ -0,0 +1,9 @@
1
+ import type { ChatWidgetConfig, VTiltConfig } from "../types";
2
+ /** Deep-merge a partial `chat` patch into the stored VTilt config. */
3
+ export declare function mergeChatWidgetConfig(current: ChatWidgetConfig | undefined, patch: ChatWidgetConfig): ChatWidgetConfig;
4
+ /**
5
+ * Apply a partial VTilt config patch with deep merge for nested `chat`
6
+ * (so `updateConfig({ chat: { bubble: { offset } } })` does not wipe other
7
+ * chat keys from init).
8
+ */
9
+ export declare function mergeVtiltConfigPatch(current: VTiltConfig, patch: Partial<VTiltConfig>): Partial<VTiltConfig>;