@foormjs/vue 0.2.3 → 0.2.5

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.
@@ -0,0 +1,279 @@
1
+ # OoForm Setup & API — @foormjs/vue
2
+
3
+ > OoForm props, events, slots, `types` vs `components` props, `useFoorm`, `createDefaultTypes`, and validation timing.
4
+
5
+ ## Concepts
6
+
7
+ OoForm is the root component. It provides context to all descendant fields via provide/inject and renders `<OoField :field="def.rootField" />`. You must supply two things:
8
+
9
+ 1. **`def`** — a `FoormDef` from `useFoorm()` or `createFoormDef()`
10
+ 2. **`types`** — a map of field type strings to Vue components
11
+
12
+ OoForm renders a `<form>` element. On submit, it validates all fields and emits either `submit` (success) or `error` (validation failures).
13
+
14
+ ## `types` vs `components` — Two Ways to Provide Custom Components
15
+
16
+ Every field has a **type** (always defined — either from `@foorm.type`, or auto-inferred from the schema: `string` → `'text'`, `number` → `'number'`, `boolean` → `'checkbox'`, etc.). The `types` prop maps these type strings to Vue components.
17
+
18
+ When multiple fields share the same data type but need different UI representations, use `@foorm.component 'Name'` to override the type-based mapping for specific fields. The `components` prop maps these names to Vue components. Named components take priority over type-based resolution.
19
+
20
+ ```
21
+ Resolution order: @foorm.component → components[name] → types[field.type]
22
+ ```
23
+
24
+ ### `types` prop (required) — Type Components
25
+
26
+ Maps field type strings to Vue components. The built-in type keys (`text`, `password`, `number`) correspond to HTML `<input type="...">` values, but type keys are not limited to valid HTML input types — you can use any string that makes sense for your use case (e.g., `textarea`, `date`, `rating`, `color`). As long as the key is defined in the `types` map and referenced via `@foorm.type` in the schema, it will resolve to the mapped component.
27
+
28
+ Must include all base types:
29
+
30
+ ```ts
31
+ import type { TFoormTypeComponents } from '@foormjs/vue'
32
+
33
+ const types: TFoormTypeComponents = {
34
+ text: MyInput, // string fields (default), @foorm.type 'text'
35
+ password: MyInput, // @foorm.type 'password'
36
+ number: MyInput, // number fields, @foorm.type 'number'
37
+ select: MySelect, // foorm.select primitive
38
+ radio: MyRadio, // foorm.radio primitive
39
+ checkbox: MyCheckbox, // boolean / foorm.checkbox
40
+ paragraph: MyParagraph, // foorm.paragraph phantom
41
+ action: MyAction, // foorm.action phantom
42
+ object: MyObject, // nested objects / @foorm.title groups
43
+ array: MyArray, // type[] arrays
44
+ union: MyUnion, // union types (A | B)
45
+ tuple: MyTuple, // tuple types [A, B]
46
+ // Custom type keys — any string works:
47
+ textarea: MyTextarea, // @foorm.type 'textarea'
48
+ date: MyDatePicker, // @foorm.type 'date'
49
+ rating: MyRating, // @foorm.type 'rating'
50
+ }
51
+ ```
52
+
53
+ Use `createDefaultTypes()` for a pre-filled map, then override entries:
54
+
55
+ ```ts
56
+ import { createDefaultTypes } from '@foormjs/vue'
57
+
58
+ const types = { ...createDefaultTypes(), text: MyInput, select: MySelect }
59
+ ```
60
+
61
+ ### `components` prop (optional) — Named Components
62
+
63
+ Maps `@foorm.component` names to Vue components. Per-field override.
64
+
65
+ ```
66
+ // schema.as
67
+ @foorm.component 'StarRating'
68
+ rating?: number
69
+ ```
70
+
71
+ ```vue
72
+ <OoForm :types="types" :components="{ StarRating: MyStarRating }" ... />
73
+ ```
74
+
75
+ The field still has a type (`'number'`), but `@foorm.component 'StarRating'` overrides it — OoField renders `MyStarRating` instead of `types['number']`.
76
+
77
+ ### IDE autocomplete for custom types and components
78
+
79
+ When using the ATScript VSCode extension, pass your custom type keys and component names to `foormPlugin()` in `atscript.config.ts` to get autocomplete and validation for `@foorm.type` and `@foorm.component` annotations:
80
+
81
+ ```ts
82
+ // atscript.config.ts
83
+ import { foormPlugin } from '@foormjs/atscript/plugin'
84
+
85
+ export default defineConfig({
86
+ plugins: [
87
+ ts(),
88
+ foormPlugin({
89
+ extraTypes: ['textarea', 'date', 'rating'], // extends @foorm.type suggest list
90
+ components: ['StarRating', 'ColorPicker'], // extends @foorm.component suggest list
91
+ }),
92
+ ],
93
+ })
94
+ ```
95
+
96
+ Without this, `@foorm.type` only suggests built-in values and `@foorm.component` accepts any string without validation.
97
+
98
+ ## Installation & Setup
99
+
100
+ ```bash
101
+ pnpm add @foormjs/vue @foormjs/atscript @atscript/core @atscript/typescript
102
+ pnpm add -D unplugin-atscript @vitejs/plugin-vue
103
+ ```
104
+
105
+ ```ts
106
+ // vite.config.ts — atscript() MUST come before vue()
107
+ import { defineConfig } from 'vite'
108
+ import vue from '@vitejs/plugin-vue'
109
+ import atscript from 'unplugin-atscript/vite'
110
+
111
+ export default defineConfig({ plugins: [atscript(), vue()] })
112
+ ```
113
+
114
+ ## `useFoorm(type): { def, formData }`
115
+
116
+ Creates a form definition and reactive form data from an ATScript type.
117
+
118
+ ```ts
119
+ import { useFoorm } from '@foormjs/vue'
120
+ import { MyForm } from './forms/my-form.as'
121
+
122
+ const { def, formData } = useFoorm(MyForm)
123
+ ```
124
+
125
+ - `def` — `FoormDef` with root field, ordered fields, flat map
126
+ - `formData` — Vue `reactive()` object with default values from schema (wrapped in `{ value: ... }`)
127
+
128
+ ## OoForm Props
129
+
130
+ | Prop | Type | Required | Default | Description |
131
+ | ----------------- | ------------------------------------------------------------------------ | -------- | ------------- | ------------------------------------------------- |
132
+ | `def` | `FoormDef` | Yes | — | Form definition from `useFoorm()` |
133
+ | `formData` | `object` | No | `{}` | Reactive form data |
134
+ | `formContext` | `object` | No | — | External context for `@foorm.fn.*` and validators |
135
+ | `firstValidation` | `'on-change' \| 'touched-on-blur' \| 'on-blur' \| 'on-submit' \| 'none'` | No | `'on-change'` | When to show errors first |
136
+ | `types` | `TFoormTypeComponents` | Yes | — | Type-to-component map (see above) |
137
+ | `components` | `Record<string, Component>` | No | — | Named components for `@foorm.component` |
138
+ | `errors` | `Record<string, string>` | No | — | External errors keyed by field path |
139
+
140
+ ## OoForm Events
141
+
142
+ | Event | Payload | Description |
143
+ | -------------------- | ---------------------------------------------------------------- | ----------------------------------------------------- |
144
+ | `submit` | `formData` | All fields passed validation |
145
+ | `error` | `{ path: string; message: string }[]` | Validation failed on submit |
146
+ | `action` | `name: string, formData` | `@foorm.altAction` button clicked (field supports it) |
147
+ | `unsupported-action` | `name: string, formData` | Action button clicked but no field has that action |
148
+ | `change` | `type: TFoormChangeType, path: string, value: unknown, formData` | Field/array/union changed |
149
+
150
+ **Change types:** `'update'` (field blur with changed value), `'array-add'`, `'array-remove'`, `'union-switch'`
151
+
152
+ ## OoForm Slots
153
+
154
+ All slots receive `clearErrors()`, `reset()`, `setErrors()` for programmatic control.
155
+
156
+ ```vue
157
+ <OoForm :def="def" :form-data="formData" :types="types" @submit="onSubmit">
158
+ <template #form.header="{ clearErrors, reset, setErrors, formContext, disabled }">
159
+ <!-- Before the form title -->
160
+ </template>
161
+ <template #form.before="{ clearErrors, reset, setErrors }">
162
+ <!-- After title, before fields -->
163
+ </template>
164
+ <template #form.after="{ disabled, formContext, clearErrors, reset, setErrors }">
165
+ <!-- After fields, before submit -->
166
+ </template>
167
+ <template #form.submit="{ text, disabled, clearErrors, reset, setErrors, formContext }">
168
+ <button type="submit" :disabled="disabled">{{ text }}</button>
169
+ </template>
170
+ <template #form.footer="{ disabled, clearErrors, reset, setErrors, formContext }">
171
+ <!-- After submit button -->
172
+ </template>
173
+ </OoForm>
174
+ ```
175
+
176
+ ## Validation Timing (`firstValidation`)
177
+
178
+ Controls when errors first appear:
179
+
180
+ | Value | Behavior |
181
+ | ------------------- | ----------------------------------------------- |
182
+ | `'on-change'` | Show errors immediately as user types (default) |
183
+ | `'touched-on-blur'` | Show after field blurred AND modified |
184
+ | `'on-blur'` | Show when field loses focus |
185
+ | `'on-submit'` | No errors until form submit |
186
+ | `'none'` | Never auto-validate |
187
+
188
+ After first submit, all fields show errors on change regardless of this setting.
189
+
190
+ ## Common Patterns
191
+
192
+ ### Basic form
193
+
194
+ ```vue
195
+ <script setup lang="ts">
196
+ import { OoForm, createDefaultTypes, useFoorm } from '@foormjs/vue'
197
+ import { MyForm } from './forms/my-form.as'
198
+
199
+ const { def, formData } = useFoorm(MyForm)
200
+ const types = createDefaultTypes()
201
+ </script>
202
+
203
+ <template>
204
+ <OoForm :def="def" :form-data="formData" :types="types" @submit="handleSubmit" />
205
+ </template>
206
+ ```
207
+
208
+ ### With context, server errors, and custom components
209
+
210
+ ```vue
211
+ <script setup lang="ts">
212
+ import { reactive, ref } from 'vue'
213
+ import { OoForm, createDefaultTypes, useFoorm } from '@foormjs/vue'
214
+ import { MyForm } from './forms/my-form.as'
215
+ import StarRating from './components/StarRating.vue'
216
+
217
+ const { def, formData } = useFoorm(MyForm)
218
+ const types = { ...createDefaultTypes(), textarea: MyTextarea }
219
+ const serverErrors = ref<Record<string, string>>({})
220
+ const context = reactive({ cityOptions: [] })
221
+ </script>
222
+
223
+ <template>
224
+ <OoForm
225
+ :def="def"
226
+ :form-data="formData"
227
+ :form-context="context"
228
+ :types="types"
229
+ :components="{ StarRating }"
230
+ :errors="serverErrors"
231
+ first-validation="on-blur"
232
+ @submit="handleSubmit"
233
+ @change="handleChange"
234
+ />
235
+ </template>
236
+ ```
237
+
238
+ ### Backend-driven form (deserialized)
239
+
240
+ ATScript annotated types can be serialized to JSON on the backend and deserialized on the frontend — the form is fully backend-controlled without shipping `.as` files to the client. Use `createFoormDef` + `createFormData` directly (not `useFoorm`) since the type arrives asynchronously. See the `@foormjs/atscript` skill for serialization API details (`serializeAnnotatedType`, `fromJsonSchema`, `defineAnnotatedType`).
241
+
242
+ ```vue
243
+ <script setup lang="ts">
244
+ import { ref, reactive, onMounted } from 'vue'
245
+ import { deserializeAnnotatedType } from '@atscript/typescript/utils'
246
+ import { createFoormDef, createFormData } from '@foormjs/atscript'
247
+ import { OoForm, createDefaultTypes } from '@foormjs/vue'
248
+ import type { FoormDef } from '@foormjs/atscript'
249
+
250
+ const def = ref<FoormDef>()
251
+ const formData = ref<{ value: Record<string, unknown> }>()
252
+ const types = createDefaultTypes()
253
+
254
+ onMounted(async () => {
255
+ const res = await fetch('/api/form')
256
+ const type = deserializeAnnotatedType(await res.json())
257
+ def.value = createFoormDef(type)
258
+ formData.value = reactive(createFormData(type, def.value.fields))
259
+ })
260
+ </script>
261
+
262
+ <template>
263
+ <OoForm
264
+ v-if="def && formData"
265
+ :def="def"
266
+ :form-data="formData"
267
+ :types="types"
268
+ @submit="onSubmit"
269
+ />
270
+ </template>
271
+ ```
272
+
273
+ ## Gotchas
274
+
275
+ - `types` prop is required — every field type your form uses must have a matching component entry
276
+ - `atscript()` must come before `vue()` in Vite plugins
277
+ - `formData` is wrapped in `{ value: ... }` — `useFoorm` handles this, manual setup must account for it
278
+ - `change` event fires on blur for `'update'` type, not on every keystroke
279
+ - `@foormjs/vue/styles` provides default component styles — import if using defaults