@duffcloudservices/site-forms 0.1.2 → 0.1.3

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.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/composables/useFormValidation.ts","../src/composables/useDcsForm.ts","../src/composables/useFormSubmission.ts","../src/schema/validate.ts","../src/loaders/yaml.ts","../src/fields/DcsFormFieldWrapper.vue","../src/fields/DcsFormText.vue","../src/fields/DcsFormTextarea.vue","../src/fields/DcsFormSelect.vue","../src/fields/DcsFormRadio.vue","../src/fields/DcsFormCheckboxGroup.vue","../src/fields/DcsFormCheckbox.vue","../src/fields/DcsFormDate.vue","../src/fields/DcsFormFile.vue","../src/fields/DcsFormHidden.vue","../src/fields/DcsFormSection.vue","../src/fields/DcsFormHtmlBlock.vue","../src/DcsForm.vue"],"sourcesContent":["import type {\n PortalFormDefinition,\n PortalFormField,\n FormErrors,\n FormValues,\n} from '../types'\n\n/**\n * Returns true iff `field` is currently visible given the form's\n * other values. Hidden fields are never validated and never sent.\n */\nexport function isFieldVisible(\n field: PortalFormField,\n values: FormValues,\n): boolean {\n if (!field.visibleIf) return true\n const sibling = values[field.visibleIf.fieldId]\n return sibling === field.visibleIf.equals\n}\n\n/**\n * Validates a single field's value. Returns an error string or\n * `undefined` if valid. Layout-only field types (section-heading,\n * html-block) and hidden fields never produce errors.\n */\nexport function validateField(\n field: PortalFormField,\n value: unknown,\n): string | undefined {\n if (\n field.type === 'section-heading' ||\n field.type === 'html-block' ||\n field.type === 'hidden'\n ) {\n return undefined\n }\n\n const isEmpty =\n value === undefined ||\n value === null ||\n value === '' ||\n (Array.isArray(value) && value.length === 0)\n\n if (field.required && isEmpty) {\n return `${field.label} is required`\n }\n if (isEmpty) return undefined\n\n // Type-specific built-ins\n if (field.type === 'email' && typeof value === 'string') {\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(value)) {\n return `${field.label} must be a valid email address`\n }\n }\n\n const v = field.validation\n if (!v) return undefined\n\n if (typeof value === 'string') {\n if (v.minLength != null && value.length < v.minLength) {\n return `${field.label} must be at least ${v.minLength} characters`\n }\n if (v.maxLength != null && value.length > v.maxLength) {\n return `${field.label} must be at most ${v.maxLength} characters`\n }\n if (v.regex) {\n try {\n if (!new RegExp(v.regex).test(value)) {\n return `${field.label} is invalid`\n }\n } catch {\n // bad regex in the definition — treat as no rule\n }\n }\n }\n\n if (typeof value === 'number') {\n if (v.min != null && value < v.min) {\n return `${field.label} must be at least ${v.min}`\n }\n if (v.max != null && value > v.max) {\n return `${field.label} must be at most ${v.max}`\n }\n }\n\n if (field.type === 'file' && v.accept && v.accept.length > 0) {\n const file = value as File | null\n if (file && typeof File !== 'undefined' && file instanceof File) {\n const accepted = v.accept.some((a) => {\n if (a.startsWith('.')) {\n return file.name.toLowerCase().endsWith(a.toLowerCase())\n }\n return file.type === a\n })\n if (!accepted) {\n return `${field.label} must be one of: ${v.accept.join(', ')}`\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Validates an entire form. Skips fields that are not visible.\n * If `fieldIds` is supplied, only those fields are validated\n * (used for per-step validation in multi-step forms).\n */\nexport function validateForm(\n def: PortalFormDefinition,\n values: FormValues,\n fieldIds?: string[],\n): FormErrors {\n const errors: FormErrors = {}\n const ids = fieldIds ? new Set(fieldIds) : null\n for (const field of def.fields) {\n if (ids && !ids.has(field.id)) continue\n if (!isFieldVisible(field, values)) continue\n const err = validateField(field, values[field.id])\n if (err) errors[field.id] = err\n }\n return errors\n}\n\nexport function hasErrors(errors: FormErrors): boolean {\n return Object.values(errors).some((e) => !!e)\n}\n","import { computed, reactive, ref, type ComputedRef, type Ref } from 'vue'\nimport type {\n PortalFormDefinition,\n PortalFormField,\n PortalFormStep,\n FormErrors,\n FormValues,\n} from '../types'\nimport {\n hasErrors,\n isFieldVisible,\n validateForm,\n} from './useFormValidation'\n\nexport interface UseDcsFormOptions {\n definition: PortalFormDefinition\n}\n\nexport interface UseDcsFormReturn {\n values: FormValues\n errors: Ref<FormErrors>\n touched: Ref<Record<string, boolean>>\n submitting: Ref<boolean>\n submitted: Ref<boolean>\n submitError: Ref<string | null>\n /** All fields, with layout-only types preserved. */\n fields: ComputedRef<PortalFormField[]>\n /** Steps if multi-step, else null. */\n steps: ComputedRef<PortalFormStep[] | null>\n currentStepIndex: Ref<number>\n currentStep: ComputedRef<PortalFormStep | null>\n currentStepFields: ComputedRef<PortalFormField[]>\n isFirstStep: ComputedRef<boolean>\n isLastStep: ComputedRef<boolean>\n visibleFields: ComputedRef<PortalFormField[]>\n /** Sets a field value and clears that field's error. */\n setValue: (id: string, value: unknown) => void\n /** Marks a field as touched + revalidates only that field's row. */\n touch: (id: string) => void\n /** Validates the current step (multi-step) or the entire form. */\n validateCurrentScope: () => boolean\n validateAll: () => boolean\n next: () => boolean\n prev: () => void\n reset: () => void\n /** Returns the values that should actually be submitted (visible only). */\n collectSubmissionValues: () => FormValues\n}\n\nfunction defaultsFor(def: PortalFormDefinition): FormValues {\n const out: FormValues = {}\n for (const f of def.fields) {\n if (f.defaultValue !== undefined) {\n out[f.id] = f.defaultValue as unknown\n } else if (f.type === 'checkbox') {\n out[f.id] = false\n } else if (f.type === 'multiselect' || f.type === 'checkbox-group') {\n out[f.id] = [] as string[]\n } else {\n out[f.id] = ''\n }\n }\n return out\n}\n\nexport function useDcsForm(opts: UseDcsFormOptions): UseDcsFormReturn {\n const { definition } = opts\n const values = reactive<FormValues>(defaultsFor(definition))\n const errors = ref<FormErrors>({})\n const touched = ref<Record<string, boolean>>({})\n const submitting = ref(false)\n const submitted = ref(false)\n const submitError = ref<string | null>(null)\n const currentStepIndex = ref(0)\n\n const fields = computed(() => definition.fields)\n const steps = computed<PortalFormStep[] | null>(() =>\n definition.steps && definition.steps.length > 0 ? definition.steps : null,\n )\n const currentStep = computed<PortalFormStep | null>(() => {\n const s = steps.value\n return s ? (s[currentStepIndex.value] ?? null) : null\n })\n const currentStepFields = computed<PortalFormField[]>(() => {\n const step = currentStep.value\n if (!step) return definition.fields\n const ids = new Set(step.fieldIds)\n return definition.fields.filter((f) => ids.has(f.id))\n })\n const isFirstStep = computed(() => currentStepIndex.value === 0)\n const isLastStep = computed(() => {\n const s = steps.value\n return !s || currentStepIndex.value >= s.length - 1\n })\n const visibleFields = computed(() =>\n currentStepFields.value.filter((f) => isFieldVisible(f, values as FormValues)),\n )\n\n function setValue(id: string, value: unknown): void {\n ;(values as FormValues)[id] = value\n if (errors.value[id]) {\n errors.value = { ...errors.value, [id]: undefined }\n }\n }\n\n function touch(id: string): void {\n touched.value = { ...touched.value, [id]: true }\n }\n\n function validateScope(scopeFieldIds?: string[]): boolean {\n const next = validateForm(definition, values as FormValues, scopeFieldIds)\n // Merge with existing errors but only for the validated scope so we\n // don't wipe out errors from other steps.\n if (scopeFieldIds) {\n const merged = { ...errors.value }\n for (const id of scopeFieldIds) {\n merged[id] = next[id]\n }\n errors.value = merged\n } else {\n errors.value = next\n }\n return !hasErrors(errors.value)\n }\n\n function validateCurrentScope(): boolean {\n const scope = currentStep.value?.fieldIds\n return validateScope(scope)\n }\n\n function validateAll(): boolean {\n return validateScope()\n }\n\n function next(): boolean {\n if (!steps.value) return false\n if (!validateCurrentScope()) return false\n if (currentStepIndex.value < steps.value.length - 1) {\n currentStepIndex.value++\n return true\n }\n return false\n }\n\n function prev(): void {\n if (currentStepIndex.value > 0) currentStepIndex.value--\n }\n\n function reset(): void {\n const fresh = defaultsFor(definition)\n for (const k of Object.keys(values as object)) {\n delete (values as FormValues)[k]\n }\n Object.assign(values as FormValues, fresh)\n errors.value = {}\n touched.value = {}\n submitting.value = false\n submitted.value = false\n submitError.value = null\n currentStepIndex.value = 0\n }\n\n function collectSubmissionValues(): FormValues {\n const out: FormValues = {}\n for (const f of definition.fields) {\n if (\n f.type === 'section-heading' ||\n f.type === 'html-block'\n )\n continue\n if (!isFieldVisible(f, values as FormValues)) continue\n out[f.id] = (values as FormValues)[f.id]\n }\n return out\n }\n\n return {\n values: values as FormValues,\n errors,\n touched,\n submitting,\n submitted,\n submitError,\n fields,\n steps,\n currentStepIndex,\n currentStep,\n currentStepFields,\n isFirstStep,\n isLastStep,\n visibleFields,\n setValue,\n touch,\n validateCurrentScope,\n validateAll,\n next,\n prev,\n reset,\n collectSubmissionValues,\n }\n}\n","import type {\n DcsFormSubmitPayload,\n DcsFormSubmitSuccess,\n DcsFormSubmitError,\n} from '../types'\n\nexport interface SubmitOptions {\n apiBase: string\n siteSlug: string\n payload: DcsFormSubmitPayload\n /** Number of retries on 5xx / network errors. Default 1. */\n retries?: number\n /** Optional fetch implementation override (tests). */\n fetchImpl?: typeof fetch\n}\n\n/**\n * POSTs a form submission to\n * `${apiBase}/public/sites/{siteSlug}/form-submissions`.\n *\n * Uses JSON for plain values and `multipart/form-data` when any\n * value is a `File` (file-upload fields).\n */\nexport async function submitFormValues(\n opts: SubmitOptions,\n): Promise<DcsFormSubmitSuccess> {\n const { apiBase, siteSlug, payload } = opts\n const retries = opts.retries ?? 1\n const fetchImpl = opts.fetchImpl ?? fetch\n const url = `${apiBase.replace(/\\/$/, '')}/public/sites/${encodeURIComponent(\n siteSlug,\n )}/form-submissions`\n\n const hasFile = Object.values(payload.values).some(\n (v) => typeof File !== 'undefined' && v instanceof File,\n )\n\n let lastError: Error | undefined\n let lastStatus: number | undefined\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const init: RequestInit = hasFile\n ? { method: 'POST', body: buildFormData(payload) }\n : {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n const res = await fetchImpl(url, init)\n lastStatus = res.status\n if (res.ok) {\n const body = await safeJson(res)\n return { payload, response: body }\n }\n // Retry only on 5xx\n if (res.status < 500) {\n const body = await safeText(res)\n const err: DcsFormSubmitError = {\n payload,\n status: res.status,\n error: new Error(`Submission failed (${res.status}): ${body}`),\n }\n throw err\n }\n lastError = new Error(`Server error ${res.status}`)\n } catch (e) {\n // If `e` is already a DcsFormSubmitError-shaped object, rethrow.\n if (e && typeof e === 'object' && 'payload' in e && 'error' in e) {\n throw e\n }\n lastError = e as Error\n }\n }\n\n throw {\n payload,\n status: lastStatus,\n error: lastError ?? new Error('Submission failed'),\n } satisfies DcsFormSubmitError\n}\n\nfunction buildFormData(payload: DcsFormSubmitPayload): FormData {\n const fd = new FormData()\n fd.append('formId', payload.formId)\n if (payload.captchaToken) fd.append('captchaToken', payload.captchaToken)\n for (const [key, value] of Object.entries(payload.values)) {\n if (value === undefined || value === null) continue\n if (value instanceof File) {\n fd.append(`values[${key}]`, value)\n } else if (Array.isArray(value)) {\n for (const item of value) fd.append(`values[${key}][]`, String(item))\n } else {\n fd.append(`values[${key}]`, String(value))\n }\n }\n return fd\n}\n\nasync function safeJson(res: Response): Promise<unknown> {\n try {\n return await res.json()\n } catch {\n return null\n }\n}\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text()\n } catch {\n return ''\n }\n}\n","/**\n * Validates a loaded form definition against the form-definition JSON\n * Schema emitted by `@dcs/contracts`. Dev-only — failures are logged\n * via `console.warn` rather than thrown so a malformed YAML never\n * takes down a customer site in production.\n *\n * Schema is bundled as a snapshot of\n * `contracts/dist/form-definition.schema.json` to avoid a runtime\n * dependency on the contracts build output.\n */\nimport Ajv, { type ErrorObject, type ValidateFunction } from 'ajv'\nimport addFormats from 'ajv-formats'\nimport schema from './form-definition.schema.json'\nimport type { PortalFormDefinition } from '../types'\n\nlet validator: ValidateFunction | null = null\n\nfunction getValidator(): ValidateFunction {\n if (validator) return validator\n const ajv = new Ajv({\n allErrors: true,\n strict: false,\n discriminator: false,\n })\n addFormats(ajv)\n validator = ajv.compile(schema as object)\n return validator\n}\n\nexport interface SchemaValidationResult {\n valid: boolean\n errors: ErrorObject[]\n}\n\nexport function validateFormDefinition(\n def: PortalFormDefinition | unknown,\n): SchemaValidationResult {\n const v = getValidator()\n const valid = v(def) as boolean\n return { valid, errors: valid ? [] : (v.errors ?? []) }\n}\n\n/** Logs a `console.warn` when invalid, but only when `import.meta.env.DEV`. */\nexport function warnIfInvalid(\n formId: string,\n def: PortalFormDefinition | unknown,\n isDev: boolean,\n): SchemaValidationResult {\n const result = validateFormDefinition(def)\n if (!result.valid && isDev) {\n // eslint-disable-next-line no-console\n console.warn(\n `[@duffcloudservices/site-forms] Form \"${formId}\" failed schema validation:`,\n result.errors,\n )\n }\n return result\n}\n","import yaml from 'js-yaml'\nimport type { PortalFormDefinition } from '../types'\n\n/**\n * Eagerly load every form YAML under `/.dcs/forms/*.yaml` from the\n * consuming Vite app and parse them into PortalFormDefinition objects.\n *\n * Vite returns the modules as raw strings (when using `?raw` or the\n * default text loader for unknown extensions) or as parsed objects\n * (when `vite-plugin-yaml` is installed). This helper handles both.\n */\nexport function loadFormDefinitions(\n modules: Record<string, unknown>,\n): Record<string, PortalFormDefinition> {\n const out: Record<string, PortalFormDefinition> = {}\n for (const [path, mod] of Object.entries(modules)) {\n const formId = extractFormId(path)\n const def = parseModule(mod)\n if (def) {\n out[def.formId ?? formId] = def\n }\n }\n return out\n}\n\nfunction extractFormId(path: string): string {\n const file = path.split('/').pop() ?? path\n return file.replace(/\\.ya?ml$/i, '')\n}\n\nfunction parseModule(mod: unknown): PortalFormDefinition | null {\n if (!mod) return null\n if (typeof mod === 'string') {\n return yaml.load(mod) as PortalFormDefinition\n }\n if (typeof mod === 'object') {\n // vite-plugin-yaml returns parsed objects, possibly wrapped in\n // `{ default: ... }` when imported via `import: 'default'`.\n const maybeDefault = (mod as { default?: unknown }).default\n if (maybeDefault && typeof maybeDefault === 'object') {\n return maybeDefault as PortalFormDefinition\n }\n return mod as PortalFormDefinition\n }\n return null\n}\n\n/** Parse a single raw YAML string into a PortalFormDefinition. */\nexport function parseFormYaml(raw: string): PortalFormDefinition {\n return yaml.load(raw) as PortalFormDefinition\n}\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n error?: string\n /** Optional id to use for the input — wrappers use it for label `for=`. */\n inputId?: string\n}>()\n</script>\n\n<template>\n <div\n class=\"dcs-form-field\"\n :class=\"[`dcs-form-field--${field.type}`, `dcs-form-field--width-${field.width ?? 'full'}`]\"\n :data-form-field-key=\"field.id\"\n >\n <slot name=\"label\">\n <label\n v-if=\"field.type !== 'checkbox' && field.type !== 'hidden' && field.type !== 'section-heading' && field.type !== 'html-block'\"\n :for=\"inputId ?? `field-${field.id}`\"\n class=\"dcs-form-field__label\"\n >\n {{ field.label }}\n <span v-if=\"field.required\" aria-hidden=\"true\" class=\"dcs-form-field__required\">*</span>\n </label>\n </slot>\n\n <slot />\n\n <slot name=\"help\">\n <p v-if=\"field.helpText\" class=\"dcs-form-field__help\">{{ field.helpText }}</p>\n </slot>\n\n <slot name=\"error\">\n <p v-if=\"error\" class=\"dcs-form-field__error\" role=\"alert\">{{ error }}</p>\n </slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | number | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst inputType = computed(() => {\n switch (props.field.type) {\n case 'email':\n return 'email'\n case 'tel':\n return 'tel'\n default:\n return 'text'\n }\n})\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <slot\n name=\"input\"\n :id=\"inputId\"\n :value=\"modelValue\"\n :on-input=\"(e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).value)\"\n :on-blur=\"() => emit('blur')\"\n >\n <input\n :id=\"inputId\"\n class=\"dcs-form-input\"\n :type=\"inputType\"\n :name=\"field.id\"\n :value=\"modelValue ?? ''\"\n :placeholder=\"field.placeholder\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? `${inputId}-error` : undefined\"\n @input=\"emit('update:modelValue', ($event.target as HTMLInputElement).value)\"\n @blur=\"emit('blur')\"\n />\n </slot>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <slot\n name=\"input\"\n :id=\"inputId\"\n :value=\"modelValue\"\n :on-input=\"(e: Event) => emit('update:modelValue', (e.target as HTMLTextAreaElement).value)\"\n :on-blur=\"() => emit('blur')\"\n >\n <textarea\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-textarea\"\n :name=\"field.id\"\n :placeholder=\"field.placeholder\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n rows=\"5\"\n :value=\"modelValue ?? ''\"\n @input=\"emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)\"\n @blur=\"emit('blur')\"\n />\n </slot>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | string[] | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst isMulti = computed(() => props.field.type === 'multiselect')\nconst options = computed(() => props.field.options ?? [])\n\nfunction onChange(e: Event): void {\n const sel = e.target as HTMLSelectElement\n if (isMulti.value) {\n const values: string[] = []\n for (const opt of Array.from(sel.selectedOptions)) values.push(opt.value)\n emit('update:modelValue', values)\n } else {\n emit('update:modelValue', sel.value)\n }\n}\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <slot\n name=\"input\"\n :id=\"inputId\"\n :value=\"modelValue\"\n :options=\"options\"\n :on-change=\"onChange\"\n >\n <select\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-select\"\n :name=\"field.id\"\n :required=\"field.required\"\n :multiple=\"isMulti\"\n :aria-invalid=\"!!error\"\n :value=\"modelValue ?? (isMulti ? [] : '')\"\n @change=\"onChange\"\n @blur=\"emit('blur')\"\n >\n <option v-if=\"!isMulti\" value=\"\" disabled>\n {{ field.placeholder ?? 'Select…' }}\n </option>\n <option v-for=\"opt in options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </select>\n </slot>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst options = computed(() => props.field.options ?? [])\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <fieldset\n class=\"dcs-form-radio-group\"\n role=\"radiogroup\"\n :aria-required=\"field.required\"\n :aria-invalid=\"!!error\"\n >\n <legend class=\"sr-only\">{{ field.label }}</legend>\n <label\n v-for=\"opt in options\"\n :key=\"opt.value\"\n class=\"dcs-form-radio\"\n >\n <input\n type=\"radio\"\n :name=\"field.id\"\n :value=\"opt.value\"\n :checked=\"modelValue === opt.value\"\n @change=\"emit('update:modelValue', opt.value)\"\n />\n <span>{{ opt.label }}</span>\n </label>\n </fieldset>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string[] | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string[]]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst options = computed(() => props.field.options ?? [])\nconst current = computed(() => props.modelValue ?? [])\n\nfunction toggle(value: string, checked: boolean): void {\n const set = new Set(current.value)\n if (checked) set.add(value)\n else set.delete(value)\n emit('update:modelValue', Array.from(set))\n}\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <fieldset\n class=\"dcs-form-checkbox-group\"\n :aria-required=\"field.required\"\n :aria-invalid=\"!!error\"\n >\n <legend class=\"sr-only\">{{ field.label }}</legend>\n <label\n v-for=\"opt in options\"\n :key=\"opt.value\"\n class=\"dcs-form-checkbox\"\n >\n <input\n type=\"checkbox\"\n :name=\"`${field.id}[]`\"\n :value=\"opt.value\"\n :checked=\"current.includes(opt.value)\"\n @change=\"toggle(opt.value, ($event.target as HTMLInputElement).checked)\"\n />\n <span>{{ opt.label }}</span>\n </label>\n </fieldset>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: boolean | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <template #label><span class=\"sr-only\">{{ field.label }}</span></template>\n <label class=\"dcs-form-checkbox-single\" :for=\"inputId\">\n <input\n :id=\"inputId\"\n type=\"checkbox\"\n :name=\"field.id\"\n :checked=\"!!modelValue\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n @change=\"emit('update:modelValue', ($event.target as HTMLInputElement).checked)\"\n />\n <span>{{ field.label }}<span v-if=\"field.required\" aria-hidden=\"true\"> *</span></span>\n </label>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <input\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-date\"\n type=\"date\"\n :name=\"field.id\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n :value=\"modelValue ?? ''\"\n @input=\"emit('update:modelValue', ($event.target as HTMLInputElement).value)\"\n @blur=\"emit('blur')\"\n />\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: File | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: File | undefined]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst accept = computed(() => (props.field.validation?.accept ?? []).join(','))\n\nfunction onChange(e: Event): void {\n const target = e.target as HTMLInputElement\n emit('update:modelValue', target.files?.[0])\n}\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <input\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-file\"\n type=\"file\"\n :name=\"field.id\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n :accept=\"accept || undefined\"\n @change=\"onChange\"\n />\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n modelValue: string | number | undefined\n}>()\n</script>\n\n<template>\n <input\n type=\"hidden\"\n :name=\"field.id\"\n :value=\"modelValue ?? (field.defaultValue as string | number | undefined) ?? ''\"\n :data-form-field-key=\"field.id\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n}>()\n</script>\n\n<template>\n <div\n class=\"dcs-form-section\"\n :data-form-field-key=\"field.id\"\n >\n <slot>\n <h3 class=\"dcs-form-section__heading\">{{ field.label }}</h3>\n <p v-if=\"field.helpText\" class=\"dcs-form-section__help\">{{ field.helpText }}</p>\n </slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n}>()\n</script>\n\n<template>\n <!--\n `html` is sanitized server-side per the schema; rendering with v-html\n is intentional. Site authors must keep portal-side sanitization on.\n -->\n <div\n class=\"dcs-form-html-block\"\n :data-form-field-key=\"field.id\"\n v-html=\"field.html ?? ''\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, watch } from 'vue'\nimport type {\n DcsFormSubmitError,\n DcsFormSubmitSuccess,\n PortalFormDefinition,\n PortalFormField,\n} from './types'\nimport { useDcsForm } from './composables/useDcsForm'\nimport { submitFormValues } from './composables/useFormSubmission'\nimport { warnIfInvalid } from './schema/validate'\nimport { loadFormDefinitions } from './loaders/yaml'\n\nimport DcsFormText from './fields/DcsFormText.vue'\nimport DcsFormTextarea from './fields/DcsFormTextarea.vue'\nimport DcsFormSelect from './fields/DcsFormSelect.vue'\nimport DcsFormRadio from './fields/DcsFormRadio.vue'\nimport DcsFormCheckboxGroup from './fields/DcsFormCheckboxGroup.vue'\nimport DcsFormCheckbox from './fields/DcsFormCheckbox.vue'\nimport DcsFormDate from './fields/DcsFormDate.vue'\nimport DcsFormFile from './fields/DcsFormFile.vue'\nimport DcsFormHidden from './fields/DcsFormHidden.vue'\nimport DcsFormSection from './fields/DcsFormSection.vue'\nimport DcsFormHtmlBlock from './fields/DcsFormHtmlBlock.vue'\n\ninterface Props {\n /** Kebab-case form id; matches `.dcs/forms/<formId>.yaml`. */\n formId: string\n /** Site slug used for the submission endpoint path. */\n siteSlug?: string\n /** Override the loaded definition (used by the portal preview iframe). */\n definitionOverride?: PortalFormDefinition\n /** Override the API base URL; defaults to `VITE_DCS_PUBLIC_API`. */\n apiBase?: string\n /** Optional captcha token, attached to the submission payload. */\n captchaToken?: string\n /**\n * Optional override for the YAML modules map. Mostly for tests; in\n * real apps the build-time `import.meta.glob` call below is used.\n */\n formsModules?: Record<string, unknown>\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'submit-success': [event: DcsFormSubmitSuccess]\n 'submit-error': [event: DcsFormSubmitError]\n 'validation-error': [errors: Record<string, string | undefined>]\n}>()\n\n// Eager glob — Vite inlines every site form at build time. Customer\n// sites should configure `vite-plugin-yaml` so YAML is parsed to an\n// object; without it the loader falls back to parsing raw strings.\nconst eagerFormsModules = (import.meta as unknown as {\n glob: (\n pattern: string,\n opts: { eager: true; import: string; query?: string },\n ) => Record<string, unknown>\n}).glob('/.dcs/forms/*.yaml', { eager: true, import: 'default' })\n\nconst definitions = computed(() =>\n loadFormDefinitions(props.formsModules ?? eagerFormsModules),\n)\n\nconst definition = computed<PortalFormDefinition | null>(() => {\n if (props.definitionOverride) return props.definitionOverride\n return definitions.value[props.formId] ?? null\n})\n\nconst isDev = !!(import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV\n\nwatch(\n definition,\n (def) => {\n if (def) warnIfInvalid(props.formId, def, isDev)\n },\n { immediate: true },\n)\n\n// We always call useDcsForm with *some* definition so hooks render\n// stably; if the form is missing we'll show an error state instead.\nconst safeDefinition = computed<PortalFormDefinition>(\n () =>\n definition.value ?? {\n formId: props.formId,\n title: '',\n submission: { kind: 'lead' },\n fields: [],\n },\n)\n\nconst form = useDcsForm({ definition: safeDefinition.value })\n\n// If the resolved definition changes (e.g. preview iframe updates),\n// re-create derived state by resetting.\nwatch(\n () => safeDefinition.value,\n () => form.reset(),\n)\n\nconst apiBase = computed(\n () =>\n props.apiBase ??\n (import.meta as unknown as { env?: { VITE_DCS_PUBLIC_API?: string } }).env\n ?.VITE_DCS_PUBLIC_API ??\n '',\n)\n\nconst resolvedSiteSlug = computed(\n () =>\n props.siteSlug ??\n (import.meta as unknown as { env?: { VITE_DCS_SITE_SLUG?: string } }).env\n ?.VITE_DCS_SITE_SLUG ??\n '',\n)\n\nconst submitLabel = computed(\n () => safeDefinition.value.submitLabel ?? 'Send',\n)\n\nfunction fieldComponent(field: PortalFormField) {\n switch (field.type) {\n case 'text':\n case 'email':\n case 'tel':\n return DcsFormText\n case 'textarea':\n return DcsFormTextarea\n case 'select':\n case 'multiselect':\n return DcsFormSelect\n case 'radio':\n return DcsFormRadio\n case 'checkbox-group':\n return DcsFormCheckboxGroup\n case 'checkbox':\n return DcsFormCheckbox\n case 'date':\n return DcsFormDate\n case 'file':\n return DcsFormFile\n case 'hidden':\n return DcsFormHidden\n case 'section-heading':\n return DcsFormSection\n case 'html-block':\n return DcsFormHtmlBlock\n default:\n return DcsFormText\n }\n}\n\nconst formEl = ref<HTMLFormElement | null>(null)\n\nasync function onSubmit(e: Event): Promise<void> {\n e.preventDefault()\n if (!definition.value) return\n const ok = form.validateAll()\n if (!ok) {\n emit('validation-error', form.errors.value)\n return\n }\n form.submitting.value = true\n form.submitError.value = null\n const payload = {\n formId: props.formId,\n values: form.collectSubmissionValues(),\n captchaToken: props.captchaToken,\n }\n try {\n const result = await submitFormValues({\n apiBase: apiBase.value,\n siteSlug: resolvedSiteSlug.value,\n payload,\n })\n form.submitted.value = true\n emit('submit-success', result)\n } catch (err) {\n const e2 = err as DcsFormSubmitError\n form.submitError.value = e2.error?.message ?? 'Submission failed'\n emit('submit-error', e2)\n } finally {\n form.submitting.value = false\n }\n}\n\nonMounted(() => {\n if (!definition.value) {\n // eslint-disable-next-line no-console\n console.warn(\n `[@duffcloudservices/site-forms] No form definition found for \"${props.formId}\". ` +\n `Expected a YAML at .dcs/forms/${props.formId}.yaml. ` +\n `In customer-site repos the YAML lives above the Vite root, so the ` +\n `internal auto-glob will not find it — pass formsModules explicitly: ` +\n `<DcsForm form-id=\"${props.formId}\" :forms-modules=\"dcsFormsModules\" />. ` +\n `See @duffcloudservices/site-forms README \"Vite setup\" section.`,\n )\n }\n})\n</script>\n\n<template>\n <div v-if=\"!definition\" class=\"dcs-form dcs-form--missing\" :data-form-key=\"formId\">\n <slot name=\"missing\" :form-id=\"formId\">\n <p>Form &ldquo;{{ formId }}&rdquo; is not configured.</p>\n </slot>\n </div>\n\n <div v-else-if=\"form.submitted.value\" class=\"dcs-form dcs-form--success\" :data-form-key=\"formId\">\n <slot name=\"success\" :definition=\"definition\">\n <p>{{ definition.successMessage ?? 'Thanks — we received your message.' }}</p>\n </slot>\n </div>\n\n <form\n v-else\n ref=\"formEl\"\n class=\"dcs-form\"\n :data-form-key=\"formId\"\n novalidate\n @submit=\"onSubmit\"\n >\n <slot name=\"header\" :definition=\"definition\">\n <header class=\"dcs-form__header\">\n <h2 class=\"dcs-form__title\">{{ definition.title }}</h2>\n <p v-if=\"definition.description\" class=\"dcs-form__description\">\n {{ definition.description }}\n </p>\n </header>\n </slot>\n\n <div v-if=\"form.steps.value\" class=\"dcs-form__progress\" aria-live=\"polite\">\n <slot\n name=\"progress\"\n :current=\"form.currentStepIndex.value\"\n :total=\"form.steps.value.length\"\n :step=\"form.currentStep.value\"\n >\n <p>\n Step {{ form.currentStepIndex.value + 1 }} of\n {{ form.steps.value.length }}\n <template v-if=\"form.currentStep.value\">\n — {{ form.currentStep.value.title }}\n </template>\n </p>\n </slot>\n </div>\n\n <div class=\"dcs-form__fields\">\n <component\n :is=\"fieldComponent(field)\"\n v-for=\"field in form.visibleFields.value\"\n :key=\"field.id\"\n :field=\"field\"\n :model-value=\"form.values[field.id]\"\n :error=\"form.errors.value[field.id]\"\n @update:model-value=\"(v: unknown) => form.setValue(field.id, v)\"\n @blur=\"form.touch(field.id)\"\n />\n </div>\n\n <div v-if=\"form.submitError.value\" class=\"dcs-form__submit-error\" role=\"alert\">\n {{ form.submitError.value }}\n </div>\n\n <div class=\"dcs-form__actions\">\n <slot\n name=\"actions\"\n :is-first-step=\"form.isFirstStep.value\"\n :is-last-step=\"form.isLastStep.value\"\n :submitting=\"form.submitting.value\"\n :prev=\"form.prev\"\n :next=\"form.next\"\n >\n <button\n v-if=\"form.steps.value && !form.isFirstStep.value\"\n type=\"button\"\n class=\"dcs-form__btn dcs-form__btn--prev\"\n @click=\"form.prev()\"\n >\n Previous\n </button>\n <button\n v-if=\"form.steps.value && !form.isLastStep.value\"\n type=\"button\"\n class=\"dcs-form__btn dcs-form__btn--next\"\n @click=\"form.next()\"\n >\n Next\n </button>\n <button\n v-if=\"!form.steps.value || form.isLastStep.value\"\n type=\"submit\"\n class=\"dcs-form__btn dcs-form__btn--submit\"\n :disabled=\"form.submitting.value\"\n >\n {{ form.submitting.value ? 'Sending…' : submitLabel }}\n </button>\n </slot>\n </div>\n </form>\n</template>\n"],"names":["isFieldVisible","field","values","validateField","value","isEmpty","v","file","a","validateForm","def","fieldIds","errors","ids","err","hasErrors","e","defaultsFor","out","f","useDcsForm","opts","definition","reactive","ref","touched","submitting","submitted","submitError","currentStepIndex","fields","computed","steps","currentStep","s","currentStepFields","step","isFirstStep","isLastStep","visibleFields","setValue","id","touch","validateScope","scopeFieldIds","next","merged","validateCurrentScope","scope","validateAll","prev","reset","fresh","k","collectSubmissionValues","submitFormValues","apiBase","siteSlug","payload","retries","fetchImpl","url","hasFile","lastError","lastStatus","attempt","init","buildFormData","res","body","safeJson","safeText","fd","key","item","validator","getValidator","ajv","Ajv","addFormats","schema","validateFormDefinition","valid","warnIfInvalid","formId","isDev","result","loadFormDefinitions","modules","path","mod","extractFormId","parseModule","yaml","maybeDefault","parseFormYaml","raw","_createElementBlock","_normalizeClass","__props","_renderSlot","_ctx","_hoisted_3","_openBlock","_hoisted_4","_toDisplayString","_hoisted_5","props","emit","__emit","inputId","inputType","_createBlock","DcsFormFieldWrapper","_createElementVNode","$event","isMulti","options","onChange","sel","opt","_hoisted_2","_Fragment","_renderList","current","toggle","checked","set","_hoisted_1","_createTextVNode","accept","target","eagerFormsModules","definitions","watch","safeDefinition","form","__vite_import_meta_env__","resolvedSiteSlug","submitLabel","fieldComponent","DcsFormText","DcsFormTextarea","DcsFormSelect","DcsFormRadio","DcsFormCheckboxGroup","DcsFormCheckbox","DcsFormDate","DcsFormFile","DcsFormHidden","DcsFormSection","DcsFormHtmlBlock","formEl","onSubmit","e2","onMounted","_unref","_hoisted_6","_hoisted_7","_hoisted_8","_resolveDynamicComponent","_hoisted_9","_hoisted_10","_cache","_hoisted_11"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../src/composables/useFormValidation.ts","../src/composables/useDcsForm.ts","../src/composables/useFormSubmission.ts","../src/schema/validate.ts","../src/loaders/yaml.ts","../src/fields/DcsFormFieldWrapper.vue","../src/fields/DcsFormText.vue","../src/fields/DcsFormTextarea.vue","../src/fields/DcsFormSelect.vue","../src/fields/DcsFormRadio.vue","../src/fields/DcsFormCheckboxGroup.vue","../src/fields/DcsFormCheckbox.vue","../src/fields/DcsFormDate.vue","../src/fields/DcsFormFile.vue","../src/fields/DcsFormHidden.vue","../src/fields/DcsFormSection.vue","../src/fields/DcsFormHtmlBlock.vue","../src/DcsForm.vue","../src/presets.ts"],"sourcesContent":["import type {\n PortalFormDefinition,\n PortalFormField,\n FormErrors,\n FormValues,\n} from '../types'\n\n/**\n * Returns true iff `field` is currently visible given the form's\n * other values. Hidden fields are never validated and never sent.\n */\nexport function isFieldVisible(\n field: PortalFormField,\n values: FormValues,\n): boolean {\n if (!field.visibleIf) return true\n const sibling = values[field.visibleIf.fieldId]\n return sibling === field.visibleIf.equals\n}\n\n/**\n * Validates a single field's value. Returns an error string or\n * `undefined` if valid. Layout-only field types (section-heading,\n * html-block) and hidden fields never produce errors.\n */\nexport function validateField(\n field: PortalFormField,\n value: unknown,\n): string | undefined {\n if (\n field.type === 'section-heading' ||\n field.type === 'html-block' ||\n field.type === 'hidden'\n ) {\n return undefined\n }\n\n const normalizedValue = typeof value === 'string' ? value.trim() : value\n const isEmpty =\n value === undefined ||\n value === null ||\n normalizedValue === '' ||\n (Array.isArray(value) && value.length === 0)\n\n if (field.required && isEmpty) {\n return `${field.label} is required`\n }\n if (isEmpty) return undefined\n\n // Type-specific built-ins\n if (field.type === 'email' && typeof normalizedValue === 'string') {\n if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(normalizedValue)) {\n return `${field.label} must be a valid email address`\n }\n }\n\n const v = field.validation\n if (!v) return undefined\n\n if (typeof normalizedValue === 'string') {\n if (v.minLength != null && normalizedValue.length < v.minLength) {\n return `${field.label} must be at least ${v.minLength} characters`\n }\n if (v.maxLength != null && normalizedValue.length > v.maxLength) {\n return `${field.label} must be at most ${v.maxLength} characters`\n }\n if (v.regex) {\n try {\n if (!new RegExp(v.regex).test(normalizedValue)) {\n return `${field.label} is invalid`\n }\n } catch {\n // bad regex in the definition — treat as no rule\n }\n }\n }\n\n if (typeof value === 'number') {\n if (v.min != null && value < v.min) {\n return `${field.label} must be at least ${v.min}`\n }\n if (v.max != null && value > v.max) {\n return `${field.label} must be at most ${v.max}`\n }\n }\n\n if (field.type === 'file' && v.accept && v.accept.length > 0) {\n const file = value as File | null\n if (file && typeof File !== 'undefined' && file instanceof File) {\n const accepted = v.accept.some((a) => {\n if (a.startsWith('.')) {\n return file.name.toLowerCase().endsWith(a.toLowerCase())\n }\n return file.type === a\n })\n if (!accepted) {\n return `${field.label} must be one of: ${v.accept.join(', ')}`\n }\n }\n }\n\n return undefined\n}\n\n/**\n * Validates an entire form. Skips fields that are not visible.\n * If `fieldIds` is supplied, only those fields are validated\n * (used for per-step validation in multi-step forms).\n */\nexport function validateForm(\n def: PortalFormDefinition,\n values: FormValues,\n fieldIds?: string[],\n): FormErrors {\n const errors: FormErrors = {}\n const ids = fieldIds ? new Set(fieldIds) : null\n for (const field of def.fields) {\n if (ids && !ids.has(field.id)) continue\n if (!isFieldVisible(field, values)) continue\n const err = validateField(field, values[field.id])\n if (err) errors[field.id] = err\n }\n return errors\n}\n\nexport function hasErrors(errors: FormErrors): boolean {\n return Object.values(errors).some((e) => !!e)\n}\n","import { computed, reactive, ref, type ComputedRef, type Ref } from 'vue'\nimport type {\n PortalFormDefinition,\n PortalFormField,\n PortalFormStep,\n FormErrors,\n FormValues,\n} from '../types'\nimport {\n hasErrors,\n isFieldVisible,\n validateForm,\n} from './useFormValidation'\n\nexport interface UseDcsFormOptions {\n definition: PortalFormDefinition\n}\n\nexport interface UseDcsFormReturn {\n values: FormValues\n errors: Ref<FormErrors>\n touched: Ref<Record<string, boolean>>\n submitting: Ref<boolean>\n submitted: Ref<boolean>\n submitError: Ref<string | null>\n /** All fields, with layout-only types preserved. */\n fields: ComputedRef<PortalFormField[]>\n /** Steps if multi-step, else null. */\n steps: ComputedRef<PortalFormStep[] | null>\n currentStepIndex: Ref<number>\n currentStep: ComputedRef<PortalFormStep | null>\n currentStepFields: ComputedRef<PortalFormField[]>\n isFirstStep: ComputedRef<boolean>\n isLastStep: ComputedRef<boolean>\n visibleFields: ComputedRef<PortalFormField[]>\n /** Sets a field value and clears that field's error. */\n setValue: (id: string, value: unknown) => void\n /** Marks a field as touched + revalidates only that field's row. */\n touch: (id: string) => void\n /** Validates the current step (multi-step) or the entire form. */\n validateCurrentScope: () => boolean\n validateAll: () => boolean\n next: () => boolean\n prev: () => void\n reset: () => void\n /** Returns the values that should actually be submitted (visible only). */\n collectSubmissionValues: () => FormValues\n}\n\nfunction defaultsFor(def: PortalFormDefinition): FormValues {\n const out: FormValues = {}\n for (const f of def.fields) {\n if (f.defaultValue !== undefined) {\n out[f.id] = f.defaultValue as unknown\n } else if (f.type === 'checkbox') {\n out[f.id] = false\n } else if (f.type === 'multiselect' || f.type === 'checkbox-group') {\n out[f.id] = [] as string[]\n } else {\n out[f.id] = ''\n }\n }\n return out\n}\n\nexport function useDcsForm(opts: UseDcsFormOptions): UseDcsFormReturn {\n const { definition } = opts\n const values = reactive<FormValues>(defaultsFor(definition))\n const errors = ref<FormErrors>({})\n const touched = ref<Record<string, boolean>>({})\n const submitting = ref(false)\n const submitted = ref(false)\n const submitError = ref<string | null>(null)\n const currentStepIndex = ref(0)\n\n const fields = computed(() => definition.fields)\n const steps = computed<PortalFormStep[] | null>(() =>\n definition.steps && definition.steps.length > 0 ? definition.steps : null,\n )\n const currentStep = computed<PortalFormStep | null>(() => {\n const s = steps.value\n return s ? (s[currentStepIndex.value] ?? null) : null\n })\n const currentStepFields = computed<PortalFormField[]>(() => {\n const step = currentStep.value\n if (!step) return definition.fields\n const ids = new Set(step.fieldIds)\n return definition.fields.filter((f) => ids.has(f.id))\n })\n const isFirstStep = computed(() => currentStepIndex.value === 0)\n const isLastStep = computed(() => {\n const s = steps.value\n return !s || currentStepIndex.value >= s.length - 1\n })\n const visibleFields = computed(() =>\n currentStepFields.value.filter((f) => isFieldVisible(f, values as FormValues)),\n )\n\n function setValue(id: string, value: unknown): void {\n ;(values as FormValues)[id] = value\n if (errors.value[id]) {\n errors.value = { ...errors.value, [id]: undefined }\n }\n }\n\n function touch(id: string): void {\n touched.value = { ...touched.value, [id]: true }\n }\n\n function validateScope(scopeFieldIds?: string[]): boolean {\n const next = validateForm(definition, values as FormValues, scopeFieldIds)\n // Merge with existing errors but only for the validated scope so we\n // don't wipe out errors from other steps.\n if (scopeFieldIds) {\n const merged = { ...errors.value }\n for (const id of scopeFieldIds) {\n merged[id] = next[id]\n }\n errors.value = merged\n } else {\n errors.value = next\n }\n return !hasErrors(errors.value)\n }\n\n function validateCurrentScope(): boolean {\n const scope = currentStep.value?.fieldIds\n return validateScope(scope)\n }\n\n function validateAll(): boolean {\n return validateScope()\n }\n\n function next(): boolean {\n if (!steps.value) return false\n if (!validateCurrentScope()) return false\n if (currentStepIndex.value < steps.value.length - 1) {\n currentStepIndex.value++\n return true\n }\n return false\n }\n\n function prev(): void {\n if (currentStepIndex.value > 0) currentStepIndex.value--\n }\n\n function reset(): void {\n const fresh = defaultsFor(definition)\n for (const k of Object.keys(values as object)) {\n delete (values as FormValues)[k]\n }\n Object.assign(values as FormValues, fresh)\n errors.value = {}\n touched.value = {}\n submitting.value = false\n submitted.value = false\n submitError.value = null\n currentStepIndex.value = 0\n }\n\n function collectSubmissionValues(): FormValues {\n const out: FormValues = {}\n for (const f of definition.fields) {\n if (\n f.type === 'section-heading' ||\n f.type === 'html-block'\n )\n continue\n if (!isFieldVisible(f, values as FormValues)) continue\n out[f.id] = (values as FormValues)[f.id]\n }\n return out\n }\n\n return {\n values: values as FormValues,\n errors,\n touched,\n submitting,\n submitted,\n submitError,\n fields,\n steps,\n currentStepIndex,\n currentStep,\n currentStepFields,\n isFirstStep,\n isLastStep,\n visibleFields,\n setValue,\n touch,\n validateCurrentScope,\n validateAll,\n next,\n prev,\n reset,\n collectSubmissionValues,\n }\n}\n","import type {\n DcsFormSubmitPayload,\n DcsFormSubmitSuccess,\n DcsFormSubmitError,\n} from '../types'\n\nexport interface SubmitOptions {\n apiBase: string\n siteSlug: string\n payload: DcsFormSubmitPayload\n /** Number of retries on 5xx / network errors. Default 1. */\n retries?: number\n /** Optional fetch implementation override (tests). */\n fetchImpl?: typeof fetch\n}\n\n/**\n * POSTs a form submission to\n * `${apiBase}/sites/{siteSlug}/forms/{formId}/submissions`.\n *\n * Uses JSON for plain values and `multipart/form-data` when any\n * value is a `File` (file-upload fields).\n */\nexport async function submitFormValues(\n opts: SubmitOptions,\n): Promise<DcsFormSubmitSuccess> {\n const { apiBase, siteSlug, payload } = opts\n const retries = opts.retries ?? 1\n const fetchImpl = opts.fetchImpl ?? fetch\n const url = `${apiBase.replace(/\\/$/, '')}/sites/${encodeURIComponent(\n siteSlug,\n )}/forms/${encodeURIComponent(payload.formId)}/submissions`\n\n const hasFile = Object.values(payload.values).some(\n (v) => typeof File !== 'undefined' && v instanceof File,\n )\n\n let lastError: Error | undefined\n let lastStatus: number | undefined\n for (let attempt = 0; attempt <= retries; attempt++) {\n try {\n const init: RequestInit = hasFile\n ? { method: 'POST', body: buildFormData(payload) }\n : {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(payload),\n }\n const res = await fetchImpl(url, init)\n lastStatus = res.status\n if (res.ok) {\n const body = await safeJson(res)\n return { payload, response: body }\n }\n // Retry only on 5xx\n if (res.status < 500) {\n const body = await safeText(res)\n const err: DcsFormSubmitError = {\n payload,\n status: res.status,\n error: new Error(`Submission failed (${res.status}): ${body}`),\n }\n throw err\n }\n lastError = new Error(`Server error ${res.status}`)\n } catch (e) {\n // If `e` is already a DcsFormSubmitError-shaped object, rethrow.\n if (e && typeof e === 'object' && 'payload' in e && 'error' in e) {\n throw e\n }\n lastError = e as Error\n }\n }\n\n throw {\n payload,\n status: lastStatus,\n error: lastError ?? new Error('Submission failed'),\n } satisfies DcsFormSubmitError\n}\n\nfunction buildFormData(payload: DcsFormSubmitPayload): FormData {\n const fd = new FormData()\n fd.append('formId', payload.formId)\n if (payload.captchaToken) fd.append('captchaToken', payload.captchaToken)\n for (const [key, value] of Object.entries(payload.values)) {\n if (value === undefined || value === null) continue\n if (value instanceof File) {\n fd.append(`values[${key}]`, value)\n } else if (Array.isArray(value)) {\n for (const item of value) fd.append(`values[${key}][]`, String(item))\n } else {\n fd.append(`values[${key}]`, String(value))\n }\n }\n return fd\n}\n\nasync function safeJson(res: Response): Promise<unknown> {\n try {\n return await res.json()\n } catch {\n return null\n }\n}\n\nasync function safeText(res: Response): Promise<string> {\n try {\n return await res.text()\n } catch {\n return ''\n }\n}\n","/**\n * Validates a loaded form definition against the form-definition JSON\n * Schema emitted by `@dcs/contracts`. Dev-only — failures are logged\n * via `console.warn` rather than thrown so a malformed YAML never\n * takes down a customer site in production.\n *\n * Schema is bundled as a snapshot of\n * `contracts/dist/form-definition.schema.json` to avoid a runtime\n * dependency on the contracts build output.\n */\nimport Ajv, { type ErrorObject, type ValidateFunction } from 'ajv'\nimport addFormats from 'ajv-formats'\nimport schema from './form-definition.schema.json'\nimport type { PortalFormDefinition } from '../types'\n\nlet validator: ValidateFunction | null = null\n\nfunction getValidator(): ValidateFunction {\n if (validator) return validator\n const ajv = new Ajv({\n allErrors: true,\n strict: false,\n discriminator: false,\n })\n addFormats(ajv)\n validator = ajv.compile(schema as object)\n return validator\n}\n\nexport interface SchemaValidationResult {\n valid: boolean\n errors: ErrorObject[]\n}\n\nexport function validateFormDefinition(\n def: PortalFormDefinition | unknown,\n): SchemaValidationResult {\n const v = getValidator()\n const valid = v(def) as boolean\n return { valid, errors: valid ? [] : (v.errors ?? []) }\n}\n\n/** Logs a `console.warn` when invalid, but only when `import.meta.env.DEV`. */\nexport function warnIfInvalid(\n formId: string,\n def: PortalFormDefinition | unknown,\n isDev: boolean,\n): SchemaValidationResult {\n const result = validateFormDefinition(def)\n if (!result.valid && isDev) {\n // eslint-disable-next-line no-console\n console.warn(\n `[@duffcloudservices/site-forms] Form \"${formId}\" failed schema validation:`,\n result.errors,\n )\n }\n return result\n}\n","import yaml from 'js-yaml'\nimport type { PortalFormDefinition } from '../types'\n\n/**\n * Eagerly load every form YAML under `/.dcs/forms/*.yaml` from the\n * consuming Vite app and parse them into PortalFormDefinition objects.\n *\n * Vite returns the modules as raw strings (when using `?raw` or the\n * default text loader for unknown extensions) or as parsed objects\n * (when `vite-plugin-yaml` is installed). This helper handles both.\n */\nexport function loadFormDefinitions(\n modules: Record<string, unknown>,\n): Record<string, PortalFormDefinition> {\n const out: Record<string, PortalFormDefinition> = {}\n for (const [path, mod] of Object.entries(modules)) {\n const formId = extractFormId(path)\n const def = parseModule(mod)\n if (def) {\n out[def.formId ?? formId] = def\n }\n }\n return out\n}\n\nfunction extractFormId(path: string): string {\n const file = path.split('/').pop() ?? path\n return file.replace(/\\.ya?ml$/i, '')\n}\n\nfunction parseModule(mod: unknown): PortalFormDefinition | null {\n if (!mod) return null\n if (typeof mod === 'string') {\n return yaml.load(mod) as PortalFormDefinition\n }\n if (typeof mod === 'object') {\n // vite-plugin-yaml returns parsed objects, possibly wrapped in\n // `{ default: ... }` when imported via `import: 'default'`.\n const maybeDefault = (mod as { default?: unknown }).default\n if (maybeDefault && typeof maybeDefault === 'object') {\n return maybeDefault as PortalFormDefinition\n }\n return mod as PortalFormDefinition\n }\n return null\n}\n\n/** Parse a single raw YAML string into a PortalFormDefinition. */\nexport function parseFormYaml(raw: string): PortalFormDefinition {\n return yaml.load(raw) as PortalFormDefinition\n}\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n error?: string\n /** Optional id to use for the input — wrappers use it for label `for=`. */\n inputId?: string\n}>()\n</script>\n\n<template>\n <div\n class=\"dcs-form-field\"\n :class=\"[`dcs-form-field--${field.type}`, `dcs-form-field--width-${field.width ?? 'full'}`]\"\n :data-form-field-key=\"field.id\"\n >\n <slot name=\"label\">\n <label\n v-if=\"field.type !== 'checkbox' && field.type !== 'hidden' && field.type !== 'section-heading' && field.type !== 'html-block'\"\n :for=\"inputId ?? `field-${field.id}`\"\n class=\"dcs-form-field__label\"\n >\n {{ field.label }}\n <span v-if=\"field.required\" aria-hidden=\"true\" class=\"dcs-form-field__required\">*</span>\n </label>\n </slot>\n\n <slot />\n\n <slot name=\"help\">\n <p v-if=\"field.helpText\" class=\"dcs-form-field__help\">{{ field.helpText }}</p>\n </slot>\n\n <slot name=\"error\">\n <p v-if=\"error\" class=\"dcs-form-field__error\" role=\"alert\">{{ error }}</p>\n </slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | number | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst inputType = computed(() => {\n switch (props.field.type) {\n case 'email':\n return 'email'\n case 'tel':\n return 'tel'\n default:\n return 'text'\n }\n})\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <slot\n name=\"input\"\n :id=\"inputId\"\n :value=\"modelValue\"\n :on-input=\"(e: Event) => emit('update:modelValue', (e.target as HTMLInputElement).value)\"\n :on-blur=\"() => emit('blur')\"\n >\n <input\n :id=\"inputId\"\n class=\"dcs-form-input\"\n :type=\"inputType\"\n :name=\"field.id\"\n :value=\"modelValue ?? ''\"\n :placeholder=\"field.placeholder\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n :aria-describedby=\"error ? `${inputId}-error` : undefined\"\n @input=\"emit('update:modelValue', ($event.target as HTMLInputElement).value)\"\n @blur=\"emit('blur')\"\n />\n </slot>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <slot\n name=\"input\"\n :id=\"inputId\"\n :value=\"modelValue\"\n :on-input=\"(e: Event) => emit('update:modelValue', (e.target as HTMLTextAreaElement).value)\"\n :on-blur=\"() => emit('blur')\"\n >\n <textarea\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-textarea\"\n :name=\"field.id\"\n :placeholder=\"field.placeholder\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n rows=\"5\"\n :value=\"modelValue ?? ''\"\n @input=\"emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)\"\n @blur=\"emit('blur')\"\n />\n </slot>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | string[] | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string | string[]]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst isMulti = computed(() => props.field.type === 'multiselect')\nconst options = computed(() => props.field.options ?? [])\n\nfunction onChange(e: Event): void {\n const sel = e.target as HTMLSelectElement\n if (isMulti.value) {\n const values: string[] = []\n for (const opt of Array.from(sel.selectedOptions)) values.push(opt.value)\n emit('update:modelValue', values)\n } else {\n emit('update:modelValue', sel.value)\n }\n}\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <slot\n name=\"input\"\n :id=\"inputId\"\n :value=\"modelValue\"\n :options=\"options\"\n :on-change=\"onChange\"\n >\n <select\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-select\"\n :name=\"field.id\"\n :required=\"field.required\"\n :multiple=\"isMulti\"\n :aria-invalid=\"!!error\"\n :value=\"modelValue ?? (isMulti ? [] : '')\"\n @change=\"onChange\"\n @blur=\"emit('blur')\"\n >\n <option v-if=\"!isMulti\" value=\"\" disabled>\n {{ field.placeholder ?? 'Select…' }}\n </option>\n <option v-for=\"opt in options\" :key=\"opt.value\" :value=\"opt.value\">\n {{ opt.label }}\n </option>\n </select>\n </slot>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst options = computed(() => props.field.options ?? [])\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <fieldset\n class=\"dcs-form-radio-group\"\n role=\"radiogroup\"\n :aria-required=\"field.required\"\n :aria-invalid=\"!!error\"\n >\n <legend class=\"sr-only\">{{ field.label }}</legend>\n <label\n v-for=\"opt in options\"\n :key=\"opt.value\"\n class=\"dcs-form-radio\"\n >\n <input\n type=\"radio\"\n :name=\"field.id\"\n :value=\"opt.value\"\n :checked=\"modelValue === opt.value\"\n @change=\"emit('update:modelValue', opt.value)\"\n />\n <span>{{ opt.label }}</span>\n </label>\n </fieldset>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string[] | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string[]]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst options = computed(() => props.field.options ?? [])\nconst current = computed(() => props.modelValue ?? [])\n\nfunction toggle(value: string, checked: boolean): void {\n const set = new Set(current.value)\n if (checked) set.add(value)\n else set.delete(value)\n emit('update:modelValue', Array.from(set))\n}\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <fieldset\n class=\"dcs-form-checkbox-group\"\n :aria-required=\"field.required\"\n :aria-invalid=\"!!error\"\n >\n <legend class=\"sr-only\">{{ field.label }}</legend>\n <label\n v-for=\"opt in options\"\n :key=\"opt.value\"\n class=\"dcs-form-checkbox\"\n >\n <input\n type=\"checkbox\"\n :name=\"`${field.id}[]`\"\n :value=\"opt.value\"\n :checked=\"current.includes(opt.value)\"\n @change=\"toggle(opt.value, ($event.target as HTMLInputElement).checked)\"\n />\n <span>{{ opt.label }}</span>\n </label>\n </fieldset>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: boolean | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: boolean]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <template #label><span class=\"sr-only\">{{ field.label }}</span></template>\n <label class=\"dcs-form-checkbox-single\" :for=\"inputId\">\n <input\n :id=\"inputId\"\n type=\"checkbox\"\n :name=\"field.id\"\n :checked=\"!!modelValue\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n @change=\"emit('update:modelValue', ($event.target as HTMLInputElement).checked)\"\n />\n <span>{{ field.label }}<span v-if=\"field.required\" aria-hidden=\"true\"> *</span></span>\n </label>\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: string | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: string]\n blur: []\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <input\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-date\"\n type=\"date\"\n :name=\"field.id\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n :value=\"modelValue ?? ''\"\n @input=\"emit('update:modelValue', ($event.target as HTMLInputElement).value)\"\n @blur=\"emit('blur')\"\n />\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport { computed } from 'vue'\nimport type { PortalFormField } from '../types'\nimport DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'\n\nconst props = defineProps<{\n field: PortalFormField\n modelValue: File | undefined\n error?: string\n}>()\n\nconst emit = defineEmits<{\n 'update:modelValue': [value: File | undefined]\n}>()\n\nconst inputId = computed(() => `field-${props.field.id}`)\nconst accept = computed(() => (props.field.validation?.accept ?? []).join(','))\n\nfunction onChange(e: Event): void {\n const target = e.target as HTMLInputElement\n emit('update:modelValue', target.files?.[0])\n}\n</script>\n\n<template>\n <DcsFormFieldWrapper :field=\"field\" :error=\"error\" :input-id=\"inputId\">\n <input\n :id=\"inputId\"\n class=\"dcs-form-input dcs-form-file\"\n type=\"file\"\n :name=\"field.id\"\n :required=\"field.required\"\n :aria-invalid=\"!!error\"\n :accept=\"accept || undefined\"\n @change=\"onChange\"\n />\n </DcsFormFieldWrapper>\n</template>\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n modelValue: string | number | undefined\n}>()\n</script>\n\n<template>\n <input\n type=\"hidden\"\n :name=\"field.id\"\n :value=\"modelValue ?? (field.defaultValue as string | number | undefined) ?? ''\"\n :data-form-field-key=\"field.id\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n}>()\n</script>\n\n<template>\n <div\n class=\"dcs-form-section\"\n :data-form-field-key=\"field.id\"\n >\n <slot>\n <h3 class=\"dcs-form-section__heading\">{{ field.label }}</h3>\n <p v-if=\"field.helpText\" class=\"dcs-form-section__help\">{{ field.helpText }}</p>\n </slot>\n </div>\n</template>\n","<script setup lang=\"ts\">\nimport type { PortalFormField } from '../types'\n\ndefineProps<{\n field: PortalFormField\n}>()\n</script>\n\n<template>\n <!--\n `html` is sanitized server-side per the schema; rendering with v-html\n is intentional. Site authors must keep portal-side sanitization on.\n -->\n <div\n class=\"dcs-form-html-block\"\n :data-form-field-key=\"field.id\"\n v-html=\"field.html ?? ''\"\n />\n</template>\n","<script setup lang=\"ts\">\nimport { computed, onMounted, ref, watch } from 'vue'\nimport type {\n DcsFormSubmitError,\n DcsFormSubmitSuccess,\n PortalFormDefinition,\n PortalFormField,\n} from './types'\nimport { useDcsForm } from './composables/useDcsForm'\nimport { submitFormValues } from './composables/useFormSubmission'\nimport { warnIfInvalid } from './schema/validate'\nimport { loadFormDefinitions } from './loaders/yaml'\n\nimport DcsFormText from './fields/DcsFormText.vue'\nimport DcsFormTextarea from './fields/DcsFormTextarea.vue'\nimport DcsFormSelect from './fields/DcsFormSelect.vue'\nimport DcsFormRadio from './fields/DcsFormRadio.vue'\nimport DcsFormCheckboxGroup from './fields/DcsFormCheckboxGroup.vue'\nimport DcsFormCheckbox from './fields/DcsFormCheckbox.vue'\nimport DcsFormDate from './fields/DcsFormDate.vue'\nimport DcsFormFile from './fields/DcsFormFile.vue'\nimport DcsFormHidden from './fields/DcsFormHidden.vue'\nimport DcsFormSection from './fields/DcsFormSection.vue'\nimport DcsFormHtmlBlock from './fields/DcsFormHtmlBlock.vue'\n\ninterface Props {\n /** Kebab-case form id; matches `.dcs/forms/<formId>.yaml`. */\n formId: string\n /** Site slug used for the submission endpoint path. */\n siteSlug?: string\n /** Override the loaded definition (used by the portal preview iframe). */\n definitionOverride?: PortalFormDefinition\n /** Override the API base URL; defaults to `VITE_DCS_PUBLIC_API`. */\n apiBase?: string\n /** Optional captcha token, attached to the submission payload. */\n captchaToken?: string\n /**\n * Optional override for the YAML modules map. Mostly for tests; in\n * real apps the build-time `import.meta.glob` call below is used.\n */\n formsModules?: Record<string, unknown>\n}\n\nconst props = defineProps<Props>()\n\nconst emit = defineEmits<{\n 'submit-success': [event: DcsFormSubmitSuccess]\n 'submit-error': [event: DcsFormSubmitError]\n 'validation-error': [errors: Record<string, string | undefined>]\n}>()\n\n// Eager glob — Vite inlines every site form at build time. Customer\n// sites should configure `vite-plugin-yaml` so YAML is parsed to an\n// object; without it the loader falls back to parsing raw strings.\nconst eagerFormsModules = (import.meta as unknown as {\n glob: (\n pattern: string,\n opts: { eager: true; import: string; query?: string },\n ) => Record<string, unknown>\n}).glob('/.dcs/forms/*.yaml', { eager: true, import: 'default' })\n\nconst definitions = computed(() =>\n loadFormDefinitions(props.formsModules ?? eagerFormsModules),\n)\n\nconst definition = computed<PortalFormDefinition | null>(() => {\n if (props.definitionOverride) return props.definitionOverride\n return definitions.value[props.formId] ?? null\n})\n\nconst isDev = !!(import.meta as unknown as { env?: { DEV?: boolean } }).env?.DEV\n\nwatch(\n definition,\n (def) => {\n if (def) warnIfInvalid(props.formId, def, isDev)\n },\n { immediate: true },\n)\n\n// We always call useDcsForm with *some* definition so hooks render\n// stably; if the form is missing we'll show an error state instead.\nconst safeDefinition = computed<PortalFormDefinition>(\n () =>\n definition.value ?? {\n formId: props.formId,\n submission: { kind: 'lead' },\n fields: [],\n },\n)\n\nconst form = useDcsForm({ definition: safeDefinition.value })\n\n// If the resolved definition changes (e.g. preview iframe updates),\n// re-create derived state by resetting.\nwatch(\n () => safeDefinition.value,\n () => form.reset(),\n)\n\nconst apiBase = computed(\n () =>\n props.apiBase ??\n (import.meta as unknown as { env?: { VITE_DCS_PUBLIC_API?: string } }).env\n ?.VITE_DCS_PUBLIC_API ??\n '',\n)\n\nconst resolvedSiteSlug = computed(\n () =>\n props.siteSlug ??\n (import.meta as unknown as { env?: { VITE_DCS_SITE_SLUG?: string } }).env\n ?.VITE_DCS_SITE_SLUG ??\n '',\n)\n\nconst submitLabel = computed(\n () => safeDefinition.value.submitLabel ?? 'Send',\n)\n\nfunction fieldComponent(field: PortalFormField) {\n switch (field.type) {\n case 'text':\n case 'email':\n case 'tel':\n return DcsFormText\n case 'textarea':\n return DcsFormTextarea\n case 'select':\n case 'multiselect':\n return DcsFormSelect\n case 'radio':\n return DcsFormRadio\n case 'checkbox-group':\n return DcsFormCheckboxGroup\n case 'checkbox':\n return DcsFormCheckbox\n case 'date':\n return DcsFormDate\n case 'file':\n return DcsFormFile\n case 'hidden':\n return DcsFormHidden\n case 'section-heading':\n return DcsFormSection\n case 'html-block':\n return DcsFormHtmlBlock\n default:\n return DcsFormText\n }\n}\n\nconst formEl = ref<HTMLFormElement | null>(null)\n\nasync function onSubmit(e: Event): Promise<void> {\n e.preventDefault()\n if (!definition.value) return\n const ok = form.validateAll()\n if (!ok) {\n emit('validation-error', form.errors.value)\n return\n }\n form.submitting.value = true\n form.submitError.value = null\n const payload = {\n formId: props.formId,\n values: form.collectSubmissionValues(),\n captchaToken: props.captchaToken,\n }\n try {\n const result = await submitFormValues({\n apiBase: apiBase.value,\n siteSlug: resolvedSiteSlug.value,\n payload,\n })\n form.submitted.value = true\n emit('submit-success', result)\n } catch (err) {\n const e2 = err as DcsFormSubmitError\n form.submitError.value = e2.error?.message ?? 'Submission failed'\n emit('submit-error', e2)\n } finally {\n form.submitting.value = false\n }\n}\n\nonMounted(() => {\n if (!definition.value) {\n // eslint-disable-next-line no-console\n console.warn(\n `[@duffcloudservices/site-forms] No form definition found for \"${props.formId}\". ` +\n `Expected a YAML at .dcs/forms/${props.formId}.yaml. ` +\n `In customer-site repos the YAML lives above the Vite root, so the ` +\n `internal auto-glob will not find it — pass formsModules explicitly: ` +\n `<DcsForm form-id=\"${props.formId}\" :forms-modules=\"dcsFormsModules\" />. ` +\n `See @duffcloudservices/site-forms README \"Vite setup\" section.`,\n )\n }\n})\n</script>\n\n<template>\n <div v-if=\"!definition\" class=\"dcs-form dcs-form--missing\" :data-form-key=\"formId\">\n <slot name=\"missing\" :form-id=\"formId\">\n <p>Form &ldquo;{{ formId }}&rdquo; is not configured.</p>\n </slot>\n </div>\n\n <div v-else-if=\"form.submitted.value\" class=\"dcs-form dcs-form--success\" :data-form-key=\"formId\">\n <slot name=\"success\" :definition=\"definition\">\n <p>{{ definition.successMessage ?? 'Thanks — we received your message.' }}</p>\n </slot>\n </div>\n\n <form\n v-else\n ref=\"formEl\"\n class=\"dcs-form\"\n :data-form-key=\"formId\"\n novalidate\n @submit=\"onSubmit\"\n >\n <slot name=\"header\" :definition=\"definition\" />\n\n <div v-if=\"form.steps.value\" class=\"dcs-form__progress\" aria-live=\"polite\">\n <slot\n name=\"progress\"\n :current=\"form.currentStepIndex.value\"\n :total=\"form.steps.value.length\"\n :step=\"form.currentStep.value\"\n >\n <p>\n Step {{ form.currentStepIndex.value + 1 }} of\n {{ form.steps.value.length }}\n <template v-if=\"form.currentStep.value\">\n — {{ form.currentStep.value.title }}\n </template>\n </p>\n </slot>\n </div>\n\n <div class=\"dcs-form__fields\">\n <component\n :is=\"fieldComponent(field)\"\n v-for=\"field in form.visibleFields.value\"\n :key=\"field.id\"\n :field=\"field\"\n :model-value=\"form.values[field.id]\"\n :error=\"form.errors.value[field.id]\"\n @update:model-value=\"(v: unknown) => form.setValue(field.id, v)\"\n @blur=\"form.touch(field.id)\"\n />\n </div>\n\n <div v-if=\"form.submitError.value\" class=\"dcs-form__submit-error\" role=\"alert\">\n {{ form.submitError.value }}\n </div>\n\n <div class=\"dcs-form__actions\">\n <slot\n name=\"actions\"\n :is-first-step=\"form.isFirstStep.value\"\n :is-last-step=\"form.isLastStep.value\"\n :submitting=\"form.submitting.value\"\n :prev=\"form.prev\"\n :next=\"form.next\"\n >\n <button\n v-if=\"form.steps.value && !form.isFirstStep.value\"\n type=\"button\"\n class=\"dcs-form__btn dcs-form__btn--prev\"\n @click=\"form.prev()\"\n >\n Previous\n </button>\n <button\n v-if=\"form.steps.value && !form.isLastStep.value\"\n type=\"button\"\n class=\"dcs-form__btn dcs-form__btn--next\"\n @click=\"form.next()\"\n >\n Next\n </button>\n <button\n v-if=\"!form.steps.value || form.isLastStep.value\"\n type=\"submit\"\n class=\"dcs-form__btn dcs-form__btn--submit\"\n :disabled=\"form.submitting.value\"\n >\n {{ form.submitting.value ? 'Sending…' : submitLabel }}\n </button>\n </slot>\n </div>\n </form>\n</template>\n","import type { PortalFormDefinition, PortalFormField } from './types'\n\nexport type StandardFormPreset = 'contact' | 'revenue-contractor' | 'resume-submission' | 'custom'\n\nexport interface BuildStandardFormOptions {\n formId?: string\n submitLabel?: string\n successMessage?: string\n leadCategory?: string\n}\n\nexport const STANDARD_FORM_PRESET_META: Record<StandardFormPreset, { label: string; description: string }> = {\n contact: {\n label: 'Contact',\n description: 'Canonical contact form with standard contact fields.',\n },\n 'revenue-contractor': {\n label: 'Revenue contractor',\n description: 'Contact fields plus a required project summary and optional image upload.',\n },\n 'resume-submission': {\n label: 'Resume submission',\n description: 'Candidate contact fields plus a required resume upload.',\n },\n custom: {\n label: 'Custom / free-form',\n description: 'Flexible starter that stays fully editable for questionnaires and bespoke intake.',\n },\n}\n\nconst buildContactFields = (): PortalFormField[] => [\n { id: 'name', type: 'text', role: 'contact-name', label: 'Full name', required: true, width: 'full' },\n { id: 'email', type: 'email', role: 'contact-email', label: 'Email', required: true, width: 'half' },\n { id: 'phone', type: 'tel', role: 'contact-phone', label: 'Phone', width: 'half' },\n { id: 'company', type: 'text', role: 'contact-company', label: 'Company', width: 'full' },\n {\n id: 'message',\n type: 'textarea',\n role: 'message',\n label: 'How can we help?',\n required: true,\n width: 'full',\n validation: { minLength: 10, maxLength: 2000 },\n },\n]\n\nconst buildRevenueContractorFields = (): PortalFormField[] => [\n { id: 'name', type: 'text', role: 'contact-name', label: 'Full name', required: true, width: 'full' },\n { id: 'email', type: 'email', role: 'contact-email', label: 'Email', required: true, width: 'half' },\n { id: 'phone', type: 'tel', role: 'contact-phone', label: 'Phone', required: true, width: 'half' },\n { id: 'company', type: 'text', role: 'contact-company', label: 'Company', width: 'full' },\n {\n id: 'message',\n type: 'textarea',\n role: 'summary',\n label: 'Project summary',\n helpText: 'Tell us what you want built, repaired, or updated.',\n required: true,\n width: 'full',\n validation: { minLength: 20, maxLength: 4000 },\n },\n {\n id: 'project-image',\n type: 'file',\n role: 'image-attachments',\n label: 'Reference image',\n helpText: 'Optional. Upload one photo or inspiration image to help us understand the request.',\n width: 'full',\n validation: { accept: ['image/*'] },\n },\n]\n\nconst buildResumeSubmissionFields = (): PortalFormField[] => [\n { id: 'name', type: 'text', role: 'contact-name', label: 'Full name', required: true, width: 'full' },\n { id: 'email', type: 'email', role: 'contact-email', label: 'Email', required: true, width: 'half' },\n { id: 'phone', type: 'tel', role: 'contact-phone', label: 'Phone', width: 'half' },\n { id: 'position', type: 'text', label: 'Role you are applying for', width: 'full' },\n {\n id: 'message',\n type: 'textarea',\n role: 'message',\n label: 'Anything else we should know?',\n width: 'full',\n validation: { maxLength: 2000 },\n },\n {\n id: 'resume',\n type: 'file',\n role: 'resume',\n label: 'Resume',\n required: true,\n width: 'full',\n validation: {\n accept: [\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n ],\n },\n },\n]\n\nconst buildCustomFields = (): PortalFormField[] => [\n { id: 'name', type: 'text', label: 'Full name', required: true, width: 'full' },\n { id: 'email', type: 'email', label: 'Email', required: true, width: 'half' },\n { id: 'phone', type: 'tel', label: 'Phone', width: 'half' },\n {\n id: 'message',\n type: 'textarea',\n label: 'Message',\n required: true,\n width: 'full',\n validation: { minLength: 10, maxLength: 2000 },\n },\n]\n\nexport function buildStandardFormDefinition(\n preset: StandardFormPreset,\n options: BuildStandardFormOptions = {},\n): PortalFormDefinition {\n const resolvedPreset = preset ?? 'custom'\n\n switch (resolvedPreset) {\n case 'contact':\n return {\n formId: options.formId ?? 'contact',\n formKind: 'contact',\n submitLabel: options.submitLabel ?? 'Send',\n successMessage: options.successMessage ?? 'Thanks — we’ll be in touch shortly.',\n submission: {\n kind: 'lead',\n ...(options.leadCategory ? { category: options.leadCategory } : {}),\n },\n fields: buildContactFields(),\n }\n\n case 'revenue-contractor':\n return {\n formId: options.formId ?? 'contact',\n formKind: 'revenue-contractor',\n submitLabel: options.submitLabel ?? 'Request estimate',\n successMessage: options.successMessage ?? 'Thanks — we’ll review your project and follow up soon.',\n submission: {\n kind: 'lead',\n ...(options.leadCategory ? { category: options.leadCategory } : {}),\n },\n attachmentPolicy: {\n expected: true,\n required: false,\n maxFiles: 5,\n accept: ['image/*'],\n },\n fields: buildRevenueContractorFields(),\n }\n\n case 'resume-submission':\n return {\n formId: options.formId ?? 'resume',\n formKind: 'resume-submission',\n submitLabel: options.submitLabel ?? 'Submit resume',\n successMessage: options.successMessage ?? 'Thanks — your application was received.',\n submission: {\n kind: 'lead',\n ...(options.leadCategory ? { category: options.leadCategory } : {}),\n },\n attachmentPolicy: {\n expected: true,\n required: true,\n maxFiles: 1,\n accept: [\n 'application/pdf',\n 'application/msword',\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n ],\n },\n fields: buildResumeSubmissionFields(),\n }\n\n default:\n return {\n formId: options.formId ?? 'custom-form',\n formKind: 'freeform',\n submitLabel: options.submitLabel ?? 'Send',\n successMessage: options.successMessage ?? 'Thanks — we received your submission.',\n submission: {\n kind: 'lead',\n ...(options.leadCategory ? { category: options.leadCategory } : {}),\n },\n fields: buildCustomFields(),\n }\n }\n}\n"],"names":["isFieldVisible","field","values","validateField","value","normalizedValue","isEmpty","v","file","a","validateForm","def","fieldIds","errors","ids","err","hasErrors","e","defaultsFor","out","f","useDcsForm","opts","definition","reactive","ref","touched","submitting","submitted","submitError","currentStepIndex","fields","computed","steps","currentStep","s","currentStepFields","step","isFirstStep","isLastStep","visibleFields","setValue","id","touch","validateScope","scopeFieldIds","next","merged","validateCurrentScope","scope","validateAll","prev","reset","fresh","k","collectSubmissionValues","submitFormValues","apiBase","siteSlug","payload","retries","fetchImpl","url","hasFile","lastError","lastStatus","attempt","init","buildFormData","res","body","safeJson","safeText","fd","key","item","validator","getValidator","ajv","Ajv","addFormats","schema","validateFormDefinition","valid","warnIfInvalid","formId","isDev","result","loadFormDefinitions","modules","path","mod","extractFormId","parseModule","yaml","maybeDefault","parseFormYaml","raw","_createElementBlock","_normalizeClass","__props","_renderSlot","_ctx","_hoisted_3","_openBlock","_hoisted_4","_toDisplayString","_hoisted_5","props","emit","__emit","inputId","inputType","_createBlock","DcsFormFieldWrapper","_createElementVNode","$event","isMulti","options","onChange","sel","opt","_hoisted_2","_Fragment","_renderList","current","toggle","checked","set","_hoisted_1","_createTextVNode","accept","target","eagerFormsModules","definitions","watch","safeDefinition","form","__vite_import_meta_env__","resolvedSiteSlug","submitLabel","fieldComponent","DcsFormText","DcsFormTextarea","DcsFormSelect","DcsFormRadio","DcsFormCheckboxGroup","DcsFormCheckbox","DcsFormDate","DcsFormFile","DcsFormHidden","DcsFormSection","DcsFormHtmlBlock","formEl","onSubmit","e2","onMounted","_unref","_resolveDynamicComponent","_hoisted_6","_hoisted_7","_cache","_hoisted_8","STANDARD_FORM_PRESET_META","buildContactFields","buildRevenueContractorFields","buildResumeSubmissionFields","buildCustomFields","buildStandardFormDefinition","preset"],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import { PortalFormDefinition } from './types';
2
+ export type StandardFormPreset = 'contact' | 'revenue-contractor' | 'resume-submission' | 'custom';
3
+ export interface BuildStandardFormOptions {
4
+ formId?: string;
5
+ submitLabel?: string;
6
+ successMessage?: string;
7
+ leadCategory?: string;
8
+ }
9
+ export declare const STANDARD_FORM_PRESET_META: Record<StandardFormPreset, {
10
+ label: string;
11
+ description: string;
12
+ }>;
13
+ export declare function buildStandardFormDefinition(preset: StandardFormPreset, options?: BuildStandardFormOptions): PortalFormDefinition;
@@ -1 +1 @@
1
- .dcs-form{display:grid;gap:1.5rem;width:100%}.dcs-form__header{display:grid;gap:.5rem}.dcs-form__title{margin:0;font-size:clamp(1.875rem,2vw + 1.25rem,2.5rem);font-weight:700;line-height:1.1}.dcs-form__description{margin:0;font-size:1rem;line-height:1.6}.dcs-form__progress{font-size:.95rem}.dcs-form__fields{display:grid;gap:1rem}.dcs-form-field{display:grid;gap:.5rem}.dcs-form-field__label{display:inline-flex;align-items:center;gap:.2rem;font-size:.95rem;font-weight:600;line-height:1.4}.dcs-form-field__required{font-weight:700}.dcs-form-input{width:100%;min-height:3rem;padding:.75rem .95rem;font:inherit;line-height:1.5;color:inherit;background:#fffffff5;border:1px solid rgba(15,23,42,.16);border-radius:.875rem;box-shadow:0 1px 2px #0f172a14;transition:border-color .15s ease,box-shadow .15s ease,background-color .15s ease}.dcs-form-input::placeholder{color:#475569bf}.dcs-form-input:hover{border-color:#2563eb61}.dcs-form-input:focus,.dcs-form-input:focus-visible{outline:none;border-color:#2563ebbf;box-shadow:0 0 0 4px #3b82f62e}.dcs-form-input[aria-invalid=true]{border-color:#dc2626b8;box-shadow:0 0 0 4px #f871712e}.dcs-form-textarea{min-height:10rem;resize:vertical}.dcs-form-select{appearance:none}.dcs-form-field__help,.dcs-form-field__error,.dcs-form__submit-error{margin:0;font-size:.875rem;line-height:1.5}.dcs-form-field__error,.dcs-form__submit-error{color:#b91c1c}.dcs-form__submit-error{padding:.85rem 1rem;border:1px solid rgba(220,38,38,.28);border-radius:.875rem;background:#fef2f2eb}.dcs-form__actions{display:flex;flex-wrap:wrap;gap:.75rem}.dcs-form__btn{appearance:none;border:0;border-radius:9999px;padding:.85rem 1.5rem;font:inherit;font-weight:700;line-height:1;cursor:pointer;transition:transform .15s ease,box-shadow .15s ease,opacity .15s ease}.dcs-form__btn:hover{transform:translateY(-1px)}.dcs-form__btn:focus,.dcs-form__btn:focus-visible{outline:none;box-shadow:0 0 0 4px #3b82f638}.dcs-form__btn:disabled{opacity:.7;cursor:not-allowed;transform:none}.dcs-form__btn--submit,.dcs-form__btn--next{color:#fff;background:linear-gradient(135deg,#1d4ed8,#2563eb);box-shadow:0 10px 24px #2563eb38}.dcs-form__btn--prev{color:#0f172a;background:#ffffffeb;box-shadow:0 8px 20px #0f172a1f}.dcs-form-checkbox-group,.dcs-form-radio-group{display:grid;gap:.75rem}.dcs-form-checkbox,.dcs-form-radio,.dcs-form-checkbox-single{display:flex;align-items:flex-start;gap:.65rem}.dcs-form-checkbox-single input,.dcs-form-checkbox input,.dcs-form-radio input{margin-top:.2rem}.dcs-form-section{display:grid;gap:.4rem}.dcs-form-section__heading,.dcs-form-section__help,.dcs-form--missing p,.dcs-form--success p{margin:0}@media(min-width:768px){.dcs-form__fields{grid-template-columns:repeat(2,minmax(0,1fr));gap:1.25rem}.dcs-form-field--width-half{grid-column:span 1}.dcs-form-field--width-full,.dcs-form-field--width-auto,.dcs-form-field--textarea,.dcs-form-field--checkbox,.dcs-form-field--checkbox-group,.dcs-form-field--radio,.dcs-form-field--section-heading,.dcs-form-field--html-block,.dcs-form-field--file{grid-column:1 / -1}}
1
+ .dcs-form{display:grid;gap:1.5rem;width:100%}.dcs-form__header{display:grid;gap:.5rem}.dcs-form__title{margin:0;font-size:clamp(1.875rem,2vw + 1.25rem,2.5rem);font-weight:700;line-height:1.1}.dcs-form__description{margin:0;font-size:1rem;line-height:1.6}.dcs-form__progress{font-size:.95rem}.dcs-form__fields{display:grid;gap:1rem}.dcs-form-field{display:grid;gap:.5rem}.dcs-form-field__label{display:inline-flex;align-items:center;gap:.2rem;font-size:.95rem;font-weight:600;line-height:1.4}.dcs-form-field__required{font-weight:700}.dcs-form-input{width:100%;min-height:3rem;padding:.75rem .95rem;font:inherit;line-height:1.5;color:inherit;background:#fffffff5;border:1px solid rgba(15,23,42,.16);border-radius:.875rem;box-shadow:0 1px 2px #0f172a14;transition:border-color .15s ease,box-shadow .15s ease,background-color .15s ease}.dcs-form-input::placeholder{color:#475569bf}.dcs-form-input:hover{border-color:#2563eb61}.dcs-form-input:focus,.dcs-form-input:focus-visible{outline:none;border-color:#2563ebbf;box-shadow:0 0 0 4px #3b82f62e}.dcs-form-input[aria-invalid=true]{border-color:#dc2626b8;box-shadow:0 0 0 4px #f871712e}.dcs-form-textarea{min-height:10rem;resize:vertical}.dcs-form-file{min-height:3.25rem;padding:.5rem .75rem;cursor:pointer}.dcs-form-file::file-selector-button{margin:0 .85rem 0 0;border:0;border-radius:9999px;padding:.65rem 1rem;font:inherit;font-weight:700;line-height:1;color:#fff;background:linear-gradient(135deg,#1d4ed8,#2563eb);box-shadow:0 8px 18px #2563eb33;cursor:pointer;transition:transform .15s ease,box-shadow .15s ease,opacity .15s ease}.dcs-form-file:hover::file-selector-button{transform:translateY(-1px);box-shadow:0 10px 22px #2563eb42}.dcs-form-file:disabled,.dcs-form-file:disabled::file-selector-button{cursor:not-allowed;opacity:.7}.dcs-form-select{appearance:none}.dcs-form-field__help,.dcs-form-field__error,.dcs-form__submit-error{margin:0;font-size:.875rem;line-height:1.5}.dcs-form-field__error,.dcs-form__submit-error{color:#b91c1c}.dcs-form__submit-error{padding:.85rem 1rem;border:1px solid rgba(220,38,38,.28);border-radius:.875rem;background:#fef2f2eb}.dcs-form__actions{display:flex;flex-wrap:wrap;gap:.75rem}.dcs-form__btn{appearance:none;border:0;border-radius:9999px;padding:.85rem 1.5rem;font:inherit;font-weight:700;line-height:1;cursor:pointer;transition:transform .15s ease,box-shadow .15s ease,opacity .15s ease}.dcs-form__btn:hover{transform:translateY(-1px)}.dcs-form__btn:focus,.dcs-form__btn:focus-visible{outline:none;box-shadow:0 0 0 4px #3b82f638}.dcs-form__btn:disabled{opacity:.7;cursor:not-allowed;transform:none}.dcs-form__btn--submit,.dcs-form__btn--next{color:#fff;background:linear-gradient(135deg,#1d4ed8,#2563eb);box-shadow:0 10px 24px #2563eb38}.dcs-form__btn--prev{color:#0f172a;background:#ffffffeb;box-shadow:0 8px 20px #0f172a1f}.dcs-form-checkbox-group,.dcs-form-radio-group{display:grid;gap:.75rem}.dcs-form-checkbox,.dcs-form-radio,.dcs-form-checkbox-single{display:flex;align-items:flex-start;gap:.65rem}.dcs-form-checkbox-single input,.dcs-form-checkbox input,.dcs-form-radio input{margin-top:.2rem}.dcs-form-section{display:grid;gap:.4rem}.dcs-form-section__heading,.dcs-form-section__help,.dcs-form--missing p,.dcs-form--success p{margin:0}@media(min-width:768px){.dcs-form__fields{grid-template-columns:repeat(2,minmax(0,1fr));gap:1.25rem}.dcs-form-field--width-half{grid-column:span 1}.dcs-form-field--width-full,.dcs-form-field--width-auto,.dcs-form-field--textarea,.dcs-form-field--checkbox,.dcs-form-field--checkbox-group,.dcs-form-field--radio,.dcs-form-field--section-heading,.dcs-form-field--html-block,.dcs-form-field--file{grid-column:1 / -1}}
package/dist/types.d.ts CHANGED
@@ -15,6 +15,15 @@
15
15
  */
16
16
  export type PortalFormFieldType = 'text' | 'email' | 'tel' | 'textarea' | 'select' | 'multiselect' | 'radio' | 'checkbox' | 'checkbox-group' | 'date' | 'file' | 'hidden' | 'section-heading' | 'html-block';
17
17
  export type PortalFormFieldWidth = 'full' | 'half' | 'third';
18
+ export type PortalFormKind = 'freeform' | 'contact' | 'revenue-contractor' | 'resume-submission';
19
+ export type PortalFormFieldRole = 'custom' | 'contact-name' | 'contact-email' | 'contact-phone' | 'contact-company' | 'subject' | 'message' | 'summary' | 'image-attachments' | 'resume' | 'consent';
20
+ export interface PortalFormAttachmentPolicy {
21
+ expected?: boolean;
22
+ required?: boolean;
23
+ maxFiles?: number;
24
+ maxFileSizeBytes?: number;
25
+ accept?: string[];
26
+ }
18
27
  export interface PortalFormFieldOption {
19
28
  value: string;
20
29
  label: string;
@@ -39,6 +48,7 @@ export interface PortalFormField {
39
48
  placeholder?: string;
40
49
  defaultValue?: string | number | boolean | string[];
41
50
  required?: boolean;
51
+ role?: PortalFormFieldRole;
42
52
  width?: PortalFormFieldWidth;
43
53
  options?: PortalFormFieldOption[];
44
54
  validation?: PortalFormFieldValidation;
@@ -75,11 +85,11 @@ export interface PortalFormSubmissionWebhookConfig {
75
85
  export type PortalFormSubmissionConfig = PortalFormSubmissionLeadConfig | PortalFormSubmissionEmailConfig | PortalFormSubmissionWebhookConfig;
76
86
  export interface PortalFormDefinition {
77
87
  formId: string;
78
- title: string;
79
- description?: string;
88
+ formKind?: PortalFormKind;
80
89
  submitLabel?: string;
81
90
  successMessage?: string;
82
91
  submission: PortalFormSubmissionConfig;
92
+ attachmentPolicy?: PortalFormAttachmentPolicy;
83
93
  steps?: PortalFormStep[];
84
94
  fields: PortalFormField[];
85
95
  version?: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duffcloudservices/site-forms",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Shared <DcsForm/> runtime for DCS customer sites — renders managed form definitions from .dcs/forms/<formId>.yaml",
5
5
  "type": "module",
6
6
  "files": [
package/src/DcsForm.vue CHANGED
@@ -84,7 +84,6 @@ const safeDefinition = computed<PortalFormDefinition>(
84
84
  () =>
85
85
  definition.value ?? {
86
86
  formId: props.formId,
87
- title: '',
88
87
  submission: { kind: 'lead' },
89
88
  fields: [],
90
89
  },
@@ -221,14 +220,7 @@ onMounted(() => {
221
220
  novalidate
222
221
  @submit="onSubmit"
223
222
  >
224
- <slot name="header" :definition="definition">
225
- <header class="dcs-form__header">
226
- <h2 class="dcs-form__title">{{ definition.title }}</h2>
227
- <p v-if="definition.description" class="dcs-form__description">
228
- {{ definition.description }}
229
- </p>
230
- </header>
231
- </slot>
223
+ <slot name="header" :definition="definition" />
232
224
 
233
225
  <div v-if="form.steps.value" class="dcs-form__progress" aria-live="polite">
234
226
  <slot
@@ -8,7 +8,6 @@ function makeDef(
8
8
  ): PortalFormDefinition {
9
9
  return {
10
10
  formId: 'contact',
11
- title: 'Contact',
12
11
  submission: { kind: 'lead' },
13
12
  fields: [
14
13
  { id: 'name', type: 'text', label: 'Name', required: true },
@@ -5,7 +5,6 @@ import type { PortalFormDefinition } from '../types'
5
5
 
6
6
  const def: PortalFormDefinition = {
7
7
  formId: 'wiz',
8
- title: 'Wizard',
9
8
  submission: { kind: 'lead' },
10
9
  steps: [
11
10
  { id: 's1', title: 'One', fieldIds: ['a'] },
@@ -0,0 +1,64 @@
1
+ import { describe, expect, it } from 'vitest'
2
+
3
+ import { buildStandardFormDefinition } from '../presets'
4
+
5
+ describe('buildStandardFormDefinition', () => {
6
+ it('builds the standard contact scaffold with contact-compatible field ids', () => {
7
+ const definition = buildStandardFormDefinition('contact')
8
+
9
+ expect(definition.formId).toBe('contact')
10
+ expect(definition.formKind).toBe('contact')
11
+ expect(definition.fields.map(field => field.id)).toEqual(['name', 'email', 'phone', 'company', 'message'])
12
+ expect(definition.fields.map(field => field.role)).toEqual([
13
+ 'contact-name',
14
+ 'contact-email',
15
+ 'contact-phone',
16
+ 'contact-company',
17
+ 'message',
18
+ ])
19
+ })
20
+
21
+ it('builds the revenue contractor scaffold with project summary and image upload', () => {
22
+ const definition = buildStandardFormDefinition('revenue-contractor')
23
+
24
+ expect(definition.formKind).toBe('revenue-contractor')
25
+ expect(definition.attachmentPolicy).toEqual(expect.objectContaining({
26
+ expected: true,
27
+ accept: ['image/*'],
28
+ }))
29
+ expect(definition.fields.map(field => field.id)).toContain('project-image')
30
+ expect(definition.fields.find(field => field.id === 'message')?.label).toBe('Project summary')
31
+ expect(definition.fields.find(field => field.id === 'message')?.role).toBe('summary')
32
+ expect(definition.fields.find(field => field.id === 'project-image')?.role).toBe('image-attachments')
33
+ })
34
+
35
+ it('builds the resume submission scaffold with a required resume field', () => {
36
+ const definition = buildStandardFormDefinition('resume-submission')
37
+ const resumeField = definition.fields.find(field => field.id === 'resume')
38
+
39
+ expect(definition.formId).toBe('resume')
40
+ expect(definition.formKind).toBe('resume-submission')
41
+ expect(definition.attachmentPolicy).toEqual(expect.objectContaining({
42
+ expected: true,
43
+ required: true,
44
+ maxFiles: 1,
45
+ }))
46
+ expect(resumeField?.type).toBe('file')
47
+ expect(resumeField?.required).toBe(true)
48
+ expect(resumeField?.role).toBe('resume')
49
+ })
50
+
51
+ it('allows callers to override submission metadata', () => {
52
+ const definition = buildStandardFormDefinition('contact', {
53
+ formId: 'general-question',
54
+ submitLabel: 'Send question',
55
+ successMessage: 'Thanks!',
56
+ leadCategory: 'contractor',
57
+ })
58
+
59
+ expect(definition.formId).toBe('general-question')
60
+ expect(definition.submitLabel).toBe('Send question')
61
+ expect(definition.successMessage).toBe('Thanks!')
62
+ expect(definition.submission).toEqual({ kind: 'lead', category: 'contractor' })
63
+ })
64
+ })
@@ -4,7 +4,6 @@ import type { PortalFormDefinition } from '../types'
4
4
 
5
5
  const valid: PortalFormDefinition = {
6
6
  formId: 'contact',
7
- title: 'Contact',
8
7
  submission: { kind: 'lead' },
9
8
  fields: [
10
9
  { id: 'name', type: 'text', label: 'Name', required: true },
@@ -1,11 +1,11 @@
1
1
  import { describe, it, expect, vi } from 'vitest'
2
2
  import { mount, flushPromises } from '@vue/test-utils'
3
3
  import DcsForm from '../DcsForm.vue'
4
+ import { submitFormValues } from '../composables/useFormSubmission'
4
5
  import type { PortalFormDefinition } from '../types'
5
6
 
6
7
  const def: PortalFormDefinition = {
7
8
  formId: 'contact',
8
- title: 'Contact',
9
9
  submission: { kind: 'lead' },
10
10
  fields: [
11
11
  { id: 'name', type: 'text', label: 'Name', required: true },
@@ -44,7 +44,7 @@ describe('submission happy path', () => {
44
44
 
45
45
  expect(fetchMock).toHaveBeenCalledTimes(1)
46
46
  const [url, init] = fetchMock.mock.calls[0]
47
- expect(url).toBe('https://api.example.com/public/sites/kept/form-submissions')
47
+ expect(url).toBe('https://api.example.com/sites/kept/forms/contact/submissions')
48
48
  expect((init as RequestInit).method).toBe('POST')
49
49
  expect(((init as RequestInit).headers as Record<string, string>)['Content-Type']).toBe(
50
50
  'application/json',
@@ -74,4 +74,42 @@ describe('submission happy path', () => {
74
74
  await flushPromises()
75
75
  expect(wrapper.emitted('validation-error')?.length).toBe(1)
76
76
  })
77
+
78
+ it('uses multipart form data for file submissions on the managed-form route', async () => {
79
+ const fetchMock = vi.fn().mockResolvedValue({
80
+ ok: true,
81
+ status: 202,
82
+ json: async () => ({ status: 'ok' }),
83
+ })
84
+ const file = new File(['hello'], 'estimate.txt', { type: 'text/plain' })
85
+
86
+ await submitFormValues({
87
+ apiBase: 'https://api.example.com/api/v1/',
88
+ siteSlug: 'handyman-bryan',
89
+ fetchImpl: fetchMock as unknown as typeof fetch,
90
+ payload: {
91
+ formId: 'quote request',
92
+ values: {
93
+ name: 'Jane',
94
+ attachment: file,
95
+ services: ['drywall', 'paint'],
96
+ },
97
+ captchaToken: 'tok-file',
98
+ },
99
+ })
100
+
101
+ expect(fetchMock).toHaveBeenCalledTimes(1)
102
+ const [url, init] = fetchMock.mock.calls[0]
103
+ expect(url).toBe(
104
+ 'https://api.example.com/api/v1/sites/handyman-bryan/forms/quote%20request/submissions',
105
+ )
106
+ expect(((init as RequestInit).headers as Record<string, string> | undefined)?.['Content-Type']).toBeUndefined()
107
+ const body = (init as RequestInit).body as FormData
108
+ expect(body).toBeInstanceOf(FormData)
109
+ expect(body.get('formId')).toBe('quote request')
110
+ expect(body.get('captchaToken')).toBe('tok-file')
111
+ expect(body.get('values[name]')).toBe('Jane')
112
+ expect(body.get('values[attachment]')).toBe(file)
113
+ expect(body.getAll('values[services][]')).toEqual(['drywall', 'paint'])
114
+ })
77
115
  })