@volverjs/form-vue 1.0.0-beta.8 → 1.0.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.
@@ -1,171 +1,208 @@
1
- import {
2
- type InjectionKey,
3
- type Ref,
4
- computed,
5
- defineComponent,
6
- inject,
7
- provide,
8
- readonly,
9
- ref,
10
- toRefs,
11
- watch,
12
- h,
13
- type DeepReadonly,
14
- } from 'vue'
15
- import type { TypeOf, z } from 'zod'
1
+ import type { DeepReadonly, InjectionKey, Ref, SlotsType } from 'vue'
2
+ import type { z } from 'zod'
16
3
  import type {
17
- FormSchema,
18
- InjectedFormData,
19
- InjectedFormWrapperData,
4
+ FormSchema,
5
+ InjectedFormData,
6
+ InjectedFormWrapperData,
7
+ Path,
20
8
  } from './types'
9
+ import {
10
+ computed,
11
+ defineComponent,
12
+ h,
13
+ inject,
14
+ onBeforeUnmount,
15
+ onMounted,
16
+ provide,
17
+ readonly,
18
+ ref,
19
+ toRefs,
20
+ watch,
21
+ } from 'vue'
22
+
23
+ export function defineFormWrapper<Schema extends FormSchema, Type = undefined>(formProvideKey: InjectionKey<InjectedFormData<Schema, Type>>, wrapperProvideKey: InjectionKey<InjectedFormWrapperData<Schema>>) {
24
+ return defineComponent({
25
+ name: 'VvFormWrapper',
26
+ props: {
27
+ name: {
28
+ type: String,
29
+ required: true,
30
+ },
31
+ tag: {
32
+ type: String,
33
+ default: undefined,
34
+ },
35
+ readonly: {
36
+ type: Boolean,
37
+ default: false,
38
+ },
39
+ },
40
+ emits: ['invalid', 'valid'],
41
+ expose: [
42
+ 'clear',
43
+ 'errors',
44
+ 'fields',
45
+ 'fieldsErrors',
46
+ 'formData',
47
+ 'invalid',
48
+ 'readonly',
49
+ 'reset',
50
+ 'submit',
51
+ 'tag',
52
+ 'validate',
53
+ 'validateWrapper',
54
+ ],
55
+ slots: Object as SlotsType<{
56
+ default: {
57
+ errors?: DeepReadonly<z.inferFormattedError<Schema>>
58
+ fieldsErrors: Map<string, z.inferFormattedError<Schema>>
59
+ formData?: undefined extends Type ? Partial<z.infer<Schema>> : Type
60
+ formErrors?: DeepReadonly<z.inferFormattedError<Schema>>
61
+ invalid: boolean
62
+ readonly: boolean
63
+ clear?: InjectedFormData<Schema, Type>['clear']
64
+ reset?: InjectedFormData<Schema, Type>['reset']
65
+ submit?: InjectedFormData<Schema, Type>['submit']
66
+ validate?: InjectedFormData<Schema, Type>['validate']
67
+ validateWrapper?: () => Promise<boolean>
68
+ }
69
+ }>,
70
+ setup(props, { emit }) {
71
+ // inject data from parent form
72
+ const injectedFormData = inject(formProvideKey)
73
+ // inject data from parent form wrapper
74
+ const injectedWrapperData = inject(wrapperProvideKey, undefined)
75
+ const fields: Ref<Map<string, Path<z.infer<Schema>>>> = ref(new Map())
76
+ const fieldsErrors: Ref<
77
+ Map<string, z.inferFormattedError<Schema>>
78
+ > = ref(new Map())
79
+ const { name } = toRefs(props)
80
+
81
+ // invalid
82
+ const isInvalid = computed(() => {
83
+ if (!injectedFormData?.invalid.value) {
84
+ return false
85
+ }
86
+ return fieldsErrors.value.size > 0
87
+ })
88
+ watch(isInvalid, (newValue) => {
89
+ if (newValue) {
90
+ emit('invalid')
91
+ return
92
+ }
93
+ emit('valid')
94
+ })
21
95
 
22
- export const defineFormWrapper = <Schema extends FormSchema>(
23
- formProvideKey: InjectionKey<InjectedFormData<Schema>>,
24
- wrapperProvideKey: InjectionKey<InjectedFormWrapperData<Schema>>,
25
- ) => {
26
- const VvFormWrapper = defineComponent({
27
- name: 'VvFormWrapper',
28
- props: {
29
- name: {
30
- type: String,
31
- required: true,
32
- },
33
- tag: {
34
- type: String,
35
- default: undefined,
36
- },
37
- },
38
- emits: ['invalid', 'valid'],
39
- expose: ['fields', 'invalid'],
40
- setup(props, { emit }) {
41
- const injectedFormData = inject(formProvideKey)
42
- const wrapperProvided = inject(wrapperProvideKey, undefined)
43
- const fields = ref(new Set<string>())
44
- const fieldsErrors: Ref<
45
- Map<string, z.inferFormattedError<Schema>>
46
- > = ref(new Map())
47
- const { name } = toRefs(props)
96
+ // readonly
97
+ const isReadonly = computed(() => injectedFormData?.readonly.value || props.readonly)
48
98
 
49
- // provide data to child fields
50
- provide(wrapperProvideKey, {
51
- name: readonly(name),
52
- errors: fieldsErrors,
53
- fields,
54
- })
99
+ // provide data to child fields
100
+ const providedData = {
101
+ name: readonly(name),
102
+ errors: fieldsErrors,
103
+ invalid: readonly(isInvalid),
104
+ readonly: readonly(isReadonly),
105
+ fields,
106
+ }
107
+ provide(wrapperProvideKey, providedData)
55
108
 
56
- // add fields to parent wrapper
57
- watch(
58
- fields,
59
- (newValue) => {
60
- if (wrapperProvided?.fields) {
61
- newValue.forEach((field) => {
62
- wrapperProvided?.fields.value.add(field)
63
- })
64
- }
65
- },
66
- { deep: true },
67
- )
109
+ // add fields to parent wrapper
110
+ const computedFields = computed(() => new Map(fields.value))
111
+ watch(
112
+ computedFields,
113
+ (newValue, oldValue) => {
114
+ if (injectedWrapperData?.fields) {
115
+ oldValue.forEach((_field, key) => {
116
+ if (!newValue.has(key)) {
117
+ injectedWrapperData?.fields.value.delete(key)
118
+ }
119
+ })
120
+ newValue.forEach((field, key) => {
121
+ if (!injectedWrapperData?.fields.value.has(key)) {
122
+ injectedWrapperData?.fields.value.set(key, field)
123
+ }
124
+ })
125
+ }
126
+ },
127
+ { deep: true },
128
+ )
68
129
 
69
- // add fields to parent wrapper
70
- watch(
71
- () => new Map(fieldsErrors.value),
72
- (newValue, oldValue) => {
73
- if (wrapperProvided?.errors) {
74
- Array.from(oldValue.keys()).forEach((key) => {
75
- wrapperProvided.errors.value.delete(key)
76
- })
77
- Array.from(newValue.keys()).forEach((key) => {
78
- const value = newValue.get(key)
79
- if (value) {
80
- wrapperProvided.errors.value.set(key, value)
81
- }
82
- })
83
- }
84
- },
85
- { deep: true },
86
- )
130
+ // add fields errors to parent wrapper
131
+ watch(
132
+ fieldsErrors,
133
+ (newValue) => {
134
+ if (injectedWrapperData?.errors) {
135
+ fields.value.forEach((field) => {
136
+ if (!newValue.has(field)) {
137
+ injectedWrapperData.errors.value.delete(field)
138
+ }
139
+ if (newValue.has(field)) {
140
+ const value = newValue.get(field)
141
+ if (value) {
142
+ injectedWrapperData.errors.value.set(field, value)
143
+ }
144
+ }
145
+ })
146
+ }
147
+ },
148
+ { deep: true },
149
+ )
87
150
 
88
- const invalid = computed(() => {
89
- if (!injectedFormData?.invalid.value) {
90
- return false
91
- }
92
- return fieldsErrors.value.size > 0
93
- })
151
+ onMounted(() => {
152
+ if (!injectedFormData?.wrappers || !name.value) {
153
+ console.warn('[@volverjs/form-vue]: Invalid wrapper registration state')
154
+ return
155
+ }
156
+ if (injectedFormData.wrappers.has(name.value)) {
157
+ console.warn(`[@volverjs/form-vue]: wrapper name "${name.value}" is already used`)
158
+ return
159
+ }
160
+ injectedFormData.wrappers.set(name.value, providedData)
161
+ })
162
+ onBeforeUnmount(() => {
163
+ if (injectedFormData?.wrappers && name.value) {
164
+ injectedFormData.wrappers.delete(name.value)
165
+ }
166
+ })
94
167
 
95
- watch(invalid, () => {
96
- if (invalid.value) {
97
- emit('invalid')
98
- } else {
99
- emit('valid')
100
- }
101
- })
168
+ const validateWrapper = () => {
169
+ return injectedFormData?.validate(undefined, { fields: new Set(fields.value.values()) }) ?? Promise.resolve(true)
170
+ }
102
171
 
103
- return {
104
- formData: injectedFormData?.formData,
105
- errors: injectedFormData?.errors,
106
- submit: injectedFormData?.submit,
107
- validate: injectedFormData?.validate,
108
- invalid,
109
- fields,
110
- fieldsErrors,
111
- }
112
- },
113
- render() {
114
- if (this.tag) {
115
- return h(this.tag, null, {
116
- default: () =>
117
- this.$slots.default?.({
118
- invalid: this.invalid,
119
- formData: this.formData,
120
- submit: this.submit,
121
- validate: this.validate,
122
- errors: this.errors,
123
- fieldsErrors: this.fieldsErrors,
124
- }) ?? this.$slots.defalut,
125
- })
126
- }
127
- return (
128
- this.$slots.default?.({
129
- invalid: this.invalid,
130
- formData: this.formData,
131
- submit: this.submit,
132
- validate: this.validate,
133
- errors: this.errors,
134
- fieldsErrors: this.fieldsErrors,
135
- }) ?? this.$slots.defalut
136
- )
137
- },
138
- })
139
- /**
140
- * An hack to add types to the default slot
141
- */
142
- return VvFormWrapper as typeof VvFormWrapper & {
143
- new (): {
144
- $slots: {
145
- default: (_: {
146
- invalid: boolean
147
- formData: unknown extends
148
- | Partial<TypeOf<Schema>>
149
- | undefined
150
- ? undefined
151
- : Partial<TypeOf<Schema>> | undefined
152
- submit: () => Promise<boolean>
153
- validate: () => Promise<boolean>
154
- errors: Readonly<
155
- Ref<DeepReadonly<z.inferFormattedError<Schema>>>
156
- >
157
- fieldsErrors: Map<
158
- string,
159
- Record<
160
- string,
161
- {
162
- _errors: string[]
163
- }
164
- >
165
- >
166
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
167
- }) => any
168
- }
169
- }
170
- }
172
+ return {
173
+ errors: injectedFormData?.errors,
174
+ fields,
175
+ fieldsErrors,
176
+ formData: injectedFormData?.formData,
177
+ invalid: isInvalid,
178
+ readonly: isReadonly,
179
+ clear: injectedFormData?.clear,
180
+ reset: injectedFormData?.reset,
181
+ submit: injectedFormData?.submit,
182
+ validate: injectedFormData?.validate,
183
+ validateWrapper,
184
+ }
185
+ },
186
+ render() {
187
+ const defaultSlot = () =>
188
+ this.$slots.default?.({
189
+ errors: this.errors,
190
+ fieldsErrors: this.fieldsErrors,
191
+ formData: this.formData,
192
+ invalid: this.invalid,
193
+ readonly: this.readonly,
194
+ clear: this.clear,
195
+ reset: this.reset,
196
+ submit: this.submit,
197
+ validate: this.validate,
198
+ validateWrapper: this.validateWrapper,
199
+ })
200
+ if (this.tag) {
201
+ return h(this.tag, null, {
202
+ default: defaultSlot,
203
+ })
204
+ }
205
+ return defaultSlot()
206
+ },
207
+ })
171
208
  }
package/src/enums.ts CHANGED
@@ -1,31 +1,32 @@
1
1
  export enum FormFieldType {
2
- text = 'text',
3
- number = 'number',
4
- email = 'email',
5
- password = 'password',
6
- tel = 'tel',
7
- url = 'url',
8
- search = 'search',
9
- date = 'date',
10
- time = 'time',
11
- datetimeLocal = 'datetime-local',
12
- month = 'month',
13
- week = 'week',
14
- color = 'color',
15
- select = 'select',
16
- checkbox = 'checkbox',
17
- radio = 'radio',
18
- textarea = 'textarea',
19
- radioGroup = 'radioGroup',
20
- checkboxGroup = 'checkboxGroup',
21
- combobox = 'combobox',
22
- custom = 'custom',
2
+ text = 'text',
3
+ number = 'number',
4
+ email = 'email',
5
+ password = 'password',
6
+ tel = 'tel',
7
+ url = 'url',
8
+ search = 'search',
9
+ date = 'date',
10
+ time = 'time',
11
+ datetimeLocal = 'datetime-local',
12
+ month = 'month',
13
+ week = 'week',
14
+ color = 'color',
15
+ select = 'select',
16
+ checkbox = 'checkbox',
17
+ radio = 'radio',
18
+ textarea = 'textarea',
19
+ radioGroup = 'radioGroup',
20
+ checkboxGroup = 'checkboxGroup',
21
+ combobox = 'combobox',
22
+ custom = 'custom',
23
23
  }
24
24
 
25
25
  export enum FormStatus {
26
- invalid = 'invalid',
27
- valid = 'valid',
28
- submitting = 'submitting',
29
- updated = 'updated',
30
- unknown = 'unknown',
26
+ invalid = 'invalid',
27
+ valid = 'valid',
28
+ submitting = 'submitting',
29
+ reset = 'reset',
30
+ updated = 'updated',
31
+ unknown = 'unknown',
31
32
  }