@sonordev/site-kit 8.5.1 → 8.6.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 (102) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/agent-manifest.json +1 -1
  3. package/dist/{AnalyticsProvider-KLOAGTWZ.js → AnalyticsProvider-GVJYL6XY.js} +6 -6
  4. package/dist/{ArticleViewTracker-VKNPRCY2.js → ArticleViewTracker-AOJXEBYM.js} +7 -7
  5. package/dist/{BlocksPopup-OL3L6BKW.js → BlocksPopup-AJ3CKVQG.js} +4 -4
  6. package/dist/{ChatWidget-YSGSX3WM.js → ChatWidget-EYBVB5SZ.js} +7 -7
  7. package/dist/FileField-UEQYIFRO.js +237 -0
  8. package/dist/{FormSpotlight-JNTADX4I.js → FormSpotlight-H73ZZ66T.js} +2 -2
  9. package/dist/{FormStage-FU5AOMED.js → FormStage-2PLWWYBW.js} +2 -2
  10. package/dist/{ManagedForm-XS6JPQLH.js → ManagedForm-ZQOFXEWS.js} +9 -9
  11. package/dist/{ManagedNewsletterForm-KV4PQDKB.js → ManagedNewsletterForm-5TE47YZD.js} +11 -11
  12. package/dist/{SignalCore-MTYA5WGO.js → SignalCore-FEFNMNYN.js} +5 -5
  13. package/dist/{SiteChat-W25JYBBW.js → SiteChat-3MKYL2XM.js} +1 -1
  14. package/dist/{SiteDesignReporter-7ZJYE2EL.js → SiteDesignReporter-AJDIBNHI.js} +5 -5
  15. package/dist/{SitePopups-Q6OZDNCA.js → SitePopups-C44YTQ3Q.js} +6 -6
  16. package/dist/{SitemapSync-X5RLROGU.js → SitemapSync-UJA6JVPW.js} +4 -4
  17. package/dist/_client/booking-widget.js +7 -5
  18. package/dist/affiliates/index.js +3 -3
  19. package/dist/analytics/index.js +6 -6
  20. package/dist/articles/index.js +1 -1
  21. package/dist/articles/server-ui.js +3 -3
  22. package/dist/articles/server.js +2 -2
  23. package/dist/chat/index.js +8 -8
  24. package/dist/{chunk-B42B2D3N.js → chunk-6AWIUO2Z.js} +1 -1
  25. package/dist/{chunk-PJWAZ3NK.js → chunk-6NXFA7HE.js} +1 -1
  26. package/dist/{chunk-DY3OOSYN.js → chunk-72F44HKL.js} +8 -2
  27. package/dist/{chunk-LKYXK26A.js → chunk-7R2STOVD.js} +5 -5
  28. package/dist/{chunk-TDPYVU3Z.js → chunk-AJG74ZZV.js} +2 -2
  29. package/dist/{chunk-F4CTVKTD.js → chunk-BUE3Z32O.js} +1 -1
  30. package/dist/{chunk-ZPARXMUW.js → chunk-CMUD37WV.js} +1 -1
  31. package/dist/{chunk-TYAVHRWH.js → chunk-CVADNHJ4.js} +8 -2
  32. package/dist/{chunk-2Q4FBZHZ.js → chunk-DSD3XZQQ.js} +1 -1
  33. package/dist/{chunk-N7XRLWGW.js → chunk-FTSEP7Q3.js} +3 -3
  34. package/dist/{chunk-QXTM6ZC5.js → chunk-GFY3E7EQ.js} +2 -2
  35. package/dist/{chunk-AVDSPRCL.js → chunk-HMTPRBYJ.js} +2 -2
  36. package/dist/{chunk-SNU74EFA.js → chunk-IGAIQ2B3.js} +2 -2
  37. package/dist/{chunk-5HQOFANA.js → chunk-IYCSDFPN.js} +2 -2
  38. package/dist/{chunk-OISBBGOB.js → chunk-JSW42BHL.js} +1 -1
  39. package/dist/{chunk-IIBDU7EN.js → chunk-KTMHRXDT.js} +1 -1
  40. package/dist/{chunk-7QAPTPY7.js → chunk-L6QTERSH.js} +1 -1
  41. package/dist/{chunk-6AD52ATG.js → chunk-NI6ZTP3D.js} +4 -9
  42. package/dist/{chunk-ZHRHB4XS.js → chunk-NUXXFCIT.js} +2 -2
  43. package/dist/{chunk-AWQD6V77.js → chunk-O3Z5HHFP.js} +1 -1
  44. package/dist/{chunk-HOFIM5HD.js → chunk-OFSV5TZY.js} +1 -1
  45. package/dist/{chunk-D24GZ5DA.js → chunk-OWO3RFEX.js} +2 -2
  46. package/dist/{chunk-2TL6SL5V.js → chunk-QNOJSSQT.js} +1 -1
  47. package/dist/{chunk-P7HAR2NS.js → chunk-QQT2T6JQ.js} +1 -1
  48. package/dist/{chunk-5SQK3D53.js → chunk-TYSIAWC3.js} +1 -0
  49. package/dist/{chunk-NONH4CXJ.js → chunk-U34O6I7W.js} +2 -2
  50. package/dist/{chunk-JVOLGFBE.js → chunk-VA6IJ55K.js} +1 -1
  51. package/dist/{chunk-LNSF6BJC.js → chunk-VPZJ7ZTL.js} +2 -2
  52. package/dist/{chunk-4SX2HMLH.js → chunk-YJH6WE76.js} +9 -9
  53. package/dist/{chunk-I75RODCU.js → chunk-YNBRVVTX.js} +1 -1
  54. package/dist/{chunk-JQXHA6WI.js → chunk-ZZSAWXWD.js} +1 -1
  55. package/dist/client/index.js +6 -6
  56. package/dist/contracts/broadcast-layouts.d.ts +133 -0
  57. package/dist/contracts/business-claims.d.ts +456 -0
  58. package/dist/contracts/entries.d.ts +1 -1
  59. package/dist/contracts/outreach.d.ts +267 -0
  60. package/dist/contracts/page-titles.d.ts +140 -0
  61. package/dist/contracts/public-url-host.d.ts +10 -0
  62. package/dist/contracts/publication-branding.d.ts +143 -0
  63. package/dist/contracts/publication-charter.d.ts +14 -0
  64. package/dist/fleet/index.js +4 -4
  65. package/dist/forms/FileField.d.ts +3 -1
  66. package/dist/forms/PrivateFileField.d.ts +3 -0
  67. package/dist/forms/index.js +11 -11
  68. package/dist/forms/private-file-rules.d.ts +8 -0
  69. package/dist/forms/server.js +5 -5
  70. package/dist/forms/static.js +3 -3
  71. package/dist/forms/styles.css +17 -1
  72. package/dist/forms/types.d.ts +4 -0
  73. package/dist/images/index.js +4 -4
  74. package/dist/index.js +1 -1
  75. package/dist/layout/client.js +9 -9
  76. package/dist/layout/index.js +10 -10
  77. package/dist/llms/index.js +4 -4
  78. package/dist/maps/index.js +3 -3
  79. package/dist/mcp/sonor.js +11 -9
  80. package/dist/revalidate/index.js +2 -2
  81. package/dist/runtime/index.js +3 -3
  82. package/dist/seo/client.js +4 -4
  83. package/dist/seo/index.js +4 -4
  84. package/dist/seo/llms.js +4 -4
  85. package/dist/seo/register-sitemap-cli.js +1 -1
  86. package/dist/seo/sitemap.js +2 -2
  87. package/dist/server/index.js +2 -2
  88. package/dist/shared/version.d.ts +1 -1
  89. package/dist/signal/index.js +2 -2
  90. package/dist/sitemap/index.js +2 -2
  91. package/dist/sync/api.d.ts +3 -0
  92. package/dist/sync/index.js +7 -5
  93. package/dist/website/images.js +4 -4
  94. package/dist/website/index.js +5 -5
  95. package/dist/website/popups.js +6 -6
  96. package/dist/{writeLLMsTxt-NOTTCJB5.js → writeLLMsTxt-KHVHNSDM.js} +1 -1
  97. package/package.json +1 -1
  98. package/src/forms/README.md +22 -0
  99. package/src/sync/README.md +8 -0
  100. package/dist/FileField-PZOAS6MJ.js +0 -83
  101. package/dist/{chunk-ZKADJNR2.js → chunk-IPQKQEWI.js} +1 -1
  102. /package/dist/{chunk-MWWGUEBK.js → chunk-V67OD7RW.js} +0 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @sonordev/site-kit Changelog
2
2
 
3
+ ## 8.6.0
4
+
5
+ - Managed file fields can accept private documents with drag and drop, upload status, removal and retry. Configure `validation.private_attachments` and `validation.max_files` to accept up to four PDF, JPEG, PNG or WebP files, each up to 10 MB. This requires Sonor's private attachment endpoints; documents never fall back to public media uploads.
6
+ - Lazy date and file controls continue loading correctly when React Strict Mode cleans up and restarts their effects.
7
+
8
+ ## 8.5.2
9
+
10
+ - Booking requests include the browser's existing visitor and analytics session IDs so Sonor can connect the recorded journey to the accepted booking's CRM contact. Slot-hold session ownership stays separate.
11
+ - The shared booking API covers both the widget and custom booking screens, and leaves tracking IDs absent when analytics hasn't created them.
12
+
3
13
  ## 8.5.1
4
14
 
5
15
  - Add `AiDiscoveryLinks` and `SiteKitLayout.llmsDiscovery` for static HTML links to public AI resources, preserving framework font and stylesheet preload headers across host adapters.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "package": "@sonordev/site-kit",
4
- "version": "8.5.1",
4
+ "version": "8.6.0",
5
5
  "description": "Machine-readable manifest for coding agents integrating a site with Sonor. See AGENTS.md (shipped) and docs/SITE-KIT-AGENT-NATIVE.md.",
6
6
  "env": {
7
7
  "required": [
@@ -1,13 +1,13 @@
1
- export { AnalyticsProvider, useAnalytics, useAnalyticsOptional, useTrackEvent } from './chunk-QXTM6ZC5.js';
1
+ export { AnalyticsProvider, useAnalytics, useAnalyticsOptional, useTrackEvent } from './chunk-GFY3E7EQ.js';
2
2
  import './chunk-LCDVPAMR.js';
3
- import './chunk-ZKADJNR2.js';
3
+ import './chunk-IPQKQEWI.js';
4
4
  import './chunk-L2V5PUFN.js';
5
- import './chunk-5TM3WPCI.js';
6
5
  import './chunk-GJWI74ZZ.js';
6
+ import './chunk-5TM3WPCI.js';
7
7
  import './chunk-DLM4FFNH.js';
8
8
  import './chunk-EKBEOXTH.js';
9
- import './chunk-5HQOFANA.js';
10
- import './chunk-P7HAR2NS.js';
9
+ import './chunk-IYCSDFPN.js';
10
+ import './chunk-QQT2T6JQ.js';
11
11
  import './chunk-W7OPEMGJ.js';
12
- import './chunk-F4CTVKTD.js';
12
+ import './chunk-BUE3Z32O.js';
13
13
  import './chunk-PKBMQBKP.js';
@@ -1,7 +1,7 @@
1
1
  'use client';
2
- import './chunk-B42B2D3N.js';
2
+ import './chunk-6AWIUO2Z.js';
3
3
  import './chunk-B7T2SFUF.js';
4
- import './chunk-JVOLGFBE.js';
4
+ import './chunk-VA6IJ55K.js';
5
5
  import './chunk-6T6CQVNL.js';
6
6
  import './chunk-5ROTTMBL.js';
7
7
  import { useDeferredActivation } from './chunk-YC7ELZS3.js';
@@ -12,13 +12,13 @@ import './chunk-YVKRYQRG.js';
12
12
  import './chunk-FESKKJAS.js';
13
13
  import './chunk-A4CC4ZG3.js';
14
14
  import './chunk-LCDVPAMR.js';
15
- import './chunk-ZKADJNR2.js';
16
- import './chunk-5TM3WPCI.js';
15
+ import './chunk-IPQKQEWI.js';
17
16
  import './chunk-GJWI74ZZ.js';
18
- import { sonorFetch } from './chunk-5HQOFANA.js';
19
- import './chunk-P7HAR2NS.js';
17
+ import './chunk-5TM3WPCI.js';
18
+ import { sonorFetch } from './chunk-IYCSDFPN.js';
19
+ import './chunk-QQT2T6JQ.js';
20
20
  import { readPublishedApiConfig, readPublishedSite } from './chunk-W7OPEMGJ.js';
21
- import './chunk-F4CTVKTD.js';
21
+ import './chunk-BUE3Z32O.js';
22
22
  import './chunk-PKBMQBKP.js';
23
23
  import { useEffect } from 'react';
24
24
 
@@ -1,14 +1,14 @@
1
1
  'use client';
2
- import { getSiteDesignTokens } from './chunk-ZHRHB4XS.js';
2
+ import { getSiteDesignTokens } from './chunk-NUXXFCIT.js';
3
3
  import { DEFAULT_POPUP_TOKENS, PopupBlocks } from './chunk-BF7TZYC3.js';
4
4
  import { normalizePopupBlocks, toHexColor, readableOn } from './chunk-IHG36STL.js';
5
5
  import './chunk-S22FSH7C.js';
6
6
  import './chunk-DLM4FFNH.js';
7
7
  import './chunk-EKBEOXTH.js';
8
- import './chunk-5HQOFANA.js';
9
- import './chunk-P7HAR2NS.js';
8
+ import './chunk-IYCSDFPN.js';
9
+ import './chunk-QQT2T6JQ.js';
10
10
  import './chunk-W7OPEMGJ.js';
11
- import './chunk-F4CTVKTD.js';
11
+ import './chunk-BUE3Z32O.js';
12
12
  import './chunk-PKBMQBKP.js';
13
13
  import { useState } from 'react';
14
14
  import { jsx } from 'react/jsx-runtime';
@@ -1,20 +1,20 @@
1
- export { ChatWidget, EchoChat, isChatEnabled } from './chunk-SNU74EFA.js';
1
+ export { ChatWidget, EchoChat, isChatEnabled } from './chunk-IGAIQ2B3.js';
2
2
  import './chunk-6T6CQVNL.js';
3
3
  import './chunk-JE4A5S4F.js';
4
4
  import './chunk-27FISYK4.js';
5
- import './chunk-AVDSPRCL.js';
5
+ import './chunk-HMTPRBYJ.js';
6
6
  import './chunk-Q6E4OPGG.js';
7
7
  import './chunk-5ROTTMBL.js';
8
8
  import './chunk-C2FZBUSS.js';
9
- import './chunk-5SQK3D53.js';
9
+ import './chunk-TYSIAWC3.js';
10
10
  import './chunk-HW7B43E5.js';
11
11
  import './chunk-NJCTQH2P.js';
12
12
  import './chunk-S22FSH7C.js';
13
13
  import './chunk-L2V5PUFN.js';
14
- import './chunk-5TM3WPCI.js';
15
14
  import './chunk-GJWI74ZZ.js';
16
- import './chunk-5HQOFANA.js';
17
- import './chunk-P7HAR2NS.js';
15
+ import './chunk-5TM3WPCI.js';
16
+ import './chunk-IYCSDFPN.js';
17
+ import './chunk-QQT2T6JQ.js';
18
18
  import './chunk-W7OPEMGJ.js';
19
- import './chunk-F4CTVKTD.js';
19
+ import './chunk-BUE3Z32O.js';
20
20
  import './chunk-PKBMQBKP.js';
@@ -0,0 +1,237 @@
1
+ 'use client';
2
+ import { sonorFetch } from './chunk-IYCSDFPN.js';
3
+ import './chunk-QQT2T6JQ.js';
4
+ import { getClientApiConfig } from './chunk-W7OPEMGJ.js';
5
+ import './chunk-BUE3Z32O.js';
6
+ import './chunk-PKBMQBKP.js';
7
+ import React2, { useState, useRef } from 'react';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
+
10
+ // src/forms/private-file-rules.ts
11
+ var PRIVATE_FILE_MAX_BYTES = 10 * 1024 * 1024;
12
+ var PRIVATE_FILE_TYPES = ["application/pdf", "image/jpeg", "image/png", "image/webp"];
13
+ var PRIVATE_FILE_ACCEPT = ".pdf,.jpg,.jpeg,.png,.webp";
14
+ function validatePrivateFileSelection(files, maxFiles = 4) {
15
+ if (!files.length) return null;
16
+ if (files.length > Math.max(1, Math.min(4, maxFiles))) return `Choose up to ${Math.max(1, Math.min(4, maxFiles))} files.`;
17
+ if (files.some((file) => !file.size || file.size > PRIVATE_FILE_MAX_BYTES)) return "Each file must be between 1 byte and 10 MB.";
18
+ if (files.some((file) => !PRIVATE_FILE_TYPES.includes(file.type))) return "Choose PDF, JPG, PNG or WebP files.";
19
+ return null;
20
+ }
21
+ function PrivateFileField({ field, inputId, onChange, ariaInvalid, describedBy }) {
22
+ const [entries, setEntries] = useState([]);
23
+ const [busy, setBusy] = useState(false);
24
+ const [dragging, setDragging] = useState(false);
25
+ const [error, setError] = useState(null);
26
+ const serial = useRef(0);
27
+ const dragDepth = useRef(0);
28
+ const busyRef = useRef(false);
29
+ const maxFiles = Math.max(1, Math.min(4, field.validation?.max_files || 4));
30
+ const report = (next) => onChange(next.every((item) => item.state === "ready" && item.token) ? next.map((item) => item.token) : []);
31
+ const upload = async (next) => {
32
+ if (busyRef.current) return;
33
+ busyRef.current = true;
34
+ setBusy(true);
35
+ setError(null);
36
+ onChange([]);
37
+ const current = next.map((item) => ({ ...item }));
38
+ for (const entry of current) {
39
+ if (entry.token) continue;
40
+ entry.state = "uploading";
41
+ setEntries(current.map((item) => ({ ...item })));
42
+ try {
43
+ const { apiUrl, apiKey } = getClientApiConfig();
44
+ if (!apiKey || !field.form_id) throw new Error("Unavailable");
45
+ const body = new FormData();
46
+ body.append("form_id", field.form_id);
47
+ body.append("field_slug", field.slug);
48
+ body.append("file", entry.file);
49
+ const response = await sonorFetch(`${apiUrl}/api/public/forms/attachments`, { method: "POST", headers: { Authorization: `Bearer ${apiKey}` }, body }, { retries: 0, timeoutMs: 6e4 });
50
+ if (!response.ok) throw new Error("Upload failed");
51
+ const data = await response.json();
52
+ if (!data.token?.startsWith("sonor-private-upload:")) throw new Error("Invalid upload response");
53
+ entry.token = data.token;
54
+ entry.state = "ready";
55
+ } catch {
56
+ entry.state = "error";
57
+ setError("A document could not be uploaded. Try again or remove it before sending.");
58
+ }
59
+ setEntries(current.map((item) => ({ ...item })));
60
+ }
61
+ report(current);
62
+ busyRef.current = false;
63
+ setBusy(false);
64
+ };
65
+ const addFiles = (files) => {
66
+ if (busyRef.current) return;
67
+ const fresh = files.filter((file) => !entries.some((entry) => entry.file.name === file.name && entry.file.size === file.size && entry.file.lastModified === file.lastModified));
68
+ const next = [...entries, ...fresh.map((file) => ({ id: ++serial.current, file, state: "waiting" }))];
69
+ const problem = validatePrivateFileSelection(next.map((item) => item.file), maxFiles);
70
+ if (problem) {
71
+ setError(problem);
72
+ return;
73
+ }
74
+ if (fresh.length) void upload(next);
75
+ };
76
+ const remove = (id) => {
77
+ const next = entries.filter((item) => item.id !== id);
78
+ setEntries(next);
79
+ setError(next.some((item) => item.state === "error") ? "Try uploading the remaining document again." : null);
80
+ report(next);
81
+ };
82
+ return /* @__PURE__ */ jsxs("div", { className: "sk-private-files", "aria-busy": busy, children: [
83
+ /* @__PURE__ */ jsxs(
84
+ "label",
85
+ {
86
+ htmlFor: inputId,
87
+ className: `sk-file-dropzone${dragging ? " sk-file-dropzone--active" : ""}${busy ? " sk-file-dropzone--busy" : ""}`,
88
+ onDragEnter: (event) => {
89
+ event.preventDefault();
90
+ dragDepth.current++;
91
+ setDragging(true);
92
+ },
93
+ onDragOver: (event) => {
94
+ event.preventDefault();
95
+ event.dataTransfer.dropEffect = busy ? "none" : "copy";
96
+ },
97
+ onDragLeave: (event) => {
98
+ event.preventDefault();
99
+ if (--dragDepth.current <= 0) {
100
+ dragDepth.current = 0;
101
+ setDragging(false);
102
+ }
103
+ },
104
+ onDrop: (event) => {
105
+ event.preventDefault();
106
+ dragDepth.current = 0;
107
+ setDragging(false);
108
+ addFiles(Array.from(event.dataTransfer.files));
109
+ },
110
+ children: [
111
+ /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", width: "28", height: "28", fill: "none", stroke: "currentColor", strokeWidth: "1.4", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 16V3m-5 5 5-5 5 5M4 15v5h16v-5" }) }),
112
+ /* @__PURE__ */ jsx("span", { className: "sk-file-dropzone__title", children: busy ? "Uploading privately\u2026" : entries.length ? "Add another document" : "Drop your estimate here" }),
113
+ /* @__PURE__ */ jsxs("span", { className: "sk-file-dropzone__hint", children: [
114
+ "or ",
115
+ /* @__PURE__ */ jsx("span", { children: "choose files" })
116
+ ] }),
117
+ /* @__PURE__ */ jsx("span", { className: "sk-file-dropzone__formats", children: "PDF, JPG, PNG or WebP \xB7 10 MB each" }),
118
+ /* @__PURE__ */ jsx(
119
+ "input",
120
+ {
121
+ id: inputId,
122
+ className: "sk-file-dropzone__input",
123
+ type: "file",
124
+ name: field.slug,
125
+ accept: PRIVATE_FILE_ACCEPT,
126
+ multiple: maxFiles > 1,
127
+ disabled: busy,
128
+ "aria-required": field.is_required,
129
+ "aria-invalid": ariaInvalid,
130
+ "aria-describedby": [describedBy, `${inputId}-upload-status`, error ? `${inputId}-upload-error` : null].filter(Boolean).join(" "),
131
+ onChange: (event) => {
132
+ addFiles(Array.from(event.target.files || []));
133
+ event.target.value = "";
134
+ }
135
+ }
136
+ )
137
+ ]
138
+ }
139
+ ),
140
+ !!entries.length && /* @__PURE__ */ jsx("ul", { className: "sk-private-files__list", children: entries.map((entry) => /* @__PURE__ */ jsxs("li", { children: [
141
+ /* @__PURE__ */ jsxs("span", { className: "sk-private-files__name", children: [
142
+ entry.file.name,
143
+ /* @__PURE__ */ jsxs("small", { children: [
144
+ (entry.file.size / 1024 / 1024).toFixed(1),
145
+ " MB \xB7 ",
146
+ entry.state === "ready" ? "Uploaded" : entry.state === "error" ? "Upload failed" : entry.state === "uploading" ? "Uploading\u2026" : "Waiting"
147
+ ] })
148
+ ] }),
149
+ /* @__PURE__ */ jsxs("div", { children: [
150
+ entry.state === "error" && /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, onClick: () => void upload(entries), children: "Try again" }),
151
+ /* @__PURE__ */ jsx("button", { type: "button", disabled: busy, "aria-label": `Remove ${entry.file.name}`, onClick: () => remove(entry.id), children: "Remove" })
152
+ ] })
153
+ ] }, entry.id)) }),
154
+ /* @__PURE__ */ jsx("p", { id: `${inputId}-upload-status`, className: "sk-private-files__status", role: "status", "aria-live": "polite", children: busy ? "Wait for your documents to finish uploading." : entries.length && entries.every((entry) => entry.state === "ready") ? `${entries.length} ${entries.length === 1 ? "document" : "documents"} ready to send.` : "Files upload privately when you add them." }),
155
+ error && /* @__PURE__ */ jsx("p", { id: `${inputId}-upload-error`, className: "sk-private-files__error", role: "alert", children: error })
156
+ ] });
157
+ }
158
+ function FileField({
159
+ field,
160
+ inputId,
161
+ classPrefix,
162
+ baseInputStyle,
163
+ onChange,
164
+ ariaInvalid,
165
+ describedBy
166
+ }) {
167
+ if (field.validation?.private_attachments) return /* @__PURE__ */ jsx(PrivateFileField, { field, inputId, onChange, ariaInvalid, describedBy });
168
+ return /* @__PURE__ */ jsx(PublicFileField, { field, inputId, classPrefix, baseInputStyle, onChange });
169
+ }
170
+ function PublicFileField({ field, inputId, classPrefix, baseInputStyle, onChange }) {
171
+ const [status, setStatus] = React2.useState("idle");
172
+ const [fileName, setFileName] = React2.useState("");
173
+ const handleFile = async (e) => {
174
+ const file = e.target.files?.[0];
175
+ if (!file) return;
176
+ setFileName(file.name);
177
+ setStatus("uploading");
178
+ onChange("");
179
+ try {
180
+ const url = await uploadFormFile(file);
181
+ onChange(url);
182
+ setStatus("idle");
183
+ } catch (err) {
184
+ console.error("[Forms] File upload failed:", err);
185
+ onChange("");
186
+ setStatus("error");
187
+ }
188
+ };
189
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
190
+ /* @__PURE__ */ jsx(
191
+ "input",
192
+ {
193
+ id: inputId,
194
+ className: `${classPrefix}__input ${classPrefix}__input--file`,
195
+ type: "file",
196
+ name: field.slug,
197
+ required: field.is_required,
198
+ disabled: status === "uploading",
199
+ onChange: handleFile,
200
+ style: baseInputStyle
201
+ }
202
+ ),
203
+ status === "uploading" && /* @__PURE__ */ jsxs("p", { style: { color: "var(--sk-text-tertiary, #6b7280)", fontSize: 14, marginTop: 4 }, children: [
204
+ "Uploading ",
205
+ fileName,
206
+ "\u2026"
207
+ ] }),
208
+ status === "error" && /* @__PURE__ */ jsx("p", { role: "alert", style: { color: "var(--sk-error, #dc2626)", fontSize: 14, marginTop: 4 }, children: "Upload failed. Please choose the file again." })
209
+ ] });
210
+ }
211
+ async function uploadFormFile(file) {
212
+ const { apiUrl, apiKey } = getClientApiConfig();
213
+ if (!apiKey) {
214
+ throw new Error("API key is required to upload files.");
215
+ }
216
+ const body = new FormData();
217
+ body.append("file", file);
218
+ const response = await sonorFetch(
219
+ `${apiUrl}/api/public/forms/upload`,
220
+ {
221
+ method: "POST",
222
+ headers: { Authorization: `Bearer ${apiKey}` },
223
+ body
224
+ },
225
+ { retries: 1, timeoutMs: 6e4 }
226
+ );
227
+ if (!response.ok) {
228
+ throw new Error(`Upload failed (${response.status})`);
229
+ }
230
+ const data = await response.json().catch(() => null);
231
+ if (!data?.url) {
232
+ throw new Error("Upload did not return a URL.");
233
+ }
234
+ return data.url;
235
+ }
236
+
237
+ export { FileField };
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
  import { StageStyles, SummaryTick } from './chunk-3YKQLC7J.js';
3
- import { stageFirstName, useFieldControls, stageViews, stageEstimate, AUTO_ADVANCE_TYPES } from './chunk-6AD52ATG.js';
3
+ import { stageFirstName, useFieldControls, stageViews, stageEstimate, AUTO_ADVANCE_TYPES } from './chunk-NI6ZTP3D.js';
4
4
  import { readAgentSubmit, formToolAttributes } from './chunk-Q6E4OPGG.js';
5
5
  import { isEmptyValue, unseenControlValues, isDisplayOnly } from './chunk-C2FZBUSS.js';
6
- import { FormField } from './chunk-TYAVHRWH.js';
6
+ import { FormField } from './chunk-CVADNHJ4.js';
7
7
  import './chunk-HW7B43E5.js';
8
8
  import { DEFAULT_HONEYPOT_FIELD } from './chunk-NJCTQH2P.js';
9
9
  import './chunk-PKBMQBKP.js';
@@ -1,9 +1,9 @@
1
1
  'use client';
2
2
  import { StageStyles, SummaryTick } from './chunk-3YKQLC7J.js';
3
- import { useFieldControls, stageViews, stageFirstName, stageEstimate, formatAnswer, AUTO_ADVANCE_TYPES } from './chunk-6AD52ATG.js';
3
+ import { useFieldControls, stageViews, stageFirstName, stageEstimate, formatAnswer, AUTO_ADVANCE_TYPES } from './chunk-NI6ZTP3D.js';
4
4
  import { readAgentSubmit, formToolAttributes } from './chunk-Q6E4OPGG.js';
5
5
  import { isEmptyValue, unseenControlValues } from './chunk-C2FZBUSS.js';
6
- import { FormField } from './chunk-TYAVHRWH.js';
6
+ import { FormField } from './chunk-CVADNHJ4.js';
7
7
  import './chunk-HW7B43E5.js';
8
8
  import { DEFAULT_HONEYPOT_FIELD } from './chunk-NJCTQH2P.js';
9
9
  import './chunk-PKBMQBKP.js';
@@ -1,19 +1,19 @@
1
- export { ManagedForm } from './chunk-LKYXK26A.js';
2
- import './chunk-6AD52ATG.js';
3
- import './chunk-AVDSPRCL.js';
1
+ export { ManagedForm } from './chunk-7R2STOVD.js';
2
+ import './chunk-NI6ZTP3D.js';
3
+ import './chunk-HMTPRBYJ.js';
4
4
  import './chunk-Q6E4OPGG.js';
5
5
  import './chunk-5ROTTMBL.js';
6
6
  import './chunk-C2FZBUSS.js';
7
7
  import './chunk-DXO6SBMD.js';
8
- import './chunk-TYAVHRWH.js';
9
- import './chunk-5SQK3D53.js';
8
+ import './chunk-CVADNHJ4.js';
9
+ import './chunk-TYSIAWC3.js';
10
10
  import './chunk-HW7B43E5.js';
11
11
  import './chunk-NJCTQH2P.js';
12
12
  import './chunk-L2V5PUFN.js';
13
- import './chunk-5TM3WPCI.js';
14
13
  import './chunk-GJWI74ZZ.js';
15
- import './chunk-5HQOFANA.js';
16
- import './chunk-P7HAR2NS.js';
14
+ import './chunk-5TM3WPCI.js';
15
+ import './chunk-IYCSDFPN.js';
16
+ import './chunk-QQT2T6JQ.js';
17
17
  import './chunk-W7OPEMGJ.js';
18
- import './chunk-F4CTVKTD.js';
18
+ import './chunk-BUE3Z32O.js';
19
19
  import './chunk-PKBMQBKP.js';
@@ -1,28 +1,28 @@
1
- import './chunk-7QAPTPY7.js';
2
- import './chunk-LKYXK26A.js';
3
- import './chunk-6AD52ATG.js';
1
+ import './chunk-L6QTERSH.js';
2
+ import './chunk-7R2STOVD.js';
3
+ import './chunk-NI6ZTP3D.js';
4
4
  import './chunk-SOD2F6C2.js';
5
5
  import './chunk-YGGDU6C3.js';
6
- import { useForm } from './chunk-AVDSPRCL.js';
6
+ import { useForm } from './chunk-HMTPRBYJ.js';
7
7
  import './chunk-Q6E4OPGG.js';
8
8
  import './chunk-5ROTTMBL.js';
9
- import './chunk-D24GZ5DA.js';
9
+ import './chunk-OWO3RFEX.js';
10
10
  import './chunk-C2FZBUSS.js';
11
11
  import './chunk-YC7ELZS3.js';
12
12
  import './chunk-DXO6SBMD.js';
13
- import './chunk-TYAVHRWH.js';
14
- import './chunk-5SQK3D53.js';
13
+ import './chunk-CVADNHJ4.js';
14
+ import './chunk-TYSIAWC3.js';
15
15
  import './chunk-HW7B43E5.js';
16
16
  import './chunk-NJCTQH2P.js';
17
17
  import { NewsletterShell } from './chunk-JHTPHA2D.js';
18
18
  import './chunk-DJAYFHVF.js';
19
19
  import './chunk-L2V5PUFN.js';
20
- import './chunk-5TM3WPCI.js';
21
20
  import './chunk-GJWI74ZZ.js';
22
- import './chunk-5HQOFANA.js';
23
- import './chunk-P7HAR2NS.js';
21
+ import './chunk-5TM3WPCI.js';
22
+ import './chunk-IYCSDFPN.js';
23
+ import './chunk-QQT2T6JQ.js';
24
24
  import './chunk-W7OPEMGJ.js';
25
- import './chunk-F4CTVKTD.js';
25
+ import './chunk-BUE3Z32O.js';
26
26
  import './chunk-PKBMQBKP.js';
27
27
  import { jsxs, jsx } from 'react/jsx-runtime';
28
28
 
@@ -1,14 +1,14 @@
1
1
  'use client';
2
- import { getOrCreateVisitorId, getOrCreateSessionId } from './chunk-ZKADJNR2.js';
2
+ import { getOrCreateVisitorId, getOrCreateSessionId } from './chunk-IPQKQEWI.js';
3
3
  import { onPageLeave } from './chunk-L2V5PUFN.js';
4
- import './chunk-5TM3WPCI.js';
5
4
  import './chunk-GJWI74ZZ.js';
5
+ import './chunk-5TM3WPCI.js';
6
6
  import { isPrivatePreview } from './chunk-DLM4FFNH.js';
7
7
  import './chunk-EKBEOXTH.js';
8
- import { sonorFetch } from './chunk-5HQOFANA.js';
9
- import { getFreshCredential, isSiteToken } from './chunk-P7HAR2NS.js';
8
+ import { sonorFetch } from './chunk-IYCSDFPN.js';
9
+ import { getFreshCredential, isSiteToken } from './chunk-QQT2T6JQ.js';
10
10
  import { getClientApiConfig } from './chunk-W7OPEMGJ.js';
11
- import './chunk-F4CTVKTD.js';
11
+ import './chunk-BUE3Z32O.js';
12
12
  import './chunk-PKBMQBKP.js';
13
13
  import { useState, useRef, useEffect, useCallback } from 'react';
14
14
 
@@ -1,4 +1,4 @@
1
- export { SiteChat } from './chunk-AWQD6V77.js';
1
+ export { SiteChat } from './chunk-O3Z5HHFP.js';
2
2
  import './chunk-27FISYK4.js';
3
3
  import './chunk-DLM4FFNH.js';
4
4
  import './chunk-EKBEOXTH.js';
@@ -1,12 +1,12 @@
1
- export { SiteDesignReporter } from './chunk-2Q4FBZHZ.js';
2
- import './chunk-ZHRHB4XS.js';
1
+ export { SiteDesignReporter } from './chunk-DSD3XZQQ.js';
2
+ import './chunk-NUXXFCIT.js';
3
3
  import './chunk-IHG36STL.js';
4
4
  import './chunk-YC7ELZS3.js';
5
5
  import './chunk-S22FSH7C.js';
6
6
  import './chunk-DLM4FFNH.js';
7
7
  import './chunk-EKBEOXTH.js';
8
- import './chunk-5HQOFANA.js';
9
- import './chunk-P7HAR2NS.js';
8
+ import './chunk-IYCSDFPN.js';
9
+ import './chunk-QQT2T6JQ.js';
10
10
  import './chunk-W7OPEMGJ.js';
11
- import './chunk-F4CTVKTD.js';
11
+ import './chunk-BUE3Z32O.js';
12
12
  import './chunk-PKBMQBKP.js';
@@ -1,13 +1,13 @@
1
- export { SitePopups } from './chunk-N7XRLWGW.js';
1
+ export { SitePopups } from './chunk-FTSEP7Q3.js';
2
2
  import './chunk-27FISYK4.js';
3
3
  import './chunk-5ROTTMBL.js';
4
- import './chunk-ZKADJNR2.js';
5
- import './chunk-5TM3WPCI.js';
4
+ import './chunk-IPQKQEWI.js';
6
5
  import './chunk-GJWI74ZZ.js';
6
+ import './chunk-5TM3WPCI.js';
7
7
  import './chunk-DLM4FFNH.js';
8
8
  import './chunk-EKBEOXTH.js';
9
- import './chunk-5HQOFANA.js';
10
- import './chunk-P7HAR2NS.js';
9
+ import './chunk-IYCSDFPN.js';
10
+ import './chunk-QQT2T6JQ.js';
11
11
  import './chunk-W7OPEMGJ.js';
12
- import './chunk-F4CTVKTD.js';
12
+ import './chunk-BUE3Z32O.js';
13
13
  import './chunk-PKBMQBKP.js';
@@ -1,9 +1,9 @@
1
- export { SitemapSync } from './chunk-PJWAZ3NK.js';
1
+ export { SitemapSync } from './chunk-6NXFA7HE.js';
2
2
  import './chunk-YVKRYQRG.js';
3
3
  import './chunk-DLM4FFNH.js';
4
4
  import './chunk-EKBEOXTH.js';
5
- import './chunk-5HQOFANA.js';
6
- import './chunk-P7HAR2NS.js';
5
+ import './chunk-IYCSDFPN.js';
6
+ import './chunk-QQT2T6JQ.js';
7
7
  import './chunk-W7OPEMGJ.js';
8
- import './chunk-F4CTVKTD.js';
8
+ import './chunk-BUE3Z32O.js';
9
9
  import './chunk-PKBMQBKP.js';
@@ -1,8 +1,10 @@
1
1
  'use client';
2
- export { BookingWidget, BookingWidget_default as default } from '../chunk-2TL6SL5V.js';
3
- import '../chunk-DY3OOSYN.js';
4
- import '../chunk-5HQOFANA.js';
5
- import '../chunk-P7HAR2NS.js';
2
+ export { BookingWidget, BookingWidget_default as default } from '../chunk-QNOJSSQT.js';
3
+ import '../chunk-72F44HKL.js';
4
+ import '../chunk-5ROTTMBL.js';
5
+ import '../chunk-5TM3WPCI.js';
6
+ import '../chunk-IYCSDFPN.js';
7
+ import '../chunk-QQT2T6JQ.js';
6
8
  import '../chunk-W7OPEMGJ.js';
7
- import '../chunk-F4CTVKTD.js';
9
+ import '../chunk-BUE3Z32O.js';
8
10
  import '../chunk-PKBMQBKP.js';
@@ -1,8 +1,8 @@
1
1
  'use client';
2
- import { sonorFetch } from '../chunk-5HQOFANA.js';
3
- import '../chunk-P7HAR2NS.js';
2
+ import { sonorFetch } from '../chunk-IYCSDFPN.js';
3
+ import '../chunk-QQT2T6JQ.js';
4
4
  import { getClientApiConfig } from '../chunk-W7OPEMGJ.js';
5
- import '../chunk-F4CTVKTD.js';
5
+ import '../chunk-BUE3Z32O.js';
6
6
  import '../chunk-PKBMQBKP.js';
7
7
  import { useState, useEffect } from 'react';
8
8
  import { jsx, jsxs } from 'react/jsx-runtime';
@@ -1,17 +1,17 @@
1
1
  'use client';
2
- export { AnalyticsProvider, WebVitals, useAnalytics, useAnalyticsOptional, useTrackEvent } from '../chunk-QXTM6ZC5.js';
2
+ export { AnalyticsProvider, WebVitals, useAnalytics, useAnalyticsOptional, useTrackEvent } from '../chunk-GFY3E7EQ.js';
3
3
  import { trackConversion } from '../chunk-LCDVPAMR.js';
4
4
  export { trackConversion, trackEvent } from '../chunk-LCDVPAMR.js';
5
- import '../chunk-ZKADJNR2.js';
5
+ import '../chunk-IPQKQEWI.js';
6
6
  import '../chunk-L2V5PUFN.js';
7
- import '../chunk-5TM3WPCI.js';
8
7
  import '../chunk-GJWI74ZZ.js';
8
+ import '../chunk-5TM3WPCI.js';
9
9
  export { isCrossOriginFrame, isFramed, isTopFrameSameOrigin } from '../chunk-DLM4FFNH.js';
10
10
  import '../chunk-EKBEOXTH.js';
11
- import '../chunk-5HQOFANA.js';
12
- import '../chunk-P7HAR2NS.js';
11
+ import '../chunk-IYCSDFPN.js';
12
+ import '../chunk-QQT2T6JQ.js';
13
13
  import '../chunk-W7OPEMGJ.js';
14
- import '../chunk-F4CTVKTD.js';
14
+ import '../chunk-BUE3Z32O.js';
15
15
  import '../chunk-PKBMQBKP.js';
16
16
  import { useCallback, useEffect } from 'react';
17
17
 
@@ -20,7 +20,7 @@ import '../chunk-PKBMQBKP.js';
20
20
  import React, { useState, Suspense, useEffect } from 'react';
21
21
  import { jsx, jsxs } from 'react/jsx-runtime';
22
22
 
23
- var ManagedNewsletterForm = React.lazy(() => import('../ManagedNewsletterForm-KV4PQDKB.js'));
23
+ var ManagedNewsletterForm = React.lazy(() => import('../ManagedNewsletterForm-5TE47YZD.js'));
24
24
  function NewsletterWidget({
25
25
  title = "Subscribe to Our Newsletter",
26
26
  description = "Get the latest posts delivered straight to your inbox.",
@@ -12,7 +12,7 @@ import '../chunk-H72ZFO3F.js';
12
12
  import { createPublicationRoutes, articleCategorySlug } from '../chunk-4K735O3J.js';
13
13
  export { articleCategorySlug, createPublicationRoutes, resolveArticleUrl, resolvePublicationUrl } from '../chunk-4K735O3J.js';
14
14
  import '../chunk-34ZYJTQR.js';
15
- import '../chunk-OISBBGOB.js';
15
+ import '../chunk-JSW42BHL.js';
16
16
  import '../chunk-UAYAJPEP.js';
17
17
  import '../chunk-VWWITFWM.js';
18
18
  import '../chunk-4KZ6W3ZH.js';
@@ -24,7 +24,7 @@ import '../chunk-YVKRYQRG.js';
24
24
  import '../chunk-FESKKJAS.js';
25
25
  import '../chunk-A4CC4ZG3.js';
26
26
  import '../chunk-W7OPEMGJ.js';
27
- import '../chunk-F4CTVKTD.js';
27
+ import '../chunk-BUE3Z32O.js';
28
28
  import '../chunk-PKBMQBKP.js';
29
29
  import 'server-only';
30
30
  import { marked } from 'marked';
@@ -144,7 +144,7 @@ async function Article({
144
144
  ...item,
145
145
  answer: addExternalLinkTargets(item.answer || "", siteUrlResolved)
146
146
  })) ?? faqRaw;
147
- const { ArticleViewTracker } = await import('../ArticleViewTracker-VKNPRCY2.js');
147
+ const { ArticleViewTracker } = await import('../ArticleViewTracker-AOJXEBYM.js');
148
148
  if (children) {
149
149
  return /* @__PURE__ */ jsxs(Fragment, { children: [
150
150
  /* @__PURE__ */ jsx(ArticleViewTracker, { slug: post.slug, site }),