@vuehookform/core 0.1.0

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 ADDED
@@ -0,0 +1,312 @@
1
+ # Vue Hook Form 📝
2
+
3
+ > A TypeScript-first form library for Vue 3, inspired by React Hook Form
4
+
5
+ **Vue Hook Form** makes form handling in Vue.js feel like a superpower. Built with performance, developer experience, and type safety as top priorities.
6
+
7
+ ## ✨ Features
8
+
9
+ - **🎯 TypeScript First** - Perfect type inference with zero manual typing
10
+ - **⚡ Zero Config** - Works out of the box with sensible defaults
11
+ - **🚀 Performant** - Minimal re-renders using uncontrolled inputs
12
+ - **🔥 Zod Native** - First-class Zod integration for validation
13
+ - **📦 Tiny Bundle** - Tree-shakable and dependency-free (< 10kb gzipped)
14
+ - **🎨 UI Agnostic** - Works with any UI library or custom components
15
+ - **🔌 Composable-First** - Built for Vue 3's Composition API
16
+
17
+ ## 🚀 Quick Start
18
+
19
+ ### Installation
20
+
21
+ ```bash
22
+ npm install @vuehookform/core zod
23
+ # or
24
+ bun add @vuehookform/core zod
25
+ ```
26
+
27
+ ### Basic Usage
28
+
29
+ ```vue
30
+ <script setup lang="ts">
31
+ import { useForm } from '@vuehookform/core'
32
+ import { z } from 'zod'
33
+
34
+ // 1. Define your schema
35
+ const schema = z.object({
36
+ email: z.email('Invalid email'),
37
+ password: z.string().min(8, 'At least 8 characters'),
38
+ })
39
+
40
+ // 2. Initialize form
41
+ const { register, handleSubmit, formState } = useForm({
42
+ schema,
43
+ mode: 'onBlur',
44
+ })
45
+
46
+ // 3. Handle submission
47
+ const onSubmit = (data) => {
48
+ console.log(data) // Fully typed! { email: string, password: string }
49
+ }
50
+ </script>
51
+
52
+ <template>
53
+ <form @submit="handleSubmit(onSubmit)">
54
+ <!-- Bind inputs with v-bind -->
55
+ <input v-bind="register('email')" type="email" />
56
+ <span v-if="formState.errors.email">{{ formState.errors.email }}</span>
57
+
58
+ <input v-bind="register('password')" type="password" />
59
+ <span v-if="formState.errors.password">{{ formState.errors.password }}</span>
60
+
61
+ <button type="submit" :disabled="formState.isSubmitting">Submit</button>
62
+ </form>
63
+ </template>
64
+ ```
65
+
66
+ That's it! Three steps and you have a fully validated, type-safe form. 🎉
67
+
68
+ ## 📚 Examples
69
+
70
+ This repo includes working examples showcasing all features:
71
+
72
+ ### Run Examples
73
+
74
+ ```bash
75
+ # Install dependencies
76
+ npm install
77
+
78
+ # Start dev server
79
+ npm run dev
80
+ ```
81
+
82
+ Then open http://localhost:5173 to see:
83
+
84
+ 1. **Basic Form** - Simple validation, error handling, form state
85
+ 2. **Dynamic Arrays** - Add/remove nested form sections dynamically
86
+
87
+ ## 💡 Key Concepts
88
+
89
+ ### Schema as Source of Truth
90
+
91
+ Define your form structure and validation in one place:
92
+
93
+ ```typescript
94
+ const userSchema = z.object({
95
+ name: z.string().min(2),
96
+ email: z.email(),
97
+ age: z.number().min(18),
98
+ })
99
+
100
+ // Types are automatically inferred
101
+ type UserForm = z.infer<typeof userSchema>
102
+ // { name: string; email: string; age: number }
103
+ ```
104
+
105
+ ### Zero-Boilerplate Dynamic Arrays
106
+
107
+ Managing dynamic form sections is trivial:
108
+
109
+ ```vue
110
+ <script setup>
111
+ const { register, fields } = useForm({ schema })
112
+
113
+ // Get field array manager
114
+ const addresses = fields('addresses')
115
+ </script>
116
+
117
+ <template>
118
+ <div v-for="field in addresses.value" :key="field.key">
119
+ <input v-bind="register(`addresses.${field.index}.street`)" />
120
+ <button @click="field.remove()">Remove</button>
121
+ </div>
122
+
123
+ <button @click="addresses.append({ street: '', city: '' })">Add Address</button>
124
+ </template>
125
+ ```
126
+
127
+ ### Validation Modes
128
+
129
+ Control when validation runs:
130
+
131
+ ```typescript
132
+ useForm({
133
+ schema,
134
+ mode: 'onSubmit', // Only validate on submit (default)
135
+ // mode: 'onBlur', // Validate when field loses focus
136
+ // mode: 'onChange', // Validate on every keystroke
137
+ // mode: 'onTouched', // Validate after field is touched
138
+ })
139
+ ```
140
+
141
+ ## 🎨 API Reference
142
+
143
+ ### `useForm(options)`
144
+
145
+ Main composable for form management.
146
+
147
+ **Options:**
148
+
149
+ ```typescript
150
+ {
151
+ schema: ZodSchema // Zod schema for validation
152
+ defaultValues?: Partial<T> // Initial form values
153
+ mode?: ValidationMode // When to validate
154
+ }
155
+ ```
156
+
157
+ **Returns:**
158
+
159
+ ```typescript
160
+ {
161
+ register: (name, options?) => RegisterReturn
162
+ handleSubmit: (onValid, onInvalid?) => (e) => Promise<void>
163
+ formState: ComputedRef<FormState>
164
+ fields: (name) => FieldArray
165
+ setValue: (name, value) => void
166
+ getValue: (name) => any
167
+ reset: (values?) => void
168
+ watch: (name?) => ComputedRef<any>
169
+ validate: (name?) => Promise<boolean>
170
+ }
171
+ ```
172
+
173
+ ### `register(name, options?)`
174
+
175
+ Register an input field for validation and state management.
176
+
177
+ ```vue
178
+ <input v-bind="register('email')" />
179
+ <input v-bind="register('email', { controlled: true })" />
180
+ ```
181
+
182
+ ### `handleSubmit(onValid, onInvalid?)`
183
+
184
+ Create submit handler with validation.
185
+
186
+ ```typescript
187
+ const onSubmit = handleSubmit(
188
+ (data) => {
189
+ // Called with validated data
190
+ console.log(data)
191
+ },
192
+ (errors) => {
193
+ // Optional: called when validation fails
194
+ console.log(errors)
195
+ },
196
+ )
197
+ ```
198
+
199
+ ### `fields(name)`
200
+
201
+ Manage dynamic field arrays.
202
+
203
+ ```typescript
204
+ const addresses = fields('addresses')
205
+
206
+ addresses.append({ street: '', city: '' }) // Add item
207
+ addresses.remove(0) // Remove by index
208
+ addresses.insert(1, value) // Insert at index
209
+ addresses.swap(0, 1) // Swap two items
210
+ addresses.move(0, 2) // Move item
211
+ ```
212
+
213
+ ## 🏗️ Project Structure
214
+
215
+ ```
216
+ src/
217
+ ├── lib/ # Library source code
218
+ │ ├── index.ts # Public exports
219
+ │ ├── useForm.ts # Main composable
220
+ │ ├── types.ts # TypeScript definitions
221
+ │ └── utils/ # Helper functions
222
+ ├── examples/ # Demo applications
223
+ │ ├── BasicForm.vue
224
+ │ └── DynamicArrays.vue
225
+ └── App.vue # Example showcase
226
+ ```
227
+
228
+ ## 🎯 Why Vue Hook Form?
229
+
230
+ ### vs VeeValidate
231
+
232
+ - **Less boilerplate** - One composable manages entire form
233
+ - **Better performance** - Uncontrolled inputs by default
234
+ - **Simpler API** - Form-level vs field-level management
235
+
236
+ ### vs FormKit
237
+
238
+ - **Lighter** - < 10kb vs 50kb+
239
+ - **Less opinionated** - No UI components required
240
+ - **Native Zod** - First-class integration, not an adapter
241
+
242
+ ### vs Vuelidate
243
+
244
+ - **Type-safe** - Perfect TypeScript inference
245
+ - **Built-in arrays** - Dynamic fields work out of the box
246
+ - **Modern** - Built for Composition API
247
+
248
+ ## 🔧 Development
249
+
250
+ ### Setup
251
+
252
+ ```bash
253
+ npm install
254
+ ```
255
+
256
+ ### Run Dev Server
257
+
258
+ ```bash
259
+ npm run dev
260
+ ```
261
+
262
+ ### Type Check
263
+
264
+ ```bash
265
+ npm run type-check
266
+ ```
267
+
268
+ ### Lint
269
+
270
+ ```bash
271
+ npm run lint
272
+ ```
273
+
274
+ ### Build
275
+
276
+ ```bash
277
+ npm run build
278
+ ```
279
+
280
+ ## 📖 Documentation
281
+
282
+ For detailed implementation notes, architecture decisions, and future roadmap, see [CORE_CONCEPTS.md](./CORE_CONCEPTS.md).
283
+
284
+ ## 🤝 Contributing
285
+
286
+ Contributions welcome! This is a proof-of-concept library demonstrating:
287
+
288
+ - Form-level state management
289
+ - Zod-first validation
290
+ - TypeScript-first design
291
+ - Performance-optimized architecture
292
+
293
+ Feel free to:
294
+
295
+ - Report bugs
296
+ - Suggest features
297
+ - Submit PRs
298
+ - Ask questions
299
+
300
+ ## 📝 License
301
+
302
+ MIT - Build something awesome!
303
+
304
+ ## 🙏 Inspiration
305
+
306
+ - [React Hook Form](https://react-hook-form.com/) - API design inspiration
307
+ - [Zod](https://zod.dev/) - Schema validation
308
+ - [VeeValidate](https://vee-validate.logaretm.com/) - Vue form validation patterns
309
+
310
+ ---
311
+
312
+ **Made with ❤️ for the Vue community**
@@ -0,0 +1,38 @@
1
+ import { InjectionKey } from 'vue';
2
+ import { UseFormReturn } from './types';
3
+ import { ZodType } from 'zod';
4
+ /**
5
+ * Injection key for form context
6
+ */
7
+ export declare const FormContextKey: InjectionKey<UseFormReturn<ZodType>>;
8
+ /**
9
+ * Provide form methods to child components via Vue's dependency injection.
10
+ *
11
+ * Call this in a parent component's setup function after calling useForm().
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * // Parent component
16
+ * const form = useForm({ schema })
17
+ * provideForm(form)
18
+ * ```
19
+ *
20
+ * @param methods - The return value from useForm()
21
+ */
22
+ export declare function provideForm<TSchema extends ZodType>(methods: UseFormReturn<TSchema>): void;
23
+ /**
24
+ * Access form methods in a child component via Vue's dependency injection.
25
+ *
26
+ * Must be used within a component tree where provideForm() has been called.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * // Child component
31
+ * const { register, formState } = useFormContext()
32
+ * ```
33
+ *
34
+ * @returns The form methods from the parent component's useForm() call
35
+ * @throws Error if used outside of a FormProvider context
36
+ */
37
+ export declare function useFormContext<TSchema extends ZodType>(): UseFormReturn<TSchema>;
38
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/lib/context.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,KAAK,CAAA;AACxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AAElC;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE,YAAY,CAAC,aAAa,CAAC,OAAO,CAAC,CAAyB,CAAA;AAEzF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,OAAO,SAAS,OAAO,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,IAAI,CAE1F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,OAAO,KAAK,aAAa,CAAC,OAAO,CAAC,CAWhF"}
@@ -0,0 +1,35 @@
1
+ import { Ref, ShallowRef } from 'vue';
2
+ import { ZodType } from 'zod';
3
+ import { UseFormOptions, FieldErrors, InferSchema, RegisterOptions, FieldArrayItem } from '../types';
4
+ /**
5
+ * Internal state for field array management
6
+ */
7
+ export interface FieldArrayState {
8
+ items: Ref<FieldArrayItem[]>;
9
+ values: unknown[];
10
+ }
11
+ /**
12
+ * Shared form context containing all reactive state
13
+ * This is passed to sub-modules via dependency injection
14
+ */
15
+ export interface FormContext<FormValues> {
16
+ formData: Record<string, unknown>;
17
+ defaultValues: Record<string, unknown>;
18
+ errors: ShallowRef<FieldErrors<FormValues>>;
19
+ touchedFields: Ref<Record<string, boolean>>;
20
+ dirtyFields: Ref<Record<string, boolean>>;
21
+ isSubmitting: Ref<boolean>;
22
+ isLoading: Ref<boolean>;
23
+ submitCount: Ref<number>;
24
+ fieldRefs: Map<string, Ref<HTMLInputElement | null>>;
25
+ fieldOptions: Map<string, RegisterOptions>;
26
+ fieldArrays: Map<string, FieldArrayState>;
27
+ debounceTimers: Map<string, ReturnType<typeof setTimeout>>;
28
+ validationRequestIds: Map<string, number>;
29
+ options: UseFormOptions<ZodType>;
30
+ }
31
+ /**
32
+ * Create a new form context with all reactive state initialized
33
+ */
34
+ export declare function createFormContext<TSchema extends ZodType>(options: UseFormOptions<TSchema>): FormContext<InferSchema<TSchema>>;
35
+ //# sourceMappingURL=formContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formContext.d.ts","sourceRoot":"","sources":["../../src/lib/core/formContext.ts"],"names":[],"mappings":"AAAA,OAAO,EAA6B,KAAK,GAAG,EAAE,KAAK,UAAU,EAAE,MAAM,KAAK,CAAA;AAC1E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAA;AAClC,OAAO,KAAK,EACV,cAAc,EACd,WAAW,EACX,WAAW,EACX,eAAe,EACf,cAAc,EACf,MAAM,UAAU,CAAA;AAEjB;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,GAAG,CAAC,cAAc,EAAE,CAAC,CAAA;IAC5B,MAAM,EAAE,OAAO,EAAE,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,WAAW,CAAC,UAAU;IAErC,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IACjC,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAGtC,MAAM,EAAE,UAAU,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAAA;IAC3C,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IAC3C,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACzC,YAAY,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAC1B,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAGxB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAC,CAAA;IACpD,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC1C,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAGzC,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,CAAA;IAC1D,oBAAoB,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAGzC,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,CAAA;CACjC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,SAAS,OAAO,EACvD,OAAO,EAAE,cAAc,CAAC,OAAO,CAAC,GAC/B,WAAW,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAgEnC"}
@@ -0,0 +1,9 @@
1
+ import { FormContext } from './formContext';
2
+ import { FieldArray, Path } from '../types';
3
+ /**
4
+ * Create field array management functions
5
+ */
6
+ export declare function createFieldArrayManager<FormValues>(ctx: FormContext<FormValues>, validate: (fieldPath?: string) => Promise<boolean>): {
7
+ fields: <TPath extends Path<FormValues>>(name: TPath) => FieldArray;
8
+ };
9
+ //# sourceMappingURL=useFieldArray.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFieldArray.d.ts","sourceRoot":"","sources":["../../src/lib/core/useFieldArray.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,KAAK,EAAE,UAAU,EAAkB,IAAI,EAAE,MAAM,UAAU,CAAA;AAGhE;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAChD,GAAG,EAAE,WAAW,CAAC,UAAU,CAAC,EAC5B,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC;aAKlC,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,KAAG,UAAU;EAyKzE"}
@@ -0,0 +1,10 @@
1
+ import { FormContext } from './formContext';
2
+ import { RegisterOptions, RegisterReturn, Path } from '../types';
3
+ /**
4
+ * Create field registration functions
5
+ */
6
+ export declare function createFieldRegistration<FormValues>(ctx: FormContext<FormValues>, validate: (fieldPath?: string) => Promise<boolean>): {
7
+ register: <TPath extends Path<FormValues>>(name: TPath, registerOptions?: RegisterOptions) => RegisterReturn;
8
+ unregister: <TPath extends Path<FormValues>>(name: TPath) => void;
9
+ };
10
+ //# sourceMappingURL=useFieldRegistration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFieldRegistration.d.ts","sourceRoot":"","sources":["../../src/lib/core/useFieldRegistration.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,KAAK,EACV,eAAe,EACf,cAAc,EAEd,IAAI,EACL,MAAM,UAAU,CAAA;AAGjB;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,UAAU,EAChD,GAAG,EAAE,WAAW,CAAC,UAAU,CAAC,EAC5B,QAAQ,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC;eAKhC,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,QACxC,KAAK,oBACO,eAAe,KAChC,cAAc;iBA6LG,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,QAAQ,KAAK,KAAG,IAAI;EAcvE"}
@@ -0,0 +1,8 @@
1
+ import { FormContext } from './formContext';
2
+ /**
3
+ * Create validation functions for form
4
+ */
5
+ export declare function createValidation<FormValues>(ctx: FormContext<FormValues>): {
6
+ validate: (fieldPath?: string) => Promise<boolean>;
7
+ };
8
+ //# sourceMappingURL=useValidation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useValidation.d.ts","sourceRoot":"","sources":["../../src/lib/core/useValidation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAsEhD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,GAAG,EAAE,WAAW,CAAC,UAAU,CAAC;2BAInC,MAAM,KAAG,OAAO,CAAC,OAAO,CAAC;EAuD9D"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Vue Hook Form - TypeScript-first form library for Vue 3
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { useForm } from './lib'
7
+ * import { z } from 'zod'
8
+ *
9
+ * const schema = z.object({
10
+ * email: z.email(),
11
+ * name: z.string().min(2)
12
+ * })
13
+ *
14
+ * const { register, handleSubmit, formState } = useForm({ schema })
15
+ * ```
16
+ */
17
+ export { useForm } from './useForm';
18
+ export { provideForm, useFormContext, FormContextKey } from './context';
19
+ export { useWatch, type UseWatchOptions } from './useWatch';
20
+ export { useController, type UseControllerOptions, type UseControllerReturn, type ControllerFieldProps } from './useController';
21
+ export { useFormState, type UseFormStateOptions, type FormStateKey } from './useFormState';
22
+ export type { UseFormOptions, UseFormReturn, RegisterOptions, RegisterReturn, FormState, FieldState, FieldErrors, FieldError, FieldErrorValue, FieldArray, FieldArrayItem, ValidationMode, InferSchema, Path, PathValue, ErrorOption, SetFocusOptions, ResetOptions, AsyncDefaultValues, } from './types';
23
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AACvE,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,KAAK,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAC/H,OAAO,EAAE,YAAY,EAAE,KAAK,mBAAmB,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAE1F,YAAY,EACV,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,EACd,SAAS,EACT,UAAU,EACV,WAAW,EACX,UAAU,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACd,cAAc,EACd,WAAW,EACX,IAAI,EACJ,SAAS,EACT,WAAW,EACX,eAAe,EACf,YAAY,EACZ,kBAAkB,GACnB,MAAM,SAAS,CAAA"}