@sonordev/site-kit 7.1.2 → 7.2.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.
Files changed (167) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +51 -0
  3. package/README.md +9 -8
  4. package/agent-manifest.json +12 -4
  5. package/dist/{AnalyticsProvider-ZB33W6MC.js → AnalyticsProvider-SLFFBRPE.js} +4 -4
  6. package/dist/{ArticleViewTracker-GMHYLLLZ.js → ArticleViewTracker-TM5AMGN2.js} +3 -3
  7. package/dist/{BlocksPopup-EA6LYL25.js → BlocksPopup-YJNJIVPO.js} +5 -5
  8. package/dist/ChatWidget-XKOSH66S.js +17 -0
  9. package/dist/{FileField-QKA3JB2Q.js → FileField-CI4UL5SH.js} +3 -3
  10. package/dist/{FormSpotlight-AQTIA5TP.js → FormSpotlight-F2AAEGBN.js} +20 -6
  11. package/dist/{FormStage-FDTLTP3B.js → FormStage-BI5R5CNC.js} +27 -7
  12. package/dist/ManagedForm-G6COGHXL.js +16 -0
  13. package/dist/{ManagedNewsletterForm-J6QSAGGP.js → ManagedNewsletterForm-BO3WCGXX.js} +7 -5
  14. package/dist/{SignalCore-JZNXOLNT.js → SignalCore-ZA6WHKBR.js} +3 -3
  15. package/dist/SiteChat-YJADDTKG.js +5 -0
  16. package/dist/{SiteDesignReporter-W4EJUSTE.js → SiteDesignReporter-5YAW36AQ.js} +5 -5
  17. package/dist/SitePopups-ST5PMKZE.js +10 -0
  18. package/dist/SitemapSync-RJTEBINX.js +8 -0
  19. package/dist/_client/booking-widget.js +5 -5
  20. package/dist/affiliates/index.js +3 -3
  21. package/dist/analytics/index.js +4 -4
  22. package/dist/analytics/send-gate.d.ts +1 -1
  23. package/dist/articles/index.js +2 -2
  24. package/dist/articles/server-ui.js +3 -2
  25. package/dist/articles/server.js +2 -1
  26. package/dist/{engage → chat}/ChatWidget.d.ts +7 -6
  27. package/dist/{engage → chat}/EchoUiActions.d.ts +1 -1
  28. package/dist/chat/SiteChat.d.ts +25 -0
  29. package/dist/{engage → chat}/brand-color.d.ts +1 -1
  30. package/dist/{engage → chat}/chat-messages.d.ts +1 -1
  31. package/dist/{engage → chat}/echo-config.d.ts +1 -1
  32. package/dist/chat/index.d.ts +10 -6
  33. package/dist/chat/index.js +14 -8
  34. package/dist/{engage → chat}/launcher-placement.d.ts +1 -7
  35. package/dist/{engage → chat}/socket-loader.d.ts +2 -2
  36. package/dist/chat/types.d.ts +136 -0
  37. package/dist/chunk-27FISYK4.js +4 -0
  38. package/dist/{chunk-LUXVWITO.js → chunk-4NTBQNHA.js} +247 -78
  39. package/dist/{chunk-V3PUVBME.js → chunk-5V2V5C3Y.js} +2 -4
  40. package/dist/{chunk-Q4W23JYM.js → chunk-5YCEMV2L.js} +1 -1
  41. package/dist/{chunk-RHNWA34F.js → chunk-64DKGVEI.js} +41 -7
  42. package/dist/{chunk-GLHQ3LRO.js → chunk-6U3VLV2C.js} +1 -1
  43. package/dist/{chunk-WWSHFRDL.js → chunk-BC4Y3S3D.js} +1 -1
  44. package/dist/{chunk-JSZN6LDZ.js → chunk-BF7TZYC3.js} +38 -31
  45. package/dist/{chunk-M3YEPKP7.js → chunk-BKY2ZNM7.js} +1 -1
  46. package/dist/{chunk-AQSNPZY4.js → chunk-C2FZBUSS.js} +80 -1
  47. package/dist/{chunk-NK4CSJNP.js → chunk-CF5QNFYU.js} +2 -2
  48. package/dist/{chunk-D5EC5KVO.js → chunk-D5DCBD4Y.js} +2 -2
  49. package/dist/{chunk-3SDOQUUF.js → chunk-D6ZC7XKZ.js} +2 -2
  50. package/dist/chunk-EJVQJCG5.js +370 -0
  51. package/dist/{chunk-AH5Q262S.js → chunk-F24FNCTK.js} +1 -1
  52. package/dist/{chunk-V6FDQLR7.js → chunk-FR6BYFPC.js} +53 -21
  53. package/dist/{chunk-UXOGCKL6.js → chunk-GEXT7BTE.js} +1 -1
  54. package/dist/{chunk-JTU2GOM4.js → chunk-GGORNBSC.js} +18 -6
  55. package/dist/{chunk-ERXXITG4.js → chunk-HGFAEDG3.js} +1 -1
  56. package/dist/chunk-HODO5BX5.js +28 -0
  57. package/dist/chunk-HW7B43E5.js +14 -0
  58. package/dist/{chunk-CSTQDNGK.js → chunk-IAM3D3RL.js} +43 -24
  59. package/dist/{chunk-PGMVL4AB.js → chunk-JJ3DELUL.js} +2 -2
  60. package/dist/{chunk-ILU5I6RW.js → chunk-JMLK6MH7.js} +1 -1
  61. package/dist/{chunk-YEMPUQVU.js → chunk-JWTQS5F5.js} +2 -2
  62. package/dist/{chunk-VDF5DFWT.js → chunk-K4ZHAURD.js} +1 -1
  63. package/dist/chunk-NV72HZ4W.js +45 -0
  64. package/dist/{chunk-4Y5FWDPM.js → chunk-NZ4RXN3G.js} +55 -7
  65. package/dist/chunk-OWZ7ATLZ.js +187 -0
  66. package/dist/{chunk-TXBAOEKG.js → chunk-PKGN32AU.js} +5 -4
  67. package/dist/chunk-Q6E4OPGG.js +40 -0
  68. package/dist/{chunk-QEDHRBAT.js → chunk-QL6XNPSD.js} +2 -2
  69. package/dist/{chunk-JBE46QJZ.js → chunk-R46IQIGK.js} +1 -1
  70. package/dist/{chunk-6WIQCXOO.js → chunk-RPZ7OQGD.js} +1 -1
  71. package/dist/{chunk-PHYMNUJU.js → chunk-S54ECQGY.js} +1 -1
  72. package/dist/{chunk-VGH4MDTU.js → chunk-VHKEF5ZB.js} +1 -1
  73. package/dist/{chunk-HF57LG73.js → chunk-VO3JYRT5.js} +43 -29
  74. package/dist/chunk-VWWITFWM.js +300 -0
  75. package/dist/{chunk-CIG5UL2S.js → chunk-ZTLMPUO6.js} +3 -2
  76. package/dist/client/index.js +3 -3
  77. package/dist/commerce/index.js +4 -4
  78. package/dist/contracts/color.d.ts +1 -1
  79. package/dist/contracts/entries.d.ts +1 -1
  80. package/dist/contracts/error-page.d.ts +183 -0
  81. package/dist/contracts/llms.d.ts +1 -0
  82. package/dist/contracts/proposal-sitemap.d.ts +130 -0
  83. package/dist/contracts/schema-placeholders.d.ts +87 -0
  84. package/dist/engage/EngageWidget.d.ts +13 -12
  85. package/dist/engage/index.d.ts +10 -9
  86. package/dist/engage/index.js +31 -35
  87. package/dist/engage/types.d.ts +19 -244
  88. package/dist/fleet/FleetHeartbeat.d.ts +1 -1
  89. package/dist/fleet/index.js +4 -4
  90. package/dist/forms/FormEnhancer.d.ts +25 -12
  91. package/dist/forms/ServerForm.d.ts +11 -10
  92. package/dist/forms/StaticForm.d.ts +3 -1
  93. package/dist/forms/field-autocomplete.d.ts +28 -0
  94. package/dist/forms/form-dom-values.d.ts +42 -0
  95. package/dist/forms/index.js +11 -9
  96. package/dist/forms/server.js +6 -4
  97. package/dist/forms/static.js +3 -2
  98. package/dist/forms/submitForm.d.ts +8 -1
  99. package/dist/forms/types.d.ts +41 -2
  100. package/dist/forms/useForm.d.ts +21 -4
  101. package/dist/forms/webmcp.d.ts +38 -0
  102. package/dist/images/index.js +4 -4
  103. package/dist/index.d.ts +4 -1
  104. package/dist/index.js +1 -1
  105. package/dist/layout/SiteKitClientProviders.d.ts +3 -3
  106. package/dist/layout/SiteKitLayout.d.ts +4 -4
  107. package/dist/layout/client.d.ts +3 -3
  108. package/dist/layout/client.js +7 -7
  109. package/dist/layout/index.js +8 -8
  110. package/dist/layout/types.d.ts +5 -4
  111. package/dist/llms/contract.js +1 -1
  112. package/dist/llms/index.js +6 -6
  113. package/dist/maps/index.js +3 -3
  114. package/dist/mcp/index.js +2 -14
  115. package/dist/mcp/sonor.js +14 -11
  116. package/dist/revalidate/index.js +2 -2
  117. package/dist/seo/client.js +4 -4
  118. package/dist/seo/index.js +15 -8
  119. package/dist/seo/llms/contract.js +1 -1
  120. package/dist/seo/llms.js +6 -6
  121. package/dist/seo/register-sitemap-cli.js +1 -1
  122. package/dist/seo/sitemap.js +4 -4
  123. package/dist/server/index.js +2 -2
  124. package/dist/shared/dialog.d.ts +27 -0
  125. package/dist/shared/identity.d.ts +1 -1
  126. package/dist/shared/layers.d.ts +7 -0
  127. package/dist/shared/mid-form.d.ts +53 -0
  128. package/dist/shared/reporting-gate.d.ts +1 -1
  129. package/dist/shared/version.d.ts +1 -1
  130. package/dist/shared/visual-viewport-gap.d.ts +1 -1
  131. package/dist/signal/index.js +2 -2
  132. package/dist/signal/types.d.ts +1 -1
  133. package/dist/sitemap/index.js +4 -4
  134. package/dist/{socket-loader-R24ZSRSQ.js → socket-loader-CGIPEG74.js} +1 -1
  135. package/dist/sync/index.js +5 -5
  136. package/dist/types.d.ts +3 -1
  137. package/dist/{web-vitals.attribution-GD6LGLVF.js → web-vitals.attribution-N2PCCF4Q.js} +1 -1
  138. package/dist/website/BlocksPopup.d.ts +1 -1
  139. package/dist/website/PopupBlocks.d.ts +5 -2
  140. package/dist/website/SitePopups.d.ts +25 -0
  141. package/dist/website/images.js +4 -4
  142. package/dist/website/index.js +6 -6
  143. package/dist/website/popup-rules.d.ts +46 -0
  144. package/dist/website/popup-types.d.ts +113 -0
  145. package/dist/website/popups.d.ts +2 -6
  146. package/dist/website/popups.js +6 -14
  147. package/dist/{writeLLMsTxt-G3JBNBC5.js → writeLLMsTxt-OV24LQVL.js} +3 -3
  148. package/docs/MIGRATING-TO-7.md +41 -9
  149. package/docs.json +2 -1
  150. package/package.json +2 -2
  151. package/src/analytics/README.md +2 -2
  152. package/src/articles/README.md +4 -1
  153. package/src/{engage → chat}/README.md +63 -62
  154. package/src/forms/README.md +49 -3
  155. package/src/layout/README.md +8 -5
  156. package/src/seo/README.md +3 -1
  157. package/src/sync/README.md +10 -0
  158. package/src/website/README.md +133 -0
  159. package/dist/ChatWidget-UCDWMWCW.js +0 -15
  160. package/dist/EngageWidget-7ZYSK4GE.js +0 -11
  161. package/dist/ManagedForm-H7BVAH2W.js +0 -14
  162. package/dist/SitemapSync-XBANGKHZ.js +0 -8
  163. package/dist/chunk-NDF4A5JM.js +0 -37
  164. package/dist/chunk-VLARKWZU.js +0 -100
  165. package/dist/chunk-YCJT4JJG.js +0 -837
  166. package/dist/engage/DesignRenderer.d.ts +0 -57
  167. package/dist/engage/element-rules.d.ts +0 -38
@@ -1,837 +0,0 @@
1
- 'use client';
2
- import { DEFAULT_ENGAGE_Z_INDEX } from './chunk-NDF4A5JM.js';
3
- import { getOrCreateVisitorId } from './chunk-24QZEO3Q.js';
4
- import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
5
- import { sonorFetch } from './chunk-3SDOQUUF.js';
6
- import React, { useState, useEffect, useCallback, useRef, createElement } from 'react';
7
- import ReactDOM from 'react-dom';
8
- import dynamic from 'next/dynamic';
9
- import { usePathname } from 'next/navigation';
10
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
11
-
12
- // src/engage/element-rules.ts
13
- function hasControlOrSpace(value) {
14
- for (let i = 0; i < value.length; i++) {
15
- const code = value.charCodeAt(i);
16
- if (code <= 32 || code >= 127 && code <= 159) return true;
17
- }
18
- return false;
19
- }
20
- function isSitePath(value) {
21
- return value.startsWith("/") && !value.startsWith("//") && !value.startsWith("/\\");
22
- }
23
- function safeActionUrl(raw) {
24
- if (typeof raw !== "string") return void 0;
25
- const value = raw.trim();
26
- if (!value || hasControlOrSpace(value)) return void 0;
27
- if (value.startsWith("#") || isSitePath(value)) return value;
28
- const scheme = /^([a-z][a-z0-9+.-]*):/i.exec(value)?.[1]?.toLowerCase();
29
- return scheme && ["http", "https", "mailto", "tel"].includes(scheme) ? value : void 0;
30
- }
31
- function safeImageSrc(raw) {
32
- if (typeof raw !== "string") return void 0;
33
- const value = raw.trim();
34
- if (!value || hasControlOrSpace(value)) return void 0;
35
- if (isSitePath(value)) return value;
36
- return /^https?:\/\//i.test(value) ? value : void 0;
37
- }
38
- function matchPath(pathname, pattern) {
39
- if (pattern.endsWith("*")) return pathname.startsWith(pattern.slice(0, -1));
40
- return pathname === pattern;
41
- }
42
- function deviceFromUserAgent(ua) {
43
- if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
44
- if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return "mobile";
45
- return "desktop";
46
- }
47
- function seenKey(elementId) {
48
- return `_engage_${elementId}`;
49
- }
50
- function isEligible(element, pathname, device, store, now = Date.now()) {
51
- const pages = element.targeting?.pages;
52
- if (pages) {
53
- if (pages.exclude?.some((p) => matchPath(pathname, p))) return false;
54
- if (pages.include && !pages.include.some((p) => matchPath(pathname, p))) return false;
55
- }
56
- if (element.targeting?.devices && !element.targeting.devices.includes(device)) return false;
57
- const frequency = element.trigger?.frequency;
58
- if (frequency) {
59
- const key = seenKey(element.id);
60
- if (frequency.type === "once") {
61
- if (store.local?.getItem(key)) return false;
62
- } else if (frequency.type === "once-per-session") {
63
- if (store.session?.getItem(key)) return false;
64
- } else if (frequency.type === "every-n-days" && frequency.days) {
65
- const last = store.local?.getItem(key);
66
- if (last && now - parseInt(last, 10) < frequency.days * 864e5) return false;
67
- }
68
- }
69
- return true;
70
- }
71
- function activateElement(active, id, elements) {
72
- if (active.includes(id)) return active;
73
- const typeOf = (elementId) => elements.find((e) => e.id === elementId)?.type;
74
- if (typeOf(id) === "popup" && active.some((other) => typeOf(other) === "popup")) return active;
75
- return [...active, id];
76
- }
77
- function handleAction(action, node, onClose, onAction) {
78
- if (!action?.action || action.action === "none") return;
79
- if (onAction) {
80
- onAction(action, node);
81
- }
82
- switch (action.action) {
83
- case "link": {
84
- const url = safeActionUrl(action.url);
85
- if (url) {
86
- if (action.newTab) {
87
- window.open(url, "_blank", "noopener,noreferrer");
88
- } else {
89
- window.location.href = url;
90
- }
91
- }
92
- break;
93
- }
94
- case "scroll":
95
- if (action.target) {
96
- const element = document.querySelector(action.target);
97
- element?.scrollIntoView({ behavior: "smooth" });
98
- }
99
- break;
100
- case "close":
101
- onClose?.();
102
- break;
103
- case "copy":
104
- if (action.text) {
105
- navigator.clipboard.writeText(action.text).catch(console.error);
106
- }
107
- break;
108
- case "share":
109
- if (navigator.share) {
110
- navigator.share({
111
- title: document.title,
112
- url: window.location.href
113
- }).catch(console.error);
114
- }
115
- break;
116
- case "download": {
117
- const url = safeActionUrl(action.url);
118
- if (url) {
119
- const a = document.createElement("a");
120
- a.href = url;
121
- a.download = "";
122
- a.click();
123
- }
124
- break;
125
- }
126
- }
127
- }
128
- var animationKeyframes = `
129
- @keyframes engageFadeIn { from { opacity: 0; } to { opacity: 1; } }
130
- @keyframes engageFadeInUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
131
- @keyframes engageFadeInDown { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } }
132
- @keyframes engageSlideInLeft { from { opacity: 0; transform: translateX(-20px); } to { opacity: 1; transform: translateX(0); } }
133
- @keyframes engageSlideInRight { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
134
- @keyframes engageSlideInUp { from { opacity: 0; transform: translateY(100%); } to { opacity: 1; transform: translateY(0); } }
135
- @keyframes engageScaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
136
- @keyframes engageBounceIn { 0% { opacity: 0; transform: scale(0.3); } 50% { transform: scale(1.05); } 70% { transform: scale(0.9); } 100% { opacity: 1; transform: scale(1); } }
137
- `;
138
- function getAnimationStyle(animation, delay, duration) {
139
- if (!animation || animation === "none") return {};
140
- const animationMap = {
141
- fadeIn: "engageFadeIn",
142
- fadeInUp: "engageFadeInUp",
143
- fadeInDown: "engageFadeInDown",
144
- slideInLeft: "engageSlideInLeft",
145
- slideInRight: "engageSlideInRight",
146
- slideInUp: "engageSlideInUp",
147
- scaleIn: "engageScaleIn",
148
- bounceIn: "engageBounceIn"
149
- };
150
- const animationName = animationMap[animation];
151
- if (!animationName) return {};
152
- return {
153
- animation: `${animationName} ${duration || 300}ms ease-out ${delay || 0}ms forwards`,
154
- opacity: 0
155
- // Start invisible, animation will show it
156
- };
157
- }
158
- function NodeRenderer({ node, onClose, onAction, context }) {
159
- const [isHovered, setIsHovered] = useState(false);
160
- const { type, props = {}, style = {}, children } = node;
161
- const computedStyle = { ...style };
162
- if (props.animation) {
163
- Object.assign(computedStyle, getAnimationStyle(
164
- props.animation,
165
- props.animationDelay,
166
- props.animationDuration
167
- ));
168
- }
169
- if (isHovered) {
170
- if (props.hoverScale && props.hoverScale !== "none") {
171
- computedStyle.transform = `scale(${props.hoverScale})`;
172
- computedStyle.transition = "transform 0.2s ease";
173
- }
174
- if (props.hoverShadow && props.hoverShadow !== "none") {
175
- const shadowMap = {
176
- sm: "0 1px 2px 0 rgb(0 0 0 / 0.05)",
177
- md: "0 4px 6px -1px rgb(0 0 0 / 0.1)",
178
- lg: "0 10px 15px -3px rgb(0 0 0 / 0.1)"
179
- };
180
- computedStyle.boxShadow = shadowMap[props.hoverShadow];
181
- }
182
- }
183
- const eventHandlers = {
184
- onMouseEnter: () => setIsHovered(true),
185
- onMouseLeave: () => setIsHovered(false)
186
- };
187
- if (props.onClick) {
188
- eventHandlers.onClick = (e) => {
189
- e.preventDefault();
190
- handleAction(props.onClick, node, onClose, onAction);
191
- };
192
- computedStyle.cursor = "pointer";
193
- }
194
- const renderedChildren = children?.map((child) => /* @__PURE__ */ jsx(
195
- NodeRenderer,
196
- {
197
- node: child,
198
- onClose,
199
- onAction,
200
- context
201
- },
202
- child.id
203
- ));
204
- switch (type) {
205
- case "Container":
206
- case "Box":
207
- case "Section":
208
- return /* @__PURE__ */ jsx("div", { style: computedStyle, ...eventHandlers, children: renderedChildren });
209
- case "Text":
210
- return /* @__PURE__ */ jsx("p", { style: computedStyle, ...eventHandlers, children: resolveText(props.text, context) });
211
- case "Heading":
212
- const HeadingTag = `h${props.level || 2}`;
213
- return createElement(
214
- HeadingTag,
215
- { style: computedStyle, ...eventHandlers },
216
- resolveText(props.text, context)
217
- );
218
- case "Button":
219
- case "BookingButton":
220
- case "BuyNow":
221
- case "AddToCart":
222
- case "EventRSVP":
223
- return /* @__PURE__ */ jsx(
224
- "button",
225
- {
226
- style: computedStyle,
227
- ...eventHandlers,
228
- type: "button",
229
- "aria-label": typeof props.ariaLabel === "string" ? props.ariaLabel : void 0,
230
- children: resolveText(props.text || props.label, context) || "Button"
231
- }
232
- );
233
- case "Image":
234
- return /* @__PURE__ */ jsx(
235
- "img",
236
- {
237
- src: safeImageSrc(props.src),
238
- alt: props.alt || "",
239
- style: computedStyle,
240
- ...eventHandlers
241
- }
242
- );
243
- case "Link":
244
- return /* @__PURE__ */ jsx(
245
- "a",
246
- {
247
- href: safeActionUrl(props.href) ?? "#",
248
- target: props.newTab ? "_blank" : void 0,
249
- rel: props.newTab ? "noopener noreferrer" : void 0,
250
- style: computedStyle,
251
- ...eventHandlers,
252
- children: resolveText(props.text, context) || renderedChildren
253
- }
254
- );
255
- case "Input":
256
- return /* @__PURE__ */ jsx(
257
- "input",
258
- {
259
- type: props.inputType || "text",
260
- placeholder: props.placeholder,
261
- style: computedStyle,
262
- ...eventHandlers
263
- }
264
- );
265
- case "Divider":
266
- return /* @__PURE__ */ jsx("hr", { style: { border: "none", borderTop: "1px solid var(--sk-border, #e5e7eb)", ...computedStyle } });
267
- case "Spacer":
268
- return /* @__PURE__ */ jsx("div", { style: { ...computedStyle, width: props.width, height: props.height } });
269
- case "Icon":
270
- return /* @__PURE__ */ jsx("span", { style: computedStyle, ...eventHandlers, children: props.icon || "\u2605" });
271
- case "FormEmbed":
272
- return /* @__PURE__ */ jsx(
273
- "div",
274
- {
275
- style: computedStyle,
276
- "data-engage-form": props.form_id,
277
- ...eventHandlers,
278
- children: /* @__PURE__ */ jsxs("div", { style: { padding: "16px", background: "var(--sk-bg-elevated, #f5f5f5)", borderRadius: "8px", textAlign: "center" }, children: [
279
- "Form: ",
280
- props.form_id || "Not configured"
281
- ] })
282
- }
283
- );
284
- case "ProductCard":
285
- case "EventCard":
286
- return /* @__PURE__ */ jsx(
287
- "div",
288
- {
289
- style: computedStyle,
290
- "data-engage-offering": props.offering_id,
291
- ...eventHandlers,
292
- children: renderedChildren
293
- }
294
- );
295
- default:
296
- console.warn(`[DesignRenderer] Unknown node type: ${type}`);
297
- return /* @__PURE__ */ jsx("div", { style: computedStyle, ...eventHandlers, children: renderedChildren });
298
- }
299
- }
300
- function resolveText(text, context) {
301
- if (!text) return "";
302
- if (!context) return text;
303
- return text.replace(/\{\{(\w+(?:\.\w+)*)\}\}/g, (match, path) => {
304
- const value = getNestedValue(context, path);
305
- return value !== void 0 ? String(value) : match;
306
- });
307
- }
308
- function getNestedValue(obj, path) {
309
- return path.split(".").reduce((acc, key) => acc?.[key], obj);
310
- }
311
- function DesignRenderer({
312
- design,
313
- onClose,
314
- onAction,
315
- context
316
- }) {
317
- React.useEffect(() => {
318
- if (typeof document === "undefined") return;
319
- const styleId = "engage-design-renderer-animations";
320
- if (!document.getElementById(styleId)) {
321
- const styleSheet = document.createElement("style");
322
- styleSheet.id = styleId;
323
- styleSheet.textContent = animationKeyframes;
324
- document.head.appendChild(styleSheet);
325
- }
326
- }, []);
327
- const rootStyle = {
328
- ...design.style
329
- };
330
- return /* @__PURE__ */ jsx("div", { style: rootStyle, "data-engage-design": design.id, children: design.children?.map((node) => /* @__PURE__ */ jsx(
331
- NodeRenderer,
332
- {
333
- node,
334
- onClose,
335
- onAction,
336
- context
337
- },
338
- node.id
339
- )) });
340
- }
341
- var LazyBlocksPopup = dynamic(
342
- () => import('./BlocksPopup-EA6LYL25.js').then((m) => ({ default: m.BlocksPopup })),
343
- { ssr: false }
344
- );
345
- var LazyChatWidget = dynamic(
346
- () => import('./ChatWidget-UCDWMWCW.js').then((m) => ({ default: m.ChatWidget })),
347
- { ssr: false }
348
- );
349
- function EngageWidget(props) {
350
- const target = resolveAnalyticsTarget(props);
351
- if (!target) return null;
352
- return /* @__PURE__ */ jsx(AllowedEngageWidget, { ...props, ...target });
353
- }
354
- function AllowedEngageWidget({
355
- apiUrl: propApiUrl,
356
- apiKey: propApiKey,
357
- projectId: propProjectId,
358
- position = "bottom-right",
359
- offsetBottom,
360
- zIndex = DEFAULT_ENGAGE_Z_INDEX,
361
- chatEnabled = true,
362
- popupsEnabled = true,
363
- debug = false
364
- }) {
365
- const pathname = usePathname();
366
- const [elements, setElements] = useState([]);
367
- const [activeElements, setActiveElements] = useState([]);
368
- const [dismissedElements, setDismissedElements] = useState(/* @__PURE__ */ new Set());
369
- const [chatReady, setChatReady] = useState(false);
370
- useEffect(() => {
371
- if (!popupsEnabled) return;
372
- async function loadElements() {
373
- const apiUrl = propApiUrl;
374
- const apiKey = propApiKey;
375
- if (!apiKey) {
376
- if (debug) console.warn("[Engage] No API key configured");
377
- return;
378
- }
379
- const visitorId = getOrCreateVisitorId();
380
- const url = typeof window !== "undefined" ? window.location.href : "";
381
- try {
382
- const response = await sonorFetch(
383
- `${apiUrl.replace(/\/$/, "")}/engage/widget/elements?url=${encodeURIComponent(url)}&visitorId=${encodeURIComponent(visitorId)}&deviceType=${getDeviceType()}`,
384
- {},
385
- { apiKey }
386
- );
387
- if (!response.ok) {
388
- if (debug) console.error("[Engage] Error loading elements:", response.statusText);
389
- return;
390
- }
391
- const json = await response.json();
392
- const elementsList = json.data?.elements ?? json.elements ?? [];
393
- if (debug) console.log("[Engage] Loaded elements:", elementsList);
394
- setElements(elementsList);
395
- } catch (error) {
396
- if (debug) console.error("[Engage] Error loading elements:", error);
397
- }
398
- }
399
- loadElements();
400
- }, [propApiUrl, propApiKey, debug, popupsEnabled]);
401
- useEffect(() => {
402
- if (!chatEnabled) return;
403
- const id = typeof requestIdleCallback !== "undefined" ? requestIdleCallback(() => setChatReady(true), { timeout: 3e3 }) : setTimeout(() => setChatReady(true), 1);
404
- return () => {
405
- typeof cancelIdleCallback !== "undefined" ? cancelIdleCallback(id) : clearTimeout(id);
406
- };
407
- }, [chatEnabled]);
408
- useEffect(() => {
409
- if (!elements.length) return;
410
- const store = { local: storageOrNull("local"), session: storageOrNull("session") };
411
- const device = getDeviceType();
412
- const eligible = elements.filter(
413
- (element) => !dismissedElements.has(element.id) && isEligible(element, pathname, device, store)
414
- );
415
- if (debug) console.log("[Engage] Eligible elements:", eligible);
416
- const eligibleIds = new Set(eligible.map((e) => e.id));
417
- setActiveElements((prev) => {
418
- const kept = prev.filter((id) => eligibleIds.has(id));
419
- return kept.length === prev.length ? prev : kept;
420
- });
421
- const activate = (id) => {
422
- setActiveElements((prev) => activateElement(prev, id, elements));
423
- };
424
- const cleanups = [];
425
- eligible.forEach((element) => {
426
- const trigger = element.trigger;
427
- if (trigger?.type === "immediate" || !trigger?.type) {
428
- activate(element.id);
429
- } else if (trigger?.type === "delay" && trigger.delay) {
430
- const timer = setTimeout(() => activate(element.id), trigger.delay * 1e3);
431
- cleanups.push(() => clearTimeout(timer));
432
- } else if (trigger?.type === "exit-intent") {
433
- const handleMouseLeave = (e) => {
434
- if (e.clientY < 10) {
435
- activate(element.id);
436
- document.removeEventListener("mouseleave", handleMouseLeave);
437
- }
438
- };
439
- document.addEventListener("mouseleave", handleMouseLeave);
440
- cleanups.push(() => document.removeEventListener("mouseleave", handleMouseLeave));
441
- } else if (trigger?.type === "scroll" && trigger.scrollPercentage) {
442
- const handleScroll = () => {
443
- const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight) * 100;
444
- if (scrollPercent >= (trigger.scrollPercentage || 50)) {
445
- activate(element.id);
446
- window.removeEventListener("scroll", handleScroll);
447
- }
448
- };
449
- window.addEventListener("scroll", handleScroll, { passive: true });
450
- cleanups.push(() => window.removeEventListener("scroll", handleScroll));
451
- }
452
- });
453
- return () => {
454
- cleanups.forEach((fn) => fn());
455
- };
456
- }, [elements, pathname, dismissedElements, debug]);
457
- const recordSeen = useCallback((elementId) => {
458
- const element = elements.find((e) => e.id === elementId);
459
- if (!element?.trigger?.frequency) return;
460
- try {
461
- if (element.trigger.frequency.type === "once-per-session") {
462
- sessionStorage.setItem(seenKey(elementId), "true");
463
- } else {
464
- localStorage.setItem(seenKey(elementId), Date.now().toString());
465
- }
466
- } catch {
467
- }
468
- }, [elements]);
469
- const handleDismiss = useCallback((elementId) => {
470
- setDismissedElements((prev) => /* @__PURE__ */ new Set([...prev, elementId]));
471
- setActiveElements((prev) => prev.filter((id) => id !== elementId));
472
- recordSeen(elementId);
473
- }, [recordSeen]);
474
- if (typeof document === "undefined") return null;
475
- return ReactDOM.createPortal(
476
- /* @__PURE__ */ jsxs(Fragment, { children: [
477
- activeElements.map((elementId) => {
478
- const element = elements.find((e) => e.id === elementId);
479
- if (!element) return null;
480
- return /* @__PURE__ */ jsx(
481
- EngageElementRenderer,
482
- {
483
- element,
484
- onDismiss: () => handleDismiss(element.id),
485
- onEngaged: () => recordSeen(element.id),
486
- zIndex,
487
- apiUrl: propApiUrl,
488
- apiKey: propApiKey
489
- },
490
- element.id
491
- );
492
- }),
493
- chatEnabled && chatReady && /* @__PURE__ */ jsx(
494
- LazyChatWidget,
495
- {
496
- projectId: propProjectId ?? void 0,
497
- config: {
498
- position,
499
- offsetBottom,
500
- zIndex,
501
- buttonColor: "#2563eb"
502
- // Default blue-600 (AA on white), overridden by --sk-primary CSS var or API brand_primary
503
- }
504
- }
505
- )
506
- ] }),
507
- document.body
508
- );
509
- }
510
- function trackEngageEvent(apiUrl, apiKey, endpoint, data) {
511
- if (!apiKey) return;
512
- const visitorId = typeof localStorage !== "undefined" ? localStorage.getItem("_sk_vid") : null;
513
- sonorFetch(`${apiUrl.replace(/\/$/, "")}/engage/widget/${endpoint}`, {
514
- method: "POST",
515
- headers: { "Content-Type": "application/json" },
516
- body: JSON.stringify({ ...data, visitorId, pageUrl: window.location.href }),
517
- keepalive: true
518
- }, { apiKey, retries: 0 }).catch(() => {
519
- });
520
- }
521
- function handleEngageAction(action, _node, element, apiUrl, apiKey) {
522
- trackEngageEvent(apiUrl, apiKey, "click", {
523
- elementId: element.id,
524
- action: action.action
525
- });
526
- switch (action.action) {
527
- case "navigate": {
528
- const url = safeActionUrl(action.url);
529
- if (url) window.location.href = url;
530
- break;
531
- }
532
- case "open-url": {
533
- const url = safeActionUrl(action.url);
534
- if (url) window.open(url, "_blank", "noopener,noreferrer");
535
- break;
536
- }
537
- case "submit-form":
538
- case "submit_form":
539
- window.dispatchEvent(new CustomEvent("engage:submit-form", {
540
- detail: { formId: action.formId || action.url, elementId: element.id }
541
- }));
542
- break;
543
- case "track-conversion":
544
- window.dispatchEvent(new CustomEvent("engage:conversion", {
545
- detail: { action, elementId: element.id }
546
- }));
547
- break;
548
- default:
549
- window.dispatchEvent(new CustomEvent("engage:action", {
550
- detail: { action, elementId: element.id }
551
- }));
552
- break;
553
- }
554
- }
555
- function EngageElementRenderer({
556
- element,
557
- onDismiss,
558
- onEngaged,
559
- zIndex,
560
- apiUrl,
561
- apiKey
562
- }) {
563
- const containerRef = useRef(null);
564
- const impressionTrackedRef = useRef(false);
565
- useEffect(() => {
566
- const el = containerRef.current;
567
- if (!el || impressionTrackedRef.current) return;
568
- const observer = new IntersectionObserver(
569
- ([entry]) => {
570
- if (entry.isIntersecting && !impressionTrackedRef.current) {
571
- impressionTrackedRef.current = true;
572
- trackEngageEvent(apiUrl, apiKey, "impression", { elementId: element.id });
573
- observer.disconnect();
574
- }
575
- },
576
- { threshold: 0.5 }
577
- );
578
- observer.observe(el);
579
- return () => observer.disconnect();
580
- }, [apiUrl, apiKey, element.id]);
581
- const onAction = useCallback(
582
- (action, node) => {
583
- if (action.action !== "close") onEngaged();
584
- handleEngageAction(action, node, element, apiUrl, apiKey);
585
- },
586
- [element, apiUrl, apiKey, onEngaged]
587
- );
588
- if (element.blocks?.length) {
589
- return /* @__PURE__ */ jsx(
590
- BlocksElement,
591
- {
592
- element,
593
- onDismiss,
594
- onEngaged,
595
- zIndex,
596
- apiUrl,
597
- apiKey,
598
- rootRef: containerRef
599
- }
600
- );
601
- }
602
- if (element.design_json) {
603
- if (element.type === "popup") {
604
- return /* @__PURE__ */ jsx(
605
- "div",
606
- {
607
- ref: containerRef,
608
- "data-engage-element": element.id,
609
- style: {
610
- position: "fixed",
611
- top: 0,
612
- left: 0,
613
- right: 0,
614
- bottom: 0,
615
- backgroundColor: "rgba(0,0,0,0.5)",
616
- display: "flex",
617
- alignItems: "center",
618
- justifyContent: "center",
619
- zIndex
620
- },
621
- onClick: onDismiss,
622
- children: /* @__PURE__ */ jsx("div", { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
623
- DesignRenderer,
624
- {
625
- design: element.design_json,
626
- onClose: onDismiss,
627
- onAction
628
- }
629
- ) })
630
- }
631
- );
632
- }
633
- if (element.type === "bar") {
634
- return /* @__PURE__ */ jsx(
635
- "div",
636
- {
637
- ref: containerRef,
638
- "data-engage-element": element.id,
639
- style: {
640
- position: "fixed",
641
- top: 0,
642
- left: 0,
643
- right: 0,
644
- zIndex
645
- },
646
- children: /* @__PURE__ */ jsx(
647
- DesignRenderer,
648
- {
649
- design: element.design_json,
650
- onClose: onDismiss,
651
- onAction
652
- }
653
- )
654
- }
655
- );
656
- }
657
- if (element.type === "nudge" || element.type === "slide-in") {
658
- const position = element.config?.position || "bottom-right";
659
- const positionStyles = {
660
- "bottom-right": { bottom: 20, right: 20 },
661
- "bottom-left": { bottom: 20, left: 20 },
662
- "top-right": { top: 20, right: 20 },
663
- "top-left": { top: 20, left: 20 }
664
- };
665
- return /* @__PURE__ */ jsx(
666
- "div",
667
- {
668
- ref: containerRef,
669
- "data-engage-element": element.id,
670
- style: {
671
- position: "fixed",
672
- zIndex,
673
- ...positionStyles[position]
674
- },
675
- children: /* @__PURE__ */ jsx(
676
- DesignRenderer,
677
- {
678
- design: element.design_json,
679
- onClose: onDismiss,
680
- onAction
681
- }
682
- )
683
- }
684
- );
685
- }
686
- return /* @__PURE__ */ jsx("div", { ref: containerRef, "data-engage-element": element.id, children: /* @__PURE__ */ jsx(
687
- DesignRenderer,
688
- {
689
- design: element.design_json,
690
- onClose: onDismiss,
691
- onAction
692
- }
693
- ) });
694
- }
695
- if (element.type === "popup") {
696
- return /* @__PURE__ */ jsx(
697
- "div",
698
- {
699
- style: {
700
- position: "fixed",
701
- top: 0,
702
- left: 0,
703
- right: 0,
704
- bottom: 0,
705
- backgroundColor: "rgba(0,0,0,0.5)",
706
- display: "flex",
707
- alignItems: "center",
708
- justifyContent: "center",
709
- zIndex
710
- },
711
- onClick: onDismiss,
712
- children: /* @__PURE__ */ jsxs(
713
- "div",
714
- {
715
- style: {
716
- backgroundColor: "var(--sk-bg, #ffffff)",
717
- padding: 24,
718
- borderRadius: 8,
719
- maxWidth: 500,
720
- width: "90%"
721
- },
722
- onClick: (e) => e.stopPropagation(),
723
- children: [
724
- /* @__PURE__ */ jsx(
725
- "button",
726
- {
727
- onClick: onDismiss,
728
- style: {
729
- position: "absolute",
730
- top: 8,
731
- right: 8,
732
- background: "none",
733
- border: "none",
734
- fontSize: 24,
735
- cursor: "pointer"
736
- },
737
- children: "\xD7"
738
- }
739
- ),
740
- element.config?.title && /* @__PURE__ */ jsx("h2", { children: element.config.title }),
741
- element.config?.message && /* @__PURE__ */ jsx("p", { children: element.config.message })
742
- ]
743
- }
744
- )
745
- }
746
- );
747
- }
748
- if (element.type === "bar") {
749
- return /* @__PURE__ */ jsxs(
750
- "div",
751
- {
752
- style: {
753
- position: "fixed",
754
- top: 0,
755
- left: 0,
756
- right: 0,
757
- backgroundColor: element.config?.backgroundColor || "var(--sk-primary, #2563eb)",
758
- color: element.config?.textColor || "var(--sk-bg, #ffffff)",
759
- padding: "12px 24px",
760
- display: "flex",
761
- alignItems: "center",
762
- justifyContent: "center",
763
- gap: 16,
764
- zIndex
765
- },
766
- children: [
767
- /* @__PURE__ */ jsx("span", { children: element.config?.message }),
768
- /* @__PURE__ */ jsx(
769
- "button",
770
- {
771
- onClick: onDismiss,
772
- style: {
773
- background: "none",
774
- border: "none",
775
- color: "inherit",
776
- fontSize: 20,
777
- cursor: "pointer"
778
- },
779
- children: "\xD7"
780
- }
781
- )
782
- ]
783
- }
784
- );
785
- }
786
- return null;
787
- }
788
- var PLACEMENT = {
789
- popup: "popup",
790
- bar: "bar",
791
- nudge: "slide-in",
792
- "slide-in": "slide-in"
793
- };
794
- function BlocksElement({
795
- element,
796
- onDismiss,
797
- onEngaged,
798
- zIndex,
799
- apiUrl,
800
- apiKey,
801
- rootRef
802
- }) {
803
- const layout = element.layout;
804
- const position = layout?.corner ?? element.config?.position;
805
- const corner = position === "bottom-left" || position === "top-left" || position === "top-right" ? position : "bottom-right";
806
- return /* @__PURE__ */ jsx(
807
- LazyBlocksPopup,
808
- {
809
- blocks: element.blocks,
810
- placement: layout?.placement ?? PLACEMENT[element.type] ?? "popup",
811
- edge: layout?.edge,
812
- accent: layout?.accent,
813
- corner,
814
- zIndex,
815
- rootRef,
816
- elementId: element.id,
817
- onClose: onDismiss,
818
- onButton: (url) => {
819
- onEngaged();
820
- trackEngageEvent(apiUrl, apiKey, "click", { elementId: element.id, action: "navigate", url });
821
- }
822
- }
823
- );
824
- }
825
- function getDeviceType() {
826
- if (typeof navigator === "undefined") return "desktop";
827
- return deviceFromUserAgent(navigator.userAgent);
828
- }
829
- function storageOrNull(kind) {
830
- try {
831
- return kind === "local" ? window.localStorage : window.sessionStorage;
832
- } catch {
833
- return null;
834
- }
835
- }
836
-
837
- export { DesignRenderer, EngageWidget };