@duffcloudservices/site-forms 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -260
- package/dist/index.js.map +1 -1
- package/dist/site-forms.css +1 -0
- package/package.json +72 -73
- package/src/DcsForm.vue +303 -303
- package/src/__tests__/fields.test.ts +82 -82
- package/src/__tests__/multi-step.test.ts +46 -46
- package/src/__tests__/schema.test.ts +42 -42
- package/src/__tests__/style-import.test.ts +9 -0
- package/src/__tests__/submission.test.ts +77 -77
- package/src/__tests__/visible-if.test.ts +111 -111
- package/src/composables/useDcsForm.ts +201 -201
- package/src/composables/useFormSubmission.ts +113 -113
- package/src/composables/useFormValidation.ts +127 -127
- package/src/fields/DcsFormCheckbox.vue +35 -35
- package/src/fields/DcsFormCheckboxGroup.vue +52 -52
- package/src/fields/DcsFormDate.vue +34 -34
- package/src/fields/DcsFormFieldWrapper.vue +39 -39
- package/src/fields/DcsFormFile.vue +38 -38
- package/src/fields/DcsFormHidden.vue +17 -17
- package/src/fields/DcsFormHtmlBlock.vue +19 -19
- package/src/fields/DcsFormRadio.vue +45 -45
- package/src/fields/DcsFormSection.vue +19 -19
- package/src/fields/DcsFormSelect.vue +62 -62
- package/src/fields/DcsFormText.vue +54 -54
- package/src/fields/DcsFormTextarea.vue +43 -43
- package/src/index.ts +53 -51
- package/src/loaders/yaml.ts +51 -51
- package/src/schema/validate.ts +58 -58
- package/src/shims.d.ts +10 -10
- package/src/style.css +221 -0
- package/src/types.ts +140 -140
package/README.md
CHANGED
|
@@ -1,260 +1,260 @@
|
|
|
1
|
-
# @duffcloudservices/site-forms
|
|
2
|
-
|
|
3
|
-
Shared `<DcsForm/>` runtime for DCS customer sites. Renders a managed
|
|
4
|
-
form definition (created in the portal Form Manager) from a build-time
|
|
5
|
-
`.dcs/forms/<formId>.yaml` snapshot, validates user input, and posts
|
|
6
|
-
submissions to the public site-forms API.
|
|
7
|
-
|
|
8
|
-
This is the single import surface for managed-form runtime code on
|
|
9
|
-
customer sites — do not redefine field components per site.
|
|
10
|
-
|
|
11
|
-
> **Inside the `dcs-again` workspace** the package is also reachable
|
|
12
|
-
> as `@duffcloudservices/site-forms` via `workspace:*` (the workspace
|
|
13
|
-
> name and the published name are the same).
|
|
14
|
-
> **In sibling customer-site repos** install the published package
|
|
15
|
-
> from public npm — see [`PUBLISHING.md`](./PUBLISHING.md) for the
|
|
16
|
-
> consumption story and registry setup.
|
|
17
|
-
|
|
18
|
-
## Install
|
|
19
|
-
|
|
20
|
-
### In a workspace app (inside `dcs-again`)
|
|
21
|
-
|
|
22
|
-
```jsonc
|
|
23
|
-
// portal/package.json
|
|
24
|
-
{
|
|
25
|
-
"dependencies": {
|
|
26
|
-
"@duffcloudservices/site-forms": "workspace:*"
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
Then `pnpm install` from the repo root.
|
|
32
|
-
|
|
33
|
-
### In a sibling customer-site repo (e.g. `ktbraunlaw`, `kept`)
|
|
34
|
-
|
|
35
|
-
```powershell
|
|
36
|
-
pnpm add @duffcloudservices/site-forms
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
See [`PUBLISHING.md`](./PUBLISHING.md) for the registry, version, and
|
|
40
|
-
release-workflow details.
|
|
41
|
-
|
|
42
|
-
## Vite setup
|
|
43
|
-
|
|
44
|
-
Three pieces are required in the consuming site:
|
|
45
|
-
|
|
46
|
-
1. **`vite-plugin-yaml`** so YAML modules return parsed objects:
|
|
47
|
-
|
|
48
|
-
```ts
|
|
49
|
-
// vite.config.ts
|
|
50
|
-
import yaml from '@modyfi/vite-plugin-yaml'
|
|
51
|
-
|
|
52
|
-
export default defineConfig({
|
|
53
|
-
plugins: [vue(), yaml()],
|
|
54
|
-
})
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
Without it, the loader falls back to parsing raw strings via
|
|
58
|
-
`js-yaml`, which works but pays the parse cost at boot.
|
|
59
|
-
|
|
60
|
-
2. **A `formsModules` loader** in your site that does the
|
|
61
|
-
`import.meta.glob` from a path Vite can resolve (see below).
|
|
62
|
-
|
|
63
|
-
3. **Env vars** the runtime reads:
|
|
64
|
-
|
|
65
|
-
| Variable | Purpose |
|
|
66
|
-
| ----------------------- | ---------------------------------------------------- |
|
|
67
|
-
| `VITE_DCS_PUBLIC_API` | Base URL of the DCS public API (no trailing slash). |
|
|
68
|
-
| `VITE_DCS_SITE_SLUG` | Default site slug used when the prop is omitted. |
|
|
69
|
-
|
|
70
|
-
### Why the `formsModules` prop is required in real sites
|
|
71
|
-
|
|
72
|
-
`<DcsForm/>` ships with an internal `import.meta.glob('/.dcs/forms/*.yaml')`
|
|
73
|
-
fallback, but Vite resolves the leading `/` against the **consumer's
|
|
74
|
-
Vite project root** (the directory containing `vite.config.ts`).
|
|
75
|
-
On every customer-site repo today, the `.dcs/forms/` directory lives
|
|
76
|
-
at the **repo root**, one or more levels above the Vite root
|
|
77
|
-
(typically `site/` or `docs/`). The internal glob therefore matches
|
|
78
|
-
nothing and you get:
|
|
79
|
-
|
|
80
|
-
```
|
|
81
|
-
[@duffcloudservices/site-forms] No form definition found for "contact".
|
|
82
|
-
Expected a YAML at /.dcs/forms/contact.yaml.
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
The fix is a one-file loader the rest of your site imports from.
|
|
86
|
-
|
|
87
|
-
#### Vue SPA (`vite.config.ts` in `site/`)
|
|
88
|
-
|
|
89
|
-
```ts
|
|
90
|
-
// site/src/dcs-forms.ts
|
|
91
|
-
const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
|
|
92
|
-
eager: true,
|
|
93
|
-
import: 'default',
|
|
94
|
-
})
|
|
95
|
-
export const dcsFormsModules: Record<string, unknown> = modules
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
#### VitePress (`vite` block in `docs/.vitepress/config.ts`)
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
// docs/.vitepress/dcs-forms-loader.ts
|
|
102
|
-
const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
|
|
103
|
-
eager: true,
|
|
104
|
-
import: 'default',
|
|
105
|
-
})
|
|
106
|
-
export const dcsFormsModules: Record<string, unknown> = modules
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
The relative depth (`../../`) depends on where the loader file lives
|
|
110
|
-
relative to the repo root. Adjust as needed.
|
|
111
|
-
|
|
112
|
-
## Usage
|
|
113
|
-
|
|
114
|
-
Place a YAML file at `<site>/.dcs/forms/contact.yaml`:
|
|
115
|
-
|
|
116
|
-
```yaml
|
|
117
|
-
formId: contact
|
|
118
|
-
title: Contact Us
|
|
119
|
-
submission:
|
|
120
|
-
kind: lead
|
|
121
|
-
fields:
|
|
122
|
-
- id: name
|
|
123
|
-
type: text
|
|
124
|
-
label: Name
|
|
125
|
-
required: true
|
|
126
|
-
- id: email
|
|
127
|
-
type: email
|
|
128
|
-
label: Email
|
|
129
|
-
required: true
|
|
130
|
-
- id: message
|
|
131
|
-
type: textarea
|
|
132
|
-
label: Message
|
|
133
|
-
required: true
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
Then in any page component:
|
|
137
|
-
|
|
138
|
-
```vue
|
|
139
|
-
<script setup lang="ts">
|
|
140
|
-
import { DcsForm } from '@duffcloudservices/site-forms'
|
|
141
|
-
import { dcsFormsModules } from '@/dcs-forms'
|
|
142
|
-
</script>
|
|
143
|
-
|
|
144
|
-
<template>
|
|
145
|
-
<DcsForm
|
|
146
|
-
form-id="contact"
|
|
147
|
-
:forms-modules="dcsFormsModules"
|
|
148
|
-
@submit-success="onSuccess"
|
|
149
|
-
@submit-error="onError"
|
|
150
|
-
/>
|
|
151
|
-
</template>
|
|
152
|
-
```
|
|
153
|
-
|
|
154
|
-
## Props
|
|
155
|
-
|
|
156
|
-
| Prop | Type | Default | Notes |
|
|
157
|
-
| -------------------- | --------------------------------- | -------------------------------------- | ----------------------------------------------------------- |
|
|
158
|
-
| `formId` | `string` (required) | — | Matches `.dcs/forms/<formId>.yaml`. |
|
|
159
|
-
| `siteSlug` | `string` | `import.meta.env.VITE_DCS_SITE_SLUG` | Path segment in the submission URL. |
|
|
160
|
-
| `definitionOverride` | `PortalFormDefinition` | — | Used by the portal preview iframe to show in-flight edits. |
|
|
161
|
-
| `apiBase` | `string` | `import.meta.env.VITE_DCS_PUBLIC_API` | Override for tests / non-prod environments. |
|
|
162
|
-
| `captchaToken` | `string` | — | Attached to the submission payload when set. |
|
|
163
|
-
| `formsModules` | `Record<string, unknown>` | internal fallback glob (rarely matches) | **Required in real sites.** Pass a `Record<string, unknown>` from your own `import.meta.glob('../../.dcs/forms/*.yaml', { eager: true, import: 'default' })` — see Vite setup section. |
|
|
164
|
-
|
|
165
|
-
## Emits
|
|
166
|
-
|
|
167
|
-
| Event | Payload | When |
|
|
168
|
-
| ------------------ | ---------------------- | ------------------------------------------- |
|
|
169
|
-
| `submit-success` | `DcsFormSubmitSuccess` | API responded `2xx`. |
|
|
170
|
-
| `submit-error` | `DcsFormSubmitError` | Network or non-2xx response after retries. |
|
|
171
|
-
| `validation-error` | `FormErrors` | Submit attempted with invalid required/regex/etc. fields. |
|
|
172
|
-
|
|
173
|
-
## Slots
|
|
174
|
-
|
|
175
|
-
Every slot exposes scoped data so consumers (KT Braun, Kept) can swap
|
|
176
|
-
shadcn primitives in without forking field components.
|
|
177
|
-
|
|
178
|
-
| Slot | Scope | Default |
|
|
179
|
-
| ---------- | ------------------------------------------------------------------ | ---------------------------------------------------- |
|
|
180
|
-
| `header` | `{ definition }` | `<h2>` + description |
|
|
181
|
-
| `progress` | `{ current, total, step }` | `Step N of M — Title` (multi-step only) |
|
|
182
|
-
| `actions` | `{ isFirstStep, isLastStep, submitting, prev, next }` | Plain `<button>` elements |
|
|
183
|
-
| `success` | `{ definition }` | `definition.successMessage` |
|
|
184
|
-
| `missing` | `{ formId }` | Friendly fallback when the YAML can't be found |
|
|
185
|
-
|
|
186
|
-
Per-field components (`DcsFormText` etc.) expose `#input` slots so a
|
|
187
|
-
shadcn site can replace the underlying primitive while keeping the
|
|
188
|
-
wrapper, label, help, and error-message structure.
|
|
189
|
-
|
|
190
|
-
## Composables
|
|
191
|
-
|
|
192
|
-
For sites that want a fully custom layout, drop `<DcsForm/>` and use
|
|
193
|
-
the underlying composables directly:
|
|
194
|
-
|
|
195
|
-
```ts
|
|
196
|
-
import {
|
|
197
|
-
useDcsForm,
|
|
198
|
-
validateForm,
|
|
199
|
-
submitFormValues,
|
|
200
|
-
parseFormYaml,
|
|
201
|
-
} from '@duffcloudservices/site-forms'
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
- `useDcsForm({ definition })` — reactive `values`, `errors`, `steps`,
|
|
205
|
-
`next`, `prev`, `validateAll`, `collectSubmissionValues`, etc.
|
|
206
|
-
- `validateForm(def, values, fieldIds?)` — pure validator usable in
|
|
207
|
-
any setting (server-side, tests, custom adapters).
|
|
208
|
-
- `submitFormValues({ apiBase, siteSlug, payload })` — one-shot POST
|
|
209
|
-
with a single retry on 5xx and `multipart/form-data` when files are
|
|
210
|
-
present.
|
|
211
|
-
|
|
212
|
-
## Visual editor integration
|
|
213
|
-
|
|
214
|
-
The form root carries `data-form-key="<formId>"` and every field
|
|
215
|
-
wrapper carries `data-form-field-key="<fieldId>"`. The portal preview
|
|
216
|
-
iframe bridge uses these to highlight and select form regions. Do not
|
|
217
|
-
strip them in custom layouts.
|
|
218
|
-
|
|
219
|
-
## Schema validation
|
|
220
|
-
|
|
221
|
-
In dev (`import.meta.env.DEV === true`) the runtime validates each
|
|
222
|
-
loaded definition against the JSON Schema bundled in
|
|
223
|
-
`src/schema/form-definition.schema.json` (snapshot of
|
|
224
|
-
`contracts/dist/form-definition.schema.json`) and logs failures via
|
|
225
|
-
`console.warn`. Production builds skip the warning to avoid noisy
|
|
226
|
-
end-user consoles.
|
|
227
|
-
|
|
228
|
-
When the contracts schema is regenerated (`pnpm --filter @dcs/contracts
|
|
229
|
-
generate`), refresh the snapshot:
|
|
230
|
-
|
|
231
|
-
```powershell
|
|
232
|
-
Copy-Item ../../contracts/dist/form-definition.schema.json ./src/schema/form-definition.schema.json -Force
|
|
233
|
-
pnpm --filter @duffcloudservices/site-forms test --run
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## Scripts
|
|
237
|
-
|
|
238
|
-
```powershell
|
|
239
|
-
pnpm --filter @duffcloudservices/site-forms build # vite library build (esm + dts)
|
|
240
|
-
pnpm --filter @duffcloudservices/site-forms test # vitest --run
|
|
241
|
-
pnpm --filter @duffcloudservices/site-forms type-check # vue-tsc --noEmit
|
|
242
|
-
```
|
|
243
|
-
|
|
244
|
-
## Related docs
|
|
245
|
-
|
|
246
|
-
- **Authoring guide** — [`.docs/forms/AUTHORING.md`](../../.docs/forms/AUTHORING.md)
|
|
247
|
-
covers the YAML schema, worked examples, validation flow, HIPAA
|
|
248
|
-
guardrails, and the hand-coded → `<DcsForm/>` migration recipe.
|
|
249
|
-
- **Publishing** — [`PUBLISHING.md`](./PUBLISHING.md) covers the
|
|
250
|
-
registry, OIDC trusted publishing, version bump policy, and the
|
|
251
|
-
exact dep line sibling customer-site repos should add.
|
|
252
|
-
- **Validation CLI** — [`cli/forms/README.md`](../../cli/forms/README.md)
|
|
253
|
-
documents `dcs forms validate` and `dcs forms doctor`, which lint
|
|
254
|
-
the `.dcs/forms/*.yaml` files in a customer-site repo.
|
|
255
|
-
|
|
256
|
-
## Ownership
|
|
257
|
-
|
|
258
|
-
Per `packages/README.md`: external/consumer-facing docs live here, not
|
|
259
|
-
in repo-root docs. Cross-cutting details (e.g. the public submissions
|
|
260
|
-
API contract) belong in `contracts/README.md`.
|
|
1
|
+
# @duffcloudservices/site-forms
|
|
2
|
+
|
|
3
|
+
Shared `<DcsForm/>` runtime for DCS customer sites. Renders a managed
|
|
4
|
+
form definition (created in the portal Form Manager) from a build-time
|
|
5
|
+
`.dcs/forms/<formId>.yaml` snapshot, validates user input, and posts
|
|
6
|
+
submissions to the public site-forms API.
|
|
7
|
+
|
|
8
|
+
This is the single import surface for managed-form runtime code on
|
|
9
|
+
customer sites — do not redefine field components per site.
|
|
10
|
+
|
|
11
|
+
> **Inside the `dcs-again` workspace** the package is also reachable
|
|
12
|
+
> as `@duffcloudservices/site-forms` via `workspace:*` (the workspace
|
|
13
|
+
> name and the published name are the same).
|
|
14
|
+
> **In sibling customer-site repos** install the published package
|
|
15
|
+
> from public npm — see [`PUBLISHING.md`](./PUBLISHING.md) for the
|
|
16
|
+
> consumption story and registry setup.
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
### In a workspace app (inside `dcs-again`)
|
|
21
|
+
|
|
22
|
+
```jsonc
|
|
23
|
+
// portal/package.json
|
|
24
|
+
{
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@duffcloudservices/site-forms": "workspace:*"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Then `pnpm install` from the repo root.
|
|
32
|
+
|
|
33
|
+
### In a sibling customer-site repo (e.g. `ktbraunlaw`, `kept`)
|
|
34
|
+
|
|
35
|
+
```powershell
|
|
36
|
+
pnpm add @duffcloudservices/site-forms
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
See [`PUBLISHING.md`](./PUBLISHING.md) for the registry, version, and
|
|
40
|
+
release-workflow details.
|
|
41
|
+
|
|
42
|
+
## Vite setup
|
|
43
|
+
|
|
44
|
+
Three pieces are required in the consuming site:
|
|
45
|
+
|
|
46
|
+
1. **`vite-plugin-yaml`** so YAML modules return parsed objects:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
// vite.config.ts
|
|
50
|
+
import yaml from '@modyfi/vite-plugin-yaml'
|
|
51
|
+
|
|
52
|
+
export default defineConfig({
|
|
53
|
+
plugins: [vue(), yaml()],
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Without it, the loader falls back to parsing raw strings via
|
|
58
|
+
`js-yaml`, which works but pays the parse cost at boot.
|
|
59
|
+
|
|
60
|
+
2. **A `formsModules` loader** in your site that does the
|
|
61
|
+
`import.meta.glob` from a path Vite can resolve (see below).
|
|
62
|
+
|
|
63
|
+
3. **Env vars** the runtime reads:
|
|
64
|
+
|
|
65
|
+
| Variable | Purpose |
|
|
66
|
+
| ----------------------- | ---------------------------------------------------- |
|
|
67
|
+
| `VITE_DCS_PUBLIC_API` | Base URL of the DCS public API (no trailing slash). |
|
|
68
|
+
| `VITE_DCS_SITE_SLUG` | Default site slug used when the prop is omitted. |
|
|
69
|
+
|
|
70
|
+
### Why the `formsModules` prop is required in real sites
|
|
71
|
+
|
|
72
|
+
`<DcsForm/>` ships with an internal `import.meta.glob('/.dcs/forms/*.yaml')`
|
|
73
|
+
fallback, but Vite resolves the leading `/` against the **consumer's
|
|
74
|
+
Vite project root** (the directory containing `vite.config.ts`).
|
|
75
|
+
On every customer-site repo today, the `.dcs/forms/` directory lives
|
|
76
|
+
at the **repo root**, one or more levels above the Vite root
|
|
77
|
+
(typically `site/` or `docs/`). The internal glob therefore matches
|
|
78
|
+
nothing and you get:
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
[@duffcloudservices/site-forms] No form definition found for "contact".
|
|
82
|
+
Expected a YAML at /.dcs/forms/contact.yaml.
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
The fix is a one-file loader the rest of your site imports from.
|
|
86
|
+
|
|
87
|
+
#### Vue SPA (`vite.config.ts` in `site/`)
|
|
88
|
+
|
|
89
|
+
```ts
|
|
90
|
+
// site/src/dcs-forms.ts
|
|
91
|
+
const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
|
|
92
|
+
eager: true,
|
|
93
|
+
import: 'default',
|
|
94
|
+
})
|
|
95
|
+
export const dcsFormsModules: Record<string, unknown> = modules
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
#### VitePress (`vite` block in `docs/.vitepress/config.ts`)
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
// docs/.vitepress/dcs-forms-loader.ts
|
|
102
|
+
const modules = import.meta.glob('../../.dcs/forms/*.yaml', {
|
|
103
|
+
eager: true,
|
|
104
|
+
import: 'default',
|
|
105
|
+
})
|
|
106
|
+
export const dcsFormsModules: Record<string, unknown> = modules
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
The relative depth (`../../`) depends on where the loader file lives
|
|
110
|
+
relative to the repo root. Adjust as needed.
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
Place a YAML file at `<site>/.dcs/forms/contact.yaml`:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
formId: contact
|
|
118
|
+
title: Contact Us
|
|
119
|
+
submission:
|
|
120
|
+
kind: lead
|
|
121
|
+
fields:
|
|
122
|
+
- id: name
|
|
123
|
+
type: text
|
|
124
|
+
label: Name
|
|
125
|
+
required: true
|
|
126
|
+
- id: email
|
|
127
|
+
type: email
|
|
128
|
+
label: Email
|
|
129
|
+
required: true
|
|
130
|
+
- id: message
|
|
131
|
+
type: textarea
|
|
132
|
+
label: Message
|
|
133
|
+
required: true
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Then in any page component:
|
|
137
|
+
|
|
138
|
+
```vue
|
|
139
|
+
<script setup lang="ts">
|
|
140
|
+
import { DcsForm } from '@duffcloudservices/site-forms'
|
|
141
|
+
import { dcsFormsModules } from '@/dcs-forms'
|
|
142
|
+
</script>
|
|
143
|
+
|
|
144
|
+
<template>
|
|
145
|
+
<DcsForm
|
|
146
|
+
form-id="contact"
|
|
147
|
+
:forms-modules="dcsFormsModules"
|
|
148
|
+
@submit-success="onSuccess"
|
|
149
|
+
@submit-error="onError"
|
|
150
|
+
/>
|
|
151
|
+
</template>
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Props
|
|
155
|
+
|
|
156
|
+
| Prop | Type | Default | Notes |
|
|
157
|
+
| -------------------- | --------------------------------- | -------------------------------------- | ----------------------------------------------------------- |
|
|
158
|
+
| `formId` | `string` (required) | — | Matches `.dcs/forms/<formId>.yaml`. |
|
|
159
|
+
| `siteSlug` | `string` | `import.meta.env.VITE_DCS_SITE_SLUG` | Path segment in the submission URL. |
|
|
160
|
+
| `definitionOverride` | `PortalFormDefinition` | — | Used by the portal preview iframe to show in-flight edits. |
|
|
161
|
+
| `apiBase` | `string` | `import.meta.env.VITE_DCS_PUBLIC_API` | Override for tests / non-prod environments. |
|
|
162
|
+
| `captchaToken` | `string` | — | Attached to the submission payload when set. |
|
|
163
|
+
| `formsModules` | `Record<string, unknown>` | internal fallback glob (rarely matches) | **Required in real sites.** Pass a `Record<string, unknown>` from your own `import.meta.glob('../../.dcs/forms/*.yaml', { eager: true, import: 'default' })` — see Vite setup section. |
|
|
164
|
+
|
|
165
|
+
## Emits
|
|
166
|
+
|
|
167
|
+
| Event | Payload | When |
|
|
168
|
+
| ------------------ | ---------------------- | ------------------------------------------- |
|
|
169
|
+
| `submit-success` | `DcsFormSubmitSuccess` | API responded `2xx`. |
|
|
170
|
+
| `submit-error` | `DcsFormSubmitError` | Network or non-2xx response after retries. |
|
|
171
|
+
| `validation-error` | `FormErrors` | Submit attempted with invalid required/regex/etc. fields. |
|
|
172
|
+
|
|
173
|
+
## Slots
|
|
174
|
+
|
|
175
|
+
Every slot exposes scoped data so consumers (KT Braun, Kept) can swap
|
|
176
|
+
shadcn primitives in without forking field components.
|
|
177
|
+
|
|
178
|
+
| Slot | Scope | Default |
|
|
179
|
+
| ---------- | ------------------------------------------------------------------ | ---------------------------------------------------- |
|
|
180
|
+
| `header` | `{ definition }` | `<h2>` + description |
|
|
181
|
+
| `progress` | `{ current, total, step }` | `Step N of M — Title` (multi-step only) |
|
|
182
|
+
| `actions` | `{ isFirstStep, isLastStep, submitting, prev, next }` | Plain `<button>` elements |
|
|
183
|
+
| `success` | `{ definition }` | `definition.successMessage` |
|
|
184
|
+
| `missing` | `{ formId }` | Friendly fallback when the YAML can't be found |
|
|
185
|
+
|
|
186
|
+
Per-field components (`DcsFormText` etc.) expose `#input` slots so a
|
|
187
|
+
shadcn site can replace the underlying primitive while keeping the
|
|
188
|
+
wrapper, label, help, and error-message structure.
|
|
189
|
+
|
|
190
|
+
## Composables
|
|
191
|
+
|
|
192
|
+
For sites that want a fully custom layout, drop `<DcsForm/>` and use
|
|
193
|
+
the underlying composables directly:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
import {
|
|
197
|
+
useDcsForm,
|
|
198
|
+
validateForm,
|
|
199
|
+
submitFormValues,
|
|
200
|
+
parseFormYaml,
|
|
201
|
+
} from '@duffcloudservices/site-forms'
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
- `useDcsForm({ definition })` — reactive `values`, `errors`, `steps`,
|
|
205
|
+
`next`, `prev`, `validateAll`, `collectSubmissionValues`, etc.
|
|
206
|
+
- `validateForm(def, values, fieldIds?)` — pure validator usable in
|
|
207
|
+
any setting (server-side, tests, custom adapters).
|
|
208
|
+
- `submitFormValues({ apiBase, siteSlug, payload })` — one-shot POST
|
|
209
|
+
with a single retry on 5xx and `multipart/form-data` when files are
|
|
210
|
+
present.
|
|
211
|
+
|
|
212
|
+
## Visual editor integration
|
|
213
|
+
|
|
214
|
+
The form root carries `data-form-key="<formId>"` and every field
|
|
215
|
+
wrapper carries `data-form-field-key="<fieldId>"`. The portal preview
|
|
216
|
+
iframe bridge uses these to highlight and select form regions. Do not
|
|
217
|
+
strip them in custom layouts.
|
|
218
|
+
|
|
219
|
+
## Schema validation
|
|
220
|
+
|
|
221
|
+
In dev (`import.meta.env.DEV === true`) the runtime validates each
|
|
222
|
+
loaded definition against the JSON Schema bundled in
|
|
223
|
+
`src/schema/form-definition.schema.json` (snapshot of
|
|
224
|
+
`contracts/dist/form-definition.schema.json`) and logs failures via
|
|
225
|
+
`console.warn`. Production builds skip the warning to avoid noisy
|
|
226
|
+
end-user consoles.
|
|
227
|
+
|
|
228
|
+
When the contracts schema is regenerated (`pnpm --filter @dcs/contracts
|
|
229
|
+
generate`), refresh the snapshot:
|
|
230
|
+
|
|
231
|
+
```powershell
|
|
232
|
+
Copy-Item ../../contracts/dist/form-definition.schema.json ./src/schema/form-definition.schema.json -Force
|
|
233
|
+
pnpm --filter @duffcloudservices/site-forms test --run
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Scripts
|
|
237
|
+
|
|
238
|
+
```powershell
|
|
239
|
+
pnpm --filter @duffcloudservices/site-forms build # vite library build (esm + dts)
|
|
240
|
+
pnpm --filter @duffcloudservices/site-forms test # vitest --run
|
|
241
|
+
pnpm --filter @duffcloudservices/site-forms type-check # vue-tsc --noEmit
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## Related docs
|
|
245
|
+
|
|
246
|
+
- **Authoring guide** — [`.docs/forms/AUTHORING.md`](../../.docs/forms/AUTHORING.md)
|
|
247
|
+
covers the YAML schema, worked examples, validation flow, HIPAA
|
|
248
|
+
guardrails, and the hand-coded → `<DcsForm/>` migration recipe.
|
|
249
|
+
- **Publishing** — [`PUBLISHING.md`](./PUBLISHING.md) covers the
|
|
250
|
+
registry, OIDC trusted publishing, version bump policy, and the
|
|
251
|
+
exact dep line sibling customer-site repos should add.
|
|
252
|
+
- **Validation CLI** — [`cli/forms/README.md`](../../cli/forms/README.md)
|
|
253
|
+
documents `dcs forms validate` and `dcs forms doctor`, which lint
|
|
254
|
+
the `.dcs/forms/*.yaml` files in a customer-site repo.
|
|
255
|
+
|
|
256
|
+
## Ownership
|
|
257
|
+
|
|
258
|
+
Per `packages/README.md`: external/consumer-facing docs live here, not
|
|
259
|
+
in repo-root docs. Cross-cutting details (e.g. the public submissions
|
|
260
|
+
API contract) belong in `contracts/README.md`.
|