@moonmangit/vue-autoform 0.1.0 → 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.
Files changed (2) hide show
  1. package/README.md +259 -119
  2. package/package.json +3 -2
package/README.md CHANGED
@@ -1,233 +1,373 @@
1
- # vue-autoform
1
+ # @moonmangit/vue-autoform
2
2
 
3
- Headless Vue 3 auto-rendering form library with Zod schema validation, responsive CSS grid layout, and fully customisable field components.
3
+ [![npm version](https://img.shields.io/npm/v/@moonmangit/vue-autoform)](https://www.npmjs.com/package/@moonmangit/vue-autoform)
4
+ [![license](https://img.shields.io/npm/l/@moonmangit/vue-autoform)](./LICENSE)
5
+ [![Vue 3](https://img.shields.io/badge/vue-3.x-42b883)](https://vuejs.org)
6
+ [![Zod](https://img.shields.io/badge/zod-3.x-3068B7)](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
- - **Schema-driven** — define fields with Zod; validation is automatic
8
- - **Headless** bring your own UI components; no styles imposed
9
- - **Responsive grid** shorthand col-counts or explicit per-breakpoint layouts, CSS-only
10
- - **Flexible validation** run on `blur`, `input`, or `submit`
11
- - **Full TypeScript support**
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 add vue-autoform zod
17
- # or
18
- npm install vue-autoform zod
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 and must be installed in your project.
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
- ## Basic Usage
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 { reactive } from 'vue'
39
- import { z } from 'zod'
40
- import { AutoForm } from 'vue-autoform'
41
- import MyTextInput from './MyTextInput.vue'
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('Invalid email'),
45
- password: z.string().min(8, 'Min 8 characters'),
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: MyTextInput,
55
- props: { label: 'Password', type: 'password' },
82
+ component: TextInput,
83
+ props: { label: "Password", type: "password" },
56
84
  },
57
- }
85
+ };
58
86
 
59
- const formData = reactive({ email: '', password: '' })
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 Component Contract
102
+ ## Field Components
66
103
 
67
- Your custom field components receive these props automatically:
104
+ `AutoForm` is **headless** — you provide the components that render each field. Every field component receives these props automatically:
68
105
 
69
- | Prop | Type | Description |
70
- |---|---|---|
71
- | `modelValue` | `unknown` | Current field value |
72
- | `error` | `string \| undefined` | Zod validation error message |
73
- | `...props` | — | Any extra props from `FieldConfig.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
- They should emit `update:modelValue` for v-model binding and `blur` for blur-based validation.
117
+ ### Minimal example
76
118
 
77
119
  ```vue
78
- <!-- Example field component -->
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
- <input :value="modelValue" @input="emit('update:modelValue', $event.target.value)" @blur="emit('blur')" />
82
- <span v-if="error">{{ error }}</span>
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
- <script setup lang="ts">
86
- defineProps<{ modelValue?: string; error?: string; label?: string }>()
87
- const emit = defineEmits(['update:modelValue', 'blur'])
88
- </script>
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. Omitting it renders all fields in a single column in schema-key order.
161
+ The `layout` prop is **optional**. Without it, fields render in schema-key order in a single column.
96
162
 
97
- ### Shorthand column count per breakpoint
163
+ ### No Layout (single column)
98
164
 
99
- ```ts
100
- const layout = {
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
- Fields auto-flow into columns following schema-key order.
169
+ ### Shorthand Layout
108
170
 
109
- ### Explicit full grid per breakpoint
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
- ['firstName'],
115
- ['lastName'],
116
- ['email'],
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
- v-model="formData"
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
- <!-- Validate on blur (default) -->
217
+ <!-- Per-field on focus-out (default) -->
159
218
  <AutoForm validate-on="blur" ... />
160
219
 
161
- <!-- Validate on every keystroke -->
220
+ <!-- Live — re-validates on every keystroke -->
162
221
  <AutoForm validate-on="input" ... />
163
222
 
164
- <!-- Validate only on submit -->
165
- <AutoForm ref="form" validate-on="submit" ... />
223
+ <!-- Deferred — errors only shown after submit -->
224
+ <AutoForm validate-on="submit" ... />
166
225
  ```
167
226
 
168
- For `submit` mode, call `validateAll()` via template ref:
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
- const valid = form.value.validateAll() // returns true/false
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 Slot
266
+ ## Custom Error Rendering
180
267
 
181
- Use the `#error` scoped slot for custom error rendering:
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" v-model="formData">
271
+ <AutoForm v-model="formData" :schema="schema" :fields="fields">
185
272
  <template #error="{ fieldKey, error }">
186
- <p v-if="error" class="my-error">{{ fieldKey }}: {{ error }}</p>
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
- ## Props Reference
288
+ ## Custom Breakpoints
289
+
290
+ Default breakpoints are Tailwind-compatible. Override per form with the `breakpoints` prop:
194
291
 
195
- | Prop | Type | Default | Description |
196
- |---|---|---|---|
197
- | `schema` | `ZodObject` | required | Zod object schema |
198
- | `fields` | `Record<string, FieldConfig>` | required | Field component map |
199
- | `modelValue` | `Record<string, unknown>` | required | Form data (`v-model`) |
200
- | `layout` | `AutoFormLayout` | undefined | Grid layout config |
201
- | `validateOn` | `'blur' \| 'input' \| 'submit'` | `'blur'` | Validation trigger |
202
- | `breakpoints` | `Partial<BreakpointMap>` | Tailwind defaults | Custom breakpoint widths |
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
- Customise spacing without overriding component styles:
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; /* gap between fields in a row */
213
- --autoform-row-gap: 1rem; /* gap between rows */
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 (port 5174)
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 check
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moonmangit/vue-autoform",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Headless Vue 3 auto-rendering form library with Zod schema validation and responsive grid layout",
5
5
  "type": "module",
6
6
  "main": "./dist/vue-autoform.cjs.js",
@@ -11,7 +11,8 @@
11
11
  "import": "./dist/vue-autoform.es.js",
12
12
  "require": "./dist/vue-autoform.cjs.js",
13
13
  "types": "./dist/vue-autoform.d.ts"
14
- }
14
+ },
15
+ "./dist/style.css": "./dist/style.css"
15
16
  },
16
17
  "files": [
17
18
  "dist"