@foormjs/vue 0.2.4 → 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.
- package/README.md +385 -236
- package/dist/index.cjs +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +446 -216
- package/dist/index.js +1554 -371
- package/package.json +29 -25
- package/scripts/setup-skills.js +78 -0
- package/skills/foormjs-vue/.placeholder +0 -0
- package/skills/foormjs-vue/SKILL.md +53 -0
- package/skills/foormjs-vue/composables.md +189 -0
- package/skills/foormjs-vue/core.md +279 -0
- package/skills/foormjs-vue/custom-components.md +677 -0
- package/skills/foormjs-vue/defaults.md +266 -0
- package/skills/foormjs-vue/rendering.md +175 -0
- package/dist/index.umd.cjs +0 -1
- package/dist/style.css +0 -1
|
@@ -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
|