@volverjs/form-vue 1.0.0-beta.12 → 1.0.0-beta.13

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,347 +1,345 @@
1
1
  import { get, set } from 'ts-dot-prop'
2
2
  import {
3
- type Component,
4
- type InjectionKey,
5
- type PropType,
6
- type Ref,
7
- type ConcreteComponent,
8
- computed,
9
- defineAsyncComponent,
10
- h,
11
- inject,
12
- onMounted,
13
- provide,
14
- readonly,
15
- resolveComponent,
16
- toRefs,
17
- watch,
18
- defineComponent,
19
- onBeforeUnmount,
20
- unref,
3
+ type Component,
4
+ type InjectionKey,
5
+ type PropType,
6
+ type Ref,
7
+ type ConcreteComponent,
8
+ computed,
9
+ defineAsyncComponent,
10
+ h,
11
+ inject,
12
+ onMounted,
13
+ provide,
14
+ readonly,
15
+ resolveComponent,
16
+ toRefs,
17
+ watch,
18
+ defineComponent,
19
+ onBeforeUnmount,
20
+ unref,
21
21
  } from 'vue'
22
22
  import type { z } from 'zod'
23
23
  import { FormFieldType } from './enums'
24
24
  import type {
25
- InjectedFormData,
26
- InjectedFormWrapperData,
27
- InjectedFormFieldData,
28
- FormFieldComponentOptions,
29
- Path,
30
- FormSchema,
25
+ InjectedFormData,
26
+ InjectedFormWrapperData,
27
+ InjectedFormFieldData,
28
+ FormFieldComponentOptions,
29
+ Path,
30
+ FormSchema,
31
31
  } from './types'
32
32
 
33
- export const defineFormField = <Schema extends FormSchema>(
34
- formProvideKey: InjectionKey<InjectedFormData<Schema>>,
35
- wrapperProvideKey: InjectionKey<InjectedFormWrapperData<Schema>>,
36
- formFieldInjectionKey: InjectionKey<InjectedFormFieldData<Schema>>,
37
- options?: FormFieldComponentOptions,
38
- ) => {
39
- // define component
40
- return defineComponent({
41
- name: 'VvFormField',
42
- props: {
43
- type: {
44
- type: String as PropType<`${FormFieldType}`>,
45
- validator: (value: FormFieldType) => {
46
- return Object.values(FormFieldType).includes(value)
47
- },
48
- default: FormFieldType.custom,
49
- },
50
- is: {
51
- type: [Object, String] as PropType<Component>,
52
- default: undefined,
53
- },
54
- name: {
55
- type: [String, Number, Boolean, Symbol] as PropType<
33
+ export function defineFormField<Schema extends FormSchema>(formProvideKey: InjectionKey<InjectedFormData<Schema>>, wrapperProvideKey: InjectionKey<InjectedFormWrapperData<Schema>>, formFieldInjectionKey: InjectionKey<InjectedFormFieldData<Schema>>, options?: FormFieldComponentOptions) {
34
+ // define component
35
+ return defineComponent({
36
+ name: 'VvFormField',
37
+ props: {
38
+ type: {
39
+ type: String as PropType<`${FormFieldType}`>,
40
+ validator: (value: FormFieldType) => {
41
+ return Object.values(FormFieldType).includes(value)
42
+ },
43
+ default: FormFieldType.custom,
44
+ },
45
+ is: {
46
+ type: [Object, String] as PropType<Component | string>,
47
+ default: undefined,
48
+ },
49
+ name: {
50
+ type: [String, Number, Boolean, Symbol] as PropType<
56
51
  Path<z.infer<Schema>>
57
52
  >,
58
- required: true,
59
- },
60
- props: {
61
- type: [Object, Function] as PropType<
53
+ required: true,
54
+ },
55
+ props: {
56
+ type: [Object, Function] as PropType<
62
57
  Partial<
63
- | z.infer<Schema>
64
- | undefined
65
- | ((
66
- formData?: Ref<ObjectConstructor>,
58
+ | z.infer<Schema>
59
+ | undefined
60
+ | ((
61
+ formData?: Ref<ObjectConstructor>,
67
62
  ) => Partial<z.infer<Schema>> | undefined)
68
63
  >
69
64
  >,
70
- default: () => ({}),
71
- },
72
- showValid: {
73
- type: Boolean,
74
- default: false,
75
- },
76
- defaultValue: {
77
- type: [String, Number, Boolean, Array, Object],
78
- default: undefined,
79
- },
80
- lazyLoad: {
81
- type: Boolean,
82
- default: false,
83
- },
84
- readonly: {
85
- type: Boolean,
86
- default: undefined,
87
- },
88
- },
89
- emits: ['invalid', 'valid', 'update:formData', 'update:modelValue'],
90
- expose: ['invalid', 'invalidLabel', 'errors'],
91
- setup(props, { slots, emit }) {
92
- // v-model
93
- const modelValue = computed({
94
- get() {
95
- if (!injectedFormData?.formData) return
96
- return get(
97
- Object(injectedFormData.formData.value),
98
- String(props.name),
99
- )
100
- },
101
- set(value) {
102
- if (!injectedFormData?.formData) return
103
- set(
104
- Object(injectedFormData.formData.value),
105
- String(props.name),
106
- value,
107
- )
108
- emit('update:modelValue', {
109
- newValue: modelValue.value,
110
- formData: injectedFormData?.formData,
111
- })
112
- },
113
- })
114
- onMounted(() => {
115
- if (
116
- modelValue.value === undefined &&
117
- props.defaultValue !== undefined
118
- ) {
119
- modelValue.value = props.defaultValue
120
- }
121
- })
65
+ default: () => ({}),
66
+ },
67
+ showValid: {
68
+ type: Boolean,
69
+ default: false,
70
+ },
71
+ defaultValue: {
72
+ type: [String, Number, Boolean, Array, Object],
73
+ default: undefined,
74
+ },
75
+ lazyLoad: {
76
+ type: Boolean,
77
+ default: false,
78
+ },
79
+ readonly: {
80
+ type: Boolean,
81
+ default: undefined,
82
+ },
83
+ },
84
+ emits: ['invalid', 'valid', 'update:formData', 'update:modelValue'],
85
+ expose: ['invalid', 'invalidLabel', 'errors'],
86
+ setup(props, { slots, emit }) {
87
+ // inject data from parent form wrapper
88
+ const injectedWrapperData = inject(wrapperProvideKey, undefined)
89
+ if (injectedWrapperData) {
90
+ injectedWrapperData.fields.value.add(props.name as string)
91
+ }
122
92
 
123
- onBeforeUnmount(() => {
124
- unwatchInvalid()
125
- unwatchInjectedFormData()
126
- })
93
+ // inject data from parent form
94
+ const injectedFormData = inject(formProvideKey)
95
+ const { props: fieldProps, name: fieldName } = toRefs(props)
127
96
 
128
- // inject data from parent form wrapper
129
- const injectedWrapperData = inject(wrapperProvideKey, undefined)
130
- if (injectedWrapperData) {
131
- injectedWrapperData.fields.value.add(props.name as string)
132
- }
97
+ // v-model
98
+ const modelValue = computed({
99
+ get() {
100
+ if (!injectedFormData?.formData)
101
+ return
102
+ return get(
103
+ Object(injectedFormData.formData.value),
104
+ String(props.name),
105
+ )
106
+ },
107
+ set(value) {
108
+ if (!injectedFormData?.formData)
109
+ return
110
+ set(
111
+ Object(injectedFormData.formData.value),
112
+ String(props.name),
113
+ value,
114
+ )
115
+ emit('update:modelValue', {
116
+ newValue: modelValue.value,
117
+ formData: injectedFormData?.formData,
118
+ })
119
+ },
120
+ })
121
+ onMounted(() => {
122
+ if (
123
+ modelValue.value === undefined
124
+ && props.defaultValue !== undefined
125
+ ) {
126
+ modelValue.value = props.defaultValue
127
+ }
128
+ })
133
129
 
134
- // inject data from parent form
135
- const injectedFormData = inject(formProvideKey)
136
- const { props: fieldProps, name: fieldName } = toRefs(props)
130
+ const errors = computed(() => {
131
+ if (!injectedFormData?.errors.value) {
132
+ return undefined
133
+ }
134
+ return get(injectedFormData.errors.value, String(props.name))
135
+ })
136
+ const invalidLabel = computed(() => {
137
+ return errors.value?._errors
138
+ })
139
+ const invalid = computed(() => {
140
+ return errors.value !== undefined
141
+ })
142
+ const unwatchInvalid = watch(invalid, () => {
143
+ if (invalid.value) {
144
+ emit('invalid', invalidLabel.value)
145
+ if (injectedWrapperData) {
146
+ injectedWrapperData.errors.value.set(
147
+ props.name as string,
148
+ {
149
+ _errors: invalidLabel.value,
150
+ } as z.inferFormattedError<Schema>,
151
+ )
152
+ }
153
+ }
154
+ else {
155
+ emit('valid', modelValue.value)
156
+ if (injectedWrapperData) {
157
+ injectedWrapperData.errors.value.delete(
158
+ props.name as string,
159
+ )
160
+ }
161
+ }
162
+ })
163
+ const unwatchInjectedFormData = watch(
164
+ () => injectedFormData?.formData,
165
+ () => {
166
+ emit('update:formData', injectedFormData?.formData)
167
+ },
168
+ { deep: true },
169
+ )
170
+ onBeforeUnmount(() => {
171
+ unwatchInvalid()
172
+ unwatchInjectedFormData()
173
+ })
174
+ const onUpdate = (value: unknown) => {
175
+ modelValue.value = value
176
+ }
177
+ const hasFieldProps = computed(() => {
178
+ let toReturn = fieldProps.value
179
+ if (typeof toReturn === 'function') {
180
+ toReturn = toReturn(injectedFormData?.formData)
181
+ }
182
+ return Object.keys(toReturn).reduce<Record<string, unknown>>(
183
+ (acc, key) => {
184
+ acc[key] = unref(toReturn[key])
185
+ return acc
186
+ },
187
+ {},
188
+ )
189
+ })
190
+ const isReadonly = computed(() => {
191
+ if (injectedFormData?.readonly.value) {
192
+ return true
193
+ }
194
+ return hasFieldProps.value.readonly ?? props.readonly
195
+ })
196
+ const hasProps = computed(() => ({
197
+ ...hasFieldProps.value,
198
+ 'name': hasFieldProps.value.name ?? props.name,
199
+ 'invalid': invalid.value,
200
+ 'valid': props.showValid
201
+ ? Boolean(!invalid.value && modelValue.value)
202
+ : undefined,
203
+ 'type': ((type: FormFieldType) => {
204
+ if (
205
+ [
206
+ FormFieldType.text,
207
+ FormFieldType.number,
208
+ FormFieldType.email,
209
+ FormFieldType.password,
210
+ FormFieldType.tel,
211
+ FormFieldType.url,
212
+ FormFieldType.search,
213
+ FormFieldType.date,
214
+ FormFieldType.time,
215
+ FormFieldType.datetimeLocal,
216
+ FormFieldType.month,
217
+ FormFieldType.week,
218
+ FormFieldType.color,
219
+ ].includes(type)
220
+ ) {
221
+ return type
222
+ }
223
+ return undefined
224
+ })(props.type as FormFieldType),
225
+ 'invalidLabel': invalidLabel.value,
226
+ 'modelValue': modelValue.value,
227
+ 'readonly': isReadonly.value,
228
+ 'onUpdate:modelValue': onUpdate,
229
+ }))
137
230
 
138
- const errors = computed(() => {
139
- if (!injectedFormData?.errors.value) {
140
- return undefined
141
- }
142
- return get(injectedFormData.errors.value, String(props.name))
143
- })
144
- const invalidLabel = computed(() => {
145
- return errors.value?._errors
146
- })
147
- const invalid = computed(() => {
148
- return errors.value !== undefined
149
- })
150
- const unwatchInvalid = watch(invalid, () => {
151
- if (invalid.value) {
152
- emit('invalid', invalidLabel.value)
153
- if (injectedWrapperData) {
154
- injectedWrapperData.errors.value.set(
155
- props.name as string,
156
- {
157
- _errors: invalidLabel.value,
158
- } as z.inferFormattedError<Schema>,
159
- )
160
- }
161
- } else {
162
- emit('valid', modelValue.value)
163
- if (injectedWrapperData) {
164
- injectedWrapperData.errors.value.delete(
165
- props.name as string,
166
- )
167
- }
168
- }
169
- })
170
- const unwatchInjectedFormData = watch(
171
- () => injectedFormData?.formData,
172
- () => {
173
- emit('update:formData', injectedFormData?.formData)
174
- },
175
- { deep: true },
176
- )
177
- const onUpdate = (value: unknown) => {
178
- modelValue.value = value
179
- }
180
- const hasFieldProps = computed(() => {
181
- let toReturn = fieldProps.value
182
- if (typeof toReturn === 'function') {
183
- toReturn = toReturn(injectedFormData?.formData)
184
- }
185
- return Object.keys(toReturn).reduce<Record<string, unknown>>(
186
- (acc, key) => {
187
- acc[key] = unref(toReturn[key])
188
- return acc
189
- },
190
- {},
191
- )
192
- })
193
- const isReadonly = computed(() => {
194
- if (injectedFormData?.readonly.value) {
195
- return true
196
- }
197
- return hasFieldProps.value.readonly ?? props.readonly
198
- })
199
- const hasProps = computed(() => ({
200
- ...hasFieldProps.value,
201
- name: hasFieldProps.value.name ?? props.name,
202
- invalid: invalid.value,
203
- valid: props.showValid
204
- ? Boolean(!invalid.value && modelValue.value)
205
- : undefined,
206
- type: ((type: FormFieldType) => {
207
- if (
208
- [
209
- FormFieldType.text,
210
- FormFieldType.number,
211
- FormFieldType.email,
212
- FormFieldType.password,
213
- FormFieldType.tel,
214
- FormFieldType.url,
215
- FormFieldType.search,
216
- FormFieldType.date,
217
- FormFieldType.time,
218
- FormFieldType.datetimeLocal,
219
- FormFieldType.month,
220
- FormFieldType.week,
221
- FormFieldType.color,
222
- ].includes(type)
223
- ) {
224
- return type
225
- }
226
- return undefined
227
- })(props.type as FormFieldType),
228
- invalidLabel: invalidLabel.value,
229
- modelValue: modelValue.value,
230
- readonly: isReadonly.value,
231
- 'onUpdate:modelValue': onUpdate,
232
- }))
231
+ // provide data to children
232
+ provide(formFieldInjectionKey, {
233
+ name: readonly(fieldName as Ref<string>),
234
+ errors: readonly(errors),
235
+ })
233
236
 
234
- // provide data to children
235
- provide(formFieldInjectionKey, {
236
- name: readonly(fieldName as Ref<string>),
237
- errors: readonly(errors),
238
- })
239
-
240
- // load component
241
- const component = computed(() => {
242
- if (props.type === FormFieldType.custom) {
243
- return {
244
- render() {
245
- return (
246
- slots.default?.({
247
- modelValue: modelValue.value,
248
- onUpdate,
249
- submit: injectedFormData?.submit,
250
- validate: injectedFormData?.validate,
251
- invalid: invalid.value,
252
- invalidLabel: invalidLabel.value,
253
- formData: injectedFormData?.formData.value,
254
- formErrors: injectedFormData?.errors.value,
255
- errors: errors.value,
256
- readonly: isReadonly.value,
257
- }) ?? slots.defalut
258
- )
259
- },
260
- }
261
- }
262
- if (!(options?.lazyLoad ?? props.lazyLoad)) {
263
- let component: string | ConcreteComponent
264
- switch (props.type) {
265
- case FormFieldType.select:
266
- component = resolveComponent('VvSelect')
267
- break
268
- case FormFieldType.checkbox:
269
- component = resolveComponent('VvCheckbox')
270
- break
271
- case FormFieldType.radio:
272
- component = resolveComponent('VvRadio')
273
- break
274
- case FormFieldType.textarea:
275
- component = resolveComponent('VvTextarea')
276
- break
277
- case FormFieldType.radioGroup:
278
- component = resolveComponent('VvRadioGroup')
279
- break
280
- case FormFieldType.checkboxGroup:
281
- component = resolveComponent('VvCheckboxGroup')
282
- break
283
- case FormFieldType.combobox:
284
- component = resolveComponent('VvCombobox')
285
- break
286
- default:
287
- component = resolveComponent('VvInputText')
288
- }
289
- if (typeof component !== 'string') {
290
- return component
291
- } else {
292
- console.warn(
237
+ // load component
238
+ const component = computed(() => {
239
+ if (props.type === FormFieldType.custom) {
240
+ return {
241
+ render() {
242
+ return (
243
+ slots.default?.({
244
+ modelValue: modelValue.value,
245
+ onUpdate,
246
+ submit: injectedFormData?.submit,
247
+ validate: injectedFormData?.validate,
248
+ invalid: invalid.value,
249
+ invalidLabel: invalidLabel.value,
250
+ formData: injectedFormData?.formData.value,
251
+ formErrors: injectedFormData?.errors.value,
252
+ errors: errors.value,
253
+ readonly: isReadonly.value,
254
+ }) ?? slots.defalut
255
+ )
256
+ },
257
+ }
258
+ }
259
+ if (!(options?.lazyLoad ?? props.lazyLoad)) {
260
+ let component: string | ConcreteComponent
261
+ switch (props.type) {
262
+ case FormFieldType.select:
263
+ component = resolveComponent('VvSelect')
264
+ break
265
+ case FormFieldType.checkbox:
266
+ component = resolveComponent('VvCheckbox')
267
+ break
268
+ case FormFieldType.radio:
269
+ component = resolveComponent('VvRadio')
270
+ break
271
+ case FormFieldType.textarea:
272
+ component = resolveComponent('VvTextarea')
273
+ break
274
+ case FormFieldType.radioGroup:
275
+ component = resolveComponent('VvRadioGroup')
276
+ break
277
+ case FormFieldType.checkboxGroup:
278
+ component = resolveComponent('VvCheckboxGroup')
279
+ break
280
+ case FormFieldType.combobox:
281
+ component = resolveComponent('VvCombobox')
282
+ break
283
+ default:
284
+ component = resolveComponent('VvInputText')
285
+ }
286
+ if (typeof component !== 'string') {
287
+ return component
288
+ }
289
+ else {
290
+ console.warn(
293
291
  `[form-vue warn]: ${component} not found, the component will be loaded asynchronously. To avoid this warning, please set "lazyLoad" option.`,
294
- )
295
- }
296
- }
297
- return defineAsyncComponent(async () => {
298
- if (options?.sideEffects) {
299
- await Promise.resolve(options.sideEffects(props.type))
300
- }
301
- switch (props.type) {
302
- case FormFieldType.textarea:
303
- return import(
304
- '@volverjs/ui-vue/vv-textarea'
305
- ) as Component
306
- case FormFieldType.radio:
307
- return import(
308
- '@volverjs/ui-vue/vv-radio'
309
- ) as Component
310
- case FormFieldType.radioGroup:
311
- return import(
312
- '@volverjs/ui-vue/vv-radio-group'
313
- ) as Component
314
- case FormFieldType.checkbox:
315
- return import(
316
- '@volverjs/ui-vue/vv-checkbox'
317
- ) as Component
318
- case FormFieldType.checkboxGroup:
319
- return import(
320
- '@volverjs/ui-vue/vv-checkbox-group'
321
- ) as Component
322
- case FormFieldType.select:
323
- return import(
324
- '@volverjs/ui-vue/vv-select'
325
- ) as Component
326
- case FormFieldType.combobox:
327
- return import(
328
- '@volverjs/ui-vue/vv-combobox'
329
- ) as Component
330
- }
331
- return import('@volverjs/ui-vue/vv-input-text') as Component
332
- })
333
- })
292
+ )
293
+ }
294
+ }
295
+ return defineAsyncComponent(async () => {
296
+ if (options?.sideEffects) {
297
+ await Promise.resolve(options.sideEffects(props.type))
298
+ }
299
+ switch (props.type) {
300
+ case FormFieldType.textarea:
301
+ return import(
302
+ '@volverjs/ui-vue/vv-textarea'
303
+ ) as Component
304
+ case FormFieldType.radio:
305
+ return import(
306
+ '@volverjs/ui-vue/vv-radio'
307
+ ) as Component
308
+ case FormFieldType.radioGroup:
309
+ return import(
310
+ '@volverjs/ui-vue/vv-radio-group'
311
+ ) as Component
312
+ case FormFieldType.checkbox:
313
+ return import(
314
+ '@volverjs/ui-vue/vv-checkbox'
315
+ ) as Component
316
+ case FormFieldType.checkboxGroup:
317
+ return import(
318
+ '@volverjs/ui-vue/vv-checkbox-group'
319
+ ) as Component
320
+ case FormFieldType.select:
321
+ return import(
322
+ '@volverjs/ui-vue/vv-select'
323
+ ) as Component
324
+ case FormFieldType.combobox:
325
+ return import(
326
+ '@volverjs/ui-vue/vv-combobox'
327
+ ) as Component
328
+ }
329
+ return import('@volverjs/ui-vue/vv-input-text') as Component
330
+ })
331
+ })
334
332
 
335
- return { component, hasProps, invalid }
336
- },
337
- render() {
338
- if (this.is) {
339
- return h(this.is, this.hasProps, this.$slots)
340
- }
341
- if (this.type === FormFieldType.custom) {
342
- return h(this.component as Component, null, this.$slots)
343
- }
344
- return h(this.component as Component, this.hasProps, this.$slots)
345
- },
346
- })
333
+ return { component, hasProps, invalid }
334
+ },
335
+ render() {
336
+ if (this.is) {
337
+ return h(this.is, this.hasProps, this.$slots)
338
+ }
339
+ if (this.type === FormFieldType.custom) {
340
+ return h(this.component, null, this.$slots)
341
+ }
342
+ return h(this.component, this.hasProps, this.$slots)
343
+ },
344
+ })
347
345
  }