@duffcloudservices/site-forms 0.1.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/README.md +213 -0
- package/dist/DcsForm.vue.d.ts +67 -0
- package/dist/composables/useDcsForm.d.ts +36 -0
- package/dist/composables/useFormSubmission.d.ts +18 -0
- package/dist/composables/useFormValidation.d.ts +19 -0
- package/dist/fields/DcsFormCheckbox.vue.d.ts +12 -0
- package/dist/fields/DcsFormCheckboxGroup.vue.d.ts +12 -0
- package/dist/fields/DcsFormDate.vue.d.ts +14 -0
- package/dist/fields/DcsFormFieldWrapper.vue.d.ts +27 -0
- package/dist/fields/DcsFormFile.vue.d.ts +12 -0
- package/dist/fields/DcsFormHidden.vue.d.ts +7 -0
- package/dist/fields/DcsFormHtmlBlock.vue.d.ts +6 -0
- package/dist/fields/DcsFormRadio.vue.d.ts +12 -0
- package/dist/fields/DcsFormSection.vue.d.ts +21 -0
- package/dist/fields/DcsFormSelect.vue.d.ts +35 -0
- package/dist/fields/DcsFormText.vue.d.ts +34 -0
- package/dist/fields/DcsFormTextarea.vue.d.ts +34 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +918 -0
- package/dist/index.js.map +1 -0
- package/dist/loaders/yaml.d.ts +12 -0
- package/dist/schema/validate.d.ts +9 -0
- package/dist/types.d.ts +106 -0
- package/package.json +73 -0
- package/src/DcsForm.vue +299 -0
- package/src/__tests__/fields.test.ts +82 -0
- package/src/__tests__/multi-step.test.ts +46 -0
- package/src/__tests__/schema.test.ts +42 -0
- package/src/__tests__/submission.test.ts +77 -0
- package/src/__tests__/visible-if.test.ts +111 -0
- package/src/composables/useDcsForm.ts +201 -0
- package/src/composables/useFormSubmission.ts +113 -0
- package/src/composables/useFormValidation.ts +127 -0
- package/src/fields/DcsFormCheckbox.vue +35 -0
- package/src/fields/DcsFormCheckboxGroup.vue +52 -0
- package/src/fields/DcsFormDate.vue +34 -0
- package/src/fields/DcsFormFieldWrapper.vue +39 -0
- package/src/fields/DcsFormFile.vue +38 -0
- package/src/fields/DcsFormHidden.vue +17 -0
- package/src/fields/DcsFormHtmlBlock.vue +19 -0
- package/src/fields/DcsFormRadio.vue +45 -0
- package/src/fields/DcsFormSection.vue +19 -0
- package/src/fields/DcsFormSelect.vue +62 -0
- package/src/fields/DcsFormText.vue +54 -0
- package/src/fields/DcsFormTextarea.vue +43 -0
- package/src/index.ts +51 -0
- package/src/loaders/yaml.ts +51 -0
- package/src/schema/form-definition.schema.json +633 -0
- package/src/schema/validate.ts +58 -0
- package/src/shims.d.ts +10 -0
- package/src/types.ts +140 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,918 @@
|
|
|
1
|
+
import { reactive as X, ref as D, computed as p, defineComponent as I, createElementBlock as f, openBlock as l, normalizeClass as Z, renderSlot as S, createCommentVNode as k, createTextVNode as A, toDisplayString as b, createBlock as w, withCtx as V, createElementVNode as v, Fragment as T, renderList as B, watch as H, onMounted as ee, unref as u, resolveDynamicComponent as te } from "vue";
|
|
2
|
+
import ie from "ajv";
|
|
3
|
+
import re from "ajv-formats";
|
|
4
|
+
import K from "js-yaml";
|
|
5
|
+
function R(e, t) {
|
|
6
|
+
return e.visibleIf ? t[e.visibleIf.fieldId] === e.visibleIf.equals : !0;
|
|
7
|
+
}
|
|
8
|
+
function ne(e, t) {
|
|
9
|
+
if (e.type === "section-heading" || e.type === "html-block" || e.type === "hidden")
|
|
10
|
+
return;
|
|
11
|
+
const r = t == null || t === "" || Array.isArray(t) && t.length === 0;
|
|
12
|
+
if (e.required && r)
|
|
13
|
+
return `${e.label} is required`;
|
|
14
|
+
if (r) return;
|
|
15
|
+
if (e.type === "email" && typeof t == "string" && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(t))
|
|
16
|
+
return `${e.label} must be a valid email address`;
|
|
17
|
+
const i = e.validation;
|
|
18
|
+
if (i) {
|
|
19
|
+
if (typeof t == "string") {
|
|
20
|
+
if (i.minLength != null && t.length < i.minLength)
|
|
21
|
+
return `${e.label} must be at least ${i.minLength} characters`;
|
|
22
|
+
if (i.maxLength != null && t.length > i.maxLength)
|
|
23
|
+
return `${e.label} must be at most ${i.maxLength} characters`;
|
|
24
|
+
if (i.regex)
|
|
25
|
+
try {
|
|
26
|
+
if (!new RegExp(i.regex).test(t))
|
|
27
|
+
return `${e.label} is invalid`;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
if (typeof t == "number") {
|
|
32
|
+
if (i.min != null && t < i.min)
|
|
33
|
+
return `${e.label} must be at least ${i.min}`;
|
|
34
|
+
if (i.max != null && t > i.max)
|
|
35
|
+
return `${e.label} must be at most ${i.max}`;
|
|
36
|
+
}
|
|
37
|
+
if (e.type === "file" && i.accept && i.accept.length > 0) {
|
|
38
|
+
const o = t;
|
|
39
|
+
if (o && typeof File < "u" && o instanceof File && !i.accept.some((s) => s.startsWith(".") ? o.name.toLowerCase().endsWith(s.toLowerCase()) : o.type === s))
|
|
40
|
+
return `${e.label} must be one of: ${i.accept.join(", ")}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function oe(e, t, r) {
|
|
45
|
+
const i = {}, o = r ? new Set(r) : null;
|
|
46
|
+
for (const d of e.fields) {
|
|
47
|
+
if (o && !o.has(d.id) || !R(d, t)) continue;
|
|
48
|
+
const s = ne(d, t[d.id]);
|
|
49
|
+
s && (i[d.id] = s);
|
|
50
|
+
}
|
|
51
|
+
return i;
|
|
52
|
+
}
|
|
53
|
+
function se(e) {
|
|
54
|
+
return Object.values(e).some((t) => !!t);
|
|
55
|
+
}
|
|
56
|
+
function _(e) {
|
|
57
|
+
const t = {};
|
|
58
|
+
for (const r of e.fields)
|
|
59
|
+
r.defaultValue !== void 0 ? t[r.id] = r.defaultValue : r.type === "checkbox" ? t[r.id] = !1 : r.type === "multiselect" || r.type === "checkbox-group" ? t[r.id] = [] : t[r.id] = "";
|
|
60
|
+
return t;
|
|
61
|
+
}
|
|
62
|
+
function ae(e) {
|
|
63
|
+
const { definition: t } = e, r = X(_(t)), i = D({}), o = D({}), d = D(!1), s = D(!1), c = D(null), a = D(0), n = p(() => t.fields), m = p(
|
|
64
|
+
() => t.steps && t.steps.length > 0 ? t.steps : null
|
|
65
|
+
), g = p(() => {
|
|
66
|
+
const h = m.value;
|
|
67
|
+
return h ? h[a.value] ?? null : null;
|
|
68
|
+
}), x = p(() => {
|
|
69
|
+
const h = g.value;
|
|
70
|
+
if (!h) return t.fields;
|
|
71
|
+
const F = new Set(h.fieldIds);
|
|
72
|
+
return t.fields.filter((E) => F.has(E.id));
|
|
73
|
+
}), P = p(() => a.value === 0), O = p(() => {
|
|
74
|
+
const h = m.value;
|
|
75
|
+
return !h || a.value >= h.length - 1;
|
|
76
|
+
}), z = p(
|
|
77
|
+
() => x.value.filter((h) => R(h, r))
|
|
78
|
+
);
|
|
79
|
+
function y(h, F) {
|
|
80
|
+
r[h] = F, i.value[h] && (i.value = { ...i.value, [h]: void 0 });
|
|
81
|
+
}
|
|
82
|
+
function C(h) {
|
|
83
|
+
o.value = { ...o.value, [h]: !0 };
|
|
84
|
+
}
|
|
85
|
+
function $(h) {
|
|
86
|
+
const F = oe(t, r, h);
|
|
87
|
+
if (h) {
|
|
88
|
+
const E = { ...i.value };
|
|
89
|
+
for (const W of h)
|
|
90
|
+
E[W] = F[W];
|
|
91
|
+
i.value = E;
|
|
92
|
+
} else
|
|
93
|
+
i.value = F;
|
|
94
|
+
return !se(i.value);
|
|
95
|
+
}
|
|
96
|
+
function L() {
|
|
97
|
+
const h = g.value?.fieldIds;
|
|
98
|
+
return $(h);
|
|
99
|
+
}
|
|
100
|
+
function j() {
|
|
101
|
+
return $();
|
|
102
|
+
}
|
|
103
|
+
function J() {
|
|
104
|
+
return !m.value || !L() ? !1 : a.value < m.value.length - 1 ? (a.value++, !0) : !1;
|
|
105
|
+
}
|
|
106
|
+
function G() {
|
|
107
|
+
a.value > 0 && a.value--;
|
|
108
|
+
}
|
|
109
|
+
function Y() {
|
|
110
|
+
const h = _(t);
|
|
111
|
+
for (const F of Object.keys(r))
|
|
112
|
+
delete r[F];
|
|
113
|
+
Object.assign(r, h), i.value = {}, o.value = {}, d.value = !1, s.value = !1, c.value = null, a.value = 0;
|
|
114
|
+
}
|
|
115
|
+
function Q() {
|
|
116
|
+
const h = {};
|
|
117
|
+
for (const F of t.fields)
|
|
118
|
+
F.type === "section-heading" || F.type === "html-block" || R(F, r) && (h[F.id] = r[F.id]);
|
|
119
|
+
return h;
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
values: r,
|
|
123
|
+
errors: i,
|
|
124
|
+
touched: o,
|
|
125
|
+
submitting: d,
|
|
126
|
+
submitted: s,
|
|
127
|
+
submitError: c,
|
|
128
|
+
fields: n,
|
|
129
|
+
steps: m,
|
|
130
|
+
currentStepIndex: a,
|
|
131
|
+
currentStep: g,
|
|
132
|
+
currentStepFields: x,
|
|
133
|
+
isFirstStep: P,
|
|
134
|
+
isLastStep: O,
|
|
135
|
+
visibleFields: z,
|
|
136
|
+
setValue: y,
|
|
137
|
+
touch: C,
|
|
138
|
+
validateCurrentScope: L,
|
|
139
|
+
validateAll: j,
|
|
140
|
+
next: J,
|
|
141
|
+
prev: G,
|
|
142
|
+
reset: Y,
|
|
143
|
+
collectSubmissionValues: Q
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
async function le(e) {
|
|
147
|
+
const { apiBase: t, siteSlug: r, payload: i } = e, o = e.retries ?? 1, d = e.fetchImpl ?? fetch, s = `${t.replace(/\/$/, "")}/public/sites/${encodeURIComponent(
|
|
148
|
+
r
|
|
149
|
+
)}/form-submissions`, c = Object.values(i.values).some(
|
|
150
|
+
(m) => typeof File < "u" && m instanceof File
|
|
151
|
+
);
|
|
152
|
+
let a, n;
|
|
153
|
+
for (let m = 0; m <= o; m++)
|
|
154
|
+
try {
|
|
155
|
+
const g = c ? { method: "POST", body: de(i) } : {
|
|
156
|
+
method: "POST",
|
|
157
|
+
headers: { "Content-Type": "application/json" },
|
|
158
|
+
body: JSON.stringify(i)
|
|
159
|
+
}, x = await d(s, g);
|
|
160
|
+
if (n = x.status, x.ok) {
|
|
161
|
+
const P = await ue(x);
|
|
162
|
+
return { payload: i, response: P };
|
|
163
|
+
}
|
|
164
|
+
if (x.status < 500) {
|
|
165
|
+
const P = await ce(x);
|
|
166
|
+
throw {
|
|
167
|
+
payload: i,
|
|
168
|
+
status: x.status,
|
|
169
|
+
error: new Error(`Submission failed (${x.status}): ${P}`)
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
a = new Error(`Server error ${x.status}`);
|
|
173
|
+
} catch (g) {
|
|
174
|
+
if (g && typeof g == "object" && "payload" in g && "error" in g)
|
|
175
|
+
throw g;
|
|
176
|
+
a = g;
|
|
177
|
+
}
|
|
178
|
+
throw {
|
|
179
|
+
payload: i,
|
|
180
|
+
status: n,
|
|
181
|
+
error: a ?? new Error("Submission failed")
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function de(e) {
|
|
185
|
+
const t = new FormData();
|
|
186
|
+
t.append("formId", e.formId), e.captchaToken && t.append("captchaToken", e.captchaToken);
|
|
187
|
+
for (const [r, i] of Object.entries(e.values))
|
|
188
|
+
if (i != null)
|
|
189
|
+
if (i instanceof File)
|
|
190
|
+
t.append(`values[${r}]`, i);
|
|
191
|
+
else if (Array.isArray(i))
|
|
192
|
+
for (const o of i) t.append(`values[${r}][]`, String(o));
|
|
193
|
+
else
|
|
194
|
+
t.append(`values[${r}]`, String(i));
|
|
195
|
+
return t;
|
|
196
|
+
}
|
|
197
|
+
async function ue(e) {
|
|
198
|
+
try {
|
|
199
|
+
return await e.json();
|
|
200
|
+
} catch {
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async function ce(e) {
|
|
205
|
+
try {
|
|
206
|
+
return await e.text();
|
|
207
|
+
} catch {
|
|
208
|
+
return "";
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const me = "http://json-schema.org/draft-07/schema#", fe = "https://duffcloudservices.com/schemas/form-definition.schema.json", pe = "PortalFormDefinition", he = "object", ve = "Portable form definition. This schema is the source of truth for\nboth the portal CRUD APIs and the `.dcs/forms/<formId>.yaml`\nfiles consumed by customer-site builds via `<DcsForm/>`.\n", be = ["formId", "title", "submission", "fields"], ge = { formId: { type: "string", description: "Kebab-case identifier, unique per site.", pattern: "^[a-z0-9][a-z0-9-]*$", minLength: 1, maxLength: 80, example: "contact" }, title: { type: "string", description: "Human-readable form title shown in the portal and rendered as the form heading.", minLength: 1, maxLength: 200, example: "Contact Us" }, description: { type: "string", description: "Optional descriptive text rendered above the fields.", maxLength: 2e3 }, submitLabel: { type: "string", description: 'Label for the submit button. Defaults to "Send" client-side.', maxLength: 80, example: "Send message" }, successMessage: { type: "string", description: "Confirmation message shown after a successful submission.", maxLength: 2e3, example: "Thanks — we'll be in touch shortly." }, submission: { $ref: "#/definitions/PortalFormSubmissionConfig" }, steps: { type: "array", description: "Optional multi-step grouping. When omitted, fields render as a single page.", items: { $ref: "#/definitions/PortalFormStep" } }, fields: { type: "array", description: "Flat list of all fields in the form. When `steps` is present,\nevery field referenced from a step's `fieldIds` must exist here.\n", items: { $ref: "#/definitions/PortalFormField" } }, version: { type: "integer", minimum: 1, description: "Monotonically increasing version, bumped on each save." }, createdAt: { type: "string", format: "date-time", description: "Form creation timestamp." }, updatedAt: { type: "string", format: "date-time", description: "Last modification timestamp." } }, ye = /* @__PURE__ */ JSON.parse('{"PortalFormFieldType":{"type":"string","description":"Field type controlling input rendering and validation. Includes\\nlayout-only types (`section-heading`, `html-block`) that render\\ndecorative content rather than capturing values, and `hidden`\\nfor prefilled values not displayed to the user.\\n","enum":["text","email","tel","textarea","select","multiselect","radio","checkbox","checkbox-group","date","file","hidden","section-heading","html-block"]},"PortalFormFieldWidth":{"type":"string","description":"Layout hint for the field within its row.","enum":["full","half","third"]},"PortalFormFieldOption":{"type":"object","description":"A single option for select / radio / checkbox-group fields.","required":["value","label"],"properties":{"value":{"type":"string","description":"Submitted value for this option."},"label":{"type":"string","description":"Human-readable label shown to the user."}}},"PortalFormFieldValidation":{"type":"object","description":"Optional validation rules applied to a field\'s value.","properties":{"regex":{"type":"string","description":"ECMAScript-compatible pattern the value must match."},"minLength":{"type":"integer","minimum":0,"description":"Minimum string length (text-like fields only)."},"maxLength":{"type":"integer","minimum":0,"description":"Maximum string length (text-like fields only)."},"min":{"type":"number","description":"Minimum numeric / date value."},"max":{"type":"number","description":"Maximum numeric / date value."},"accept":{"type":"array","description":"Accepted MIME types or file extensions for `file` fields.","items":{"type":"string"}}}},"PortalFormFieldVisibleIf":{"type":"object","description":"Single-predicate visibility rule. The field is shown only when\\nthe referenced sibling field\'s value equals `equals`. Future\\nrevisions may extend this with AND / OR composition; clients\\nshould treat unknown extra properties as opaque.\\n","required":["fieldId","equals"],"properties":{"fieldId":{"type":"string","description":"ID of the sibling field whose value gates visibility."},"equals":{"description":"Value (string, number, or boolean) the sibling field must equal.","oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"}]}}},"PortalFormField":{"type":"object","description":"A single field within a form definition.","required":["id","type","label"],"properties":{"id":{"type":"string","description":"Kebab-case identifier, unique within the form.","pattern":"^[a-z0-9][a-z0-9-]*$","minLength":1,"maxLength":80},"type":{"$ref":"#/definitions/PortalFormFieldType"},"label":{"type":"string","description":"Human-readable label rendered above the input.","minLength":1,"maxLength":200},"helpText":{"type":"string","description":"Optional helper / description text shown beneath the label.","maxLength":500},"placeholder":{"type":"string","description":"Optional placeholder shown inside empty inputs.","maxLength":200},"defaultValue":{"description":"Optional default value (string, number, boolean, or array of strings).","oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}}]},"required":{"type":"boolean","default":false,"description":"Whether the field must be supplied to submit the form."},"width":{"allOf":[{"$ref":"#/definitions/PortalFormFieldWidth"}],"default":"full"},"options":{"type":"array","description":"Options for `select`, `multiselect`, `radio`, `checkbox-group` fields.","items":{"$ref":"#/definitions/PortalFormFieldOption"}},"validation":{"$ref":"#/definitions/PortalFormFieldValidation"},"visibleIf":{"$ref":"#/definitions/PortalFormFieldVisibleIf"},"phi":{"type":"boolean","default":false,"description":"Marks the field as collecting Protected Health Information.\\nWhen true the value must never appear in notification emails\\nor logs and the owning site must be in the Medical category\\n(see `compliance.instructions.md`).\\n"},"html":{"type":"string","description":"Sanitized HTML body for `html-block` fields. Ignored for\\nother field types.\\n","maxLength":10000}}},"PortalFormStep":{"type":"object","description":"Optional grouping for multi-step (wizard-style) forms such as\\nthe KT Braun estate-planning questionnaires. When `steps` is\\nomitted on a form, all fields render as a single page.\\n","required":["id","title","fieldIds"],"properties":{"id":{"type":"string","description":"Kebab-case step identifier, unique within the form.","pattern":"^[a-z0-9][a-z0-9-]*$","minLength":1,"maxLength":80},"title":{"type":"string","description":"Step title shown in the wizard header.","minLength":1,"maxLength":200},"description":{"type":"string","description":"Optional descriptive text shown below the step title.","maxLength":1000},"fieldIds":{"type":"array","description":"Ordered list of field IDs that belong to this step.","items":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$"}}}},"PortalFormSubmissionLeadConfig":{"type":"object","required":["kind"],"description":"Routes submissions into the existing PortalLeads pipeline.","properties":{"kind":{"type":"string","enum":["lead"]},"category":{"type":"string","description":"Service category id used to route the lead."}}},"PortalFormSubmissionEmailConfig":{"type":"object","required":["kind","to"],"description":"Routes submissions as a notification email to the listed recipients.","properties":{"kind":{"type":"string","enum":["email"]},"to":{"type":"array","minItems":1,"items":{"type":"string","format":"email"},"description":"Recipient email addresses."},"subjectTemplate":{"type":"string","description":"Optional subject-line template. Use `{title}` to interpolate\\nthe form\'s title; values are intentionally not interpolated\\ninto the subject line for compliance reasons.\\n","maxLength":200}}},"PortalFormSubmissionWebhookConfig":{"type":"object","required":["kind","url"],"description":"Posts the submission JSON to a third-party webhook.","properties":{"kind":{"type":"string","enum":["webhook"]},"url":{"type":"string","format":"uri","description":"HTTPS endpoint receiving the signed POST."},"signingSecretRef":{"type":"string","description":"Name of the Key Vault secret used to HMAC-sign the request.\\nThe secret value is never returned in API responses.\\n"}}},"PortalFormSubmissionConfig":{"description":"Discriminated union of submission destinations. Exactly one of\\n`lead`, `email`, or `webhook` shapes is used based on `kind`.\\n","oneOf":[{"$ref":"#/definitions/PortalFormSubmissionLeadConfig"},{"$ref":"#/definitions/PortalFormSubmissionEmailConfig"},{"$ref":"#/definitions/PortalFormSubmissionWebhookConfig"}],"discriminator":{"propertyName":"kind","mapping":{"lead":"#/definitions/PortalFormSubmissionLeadConfig","email":"#/definitions/PortalFormSubmissionEmailConfig","webhook":"#/definitions/PortalFormSubmissionWebhookConfig"}}},"PortalFormSummary":{"type":"object","description":"List-row projection of a form definition.","required":["formId","title","fieldCount"],"properties":{"formId":{"type":"string","description":"Kebab-case identifier.","pattern":"^[a-z0-9][a-z0-9-]*$","example":"contact"},"title":{"type":"string","description":"Human-readable title.","example":"Contact Us"},"description":{"type":"string","description":"Optional descriptive text."},"fieldCount":{"type":"integer","minimum":0,"description":"Number of input-bearing fields in the form.","example":4},"attachedPageSlugs":{"type":"array","description":"Slugs of pages that currently reference this form via `formId`.","items":{"type":"string"}},"submissionKind":{"type":"string","enum":["lead","email","webhook"],"description":"Submission destination kind."},"lastUpdated":{"type":"string","format":"date-time","description":"Last modification timestamp."}}},"PortalCreateFormRequest":{"type":"object","description":"Payload for creating a new form definition.","required":["formId","title","submission","fields"],"properties":{"formId":{"type":"string","description":"Kebab-case identifier, unique per site.","pattern":"^[a-z0-9][a-z0-9-]*$","minLength":1,"maxLength":80},"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"submitLabel":{"type":"string","maxLength":80},"successMessage":{"type":"string","maxLength":2000},"submission":{"$ref":"#/definitions/PortalFormSubmissionConfig"},"steps":{"type":"array","items":{"$ref":"#/definitions/PortalFormStep"}},"fields":{"type":"array","items":{"$ref":"#/definitions/PortalFormField"}}}},"PortalUpdateFormRequest":{"type":"object","description":"Payload for updating an existing form definition. The `formId`\\nis taken from the URL; supplying it in the body is optional and,\\nif present, must match the path.\\n","properties":{"formId":{"type":"string","pattern":"^[a-z0-9][a-z0-9-]*$","minLength":1,"maxLength":80,"description":"Optional echo of the path formId; must match if provided."},"title":{"type":"string","minLength":1,"maxLength":200},"description":{"type":"string","maxLength":2000},"submitLabel":{"type":"string","maxLength":80},"successMessage":{"type":"string","maxLength":2000},"submission":{"$ref":"#/definitions/PortalFormSubmissionConfig"},"steps":{"type":"array","items":{"$ref":"#/definitions/PortalFormStep"}},"fields":{"type":"array","items":{"$ref":"#/definitions/PortalFormField"}},"expectedVersion":{"type":"integer","minimum":1,"description":"Optional optimistic-concurrency token. When supplied, the\\nupdate is rejected with 409 if the stored form\'s `version`\\nno longer matches.\\n"}}},"PortalDuplicateFormRequest":{"type":"object","description":"Payload for cloning an existing form under a new formId.","required":["formId"],"properties":{"formId":{"type":"string","description":"Target kebab-case formId for the cloned form.","pattern":"^[a-z0-9][a-z0-9-]*$","minLength":1,"maxLength":80},"title":{"type":"string","description":"Optional title override; defaults to `<source title> (copy)`.","minLength":1,"maxLength":200}}},"PortalFormResponse":{"type":"object","description":"Full form definition response.","required":["form"],"properties":{"form":{"$ref":"#/definitions/PortalFormDefinition"},"attachedPageSlugs":{"type":"array","description":"Slugs of pages that currently reference this form via `formId`.","items":{"type":"string"}}}},"PortalFormListResponse":{"type":"object","required":["forms","totalCount"],"properties":{"forms":{"type":"array","items":{"$ref":"#/definitions/PortalFormSummary"}},"totalCount":{"type":"integer","minimum":0,"description":"Total number of forms matching the query.","example":3}}}}'), $e = {
|
|
212
|
+
$schema: me,
|
|
213
|
+
$id: fe,
|
|
214
|
+
title: pe,
|
|
215
|
+
type: he,
|
|
216
|
+
description: ve,
|
|
217
|
+
required: be,
|
|
218
|
+
properties: ge,
|
|
219
|
+
definitions: ye
|
|
220
|
+
};
|
|
221
|
+
let M = null;
|
|
222
|
+
function Fe() {
|
|
223
|
+
if (M) return M;
|
|
224
|
+
const e = new ie({
|
|
225
|
+
allErrors: !0,
|
|
226
|
+
strict: !1,
|
|
227
|
+
discriminator: !1
|
|
228
|
+
});
|
|
229
|
+
return re(e), M = e.compile($e), M;
|
|
230
|
+
}
|
|
231
|
+
function ke(e) {
|
|
232
|
+
const t = Fe(), r = t(e);
|
|
233
|
+
return { valid: r, errors: r ? [] : t.errors ?? [] };
|
|
234
|
+
}
|
|
235
|
+
function xe(e, t, r) {
|
|
236
|
+
const i = ke(t);
|
|
237
|
+
return !i.valid && r && console.warn(
|
|
238
|
+
`[@duffcloudservices/site-forms] Form "${e}" failed schema validation:`,
|
|
239
|
+
i.errors
|
|
240
|
+
), i;
|
|
241
|
+
}
|
|
242
|
+
function Se(e) {
|
|
243
|
+
const t = {};
|
|
244
|
+
for (const [r, i] of Object.entries(e)) {
|
|
245
|
+
const o = Ie(r), d = Le(i);
|
|
246
|
+
d && (t[d.formId ?? o] = d);
|
|
247
|
+
}
|
|
248
|
+
return t;
|
|
249
|
+
}
|
|
250
|
+
function Ie(e) {
|
|
251
|
+
return (e.split("/").pop() ?? e).replace(/\.ya?ml$/i, "");
|
|
252
|
+
}
|
|
253
|
+
function Le(e) {
|
|
254
|
+
if (!e) return null;
|
|
255
|
+
if (typeof e == "string")
|
|
256
|
+
return K.load(e);
|
|
257
|
+
if (typeof e == "object") {
|
|
258
|
+
const t = e.default;
|
|
259
|
+
return t && typeof t == "object" ? t : e;
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
function It(e) {
|
|
264
|
+
return K.load(e);
|
|
265
|
+
}
|
|
266
|
+
const Ve = ["data-form-field-key"], we = ["for"], qe = {
|
|
267
|
+
key: 0,
|
|
268
|
+
"aria-hidden": "true",
|
|
269
|
+
class: "dcs-form-field__required"
|
|
270
|
+
}, Pe = {
|
|
271
|
+
key: 0,
|
|
272
|
+
class: "dcs-form-field__help"
|
|
273
|
+
}, Ce = {
|
|
274
|
+
key: 0,
|
|
275
|
+
class: "dcs-form-field__error",
|
|
276
|
+
role: "alert"
|
|
277
|
+
}, q = /* @__PURE__ */ I({
|
|
278
|
+
__name: "DcsFormFieldWrapper",
|
|
279
|
+
props: {
|
|
280
|
+
field: {},
|
|
281
|
+
error: {},
|
|
282
|
+
inputId: {}
|
|
283
|
+
},
|
|
284
|
+
setup(e) {
|
|
285
|
+
return (t, r) => (l(), f("div", {
|
|
286
|
+
class: Z(["dcs-form-field", [`dcs-form-field--${e.field.type}`, `dcs-form-field--width-${e.field.width ?? "full"}`]]),
|
|
287
|
+
"data-form-field-key": e.field.id
|
|
288
|
+
}, [
|
|
289
|
+
S(t.$slots, "label", {}, () => [
|
|
290
|
+
e.field.type !== "checkbox" && e.field.type !== "hidden" && e.field.type !== "section-heading" && e.field.type !== "html-block" ? (l(), f("label", {
|
|
291
|
+
key: 0,
|
|
292
|
+
for: e.inputId ?? `field-${e.field.id}`,
|
|
293
|
+
class: "dcs-form-field__label"
|
|
294
|
+
}, [
|
|
295
|
+
A(b(e.field.label) + " ", 1),
|
|
296
|
+
e.field.required ? (l(), f("span", qe, "*")) : k("", !0)
|
|
297
|
+
], 8, we)) : k("", !0)
|
|
298
|
+
]),
|
|
299
|
+
S(t.$slots, "default"),
|
|
300
|
+
S(t.$slots, "help", {}, () => [
|
|
301
|
+
e.field.helpText ? (l(), f("p", Pe, b(e.field.helpText), 1)) : k("", !0)
|
|
302
|
+
]),
|
|
303
|
+
S(t.$slots, "error", {}, () => [
|
|
304
|
+
e.error ? (l(), f("p", Ce, b(e.error), 1)) : k("", !0)
|
|
305
|
+
])
|
|
306
|
+
], 10, Ve));
|
|
307
|
+
}
|
|
308
|
+
}), De = ["id", "type", "name", "value", "placeholder", "required", "aria-invalid", "aria-describedby"], N = /* @__PURE__ */ I({
|
|
309
|
+
__name: "DcsFormText",
|
|
310
|
+
props: {
|
|
311
|
+
field: {},
|
|
312
|
+
modelValue: {},
|
|
313
|
+
error: {}
|
|
314
|
+
},
|
|
315
|
+
emits: ["update:modelValue", "blur"],
|
|
316
|
+
setup(e, { emit: t }) {
|
|
317
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`), d = p(() => {
|
|
318
|
+
switch (r.field.type) {
|
|
319
|
+
case "email":
|
|
320
|
+
return "email";
|
|
321
|
+
case "tel":
|
|
322
|
+
return "tel";
|
|
323
|
+
default:
|
|
324
|
+
return "text";
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
return (s, c) => (l(), w(q, {
|
|
328
|
+
field: e.field,
|
|
329
|
+
error: e.error,
|
|
330
|
+
"input-id": o.value
|
|
331
|
+
}, {
|
|
332
|
+
default: V(() => [
|
|
333
|
+
S(s.$slots, "input", {
|
|
334
|
+
id: o.value,
|
|
335
|
+
value: e.modelValue,
|
|
336
|
+
onInput: (a) => i("update:modelValue", a.target.value),
|
|
337
|
+
onBlur: () => i("blur")
|
|
338
|
+
}, () => [
|
|
339
|
+
v("input", {
|
|
340
|
+
id: o.value,
|
|
341
|
+
class: "dcs-form-input",
|
|
342
|
+
type: d.value,
|
|
343
|
+
name: e.field.id,
|
|
344
|
+
value: e.modelValue ?? "",
|
|
345
|
+
placeholder: e.field.placeholder,
|
|
346
|
+
required: e.field.required,
|
|
347
|
+
"aria-invalid": !!e.error,
|
|
348
|
+
"aria-describedby": e.error ? `${o.value}-error` : void 0,
|
|
349
|
+
onInput: c[0] || (c[0] = (a) => i("update:modelValue", a.target.value)),
|
|
350
|
+
onBlur: c[1] || (c[1] = (a) => i("blur"))
|
|
351
|
+
}, null, 40, De)
|
|
352
|
+
])
|
|
353
|
+
]),
|
|
354
|
+
_: 3
|
|
355
|
+
}, 8, ["field", "error", "input-id"]));
|
|
356
|
+
}
|
|
357
|
+
}), Te = ["id", "name", "placeholder", "required", "aria-invalid", "value"], Oe = /* @__PURE__ */ I({
|
|
358
|
+
__name: "DcsFormTextarea",
|
|
359
|
+
props: {
|
|
360
|
+
field: {},
|
|
361
|
+
modelValue: {},
|
|
362
|
+
error: {}
|
|
363
|
+
},
|
|
364
|
+
emits: ["update:modelValue", "blur"],
|
|
365
|
+
setup(e, { emit: t }) {
|
|
366
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`);
|
|
367
|
+
return (d, s) => (l(), w(q, {
|
|
368
|
+
field: e.field,
|
|
369
|
+
error: e.error,
|
|
370
|
+
"input-id": o.value
|
|
371
|
+
}, {
|
|
372
|
+
default: V(() => [
|
|
373
|
+
S(d.$slots, "input", {
|
|
374
|
+
id: o.value,
|
|
375
|
+
value: e.modelValue,
|
|
376
|
+
onInput: (c) => i("update:modelValue", c.target.value),
|
|
377
|
+
onBlur: () => i("blur")
|
|
378
|
+
}, () => [
|
|
379
|
+
v("textarea", {
|
|
380
|
+
id: o.value,
|
|
381
|
+
class: "dcs-form-input dcs-form-textarea",
|
|
382
|
+
name: e.field.id,
|
|
383
|
+
placeholder: e.field.placeholder,
|
|
384
|
+
required: e.field.required,
|
|
385
|
+
"aria-invalid": !!e.error,
|
|
386
|
+
rows: "5",
|
|
387
|
+
value: e.modelValue ?? "",
|
|
388
|
+
onInput: s[0] || (s[0] = (c) => i("update:modelValue", c.target.value)),
|
|
389
|
+
onBlur: s[1] || (s[1] = (c) => i("blur"))
|
|
390
|
+
}, null, 40, Te)
|
|
391
|
+
])
|
|
392
|
+
]),
|
|
393
|
+
_: 3
|
|
394
|
+
}, 8, ["field", "error", "input-id"]));
|
|
395
|
+
}
|
|
396
|
+
}), je = ["id", "name", "required", "multiple", "aria-invalid", "value"], Ee = {
|
|
397
|
+
key: 0,
|
|
398
|
+
value: "",
|
|
399
|
+
disabled: ""
|
|
400
|
+
}, Me = ["value"], Ae = /* @__PURE__ */ I({
|
|
401
|
+
__name: "DcsFormSelect",
|
|
402
|
+
props: {
|
|
403
|
+
field: {},
|
|
404
|
+
modelValue: {},
|
|
405
|
+
error: {}
|
|
406
|
+
},
|
|
407
|
+
emits: ["update:modelValue", "blur"],
|
|
408
|
+
setup(e, { emit: t }) {
|
|
409
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`), d = p(() => r.field.type === "multiselect"), s = p(() => r.field.options ?? []);
|
|
410
|
+
function c(a) {
|
|
411
|
+
const n = a.target;
|
|
412
|
+
if (d.value) {
|
|
413
|
+
const m = [];
|
|
414
|
+
for (const g of Array.from(n.selectedOptions)) m.push(g.value);
|
|
415
|
+
i("update:modelValue", m);
|
|
416
|
+
} else
|
|
417
|
+
i("update:modelValue", n.value);
|
|
418
|
+
}
|
|
419
|
+
return (a, n) => (l(), w(q, {
|
|
420
|
+
field: e.field,
|
|
421
|
+
error: e.error,
|
|
422
|
+
"input-id": o.value
|
|
423
|
+
}, {
|
|
424
|
+
default: V(() => [
|
|
425
|
+
S(a.$slots, "input", {
|
|
426
|
+
id: o.value,
|
|
427
|
+
value: e.modelValue,
|
|
428
|
+
options: s.value,
|
|
429
|
+
onChange: c
|
|
430
|
+
}, () => [
|
|
431
|
+
v("select", {
|
|
432
|
+
id: o.value,
|
|
433
|
+
class: "dcs-form-input dcs-form-select",
|
|
434
|
+
name: e.field.id,
|
|
435
|
+
required: e.field.required,
|
|
436
|
+
multiple: d.value,
|
|
437
|
+
"aria-invalid": !!e.error,
|
|
438
|
+
value: e.modelValue ?? (d.value ? [] : ""),
|
|
439
|
+
onChange: c,
|
|
440
|
+
onBlur: n[0] || (n[0] = (m) => i("blur"))
|
|
441
|
+
}, [
|
|
442
|
+
d.value ? k("", !0) : (l(), f("option", Ee, b(e.field.placeholder ?? "Select…"), 1)),
|
|
443
|
+
(l(!0), f(T, null, B(s.value, (m) => (l(), f("option", {
|
|
444
|
+
key: m.value,
|
|
445
|
+
value: m.value
|
|
446
|
+
}, b(m.label), 9, Me))), 128))
|
|
447
|
+
], 40, je)
|
|
448
|
+
])
|
|
449
|
+
]),
|
|
450
|
+
_: 3
|
|
451
|
+
}, 8, ["field", "error", "input-id"]));
|
|
452
|
+
}
|
|
453
|
+
}), Be = ["aria-required", "aria-invalid"], ze = { class: "sr-only" }, Re = ["name", "value", "checked", "onChange"], We = /* @__PURE__ */ I({
|
|
454
|
+
__name: "DcsFormRadio",
|
|
455
|
+
props: {
|
|
456
|
+
field: {},
|
|
457
|
+
modelValue: {},
|
|
458
|
+
error: {}
|
|
459
|
+
},
|
|
460
|
+
emits: ["update:modelValue"],
|
|
461
|
+
setup(e, { emit: t }) {
|
|
462
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`), d = p(() => r.field.options ?? []);
|
|
463
|
+
return (s, c) => (l(), w(q, {
|
|
464
|
+
field: e.field,
|
|
465
|
+
error: e.error,
|
|
466
|
+
"input-id": o.value
|
|
467
|
+
}, {
|
|
468
|
+
default: V(() => [
|
|
469
|
+
v("fieldset", {
|
|
470
|
+
class: "dcs-form-radio-group",
|
|
471
|
+
role: "radiogroup",
|
|
472
|
+
"aria-required": e.field.required,
|
|
473
|
+
"aria-invalid": !!e.error
|
|
474
|
+
}, [
|
|
475
|
+
v("legend", ze, b(e.field.label), 1),
|
|
476
|
+
(l(!0), f(T, null, B(d.value, (a) => (l(), f("label", {
|
|
477
|
+
key: a.value,
|
|
478
|
+
class: "dcs-form-radio"
|
|
479
|
+
}, [
|
|
480
|
+
v("input", {
|
|
481
|
+
type: "radio",
|
|
482
|
+
name: e.field.id,
|
|
483
|
+
value: a.value,
|
|
484
|
+
checked: e.modelValue === a.value,
|
|
485
|
+
onChange: (n) => i("update:modelValue", a.value)
|
|
486
|
+
}, null, 40, Re),
|
|
487
|
+
v("span", null, b(a.label), 1)
|
|
488
|
+
]))), 128))
|
|
489
|
+
], 8, Be)
|
|
490
|
+
]),
|
|
491
|
+
_: 1
|
|
492
|
+
}, 8, ["field", "error", "input-id"]));
|
|
493
|
+
}
|
|
494
|
+
}), He = ["aria-required", "aria-invalid"], _e = { class: "sr-only" }, Ne = ["name", "value", "checked", "onChange"], Ue = /* @__PURE__ */ I({
|
|
495
|
+
__name: "DcsFormCheckboxGroup",
|
|
496
|
+
props: {
|
|
497
|
+
field: {},
|
|
498
|
+
modelValue: {},
|
|
499
|
+
error: {}
|
|
500
|
+
},
|
|
501
|
+
emits: ["update:modelValue"],
|
|
502
|
+
setup(e, { emit: t }) {
|
|
503
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`), d = p(() => r.field.options ?? []), s = p(() => r.modelValue ?? []);
|
|
504
|
+
function c(a, n) {
|
|
505
|
+
const m = new Set(s.value);
|
|
506
|
+
n ? m.add(a) : m.delete(a), i("update:modelValue", Array.from(m));
|
|
507
|
+
}
|
|
508
|
+
return (a, n) => (l(), w(q, {
|
|
509
|
+
field: e.field,
|
|
510
|
+
error: e.error,
|
|
511
|
+
"input-id": o.value
|
|
512
|
+
}, {
|
|
513
|
+
default: V(() => [
|
|
514
|
+
v("fieldset", {
|
|
515
|
+
class: "dcs-form-checkbox-group",
|
|
516
|
+
"aria-required": e.field.required,
|
|
517
|
+
"aria-invalid": !!e.error
|
|
518
|
+
}, [
|
|
519
|
+
v("legend", _e, b(e.field.label), 1),
|
|
520
|
+
(l(!0), f(T, null, B(d.value, (m) => (l(), f("label", {
|
|
521
|
+
key: m.value,
|
|
522
|
+
class: "dcs-form-checkbox"
|
|
523
|
+
}, [
|
|
524
|
+
v("input", {
|
|
525
|
+
type: "checkbox",
|
|
526
|
+
name: `${e.field.id}[]`,
|
|
527
|
+
value: m.value,
|
|
528
|
+
checked: s.value.includes(m.value),
|
|
529
|
+
onChange: (g) => c(m.value, g.target.checked)
|
|
530
|
+
}, null, 40, Ne),
|
|
531
|
+
v("span", null, b(m.label), 1)
|
|
532
|
+
]))), 128))
|
|
533
|
+
], 8, He)
|
|
534
|
+
]),
|
|
535
|
+
_: 1
|
|
536
|
+
}, 8, ["field", "error", "input-id"]));
|
|
537
|
+
}
|
|
538
|
+
}), Ke = { class: "sr-only" }, Je = ["for"], Ge = ["id", "name", "checked", "required", "aria-invalid"], Ye = {
|
|
539
|
+
key: 0,
|
|
540
|
+
"aria-hidden": "true"
|
|
541
|
+
}, Qe = /* @__PURE__ */ I({
|
|
542
|
+
__name: "DcsFormCheckbox",
|
|
543
|
+
props: {
|
|
544
|
+
field: {},
|
|
545
|
+
modelValue: { type: Boolean },
|
|
546
|
+
error: {}
|
|
547
|
+
},
|
|
548
|
+
emits: ["update:modelValue"],
|
|
549
|
+
setup(e, { emit: t }) {
|
|
550
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`);
|
|
551
|
+
return (d, s) => (l(), w(q, {
|
|
552
|
+
field: e.field,
|
|
553
|
+
error: e.error,
|
|
554
|
+
"input-id": o.value
|
|
555
|
+
}, {
|
|
556
|
+
label: V(() => [
|
|
557
|
+
v("span", Ke, b(e.field.label), 1)
|
|
558
|
+
]),
|
|
559
|
+
default: V(() => [
|
|
560
|
+
v("label", {
|
|
561
|
+
class: "dcs-form-checkbox-single",
|
|
562
|
+
for: o.value
|
|
563
|
+
}, [
|
|
564
|
+
v("input", {
|
|
565
|
+
id: o.value,
|
|
566
|
+
type: "checkbox",
|
|
567
|
+
name: e.field.id,
|
|
568
|
+
checked: !!e.modelValue,
|
|
569
|
+
required: e.field.required,
|
|
570
|
+
"aria-invalid": !!e.error,
|
|
571
|
+
onChange: s[0] || (s[0] = (c) => i("update:modelValue", c.target.checked))
|
|
572
|
+
}, null, 40, Ge),
|
|
573
|
+
v("span", null, [
|
|
574
|
+
A(b(e.field.label), 1),
|
|
575
|
+
e.field.required ? (l(), f("span", Ye, " *")) : k("", !0)
|
|
576
|
+
])
|
|
577
|
+
], 8, Je)
|
|
578
|
+
]),
|
|
579
|
+
_: 1
|
|
580
|
+
}, 8, ["field", "error", "input-id"]));
|
|
581
|
+
}
|
|
582
|
+
}), Xe = ["id", "name", "required", "aria-invalid", "value"], Ze = /* @__PURE__ */ I({
|
|
583
|
+
__name: "DcsFormDate",
|
|
584
|
+
props: {
|
|
585
|
+
field: {},
|
|
586
|
+
modelValue: {},
|
|
587
|
+
error: {}
|
|
588
|
+
},
|
|
589
|
+
emits: ["update:modelValue", "blur"],
|
|
590
|
+
setup(e, { emit: t }) {
|
|
591
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`);
|
|
592
|
+
return (d, s) => (l(), w(q, {
|
|
593
|
+
field: e.field,
|
|
594
|
+
error: e.error,
|
|
595
|
+
"input-id": o.value
|
|
596
|
+
}, {
|
|
597
|
+
default: V(() => [
|
|
598
|
+
v("input", {
|
|
599
|
+
id: o.value,
|
|
600
|
+
class: "dcs-form-input dcs-form-date",
|
|
601
|
+
type: "date",
|
|
602
|
+
name: e.field.id,
|
|
603
|
+
required: e.field.required,
|
|
604
|
+
"aria-invalid": !!e.error,
|
|
605
|
+
value: e.modelValue ?? "",
|
|
606
|
+
onInput: s[0] || (s[0] = (c) => i("update:modelValue", c.target.value)),
|
|
607
|
+
onBlur: s[1] || (s[1] = (c) => i("blur"))
|
|
608
|
+
}, null, 40, Xe)
|
|
609
|
+
]),
|
|
610
|
+
_: 1
|
|
611
|
+
}, 8, ["field", "error", "input-id"]));
|
|
612
|
+
}
|
|
613
|
+
}), et = ["id", "name", "required", "aria-invalid", "accept"], tt = /* @__PURE__ */ I({
|
|
614
|
+
__name: "DcsFormFile",
|
|
615
|
+
props: {
|
|
616
|
+
field: {},
|
|
617
|
+
modelValue: {},
|
|
618
|
+
error: {}
|
|
619
|
+
},
|
|
620
|
+
emits: ["update:modelValue"],
|
|
621
|
+
setup(e, { emit: t }) {
|
|
622
|
+
const r = e, i = t, o = p(() => `field-${r.field.id}`), d = p(() => (r.field.validation?.accept ?? []).join(","));
|
|
623
|
+
function s(c) {
|
|
624
|
+
const a = c.target;
|
|
625
|
+
i("update:modelValue", a.files?.[0]);
|
|
626
|
+
}
|
|
627
|
+
return (c, a) => (l(), w(q, {
|
|
628
|
+
field: e.field,
|
|
629
|
+
error: e.error,
|
|
630
|
+
"input-id": o.value
|
|
631
|
+
}, {
|
|
632
|
+
default: V(() => [
|
|
633
|
+
v("input", {
|
|
634
|
+
id: o.value,
|
|
635
|
+
class: "dcs-form-input dcs-form-file",
|
|
636
|
+
type: "file",
|
|
637
|
+
name: e.field.id,
|
|
638
|
+
required: e.field.required,
|
|
639
|
+
"aria-invalid": !!e.error,
|
|
640
|
+
accept: d.value || void 0,
|
|
641
|
+
onChange: s
|
|
642
|
+
}, null, 40, et)
|
|
643
|
+
]),
|
|
644
|
+
_: 1
|
|
645
|
+
}, 8, ["field", "error", "input-id"]));
|
|
646
|
+
}
|
|
647
|
+
}), it = ["name", "value", "data-form-field-key"], rt = /* @__PURE__ */ I({
|
|
648
|
+
__name: "DcsFormHidden",
|
|
649
|
+
props: {
|
|
650
|
+
field: {},
|
|
651
|
+
modelValue: {}
|
|
652
|
+
},
|
|
653
|
+
setup(e) {
|
|
654
|
+
return (t, r) => (l(), f("input", {
|
|
655
|
+
type: "hidden",
|
|
656
|
+
name: e.field.id,
|
|
657
|
+
value: e.modelValue ?? e.field.defaultValue ?? "",
|
|
658
|
+
"data-form-field-key": e.field.id
|
|
659
|
+
}, null, 8, it));
|
|
660
|
+
}
|
|
661
|
+
}), nt = ["data-form-field-key"], ot = { class: "dcs-form-section__heading" }, st = {
|
|
662
|
+
key: 0,
|
|
663
|
+
class: "dcs-form-section__help"
|
|
664
|
+
}, at = /* @__PURE__ */ I({
|
|
665
|
+
__name: "DcsFormSection",
|
|
666
|
+
props: {
|
|
667
|
+
field: {}
|
|
668
|
+
},
|
|
669
|
+
setup(e) {
|
|
670
|
+
return (t, r) => (l(), f("div", {
|
|
671
|
+
class: "dcs-form-section",
|
|
672
|
+
"data-form-field-key": e.field.id
|
|
673
|
+
}, [
|
|
674
|
+
S(t.$slots, "default", {}, () => [
|
|
675
|
+
v("h3", ot, b(e.field.label), 1),
|
|
676
|
+
e.field.helpText ? (l(), f("p", st, b(e.field.helpText), 1)) : k("", !0)
|
|
677
|
+
])
|
|
678
|
+
], 8, nt));
|
|
679
|
+
}
|
|
680
|
+
}), lt = ["data-form-field-key", "innerHTML"], dt = /* @__PURE__ */ I({
|
|
681
|
+
__name: "DcsFormHtmlBlock",
|
|
682
|
+
props: {
|
|
683
|
+
field: {}
|
|
684
|
+
},
|
|
685
|
+
setup(e) {
|
|
686
|
+
return (t, r) => (l(), f("div", {
|
|
687
|
+
class: "dcs-form-html-block",
|
|
688
|
+
"data-form-field-key": e.field.id,
|
|
689
|
+
innerHTML: e.field.html ?? ""
|
|
690
|
+
}, null, 8, lt));
|
|
691
|
+
}
|
|
692
|
+
}), U = {}, ut = ["data-form-key"], ct = ["data-form-key"], mt = ["data-form-key"], ft = { class: "dcs-form__header" }, pt = { class: "dcs-form__title" }, ht = {
|
|
693
|
+
key: 0,
|
|
694
|
+
class: "dcs-form__description"
|
|
695
|
+
}, vt = {
|
|
696
|
+
key: 0,
|
|
697
|
+
class: "dcs-form__progress",
|
|
698
|
+
"aria-live": "polite"
|
|
699
|
+
}, bt = { class: "dcs-form__fields" }, gt = {
|
|
700
|
+
key: 1,
|
|
701
|
+
class: "dcs-form__submit-error",
|
|
702
|
+
role: "alert"
|
|
703
|
+
}, yt = { class: "dcs-form__actions" }, $t = ["disabled"], Lt = /* @__PURE__ */ I({
|
|
704
|
+
__name: "DcsForm",
|
|
705
|
+
props: {
|
|
706
|
+
formId: {},
|
|
707
|
+
siteSlug: {},
|
|
708
|
+
definitionOverride: {},
|
|
709
|
+
apiBase: {},
|
|
710
|
+
captchaToken: {},
|
|
711
|
+
formsModules: {}
|
|
712
|
+
},
|
|
713
|
+
emits: ["submit-success", "submit-error", "validation-error"],
|
|
714
|
+
setup(e, { emit: t }) {
|
|
715
|
+
const r = e, i = t, o = /* @__PURE__ */ Object.assign({}), d = p(
|
|
716
|
+
() => Se(r.formsModules ?? o)
|
|
717
|
+
), s = p(() => r.definitionOverride ? r.definitionOverride : d.value[r.formId] ?? null), c = !1;
|
|
718
|
+
H(
|
|
719
|
+
s,
|
|
720
|
+
(y) => {
|
|
721
|
+
y && xe(r.formId, y, c);
|
|
722
|
+
},
|
|
723
|
+
{ immediate: !0 }
|
|
724
|
+
);
|
|
725
|
+
const a = p(
|
|
726
|
+
() => s.value ?? {
|
|
727
|
+
formId: r.formId,
|
|
728
|
+
title: "",
|
|
729
|
+
submission: { kind: "lead" },
|
|
730
|
+
fields: []
|
|
731
|
+
}
|
|
732
|
+
), n = ae({ definition: a.value });
|
|
733
|
+
H(
|
|
734
|
+
() => a.value,
|
|
735
|
+
() => n.reset()
|
|
736
|
+
);
|
|
737
|
+
const m = p(
|
|
738
|
+
() => r.apiBase ?? U?.VITE_DCS_PUBLIC_API ?? ""
|
|
739
|
+
), g = p(
|
|
740
|
+
() => r.siteSlug ?? U?.VITE_DCS_SITE_SLUG ?? ""
|
|
741
|
+
), x = p(
|
|
742
|
+
() => a.value.submitLabel ?? "Send"
|
|
743
|
+
);
|
|
744
|
+
function P(y) {
|
|
745
|
+
switch (y.type) {
|
|
746
|
+
case "text":
|
|
747
|
+
case "email":
|
|
748
|
+
case "tel":
|
|
749
|
+
return N;
|
|
750
|
+
case "textarea":
|
|
751
|
+
return Oe;
|
|
752
|
+
case "select":
|
|
753
|
+
case "multiselect":
|
|
754
|
+
return Ae;
|
|
755
|
+
case "radio":
|
|
756
|
+
return We;
|
|
757
|
+
case "checkbox-group":
|
|
758
|
+
return Ue;
|
|
759
|
+
case "checkbox":
|
|
760
|
+
return Qe;
|
|
761
|
+
case "date":
|
|
762
|
+
return Ze;
|
|
763
|
+
case "file":
|
|
764
|
+
return tt;
|
|
765
|
+
case "hidden":
|
|
766
|
+
return rt;
|
|
767
|
+
case "section-heading":
|
|
768
|
+
return at;
|
|
769
|
+
case "html-block":
|
|
770
|
+
return dt;
|
|
771
|
+
default:
|
|
772
|
+
return N;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
const O = D(null);
|
|
776
|
+
async function z(y) {
|
|
777
|
+
if (y.preventDefault(), !s.value) return;
|
|
778
|
+
if (!n.validateAll()) {
|
|
779
|
+
i("validation-error", n.errors.value);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
n.submitting.value = !0, n.submitError.value = null;
|
|
783
|
+
const $ = {
|
|
784
|
+
formId: r.formId,
|
|
785
|
+
values: n.collectSubmissionValues(),
|
|
786
|
+
captchaToken: r.captchaToken
|
|
787
|
+
};
|
|
788
|
+
try {
|
|
789
|
+
const L = await le({
|
|
790
|
+
apiBase: m.value,
|
|
791
|
+
siteSlug: g.value,
|
|
792
|
+
payload: $
|
|
793
|
+
});
|
|
794
|
+
n.submitted.value = !0, i("submit-success", L);
|
|
795
|
+
} catch (L) {
|
|
796
|
+
const j = L;
|
|
797
|
+
n.submitError.value = j.error?.message ?? "Submission failed", i("submit-error", j);
|
|
798
|
+
} finally {
|
|
799
|
+
n.submitting.value = !1;
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return ee(() => {
|
|
803
|
+
s.value || console.warn(
|
|
804
|
+
`[@duffcloudservices/site-forms] No form definition found for "${r.formId}". Expected a YAML at /.dcs/forms/${r.formId}.yaml.`
|
|
805
|
+
);
|
|
806
|
+
}), (y, C) => s.value ? u(n).submitted.value ? (l(), f("div", {
|
|
807
|
+
key: 1,
|
|
808
|
+
class: "dcs-form dcs-form--success",
|
|
809
|
+
"data-form-key": e.formId
|
|
810
|
+
}, [
|
|
811
|
+
S(y.$slots, "success", { definition: s.value }, () => [
|
|
812
|
+
v("p", null, b(s.value.successMessage ?? "Thanks — we received your message."), 1)
|
|
813
|
+
])
|
|
814
|
+
], 8, ct)) : (l(), f("form", {
|
|
815
|
+
key: 2,
|
|
816
|
+
ref_key: "formEl",
|
|
817
|
+
ref: O,
|
|
818
|
+
class: "dcs-form",
|
|
819
|
+
"data-form-key": e.formId,
|
|
820
|
+
novalidate: "",
|
|
821
|
+
onSubmit: z
|
|
822
|
+
}, [
|
|
823
|
+
S(y.$slots, "header", { definition: s.value }, () => [
|
|
824
|
+
v("header", ft, [
|
|
825
|
+
v("h2", pt, b(s.value.title), 1),
|
|
826
|
+
s.value.description ? (l(), f("p", ht, b(s.value.description), 1)) : k("", !0)
|
|
827
|
+
])
|
|
828
|
+
]),
|
|
829
|
+
u(n).steps.value ? (l(), f("div", vt, [
|
|
830
|
+
S(y.$slots, "progress", {
|
|
831
|
+
current: u(n).currentStepIndex.value,
|
|
832
|
+
total: u(n).steps.value.length,
|
|
833
|
+
step: u(n).currentStep.value
|
|
834
|
+
}, () => [
|
|
835
|
+
v("p", null, [
|
|
836
|
+
A(" Step " + b(u(n).currentStepIndex.value + 1) + " of " + b(u(n).steps.value.length) + " ", 1),
|
|
837
|
+
u(n).currentStep.value ? (l(), f(T, { key: 0 }, [
|
|
838
|
+
A(" — " + b(u(n).currentStep.value.title), 1)
|
|
839
|
+
], 64)) : k("", !0)
|
|
840
|
+
])
|
|
841
|
+
])
|
|
842
|
+
])) : k("", !0),
|
|
843
|
+
v("div", bt, [
|
|
844
|
+
(l(!0), f(T, null, B(u(n).visibleFields.value, ($) => (l(), w(te(P($)), {
|
|
845
|
+
key: $.id,
|
|
846
|
+
field: $,
|
|
847
|
+
"model-value": u(n).values[$.id],
|
|
848
|
+
error: u(n).errors.value[$.id],
|
|
849
|
+
"onUpdate:modelValue": (L) => u(n).setValue($.id, L),
|
|
850
|
+
onBlur: (L) => u(n).touch($.id)
|
|
851
|
+
}, null, 40, ["field", "model-value", "error", "onUpdate:modelValue", "onBlur"]))), 128))
|
|
852
|
+
]),
|
|
853
|
+
u(n).submitError.value ? (l(), f("div", gt, b(u(n).submitError.value), 1)) : k("", !0),
|
|
854
|
+
v("div", yt, [
|
|
855
|
+
S(y.$slots, "actions", {
|
|
856
|
+
isFirstStep: u(n).isFirstStep.value,
|
|
857
|
+
isLastStep: u(n).isLastStep.value,
|
|
858
|
+
submitting: u(n).submitting.value,
|
|
859
|
+
prev: u(n).prev,
|
|
860
|
+
next: u(n).next
|
|
861
|
+
}, () => [
|
|
862
|
+
u(n).steps.value && !u(n).isFirstStep.value ? (l(), f("button", {
|
|
863
|
+
key: 0,
|
|
864
|
+
type: "button",
|
|
865
|
+
class: "dcs-form__btn dcs-form__btn--prev",
|
|
866
|
+
onClick: C[0] || (C[0] = ($) => u(n).prev())
|
|
867
|
+
}, " Previous ")) : k("", !0),
|
|
868
|
+
u(n).steps.value && !u(n).isLastStep.value ? (l(), f("button", {
|
|
869
|
+
key: 1,
|
|
870
|
+
type: "button",
|
|
871
|
+
class: "dcs-form__btn dcs-form__btn--next",
|
|
872
|
+
onClick: C[1] || (C[1] = ($) => u(n).next())
|
|
873
|
+
}, " Next ")) : k("", !0),
|
|
874
|
+
!u(n).steps.value || u(n).isLastStep.value ? (l(), f("button", {
|
|
875
|
+
key: 2,
|
|
876
|
+
type: "submit",
|
|
877
|
+
class: "dcs-form__btn dcs-form__btn--submit",
|
|
878
|
+
disabled: u(n).submitting.value
|
|
879
|
+
}, b(u(n).submitting.value ? "Sending…" : x.value), 9, $t)) : k("", !0)
|
|
880
|
+
])
|
|
881
|
+
])
|
|
882
|
+
], 40, mt)) : (l(), f("div", {
|
|
883
|
+
key: 0,
|
|
884
|
+
class: "dcs-form dcs-form--missing",
|
|
885
|
+
"data-form-key": e.formId
|
|
886
|
+
}, [
|
|
887
|
+
S(y.$slots, "missing", { formId: e.formId }, () => [
|
|
888
|
+
v("p", null, "Form “" + b(e.formId) + "” is not configured.", 1)
|
|
889
|
+
])
|
|
890
|
+
], 8, ut));
|
|
891
|
+
}
|
|
892
|
+
});
|
|
893
|
+
export {
|
|
894
|
+
Lt as DcsForm,
|
|
895
|
+
Qe as DcsFormCheckbox,
|
|
896
|
+
Ue as DcsFormCheckboxGroup,
|
|
897
|
+
Ze as DcsFormDate,
|
|
898
|
+
q as DcsFormFieldWrapper,
|
|
899
|
+
tt as DcsFormFile,
|
|
900
|
+
rt as DcsFormHidden,
|
|
901
|
+
dt as DcsFormHtmlBlock,
|
|
902
|
+
We as DcsFormRadio,
|
|
903
|
+
at as DcsFormSection,
|
|
904
|
+
Ae as DcsFormSelect,
|
|
905
|
+
N as DcsFormText,
|
|
906
|
+
Oe as DcsFormTextarea,
|
|
907
|
+
se as hasErrors,
|
|
908
|
+
R as isFieldVisible,
|
|
909
|
+
Se as loadFormDefinitions,
|
|
910
|
+
It as parseFormYaml,
|
|
911
|
+
le as submitFormValues,
|
|
912
|
+
ae as useDcsForm,
|
|
913
|
+
ne as validateField,
|
|
914
|
+
oe as validateForm,
|
|
915
|
+
ke as validateFormDefinition,
|
|
916
|
+
xe as warnIfInvalid
|
|
917
|
+
};
|
|
918
|
+
//# sourceMappingURL=index.js.map
|