@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.
- package/README.md +385 -236
- package/dist/index.cjs +1 -0
- package/dist/index.css +1 -0
- package/dist/index.d.ts +454 -146
- package/dist/index.js +1518 -1022
- package/package.json +30 -26
- 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 -3
- package/dist/style.css +0 -1
package/README.md
CHANGED
|
@@ -1,28 +1,51 @@
|
|
|
1
1
|
# @foormjs/vue
|
|
2
2
|
|
|
3
|
-
Vue
|
|
4
|
-
|
|
5
|
-
Most form libraries force you into their component system. `@foormjs/vue` works the other way: it gives you a form component with sensible defaults for every field type, but lets you replace any part of the rendering with your own components. Use the built-in inputs to get started, then gradually swap in your design system components -- per field, per type, or via scoped slots.
|
|
6
|
-
|
|
7
|
-
Forms are defined in ATScript `.as` files (via `@foormjs/atscript`), converted to a model at runtime, and rendered by `OoForm`. Validation, computed properties, and reactive state are handled automatically.
|
|
3
|
+
Renderless Vue components for ATScript-defined forms. Bring your own UI components and wrap them in `OoForm` / `OoField` to get automatic validation, computed properties, and reactive form state — all driven by `.as` schema files.
|
|
8
4
|
|
|
9
5
|
## Install
|
|
10
6
|
|
|
11
7
|
```bash
|
|
12
|
-
|
|
8
|
+
pnpm add @foormjs/vue @foormjs/atscript vue @atscript/core @atscript/typescript
|
|
13
9
|
# or
|
|
14
|
-
|
|
10
|
+
npm install @foormjs/vue @foormjs/atscript vue @atscript/core @atscript/typescript
|
|
15
11
|
```
|
|
16
12
|
|
|
17
|
-
You
|
|
13
|
+
You also need the ATScript Vite plugin for `.as` file support:
|
|
18
14
|
|
|
19
15
|
```bash
|
|
20
|
-
pnpm add -D
|
|
16
|
+
pnpm add -D unplugin-atscript @vitejs/plugin-vue
|
|
21
17
|
```
|
|
22
18
|
|
|
23
19
|
## Quick Start
|
|
24
20
|
|
|
25
|
-
### 1.
|
|
21
|
+
### 1. Configure ATScript
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
// atscript.config.ts
|
|
25
|
+
import { defineConfig } from '@atscript/core'
|
|
26
|
+
import ts from '@atscript/typescript'
|
|
27
|
+
import { foormPlugin } from '@foormjs/atscript/plugin'
|
|
28
|
+
|
|
29
|
+
export default defineConfig({
|
|
30
|
+
rootDir: 'src',
|
|
31
|
+
plugins: [ts(), foormPlugin()],
|
|
32
|
+
})
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### 2. Configure Vite
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
// vite.config.ts
|
|
39
|
+
import { defineConfig } from 'vite'
|
|
40
|
+
import vue from '@vitejs/plugin-vue'
|
|
41
|
+
import atscript from 'unplugin-atscript/vite'
|
|
42
|
+
|
|
43
|
+
export default defineConfig({
|
|
44
|
+
plugins: [atscript(), vue()],
|
|
45
|
+
})
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 3. Define a form schema
|
|
26
49
|
|
|
27
50
|
```
|
|
28
51
|
// src/forms/login.as
|
|
@@ -32,352 +55,478 @@ export interface LoginForm {
|
|
|
32
55
|
@meta.label 'Email'
|
|
33
56
|
@meta.placeholder 'you@example.com'
|
|
34
57
|
@foorm.autocomplete 'email'
|
|
35
|
-
@
|
|
36
|
-
|
|
58
|
+
@meta.required 'Email is required'
|
|
59
|
+
@foorm.order 1
|
|
60
|
+
email: string.email
|
|
37
61
|
|
|
38
62
|
@meta.label 'Password'
|
|
63
|
+
@meta.placeholder 'Enter password'
|
|
39
64
|
@foorm.type 'password'
|
|
40
|
-
@
|
|
65
|
+
@meta.required 'Password is required'
|
|
66
|
+
@foorm.order 2
|
|
41
67
|
password: string
|
|
42
|
-
|
|
43
|
-
@meta.label 'Remember me'
|
|
44
|
-
rememberMe?: foorm.checkbox
|
|
45
68
|
}
|
|
46
69
|
```
|
|
47
70
|
|
|
48
|
-
###
|
|
71
|
+
### 4. Use in a Vue component
|
|
49
72
|
|
|
50
73
|
```vue
|
|
51
74
|
<script setup lang="ts">
|
|
52
75
|
import { OoForm, useFoorm } from '@foormjs/vue'
|
|
53
76
|
import { LoginForm } from './forms/login.as'
|
|
54
77
|
|
|
55
|
-
const {
|
|
78
|
+
const { def, formData } = useFoorm(LoginForm)
|
|
56
79
|
|
|
57
80
|
function handleSubmit(data: typeof formData) {
|
|
58
|
-
console.log('
|
|
81
|
+
console.log('submitted', data)
|
|
59
82
|
}
|
|
60
83
|
</script>
|
|
61
84
|
|
|
62
85
|
<template>
|
|
63
|
-
<OoForm :
|
|
86
|
+
<OoForm :def="def" :form-data="formData" :types="{}" @submit="handleSubmit" />
|
|
64
87
|
</template>
|
|
65
88
|
```
|
|
66
89
|
|
|
67
|
-
|
|
90
|
+
`OoForm` renders default HTML inputs for all standard field types out of the box. For production use, you'll want to supply your own components via the `types` prop.
|
|
68
91
|
|
|
69
|
-
##
|
|
92
|
+
## AI Agent Skills
|
|
70
93
|
|
|
71
|
-
|
|
94
|
+
`@foormjs/vue` ships an AI agent skill for Claude Code, Cursor, Windsurf, Codex, and other compatible agents. The skill teaches your agent the library's APIs, patterns, and best practices so it can help you write correct code without hallucinating.
|
|
72
95
|
|
|
73
|
-
|
|
96
|
+
**Install the skill into your agent:**
|
|
74
97
|
|
|
98
|
+
```bash
|
|
99
|
+
# Project-local (recommended — version-locked, commits with your repo)
|
|
100
|
+
npx @foormjs/vue setup-skills
|
|
101
|
+
|
|
102
|
+
# Global (available across all your projects)
|
|
103
|
+
npx @foormjs/vue setup-skills --global
|
|
75
104
|
```
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
105
|
+
|
|
106
|
+
Restart your agent after installing.
|
|
107
|
+
|
|
108
|
+
**Auto-update on install** — to keep the skill in sync whenever you upgrade the package, add this to your project's `package.json`:
|
|
109
|
+
|
|
110
|
+
```jsonc
|
|
111
|
+
{
|
|
112
|
+
"scripts": {
|
|
113
|
+
"postinstall": "npx @foormjs/vue setup-skills --postinstall",
|
|
114
|
+
},
|
|
82
115
|
}
|
|
83
116
|
```
|
|
84
117
|
|
|
85
|
-
|
|
86
|
-
<script setup lang="ts">
|
|
87
|
-
import { OoForm, useFoorm } from '@foormjs/vue'
|
|
88
|
-
import { PreferencesForm } from './forms/preferences.as'
|
|
118
|
+
## Advanced Usage
|
|
89
119
|
|
|
90
|
-
|
|
120
|
+
### Custom Components by Type
|
|
91
121
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
122
|
+
Map field types to your UI components:
|
|
123
|
+
|
|
124
|
+
```vue
|
|
125
|
+
<script setup lang="ts">
|
|
126
|
+
import { OoForm } from '@foormjs/vue'
|
|
127
|
+
import MyTextInput from './MyTextInput.vue'
|
|
128
|
+
import MySelect from './MySelect.vue'
|
|
129
|
+
import MyCheckbox from './MyCheckbox.vue'
|
|
130
|
+
|
|
131
|
+
const typeComponents = {
|
|
132
|
+
text: MyTextInput,
|
|
133
|
+
select: MySelect,
|
|
134
|
+
checkbox: MyCheckbox,
|
|
98
135
|
}
|
|
99
136
|
</script>
|
|
100
137
|
|
|
101
138
|
<template>
|
|
102
|
-
<OoForm :
|
|
139
|
+
<OoForm :def="def" :form-data="formData" :types="typeComponents" @submit="onSubmit" />
|
|
103
140
|
</template>
|
|
104
141
|
```
|
|
105
142
|
|
|
106
|
-
|
|
143
|
+
Every field with `type: 'text'` will render `MyTextInput`, every `type: 'select'` renders `MySelect`, etc.
|
|
107
144
|
|
|
108
|
-
|
|
145
|
+
### Custom Components by Name
|
|
146
|
+
|
|
147
|
+
Use `@foorm.component` in your schema to assign a named component to a specific field:
|
|
109
148
|
|
|
110
149
|
```
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
birthday?: string
|
|
116
|
-
}
|
|
150
|
+
@meta.label 'Rating'
|
|
151
|
+
@foorm.component 'StarRating'
|
|
152
|
+
@foorm.order 5
|
|
153
|
+
rating?: number
|
|
117
154
|
```
|
|
118
155
|
|
|
156
|
+
Then pass named components via the `components` prop:
|
|
157
|
+
|
|
119
158
|
```vue
|
|
120
159
|
<template>
|
|
121
160
|
<OoForm
|
|
122
|
-
:
|
|
161
|
+
:def="def"
|
|
123
162
|
:form-data="formData"
|
|
124
|
-
:
|
|
125
|
-
|
|
163
|
+
:types="typeComponents"
|
|
164
|
+
:components="{ StarRating: MyStarRating }"
|
|
165
|
+
@submit="onSubmit"
|
|
126
166
|
/>
|
|
127
167
|
</template>
|
|
128
168
|
```
|
|
129
169
|
|
|
130
|
-
|
|
170
|
+
Named components take priority over type-based components.
|
|
171
|
+
|
|
172
|
+
### Building a Custom Component
|
|
131
173
|
|
|
132
|
-
|
|
174
|
+
Custom components receive `TFoormComponentProps` as their props:
|
|
133
175
|
|
|
134
176
|
```vue
|
|
177
|
+
<script setup lang="ts">
|
|
178
|
+
import type { TFoormComponentProps } from '@foormjs/vue'
|
|
179
|
+
|
|
180
|
+
const props = defineProps<TFoormComponentProps<string>>()
|
|
181
|
+
</script>
|
|
182
|
+
|
|
135
183
|
<template>
|
|
136
|
-
<
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
184
|
+
<div :class="{ disabled, error: !!error }">
|
|
185
|
+
<label>{{ label }}</label>
|
|
186
|
+
<input
|
|
187
|
+
:value="model.value"
|
|
188
|
+
@input="model.value = ($event.target as HTMLInputElement).value"
|
|
189
|
+
@blur="onBlur"
|
|
190
|
+
:placeholder="placeholder"
|
|
191
|
+
:disabled="disabled"
|
|
192
|
+
:readonly="readonly"
|
|
193
|
+
/>
|
|
194
|
+
<span v-if="error" class="error">{{ error }}</span>
|
|
195
|
+
<span v-else-if="hint" class="hint">{{ hint }}</span>
|
|
196
|
+
</div>
|
|
142
197
|
</template>
|
|
143
198
|
```
|
|
144
199
|
|
|
145
|
-
|
|
200
|
+
Key props available to your component:
|
|
201
|
+
|
|
202
|
+
| Prop | Type | Description |
|
|
203
|
+
| ------------------ | ----------------------------- | ---------------------------------------------------------- |
|
|
204
|
+
| `model` | `{ value: V }` | Reactive model — bind with `v-model="model.value"` |
|
|
205
|
+
| `value` | `unknown?` | Phantom display value (`@foorm.value` / `@foorm.fn.value`) |
|
|
206
|
+
| `onBlur` | `(e: FocusEvent) => void` | Triggers validation on blur |
|
|
207
|
+
| `error` | `string?` | Validation error message |
|
|
208
|
+
| `label` | `string?` | Resolved field label |
|
|
209
|
+
| `description` | `string?` | Resolved field description |
|
|
210
|
+
| `hint` | `string?` | Resolved hint text |
|
|
211
|
+
| `placeholder` | `string?` | Resolved placeholder |
|
|
212
|
+
| `disabled` | `boolean?` | Whether the field is disabled |
|
|
213
|
+
| `hidden` | `boolean?` | Whether the field is hidden |
|
|
214
|
+
| `readonly` | `boolean?` | Whether the field is read-only |
|
|
215
|
+
| `optional` | `boolean?` | Whether the field is optional |
|
|
216
|
+
| `required` | `boolean?` | Whether the field is required |
|
|
217
|
+
| `type` | `string` | The field input type |
|
|
218
|
+
| `altAction` | `TFoormAltAction?` | Alternate action `{ id, label }` from `@foorm.altAction` |
|
|
219
|
+
| `options` | `TFoormEntryOptions[]?` | Options for select/radio fields |
|
|
220
|
+
| `name` | `string?` | Field name |
|
|
221
|
+
| `maxLength` | `number?` | Max length constraint |
|
|
222
|
+
| `autocomplete` | `string?` | HTML autocomplete value |
|
|
223
|
+
| `field` | `FoormFieldDef?` | Full field definition for advanced use |
|
|
224
|
+
| `title` | `string?` | Resolved title for object/array fields |
|
|
225
|
+
| `level` | `number?` | Nesting level (root=0, increments per nested object/array) |
|
|
226
|
+
| `class` | `string \| object?` | CSS class(es) from `@foorm.fn.classes` |
|
|
227
|
+
| `style` | `string \| object?` | Inline styles from `@foorm.fn.styles` |
|
|
228
|
+
| `onRemove` | `() => void?` | Callback to remove this item from its parent array |
|
|
229
|
+
| `canRemove` | `boolean?` | Whether removal is allowed (respects minLength) |
|
|
230
|
+
| `removeLabel` | `string?` | Label for the remove button |
|
|
231
|
+
| `arrayIndex` | `number?` | Zero-based index when rendered as an array item |
|
|
232
|
+
| `onToggleOptional` | `(enabled: boolean) => void?` | Toggle an optional field on/off |
|
|
233
|
+
|
|
234
|
+
### Arrays
|
|
235
|
+
|
|
236
|
+
Array fields are handled automatically. Define them in your `.as` schema:
|
|
146
237
|
|
|
147
|
-
|
|
238
|
+
```
|
|
239
|
+
@meta.label 'Tags'
|
|
240
|
+
@foorm.array.add.label 'Add tag'
|
|
241
|
+
@foorm.array.remove.label 'x'
|
|
242
|
+
@expect.maxLength 5, 'Maximum 5 tags'
|
|
243
|
+
tags: string[]
|
|
244
|
+
|
|
245
|
+
@meta.label 'Addresses'
|
|
246
|
+
@foorm.title 'Addresses'
|
|
247
|
+
@foorm.array.add.label 'Add address'
|
|
248
|
+
addresses: {
|
|
249
|
+
@meta.label 'Street'
|
|
250
|
+
@meta.required 'Street is required'
|
|
251
|
+
street: string
|
|
252
|
+
|
|
253
|
+
@meta.label 'City'
|
|
254
|
+
city: string
|
|
255
|
+
}[]
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`OoForm` renders arrays with add/remove buttons, inline editing for primitives, and sub-form cards for objects. Array-level validation (`@expect.minLength`, `@expect.maxLength`) is displayed below the add button.
|
|
259
|
+
|
|
260
|
+
Union arrays (`(ObjectType | string)[]`) render a variant selector per item and offer one add button per variant.
|
|
261
|
+
|
|
262
|
+
The remove button is the responsibility of the wrapping component:
|
|
263
|
+
|
|
264
|
+
- **Object array items**: The object component receives `onRemove`, `canRemove`, and `removeLabel` via `TFoormComponentProps`.
|
|
265
|
+
- **Primitive array items**: The field component receives `onRemove`, `canRemove`, and `removeLabel` as props (since there is no object wrapper around them).
|
|
266
|
+
|
|
267
|
+
### Nested Groups
|
|
268
|
+
|
|
269
|
+
Use `@foorm.title` on a nested object field to render it as a titled, visually distinct section:
|
|
270
|
+
|
|
271
|
+
```
|
|
272
|
+
@foorm.title 'Settings'
|
|
273
|
+
settings: {
|
|
274
|
+
@meta.label 'Notify by email'
|
|
275
|
+
emailNotify: foorm.checkbox
|
|
276
|
+
|
|
277
|
+
@meta.label 'Page size'
|
|
278
|
+
@foorm.type 'number'
|
|
279
|
+
pageSize?: number
|
|
280
|
+
}
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
Without `@foorm.title`, nested object fields flatten into the parent form. With it, they render as an indented group with a title header. Groups can be nested to any depth.
|
|
284
|
+
|
|
285
|
+
### Scoped Slots
|
|
286
|
+
|
|
287
|
+
`OoForm` provides scoped slots for full layout control:
|
|
148
288
|
|
|
149
289
|
```vue
|
|
150
290
|
<template>
|
|
151
|
-
<OoForm :
|
|
152
|
-
<!--
|
|
153
|
-
<template #form.header="{
|
|
154
|
-
<h1
|
|
291
|
+
<OoForm :def="def" :form-data="formData" :types="typeComponents" @submit="onSubmit">
|
|
292
|
+
<!-- Form header (rendered before fields) -->
|
|
293
|
+
<template #form.header="{ clearErrors, reset, setErrors, formContext, disabled }">
|
|
294
|
+
<h1>Form Header</h1>
|
|
295
|
+
</template>
|
|
296
|
+
|
|
297
|
+
<!-- Content before/after fields -->
|
|
298
|
+
<template #form.before="{ clearErrors, reset, setErrors }">
|
|
299
|
+
<p>All fields are required unless marked optional.</p>
|
|
155
300
|
</template>
|
|
156
301
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
<div class="my-field">
|
|
160
|
-
<label>{{ field.label }}</label>
|
|
161
|
-
<input
|
|
162
|
-
v-model="field.model.value"
|
|
163
|
-
@blur="field.onBlur"
|
|
164
|
-
:placeholder="field.placeholder"
|
|
165
|
-
:disabled="field.disabled"
|
|
166
|
-
/>
|
|
167
|
-
<span v-if="field.error" class="error">{{ field.error }}</span>
|
|
168
|
-
</div>
|
|
302
|
+
<template #form.after="{ clearErrors, reset, setErrors, disabled, formContext }">
|
|
303
|
+
<p v-if="disabled">Please fill out all required fields.</p>
|
|
169
304
|
</template>
|
|
170
305
|
|
|
171
306
|
<!-- Custom submit button -->
|
|
172
|
-
<template #form.submit="{ disabled,
|
|
173
|
-
<button
|
|
307
|
+
<template #form.submit="{ text, disabled, clearErrors, reset, setErrors, formContext }">
|
|
308
|
+
<button type="submit" :disabled="disabled" class="my-btn">{{ text }}</button>
|
|
309
|
+
</template>
|
|
310
|
+
|
|
311
|
+
<!-- Footer (rendered after submit) -->
|
|
312
|
+
<template #form.footer="{ disabled, clearErrors, reset, setErrors, formContext }">
|
|
313
|
+
<p>By submitting, you agree to our terms.</p>
|
|
174
314
|
</template>
|
|
175
315
|
</OoForm>
|
|
176
316
|
</template>
|
|
177
317
|
```
|
|
178
318
|
|
|
179
|
-
###
|
|
319
|
+
### Form Context
|
|
180
320
|
|
|
181
|
-
|
|
321
|
+
Pass runtime data (user session, feature flags, dynamic options) to computed functions and validators:
|
|
182
322
|
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
resetBtn: foorm.action
|
|
323
|
+
```vue
|
|
324
|
+
<script setup lang="ts">
|
|
325
|
+
const formContext = {
|
|
326
|
+
cityOptions: [
|
|
327
|
+
{ key: 'nyc', label: 'New York' },
|
|
328
|
+
{ key: 'la', label: 'Los Angeles' },
|
|
329
|
+
],
|
|
330
|
+
user: { role: 'admin' },
|
|
192
331
|
}
|
|
193
|
-
|
|
332
|
+
</script>
|
|
194
333
|
|
|
195
|
-
```vue
|
|
196
334
|
<template>
|
|
197
|
-
<OoForm
|
|
335
|
+
<OoForm
|
|
336
|
+
:def="def"
|
|
337
|
+
:form-data="formData"
|
|
338
|
+
:form-context="formContext"
|
|
339
|
+
:types="typeComponents"
|
|
340
|
+
@submit="onSubmit"
|
|
341
|
+
/>
|
|
198
342
|
</template>
|
|
343
|
+
```
|
|
199
344
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
</script>
|
|
345
|
+
Context is accessible in ATScript function strings as the third argument:
|
|
346
|
+
|
|
347
|
+
```
|
|
348
|
+
@foorm.fn.options '(v, data, ctx) => ctx.cityOptions || []'
|
|
349
|
+
city?: foorm.select
|
|
207
350
|
```
|
|
208
351
|
|
|
209
|
-
### Server-side
|
|
352
|
+
### Server-side Errors
|
|
210
353
|
|
|
211
|
-
Pass
|
|
354
|
+
Pass server-side validation errors directly to fields:
|
|
212
355
|
|
|
213
356
|
```vue
|
|
214
|
-
<template>
|
|
215
|
-
<OoForm :form="form" :form-data="formData" :errors="serverErrors" @submit="handleSubmit" />
|
|
216
|
-
</template>
|
|
217
|
-
|
|
218
357
|
<script setup lang="ts">
|
|
219
358
|
import { ref } from 'vue'
|
|
220
359
|
|
|
221
360
|
const serverErrors = ref<Record<string, string>>({})
|
|
222
361
|
|
|
223
|
-
async function handleSubmit(data:
|
|
224
|
-
const
|
|
225
|
-
if (
|
|
226
|
-
serverErrors.value =
|
|
227
|
-
// e.g. { email: 'Email already exists' }
|
|
362
|
+
async function handleSubmit(data: any) {
|
|
363
|
+
const result = await api.submit(data)
|
|
364
|
+
if (result.errors) {
|
|
365
|
+
serverErrors.value = result.errors // e.g. { email: 'Already taken' }
|
|
228
366
|
}
|
|
229
367
|
}
|
|
230
368
|
</script>
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
## API
|
|
234
369
|
|
|
235
|
-
|
|
370
|
+
<template>
|
|
371
|
+
<OoForm
|
|
372
|
+
:def="def"
|
|
373
|
+
:form-data="formData"
|
|
374
|
+
:errors="serverErrors"
|
|
375
|
+
:types="typeComponents"
|
|
376
|
+
@submit="handleSubmit"
|
|
377
|
+
/>
|
|
378
|
+
</template>
|
|
379
|
+
```
|
|
236
380
|
|
|
237
|
-
|
|
381
|
+
### Actions
|
|
238
382
|
|
|
239
|
-
|
|
240
|
-
import { useFoorm } from '@foormjs/vue'
|
|
241
|
-
import { MyForm } from './my-form.as'
|
|
383
|
+
Define alternate submit actions using the `foorm.action` primitive:
|
|
242
384
|
|
|
243
|
-
|
|
385
|
+
```
|
|
386
|
+
@meta.label 'Reset Password'
|
|
387
|
+
@foorm.altAction 'reset-password', 'Reset Password'
|
|
388
|
+
resetBtn: foorm.action
|
|
244
389
|
```
|
|
245
390
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
- `form` -- `TFoormModel` object with fields, title, and submit config
|
|
249
|
-
- `formData` -- Vue `reactive()` object initialized from field defaults
|
|
250
|
-
|
|
251
|
-
### `OoForm` Props
|
|
252
|
-
|
|
253
|
-
| Prop | Type | Description |
|
|
254
|
-
| ----------------- | --------------------------- | -------------------------------------------- |
|
|
255
|
-
| `form` | `TFoormModel` | Form model (required) |
|
|
256
|
-
| `formData` | `object` | Reactive form data (auto-created if omitted) |
|
|
257
|
-
| `formContext` | `object` | External context for computed functions |
|
|
258
|
-
| `firstValidation` | `'on-blur' \| 'on-submit'` | When to trigger first validation |
|
|
259
|
-
| `components` | `Record<string, Component>` | Named component map for `@foorm.component` |
|
|
260
|
-
| `types` | `Record<string, Component>` | Type-based component map |
|
|
261
|
-
| `errors` | `Record<string, string>` | External validation errors |
|
|
262
|
-
|
|
263
|
-
### `OoForm` Events
|
|
264
|
-
|
|
265
|
-
| Event | Payload | Description |
|
|
266
|
-
| -------------------- | ---------------- | -------------------------------------------------------------- |
|
|
267
|
-
| `submit` | `formData` | Emitted when the form passes validation and is submitted |
|
|
268
|
-
| `action` | `name, formData` | Emitted when an action button is clicked |
|
|
269
|
-
| `unsupported-action` | `name, formData` | Emitted when an action is clicked but not defined in the model |
|
|
270
|
-
|
|
271
|
-
### `OoForm` Slots
|
|
272
|
-
|
|
273
|
-
| Slot | Props | Description |
|
|
274
|
-
| -------------- | -------------------------------------------------- | ---------------------------------------------- |
|
|
275
|
-
| `form.header` | `title, clearErrors, reset, formContext, disabled` | Before all fields (default: `<h2>` with title) |
|
|
276
|
-
| `form.before` | `clearErrors, reset` | After header, before fields |
|
|
277
|
-
| `field:{type}` | All field props (see below) | Override renderer for a field type |
|
|
278
|
-
| `form.after` | `clearErrors, reset, disabled, formContext` | After fields, before submit |
|
|
279
|
-
| `form.submit` | `disabled, text, clearErrors, reset, formContext` | Submit button |
|
|
280
|
-
| `form.footer` | `disabled, clearErrors, reset, formContext` | After submit button |
|
|
281
|
-
|
|
282
|
-
### Field Slot Props
|
|
283
|
-
|
|
284
|
-
Every field slot (`#field:text`, `#field:select`, etc.) receives:
|
|
285
|
-
|
|
286
|
-
| Prop | Type | Description |
|
|
287
|
-
| -------------- | ------------------------- | ----------------------------------------------------------- |
|
|
288
|
-
| `model` | `{ value: V }` | Two-way binding (use `v-model="field.model.value"`) |
|
|
289
|
-
| `onBlur` | `Function` | Call on blur to trigger validation |
|
|
290
|
-
| `error` | `string \| undefined` | Current validation error |
|
|
291
|
-
| `label` | `string` | Evaluated label |
|
|
292
|
-
| `description` | `string` | Evaluated description |
|
|
293
|
-
| `hint` | `string` | Evaluated hint |
|
|
294
|
-
| `placeholder` | `string` | Evaluated placeholder |
|
|
295
|
-
| `options` | `TFoormEntryOptions[]` | Options for select/radio |
|
|
296
|
-
| `classes` | `Record<string, boolean>` | CSS class object (includes `error`, `disabled`, `required`) |
|
|
297
|
-
| `styles` | `string \| Record` | Inline styles |
|
|
298
|
-
| `disabled` | `boolean` | Evaluated disabled state |
|
|
299
|
-
| `hidden` | `boolean` | Evaluated hidden state |
|
|
300
|
-
| `readonly` | `boolean` | Evaluated readonly state |
|
|
301
|
-
| `optional` | `boolean` | Evaluated optional state |
|
|
302
|
-
| `required` | `boolean` | Inverse of optional |
|
|
303
|
-
| `type` | `string` | Field type |
|
|
304
|
-
| `vName` | `string` | HTML `name` attribute |
|
|
305
|
-
| `field` | `string` | Field identifier |
|
|
306
|
-
| `altAction` | `string` | Action name (for action fields) |
|
|
307
|
-
| `autocomplete` | `string` | HTML autocomplete value |
|
|
308
|
-
| `maxLength` | `number` | Max length constraint |
|
|
309
|
-
| `formData` | `object` | Full form data |
|
|
310
|
-
| `formContext` | `object` | Form context |
|
|
311
|
-
| `attrs` | `Record` | Custom attributes (evaluated, can be used with `v-bind`) |
|
|
391
|
+
Handle the action event:
|
|
312
392
|
|
|
313
|
-
|
|
393
|
+
```vue
|
|
394
|
+
<template>
|
|
395
|
+
<OoForm
|
|
396
|
+
:def="def"
|
|
397
|
+
:form-data="formData"
|
|
398
|
+
:types="typeComponents"
|
|
399
|
+
@submit="onSubmit"
|
|
400
|
+
@action="onAction"
|
|
401
|
+
/>
|
|
402
|
+
</template>
|
|
314
403
|
|
|
315
|
-
|
|
404
|
+
<script setup lang="ts">
|
|
405
|
+
function onAction(name: string, data: any) {
|
|
406
|
+
if (name === 'reset-password') {
|
|
407
|
+
// handle reset password
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
</script>
|
|
411
|
+
```
|
|
316
412
|
|
|
317
|
-
###
|
|
413
|
+
### Paragraphs
|
|
318
414
|
|
|
319
|
-
|
|
415
|
+
Display static or computed text using the `foorm.paragraph` primitive:
|
|
320
416
|
|
|
321
|
-
```
|
|
322
|
-
|
|
417
|
+
```
|
|
418
|
+
@foorm.value 'Please fill out all required fields.'
|
|
419
|
+
info: foorm.paragraph
|
|
323
420
|
|
|
324
|
-
|
|
325
|
-
|
|
421
|
+
@foorm.fn.value '(v, data) => "Hello, " + (data.firstName || "guest") + "!"'
|
|
422
|
+
greeting: foorm.paragraph
|
|
326
423
|
```
|
|
327
424
|
|
|
328
|
-
|
|
425
|
+
Paragraphs are phantom fields — they are excluded from form data, validation, and TypeScript types. They only exist for display purposes.
|
|
329
426
|
|
|
330
|
-
|
|
427
|
+
## API Reference
|
|
331
428
|
|
|
332
|
-
|
|
333
|
-
| ---------------------------- | ------------------------- | ----------------------------------------- |
|
|
334
|
-
| `text`, `password`, `number` | `<input>` | With label, description, error/hint |
|
|
335
|
-
| `select` | `<select>` | With placeholder as disabled first option |
|
|
336
|
-
| `radio` | Radio group | Vertical layout with labels |
|
|
337
|
-
| `checkbox` | `<input type="checkbox">` | Label beside the checkbox |
|
|
338
|
-
| `paragraph` | `<p>` | Description text, no input |
|
|
339
|
-
| `action` | `<button>` | Emits action event on click |
|
|
429
|
+
### `useFoorm(type)`
|
|
340
430
|
|
|
341
|
-
|
|
431
|
+
Creates a reactive form definition and data object from an ATScript annotated type.
|
|
342
432
|
|
|
343
|
-
|
|
433
|
+
```ts
|
|
434
|
+
const { def, formData } = useFoorm(MyFormType)
|
|
435
|
+
```
|
|
344
436
|
|
|
345
|
-
|
|
437
|
+
- **`def`** — `FoormDef` with root field, ordered fields, the source type, and a flatMap
|
|
438
|
+
- **`formData`** — Vue `reactive()` object with default values from the schema
|
|
346
439
|
|
|
347
|
-
|
|
348
|
-
2. **Type component** -- field type + `types` prop
|
|
349
|
-
3. **Scoped slot** -- `#field:{type}` slot
|
|
350
|
-
4. **Built-in default** -- standard HTML renderer
|
|
351
|
-
5. **Error message** -- "Not supported field type" fallback
|
|
440
|
+
### `OoForm`
|
|
352
441
|
|
|
353
|
-
|
|
442
|
+
Renderless form wrapper component.
|
|
354
443
|
|
|
355
|
-
|
|
444
|
+
**Props:**
|
|
356
445
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
446
|
+
| Prop | Type | Required | Description |
|
|
447
|
+
| ----------------- | ------------------------------------------------------------------------ | -------- | ------------------------------------------------------- |
|
|
448
|
+
| `def` | `FoormDef` | Yes | Form definition from `useFoorm()` or `createFoormDef()` |
|
|
449
|
+
| `formData` | `object` | No | Reactive form data (created internally if omitted) |
|
|
450
|
+
| `formContext` | `object` | No | External context for computed functions and validators |
|
|
451
|
+
| `firstValidation` | `'on-change' \| 'touched-on-blur' \| 'on-blur' \| 'on-submit' \| 'none'` | No | When to trigger first validation |
|
|
452
|
+
| `components` | `Record<string, Component>` | No | Named components (matched by `@foorm.component`) |
|
|
453
|
+
| `types` | `Record<string, Component>` | Yes | Type-based components (matched by field type) |
|
|
454
|
+
| `errors` | `Record<string, string>` | No | External error messages (e.g. server-side) |
|
|
362
455
|
|
|
363
|
-
|
|
364
|
-
plugins: [atscript.vite(), vue()],
|
|
365
|
-
})
|
|
366
|
-
```
|
|
456
|
+
**Events:**
|
|
367
457
|
|
|
368
|
-
|
|
458
|
+
| Event | Payload | Description |
|
|
459
|
+
| -------------------- | ----------------------------------------------- | --------------------------------------------------------------- |
|
|
460
|
+
| `submit` | `formData` | Emitted on valid form submission |
|
|
461
|
+
| `error` | `{ path: string; message: string }[]` | Emitted when validation fails on submit |
|
|
462
|
+
| `action` | `name, formData` | Emitted when an action button is clicked (supported alt action) |
|
|
463
|
+
| `unsupported-action` | `name, formData` | Emitted for unrecognized action names |
|
|
464
|
+
| `change` | `type: TFoormChangeType, path, value, formData` | Emitted on field update, array add/remove, or union switch |
|
|
369
465
|
|
|
370
|
-
|
|
371
|
-
// atscript.config.ts
|
|
372
|
-
import { defineConfig } from '@atscript/core'
|
|
373
|
-
import ts from '@atscript/typescript'
|
|
374
|
-
import { foormPlugin } from '@foormjs/atscript/plugin'
|
|
466
|
+
**Slots:**
|
|
375
467
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
}
|
|
380
|
-
|
|
468
|
+
| Slot | Scope | Description |
|
|
469
|
+
| ------------- | ---------------------------------------------------------------- | --------------------------- |
|
|
470
|
+
| `form.header` | `{ clearErrors, reset, setErrors, formContext, disabled }` | Before fields |
|
|
471
|
+
| `form.before` | `{ clearErrors, reset, setErrors }` | After header, before fields |
|
|
472
|
+
| `form.after` | `{ clearErrors, reset, setErrors, disabled, formContext }` | After fields, before submit |
|
|
473
|
+
| `form.submit` | `{ text, disabled, clearErrors, reset, setErrors, formContext }` | Submit button |
|
|
474
|
+
| `form.footer` | `{ disabled, clearErrors, reset, setErrors, formContext }` | After submit |
|
|
475
|
+
|
|
476
|
+
### `OoField`
|
|
477
|
+
|
|
478
|
+
Universal field renderer. Resolves component, props, validation, and nesting for any field type.
|
|
479
|
+
|
|
480
|
+
**Props:**
|
|
481
|
+
|
|
482
|
+
| Prop | Type | Description |
|
|
483
|
+
| ------------- | --------------- | -------------------------------------------------------------- |
|
|
484
|
+
| `field` | `FoormFieldDef` | Field definition from `def.fields` or `def.rootField` |
|
|
485
|
+
| `error` | `string?` | External error message |
|
|
486
|
+
| `onRemove` | `() => void?` | Callback to remove this item from its parent array |
|
|
487
|
+
| `canRemove` | `boolean?` | Whether removal is allowed (respects minLength) |
|
|
488
|
+
| `removeLabel` | `string?` | Label for the remove button (from `@foorm.array.remove.label`) |
|
|
489
|
+
| `arrayIndex` | `number?` | Zero-based index when rendered as an array item |
|
|
490
|
+
|
|
491
|
+
### Default Components
|
|
492
|
+
|
|
493
|
+
All default type components are exported and can be used as-is or as reference implementations:
|
|
494
|
+
|
|
495
|
+
| Component | Field Type | Description |
|
|
496
|
+
| ------------- | ----------- | -------------------------------------------------- |
|
|
497
|
+
| `OoInput` | `text`, etc | Text/password/number input with OoFieldShell |
|
|
498
|
+
| `OoSelect` | `select` | Dropdown select with OoFieldShell |
|
|
499
|
+
| `OoRadio` | `radio` | Radio button group with OoFieldShell |
|
|
500
|
+
| `OoCheckbox` | `checkbox` | Boolean checkbox with OoFieldShell |
|
|
501
|
+
| `OoParagraph` | `paragraph` | Read-only text display |
|
|
502
|
+
| `OoAction` | `action` | Action button with altAction support |
|
|
503
|
+
| `OoObject` | `object` | Object container (title + OoIterator) |
|
|
504
|
+
| `OoArray` | `array` | Array container (add/remove + OoIterator per item) |
|
|
505
|
+
| `OoUnion` | `union` | Union variant picker + selected variant rendering |
|
|
506
|
+
| `OoTuple` | `tuple` | Fixed-length tuple via OoIterator |
|
|
507
|
+
|
|
508
|
+
### Composables
|
|
509
|
+
|
|
510
|
+
| Export | Description |
|
|
511
|
+
| --------------------------------- | --------------------------------------------------------------------- |
|
|
512
|
+
| `useFoorm(type)` | Returns `{ def, formData }` from an ATScript annotated type |
|
|
513
|
+
| `useFoormArray(field, disabled?)` | Array state management (keys, add/remove, constraints) |
|
|
514
|
+
| `useFoormUnion(props)` | Union variant state management |
|
|
515
|
+
| `useConsumeUnionContext()` | Consume and clear the `__foorm_union` injection |
|
|
516
|
+
| `formatIndexedLabel()` | Format label with array index prefix (e.g. `"Address #1"`) |
|
|
517
|
+
| `createDefaultTypes()` | Returns a `TFoormTypeComponents` map with all default type components |
|
|
518
|
+
|
|
519
|
+
### Types
|
|
520
|
+
|
|
521
|
+
| Export | Description |
|
|
522
|
+
| -------------------------- | ------------------------------------------------------------- |
|
|
523
|
+
| `TFoormBaseComponentProps` | Shared base props (disabled, hidden) |
|
|
524
|
+
| `TFoormComponentProps` | Unified props interface for ALL custom field components |
|
|
525
|
+
| `TFoormTypeComponents` | Required shape for the `types` prop on `OoForm` |
|
|
526
|
+
| `TFoormChangeType` | `'update' \| 'array-add' \| 'array-remove' \| 'union-switch'` |
|
|
527
|
+
| `TFoormUnionContext` | Union context provided via `__foorm_union` inject |
|
|
528
|
+
|
|
529
|
+
For ATScript documentation, see [atscript.moost.org](https://atscript.moost.org).
|
|
381
530
|
|
|
382
531
|
## License
|
|
383
532
|
|