@teamnovu/kit-vue-forms 0.0.1 → 0.0.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/PLAN.md CHANGED
@@ -1,3 +1,5 @@
1
+ # DEPRECATED
2
+
1
3
  # Vue Forms Library - MVP Plan
2
4
 
3
5
  ## Overview
@@ -206,4 +208,4 @@ src/
206
208
  - Direct form instance passing maintains type safety
207
209
  - Subform extraction enables component reusability
208
210
  - Validation strategies can be overridden per field
209
- - Backend validation seamlessly integrates with Zod
211
+ - Backend validation seamlessly integrates with Zod
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teamnovu/kit-vue-forms",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "exports": {
@@ -29,8 +29,8 @@ export function useField<T, K extends string>(options: UseFieldOptions<T, K>): F
29
29
  return JSON.stringify(state.value) !== JSON.stringify(state.initialValue)
30
30
  })
31
31
 
32
- const setValue = (newValue: T): void => {
33
- state.value = newValue
32
+ const setData = (newData: T): void => {
33
+ state.value = newData
34
34
  }
35
35
 
36
36
  const onBlur = (): void => {
@@ -58,13 +58,13 @@ export function useField<T, K extends string>(options: UseFieldOptions<T, K>): F
58
58
  const refs = toRefs(state)
59
59
 
60
60
  return {
61
- value: refs.value as FormField<T, K>['value'],
61
+ data: refs.value as FormField<T, K>['data'],
62
62
  path: refs.path as FormField<T, K>['path'],
63
63
  initialValue: refs.initialValue as FormField<T, K>['initialValue'],
64
64
  errors: refs.errors as FormField<T, K>['errors'],
65
65
  touched: refs.touched as FormField<T, K>['touched'],
66
66
  dirty,
67
- setValue,
67
+ setData,
68
68
  onBlur,
69
69
  onFocus,
70
70
  reset,
@@ -32,7 +32,7 @@ export function useFieldRegistry<T extends FormDataDefault>(
32
32
  const defineField = <K extends Paths<T>>(options: DefineFieldOptions<PickProps<T, K>, K>) => {
33
33
  const field = useField({
34
34
  ...options,
35
- value: getLens(toRef(formState, 'formData'), options.path),
35
+ value: getLens(toRef(formState, 'data'), options.path),
36
36
  initialValue: computed(() => getNestedValue(formState.initialData, unref(options.path))),
37
37
  })
38
38
 
@@ -17,19 +17,19 @@ export interface UseFormOptions<T extends FormDataDefault> extends ValidationOpt
17
17
  export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
18
18
  const initialData = computed(() => Object.freeze(cloneRefValue(options.initialData)))
19
19
 
20
- const formData = ref<T>(cloneRefValue(initialData)) as Ref<T>
20
+ const data = ref<T>(cloneRefValue(initialData)) as Ref<T>
21
21
 
22
22
  const state = reactive({
23
23
  initialData: initialData,
24
- formData,
24
+ data,
25
25
  })
26
26
 
27
27
  const fields = useFieldRegistry(state)
28
28
  const validationState = useValidation(state, options)
29
- const formState = useFormState(state, fields)
29
+ const formState = useFormState(fields)
30
30
 
31
31
  const reset = () => {
32
- formData.value = cloneRefValue(initialData)
32
+ data.value = cloneRefValue(initialData)
33
33
  fields.getFields().forEach(field => field.reset())
34
34
  }
35
35
 
@@ -47,7 +47,7 @@ export function useForm<T extends FormDataDefault>(options: UseFormOptions<T>) {
47
47
  reset,
48
48
  getSubForm,
49
49
  initialData: toRef(state, 'initialData') as Form<T>['initialData'],
50
- formData: toRef(state, 'formData') as Form<T>['formData'],
50
+ data: toRef(state, 'data') as Form<T>['data'],
51
51
  }
52
52
 
53
53
  return formInterface
@@ -4,13 +4,13 @@ import type { FormDataDefault } from '../types/form'
4
4
  export function useFormData<T extends FormDataDefault>(
5
5
  initialData: Ref<T>,
6
6
  ) {
7
- const formData = ref(unref(initialData))
7
+ const data = ref(unref(initialData))
8
8
 
9
9
  watch(initialData, (newData) => {
10
- if (newData !== formData.value) {
11
- formData.value = newData
10
+ if (newData !== data.value) {
11
+ data.value = newData
12
12
  }
13
13
  })
14
14
 
15
- return { formData }
15
+ return { data }
16
16
  }
@@ -1,13 +1,12 @@
1
1
  import { computed, unref } from 'vue'
2
- import type { FormDataDefault, FormState } from '../types/form'
2
+ import type { FormDataDefault } from '../types/form'
3
3
  import type { FieldRegistry } from './useFieldRegistry'
4
4
 
5
5
  export function useFormState<T extends FormDataDefault>(
6
- formState: FormState<T>,
7
6
  formFieldRegistry: FieldRegistry<T>,
8
7
  ) {
9
8
  const isDirty = computed(() => {
10
- return JSON.stringify(formState.formData) !== JSON.stringify(formState.initialData)
9
+ return formFieldRegistry.getFields().some(field => unref(field.dirty))
11
10
  })
12
11
 
13
12
  const isTouched = computed(() => {
@@ -57,7 +57,7 @@ export function createSubformInterface<
57
57
  type ScopedMainPaths = Paths<T> & MP<SP>
58
58
 
59
59
  // Create reactive data scoped to subform path
60
- const formData = getLens(mainForm.formData, path) as Ref<ST>
60
+ const data = getLens(mainForm.data, path) as Ref<ST>
61
61
 
62
62
  const initialData = computed(() => {
63
63
  return getNestedValue(mainForm.initialData.value, path) as ST
@@ -70,8 +70,8 @@ export function createSubformInterface<
70
70
  return {
71
71
  ...field,
72
72
  path: computed(() => unref(field.path).replace(path + '.', '')),
73
- setValue: (newValue: PickProps<ST, S>) => {
74
- field.setValue(newValue as PickProps<T, ScopedMainPaths>)
73
+ setData: (newData: PickProps<ST, S>) => {
74
+ field.setData(newData as PickProps<T, ScopedMainPaths>)
75
75
  },
76
76
  } as unknown as FormField<PickProps<ST, S>, S>
77
77
  }
@@ -155,7 +155,7 @@ export function createSubformInterface<
155
155
  }
156
156
 
157
157
  return {
158
- formData,
158
+ data: data,
159
159
  initialData,
160
160
  defineField,
161
161
  getField,
@@ -11,7 +11,7 @@ export interface ValidatorOptions<T> {
11
11
  }
12
12
 
13
13
  export interface ValidationOptions<T> extends ValidatorOptions<T> {
14
- errors?: MaybeRef<ErrorBag>
14
+ errors?: MaybeRef<ErrorBag | undefined>
15
15
  }
16
16
 
17
17
  export const SuccessValidationResult: ValidationResult = {
@@ -113,16 +113,13 @@ export function createValidator<T extends FormDataDefault>(
113
113
  }
114
114
 
115
115
  export function useValidation<T extends FormDataDefault>(
116
- formState: { formData: T },
116
+ formState: { data: T },
117
117
  options: ValidationOptions<T>,
118
118
  ) {
119
119
  const validationState = reactive({
120
120
  validators: ref<Ref<Validator<T> | undefined>[]>([createValidator(options)]),
121
121
  isValidated: false,
122
- errors: unref(options.errors) ?? {
123
- general: [],
124
- propertyErrors: {},
125
- },
122
+ errors: unref(options.errors) ?? SuccessValidationResult.errors,
126
123
  })
127
124
 
128
125
  // Watch for changes in the error bag and update validation state
@@ -152,7 +149,7 @@ export function useValidation<T extends FormDataDefault>(
152
149
  )
153
150
 
154
151
  // Watch for changes in form data to trigger validation
155
- watch(() => formState.formData, () => {
152
+ watch(() => formState.data, () => {
156
153
  if (validationState.isValidated) {
157
154
  validateForm()
158
155
  }
@@ -178,7 +175,7 @@ export function useValidation<T extends FormDataDefault>(
178
175
  const validationResults = await Promise.all(
179
176
  validationState.validators
180
177
  .filter(validator => unref(validator) !== undefined)
181
- .map(validator => unref(validator)!.validate(formState.formData)),
178
+ .map(validator => unref(validator)!.validate(formState.data)),
182
179
  )
183
180
 
184
181
  const isValid = validationResults.every(result => result.isValid)
package/src/index.ts CHANGED
@@ -8,4 +8,4 @@ export type { UseFieldOptions } from './composables/useField'
8
8
 
9
9
  // Types
10
10
  export type { ValidationStrategy, ValidationErrorMessage as ErrorMessage, ValidationResult, ErrorBag } from './types/validation'
11
- export type { DeepPartial, FormData } from './utils/type-helpers'
11
+ export type { DeepPartial } from './utils/type-helpers'
package/src/types/form.ts CHANGED
@@ -8,18 +8,18 @@ import type { ValidatorOptions } from '../composables/useValidation'
8
8
  export type FormDataDefault = object
9
9
 
10
10
  export interface FormState<T extends FormDataDefault, TIn extends FormDataDefault = T> {
11
- formData: T
11
+ data: T
12
12
  initialData: TIn
13
13
  }
14
14
 
15
15
  export interface FormField<T, P extends string> {
16
- value: Ref<T>
16
+ data: Ref<T>
17
17
  path: Ref<P>
18
18
  initialValue: Readonly<Ref<T>>
19
19
  errors: Ref<ValidationErrors>
20
20
  touched: Ref<boolean>
21
21
  dirty: Ref<boolean>
22
- setValue: (newValue: T) => void
22
+ setData: (newData: T) => void
23
23
  onBlur: () => void
24
24
  onFocus: () => void
25
25
  reset: () => void
@@ -29,7 +29,7 @@ export interface FormField<T, P extends string> {
29
29
 
30
30
  export interface Form<T extends FormDataDefault> {
31
31
  // Data properties
32
- formData: Ref<T>
32
+ data: Ref<T>
33
33
  initialData: Readonly<Ref<T>>
34
34
 
35
35
  // Field operations
package/src/utils/path.ts CHANGED
@@ -35,13 +35,13 @@ export function setNestedValue<T, K extends Paths<T>>(obj: T, path: K | SplitPat
35
35
  target[lastKey] = value
36
36
  }
37
37
 
38
- export const getLens = <T, K extends Paths<T>>(formData: MaybeRef<T>, key: MaybeRef<K | SplitPath<K>>) => {
38
+ export const getLens = <T, K extends Paths<T>>(data: MaybeRef<T>, key: MaybeRef<K | SplitPath<K>>) => {
39
39
  return computed({
40
40
  get() {
41
- return getNestedValue(unref(formData), unref(key))
41
+ return getNestedValue(unref(data), unref(key))
42
42
  },
43
43
  set(value: PickProps<T, K>) {
44
- setNestedValue(unref(formData), unref(key), value)
44
+ setNestedValue(unref(data), unref(key), value)
45
45
  },
46
46
  })
47
47
  }
@@ -1,7 +1,8 @@
1
1
  import { describe, expect, it } from 'vitest'
2
2
  import { reactive } from 'vue'
3
- import { FieldRegistry, useFieldRegistry } from '../src/composables/useFieldRegistry'
3
+ import { useFieldRegistry } from '../src/composables/useFieldRegistry'
4
4
  import { useFormState } from '../src/composables/useFormState'
5
+ import { useForm } from '../src'
5
6
 
6
7
  describe('useFormState', () => {
7
8
  it('should detect dirty state when form data changes', () => {
@@ -9,18 +10,15 @@ describe('useFormState', () => {
9
10
  name: 'John',
10
11
  age: 30,
11
12
  }
12
- const formData = reactive({ ...initialData })
13
- const fields = {} as FieldRegistry<typeof initialData>
14
13
 
15
- const formState = useFormState({
16
- formData,
17
- initialData,
18
- }, fields)
14
+ const form = useForm({ initialData: initialData })
15
+ form.defineField({ path: 'name' })
16
+ form.defineField({ path: 'age' })
19
17
 
20
- expect(formState.isDirty.value).toBe(false)
18
+ expect(form.isDirty.value).toBe(false)
21
19
 
22
- formData.name = 'Jane'
23
- expect(formState.isDirty.value).toBe(true)
20
+ form.data.value.name = 'Jane'
21
+ expect(form.isDirty.value).toBe(true)
24
22
  })
25
23
 
26
24
  it('should not be dirty when data equals initial data', () => {
@@ -28,44 +26,34 @@ describe('useFormState', () => {
28
26
  name: 'John',
29
27
  age: 30,
30
28
  }
31
- const formData = reactive({ ...initialData })
32
- const fields = {} as FieldRegistry<typeof initialData>
33
-
34
- const formState = useFormState({
35
- formData,
36
- initialData,
37
- }, fields)
29
+ const form = useForm({ initialData: initialData })
30
+ form.defineField({ path: 'name' })
31
+ form.defineField({ path: 'age' })
38
32
 
39
- expect(formState.isDirty.value).toBe(false)
33
+ expect(form.isDirty.value).toBe(false)
40
34
 
41
- formData.name = 'Jane'
42
- expect(formState.isDirty.value).toBe(true)
35
+ form.data.value.name = 'Jane'
36
+ expect(form.isDirty.value).toBe(true)
43
37
 
44
- formData.name = 'John' // Back to initial
45
- expect(formState.isDirty.value).toBe(false)
38
+ form.data.value.name = 'John' // Back to initial
39
+ expect(form.isDirty.value).toBe(false)
46
40
  })
47
41
 
48
42
  it('should detect touched state when any field is touched', () => {
49
- const data = {
43
+ const initialData = {
50
44
  name: 'John',
51
45
  email: 'john@example.com',
52
46
  }
53
- const formData = reactive(data)
47
+ const data = reactive(initialData)
54
48
  const fields = useFieldRegistry({
55
- formData,
56
- initialData: data,
49
+ data,
50
+ initialData,
57
51
  })
58
52
 
59
53
  const nameField = fields.defineField({ path: 'name' })
60
54
  fields.defineField({ path: 'email' })
61
55
 
62
- const formState = useFormState({
63
- formData,
64
- initialData: {
65
- name: 'John',
66
- email: 'john@example.com',
67
- },
68
- }, fields)
56
+ const formState = useFormState(fields)
69
57
 
70
58
  expect(formState.isTouched.value).toBe(false)
71
59
 
@@ -74,17 +62,14 @@ describe('useFormState', () => {
74
62
  })
75
63
 
76
64
  it('should handle empty fields map', () => {
77
- const data = { name: 'John' }
78
- const formData = reactive(data)
65
+ const initialData = { name: 'John' }
66
+ const data = reactive(initialData)
79
67
  const fields = useFieldRegistry({
80
- formData,
81
- initialData: data,
68
+ data,
69
+ initialData,
82
70
  })
83
71
 
84
- const formState = useFormState({
85
- formData,
86
- initialData: { name: 'John' },
87
- }, fields)
72
+ const formState = useFormState(fields)
88
73
 
89
74
  expect(formState.isDirty.value).toBe(false)
90
75
  expect(formState.isTouched.value).toBe(false)
@@ -100,39 +85,25 @@ describe('useFormState', () => {
100
85
  },
101
86
  },
102
87
  }
103
- const formData = reactive(JSON.parse(JSON.stringify(initialData)))
104
- const fields = useFieldRegistry({
105
- formData,
106
- initialData,
107
- })
108
88
 
109
- const formState = useFormState({
110
- formData,
111
- initialData,
112
- }, fields)
89
+ const form = useForm({ initialData: initialData })
90
+ form.defineField({ path: 'user' })
113
91
 
114
- expect(formState.isDirty.value).toBe(false)
92
+ expect(form.isDirty.value).toBe(false)
115
93
 
116
- formData.user.address.city = 'Boston'
117
- expect(formState.isDirty.value).toBe(true)
94
+ form.data.value.user.address.city = 'Boston'
95
+ expect(form.isDirty.value).toBe(true)
118
96
  })
119
97
 
120
98
  it('should handle array changes', () => {
121
99
  const initialData = { tags: ['vue', 'typescript'] }
122
- const formData = reactive({ tags: [...initialData.tags] })
123
- const fields = useFieldRegistry({
124
- formData,
125
- initialData,
126
- })
127
100
 
128
- const formState = useFormState({
129
- formData,
130
- initialData,
131
- }, fields)
101
+ const form = useForm({ initialData: initialData })
102
+ form.defineField({ path: 'tags' })
132
103
 
133
- expect(formState.isDirty.value).toBe(false)
104
+ expect(form.isDirty.value).toBe(false)
134
105
 
135
- formData.tags.push('forms')
136
- expect(formState.isDirty.value).toBe(true)
106
+ form.data.value.tags.push('forms')
107
+ expect(form.isDirty.value).toBe(true)
137
108
  })
138
109
  })
@@ -32,9 +32,9 @@ describe('Integration Tests', () => {
32
32
  expect(form.isValidated.value).toBe(false)
33
33
 
34
34
  // Fill out form
35
- nameField.setValue('John')
36
- emailField.setValue('john@example.com')
37
- ageField.setValue(25)
35
+ nameField.setData('John')
36
+ emailField.setData('john@example.com')
37
+ ageField.setData(25)
38
38
 
39
39
  // Form should be dirty now
40
40
  expect(form.isDirty.value).toBe(true)
@@ -76,8 +76,8 @@ describe('Integration Tests', () => {
76
76
  expect(result.errors.propertyErrors.email).toBeDefined()
77
77
 
78
78
  // Fix the data
79
- nameField.setValue('John')
80
- emailField.setValue('john@example.com')
79
+ nameField.setData('John')
80
+ emailField.setData('john@example.com')
81
81
 
82
82
  // Re-validate
83
83
  const result2 = await form.validateForm()
@@ -93,8 +93,8 @@ describe('Integration Tests', () => {
93
93
 
94
94
  const nameField = form.defineField({ path: 'name' })
95
95
 
96
- expect(nameField.value.value).toEqual('John')
97
- expect(form.formData.value).toEqual({
96
+ expect(nameField.data.value).toEqual('John')
97
+ expect(form.data.value).toEqual({
98
98
  name: 'John',
99
99
  age: 30,
100
100
  })