@volverjs/form-vue 0.0.14 → 1.0.0-beta.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.
@@ -1,5 +1,6 @@
1
- import { type Component, type DeepReadonly, type Ref } from 'vue';
1
+ import type { Component, DeepReadonly, Ref, WatchStopHandle } from 'vue';
2
2
  import type { z, AnyZodObject, ZodEffects, inferFormattedError } from 'zod';
3
+ import type { IgnoredUpdater } from '@vueuse/core';
3
4
  import type { FormFieldType, FormStatus } from './enums';
4
5
  export type FormSchema = AnyZodObject | ZodEffects<AnyZodObject> | ZodEffects<ZodEffects<AnyZodObject>>;
5
6
  export type FormFieldComponentOptions = {
@@ -23,8 +24,10 @@ export type FormPluginOptions = FormPluginOptionsSchema & FormComposableOptions<
23
24
  export type InjectedFormData<Schema extends FormSchema> = {
24
25
  formData: Ref<Partial<z.infer<Schema>> | undefined>;
25
26
  errors: Readonly<Ref<DeepReadonly<z.inferFormattedError<Schema>> | undefined>>;
26
- submit: () => boolean;
27
- validate: () => boolean;
27
+ submit: () => Promise<boolean>;
28
+ validate: () => Promise<boolean>;
29
+ ignoreUpdates: IgnoredUpdater;
30
+ stopUpdatesWatch: WatchStopHandle;
28
31
  status: Readonly<Ref<FormStatus | undefined>>;
29
32
  invalid: Readonly<Ref<boolean>>;
30
33
  };
@@ -38,16 +41,15 @@ export type InjectedFormFieldData<Schema extends FormSchema> = {
38
41
  errors: Readonly<Ref<DeepReadonly<z.inferFormattedError<Schema>>>>;
39
42
  };
40
43
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
41
- type ArrayKey = number;
42
44
  type IsTuple<T extends readonly any[]> = number extends T['length'] ? false : true;
43
45
  type TupleKeys<T extends readonly any[]> = Exclude<keyof T, keyof any[]>;
44
46
  export type PathConcat<TKey extends string | number, TValue> = TValue extends Primitive ? `${TKey}` : `${TKey}` | `${TKey}.${Path<TValue>}`;
45
47
  export type Path<T> = T extends readonly (infer V)[] ? IsTuple<T> extends true ? {
46
48
  [K in TupleKeys<T>]-?: PathConcat<K & string, T[K]>;
47
- }[TupleKeys<T>] : PathConcat<ArrayKey, V> : {
49
+ }[TupleKeys<T>] : PathConcat<number, V> : {
48
50
  [K in keyof T]-?: PathConcat<K & string, T[K]>;
49
51
  }[keyof T];
50
- export type PathValue<T, TPath extends Path<T> | Path<T>[]> = T extends any ? TPath extends `${infer K}.${infer R}` ? K extends keyof T ? R extends Path<T[K]> ? undefined extends T[K] ? PathValue<T[K], R> | undefined : PathValue<T[K], R> : never : K extends `${ArrayKey}` ? T extends readonly (infer V)[] ? PathValue<V, R & Path<V>> : never : never : TPath extends keyof T ? T[TPath] : TPath extends `${ArrayKey}` ? T extends readonly (infer V)[] ? V : never : never : never;
52
+ export type PathValue<T, TPath extends Path<T> | Path<T>[]> = T extends any ? TPath extends `${infer K}.${infer R}` ? K extends keyof T ? R extends Path<T[K]> ? undefined extends T[K] ? PathValue<T[K], R> | undefined : PathValue<T[K], R> : never : K extends `${number}` ? T extends readonly (infer V)[] ? PathValue<V, R & Path<V>> : never : never : TPath extends keyof T ? T[TPath] : TPath extends `${number}` ? T extends readonly (infer V)[] ? V : never : never : never;
51
53
  export type AnyBoolean<Schema extends FormSchema> = boolean | Ref<boolean> | ((data?: InjectedFormData<Schema>) => boolean | Ref<boolean>);
52
54
  export type SimpleFormTemplateItem<Schema extends FormSchema> = Record<string, any> & {
53
55
  vvIs?: string | Component;
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "bugs": {
20
20
  "url": "https://github.com/volverjs/form-vue/issues"
21
21
  },
22
- "version": "0.0.14",
22
+ "version": "1.0.0-beta.2",
23
23
  "engines": {
24
24
  "node": ">= 16.x"
25
25
  },
@@ -35,35 +35,35 @@
35
35
  "*.d.ts"
36
36
  ],
37
37
  "dependencies": {
38
- "@volverjs/ui-vue": "0.0.10-beta.3",
39
- "@vueuse/core": "^10.4.1",
38
+ "@volverjs/ui-vue": "next",
39
+ "@vueuse/core": "^10.5.0",
40
40
  "ts-dot-prop": "^2.1.3",
41
- "vue": "^3.3.4",
42
- "zod": "^3.22.2"
41
+ "vue": "^3.3.6",
42
+ "zod": "^3.22.4"
43
43
  },
44
44
  "devDependencies": {
45
- "@playwright/experimental-ct-vue": "1.38.0",
45
+ "@playwright/experimental-ct-vue": "1.39.0",
46
46
  "@testing-library/vue": "^7.0.0",
47
- "@typescript-eslint/eslint-plugin": "^6.7.0",
48
- "@vitejs/plugin-vue": "^4.3.4",
47
+ "@typescript-eslint/eslint-plugin": "^6.8.0",
48
+ "@vitejs/plugin-vue": "^4.4.0",
49
49
  "@volverjs/style": "^0.1.11",
50
- "@vue/compiler-sfc": "^3.3.4",
50
+ "@vue/compiler-sfc": "^3.3.6",
51
51
  "@vue/eslint-config-typescript": "^12.0.0",
52
- "@vue/runtime-core": "^3.3.4",
52
+ "@vue/runtime-core": "^3.3.6",
53
53
  "@vue/test-utils": "^2.4.1",
54
54
  "copy": "^0.3.2",
55
- "eslint": "^8.49.0",
55
+ "eslint": "^8.52.0",
56
56
  "eslint-config-prettier": "^9.0.0",
57
- "eslint-plugin-prettier": "^5.0.0",
57
+ "eslint-plugin-prettier": "^5.0.1",
58
58
  "eslint-plugin-vue": "^9.17.0",
59
- "happy-dom": "^11.0.6",
59
+ "happy-dom": "^12.9.1",
60
60
  "prettier": "^3.0.3",
61
61
  "typescript": "^5.2.2",
62
- "vite": "^4.4.9",
63
- "vite-plugin-dts": "^3.5.3",
62
+ "vite": "^4.5.0",
63
+ "vite-plugin-dts": "^3.6.0",
64
64
  "vite-plugin-eslint": "^1.8.1",
65
65
  "vite-plugin-externalize-deps": "^0.7.0",
66
- "vitest": "^0.34.4"
66
+ "vitest": "^0.34.6"
67
67
  },
68
68
  "typesVersions": {
69
69
  "*": {
package/src/VvForm.ts CHANGED
@@ -4,6 +4,7 @@ import {
4
4
  type DeepReadonly,
5
5
  type Ref,
6
6
  type PropType,
7
+ type WatchStopHandle,
7
8
  withModifiers,
8
9
  defineComponent,
9
10
  ref,
@@ -15,8 +16,12 @@ import {
15
16
  isProxy,
16
17
  computed,
17
18
  } from 'vue'
18
- import { watchThrottled } from '@vueuse/core'
19
- import type { z, ZodFormattedError, TypeOf } from 'zod'
19
+ import {
20
+ watchIgnorable,
21
+ throttleFilter,
22
+ type IgnoredUpdater,
23
+ } from '@vueuse/core'
24
+ import { type z, type ZodFormattedError, type TypeOf } from 'zod'
20
25
  import type {
21
26
  FormComponentOptions,
22
27
  FormSchema,
@@ -35,20 +40,55 @@ export const defineForm = <Schema extends FormSchema>(
35
40
  const errors = ref<z.inferFormattedError<Schema> | undefined>()
36
41
  const status = ref<FormStatus | undefined>()
37
42
  const formData = ref<Partial<z.infer<Schema> | undefined>>()
43
+
44
+ const validate = async (value = formData.value) => {
45
+ const parseResult = await schema.safeParseAsync(value)
46
+ if (!parseResult.success) {
47
+ errors.value = parseResult.error.format() as ZodFormattedError<
48
+ z.infer<Schema>
49
+ >
50
+ status.value = FormStatus.invalid
51
+ return false
52
+ }
53
+ errors.value = undefined
54
+ status.value = FormStatus.valid
55
+ formData.value = parseResult.data
56
+ return true
57
+ }
58
+
59
+ const submit = async () => {
60
+ if (!(await validate())) {
61
+ return false
62
+ }
63
+ status.value = FormStatus.submitting
64
+ return true
65
+ }
66
+
67
+ const { ignoreUpdates, stop: stopUpdatesWatch } = watchIgnorable(
68
+ formData,
69
+ () => {
70
+ status.value = FormStatus.updated
71
+ },
72
+ {
73
+ deep: true,
74
+ eventFilter: throttleFilter(options?.updateThrottle ?? 500),
75
+ },
76
+ )
77
+
38
78
  const component = defineComponent({
39
- name: 'FormComponent',
79
+ name: 'VvForm',
40
80
  props: {
81
+ continuosValidation: {
82
+ type: Boolean,
83
+ default: false,
84
+ },
41
85
  modelValue: {
42
86
  type: Object,
43
87
  default: () => ({}),
44
88
  },
45
- updateThrottle: {
46
- type: Number,
47
- default: 500,
48
- },
49
- continuosValidation: {
50
- type: Boolean,
51
- default: false,
89
+ tag: {
90
+ type: String,
91
+ default: 'form',
52
92
  },
53
93
  template: {
54
94
  type: [Array, Function] as PropType<FormTemplate<Schema>>,
@@ -63,7 +103,6 @@ export const defineForm = <Schema extends FormSchema>(
63
103
  toRaw(props.modelValue),
64
104
  )
65
105
 
66
- // clone modelValue and update formData
67
106
  watch(
68
107
  () => props.modelValue,
69
108
  (newValue) => {
@@ -80,72 +119,62 @@ export const defineForm = <Schema extends FormSchema>(
80
119
  { deep: true },
81
120
  )
82
121
 
83
- // emit update:modelValue on formData change
84
- watchThrottled(
85
- formData,
86
- (newValue) => {
122
+ watch(status, async (newValue) => {
123
+ if (newValue === FormStatus.invalid) {
124
+ const toReturn = toRaw(
125
+ errors.value as ZodFormattedError<z.infer<Schema>>,
126
+ )
127
+ emit('invalid', toReturn)
128
+ options?.onInvalid?.(toReturn)
129
+ return
130
+ }
131
+ if (newValue === FormStatus.valid) {
132
+ const toReturn = toRaw(formData.value as z.infer<Schema>)
133
+ emit('valid', toReturn)
134
+ options?.onValid?.(toReturn)
135
+ emit('update:modelValue', toReturn)
136
+ options?.onUpdate?.(toReturn)
137
+ return
138
+ }
139
+ if (newValue === FormStatus.submitting) {
140
+ const toReturn = toRaw(formData.value as z.infer<Schema>)
141
+ emit('submit', toReturn)
142
+ options?.onSubmit?.(toReturn)
143
+ }
144
+ if (newValue === FormStatus.updated) {
87
145
  if (
88
- (errors.value || options?.continuosValidation) ??
146
+ errors.value ||
147
+ options?.continuosValidation ||
89
148
  props.continuosValidation
90
149
  ) {
91
- validate()
150
+ await validate()
92
151
  }
93
152
  if (
94
- !newValue ||
153
+ !formData.value ||
95
154
  !props.modelValue ||
96
- JSON.stringify(newValue) !==
155
+ JSON.stringify(formData.value) !==
97
156
  JSON.stringify(props.modelValue)
98
157
  ) {
99
- emit('update:modelValue', newValue)
100
- options?.onUpdate?.(toRaw(newValue))
158
+ const toReturn = toRaw(
159
+ formData.value as z.infer<Schema>,
160
+ )
161
+ emit('update:modelValue', toReturn)
162
+ options?.onUpdate?.(toReturn)
163
+ }
164
+ if (status.value === FormStatus.updated) {
165
+ status.value = FormStatus.unknown
101
166
  }
102
- },
103
- {
104
- deep: true,
105
- throttle: options?.updateThrottle ?? props.updateThrottle,
106
- },
107
- )
108
-
109
- // validate formData with safeParse
110
- const validate = (value = formData.value) => {
111
- const parseResult = schema.safeParse(value)
112
- if (!parseResult.success) {
113
- errors.value =
114
- parseResult.error.format() as ZodFormattedError<
115
- z.infer<Schema>
116
- >
117
- status.value = FormStatus.invalid
118
- emit('invalid', errors.value)
119
- options?.onInvalid?.(toRaw(errors.value))
120
- return false
121
- }
122
- errors.value = undefined
123
- status.value = FormStatus.valid
124
- formData.value = parseResult.data
125
- emit('update:modelValue', formData.value)
126
- options?.onUpdate?.(toRaw(formData.value))
127
- emit('valid', parseResult.data)
128
- options?.onValid?.(toRaw(formData.value))
129
- return true
130
- }
131
-
132
- // emit submit event if form is valid
133
- const submit = () => {
134
- if (!validate()) {
135
- return false
136
167
  }
137
- emit('submit', formData.value as z.infer<Schema>)
138
- options?.onSubmit?.(toRaw(formData.value) as z.infer<Schema>)
139
- return true
140
- }
168
+ })
141
169
 
142
170
  const invalid = computed(() => status.value === FormStatus.invalid)
143
171
 
144
- // provide data to children
145
172
  provide(provideKey, {
146
173
  formData,
147
174
  submit,
148
175
  validate,
176
+ ignoreUpdates,
177
+ stopUpdatesWatch,
149
178
  errors: readonly(errors),
150
179
  status: readonly(status),
151
180
  invalid,
@@ -155,6 +184,8 @@ export const defineForm = <Schema extends FormSchema>(
155
184
  formData,
156
185
  submit,
157
186
  validate,
187
+ ignoreUpdates,
188
+ stopUpdatesWatch,
158
189
  errors: readonly(errors),
159
190
  status: readonly(status),
160
191
  invalid,
@@ -166,12 +197,14 @@ export const defineForm = <Schema extends FormSchema>(
166
197
  formData: this.formData,
167
198
  submit: this.submit,
168
199
  validate: this.validate,
200
+ ignoreUpdates: this.ignoreUpdates,
201
+ stopUpdatesWatch: this.stopUpdatesWatch,
169
202
  errors: this.errors,
170
203
  status: this.status,
171
204
  invalid: this.invalid,
172
205
  }) ?? this.$slots.default
173
206
  return h(
174
- 'form',
207
+ this.tag,
175
208
  {
176
209
  onSubmit: withModifiers(this.submit, ['prevent']),
177
210
  },
@@ -197,6 +230,10 @@ export const defineForm = <Schema extends FormSchema>(
197
230
  errors,
198
231
  status,
199
232
  formData,
233
+ validate,
234
+ submit,
235
+ ignoreUpdates,
236
+ stopUpdatesWatch,
200
237
  /**
201
238
  * An hack to add types to the default slot
202
239
  */
@@ -209,8 +246,10 @@ export const defineForm = <Schema extends FormSchema>(
209
246
  | undefined
210
247
  ? undefined
211
248
  : Partial<TypeOf<Schema>> | undefined
212
- submit: () => boolean
213
- validate: () => boolean
249
+ submit: () => Promise<boolean>
250
+ validate: () => Promise<boolean>
251
+ ignoreUpdates: IgnoredUpdater
252
+ stopUpdatesWatch: WatchStopHandle
214
253
  errors: Readonly<
215
254
  Ref<DeepReadonly<z.inferFormattedError<Schema>>>
216
255
  >
@@ -38,7 +38,7 @@ export const defineFormField = <Schema extends FormSchema>(
38
38
  ) => {
39
39
  // define component
40
40
  return defineComponent({
41
- name: 'FieldComponent',
41
+ name: 'VvFormField',
42
42
  props: {
43
43
  type: {
44
44
  type: String as PropType<`${FormFieldType}`>,
@@ -20,6 +20,7 @@ export const defineFormTemplate = <Schema extends FormSchema>(
20
20
  VvFormField: Component,
21
21
  ) => {
22
22
  const VvFormTemplate = defineComponent({
23
+ name: 'VvFormTemplate',
23
24
  props: {
24
25
  schema: {
25
26
  type: [Array, Function] as PropType<FormTemplate<Schema>>,
@@ -162,8 +163,8 @@ export const defineFormTemplate = <Schema extends FormSchema>(
162
163
  | undefined
163
164
  ? undefined
164
165
  : Partial<TypeOf<Schema>> | undefined
165
- submit: () => boolean
166
- validate: () => boolean
166
+ submit: () => Promise<boolean>
167
+ validate: () => Promise<boolean>
167
168
  errors: Readonly<
168
169
  Ref<DeepReadonly<z.inferFormattedError<Schema>>>
169
170
  >
@@ -24,7 +24,7 @@ export const defineFormWrapper = <Schema extends FormSchema>(
24
24
  wrapperProvideKey: InjectionKey<InjectedFormWrapperData<Schema>>,
25
25
  ) => {
26
26
  const VvFormWrapper = defineComponent({
27
- name: 'WrapperComponent',
27
+ name: 'VvFormWrapper',
28
28
  props: {
29
29
  name: {
30
30
  type: String,
@@ -149,8 +149,8 @@ export const defineFormWrapper = <Schema extends FormSchema>(
149
149
  | undefined
150
150
  ? undefined
151
151
  : Partial<TypeOf<Schema>> | undefined
152
- submit: () => boolean
153
- validate: () => boolean
152
+ submit: () => Promise<boolean>
153
+ validate: () => Promise<boolean>
154
154
  errors: Readonly<
155
155
  Ref<DeepReadonly<z.inferFormattedError<Schema>>>
156
156
  >
package/src/enums.ts CHANGED
@@ -25,4 +25,7 @@ export enum FormFieldType {
25
25
  export enum FormStatus {
26
26
  invalid = 'invalid',
27
27
  valid = 'valid',
28
+ submitting = 'submitting',
29
+ updated = 'updated',
30
+ unknown = 'unknown',
28
31
  }
package/src/index.ts CHANGED
@@ -48,12 +48,16 @@ const _formFactory = <Schema extends FormSchema>(
48
48
  options,
49
49
  )
50
50
  const VvFormTemplate = defineFormTemplate(formInjectionKey, VvFormField)
51
- const { VvForm, errors, status, formData } = defineForm(
52
- schema,
53
- formInjectionKey,
54
- options,
55
- VvFormTemplate,
56
- )
51
+ const {
52
+ VvForm,
53
+ errors,
54
+ status,
55
+ formData,
56
+ validate,
57
+ submit,
58
+ ignoreUpdates,
59
+ stopUpdatesWatch,
60
+ } = defineForm(schema, formInjectionKey, options, VvFormTemplate)
57
61
 
58
62
  return {
59
63
  VvForm,
@@ -66,6 +70,10 @@ const _formFactory = <Schema extends FormSchema>(
66
70
  errors,
67
71
  status,
68
72
  formData,
73
+ validate,
74
+ submit,
75
+ ignoreUpdates,
76
+ stopUpdatesWatch,
69
77
  }
70
78
  }
71
79
 
package/src/types.ts CHANGED
@@ -1,5 +1,6 @@
1
- import { type Component, type DeepReadonly, type Ref } from 'vue'
1
+ import type { Component, DeepReadonly, Ref, WatchStopHandle } from 'vue'
2
2
  import type { z, AnyZodObject, ZodEffects, inferFormattedError } from 'zod'
3
+ import type { IgnoredUpdater } from '@vueuse/core'
3
4
  import type { FormFieldType, FormStatus } from './enums'
4
5
 
5
6
  export type FormSchema =
@@ -45,8 +46,10 @@ export type InjectedFormData<Schema extends FormSchema> = {
45
46
  errors: Readonly<
46
47
  Ref<DeepReadonly<z.inferFormattedError<Schema>> | undefined>
47
48
  >
48
- submit: () => boolean
49
- validate: () => boolean
49
+ submit: () => Promise<boolean>
50
+ validate: () => Promise<boolean>
51
+ ignoreUpdates: IgnoredUpdater
52
+ stopUpdatesWatch: WatchStopHandle
50
53
  status: Readonly<Ref<FormStatus | undefined>>
51
54
  invalid: Readonly<Ref<boolean>>
52
55
  }
@@ -71,8 +74,6 @@ export type Primitive =
71
74
  | symbol
72
75
  | bigint
73
76
 
74
- type ArrayKey = number
75
-
76
77
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
78
  type IsTuple<T extends readonly any[]> = number extends T['length']
78
79
  ? false
@@ -91,7 +92,7 @@ export type Path<T> = T extends readonly (infer V)[]
91
92
  ? {
92
93
  [K in TupleKeys<T>]-?: PathConcat<K & string, T[K]>
93
94
  }[TupleKeys<T>]
94
- : PathConcat<ArrayKey, V>
95
+ : PathConcat<number, V>
95
96
  : {
96
97
  [K in keyof T]-?: PathConcat<K & string, T[K]>
97
98
  }[keyof T]
@@ -105,14 +106,14 @@ export type PathValue<T, TPath extends Path<T> | Path<T>[]> = T extends any
105
106
  ? PathValue<T[K], R> | undefined
106
107
  : PathValue<T[K], R>
107
108
  : never
108
- : K extends `${ArrayKey}`
109
+ : K extends `${number}`
109
110
  ? T extends readonly (infer V)[]
110
111
  ? PathValue<V, R & Path<V>>
111
112
  : never
112
113
  : never
113
114
  : TPath extends keyof T
114
115
  ? T[TPath]
115
- : TPath extends `${ArrayKey}`
116
+ : TPath extends `${number}`
116
117
  ? T extends readonly (infer V)[]
117
118
  ? V
118
119
  : never