@teamnovu/kit-vue-forms 0.0.1
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/PLAN.md +209 -0
- package/dist/composables/useField.d.ts +12 -0
- package/dist/composables/useFieldRegistry.d.ts +15 -0
- package/dist/composables/useForm.d.ts +10 -0
- package/dist/composables/useFormState.d.ts +7 -0
- package/dist/composables/useSubform.d.ts +5 -0
- package/dist/composables/useValidation.d.ts +30 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +414 -0
- package/dist/types/form.d.ts +41 -0
- package/dist/types/util.d.ts +26 -0
- package/dist/types/validation.d.ts +16 -0
- package/dist/utils/general.d.ts +2 -0
- package/dist/utils/path.d.ts +11 -0
- package/dist/utils/type-helpers.d.ts +3 -0
- package/dist/utils/validation.d.ts +3 -0
- package/dist/utils/zod.d.ts +3 -0
- package/package.json +41 -0
- package/src/composables/useField.ts +74 -0
- package/src/composables/useFieldRegistry.ts +53 -0
- package/src/composables/useForm.ts +54 -0
- package/src/composables/useFormData.ts +16 -0
- package/src/composables/useFormState.ts +21 -0
- package/src/composables/useSubform.ts +173 -0
- package/src/composables/useValidation.ts +227 -0
- package/src/index.ts +11 -0
- package/src/types/form.ts +58 -0
- package/src/types/util.ts +73 -0
- package/src/types/validation.ts +22 -0
- package/src/utils/general.ts +7 -0
- package/src/utils/path.ts +87 -0
- package/src/utils/type-helpers.ts +3 -0
- package/src/utils/validation.ts +66 -0
- package/src/utils/zod.ts +24 -0
- package/tests/formState.test.ts +138 -0
- package/tests/integration.test.ts +200 -0
- package/tests/nestedPath.test.ts +651 -0
- package/tests/path-utils.test.ts +159 -0
- package/tests/subform.test.ts +1348 -0
- package/tests/useField.test.ts +147 -0
- package/tests/useForm.test.ts +178 -0
- package/tests/useValidation.test.ts +216 -0
- package/tsconfig.json +18 -0
- package/vite.config.js +39 -0
- package/vitest.config.ts +14 -0
package/PLAN.md
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Vue Forms Library - MVP Plan
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
A type-safe Vue 3 form library with immutable state management, validation, and subform support. The library uses a direct form instance pattern (no provide/inject) to maintain full type safety throughout the component tree.
|
|
6
|
+
|
|
7
|
+
## Core Architecture
|
|
8
|
+
|
|
9
|
+
### Direct Form Instance Pattern
|
|
10
|
+
- Forms are created with `createForm<T>()` and passed directly to components
|
|
11
|
+
- Full TypeScript type safety maintained throughout the component tree
|
|
12
|
+
- No context loss from provide/inject patterns
|
|
13
|
+
|
|
14
|
+
### Subform Extraction Pattern
|
|
15
|
+
```typescript
|
|
16
|
+
// Reusable components work with subforms
|
|
17
|
+
const addressSubform = form.getSubform('address')
|
|
18
|
+
// addressSubform has type FormInstance<Address>
|
|
19
|
+
|
|
20
|
+
<AddressForm :form="addressSubform" />
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
This allows components to be reusable across different parent forms while maintaining type safety.
|
|
24
|
+
|
|
25
|
+
## API Design
|
|
26
|
+
|
|
27
|
+
### Form Creation
|
|
28
|
+
```typescript
|
|
29
|
+
const form = createForm<UserData>({
|
|
30
|
+
initialData: { name: '', email: '', address: { street: '', city: '' } },
|
|
31
|
+
validationSchema: userSchema,
|
|
32
|
+
strategy: 'onTouch'
|
|
33
|
+
})
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Form Instance Interface
|
|
37
|
+
```typescript
|
|
38
|
+
interface FormInstance<T> {
|
|
39
|
+
// Data access
|
|
40
|
+
getValue<K extends keyof T>(path: K): T[K]
|
|
41
|
+
setValue<K extends keyof T>(path: K, value: T[K]): void
|
|
42
|
+
|
|
43
|
+
// Field management
|
|
44
|
+
getField<K extends keyof T>(path: K): FieldInstance<T[K]>
|
|
45
|
+
|
|
46
|
+
// Subform extraction
|
|
47
|
+
getSubform<K extends keyof T>(path: K): FormInstance<T[K]>
|
|
48
|
+
|
|
49
|
+
// State queries
|
|
50
|
+
isDirty(): boolean
|
|
51
|
+
isTouched(): boolean
|
|
52
|
+
isValid(): boolean
|
|
53
|
+
|
|
54
|
+
// Operations
|
|
55
|
+
validate(): Promise<ValidationResult>
|
|
56
|
+
reset(): void
|
|
57
|
+
submit(): Promise<void>
|
|
58
|
+
|
|
59
|
+
// Error access
|
|
60
|
+
getErrors(path?: keyof T): ErrorMessage[]
|
|
61
|
+
hasErrors(path?: keyof T): boolean
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Field Instance Interface
|
|
66
|
+
```typescript
|
|
67
|
+
interface FieldInstance<T> {
|
|
68
|
+
// Reactive value with getter/setter
|
|
69
|
+
value: Ref<T>
|
|
70
|
+
|
|
71
|
+
// State
|
|
72
|
+
touched: Ref<boolean>
|
|
73
|
+
dirty: Ref<boolean>
|
|
74
|
+
errors: Ref<ErrorMessage[]>
|
|
75
|
+
|
|
76
|
+
// Handlers
|
|
77
|
+
onBlur(): void
|
|
78
|
+
onFocus(): void
|
|
79
|
+
|
|
80
|
+
// Field-specific validation
|
|
81
|
+
validate(): Promise<ValidationResult>
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Directory Structure
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
src/
|
|
89
|
+
├── types/
|
|
90
|
+
│ ├── form.ts # Form instance types
|
|
91
|
+
│ ├── field.ts # Field instance types
|
|
92
|
+
│ ├── validation.ts # Validation types
|
|
93
|
+
│ └── index.ts
|
|
94
|
+
├── core/
|
|
95
|
+
│ ├── form-instance.ts # Form instance implementation
|
|
96
|
+
│ ├── field-instance.ts # Field instance implementation
|
|
97
|
+
│ ├── subform-instance.ts # Subform implementation
|
|
98
|
+
│ └── validation-engine.ts # Validation logic
|
|
99
|
+
├── factories/
|
|
100
|
+
│ └── create-form.ts # Main form factory
|
|
101
|
+
├── utils/
|
|
102
|
+
│ ├── immutable.ts # Immutable helpers
|
|
103
|
+
│ ├── path.ts # Object path utilities
|
|
104
|
+
│ └── type-helpers.ts # TypeScript utility types
|
|
105
|
+
└── index.ts # Main exports
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Core Features
|
|
109
|
+
|
|
110
|
+
### 1. Immutable State Management
|
|
111
|
+
- Store initial data as immutable
|
|
112
|
+
- Working copy is immutable from outside (readonly)
|
|
113
|
+
- Computed getter/setter on working copy
|
|
114
|
+
- Full clone on creation
|
|
115
|
+
|
|
116
|
+
### 2. Form State Tracking
|
|
117
|
+
- **Touched**: Field has been interacted with at least once
|
|
118
|
+
- **Dirty**: Computed property checking if value ≠ initial state
|
|
119
|
+
- State propagation from subforms to parent forms
|
|
120
|
+
|
|
121
|
+
### 3. Validation System
|
|
122
|
+
- **Strategies**: `onTouch`, `onFormOpen`, `none`, `preSubmit`
|
|
123
|
+
- **Zod Integration**: Primary validation with Zod schemas
|
|
124
|
+
- **Backend Validation**: API errors merged with Zod errors
|
|
125
|
+
- **Error Bag Structure**:
|
|
126
|
+
```typescript
|
|
127
|
+
{
|
|
128
|
+
general: ErrorMessage[],
|
|
129
|
+
propertyErrors: Record<string, ErrorMessage[]>
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### 4. Subform Support
|
|
134
|
+
- Extract subforms with `getSubform(path)`
|
|
135
|
+
- Type-safe subform instances
|
|
136
|
+
- Automatic value propagation from subforms to parent
|
|
137
|
+
- Reusable components across different parent forms
|
|
138
|
+
|
|
139
|
+
### 5. Array Functionality (Future)
|
|
140
|
+
- `pushValue()`, `removeValue()`, `getKeys()`
|
|
141
|
+
- Similar to vee-validate's field array API
|
|
142
|
+
|
|
143
|
+
## MVP Implementation Order
|
|
144
|
+
|
|
145
|
+
### Phase 1: Core Infrastructure
|
|
146
|
+
1. **Project Setup**
|
|
147
|
+
- Package.json with proper exports
|
|
148
|
+
- TypeScript configuration
|
|
149
|
+
- Vite build setup
|
|
150
|
+
- Basic documentation structure
|
|
151
|
+
|
|
152
|
+
2. **Type System**
|
|
153
|
+
- Core interfaces and types
|
|
154
|
+
- Validation types
|
|
155
|
+
- Error handling types
|
|
156
|
+
|
|
157
|
+
3. **Basic Form Instance**
|
|
158
|
+
- Form creation factory
|
|
159
|
+
- Basic field management
|
|
160
|
+
- Immutable state handling
|
|
161
|
+
|
|
162
|
+
### Phase 2: Essential Features
|
|
163
|
+
1. **Field Management**
|
|
164
|
+
- Field instance implementation
|
|
165
|
+
- Touch and dirty state tracking
|
|
166
|
+
- Basic event handlers
|
|
167
|
+
|
|
168
|
+
2. **Validation Engine**
|
|
169
|
+
- Zod integration
|
|
170
|
+
- Error collection and formatting
|
|
171
|
+
- Basic validation strategies
|
|
172
|
+
|
|
173
|
+
3. **Subform System**
|
|
174
|
+
- Subform extraction
|
|
175
|
+
- Type-safe subform instances
|
|
176
|
+
- Value propagation
|
|
177
|
+
|
|
178
|
+
### Phase 3: Polish & Testing
|
|
179
|
+
1. **Edge Cases**
|
|
180
|
+
- Error handling
|
|
181
|
+
- Reset functionality
|
|
182
|
+
- Validation edge cases
|
|
183
|
+
|
|
184
|
+
2. **Documentation**
|
|
185
|
+
- API documentation
|
|
186
|
+
- Usage examples
|
|
187
|
+
- Migration guides
|
|
188
|
+
|
|
189
|
+
3. **Testing**
|
|
190
|
+
- Unit tests
|
|
191
|
+
- Integration tests
|
|
192
|
+
- Type safety tests
|
|
193
|
+
|
|
194
|
+
## Key Benefits
|
|
195
|
+
|
|
196
|
+
- **Type Safety**: Full TypeScript support with no type loss
|
|
197
|
+
- **Immutable State**: Predictable state management
|
|
198
|
+
- **Reusable Components**: Subforms enable component reuse
|
|
199
|
+
- **Validation Flexibility**: Multiple validation strategies
|
|
200
|
+
- **Performance**: Reactive updates only where needed
|
|
201
|
+
- **Developer Experience**: Excellent IDE support and debugging
|
|
202
|
+
|
|
203
|
+
## Notes
|
|
204
|
+
|
|
205
|
+
- No provide/inject pattern to avoid type information loss
|
|
206
|
+
- Direct form instance passing maintains type safety
|
|
207
|
+
- Subform extraction enables component reusability
|
|
208
|
+
- Validation strategies can be overridden per field
|
|
209
|
+
- Backend validation seamlessly integrates with Zod
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MaybeRef, MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import { FormField } from '../types/form';
|
|
3
|
+
import { ValidationErrors } from '../types/validation';
|
|
4
|
+
export interface UseFieldOptions<T, K extends string> {
|
|
5
|
+
value?: MaybeRef<T>;
|
|
6
|
+
initialValue?: MaybeRefOrGetter<Readonly<T>>;
|
|
7
|
+
type?: MaybeRef<string>;
|
|
8
|
+
required?: MaybeRef<boolean>;
|
|
9
|
+
path: K;
|
|
10
|
+
errors?: MaybeRef<ValidationErrors>;
|
|
11
|
+
}
|
|
12
|
+
export declare function useField<T, K extends string>(options: UseFieldOptions<T, K>): FormField<T, K>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FormDataDefault, FormField, FormState } from '../types/form';
|
|
2
|
+
import { Paths, PickProps } from '../types/util';
|
|
3
|
+
import { UseFieldOptions } from './useField';
|
|
4
|
+
type FieldRegistryCache<T> = Record<Paths<T>, FormField<any, string>>;
|
|
5
|
+
export type ResolvedFormField<T, K extends Paths<T>> = FormField<PickProps<T, K>, K>;
|
|
6
|
+
export type DefineFieldOptions<F, K extends string> = Pick<UseFieldOptions<F, K>, 'path' | 'type' | 'required'>;
|
|
7
|
+
export declare function useFieldRegistry<T extends FormDataDefault>(formState: FormState<T>): {
|
|
8
|
+
fields: FieldRegistryCache<T>;
|
|
9
|
+
getField: <K extends Paths<T>>(path: K) => ResolvedFormField<T, K> | undefined;
|
|
10
|
+
getFields: () => ResolvedFormField<T, Paths<T>>[];
|
|
11
|
+
registerField: <K extends Paths<T>>(field: ResolvedFormField<T, K>) => void;
|
|
12
|
+
defineField: <K extends Paths<T>>(options: DefineFieldOptions<PickProps<T, K>, K>) => FormField<PickProps<any, K>, K>;
|
|
13
|
+
};
|
|
14
|
+
export type FieldRegistry<T extends FormDataDefault> = ReturnType<typeof useFieldRegistry<T>>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { MaybeRef, MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import { Form, FormDataDefault } from '../types/form';
|
|
3
|
+
import { ValidationStrategy } from '../types/validation';
|
|
4
|
+
import { ValidationOptions } from './useValidation';
|
|
5
|
+
export interface UseFormOptions<T extends FormDataDefault> extends ValidationOptions<T> {
|
|
6
|
+
initialData: MaybeRefOrGetter<T>;
|
|
7
|
+
validationStrategy?: MaybeRef<ValidationStrategy>;
|
|
8
|
+
keepValuesOnUnmount?: MaybeRef<boolean>;
|
|
9
|
+
}
|
|
10
|
+
export declare function useForm<T extends FormDataDefault>(options: UseFormOptions<T>): Form<T>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { FormDataDefault, FormState } from '../types/form';
|
|
2
|
+
import { FieldRegistry } from './useFieldRegistry';
|
|
3
|
+
import { ComputedRef } from 'vue';
|
|
4
|
+
export declare function useFormState<T extends FormDataDefault>(formState: FormState<T>, formFieldRegistry: FieldRegistry<T>): {
|
|
5
|
+
isDirty: ComputedRef<boolean>;
|
|
6
|
+
isTouched: ComputedRef<boolean>;
|
|
7
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Form, FormDataDefault } from '../types/form';
|
|
2
|
+
import { EntityPaths, PickEntity } from '../types/util';
|
|
3
|
+
export interface SubformOptions<_T extends FormDataDefault> {
|
|
4
|
+
}
|
|
5
|
+
export declare function createSubformInterface<T extends FormDataDefault, K extends EntityPaths<T>>(mainForm: Form<T>, path: K, _options?: SubformOptions<PickEntity<T, K>>): Form<PickEntity<T, K>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MaybeRef, Ref, ComputedRef } from 'vue';
|
|
2
|
+
import { default as z } from 'zod';
|
|
3
|
+
import { FormDataDefault } from '../types/form';
|
|
4
|
+
import { ErrorBag, ValidationFunction, ValidationResult, Validator, ValidationErrors } from '../types/validation';
|
|
5
|
+
export interface ValidatorOptions<T> {
|
|
6
|
+
schema?: MaybeRef<z.ZodType>;
|
|
7
|
+
validateFn?: MaybeRef<ValidationFunction<T>>;
|
|
8
|
+
}
|
|
9
|
+
export interface ValidationOptions<T> extends ValidatorOptions<T> {
|
|
10
|
+
errors?: MaybeRef<ErrorBag>;
|
|
11
|
+
}
|
|
12
|
+
export declare const SuccessValidationResult: ValidationResult;
|
|
13
|
+
export declare function createValidator<T extends FormDataDefault>(options: ValidatorOptions<T>): Ref<Validator<T> | undefined>;
|
|
14
|
+
export declare function useValidation<T extends FormDataDefault>(formState: {
|
|
15
|
+
formData: T;
|
|
16
|
+
}, options: ValidationOptions<T>): {
|
|
17
|
+
validateForm: () => Promise<ValidationResult>;
|
|
18
|
+
defineValidator: (options: ValidatorOptions<T> | Ref<Validator<T>>) => Ref<Validator<T> | undefined, Validator<T> | undefined>;
|
|
19
|
+
isValid: ComputedRef<boolean>;
|
|
20
|
+
validators: Ref<Ref<Validator<T> | undefined, Validator<T> | undefined>[], Ref<Validator<T> | undefined, Validator<T> | undefined>[]>;
|
|
21
|
+
isValidated: Ref<boolean, boolean>;
|
|
22
|
+
errors: Ref<{
|
|
23
|
+
general: string[] | undefined;
|
|
24
|
+
propertyErrors: Record<string, ValidationErrors>;
|
|
25
|
+
}, {
|
|
26
|
+
general: string[] | undefined;
|
|
27
|
+
propertyErrors: Record<string, ValidationErrors>;
|
|
28
|
+
}>;
|
|
29
|
+
};
|
|
30
|
+
export type ValidationState<T extends FormDataDefault> = ReturnType<typeof useValidation<T>>;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { useForm } from './composables/useForm';
|
|
2
|
+
export type { UseFormOptions } from './composables/useForm';
|
|
3
|
+
export { useField } from './composables/useField';
|
|
4
|
+
export type { UseFieldOptions } from './composables/useField';
|
|
5
|
+
export type { ValidationStrategy, ValidationErrorMessage as ErrorMessage, ValidationResult, ErrorBag } from './types/validation';
|
|
6
|
+
export type { DeepPartial, FormData } from './utils/type-helpers';
|