@agntcms/next 0.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.
package/dist/index.cjs ADDED
@@ -0,0 +1,372 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ BooleanField: () => BooleanField,
24
+ ButtonField: () => ButtonField,
25
+ FormOverridesField: () => FormOverridesField,
26
+ HoneypotCollisionError: () => HoneypotCollisionError,
27
+ ImageField: () => ImageField,
28
+ InvalidFormFieldError: () => InvalidFormFieldError,
29
+ InvalidFormNameError: () => InvalidFormNameError,
30
+ LinkField: () => LinkField,
31
+ ListField: () => ListField,
32
+ NumberField: () => NumberField,
33
+ ReferenceField: () => ReferenceField,
34
+ RichTextField: () => RichTextField,
35
+ SelectField: () => SelectField,
36
+ SubmissionsNotReadableError: () => SubmissionsNotReadableError,
37
+ TextField: () => TextField,
38
+ VideoField: () => VideoField,
39
+ defineForm: () => defineForm,
40
+ defineSection: () => defineSection,
41
+ hasUniqueSectionIds: () => hasUniqueSectionIds,
42
+ hrefOf: () => hrefOf,
43
+ isExternalLink: () => isExternalLink,
44
+ linkAnchorAttrs: () => linkAnchorAttrs,
45
+ normalizeLinkValue: () => normalizeLinkValue,
46
+ validateEmail: () => validateEmail,
47
+ validateExternalUrl: () => validateExternalUrl,
48
+ validateInternalSlug: () => validateInternalSlug,
49
+ validatePhone: () => validatePhone
50
+ });
51
+ module.exports = __toCommonJS(src_exports);
52
+
53
+ // src/domain/fields.ts
54
+ var TextField = { kind: "text" };
55
+ var RichTextField = { kind: "richText" };
56
+ var ImageField = { kind: "image" };
57
+ var VideoField = { kind: "video" };
58
+ var ReferenceField = { kind: "reference" };
59
+ var LinkField = { kind: "link" };
60
+ var NumberField = { kind: "number" };
61
+ var BooleanField = { kind: "boolean" };
62
+ var SelectField = (options, opts) => ({
63
+ kind: "select",
64
+ options,
65
+ ...opts?.default !== void 0 ? { default: opts.default } : {}
66
+ });
67
+ var ButtonField = (variants, opts) => ({
68
+ kind: "button",
69
+ variants,
70
+ ...opts?.default !== void 0 ? { default: opts.default } : {}
71
+ });
72
+ var ListField = (itemSchema, opts) => ({
73
+ kind: "list",
74
+ itemSchema,
75
+ ...opts?.min !== void 0 ? { min: opts.min } : {},
76
+ ...opts?.max !== void 0 ? { max: opts.max } : {},
77
+ ...opts?.default !== void 0 ? { default: opts.default } : {}
78
+ });
79
+
80
+ // src/domain/invariants.ts
81
+ function hasUniqueSectionIds(page) {
82
+ const seen = /* @__PURE__ */ new Set();
83
+ for (const section of page.sections) {
84
+ if (seen.has(section.id)) return false;
85
+ seen.add(section.id);
86
+ }
87
+ return true;
88
+ }
89
+
90
+ // src/domain/linkValidation.ts
91
+ var SLUG_PATTERN = /^[a-z0-9](?:[a-z0-9-/]*[a-z0-9])?$/;
92
+ var validateInternalSlug = (slug) => {
93
+ if (slug === "") return null;
94
+ if (/^https?:\/\//i.test(slug)) return "slug must not include a protocol";
95
+ if (slug.startsWith("//")) return "slug must not start with //";
96
+ if (slug.startsWith("/")) return "slug must not start with /";
97
+ if (slug.endsWith("/")) return "slug must not end with /";
98
+ if (slug.includes("//")) return "slug must not contain //";
99
+ if (slug.includes("..")) return "slug must not contain ..";
100
+ if (!SLUG_PATTERN.test(slug)) {
101
+ return "slug must use lowercase letters, digits, hyphens, and slashes only";
102
+ }
103
+ return null;
104
+ };
105
+ var validateExternalUrl = (url) => {
106
+ if (url === "") return null;
107
+ if (typeof url !== "string") return "URL must be a string";
108
+ if (!/^https?:\/\//i.test(url)) {
109
+ return "External link must start with http:// or https://";
110
+ }
111
+ try {
112
+ const parsed = new globalThis.URL(url);
113
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
114
+ return "External link must use http or https";
115
+ }
116
+ } catch {
117
+ return "External link is not a valid URL";
118
+ }
119
+ return null;
120
+ };
121
+ var EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
122
+ var validateEmail = (email) => {
123
+ if (email === "") return null;
124
+ if (typeof email !== "string") return "Invalid email address";
125
+ if (!EMAIL_PATTERN.test(email)) return "Invalid email address";
126
+ return null;
127
+ };
128
+ var validatePhone = (phone) => {
129
+ if (phone === "") return null;
130
+ if (typeof phone !== "string") return "Phone number is too short";
131
+ const digits = phone.replace(/[^\d+]/g, "").replace(/\+/g, "");
132
+ if (digits.length < 7) return "Phone number is too short";
133
+ return null;
134
+ };
135
+
136
+ // src/domain/link.ts
137
+ function hrefOf(link) {
138
+ if (link.type === "external") return link.url;
139
+ if (link.type === "email") {
140
+ if (link.email === "") return "";
141
+ return "mailto:" + link.email;
142
+ }
143
+ if (link.type === "phone") {
144
+ if (link.phone === "") return "";
145
+ return "tel:" + link.phone.replace(/[^\d+]/g, "");
146
+ }
147
+ const slug = link.slug.startsWith("/") ? link.slug.slice(1) : link.slug;
148
+ if (slug === "" || slug === "home") return "/";
149
+ return "/" + slug;
150
+ }
151
+ function isExternalLink(link) {
152
+ return link.type === "external";
153
+ }
154
+ function linkAnchorAttrs(link) {
155
+ const href = hrefOf(link);
156
+ if (link.type === "external") {
157
+ return { href, target: "_blank", rel: "noreferrer" };
158
+ }
159
+ return { href };
160
+ }
161
+ function normalizeLinkValue(raw) {
162
+ if (raw === null || typeof raw !== "object") {
163
+ return { type: "internal", slug: "", label: "" };
164
+ }
165
+ const obj = raw;
166
+ const label = typeof obj["label"] === "string" ? obj["label"] : "";
167
+ if (obj["type"] === "internal") {
168
+ const slug = typeof obj["slug"] === "string" ? obj["slug"] : "";
169
+ return { type: "internal", slug, label };
170
+ }
171
+ if (obj["type"] === "external") {
172
+ const url = typeof obj["url"] === "string" ? obj["url"] : "";
173
+ return { type: "external", url, label };
174
+ }
175
+ if (obj["type"] === "email") {
176
+ const email = typeof obj["email"] === "string" ? obj["email"] : "";
177
+ return { type: "email", email, label };
178
+ }
179
+ if (obj["type"] === "phone") {
180
+ const phone = typeof obj["phone"] === "string" ? obj["phone"] : "";
181
+ return { type: "phone", phone, label };
182
+ }
183
+ if (typeof obj["href"] === "string") {
184
+ const href = obj["href"];
185
+ if (/^mailto:/i.test(href)) {
186
+ return { type: "email", email: href.slice("mailto:".length), label };
187
+ }
188
+ if (/^tel:/i.test(href)) {
189
+ return { type: "phone", phone: href.slice("tel:".length), label };
190
+ }
191
+ if (/^https?:\/\//i.test(href)) {
192
+ return { type: "external", url: href, label };
193
+ }
194
+ const slug = href.startsWith("/") ? href.slice(1) : href;
195
+ return { type: "internal", slug, label };
196
+ }
197
+ return { type: "internal", slug: "", label: "" };
198
+ }
199
+
200
+ // src/domain/form.ts
201
+ var FORM_FORBIDDEN_KINDS = /* @__PURE__ */ new Set([
202
+ "image",
203
+ "video",
204
+ "reference",
205
+ "list",
206
+ // `formOverrides` is a section-only descriptor (it overrides another
207
+ // form schema instance). Putting it inside a form would mean a form's
208
+ // payload could carry overrides for itself or another form — a
209
+ // recursive shape with no ergonomic editor UI. Section-only by design.
210
+ "formOverrides",
211
+ // `button` is a section-only descriptor: a styled CTA with an
212
+ // optional link. Public-form payloads collect user input — a button
213
+ // value is authored content, not a submitted answer. Section-only
214
+ // by design (mirrors `formOverrides`).
215
+ "button"
216
+ ]);
217
+ var SubmissionsNotReadableError = class extends Error {
218
+ constructor(message = "submission adapter does not support reading") {
219
+ super(message);
220
+ this.name = "SubmissionsNotReadableError";
221
+ }
222
+ };
223
+
224
+ // src/domain/formOverrides.ts
225
+ var FormOverridesField = (formName, opts) => ({
226
+ kind: "formOverrides",
227
+ formName,
228
+ ...opts?.default !== void 0 ? { default: opts.default } : {}
229
+ });
230
+
231
+ // src/sections/defineSection.ts
232
+ function builtInDefault(fieldName, descriptor) {
233
+ switch (descriptor.kind) {
234
+ case "image":
235
+ return { filename: "placeholder.png", alt: "Placeholder image" };
236
+ case "video":
237
+ return { url: "" };
238
+ case "richText":
239
+ return "Start writing here...";
240
+ case "text":
241
+ return fieldName.charAt(0).toUpperCase() + fieldName.slice(1).replace(/([A-Z])/g, " $1");
242
+ case "reference":
243
+ return { slug: "" };
244
+ case "link":
245
+ return { type: "internal", slug: "", label: "Learn more" };
246
+ case "button": {
247
+ const firstVariant = descriptor.variants[0]?.value ?? "";
248
+ return { label: "Get started", variant: firstVariant };
249
+ }
250
+ case "number":
251
+ return 0;
252
+ case "boolean":
253
+ return false;
254
+ case "select":
255
+ return descriptor.options[0]?.value ?? "";
256
+ case "list":
257
+ return [];
258
+ case "formOverrides":
259
+ return {};
260
+ default: {
261
+ const _exhaustive = descriptor;
262
+ void _exhaustive;
263
+ return "";
264
+ }
265
+ }
266
+ }
267
+ function defineSection(input) {
268
+ const defaults = {};
269
+ for (const [key, descriptor] of Object.entries(input.schema)) {
270
+ defaults[key] = descriptor.default ?? builtInDefault(key, descriptor);
271
+ }
272
+ return {
273
+ name: input.name,
274
+ ...input.category !== void 0 ? { category: input.category } : {},
275
+ schema: input.schema,
276
+ component: input.component,
277
+ defaults,
278
+ // Conditional spread mirrors the `category` pattern — required so
279
+ // `exactOptionalPropertyTypes` does not see `layouts: undefined` being
280
+ // assigned to an optional-only property.
281
+ ...input.layouts !== void 0 ? { layouts: input.layouts } : {}
282
+ };
283
+ }
284
+
285
+ // src/forms/defineForm.ts
286
+ var InvalidFormFieldError = class extends Error {
287
+ formName;
288
+ fieldName;
289
+ fieldKind;
290
+ constructor(formName, fieldName, fieldKind) {
291
+ super(
292
+ `Form "${formName}": field "${fieldName}" uses kind "${fieldKind}", which is not allowed in a form schema in v1. Allowed kinds: text, richText, number, boolean, select, link. Forbidden kinds: image, reference, list, formOverrides. See ARCHITECTURE.md \xA76.5.`
293
+ );
294
+ this.name = "InvalidFormFieldError";
295
+ this.formName = formName;
296
+ this.fieldName = fieldName;
297
+ this.fieldKind = fieldKind;
298
+ }
299
+ };
300
+ var HoneypotCollisionError = class extends Error {
301
+ formName;
302
+ fieldName;
303
+ reason;
304
+ constructor(formName, fieldName, reason = "collision") {
305
+ super(
306
+ reason === "empty" ? `Form "${formName}": honeypot name must be a non-empty string.` : `Form "${formName}": honeypot name "${fieldName}" collides with a real field. Choose a honeypot name that does not appear in the schema.`
307
+ );
308
+ this.name = "HoneypotCollisionError";
309
+ this.formName = formName;
310
+ this.fieldName = fieldName;
311
+ this.reason = reason;
312
+ }
313
+ };
314
+ var InvalidFormNameError = class extends Error {
315
+ constructor(name) {
316
+ super(
317
+ `Invalid form name ${JSON.stringify(name)}. Use letters, digits, hyphen, and underscore only (no path separators).`
318
+ );
319
+ this.name = "InvalidFormNameError";
320
+ }
321
+ };
322
+ var FORM_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
323
+ function defineForm(input) {
324
+ if (typeof input.name !== "string" || !FORM_NAME_PATTERN.test(input.name)) {
325
+ throw new InvalidFormNameError(input.name);
326
+ }
327
+ for (const [fieldName, descriptor] of Object.entries(input.schema)) {
328
+ const kind = descriptor.kind;
329
+ if (FORM_FORBIDDEN_KINDS.has(kind)) {
330
+ throw new InvalidFormFieldError(input.name, fieldName, kind);
331
+ }
332
+ }
333
+ if (input.honeypot !== void 0) {
334
+ if (typeof input.honeypot !== "string" || input.honeypot === "") {
335
+ throw new HoneypotCollisionError(input.name, input.honeypot, "empty");
336
+ }
337
+ if (Object.prototype.hasOwnProperty.call(input.schema, input.honeypot)) {
338
+ throw new HoneypotCollisionError(input.name, input.honeypot, "collision");
339
+ }
340
+ }
341
+ return input.honeypot !== void 0 ? { name: input.name, schema: input.schema, honeypot: input.honeypot } : { name: input.name, schema: input.schema };
342
+ }
343
+ // Annotate the CommonJS export names for ESM import in node:
344
+ 0 && (module.exports = {
345
+ BooleanField,
346
+ ButtonField,
347
+ FormOverridesField,
348
+ HoneypotCollisionError,
349
+ ImageField,
350
+ InvalidFormFieldError,
351
+ InvalidFormNameError,
352
+ LinkField,
353
+ ListField,
354
+ NumberField,
355
+ ReferenceField,
356
+ RichTextField,
357
+ SelectField,
358
+ SubmissionsNotReadableError,
359
+ TextField,
360
+ VideoField,
361
+ defineForm,
362
+ defineSection,
363
+ hasUniqueSectionIds,
364
+ hrefOf,
365
+ isExternalLink,
366
+ linkAnchorAttrs,
367
+ normalizeLinkValue,
368
+ validateEmail,
369
+ validateExternalUrl,
370
+ validateInternalSlug,
371
+ validatePhone
372
+ });
@@ -0,0 +1,133 @@
1
+ import { P as Page, h as LinkValue } from './form-BqY0H1V5.cjs';
2
+ export { A as AnyFormDefinition, j as BooleanField, B as ButtonField, i as ButtonValue, c as FieldDescriptor, y as FieldKind, x as FieldValueFor, b as FormDefinition, p as FormFieldDescriptor, q as FormFieldOverride, n as FormFieldOverrides, r as FormOverridesField, m as FormOverridesFieldDescriptor, a as FormSchema, I as ImageField, d as ImageValue, L as LinkField, l as ListField, z as ListItem, N as NumberField, C as PageSeo, w as PageSummary, f as ReferenceField, g as ReferenceValue, R as RichTextField, D as Section, S as SectionSchema, k as SelectField, E as SelectOption, s as Submission, t as SubmissionAdapterInfo, o as SubmissionStorageAdapter, u as SubmissionSummary, v as SubmissionsNotReadableError, T as TextField, V as VideoField, e as VideoValue } from './form-BqY0H1V5.cjs';
3
+ export { G as Global } from './global-CV23g5Bn.cjs';
4
+ export { A as AnySectionDefinition, D as DataOf, a as DefineSectionInput, E as EditableSlot, F as FieldDataType, S as SectionComponent, b as SectionDefinition, c as SlotItem, d as defineSection } from './defineSection-9qQ5ulAH.cjs';
5
+ export { D as DefineFormInput, H as HoneypotCollisionError, I as InvalidFormFieldError, a as InvalidFormNameError, d as defineForm } from './defineForm-CJ8KZC93.cjs';
6
+
7
+ /**
8
+ * Returns `true` when every `section.id` in the page appears exactly once.
9
+ * Empty `sections` arrays are trivially unique.
10
+ */
11
+ declare function hasUniqueSectionIds(page: Page): boolean;
12
+
13
+ /**
14
+ * Validate the slug stored on an internal `LinkValue`.
15
+ *
16
+ * Returns `null` when valid (including the empty string, which the
17
+ * editor renders as "not yet selected"), an error message otherwise.
18
+ */
19
+ declare const validateInternalSlug: (slug: string) => string | null;
20
+ /**
21
+ * Validate the URL stored on an external `LinkValue`.
22
+ *
23
+ * Returns `null` when valid (including the empty string, which the
24
+ * editor renders as "not yet entered"), an error message otherwise.
25
+ *
26
+ * Only absolute http(s) URLs pass. Site-relative paths belong to the
27
+ * internal branch — if the author types `/about`, they should switch
28
+ * the segmented control to "Internal" and enter `about` instead.
29
+ */
30
+ declare const validateExternalUrl: (url: string) => string | null;
31
+ /**
32
+ * Validate the email stored on an `email` `LinkValue`.
33
+ *
34
+ * Returns `null` when valid (including the empty string, which the
35
+ * editor renders as "not yet entered"), an error message otherwise.
36
+ */
37
+ declare const validateEmail: (email: string) => string | null;
38
+ /**
39
+ * Validate the phone string stored on a `phone` `LinkValue`.
40
+ *
41
+ * Returns `null` when valid (including the empty string, which the
42
+ * editor renders as "not yet entered"). Non-empty input must contain
43
+ * at least seven digits after stripping every character that is
44
+ * neither a digit nor a leading `+`. Country-code structure is not
45
+ * validated — it's overkill for v1 and would reject authored strings
46
+ * that real-world dialler apps handle fine.
47
+ */
48
+ declare const validatePhone: (phone: string) => string | null;
49
+
50
+ /**
51
+ * Resolve a `LinkValue` to the href string a section component should
52
+ * render in an `<a>` element.
53
+ *
54
+ * Internal slugs route through the catch-all (`app/[[...slug]]/page.tsx`)
55
+ * so they always live under the site root:
56
+ * - empty slug or `'home'` → `'/'`
57
+ * - `'about'` → `'/about'`
58
+ * - `'/blog'` → `'/blog'` (defensive: strip leading `/`)
59
+ * - `'blog/welcome'` → `'/blog/welcome'`
60
+ *
61
+ * External links return their URL verbatim; the validator already
62
+ * enforces the http(s) protocol allow-list, so we don't re-check here.
63
+ *
64
+ * Email links return `mailto:<email>` when the email is non-empty, or
65
+ * `''` when empty — the empty href lets a section component skip the
66
+ * anchor entirely when an author has not yet filled the field.
67
+ *
68
+ * Phone links return `tel:<digits>` with non-`[\d+]` characters
69
+ * stripped from the URI (so parens, spaces, and hyphens that authors
70
+ * type for legibility are removed before emitting `tel:`). The
71
+ * original formatted string stays in `link.phone` for display
72
+ * purposes. Empty phone → `''`, same rationale as email.
73
+ */
74
+ declare function hrefOf(link: LinkValue): string;
75
+ /**
76
+ * True iff the link points outside the current site.
77
+ *
78
+ * Only the `external` branch returns true. `email` and `phone` fire
79
+ * native handlers (mail client, dialler) — they do not navigate to a
80
+ * different site, so they are NOT considered "external" for the
81
+ * purpose of `target='_blank'` / `rel='noreferrer'`. Use
82
+ * `linkAnchorAttrs` to pick the right anchor attributes from a
83
+ * `LinkValue`.
84
+ */
85
+ declare function isExternalLink(link: LinkValue): boolean;
86
+ /**
87
+ * Canonical helper for translating a `LinkValue` into anchor
88
+ * attributes. Returns the href every link should carry, plus the
89
+ * `target='_blank'` / `rel='noreferrer'` pair only for the `external`
90
+ * branch.
91
+ *
92
+ * Why a single helper instead of letting consumers compose `hrefOf` +
93
+ * `isExternalLink` themselves: the contract for "open in a new tab"
94
+ * has gotten more nuanced as the discriminated union grew. Email and
95
+ * phone hand off to native OS handlers and would behave incorrectly
96
+ * with `target='_blank'` (Safari opens a blank tab that lingers after
97
+ * the mailto handler resolves). Centralising the decision here means
98
+ * marketing-site primitives, custom CTA buttons, navigation menus,
99
+ * and any future link-rendering surface make the same choice.
100
+ *
101
+ * Section authors should prefer:
102
+ * const attrs = linkAnchorAttrs(link)
103
+ * <a {...attrs}>{link.label}</a>
104
+ * over manually computing `target` / `rel`.
105
+ */
106
+ declare function linkAnchorAttrs(link: LinkValue): {
107
+ href: string;
108
+ target?: '_blank';
109
+ rel?: 'noreferrer';
110
+ };
111
+ /**
112
+ * Project an arbitrary value into the canonical `LinkValue` shape.
113
+ *
114
+ * Three input cases:
115
+ * 1. Old-shape `{ href, label, external? }`: infer `type` from the
116
+ * `href` protocol — `mailto:` → email, `tel:` → phone, `http(s):`
117
+ * → external, otherwise internal. For the internal branch we
118
+ * strip the leading `/` so the stored slug matches the page-id
119
+ * model (no leading slash, see `hrefOf`).
120
+ * 2. New-shape `{ type: 'internal' | 'external' | 'email' | 'phone',
121
+ * … }`: returned as-is, narrowed.
122
+ * 3. Anything else (null, primitives, garbage objects): collapse to
123
+ * a blank internal link so the renderer can still emit something
124
+ * and the editor can re-author from a known-good baseline.
125
+ *
126
+ * The function is intentionally lenient: it never throws. A throwing
127
+ * normaliser would push error-handling onto every section component
128
+ * for the rare case of legacy data, defeating the point of the
129
+ * defensive narrowing.
130
+ */
131
+ declare function normalizeLinkValue(raw: unknown): LinkValue;
132
+
133
+ export { LinkValue, Page, hasUniqueSectionIds, hrefOf, isExternalLink, linkAnchorAttrs, normalizeLinkValue, validateEmail, validateExternalUrl, validateInternalSlug, validatePhone };
@@ -0,0 +1,133 @@
1
+ import { P as Page, h as LinkValue } from './form-BqY0H1V5.js';
2
+ export { A as AnyFormDefinition, j as BooleanField, B as ButtonField, i as ButtonValue, c as FieldDescriptor, y as FieldKind, x as FieldValueFor, b as FormDefinition, p as FormFieldDescriptor, q as FormFieldOverride, n as FormFieldOverrides, r as FormOverridesField, m as FormOverridesFieldDescriptor, a as FormSchema, I as ImageField, d as ImageValue, L as LinkField, l as ListField, z as ListItem, N as NumberField, C as PageSeo, w as PageSummary, f as ReferenceField, g as ReferenceValue, R as RichTextField, D as Section, S as SectionSchema, k as SelectField, E as SelectOption, s as Submission, t as SubmissionAdapterInfo, o as SubmissionStorageAdapter, u as SubmissionSummary, v as SubmissionsNotReadableError, T as TextField, V as VideoField, e as VideoValue } from './form-BqY0H1V5.js';
3
+ export { G as Global } from './global-CV23g5Bn.js';
4
+ export { A as AnySectionDefinition, D as DataOf, a as DefineSectionInput, E as EditableSlot, F as FieldDataType, S as SectionComponent, b as SectionDefinition, c as SlotItem, d as defineSection } from './defineSection-Kr0pWqMY.js';
5
+ export { D as DefineFormInput, H as HoneypotCollisionError, I as InvalidFormFieldError, a as InvalidFormNameError, d as defineForm } from './defineForm-Bp9vzW56.js';
6
+
7
+ /**
8
+ * Returns `true` when every `section.id` in the page appears exactly once.
9
+ * Empty `sections` arrays are trivially unique.
10
+ */
11
+ declare function hasUniqueSectionIds(page: Page): boolean;
12
+
13
+ /**
14
+ * Validate the slug stored on an internal `LinkValue`.
15
+ *
16
+ * Returns `null` when valid (including the empty string, which the
17
+ * editor renders as "not yet selected"), an error message otherwise.
18
+ */
19
+ declare const validateInternalSlug: (slug: string) => string | null;
20
+ /**
21
+ * Validate the URL stored on an external `LinkValue`.
22
+ *
23
+ * Returns `null` when valid (including the empty string, which the
24
+ * editor renders as "not yet entered"), an error message otherwise.
25
+ *
26
+ * Only absolute http(s) URLs pass. Site-relative paths belong to the
27
+ * internal branch — if the author types `/about`, they should switch
28
+ * the segmented control to "Internal" and enter `about` instead.
29
+ */
30
+ declare const validateExternalUrl: (url: string) => string | null;
31
+ /**
32
+ * Validate the email stored on an `email` `LinkValue`.
33
+ *
34
+ * Returns `null` when valid (including the empty string, which the
35
+ * editor renders as "not yet entered"), an error message otherwise.
36
+ */
37
+ declare const validateEmail: (email: string) => string | null;
38
+ /**
39
+ * Validate the phone string stored on a `phone` `LinkValue`.
40
+ *
41
+ * Returns `null` when valid (including the empty string, which the
42
+ * editor renders as "not yet entered"). Non-empty input must contain
43
+ * at least seven digits after stripping every character that is
44
+ * neither a digit nor a leading `+`. Country-code structure is not
45
+ * validated — it's overkill for v1 and would reject authored strings
46
+ * that real-world dialler apps handle fine.
47
+ */
48
+ declare const validatePhone: (phone: string) => string | null;
49
+
50
+ /**
51
+ * Resolve a `LinkValue` to the href string a section component should
52
+ * render in an `<a>` element.
53
+ *
54
+ * Internal slugs route through the catch-all (`app/[[...slug]]/page.tsx`)
55
+ * so they always live under the site root:
56
+ * - empty slug or `'home'` → `'/'`
57
+ * - `'about'` → `'/about'`
58
+ * - `'/blog'` → `'/blog'` (defensive: strip leading `/`)
59
+ * - `'blog/welcome'` → `'/blog/welcome'`
60
+ *
61
+ * External links return their URL verbatim; the validator already
62
+ * enforces the http(s) protocol allow-list, so we don't re-check here.
63
+ *
64
+ * Email links return `mailto:<email>` when the email is non-empty, or
65
+ * `''` when empty — the empty href lets a section component skip the
66
+ * anchor entirely when an author has not yet filled the field.
67
+ *
68
+ * Phone links return `tel:<digits>` with non-`[\d+]` characters
69
+ * stripped from the URI (so parens, spaces, and hyphens that authors
70
+ * type for legibility are removed before emitting `tel:`). The
71
+ * original formatted string stays in `link.phone` for display
72
+ * purposes. Empty phone → `''`, same rationale as email.
73
+ */
74
+ declare function hrefOf(link: LinkValue): string;
75
+ /**
76
+ * True iff the link points outside the current site.
77
+ *
78
+ * Only the `external` branch returns true. `email` and `phone` fire
79
+ * native handlers (mail client, dialler) — they do not navigate to a
80
+ * different site, so they are NOT considered "external" for the
81
+ * purpose of `target='_blank'` / `rel='noreferrer'`. Use
82
+ * `linkAnchorAttrs` to pick the right anchor attributes from a
83
+ * `LinkValue`.
84
+ */
85
+ declare function isExternalLink(link: LinkValue): boolean;
86
+ /**
87
+ * Canonical helper for translating a `LinkValue` into anchor
88
+ * attributes. Returns the href every link should carry, plus the
89
+ * `target='_blank'` / `rel='noreferrer'` pair only for the `external`
90
+ * branch.
91
+ *
92
+ * Why a single helper instead of letting consumers compose `hrefOf` +
93
+ * `isExternalLink` themselves: the contract for "open in a new tab"
94
+ * has gotten more nuanced as the discriminated union grew. Email and
95
+ * phone hand off to native OS handlers and would behave incorrectly
96
+ * with `target='_blank'` (Safari opens a blank tab that lingers after
97
+ * the mailto handler resolves). Centralising the decision here means
98
+ * marketing-site primitives, custom CTA buttons, navigation menus,
99
+ * and any future link-rendering surface make the same choice.
100
+ *
101
+ * Section authors should prefer:
102
+ * const attrs = linkAnchorAttrs(link)
103
+ * <a {...attrs}>{link.label}</a>
104
+ * over manually computing `target` / `rel`.
105
+ */
106
+ declare function linkAnchorAttrs(link: LinkValue): {
107
+ href: string;
108
+ target?: '_blank';
109
+ rel?: 'noreferrer';
110
+ };
111
+ /**
112
+ * Project an arbitrary value into the canonical `LinkValue` shape.
113
+ *
114
+ * Three input cases:
115
+ * 1. Old-shape `{ href, label, external? }`: infer `type` from the
116
+ * `href` protocol — `mailto:` → email, `tel:` → phone, `http(s):`
117
+ * → external, otherwise internal. For the internal branch we
118
+ * strip the leading `/` so the stored slug matches the page-id
119
+ * model (no leading slash, see `hrefOf`).
120
+ * 2. New-shape `{ type: 'internal' | 'external' | 'email' | 'phone',
121
+ * … }`: returned as-is, narrowed.
122
+ * 3. Anything else (null, primitives, garbage objects): collapse to
123
+ * a blank internal link so the renderer can still emit something
124
+ * and the editor can re-author from a known-good baseline.
125
+ *
126
+ * The function is intentionally lenient: it never throws. A throwing
127
+ * normaliser would push error-handling onto every section component
128
+ * for the rare case of legacy data, defeating the point of the
129
+ * defensive narrowing.
130
+ */
131
+ declare function normalizeLinkValue(raw: unknown): LinkValue;
132
+
133
+ export { LinkValue, Page, hasUniqueSectionIds, hrefOf, isExternalLink, linkAnchorAttrs, normalizeLinkValue, validateEmail, validateExternalUrl, validateInternalSlug, validatePhone };