@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
@@ -89,4 +89,83 @@ function isFieldComplete(field, values, isVisible = (f) => isFieldVisible(f, val
89
89
  return validateField(field, values, isVisible) === null;
90
90
  }
91
91
 
92
- export { isDisplayOnly, isEmptyValue, isFieldComplete, isFieldVisible, validateField };
92
+ // src/forms/form-dom-values.ts
93
+ function isUnreadable(field) {
94
+ return isDisplayOnly(field) || field.field_type === "file";
95
+ }
96
+ function controlsNamed(form, name) {
97
+ const out = [];
98
+ for (const el of Array.from(form.elements)) {
99
+ const control = el;
100
+ if (control.name === name && !control.disabled) out.push(control);
101
+ }
102
+ return out;
103
+ }
104
+ function isInput(el, type) {
105
+ return el.tagName === "INPUT" && el.type === type;
106
+ }
107
+ function isBlank(value) {
108
+ return isEmptyValue(value) || value === false || typeof value === "string" && value.trim() === "";
109
+ }
110
+ function readField(field, controls) {
111
+ switch (field.field_type) {
112
+ case "checkbox": {
113
+ const boxes = controls.filter((c) => isInput(c, "checkbox"));
114
+ if (boxes.length === 0) return void 0;
115
+ const isGroup = boxes.length > 1 || boxes[0].getAttribute("value") !== null;
116
+ return isGroup ? boxes.filter((b) => b.checked).map((b) => b.value) : boxes[0].checked;
117
+ }
118
+ case "radio": {
119
+ const chosen = controls.find((c) => isInput(c, "radio") && c.checked);
120
+ return chosen ? chosen.value : void 0;
121
+ }
122
+ case "rating": {
123
+ const chosen = controls.find((c) => isInput(c, "radio") && c.checked);
124
+ return chosen ? Number(chosen.value) : void 0;
125
+ }
126
+ case "multi-select": {
127
+ const select = controls.find((c) => c.tagName === "SELECT");
128
+ return select ? Array.from(select.selectedOptions, (o) => o.value) : void 0;
129
+ }
130
+ case "slider": {
131
+ const range = controls[0];
132
+ return range && range.value !== "" ? Number(range.value) : void 0;
133
+ }
134
+ default: {
135
+ const control = controls[0];
136
+ return control ? control.value : void 0;
137
+ }
138
+ }
139
+ }
140
+ function readFormControlValues(form, fields, honeypotName) {
141
+ const out = {};
142
+ if (!form || !fields) return out;
143
+ for (const field of fields) {
144
+ if (!field.slug || field.slug === honeypotName || isUnreadable(field)) continue;
145
+ const controls = controlsNamed(form, field.slug);
146
+ if (controls.length === 0) continue;
147
+ const value = readField(field, controls);
148
+ if (!isBlank(value)) out[field.slug] = value;
149
+ }
150
+ return out;
151
+ }
152
+ function shellValuesToCarry(form, fields, honeypotName) {
153
+ const values = readFormControlValues(form, fields, honeypotName);
154
+ for (const field of fields || []) {
155
+ const value = values[field.slug];
156
+ if (value === void 0) continue;
157
+ const fallback = field.default_value ?? "";
158
+ if (!Array.isArray(value) && String(value) === String(fallback)) delete values[field.slug];
159
+ }
160
+ return values;
161
+ }
162
+ function unseenControlValues(form, fields, honeypotName, state) {
163
+ const dom = readFormControlValues(form, fields, honeypotName);
164
+ const unseen = {};
165
+ for (const [slug, value] of Object.entries(dom)) {
166
+ if (isBlank(state[slug])) unseen[slug] = value;
167
+ }
168
+ return unseen;
169
+ }
170
+
171
+ export { isDisplayOnly, isEmptyValue, isFieldComplete, isFieldVisible, shellValuesToCarry, unseenControlValues, validateField };
@@ -2,8 +2,8 @@
2
2
  import { sanitizeFleetVersion, buildFleetHeartbeatPayload } from './chunk-ZTZQPYP6.js';
3
3
  import { useDeferredActivation } from './chunk-YC7ELZS3.js';
4
4
  import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
5
- import { sonorFetch } from './chunk-3SDOQUUF.js';
6
- import { SITE_KIT_VERSION } from './chunk-AH5Q262S.js';
5
+ import { sonorFetch } from './chunk-D6ZC7XKZ.js';
6
+ import { SITE_KIT_VERSION } from './chunk-F24FNCTK.js';
7
7
  import { useRef, useEffect } from 'react';
8
8
 
9
9
  // src/fleet/heartbeat.ts
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { isEmptyValue, isDisplayOnly } from './chunk-AQSNPZY4.js';
2
+ import { isEmptyValue, isDisplayOnly } from './chunk-C2FZBUSS.js';
3
3
  import { useMemo, useState, useRef, useEffect } from 'react';
4
4
 
5
5
  var NO_CONTROLS = {};
@@ -26,7 +26,7 @@ function useFieldControls(fields) {
26
26
  }
27
27
  if (needsFile && !requested.current.file) {
28
28
  requested.current.file = true;
29
- import('./FileField-QKA3JB2Q.js').then((m) => {
29
+ import('./FileField-CI4UL5SH.js').then((m) => {
30
30
  if (!cancelled) setControls((prev) => ({ ...prev, File: m.FileField }));
31
31
  }).catch(() => {
32
32
  requested.current.file = false;
@@ -1,5 +1,5 @@
1
- import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-VDF5DFWT.js';
2
- import { SITE_KIT_VERSION } from './chunk-AH5Q262S.js';
1
+ import { isTokenMode, getFreshCredential, isSiteToken, refreshSiteToken, currentCredential, credentialBreakerId } from './chunk-K4ZHAURD.js';
2
+ import { SITE_KIT_VERSION } from './chunk-F24FNCTK.js';
3
3
 
4
4
  // src/shared/fetchWithRetry.ts
5
5
  function backoffDelay(attempt, response) {
@@ -0,0 +1,370 @@
1
+ 'use client';
2
+ import { DEFAULT_WIDGET_Z_INDEX } from './chunk-27FISYK4.js';
3
+ import { getOrCreateVisitorId } from './chunk-24QZEO3Q.js';
4
+ import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
5
+ import { sonorFetch } from './chunk-D6ZC7XKZ.js';
6
+ import { useState, useEffect, useCallback, useRef } from 'react';
7
+ import ReactDOM from 'react-dom';
8
+ import dynamic from 'next/dynamic';
9
+ import { usePathname } from 'next/navigation';
10
+ import { jsx, Fragment } from 'react/jsx-runtime';
11
+
12
+ // src/shared/mid-form.ts
13
+ var BUTTON_TYPES = /* @__PURE__ */ new Set(["button", "submit", "reset", "image"]);
14
+ var UNREACHABLE = '[inert],[aria-hidden="true"],[data-sk-honeypot]';
15
+ var typedInto = /* @__PURE__ */ new WeakSet();
16
+ var watched = /* @__PURE__ */ new WeakSet();
17
+ function watchFormInput(doc = document) {
18
+ if (watched.has(doc)) return;
19
+ watched.add(doc);
20
+ const note = (e) => {
21
+ const target = e.composedPath?.()[0] ?? e.target;
22
+ if (target) typedInto.add(target);
23
+ };
24
+ doc.addEventListener("input", note, true);
25
+ doc.addEventListener("change", note, true);
26
+ }
27
+ function isFillable(el) {
28
+ if (!el) return false;
29
+ const tag = el.tagName;
30
+ if (tag !== "INPUT" && tag !== "TEXTAREA" && tag !== "SELECT") return false;
31
+ if (tag === "INPUT") {
32
+ const type = el.type;
33
+ if (type === "hidden" || BUTTON_TYPES.has(type)) return false;
34
+ }
35
+ return !el.closest(UNREACHABLE);
36
+ }
37
+ function belongsToForm(el) {
38
+ return Boolean(el.form ?? el.closest("form"));
39
+ }
40
+ function focused(doc) {
41
+ let el = doc.activeElement;
42
+ while (el?.shadowRoot?.activeElement) el = el.shadowRoot.activeElement;
43
+ return el;
44
+ }
45
+ function renderedSelected(option) {
46
+ return option.defaultSelected || option.hasAttribute("selected");
47
+ }
48
+ function selectChanged(select) {
49
+ const options = Array.from(select.options);
50
+ if (select.multiple) return options.some((o) => o.selected !== renderedSelected(o));
51
+ if (!select.value) return false;
52
+ const initial = [...options].reverse().find(renderedSelected) ?? options.find((o) => !o.disabled);
53
+ return select.options[select.selectedIndex] !== initial;
54
+ }
55
+ function isFilled(el) {
56
+ if (!isFillable(el)) return false;
57
+ const typed = typedInto.has(el);
58
+ if (!typed && (el.disabled || el.tagName !== "SELECT" && el.readOnly)) return false;
59
+ if (el.tagName === "SELECT") return selectChanged(el);
60
+ const field = el;
61
+ if (field.type === "checkbox" || field.type === "radio") {
62
+ const box = field;
63
+ return box.checked !== box.defaultChecked;
64
+ }
65
+ return field.value.trim() !== "" && (typed || field.value !== field.defaultValue);
66
+ }
67
+ function isVisitorMidForm(doc = document) {
68
+ if (doc.querySelector('[data-sk-state="submitting"]')) return true;
69
+ if (doc.querySelector('[data-sk-form-mount][data-sk-carried="true"] form')) return true;
70
+ const active = focused(doc);
71
+ if (active && belongsToForm(active) && (isFillable(active) || active.tagName === "IFRAME")) return true;
72
+ for (const form of Array.from(doc.forms)) {
73
+ for (const el of Array.from(form.elements)) if (isFilled(el)) return true;
74
+ }
75
+ return false;
76
+ }
77
+ function whenNotMidForm(open, options = {}) {
78
+ const { doc = document, settleMs = 1500, pollMs = 5e3 } = options;
79
+ if (!isVisitorMidForm(doc)) {
80
+ open();
81
+ return () => {
82
+ };
83
+ }
84
+ let done = false;
85
+ let settle;
86
+ const stop = () => {
87
+ done = true;
88
+ clearTimeout(settle);
89
+ clearInterval(poll);
90
+ doc.removeEventListener("focusout", onFocusOut, true);
91
+ };
92
+ const check = () => {
93
+ if (done || isVisitorMidForm(doc)) return;
94
+ stop();
95
+ open();
96
+ };
97
+ const onFocusOut = () => {
98
+ clearTimeout(settle);
99
+ settle = setTimeout(check, settleMs);
100
+ };
101
+ const poll = setInterval(check, pollMs);
102
+ doc.addEventListener("focusout", onFocusOut, true);
103
+ return stop;
104
+ }
105
+
106
+ // src/website/popup-rules.ts
107
+ function matchPath(pathname, pattern) {
108
+ if (pattern.endsWith("*")) return pathname.startsWith(pattern.slice(0, -1));
109
+ return pathname === pattern;
110
+ }
111
+ function deviceFromUserAgent(ua) {
112
+ if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet";
113
+ if (/mobile|iphone|ipod|android|blackberry|opera mini|iemobile/i.test(ua)) return "mobile";
114
+ return "desktop";
115
+ }
116
+ function seenKey(popupId) {
117
+ return `_engage_${popupId}`;
118
+ }
119
+ function isEligible(popup, pathname, device, store, now = Date.now()) {
120
+ const pages = popup.targeting?.pages;
121
+ if (pages) {
122
+ if (pages.exclude?.some((p) => matchPath(pathname, p))) return false;
123
+ if (pages.include && !pages.include.some((p) => matchPath(pathname, p))) return false;
124
+ }
125
+ if (popup.targeting?.devices && !popup.targeting.devices.includes(device)) return false;
126
+ const frequency = popup.trigger?.frequency;
127
+ if (frequency) {
128
+ const key = seenKey(popup.id);
129
+ if (frequency.type === "once") {
130
+ if (store.local?.getItem(key)) return false;
131
+ } else if (frequency.type === "once-per-session") {
132
+ if (store.session?.getItem(key)) return false;
133
+ } else if (frequency.type === "every-n-days" && frequency.days) {
134
+ const last = store.local?.getItem(key);
135
+ if (last && now - parseInt(last, 10) < frequency.days * 864e5) return false;
136
+ }
137
+ }
138
+ return true;
139
+ }
140
+ var PLACEMENT = {
141
+ popup: "popup",
142
+ bar: "bar",
143
+ nudge: "slide-in",
144
+ "slide-in": "slide-in"
145
+ };
146
+ function popupPlacement(popup) {
147
+ if (!popup.blocks?.length) return void 0;
148
+ return popup.layout?.placement ?? PLACEMENT[popup.type] ?? "popup";
149
+ }
150
+ function waitsForForms(popup) {
151
+ return popupPlacement(popup) !== "bar";
152
+ }
153
+ function activatePopup(active, id, popups) {
154
+ if (active.includes(id)) return active;
155
+ const isPopup = (popupId) => {
156
+ const popup = popups.find((p) => p.id === popupId);
157
+ return popup ? popupPlacement(popup) === "popup" : false;
158
+ };
159
+ if (isPopup(id) && active.some(isPopup)) return active;
160
+ return [...active, id];
161
+ }
162
+ var LazyBlocksPopup = dynamic(
163
+ () => import('./BlocksPopup-YJNJIVPO.js').then((m) => ({ default: m.BlocksPopup })),
164
+ { ssr: false }
165
+ );
166
+ function SitePopups(props) {
167
+ const target = resolveAnalyticsTarget(props);
168
+ if (!target) return null;
169
+ return /* @__PURE__ */ jsx(AllowedSitePopups, { ...props, ...target });
170
+ }
171
+ function AllowedSitePopups({
172
+ apiUrl,
173
+ apiKey,
174
+ zIndex = DEFAULT_WIDGET_Z_INDEX,
175
+ debug = false
176
+ }) {
177
+ const pathname = usePathname();
178
+ const [popups, setPopups] = useState([]);
179
+ const [active, setActive] = useState([]);
180
+ const [dismissed, setDismissed] = useState(/* @__PURE__ */ new Set());
181
+ useEffect(() => {
182
+ let cancelled = false;
183
+ async function load() {
184
+ const visitorId = getOrCreateVisitorId();
185
+ const url = typeof window !== "undefined" ? window.location.href : "";
186
+ try {
187
+ const response = await sonorFetch(
188
+ `${apiUrl.replace(/\/$/, "")}/engage/widget/elements?url=${encodeURIComponent(url)}&visitorId=${encodeURIComponent(visitorId)}&deviceType=${getDeviceType()}`,
189
+ {},
190
+ { apiKey }
191
+ );
192
+ if (!response.ok) {
193
+ if (debug) console.error("[SitePopups] Error loading popups:", response.statusText);
194
+ return;
195
+ }
196
+ const json = await response.json();
197
+ const list = json.data?.elements ?? json.elements ?? [];
198
+ const drawable = list.filter((popup) => popupPlacement(popup) !== void 0);
199
+ if (debug) console.log("[SitePopups] Loaded popups:", drawable);
200
+ if (!cancelled) setPopups(drawable);
201
+ } catch (error) {
202
+ if (debug) console.error("[SitePopups] Error loading popups:", error);
203
+ }
204
+ }
205
+ load();
206
+ return () => {
207
+ cancelled = true;
208
+ };
209
+ }, [apiUrl, apiKey, debug]);
210
+ useEffect(() => {
211
+ watchFormInput();
212
+ }, []);
213
+ useEffect(() => {
214
+ if (!popups.length) return;
215
+ const store = { local: storageOrNull("local"), session: storageOrNull("session") };
216
+ const device = getDeviceType();
217
+ const eligible = popups.filter((popup) => !dismissed.has(popup.id) && isEligible(popup, pathname, device, store));
218
+ if (debug) console.log("[SitePopups] Eligible popups:", eligible);
219
+ const eligibleIds = new Set(eligible.map((p) => p.id));
220
+ setActive((prev) => {
221
+ const kept = prev.filter((id) => eligibleIds.has(id));
222
+ return kept.length === prev.length ? prev : kept;
223
+ });
224
+ const cleanups = [];
225
+ const activate = (popup) => {
226
+ const show = () => setActive((prev) => activatePopup(prev, popup.id, popups));
227
+ if (waitsForForms(popup)) cleanups.push(whenNotMidForm(show));
228
+ else show();
229
+ };
230
+ eligible.forEach((popup) => {
231
+ const trigger = popup.trigger;
232
+ if (trigger?.type === "immediate" || !trigger?.type) {
233
+ activate(popup);
234
+ } else if (trigger.type === "delay" && trigger.delay) {
235
+ const timer = setTimeout(() => activate(popup), trigger.delay * 1e3);
236
+ cleanups.push(() => clearTimeout(timer));
237
+ } else if (trigger.type === "exit-intent") {
238
+ const handleMouseLeave = (e) => {
239
+ if (e.clientY < 10) {
240
+ activate(popup);
241
+ document.removeEventListener("mouseleave", handleMouseLeave);
242
+ }
243
+ };
244
+ document.addEventListener("mouseleave", handleMouseLeave);
245
+ cleanups.push(() => document.removeEventListener("mouseleave", handleMouseLeave));
246
+ } else if (trigger.type === "scroll" && trigger.scrollPercentage) {
247
+ const handleScroll = () => {
248
+ const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight) * 100;
249
+ if (scrollPercent >= (trigger.scrollPercentage || 50)) {
250
+ activate(popup);
251
+ window.removeEventListener("scroll", handleScroll);
252
+ }
253
+ };
254
+ window.addEventListener("scroll", handleScroll, { passive: true });
255
+ cleanups.push(() => window.removeEventListener("scroll", handleScroll));
256
+ }
257
+ });
258
+ return () => {
259
+ cleanups.forEach((fn) => fn());
260
+ };
261
+ }, [popups, pathname, dismissed, debug]);
262
+ const recordSeen = useCallback((popupId) => {
263
+ const popup = popups.find((p) => p.id === popupId);
264
+ if (!popup?.trigger?.frequency) return;
265
+ try {
266
+ if (popup.trigger.frequency.type === "once-per-session") {
267
+ sessionStorage.setItem(seenKey(popupId), "true");
268
+ } else {
269
+ localStorage.setItem(seenKey(popupId), Date.now().toString());
270
+ }
271
+ } catch {
272
+ }
273
+ }, [popups]);
274
+ const handleDismiss = useCallback((popupId) => {
275
+ setDismissed((prev) => /* @__PURE__ */ new Set([...prev, popupId]));
276
+ setActive((prev) => prev.filter((id) => id !== popupId));
277
+ recordSeen(popupId);
278
+ }, [recordSeen]);
279
+ if (typeof document === "undefined" || !active.length) return null;
280
+ return ReactDOM.createPortal(
281
+ /* @__PURE__ */ jsx(Fragment, { children: active.map((popupId) => {
282
+ const popup = popups.find((p) => p.id === popupId);
283
+ if (!popup) return null;
284
+ return /* @__PURE__ */ jsx(
285
+ PopupView,
286
+ {
287
+ popup,
288
+ onDismiss: () => handleDismiss(popup.id),
289
+ onEngaged: () => recordSeen(popup.id),
290
+ zIndex,
291
+ apiUrl,
292
+ apiKey
293
+ },
294
+ popup.id
295
+ );
296
+ }) }),
297
+ document.body
298
+ );
299
+ }
300
+ function PopupView({
301
+ popup,
302
+ onDismiss,
303
+ onEngaged,
304
+ zIndex,
305
+ apiUrl,
306
+ apiKey
307
+ }) {
308
+ const rootRef = useRef(null);
309
+ const counted = useRef(false);
310
+ useEffect(() => {
311
+ const el = rootRef.current;
312
+ if (!el || counted.current) return;
313
+ const observer = new IntersectionObserver(
314
+ ([entry]) => {
315
+ if (entry.isIntersecting && !counted.current) {
316
+ counted.current = true;
317
+ trackPopupEvent(apiUrl, apiKey, "impression", { elementId: popup.id });
318
+ observer.disconnect();
319
+ }
320
+ },
321
+ { threshold: 0.5 }
322
+ );
323
+ observer.observe(el);
324
+ return () => observer.disconnect();
325
+ }, [apiUrl, apiKey, popup.id]);
326
+ const layout = popup.layout;
327
+ const position = layout?.corner ?? popup.config?.position;
328
+ const corner = position === "bottom-left" || position === "top-left" || position === "top-right" ? position : "bottom-right";
329
+ return /* @__PURE__ */ jsx(
330
+ LazyBlocksPopup,
331
+ {
332
+ blocks: popup.blocks,
333
+ placement: popupPlacement(popup) ?? "popup",
334
+ edge: layout?.edge,
335
+ accent: layout?.accent,
336
+ corner,
337
+ zIndex,
338
+ rootRef,
339
+ elementId: popup.id,
340
+ onClose: onDismiss,
341
+ onButton: (url) => {
342
+ onEngaged();
343
+ trackPopupEvent(apiUrl, apiKey, "click", { elementId: popup.id, action: "navigate", url });
344
+ }
345
+ }
346
+ );
347
+ }
348
+ function trackPopupEvent(apiUrl, apiKey, endpoint, data) {
349
+ const visitorId = typeof localStorage !== "undefined" ? localStorage.getItem("_sk_vid") : null;
350
+ sonorFetch(`${apiUrl.replace(/\/$/, "")}/engage/widget/${endpoint}`, {
351
+ method: "POST",
352
+ headers: { "Content-Type": "application/json" },
353
+ body: JSON.stringify({ ...data, visitorId, pageUrl: window.location.href }),
354
+ keepalive: true
355
+ }, { apiKey, retries: 0 }).catch(() => {
356
+ });
357
+ }
358
+ function getDeviceType() {
359
+ if (typeof navigator === "undefined") return "desktop";
360
+ return deviceFromUserAgent(navigator.userAgent);
361
+ }
362
+ function storageOrNull(kind) {
363
+ try {
364
+ return kind === "local" ? window.localStorage : window.sessionStorage;
365
+ } catch {
366
+ return null;
367
+ }
368
+ }
369
+
370
+ export { SitePopups };
@@ -1,4 +1,4 @@
1
1
  // src/shared/version.ts
2
- var SITE_KIT_VERSION = "7.1.2";
2
+ var SITE_KIT_VERSION = "7.2.0";
3
3
 
4
4
  export { SITE_KIT_VERSION };
@@ -1,10 +1,11 @@
1
1
  'use client';
2
- import { isFieldVisible, validateField, isFieldComplete } from './chunk-AQSNPZY4.js';
3
- import { normalizeFormConfig } from './chunk-5SQK3D53.js';
2
+ import { readAgentSubmit, formToolAttributes } from './chunk-Q6E4OPGG.js';
3
+ import { isFieldVisible, validateField, isFieldComplete, unseenControlValues } from './chunk-C2FZBUSS.js';
4
+ import { normalizeFormConfig, DEFAULT_FORM_SUCCESS_MESSAGE } from './chunk-5SQK3D53.js';
4
5
  import { DEFAULT_HONEYPOT_FIELD } from './chunk-NJCTQH2P.js';
5
6
  import { onPageLeave } from './chunk-L2V5PUFN.js';
6
7
  import { uuid } from './chunk-GJWI74ZZ.js';
7
- import { sonorFetch, sonorBeacon } from './chunk-3SDOQUUF.js';
8
+ import { sonorFetch, sonorBeacon } from './chunk-D6ZC7XKZ.js';
8
9
  import { useRef, useCallback, useEffect, useState, useMemo } from 'react';
9
10
 
10
11
  // src/forms/field-interactions.ts
@@ -567,7 +568,8 @@ async function submitForm({
567
568
  config,
568
569
  values,
569
570
  honeypotValue,
570
- formLoadedAt
571
+ formLoadedAt,
572
+ agentInvoked
571
573
  }) {
572
574
  const { apiUrl, apiKey } = getApiConfig();
573
575
  if (!apiKey) {
@@ -606,7 +608,8 @@ async function submitForm({
606
608
  // Sub-site host — preserved on form_submissions for lead attribution
607
609
  // across multi-site projects. Resolved by SiteKitClientProviders.
608
610
  site: typeof window !== "undefined" ? window.__SITE_KIT_SITE__ || window.location.host : void 0,
609
- _formLoadedAt: formLoadedAt
611
+ _formLoadedAt: formLoadedAt,
612
+ ...agentInvoked ? { agentInvoked: true } : {}
610
613
  }
611
614
  };
612
615
  const response = await sonorFetch(
@@ -800,23 +803,24 @@ function useForm(formIdOrSlug, options = {}) {
800
803
  setErrors(newErrors);
801
804
  return Object.keys(newErrors).length === 0;
802
805
  }, [fields, validateField2]);
803
- const validateAll = useCallback(() => {
806
+ const validateAllFor = useCallback((vals) => {
807
+ const visible = (field) => isFieldVisible(field, vals);
804
808
  const newErrors = {};
805
809
  for (const field of allFields) {
806
- const error = validateField2(field);
810
+ const error = validateField(field, vals, visible);
807
811
  if (error) newErrors[field.slug] = error;
808
812
  }
809
813
  setErrors(newErrors);
810
- return Object.keys(newErrors).length === 0;
811
- }, [allFields, validateField2]);
812
- const buildVisibleValues = useCallback(() => {
814
+ return newErrors;
815
+ }, [allFields]);
816
+ const buildVisibleValues = useCallback((vals) => {
813
817
  const out = {};
814
818
  for (const field of allFields) {
815
- if (!isFieldVisible2(field)) continue;
816
- if (field.slug in values) out[field.slug] = values[field.slug];
819
+ if (!isFieldVisible(field, vals)) continue;
820
+ if (field.slug in vals) out[field.slug] = vals[field.slug];
817
821
  }
818
822
  return out;
819
- }, [allFields, values, isFieldVisible2]);
823
+ }, [allFields]);
820
824
  const setFieldValue = useCallback((key, value) => {
821
825
  setValuesState((prev) => ({ ...prev, [key]: value }));
822
826
  trackFieldChange(key);
@@ -862,19 +866,25 @@ function useForm(formIdOrSlug, options = {}) {
862
866
  trackStepChange(targetStep);
863
867
  }
864
868
  }, [totalSteps, trackStepChange]);
865
- const submit = useCallback(async () => {
866
- if (!form) return;
867
- if (isSubmittingRef.current) return;
868
- if (!validateAll()) return;
869
+ const submit = useCallback(async (options2) => {
870
+ if (!form) return { status: "failed", message: "The form is still loading." };
871
+ if (isSubmittingRef.current) return { status: "busy" };
872
+ const domFills = options2 && typeof options2 === "object" && options2.domFills && typeof options2.domFills === "object" ? options2.domFills : null;
873
+ const agentInvoked = !!options2 && typeof options2 === "object" && options2.agentInvoked === true;
874
+ const vals = domFills && Object.keys(domFills).length > 0 ? { ...values, ...domFills } : values;
875
+ if (vals !== values) setValuesState(vals);
876
+ const invalid = validateAllFor(vals);
877
+ if (Object.keys(invalid).length > 0) return { status: "invalid", errors: invalid };
869
878
  isSubmittingRef.current = true;
870
879
  setIsSubmitting(true);
871
880
  setSubmitError(null);
872
881
  try {
873
882
  const data = await submitForm({
874
883
  config: form,
875
- values: buildVisibleValues(),
884
+ values: buildVisibleValues(vals),
876
885
  honeypotValue: "",
877
- formLoadedAt: formLoadedAt()
886
+ formLoadedAt: formLoadedAt(),
887
+ agentInvoked
878
888
  });
879
889
  trackComplete();
880
890
  setIsComplete(true);
@@ -883,15 +893,35 @@ function useForm(formIdOrSlug, options = {}) {
883
893
  if (finalRedirect) {
884
894
  window.location.href = finalRedirect;
885
895
  }
896
+ const confirmation = typeof form.success_message === "string" && form.success_message.trim() ? form.success_message.trim() : DEFAULT_FORM_SUCCESS_MESSAGE;
897
+ return { status: "sent", message: confirmation, submissionId: data.submissionId || void 0 };
886
898
  } catch (error) {
887
899
  console.error("[useForm] Submission error:", error);
888
- setSubmitError("Something went wrong sending your submission. Please try again.");
900
+ const message = "Something went wrong sending your submission. Please try again.";
901
+ setSubmitError(message);
889
902
  onError?.(error);
903
+ return { status: "failed", message };
890
904
  } finally {
891
905
  isSubmittingRef.current = false;
892
906
  setIsSubmitting(false);
893
907
  }
894
- }, [form, validateAll, buildVisibleValues, trackComplete, onSuccess, onError, redirectUrl]);
908
+ }, [form, values, validateAllFor, buildVisibleValues, trackComplete, onSuccess, onError, redirectUrl]);
909
+ const handleSubmit = useCallback((event) => {
910
+ event.preventDefault();
911
+ const agent = readAgentSubmit(event.nativeEvent);
912
+ if (form?.is_multi_step && step < totalSteps) {
913
+ const moved = nextStep();
914
+ agent.respond(
915
+ Promise.resolve(
916
+ moved ? { status: "next_step", step: step + 1, totalSteps } : { status: "invalid", errors: {} }
917
+ )
918
+ );
919
+ return;
920
+ }
921
+ const domFills = unseenControlValues(event.currentTarget, allFields, "", values);
922
+ agent.respond(submit({ agentInvoked: agent.invoked, domFills }));
923
+ }, [form, step, totalSteps, nextStep, allFields, values, submit]);
924
+ const toolAttributes = useMemo(() => form ? formToolAttributes(form) : null, [form]);
895
925
  const reset = useCallback(() => {
896
926
  setValuesState(initialValues);
897
927
  setErrors({});
@@ -926,6 +956,8 @@ function useForm(formIdOrSlug, options = {}) {
926
956
  validateField: validateField2,
927
957
  isFieldVisible: isFieldVisible2,
928
958
  submit,
959
+ handleSubmit,
960
+ toolAttributes,
929
961
  isSubmitting,
930
962
  isComplete,
931
963
  reset,
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { locToPath, isMalformedSitemapPath, inferPageType } from './chunk-YVKRYQRG.js';
3
3
  import { resolveAnalyticsTarget } from './chunk-43OCZ3JA.js';
4
- import { sonorFetch } from './chunk-3SDOQUUF.js';
4
+ import { sonorFetch } from './chunk-D6ZC7XKZ.js';
5
5
  import { useRef, useEffect } from 'react';
6
6
 
7
7
  var SYNC_STAMP_KEY = "_sk_sitemap_synced_at";