@moonmangit/vue-autoform 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 +259 -119
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,233 +1,373 @@
|
|
|
1
|
-
# vue-autoform
|
|
1
|
+
# @moonmangit/vue-autoform
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@moonmangit/vue-autoform)
|
|
4
|
+
[](./LICENSE)
|
|
5
|
+
[](https://vuejs.org)
|
|
6
|
+
[](https://zod.dev)
|
|
7
|
+
|
|
8
|
+
**Headless** Vue 3 form library that auto-renders fields from a Zod schema with responsive CSS grid layout and fully customisable field components. You own the UI — the library owns the wiring.
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Table of Contents
|
|
13
|
+
|
|
14
|
+
- [Features](#features)
|
|
15
|
+
- [Install](#install)
|
|
16
|
+
- [Quick Start](#quick-start)
|
|
17
|
+
- [Field Components](#field-components)
|
|
18
|
+
- [Layout](#layout)
|
|
19
|
+
- [No Layout](#no-layout-single-column)
|
|
20
|
+
- [Shorthand Layout](#shorthand-layout)
|
|
21
|
+
- [Explicit Layout](#explicit-layout)
|
|
22
|
+
- [Validation](#validation)
|
|
23
|
+
- [Submit Handling](#submit-handling)
|
|
24
|
+
- [Custom Error Rendering](#custom-error-rendering)
|
|
25
|
+
- [Custom Breakpoints](#custom-breakpoints)
|
|
26
|
+
- [CSS Variables](#css-variables)
|
|
27
|
+
- [Props Reference](#props-reference)
|
|
28
|
+
- [Development](#development)
|
|
29
|
+
|
|
30
|
+
---
|
|
4
31
|
|
|
5
32
|
## Features
|
|
6
33
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
34
|
+
| | |
|
|
35
|
+
| -------------------------- | ---------------------------------------------------------------------------------------- |
|
|
36
|
+
| 🧩 **Schema-driven** | Define fields once in Zod — validation rules, types, and error messages come free |
|
|
37
|
+
| 🎨 **Headless** | No styles imposed. Bring your own components, any UI library |
|
|
38
|
+
| 📐 **Responsive grid** | Shorthand (`{ default: 1, md: 2, lg: 3 }`) or explicit per-breakpoint row/column layouts |
|
|
39
|
+
| ✅ **Flexible validation** | Per-field on `blur`, live on `input`, or all-at-once on `submit` |
|
|
40
|
+
| 🔑 **Submit control** | Expose `validateAll()` via template ref for full submit-flow control |
|
|
41
|
+
| 🪝 **Error slot** | Override error rendering globally or per-field via scoped slots |
|
|
42
|
+
| 🔷 **TypeScript-first** | Full type inference on schema keys, field configs, and layout |
|
|
43
|
+
|
|
44
|
+
---
|
|
12
45
|
|
|
13
46
|
## Install
|
|
14
47
|
|
|
15
48
|
```bash
|
|
16
|
-
pnpm
|
|
17
|
-
|
|
18
|
-
|
|
49
|
+
# pnpm
|
|
50
|
+
pnpm add @moonmangit/vue-autoform zod@^3
|
|
51
|
+
|
|
52
|
+
# npm
|
|
53
|
+
npm install @moonmangit/vue-autoform zod@^3
|
|
54
|
+
|
|
55
|
+
# yarn
|
|
56
|
+
yarn add @moonmangit/vue-autoform zod@^3
|
|
19
57
|
```
|
|
20
58
|
|
|
21
|
-
> `vue` and `zod` are peer dependencies
|
|
59
|
+
> **Note:** `vue` and `zod` are peer dependencies — install them in your own project.
|
|
60
|
+
> zod v4 is not yet supported; pin to `zod@^3`.
|
|
22
61
|
|
|
23
62
|
---
|
|
24
63
|
|
|
25
|
-
##
|
|
64
|
+
## Quick Start
|
|
26
65
|
|
|
27
66
|
```vue
|
|
28
|
-
<template>
|
|
29
|
-
<AutoForm
|
|
30
|
-
:schema="schema"
|
|
31
|
-
:fields="fields"
|
|
32
|
-
v-model="formData"
|
|
33
|
-
validate-on="blur"
|
|
34
|
-
/>
|
|
35
|
-
</template>
|
|
36
|
-
|
|
37
67
|
<script setup lang="ts">
|
|
38
|
-
import {
|
|
39
|
-
import { z } from
|
|
40
|
-
import { AutoForm } from
|
|
41
|
-
import
|
|
68
|
+
import { ref } from "vue";
|
|
69
|
+
import { z } from "zod";
|
|
70
|
+
import { AutoForm } from "@moonmangit/vue-autoform";
|
|
71
|
+
import "@moonmangit/vue-autoform/dist/style.css";
|
|
72
|
+
import TextInput from "./components/TextInput.vue";
|
|
42
73
|
|
|
43
74
|
const schema = z.object({
|
|
44
|
-
email: z.string().email(
|
|
45
|
-
password: z.string().min(8,
|
|
46
|
-
})
|
|
75
|
+
email: z.string().email("Invalid email"),
|
|
76
|
+
password: z.string().min(8, "At least 8 characters"),
|
|
77
|
+
});
|
|
47
78
|
|
|
48
79
|
const fields = {
|
|
49
|
-
email: {
|
|
50
|
-
component: MyTextInput,
|
|
51
|
-
props: { label: 'Email', type: 'email' },
|
|
52
|
-
},
|
|
80
|
+
email: { component: TextInput, props: { label: "Email", type: "email" } },
|
|
53
81
|
password: {
|
|
54
|
-
component:
|
|
55
|
-
props: { label:
|
|
82
|
+
component: TextInput,
|
|
83
|
+
props: { label: "Password", type: "password" },
|
|
56
84
|
},
|
|
57
|
-
}
|
|
85
|
+
};
|
|
58
86
|
|
|
59
|
-
const formData =
|
|
87
|
+
const formData = ref({ email: "", password: "" });
|
|
60
88
|
</script>
|
|
89
|
+
|
|
90
|
+
<template>
|
|
91
|
+
<AutoForm
|
|
92
|
+
v-model="formData"
|
|
93
|
+
:schema="schema"
|
|
94
|
+
:fields="fields"
|
|
95
|
+
validate-on="blur"
|
|
96
|
+
/>
|
|
97
|
+
</template>
|
|
61
98
|
```
|
|
62
99
|
|
|
63
100
|
---
|
|
64
101
|
|
|
65
|
-
## Field
|
|
102
|
+
## Field Components
|
|
66
103
|
|
|
67
|
-
|
|
104
|
+
`AutoForm` is **headless** — you provide the components that render each field. Every field component receives these props automatically:
|
|
68
105
|
|
|
69
|
-
| Prop
|
|
70
|
-
|
|
71
|
-
| `modelValue` | `unknown`
|
|
72
|
-
| `error`
|
|
73
|
-
| `...props`
|
|
106
|
+
| Prop | Type | Description |
|
|
107
|
+
| ------------ | --------------------- | --------------------------------------------- |
|
|
108
|
+
| `modelValue` | `unknown` | Current field value (bind with `:value`) |
|
|
109
|
+
| `error` | `string \| undefined` | Zod error message for this field |
|
|
110
|
+
| `...props` | — | Any extra props from your `FieldConfig.props` |
|
|
111
|
+
|
|
112
|
+
It must emit:
|
|
113
|
+
|
|
114
|
+
- `update:modelValue` — on value change
|
|
115
|
+
- `blur` — when the field loses focus (for blur validation)
|
|
74
116
|
|
|
75
|
-
|
|
117
|
+
### Minimal example
|
|
76
118
|
|
|
77
119
|
```vue
|
|
78
|
-
<!--
|
|
120
|
+
<!-- components/TextInput.vue -->
|
|
121
|
+
<script setup lang="ts">
|
|
122
|
+
defineProps<{
|
|
123
|
+
modelValue?: string;
|
|
124
|
+
error?: string;
|
|
125
|
+
label?: string;
|
|
126
|
+
type?: string;
|
|
127
|
+
}>();
|
|
128
|
+
defineEmits<{ (e: "update:modelValue", v: string): void; (e: "blur"): void }>();
|
|
129
|
+
</script>
|
|
130
|
+
|
|
79
131
|
<template>
|
|
80
132
|
<div>
|
|
81
|
-
<
|
|
82
|
-
<
|
|
133
|
+
<label>{{ label }}</label>
|
|
134
|
+
<input
|
|
135
|
+
:type="type ?? 'text'"
|
|
136
|
+
:value="modelValue"
|
|
137
|
+
@input="
|
|
138
|
+
$emit('update:modelValue', ($event.target as HTMLInputElement).value)
|
|
139
|
+
"
|
|
140
|
+
@blur="$emit('blur')"
|
|
141
|
+
:class="{ 'is-error': error }"
|
|
142
|
+
/>
|
|
143
|
+
<span v-if="error" class="error-msg">{{ error }}</span>
|
|
83
144
|
</div>
|
|
84
145
|
</template>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### FieldConfig shape
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
type FieldConfig = {
|
|
152
|
+
component: Component; // your Vue component
|
|
153
|
+
props?: Record<string, unknown>; // extra props passed through
|
|
154
|
+
};
|
|
89
155
|
```
|
|
90
156
|
|
|
91
157
|
---
|
|
92
158
|
|
|
93
159
|
## Layout
|
|
94
160
|
|
|
95
|
-
`layout` is optional
|
|
161
|
+
The `layout` prop is **optional**. Without it, fields render in schema-key order in a single column.
|
|
96
162
|
|
|
97
|
-
###
|
|
163
|
+
### No Layout (single column)
|
|
98
164
|
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
default: 1, // 1 column on mobile
|
|
102
|
-
md: 2, // 2 columns at ≥768px
|
|
103
|
-
lg: 3, // 3 columns at ≥1024px
|
|
104
|
-
}
|
|
165
|
+
```vue
|
|
166
|
+
<AutoForm v-model="formData" :schema="schema" :fields="fields" />
|
|
105
167
|
```
|
|
106
168
|
|
|
107
|
-
|
|
169
|
+
### Shorthand Layout
|
|
108
170
|
|
|
109
|
-
|
|
171
|
+
Pass column counts per breakpoint. Fields auto-flow left-to-right, top-to-bottom.
|
|
110
172
|
|
|
111
173
|
```ts
|
|
112
174
|
const layout = {
|
|
113
|
-
default:
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
],
|
|
118
|
-
md: [
|
|
119
|
-
['firstName', 'lastName'],
|
|
120
|
-
['email'],
|
|
121
|
-
],
|
|
122
|
-
}
|
|
175
|
+
default: 1, // mobile: 1 column
|
|
176
|
+
md: 2, // ≥ 768px: 2 columns
|
|
177
|
+
lg: 3, // ≥ 1024px: 3 columns
|
|
178
|
+
};
|
|
123
179
|
```
|
|
124
180
|
|
|
125
|
-
Each inner array is a row; each string is a field key matching the Zod schema.
|
|
126
|
-
|
|
127
|
-
---
|
|
128
|
-
|
|
129
|
-
## Breakpoints
|
|
130
|
-
|
|
131
|
-
Default breakpoints (Tailwind-compatible):
|
|
132
|
-
|
|
133
|
-
| Key | Min-width |
|
|
134
|
-
|---|---|
|
|
135
|
-
| `sm` | 640px |
|
|
136
|
-
| `md` | 768px |
|
|
137
|
-
| `lg` | 1024px |
|
|
138
|
-
| `xl` | 1280px |
|
|
139
|
-
| `2xl` | 1536px |
|
|
140
|
-
|
|
141
|
-
Override per form with the `breakpoints` prop:
|
|
142
|
-
|
|
143
181
|
```vue
|
|
144
182
|
<AutoForm
|
|
183
|
+
v-model="formData"
|
|
145
184
|
:schema="schema"
|
|
146
|
-
:layout="{ default: 1, tablet: 2 }"
|
|
147
|
-
:breakpoints="{ tablet: 900 }"
|
|
148
185
|
:fields="fields"
|
|
149
|
-
|
|
186
|
+
:layout="layout"
|
|
150
187
|
/>
|
|
151
188
|
```
|
|
152
189
|
|
|
190
|
+
### Explicit Layout
|
|
191
|
+
|
|
192
|
+
Control exactly which fields appear in each row at each breakpoint. Each inner array is a row; each string is a schema key.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const layout = {
|
|
196
|
+
// Mobile: one field per row
|
|
197
|
+
default: [["firstName"], ["lastName"], ["email"], ["role"]],
|
|
198
|
+
// Tablet: 2-col rows
|
|
199
|
+
md: [
|
|
200
|
+
["firstName", "lastName"],
|
|
201
|
+
["email", "role"],
|
|
202
|
+
],
|
|
203
|
+
// Desktop: everything in one row
|
|
204
|
+
lg: [["firstName", "lastName", "email", "role"]],
|
|
205
|
+
};
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
> Fields not listed in a breakpoint layout are **hidden** at that breakpoint. Use this to progressively reveal complexity.
|
|
209
|
+
|
|
153
210
|
---
|
|
154
211
|
|
|
155
212
|
## Validation
|
|
156
213
|
|
|
214
|
+
Three modes, set once per form via `validate-on`:
|
|
215
|
+
|
|
157
216
|
```vue
|
|
158
|
-
<!--
|
|
217
|
+
<!-- Per-field on focus-out (default) -->
|
|
159
218
|
<AutoForm validate-on="blur" ... />
|
|
160
219
|
|
|
161
|
-
<!--
|
|
220
|
+
<!-- Live — re-validates on every keystroke -->
|
|
162
221
|
<AutoForm validate-on="input" ... />
|
|
163
222
|
|
|
164
|
-
<!--
|
|
165
|
-
<AutoForm
|
|
223
|
+
<!-- Deferred — errors only shown after submit -->
|
|
224
|
+
<AutoForm validate-on="submit" ... />
|
|
166
225
|
```
|
|
167
226
|
|
|
168
|
-
|
|
227
|
+
---
|
|
228
|
+
|
|
229
|
+
## Submit Handling
|
|
230
|
+
|
|
231
|
+
Use a template `ref` to call `validateAll()` imperatively. It validates every field and surfaces errors, returning `true` if the form is valid.
|
|
232
|
+
|
|
233
|
+
```vue
|
|
234
|
+
<script setup lang="ts">
|
|
235
|
+
import { ref } from "vue";
|
|
236
|
+
import { AutoForm } from "@moonmangit/vue-autoform";
|
|
237
|
+
|
|
238
|
+
const formRef = ref<InstanceType<typeof AutoForm> | null>(null);
|
|
239
|
+
const submitResult = ref<boolean | null>(null);
|
|
169
240
|
|
|
170
|
-
```ts
|
|
171
|
-
const form = ref()
|
|
172
241
|
function handleSubmit() {
|
|
173
|
-
|
|
242
|
+
submitResult.value = formRef.value?.validateAll() ?? false;
|
|
243
|
+
|
|
244
|
+
if (submitResult.value) {
|
|
245
|
+
// ✅ form is valid — send to API
|
|
246
|
+
}
|
|
174
247
|
}
|
|
248
|
+
</script>
|
|
249
|
+
|
|
250
|
+
<template>
|
|
251
|
+
<AutoForm
|
|
252
|
+
ref="formRef"
|
|
253
|
+
v-model="formData"
|
|
254
|
+
:schema="schema"
|
|
255
|
+
:fields="fields"
|
|
256
|
+
validate-on="submit"
|
|
257
|
+
/>
|
|
258
|
+
<button @click="handleSubmit">Submit</button>
|
|
259
|
+
<p v-if="submitResult === true">✓ Submitted!</p>
|
|
260
|
+
<p v-if="submitResult === false">✗ Fix the errors above</p>
|
|
261
|
+
</template>
|
|
175
262
|
```
|
|
176
263
|
|
|
177
264
|
---
|
|
178
265
|
|
|
179
|
-
## Error
|
|
266
|
+
## Custom Error Rendering
|
|
180
267
|
|
|
181
|
-
|
|
268
|
+
By default, errors are passed as a prop to your field component. You can also override rendering globally using the `#error` scoped slot:
|
|
182
269
|
|
|
183
270
|
```vue
|
|
184
|
-
<AutoForm :schema="schema" :fields="fields"
|
|
271
|
+
<AutoForm v-model="formData" :schema="schema" :fields="fields">
|
|
185
272
|
<template #error="{ fieldKey, error }">
|
|
186
|
-
<p v-if="error" class="my-error">
|
|
273
|
+
<p v-if="error" class="my-custom-error">
|
|
274
|
+
⚠ {{ error }}
|
|
275
|
+
</p>
|
|
187
276
|
</template>
|
|
188
277
|
</AutoForm>
|
|
189
278
|
```
|
|
190
279
|
|
|
280
|
+
The slot receives:
|
|
281
|
+
| Slot prop | Type | Description |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `fieldKey` | `string` | The schema key of the field |
|
|
284
|
+
| `error` | `string \| undefined` | Current validation error |
|
|
285
|
+
|
|
191
286
|
---
|
|
192
287
|
|
|
193
|
-
##
|
|
288
|
+
## Custom Breakpoints
|
|
289
|
+
|
|
290
|
+
Default breakpoints are Tailwind-compatible. Override per form with the `breakpoints` prop:
|
|
194
291
|
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
|
|
292
|
+
| Key | Default min-width |
|
|
293
|
+
| ----- | ----------------- |
|
|
294
|
+
| `sm` | 640px |
|
|
295
|
+
| `md` | 768px |
|
|
296
|
+
| `lg` | 1024px |
|
|
297
|
+
| `xl` | 1280px |
|
|
298
|
+
| `2xl` | 1536px |
|
|
299
|
+
|
|
300
|
+
```vue
|
|
301
|
+
<!-- Use a custom "tablet" breakpoint at 900px -->
|
|
302
|
+
<AutoForm
|
|
303
|
+
v-model="formData"
|
|
304
|
+
:schema="schema"
|
|
305
|
+
:fields="fields"
|
|
306
|
+
:layout="{ default: 1, tablet: 2 }"
|
|
307
|
+
:breakpoints="{ tablet: 900 }"
|
|
308
|
+
/>
|
|
309
|
+
```
|
|
203
310
|
|
|
204
311
|
---
|
|
205
312
|
|
|
206
313
|
## CSS Variables
|
|
207
314
|
|
|
208
|
-
|
|
315
|
+
Control spacing without touching component internals:
|
|
209
316
|
|
|
210
317
|
```css
|
|
318
|
+
/* In your global CSS or scoped to a wrapper */
|
|
211
319
|
.autoform {
|
|
212
|
-
--autoform-gap: 1rem;
|
|
213
|
-
--autoform-row-gap: 1rem;
|
|
320
|
+
--autoform-gap: 1rem; /* column gap between fields */
|
|
321
|
+
--autoform-row-gap: 1rem; /* row gap between field rows */
|
|
214
322
|
}
|
|
215
323
|
```
|
|
216
324
|
|
|
217
325
|
---
|
|
218
326
|
|
|
327
|
+
## Props Reference
|
|
328
|
+
|
|
329
|
+
| Prop | Type | Default | Description |
|
|
330
|
+
| ------------- | ----------------------------------- | ----------------- | ------------------------------------------------ |
|
|
331
|
+
| `schema` | `ZodObject` | **required** | Zod object schema defining fields and validation |
|
|
332
|
+
| `fields` | `Record<string, FieldConfig>` | **required** | Map of field key → component + props |
|
|
333
|
+
| `modelValue` | `Record<string, unknown>` | **required** | Form data object, bound with `v-model` |
|
|
334
|
+
| `layout` | `ShorthandLayout \| ExplicitLayout` | `undefined` | Responsive grid layout config |
|
|
335
|
+
| `validateOn` | `'blur' \| 'input' \| 'submit'` | `'blur'` | When to run per-field validation |
|
|
336
|
+
| `breakpoints` | `Partial<BreakpointMap>` | Tailwind defaults | Custom breakpoint widths in px |
|
|
337
|
+
|
|
338
|
+
### Exposed methods (via template ref)
|
|
339
|
+
|
|
340
|
+
| Method | Returns | Description |
|
|
341
|
+
| --------------- | ------------------------------------- | --------------------------------------------- |
|
|
342
|
+
| `validateAll()` | `boolean` | Validates all fields; returns `true` if valid |
|
|
343
|
+
| `errors` | `Record<string, string \| undefined>` | Reactive map of current field errors |
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
219
347
|
## Development
|
|
220
348
|
|
|
221
349
|
```bash
|
|
222
350
|
# Install dependencies
|
|
223
351
|
pnpm install
|
|
224
352
|
|
|
225
|
-
# Run playground (
|
|
353
|
+
# Run interactive playground (http://localhost:5174)
|
|
226
354
|
pnpm playground
|
|
227
355
|
|
|
228
|
-
# Build library
|
|
356
|
+
# Build library for distribution
|
|
229
357
|
pnpm build
|
|
230
358
|
|
|
231
|
-
# Type
|
|
359
|
+
# Type-check library source only
|
|
232
360
|
pnpm type-check
|
|
361
|
+
|
|
362
|
+
# Lint
|
|
363
|
+
pnpm lint
|
|
364
|
+
pnpm lint:fix
|
|
233
365
|
```
|
|
366
|
+
|
|
367
|
+
The `playground/` directory contains a full demo app with 5 examples covering every feature. It uses a Vite path alias to import the library source directly — no rebuild needed during development.
|
|
368
|
+
|
|
369
|
+
---
|
|
370
|
+
|
|
371
|
+
## License
|
|
372
|
+
|
|
373
|
+
MIT © [moonmangit](https://github.com/moonmangit)
|
package/package.json
CHANGED