@volverjs/form-vue 0.0.14 → 1.0.0-beta.10

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/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(b,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("vue"),require("@vueuse/core"),require("zod")):typeof define=="function"&&define.amd?define(["exports","vue","@vueuse/core","zod"],r):(b=typeof globalThis<"u"?globalThis:b||self,r(b["@volverjs/form-vue"]={},b.Vue,b.VueUseCore,b.zod))})(this,function(b,r,q,g){"use strict";function $(e){return Array.isArray(e)}function M(e){return typeof e<"u"}function L(e){return e===null}function W(e){return typeof e=="object"}function Z(e){return typeof e=="string"}function S(e){return typeof e>"u"}const J=/^[0-9]+$/,P=["__proto__","prototype","constructor"];function I(e,i,a){const f=M(a)?a:void 0;if(!W(e)||!Z(i))return f;const s=K(i);if(s.length!==0){for(const t of s){if(t==="*")continue;const l=function(o){return o.map(u=>S(u)||L(u)?u:$(u)?l(u):u[t])};if($(e)&&!J.test(t)?e=l(e):e=e[t],S(e)||L(e))break}return S(e)?f:e}}function R(e,i,a){if(!W(e)||!Z(i))return;const f=K(i);if(f.length===0)return;const s=f.length;for(let t=0;t<s;t++){const l=f[t];if(t===s-1){e[l]=a;return}if(l==="*"&&$(e)){const o=f.slice(t+1).join(".");for(const u of e)R(u,o,a);return}S(e[l])&&(e[l]={}),e=e[l]}}function K(e){const i=e.split(/[.]|(?:\[(\d|\*)\])/).filter(a=>!!a);return i.some(a=>P.indexOf(a)!==-1)?[]:i}var d=(e=>(e.text="text",e.number="number",e.email="email",e.password="password",e.tel="tel",e.url="url",e.search="search",e.date="date",e.time="time",e.datetimeLocal="datetime-local",e.month="month",e.week="week",e.color="color",e.select="select",e.checkbox="checkbox",e.radio="radio",e.textarea="textarea",e.radioGroup="radioGroup",e.checkboxGroup="checkboxGroup",e.combobox="combobox",e.custom="custom",e))(d||{}),E=(e=>(e.invalid="invalid",e.valid="valid",e))(E||{});const T=(e,i,a,f)=>r.defineComponent({name:"FieldComponent",props:{type:{type:String,validator:s=>Object.values(d).includes(s),default:d.custom},is:{type:[Object,String],default:void 0},name:{type:[String,Number,Boolean,Symbol],required:!0},props:{type:[Object,Function],default:()=>({})},showValid:{type:Boolean,default:!1},defaultValue:{type:[String,Number,Boolean,Array,Object],default:void 0},lazyLoad:{type:Boolean,default:!1}},emits:["invalid","valid","update:formData","update:modelValue"],expose:["invalid","invalidLabel","errors"],setup(s,{slots:t,emit:l}){const o=r.computed({get(){if(n!=null&&n.formData)return I(Object(n.formData.value),String(s.name))},set(m){n!=null&&n.formData&&(R(Object(n.formData.value),String(s.name),m),l("update:modelValue",{newValue:o.value,formData:n==null?void 0:n.formData}))}});r.onMounted(()=>{o.value===void 0&&s.defaultValue!==void 0&&(o.value=s.defaultValue)}),r.onBeforeUnmount(()=>{O(),x()});const u=r.inject(i,void 0);u&&u.fields.value.add(s.name);const n=r.inject(e),{props:c,name:y}=r.toRefs(s),V=r.computed(()=>{if(n!=null&&n.errors.value)return I(n.errors.value,String(s.name))}),v=r.computed(()=>{var m;return(m=V.value)==null?void 0:m._errors}),h=r.computed(()=>V.value!==void 0),O=r.watch(h,()=>{h.value?(l("invalid",v.value),u&&u.errors.value.set(s.name,{_errors:v.value})):(l("valid",o.value),u&&u.errors.value.delete(s.name))}),x=r.watch(()=>n==null?void 0:n.formData,()=>{l("update:formData",n==null?void 0:n.formData)},{deep:!0}),p=m=>{o.value=m},w=r.computed(()=>{let m=c.value;return typeof m=="function"&&(m=m(n==null?void 0:n.formData)),Object.keys(m).reduce((C,k)=>(C[k]=r.unref(m[k]),C),{})}),N=r.computed(()=>({...w.value,name:w.value.name??s.name,invalid:h.value,valid:s.showValid?!!(!h.value&&o.value):void 0,type:(m=>{if([d.text,d.number,d.email,d.password,d.tel,d.url,d.search,d.date,d.time,d.datetimeLocal,d.month,d.week,d.color].includes(m))return m})(s.type),invalidLabel:v.value,modelValue:o.value,"onUpdate:modelValue":p}));return r.provide(a,{name:r.readonly(y),errors:r.readonly(V)}),{component:r.computed(()=>{if(s.type===d.custom)return{render(){var m;return((m=t.default)==null?void 0:m.call(t,{modelValue:o.value,onUpdate:p,submit:n==null?void 0:n.submit,validate:n==null?void 0:n.validate,invalid:h.value,invalidLabel:v.value,formData:n==null?void 0:n.formData.value,formErrors:n==null?void 0:n.errors.value,errors:V.value}))??t.defalut}};if(!((f==null?void 0:f.lazyLoad)??s.lazyLoad)){let m;switch(s.type){case d.select:m=r.resolveComponent("VvSelect");break;case d.checkbox:m=r.resolveComponent("VvCheckbox");break;case d.radio:m=r.resolveComponent("VvRadio");break;case d.textarea:m=r.resolveComponent("VvTextarea");break;case d.radioGroup:m=r.resolveComponent("VvRadioGroup");break;case d.checkboxGroup:m=r.resolveComponent("VvCheckboxGroup");break;case d.combobox:m=r.resolveComponent("VvCombobox");break;default:m=r.resolveComponent("VvInputText")}if(typeof m!="string")return m;console.warn(`[form-vue warn]: ${m} not found, the component will be loaded asynchronously. To avoid this warning, please set "lazyLoad" option.`)}return r.defineAsyncComponent(async()=>{switch(f!=null&&f.sideEffects&&await Promise.resolve(f.sideEffects(s.type)),s.type){case d.textarea:return import("@volverjs/ui-vue/vv-textarea");case d.radio:return import("@volverjs/ui-vue/vv-radio");case d.radioGroup:return import("@volverjs/ui-vue/vv-radio-group");case d.checkbox:return import("@volverjs/ui-vue/vv-checkbox");case d.checkboxGroup:return import("@volverjs/ui-vue/vv-checkbox-group");case d.select:return import("@volverjs/ui-vue/vv-select");case d.combobox:return import("@volverjs/ui-vue/vv-combobox")}return import("@volverjs/ui-vue/vv-input-text")})}),hasProps:N,invalid:h}},render(){return this.is?r.h(this.is,this.hasProps,this.$slots):this.type===d.custom?r.h(this.component,null,this.$slots):r.h(this.component,this.hasProps,this.$slots)}}),A=(e,i={})=>{const a=t=>{let l=t;for(;l instanceof g.ZodEffects;)l=l.innerType();return l instanceof g.ZodOptional&&(l=l._def.innerType),l},f=a(e);return{...(f instanceof g.ZodObject?f._def.unknownKeys==="passthrough":!1)?i:{},...Object.fromEntries(Object.entries(f.shape).map(([t,l])=>{const o=i[t];let u=a(l),n;if(u instanceof g.ZodDefault&&(n=u._def.defaultValue(),u=u._def.innerType),o===null&&u instanceof g.ZodNullable)return[t,o];if(u instanceof g.ZodSchema){const c=l.safeParse(o);if(c.success)return[t,c.data??n]}if(u instanceof g.ZodArray&&Array.isArray(o)&&o.length){const c=a(u._def.type);if(c instanceof g.ZodObject)return[t,o.map(y=>A(c,y&&typeof y=="object"?y:void 0))??n]}return u instanceof g.ZodObject?[t,A(u,o&&typeof o=="object"?o:n)]:[t,n]}))}},z=(e,i,a,f)=>{const s=r.ref(),t=r.ref(),l=r.ref(),o=r.defineComponent({name:"FormComponent",props:{modelValue:{type:Object,default:()=>({})},updateThrottle:{type:Number,default:500},continuosValidation:{type:Boolean,default:!1},template:{type:[Array,Function],default:void 0}},emits:["invalid","valid","submit","update:modelValue"],expose:["submit","validate","errors","status","valid","invalid"],setup(u,{emit:n}){l.value=A(e,r.toRaw(u.modelValue)),r.watch(()=>u.modelValue,v=>{if(v){const h=r.isProxy(v)?r.toRaw(v):v;l.value=typeof(h==null?void 0:h.clone)=="function"?h.clone():JSON.parse(JSON.stringify(h))}},{deep:!0}),q.watchThrottled(l,v=>{var h;((s.value||(a==null?void 0:a.continuosValidation))??u.continuosValidation)&&c(),(!v||!u.modelValue||JSON.stringify(v)!==JSON.stringify(u.modelValue))&&(n("update:modelValue",v),(h=a==null?void 0:a.onUpdate)==null||h.call(a,r.toRaw(v)))},{deep:!0,throttle:(a==null?void 0:a.updateThrottle)??u.updateThrottle});const c=(v=l.value)=>{var O,x,p;const h=e.safeParse(v);return h.success?(s.value=void 0,t.value=E.valid,l.value=h.data,n("update:modelValue",l.value),(x=a==null?void 0:a.onUpdate)==null||x.call(a,r.toRaw(l.value)),n("valid",h.data),(p=a==null?void 0:a.onValid)==null||p.call(a,r.toRaw(l.value)),!0):(s.value=h.error.format(),t.value=E.invalid,n("invalid",s.value),(O=a==null?void 0:a.onInvalid)==null||O.call(a,r.toRaw(s.value)),!1)},y=()=>{var v;return c()?(n("submit",l.value),(v=a==null?void 0:a.onSubmit)==null||v.call(a,r.toRaw(l.value)),!0):!1},V=r.computed(()=>t.value===E.invalid);return r.provide(i,{formData:l,submit:y,validate:c,errors:r.readonly(s),status:r.readonly(t),invalid:V}),{formData:l,submit:y,validate:c,errors:r.readonly(s),status:r.readonly(t),invalid:V}},render(){const u=()=>{var n,c;return((c=(n=this.$slots)==null?void 0:n.default)==null?void 0:c.call(n,{formData:this.formData,submit:this.submit,validate:this.validate,errors:this.errors,status:this.status,invalid:this.invalid}))??this.$slots.default};return r.h("form",{onSubmit:r.withModifiers(this.submit,["prevent"])},(this.template??(a==null?void 0:a.template))&&f?[r.h(f,{schema:this.template??(a==null?void 0:a.template)},{default:u})]:{default:u})}});return{errors:s,status:t,formData:l,VvForm:o}},H=(e,i)=>r.defineComponent({name:"WrapperComponent",props:{name:{type:String,required:!0},tag:{type:String,default:void 0}},emits:["invalid","valid"],expose:["fields","invalid"],setup(f,{emit:s}){const t=r.inject(e),l=r.inject(i,void 0),o=r.ref(new Set),u=r.ref(new Map),{name:n}=r.toRefs(f);r.provide(i,{name:r.readonly(n),errors:u,fields:o}),r.watch(o,y=>{l!=null&&l.fields&&y.forEach(V=>{l==null||l.fields.value.add(V)})},{deep:!0}),r.watch(()=>new Map(u.value),(y,V)=>{l!=null&&l.errors&&(Array.from(V.keys()).forEach(v=>{l.errors.value.delete(v)}),Array.from(y.keys()).forEach(v=>{const h=y.get(v);h&&l.errors.value.set(v,h)}))},{deep:!0});const c=r.computed(()=>t!=null&&t.invalid.value?u.value.size>0:!1);return r.watch(c,()=>{c.value?s("invalid"):s("valid")}),{formData:t==null?void 0:t.formData,errors:t==null?void 0:t.errors,submit:t==null?void 0:t.submit,validate:t==null?void 0:t.validate,invalid:c,fields:o,fieldsErrors:u}},render(){var f,s;return this.tag?r.h(this.tag,null,{default:()=>{var t,l;return((l=(t=this.$slots).default)==null?void 0:l.call(t,{invalid:this.invalid,formData:this.formData,submit:this.submit,validate:this.validate,errors:this.errors,fieldsErrors:this.fieldsErrors}))??this.$slots.defalut}}):((s=(f=this.$slots).default)==null?void 0:s.call(f,{invalid:this.invalid,formData:this.formData,submit:this.submit,validate:this.validate,errors:this.errors,fieldsErrors:this.fieldsErrors}))??this.$slots.defalut}}),Q=(e,i)=>{const a=r.defineComponent({props:{schema:{type:[Array,Function],required:!0}},setup(f,{slots:s}){const t=r.inject(e);if(t!=null&&t.formData)return()=>{var n;const l=typeof f.schema=="function"?f.schema(t):f.schema;let o;const u=l.reduce((c,y)=>{const V=typeof y=="function"?y(t):y,{vvIs:v,vvName:h,vvSlots:O,vvChildren:x,vvIf:p,vvElseIf:w,vvType:N,vvDefaultValue:U,vvShowValid:m,vvContent:C,...k}=V;if(p!==void 0){if(typeof p=="string"?o=!!I(Object(t.formData.value),p):typeof p=="function"?o=r.unref(p(t)):o=r.unref(p),!o)return c}else if(w!==void 0&&o!==void 0){if(o||(typeof w=="string"?o=!!I(Object(t.formData.value),w):typeof w=="function"?o=r.unref(w(t)):o=r.unref(w),!o))return c}else o=void 0;const G=x?r.h(a,{schema:x}):void 0;return h?(c.push(r.h(i,{name:h,is:v,type:N,defaultValue:U,showValid:m,props:k},O??G??C)),c):v?(c.push(r.h(v,k,O??G??C)),c):(x&&c.push(G),c)},[]);return u.push((n=s==null?void 0:s.default)==null?void 0:n.call(s,{formData:t==null?void 0:t.formData.value,submit:t==null?void 0:t.submit,validate:t==null?void 0:t.validate,errors:t==null?void 0:t.errors.value,status:t==null?void 0:t.status.value,invalid:t==null?void 0:t.invalid.value})),u}}});return a},_=(e,i={})=>{const a=Symbol(),f=Symbol(),s=Symbol(),t=H(a,f),l=T(a,f,s,i),o=Q(a,l),{VvForm:u,errors:n,status:c,formData:y}=z(e,a,i,o);return{VvForm:u,VvFormWrapper:t,VvFormField:l,VvFormTemplate:o,formInjectionKey:a,formWrapperInjectionKey:f,formFieldInjectionKey:s,errors:n,status:c,formData:y}},B=Symbol(),X=e=>{let i={};return e.schema&&(i=_(e.schema,e)),{...i,install(a,{global:f=!1}={}){a.provide(B,e),f&&(a.config.globalProperties.$vvForm=e,i!=null&&i.VvForm&&a.component("VvForm",i.VvForm),i!=null&&i.VvFormWrapper&&a.component("VvFormWrapper",i.VvFormWrapper),i!=null&&i.VvFormField&&a.component("VvFormField",i.VvFormField),i!=null&&i.VvFormTemplate&&a.component("VvFormTemplate",i.VvFormTemplate))}}},Y=(e,i={})=>r.getCurrentInstance()?_(e,{...r.inject(B,{}),...i}):_(e,i),D=(e,i={})=>_(e,i);b.FormFieldType=d,b.createForm=X,b.defaultObjectBySchema=A,b.formFactory=D,b.pluginInjectionKey=B,b.useForm=Y,Object.defineProperty(b,Symbol.toStringTag,{value:"Module"})});
1
+ (function(w,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("vue"),require("@vueuse/core"),require("zod")):typeof define=="function"&&define.amd?define(["exports","vue","@vueuse/core","zod"],e):(w=typeof globalThis<"u"?globalThis:w||self,e(w["@volverjs/form-vue"]={},w.Vue,w.VueUseCore,w.zod))})(this,function(w,e,$,k){"use strict";function N(r){return Array.isArray(r)}function J(r){return typeof r<"u"}function G(r){return r===null}function L(r){return typeof r=="object"}function Z(r){return typeof r=="string"}function A(r){return typeof r>"u"}const M=/^[0-9]+$/,z=["__proto__","prototype","constructor"];function E(r,u,a){const d=J(a)?a:void 0;if(!L(r)||!Z(u))return d;const i=q(u);if(i.length!==0){for(const t of i){if(t==="*")continue;const s=function(l){return l.map(f=>A(f)||G(f)?f:N(f)?s(f):f[t])};if(N(r)&&!M.test(t)?r=s(r):r=r[t],A(r)||G(r))break}return A(r)?d:r}}function K(r,u,a){if(!L(r)||!Z(u))return;const d=q(u);if(d.length===0)return;const i=d.length;for(let t=0;t<i;t++){const s=d[t];if(t===i-1){r[s]=a;return}if(s==="*"&&N(r)){const l=d.slice(t+1).join(".");for(const f of r)K(f,l,a);return}A(r[s])&&(r[s]={}),r=r[s]}}function q(r){const u=r.split(/[.]|(?:\[(\d|\*)\])/).filter(a=>!!a);return u.some(a=>z.indexOf(a)!==-1)?[]:u}var c=(r=>(r.text="text",r.number="number",r.email="email",r.password="password",r.tel="tel",r.url="url",r.search="search",r.date="date",r.time="time",r.datetimeLocal="datetime-local",r.month="month",r.week="week",r.color="color",r.select="select",r.checkbox="checkbox",r.radio="radio",r.textarea="textarea",r.radioGroup="radioGroup",r.checkboxGroup="checkboxGroup",r.combobox="combobox",r.custom="custom",r))(c||{}),O=(r=>(r.invalid="invalid",r.valid="valid",r.submitting="submitting",r.updated="updated",r.unknown="unknown",r))(O||{});const T=(r,u,a,d)=>e.defineComponent({name:"VvFormField",props:{type:{type:String,validator:i=>Object.values(c).includes(i),default:c.custom},is:{type:[Object,String],default:void 0},name:{type:[String,Number,Boolean,Symbol],required:!0},props:{type:[Object,Function],default:()=>({})},showValid:{type:Boolean,default:!1},defaultValue:{type:[String,Number,Boolean,Array,Object],default:void 0},lazyLoad:{type:Boolean,default:!1},readonly:{type:Boolean,default:void 0}},emits:["invalid","valid","update:formData","update:modelValue"],expose:["invalid","invalidLabel","errors"],setup(i,{slots:t,emit:s}){const l=e.computed({get(){if(n!=null&&n.formData)return E(Object(n.formData.value),String(i.name))},set(o){n!=null&&n.formData&&(K(Object(n.formData.value),String(i.name),o),s("update:modelValue",{newValue:l.value,formData:n==null?void 0:n.formData}))}});e.onMounted(()=>{l.value===void 0&&i.defaultValue!==void 0&&(l.value=i.defaultValue)}),e.onBeforeUnmount(()=>{y(),h()});const f=e.inject(u,void 0);f&&f.fields.value.add(i.name);const n=e.inject(r),{props:v,name:V}=e.toRefs(i),p=e.computed(()=>{if(n!=null&&n.errors.value)return E(n.errors.value,String(i.name))}),g=e.computed(()=>{var o;return(o=p.value)==null?void 0:o._errors}),m=e.computed(()=>p.value!==void 0),y=e.watch(m,()=>{m.value?(s("invalid",g.value),f&&f.errors.value.set(i.name,{_errors:g.value})):(s("valid",l.value),f&&f.errors.value.delete(i.name))}),h=e.watch(()=>n==null?void 0:n.formData,()=>{s("update:formData",n==null?void 0:n.formData)},{deep:!0}),b=o=>{l.value=o},x=e.computed(()=>{let o=v.value;return typeof o=="function"&&(o=o(n==null?void 0:n.formData)),Object.keys(o).reduce((U,C)=>(U[C]=e.unref(o[C]),U),{})}),S=e.computed(()=>n!=null&&n.readonly.value?!0:x.value.readonly??i.readonly),I=e.computed(()=>({...x.value,name:x.value.name??i.name,invalid:m.value,valid:i.showValid?!!(!m.value&&l.value):void 0,type:(o=>{if([c.text,c.number,c.email,c.password,c.tel,c.url,c.search,c.date,c.time,c.datetimeLocal,c.month,c.week,c.color].includes(o))return o})(i.type),invalidLabel:g.value,modelValue:l.value,readonly:S.value,"onUpdate:modelValue":b}));return e.provide(a,{name:e.readonly(V),errors:e.readonly(p)}),{component:e.computed(()=>{if(i.type===c.custom)return{render(){var o;return((o=t.default)==null?void 0:o.call(t,{modelValue:l.value,onUpdate:b,submit:n==null?void 0:n.submit,validate:n==null?void 0:n.validate,invalid:m.value,invalidLabel:g.value,formData:n==null?void 0:n.formData.value,formErrors:n==null?void 0:n.errors.value,errors:p.value,readonly:S.value}))??t.defalut}};if(!((d==null?void 0:d.lazyLoad)??i.lazyLoad)){let o;switch(i.type){case c.select:o=e.resolveComponent("VvSelect");break;case c.checkbox:o=e.resolveComponent("VvCheckbox");break;case c.radio:o=e.resolveComponent("VvRadio");break;case c.textarea:o=e.resolveComponent("VvTextarea");break;case c.radioGroup:o=e.resolveComponent("VvRadioGroup");break;case c.checkboxGroup:o=e.resolveComponent("VvCheckboxGroup");break;case c.combobox:o=e.resolveComponent("VvCombobox");break;default:o=e.resolveComponent("VvInputText")}if(typeof o!="string")return o;console.warn(`[form-vue warn]: ${o} not found, the component will be loaded asynchronously. To avoid this warning, please set "lazyLoad" option.`)}return e.defineAsyncComponent(async()=>{switch(d!=null&&d.sideEffects&&await Promise.resolve(d.sideEffects(i.type)),i.type){case c.textarea:return import("@volverjs/ui-vue/vv-textarea");case c.radio:return import("@volverjs/ui-vue/vv-radio");case c.radioGroup:return import("@volverjs/ui-vue/vv-radio-group");case c.checkbox:return import("@volverjs/ui-vue/vv-checkbox");case c.checkboxGroup:return import("@volverjs/ui-vue/vv-checkbox-group");case c.select:return import("@volverjs/ui-vue/vv-select");case c.combobox:return import("@volverjs/ui-vue/vv-combobox")}return import("@volverjs/ui-vue/vv-input-text")})}),hasProps:I,invalid:m}},render(){return this.is?e.h(this.is,this.hasProps,this.$slots):this.type===c.custom?e.h(this.component,null,this.$slots):e.h(this.component,this.hasProps,this.$slots)}}),W=(r,u={})=>{const a=t=>{let s=t;for(;s instanceof k.ZodEffects;)s=s.innerType();return s instanceof k.ZodOptional&&(s=s._def.innerType),s},d=a(r);return{...(d instanceof k.ZodObject?d._def.unknownKeys==="passthrough":!1)?u:{},...Object.fromEntries(Object.entries(d.shape).map(([t,s])=>{const l=u[t];let f=a(s),n;if(f instanceof k.ZodDefault&&(n=f._def.defaultValue(),f=f._def.innerType),l===null&&f instanceof k.ZodNullable)return[t,l];if(f instanceof k.ZodSchema){const v=s.safeParse(l);if(v.success)return[t,v.data??n]}if(f instanceof k.ZodArray&&Array.isArray(l)&&l.length){const v=a(f._def.type);if(v instanceof k.ZodObject)return[t,l.map(V=>W(v,V&&typeof V=="object"?V:void 0))??n]}return f instanceof k.ZodObject?[t,W(f,l&&typeof l=="object"?l:n)]:[t,n]}))}},P=(r,u,a,d)=>{const i=e.ref(),t=e.ref(),s=e.computed(()=>t.value===O.invalid),l=e.ref(),f=e.ref(!1),n=async(m=l.value)=>{if(f.value)return!0;const y=await r.safeParseAsync(m);return y.success?(i.value=void 0,t.value=O.valid,l.value=y.data,!0):(i.value=y.error.format(),t.value=O.invalid,!1)},v=async()=>f.value||!await n()?!1:(t.value=O.submitting,!0),{ignoreUpdates:V,stop:p}=$.watchIgnorable(l,()=>{t.value=O.updated},{deep:!0,eventFilter:$.throttleFilter((a==null?void 0:a.updateThrottle)??500)}),g=e.defineComponent({name:"VvForm",props:{continuosValidation:{type:Boolean,default:!1},modelValue:{type:Object,default:()=>({})},readonly:{type:Boolean,default:(a==null?void 0:a.readonly)??!1},tag:{type:String,default:"form"},template:{type:[Array,Function],default:void 0}},emits:["invalid","valid","submit","update:modelValue","update:readonly"],expose:["submit","validate","errors","status","valid","invalid","readonly"],setup(m,{emit:y}){return l.value=W(r,e.toRaw(m.modelValue)),e.watch(()=>m.modelValue,h=>{if(h){const b=e.isProxy(h)?e.toRaw(h):h;if(JSON.stringify(b)===JSON.stringify(e.toRaw(l.value)))return;l.value=typeof(b==null?void 0:b.clone)=="function"?b.clone():JSON.parse(JSON.stringify(b))}},{deep:!0}),e.watch(t,async h=>{var b,x,S,I,R;if(h===O.invalid){const o=e.toRaw(i.value);y("invalid",o),(b=a==null?void 0:a.onInvalid)==null||b.call(a,o);return}if(h===O.valid){const o=e.toRaw(l.value);y("valid",o),(x=a==null?void 0:a.onValid)==null||x.call(a,o),y("update:modelValue",o),(S=a==null?void 0:a.onUpdate)==null||S.call(a,o);return}if(h===O.submitting){const o=e.toRaw(l.value);y("submit",o),(I=a==null?void 0:a.onSubmit)==null||I.call(a,o)}if(h===O.updated){if((i.value||a!=null&&a.continuosValidation||m.continuosValidation)&&await n(),!l.value||!m.modelValue||JSON.stringify(l.value)!==JSON.stringify(m.modelValue)){const o=e.toRaw(l.value);y("update:modelValue",o),(R=a==null?void 0:a.onUpdate)==null||R.call(a,o)}t.value===O.updated&&(t.value=O.unknown)}}),e.onMounted(()=>{f.value=m.readonly}),e.watch(()=>m.readonly,h=>{f.value=h}),e.watch(f,h=>{h!==m.readonly&&y("update:readonly",f.value)}),e.provide(u,{formData:l,submit:v,validate:n,ignoreUpdates:V,stopUpdatesWatch:p,errors:e.readonly(i),status:e.readonly(t),invalid:s,readonly:f}),{formData:l,submit:v,validate:n,ignoreUpdates:V,stopUpdatesWatch:p,errors:e.readonly(i),status:e.readonly(t),invalid:s,isReadonly:f}},render(){const m=()=>{var y,h;return((h=(y=this.$slots)==null?void 0:y.default)==null?void 0:h.call(y,{formData:this.formData,submit:this.submit,validate:this.validate,ignoreUpdates:this.ignoreUpdates,stopUpdatesWatch:this.stopUpdatesWatch,errors:this.errors,status:this.status,invalid:this.invalid,readonly:this.isReadonly}))??this.$slots.default};return e.h(this.tag,{onSubmit:e.withModifiers(this.submit,["prevent"])},(this.template??(a==null?void 0:a.template))&&d?[e.h(d,{schema:this.template??(a==null?void 0:a.template)},{default:m})]:{default:m})}});return{errors:i,status:t,invalid:s,readonly:f,formData:l,validate:n,submit:v,ignoreUpdates:V,stopUpdatesWatch:p,VvForm:g}},H=(r,u)=>e.defineComponent({name:"VvFormWrapper",props:{name:{type:String,required:!0},tag:{type:String,default:void 0}},emits:["invalid","valid"],expose:["fields","invalid"],setup(d,{emit:i}){const t=e.inject(r),s=e.inject(u,void 0),l=e.ref(new Set),f=e.ref(new Map),{name:n}=e.toRefs(d);e.provide(u,{name:e.readonly(n),errors:f,fields:l}),e.watch(l,V=>{s!=null&&s.fields&&V.forEach(p=>{s==null||s.fields.value.add(p)})},{deep:!0}),e.watch(()=>new Map(f.value),(V,p)=>{s!=null&&s.errors&&(Array.from(p.keys()).forEach(g=>{s.errors.value.delete(g)}),Array.from(V.keys()).forEach(g=>{const m=V.get(g);m&&s.errors.value.set(g,m)}))},{deep:!0});const v=e.computed(()=>t!=null&&t.invalid.value?f.value.size>0:!1);return e.watch(v,()=>{v.value?i("invalid"):i("valid")}),{formData:t==null?void 0:t.formData,errors:t==null?void 0:t.errors,submit:t==null?void 0:t.submit,validate:t==null?void 0:t.validate,invalid:v,fields:l,fieldsErrors:f}},render(){var d,i;return this.tag?e.h(this.tag,null,{default:()=>{var t,s;return((s=(t=this.$slots).default)==null?void 0:s.call(t,{invalid:this.invalid,formData:this.formData,submit:this.submit,validate:this.validate,errors:this.errors,fieldsErrors:this.fieldsErrors}))??this.$slots.defalut}}):((i=(d=this.$slots).default)==null?void 0:i.call(d,{invalid:this.invalid,formData:this.formData,submit:this.submit,validate:this.validate,errors:this.errors,fieldsErrors:this.fieldsErrors}))??this.$slots.defalut}}),Q=(r,u)=>{const a=e.defineComponent({name:"VvFormTemplate",props:{schema:{type:[Array,Function],required:!0},scope:{type:Object,default:()=>({})}},setup(d,{slots:i}){const t=e.inject(r);if(t!=null&&t.formData)return()=>{var n;const s=typeof d.schema=="function"?d.schema(t,d.scope):d.schema;let l;const f=s.reduce((v,V)=>{const p=typeof V=="function"?V(t,d.scope):V,{vvIs:g,vvName:m,vvSlots:y,vvChildren:h,vvIf:b,vvElseIf:x,vvType:S,vvDefaultValue:I,vvShowValid:R,vvContent:o,...U}=p;if(b!==void 0){if(typeof b=="string"?l=!!E(Object(t.formData.value),b):typeof b=="function"?l=e.unref(b(t)):l=e.unref(b),!l)return v}else if(x!==void 0&&l!==void 0){if(l||(typeof x=="string"?l=!!E(Object(t.formData.value),x):typeof x=="function"?l=e.unref(x(t)):l=e.unref(x),!l))return v}else l=void 0;const C=h?{default:j=>e.h(a,{schema:h,scope:j})}:void 0;return m?(v.push(e.h(u,{name:m,is:g,type:S,defaultValue:I,showValid:R,props:U},y??C??o)),v):g?(v.push(e.h(g,U,y??C??o)),v):(C&&v.push(C.default(d.scope)),v)},[]);return f.push((n=i==null?void 0:i.default)==null?void 0:n.call(i,{formData:t==null?void 0:t.formData.value,submit:t==null?void 0:t.submit,validate:t==null?void 0:t.validate,errors:t==null?void 0:t.errors.value,status:t==null?void 0:t.status.value,invalid:t==null?void 0:t.invalid.value})),f}}});return a},B=(r,u={})=>{const a=Symbol(),d=Symbol(),i=Symbol(),t=H(a,d),s=T(a,d,i,u),l=Q(a,s),{VvForm:f,errors:n,status:v,invalid:V,readonly:p,formData:g,validate:m,submit:y,ignoreUpdates:h,stopUpdatesWatch:b}=P(r,a,u,l);return{VvForm:f,VvFormWrapper:t,VvFormField:s,VvFormTemplate:l,formInjectionKey:a,formWrapperInjectionKey:d,formFieldInjectionKey:i,errors:n,status:v,invalid:V,readonly:p,formData:g,validate:m,submit:y,ignoreUpdates:h,stopUpdatesWatch:b}},_=Symbol(),X=r=>{let u={};return r.schema&&(u=B(r.schema,r)),{...u,install(a,{global:d=!1}={}){a.provide(_,r),d&&(a.config.globalProperties.$vvForm=r,u!=null&&u.VvForm&&a.component("VvForm",u.VvForm),u!=null&&u.VvFormWrapper&&a.component("VvFormWrapper",u.VvFormWrapper),u!=null&&u.VvFormField&&a.component("VvFormField",u.VvFormField),u!=null&&u.VvFormTemplate&&a.component("VvFormTemplate",u.VvFormTemplate))}}},Y=(r,u={})=>e.getCurrentInstance()?B(r,{...e.inject(_,{}),...u}):B(r,u),D=(r,u={})=>B(r,u);w.FormFieldType=c,w.createForm=X,w.defaultObjectBySchema=W,w.formFactory=D,w.pluginInjectionKey=_,w.useForm=Y,Object.defineProperty(w,Symbol.toStringTag,{value:"Module"})});
@@ -1,7 +1,9 @@
1
- import { type Component, type DeepReadonly, type Ref } from 'vue';
2
- import type { z, AnyZodObject, ZodEffects, inferFormattedError } from 'zod';
3
- import type { FormFieldType, FormStatus } from './enums';
4
- export type FormSchema = AnyZodObject | ZodEffects<AnyZodObject> | ZodEffects<ZodEffects<AnyZodObject>>;
1
+ import { FormFieldType, FormStatus } from './enums';
2
+ import { IgnoredUpdater } from '@vueuse/core';
3
+ import { z } from 'zod';
4
+ import { Component, DeepReadonly, Ref, WatchStopHandle } from 'vue';
5
+
6
+ export type FormSchema = z.AnyZodObject | z.ZodEffects<z.AnyZodObject> | z.ZodEffects<z.ZodEffects<z.AnyZodObject>>;
5
7
  export type FormFieldComponentOptions = {
6
8
  lazyLoad?: boolean;
7
9
  sideEffects?: (type: `${FormFieldType}`) => Promise<void> | void;
@@ -9,11 +11,12 @@ export type FormFieldComponentOptions = {
9
11
  export type FormComponentOptions<Schema> = {
10
12
  updateThrottle?: number;
11
13
  continuosValidation?: boolean;
14
+ readonly?: boolean;
12
15
  template?: Schema extends FormSchema ? FormTemplate<Schema> : never;
13
- onUpdate?: Schema extends FormSchema ? (data: Partial<z.infer<Schema> | undefined>) => void : never;
14
- onSubmit?: Schema extends FormSchema ? (data: z.infer<Schema>) => void : never;
15
- onInvalid?: Schema extends FormSchema ? (error: inferFormattedError<Schema, string>) => void : never;
16
- onValid?: Schema extends FormSchema ? (data: z.infer<Schema>) => void : never;
16
+ onUpdate?: Schema extends FormSchema ? (data?: Partial<z.infer<Schema>>) => void : never;
17
+ onSubmit?: Schema extends FormSchema ? (data?: z.infer<Schema>) => void : never;
18
+ onInvalid?: Schema extends FormSchema ? (error?: z.inferFormattedError<Schema>) => void : never;
19
+ onValid?: Schema extends FormSchema ? (data?: z.infer<Schema>) => void : never;
17
20
  };
18
21
  export type FormComposableOptions<Schema> = FormFieldComponentOptions & FormComponentOptions<Schema>;
19
22
  type FormPluginOptionsSchema = {
@@ -23,37 +26,39 @@ export type FormPluginOptions = FormPluginOptionsSchema & FormComposableOptions<
23
26
  export type InjectedFormData<Schema extends FormSchema> = {
24
27
  formData: Ref<Partial<z.infer<Schema>> | undefined>;
25
28
  errors: Readonly<Ref<DeepReadonly<z.inferFormattedError<Schema>> | undefined>>;
26
- submit: () => boolean;
27
- validate: () => boolean;
29
+ submit: () => Promise<boolean>;
30
+ validate: () => Promise<boolean>;
31
+ ignoreUpdates: IgnoredUpdater;
32
+ stopUpdatesWatch: WatchStopHandle;
28
33
  status: Readonly<Ref<FormStatus | undefined>>;
29
34
  invalid: Readonly<Ref<boolean>>;
35
+ readonly: Ref<boolean>;
30
36
  };
31
37
  export type InjectedFormWrapperData<Schema extends FormSchema> = {
32
38
  name: Ref<string>;
33
39
  fields: Ref<Set<string>>;
34
- errors: Ref<Map<string, z.inferFormattedError<Schema, string>>>;
40
+ errors: Ref<Map<string, z.inferFormattedError<Schema>>>;
35
41
  };
36
42
  export type InjectedFormFieldData<Schema extends FormSchema> = {
37
43
  name: Ref<string>;
38
44
  errors: Readonly<Ref<DeepReadonly<z.inferFormattedError<Schema>>>>;
39
45
  };
40
46
  export type Primitive = null | undefined | string | number | boolean | symbol | bigint;
41
- type ArrayKey = number;
42
47
  type IsTuple<T extends readonly any[]> = number extends T['length'] ? false : true;
43
48
  type TupleKeys<T extends readonly any[]> = Exclude<keyof T, keyof any[]>;
44
49
  export type PathConcat<TKey extends string | number, TValue> = TValue extends Primitive ? `${TKey}` : `${TKey}` | `${TKey}.${Path<TValue>}`;
45
50
  export type Path<T> = T extends readonly (infer V)[] ? IsTuple<T> extends true ? {
46
51
  [K in TupleKeys<T>]-?: PathConcat<K & string, T[K]>;
47
- }[TupleKeys<T>] : PathConcat<ArrayKey, V> : {
52
+ }[TupleKeys<T>] : PathConcat<number, V> : {
48
53
  [K in keyof T]-?: PathConcat<K & string, T[K]>;
49
54
  }[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;
55
+ 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
56
  export type AnyBoolean<Schema extends FormSchema> = boolean | Ref<boolean> | ((data?: InjectedFormData<Schema>) => boolean | Ref<boolean>);
52
57
  export type SimpleFormTemplateItem<Schema extends FormSchema> = Record<string, any> & {
53
58
  vvIs?: string | Component;
54
59
  vvName?: Path<z.infer<Schema>>;
55
60
  vvSlots?: Record<string, any>;
56
- vvChildren?: Array<SimpleFormTemplateItem<Schema> | ((data?: InjectedFormData<Schema>) => SimpleFormTemplateItem<Schema>)>;
61
+ vvChildren?: Array<SimpleFormTemplateItem<Schema> | ((data?: InjectedFormData<Schema>, scope?: Record<string, unknown>) => SimpleFormTemplateItem<Schema>)> | ((data?: InjectedFormData<Schema>, scope?: Record<string, unknown>) => Array<SimpleFormTemplateItem<Schema> | ((data?: InjectedFormData<Schema>, scope?: Record<string, unknown>) => SimpleFormTemplateItem<Schema>)>);
57
62
  vvIf?: AnyBoolean<Schema> | Path<z.infer<Schema>>;
58
63
  vvElseIf?: AnyBoolean<Schema> | Path<z.infer<Schema>>;
59
64
  vvType?: `${FormFieldType}`;
@@ -61,6 +66,6 @@ export type SimpleFormTemplateItem<Schema extends FormSchema> = Record<string, a
61
66
  vvContent?: string;
62
67
  vvDefaultValue?: any;
63
68
  };
64
- export type FormTemplateItem<Schema extends FormSchema> = SimpleFormTemplateItem<Schema> | ((data?: InjectedFormData<Schema>) => SimpleFormTemplateItem<Schema>);
65
- export type FormTemplate<Schema extends FormSchema> = FormTemplateItem<Schema>[] | ((data?: InjectedFormData<Schema>) => FormTemplateItem<Schema>[]);
69
+ export type FormTemplateItem<Schema extends FormSchema> = SimpleFormTemplateItem<Schema> | ((data?: InjectedFormData<Schema>, scope?: Record<string, unknown>) => SimpleFormTemplateItem<Schema>);
70
+ export type FormTemplate<Schema extends FormSchema> = FormTemplateItem<Schema>[] | ((data?: InjectedFormData<Schema>, scope?: Record<string, unknown>) => FormTemplateItem<Schema>[]);
66
71
  export {};
@@ -0,0 +1,4 @@
1
+ import { FormSchema } from './types';
2
+ import { z } from 'zod';
3
+
4
+ export declare const defaultObjectBySchema: <Schema extends FormSchema>(schema: Schema, original?: Partial<z.infer<Schema>> & Record<string, unknown>) => Partial<z.infer<Schema>>;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@volverjs/form-vue",
3
3
  "description": "Vue 3 Forms with @volverjs/ui-vue",
4
- "author": "24/Consulting",
4
+ "author": "8 Wave S.r.l.",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "form",
@@ -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.10",
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": "0.0.10-beta.31",
39
+ "@vueuse/core": "^10.9.0",
40
40
  "ts-dot-prop": "^2.1.3",
41
- "vue": "^3.3.4",
42
- "zod": "^3.22.2"
41
+ "vue": "^3.4.21",
42
+ "zod": "^3.22.4"
43
43
  },
44
44
  "devDependencies": {
45
- "@playwright/experimental-ct-vue": "1.38.0",
46
- "@testing-library/vue": "^7.0.0",
47
- "@typescript-eslint/eslint-plugin": "^6.7.0",
48
- "@vitejs/plugin-vue": "^4.3.4",
49
- "@volverjs/style": "^0.1.11",
50
- "@vue/compiler-sfc": "^3.3.4",
51
- "@vue/eslint-config-typescript": "^12.0.0",
52
- "@vue/runtime-core": "^3.3.4",
53
- "@vue/test-utils": "^2.4.1",
45
+ "@nabla/vite-plugin-eslint": "^2.0.2",
46
+ "@playwright/experimental-ct-vue": "1.43.0",
47
+ "@testing-library/vue": "^8.0.3",
48
+ "@typescript-eslint/eslint-plugin": "^7.6.0",
49
+ "@vitejs/plugin-vue": "^5.0.4",
50
+ "@volverjs/style": "0.1.12-beta.15",
51
+ "@vue/compiler-sfc": "^3.4.21",
52
+ "@vue/eslint-config-typescript": "^13.0.0",
53
+ "@vue/runtime-core": "^3.4.21",
54
+ "@vue/test-utils": "^2.4.5",
54
55
  "copy": "^0.3.2",
55
- "eslint": "^8.49.0",
56
- "eslint-config-prettier": "^9.0.0",
57
- "eslint-plugin-prettier": "^5.0.0",
58
- "eslint-plugin-vue": "^9.17.0",
59
- "happy-dom": "^11.0.6",
60
- "prettier": "^3.0.3",
61
- "typescript": "^5.2.2",
62
- "vite": "^4.4.9",
63
- "vite-plugin-dts": "^3.5.3",
64
- "vite-plugin-eslint": "^1.8.1",
65
- "vite-plugin-externalize-deps": "^0.7.0",
66
- "vitest": "^0.34.4"
56
+ "eslint": "^9.0.0",
57
+ "eslint-config-prettier": "^9.1.0",
58
+ "eslint-plugin-prettier": "^5.1.3",
59
+ "eslint-plugin-vue": "^9.24.1",
60
+ "happy-dom": "^14.7.1",
61
+ "prettier": "^3.2.5",
62
+ "typescript": "^5.4.4",
63
+ "vite": "^5.2.8",
64
+ "vite-plugin-dts": "^3.8.1",
65
+ "vite-plugin-externalize-deps": "^0.8.0",
66
+ "vitest": "^1.4.0"
67
67
  },
68
68
  "typesVersions": {
69
69
  "*": {
@@ -76,7 +76,7 @@
76
76
  ".": {
77
77
  "types": "./dist/index.d.ts",
78
78
  "import": "./dist/index.es.js",
79
- "default": "./dist/index.umd.cjs"
79
+ "default": "./dist/index.umd.js"
80
80
  },
81
81
  "./src/*": "./src/*",
82
82
  "./dist/*": "./dist/*"
package/src/VvForm.ts CHANGED
@@ -4,19 +4,25 @@ 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,
10
11
  provide,
11
- readonly,
12
+ readonly as makeReadonly,
12
13
  watch,
13
14
  h,
14
15
  toRaw,
15
16
  isProxy,
16
17
  computed,
18
+ onMounted,
17
19
  } from 'vue'
18
- import { watchThrottled } from '@vueuse/core'
19
- import type { z, ZodFormattedError, TypeOf } from 'zod'
20
+ import {
21
+ watchIgnorable,
22
+ throttleFilter,
23
+ type IgnoredUpdater,
24
+ } from '@vueuse/core'
25
+ import { type z } from 'zod'
20
26
  import type {
21
27
  FormComponentOptions,
22
28
  FormSchema,
@@ -34,36 +40,95 @@ export const defineForm = <Schema extends FormSchema>(
34
40
  ) => {
35
41
  const errors = ref<z.inferFormattedError<Schema> | undefined>()
36
42
  const status = ref<FormStatus | undefined>()
43
+ const invalid = computed(() => status.value === FormStatus.invalid)
37
44
  const formData = ref<Partial<z.infer<Schema> | undefined>>()
45
+ const readonly = ref<boolean>(false)
46
+
47
+ const validate = async (value = formData.value) => {
48
+ if (readonly.value) {
49
+ return true
50
+ }
51
+ const parseResult = await schema.safeParseAsync(value)
52
+ if (!parseResult.success) {
53
+ errors.value =
54
+ parseResult.error.format() as z.inferFormattedError<Schema>
55
+ status.value = FormStatus.invalid
56
+ return false
57
+ }
58
+ errors.value = undefined
59
+ status.value = FormStatus.valid
60
+ formData.value = parseResult.data
61
+ return true
62
+ }
63
+
64
+ const submit = async () => {
65
+ if (readonly.value) {
66
+ return false
67
+ }
68
+ if (!(await validate())) {
69
+ return false
70
+ }
71
+ status.value = FormStatus.submitting
72
+ return true
73
+ }
74
+
75
+ const { ignoreUpdates, stop: stopUpdatesWatch } = watchIgnorable(
76
+ formData,
77
+ () => {
78
+ status.value = FormStatus.updated
79
+ },
80
+ {
81
+ deep: true,
82
+ eventFilter: throttleFilter(options?.updateThrottle ?? 500),
83
+ },
84
+ )
85
+
38
86
  const component = defineComponent({
39
- name: 'FormComponent',
87
+ name: 'VvForm',
40
88
  props: {
89
+ continuosValidation: {
90
+ type: Boolean,
91
+ default: false,
92
+ },
41
93
  modelValue: {
42
94
  type: Object,
43
95
  default: () => ({}),
44
96
  },
45
- updateThrottle: {
46
- type: Number,
47
- default: 500,
48
- },
49
- continuosValidation: {
97
+ readonly: {
50
98
  type: Boolean,
51
- default: false,
99
+ default: options?.readonly ?? false,
100
+ },
101
+ tag: {
102
+ type: String,
103
+ default: 'form',
52
104
  },
53
105
  template: {
54
106
  type: [Array, Function] as PropType<FormTemplate<Schema>>,
55
107
  default: undefined,
56
108
  },
57
109
  },
58
- emits: ['invalid', 'valid', 'submit', 'update:modelValue'],
59
- expose: ['submit', 'validate', 'errors', 'status', 'valid', 'invalid'],
110
+ emits: [
111
+ 'invalid',
112
+ 'valid',
113
+ 'submit',
114
+ 'update:modelValue',
115
+ 'update:readonly',
116
+ ],
117
+ expose: [
118
+ 'submit',
119
+ 'validate',
120
+ 'errors',
121
+ 'status',
122
+ 'valid',
123
+ 'invalid',
124
+ 'readonly',
125
+ ],
60
126
  setup(props, { emit }) {
61
127
  formData.value = defaultObjectBySchema(
62
128
  schema,
63
129
  toRaw(props.modelValue),
64
130
  )
65
131
 
66
- // clone modelValue and update formData
67
132
  watch(
68
133
  () => props.modelValue,
69
134
  (newValue) => {
@@ -71,6 +136,14 @@ export const defineForm = <Schema extends FormSchema>(
71
136
  const original = isProxy(newValue)
72
137
  ? toRaw(newValue)
73
138
  : newValue
139
+
140
+ if (
141
+ JSON.stringify(original) ===
142
+ JSON.stringify(toRaw(formData.value))
143
+ ) {
144
+ return
145
+ }
146
+
74
147
  formData.value =
75
148
  typeof original?.clone === 'function'
76
149
  ? original.clone()
@@ -80,84 +153,88 @@ export const defineForm = <Schema extends FormSchema>(
80
153
  { deep: true },
81
154
  )
82
155
 
83
- // emit update:modelValue on formData change
84
- watchThrottled(
85
- formData,
86
- (newValue) => {
156
+ watch(status, async (newValue) => {
157
+ if (newValue === FormStatus.invalid) {
158
+ const toReturn = toRaw(errors.value)
159
+ emit('invalid', toReturn)
160
+ options?.onInvalid?.(toReturn)
161
+ return
162
+ }
163
+ if (newValue === FormStatus.valid) {
164
+ const toReturn = toRaw(formData.value)
165
+ emit('valid', toReturn)
166
+ options?.onValid?.(toReturn)
167
+ emit('update:modelValue', toReturn)
168
+ options?.onUpdate?.(toReturn)
169
+ return
170
+ }
171
+ if (newValue === FormStatus.submitting) {
172
+ const toReturn = toRaw(formData.value)
173
+ emit('submit', toReturn)
174
+ options?.onSubmit?.(toReturn)
175
+ }
176
+ if (newValue === FormStatus.updated) {
87
177
  if (
88
- (errors.value || options?.continuosValidation) ??
178
+ errors.value ||
179
+ options?.continuosValidation ||
89
180
  props.continuosValidation
90
181
  ) {
91
- validate()
182
+ await validate()
92
183
  }
93
184
  if (
94
- !newValue ||
185
+ !formData.value ||
95
186
  !props.modelValue ||
96
- JSON.stringify(newValue) !==
187
+ JSON.stringify(formData.value) !==
97
188
  JSON.stringify(props.modelValue)
98
189
  ) {
99
- emit('update:modelValue', newValue)
100
- options?.onUpdate?.(toRaw(newValue))
190
+ const toReturn = toRaw(formData.value)
191
+ emit('update:modelValue', toReturn)
192
+ options?.onUpdate?.(toReturn)
193
+ }
194
+ if (status.value === FormStatus.updated) {
195
+ status.value = FormStatus.unknown
101
196
  }
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
197
  }
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
- }
198
+ })
131
199
 
132
- // emit submit event if form is valid
133
- const submit = () => {
134
- if (!validate()) {
135
- return false
200
+ // readonly
201
+ onMounted(() => {
202
+ readonly.value = props.readonly
203
+ })
204
+ watch(
205
+ () => props.readonly,
206
+ (newValue) => {
207
+ readonly.value = newValue
208
+ },
209
+ )
210
+ watch(readonly, (newValue) => {
211
+ if (newValue !== props.readonly) {
212
+ emit('update:readonly', readonly.value)
136
213
  }
137
- emit('submit', formData.value as z.infer<Schema>)
138
- options?.onSubmit?.(toRaw(formData.value) as z.infer<Schema>)
139
- return true
140
- }
141
-
142
- const invalid = computed(() => status.value === FormStatus.invalid)
214
+ })
143
215
 
144
- // provide data to children
145
216
  provide(provideKey, {
146
217
  formData,
147
218
  submit,
148
219
  validate,
149
- errors: readonly(errors),
150
- status: readonly(status),
220
+ ignoreUpdates,
221
+ stopUpdatesWatch,
222
+ errors: makeReadonly(errors),
223
+ status: makeReadonly(status),
151
224
  invalid,
225
+ readonly,
152
226
  })
153
227
 
154
228
  return {
155
229
  formData,
156
230
  submit,
157
231
  validate,
158
- errors: readonly(errors),
159
- status: readonly(status),
232
+ ignoreUpdates,
233
+ stopUpdatesWatch,
234
+ errors: makeReadonly(errors),
235
+ status: makeReadonly(status),
160
236
  invalid,
237
+ isReadonly: readonly,
161
238
  }
162
239
  },
163
240
  render() {
@@ -166,12 +243,15 @@ export const defineForm = <Schema extends FormSchema>(
166
243
  formData: this.formData,
167
244
  submit: this.submit,
168
245
  validate: this.validate,
246
+ ignoreUpdates: this.ignoreUpdates,
247
+ stopUpdatesWatch: this.stopUpdatesWatch,
169
248
  errors: this.errors,
170
249
  status: this.status,
171
250
  invalid: this.invalid,
251
+ readonly: this.isReadonly,
172
252
  }) ?? this.$slots.default
173
253
  return h(
174
- 'form',
254
+ this.tag,
175
255
  {
176
256
  onSubmit: withModifiers(this.submit, ['prevent']),
177
257
  },
@@ -186,17 +266,23 @@ export const defineForm = <Schema extends FormSchema>(
186
266
  default: defaultSlot,
187
267
  },
188
268
  ),
189
- ]
269
+ ]
190
270
  : {
191
271
  default: defaultSlot,
192
- },
272
+ },
193
273
  )
194
274
  },
195
275
  })
196
276
  return {
197
277
  errors,
198
278
  status,
279
+ invalid,
280
+ readonly,
199
281
  formData,
282
+ validate,
283
+ submit,
284
+ ignoreUpdates,
285
+ stopUpdatesWatch,
200
286
  /**
201
287
  * An hack to add types to the default slot
202
288
  */
@@ -205,17 +291,20 @@ export const defineForm = <Schema extends FormSchema>(
205
291
  $slots: {
206
292
  default: (_: {
207
293
  formData: unknown extends
208
- | Partial<TypeOf<Schema>>
294
+ | Partial<z.TypeOf<Schema>>
209
295
  | undefined
210
296
  ? undefined
211
- : Partial<TypeOf<Schema>> | undefined
212
- submit: () => boolean
213
- validate: () => boolean
297
+ : Partial<z.TypeOf<Schema>> | undefined
298
+ submit: () => Promise<boolean>
299
+ validate: () => Promise<boolean>
300
+ ignoreUpdates: IgnoredUpdater
301
+ stopUpdatesWatch: WatchStopHandle
214
302
  errors: Readonly<
215
303
  Ref<DeepReadonly<z.inferFormattedError<Schema>>>
216
304
  >
217
305
  status: Ref<DeepReadonly<`${FormStatus}` | undefined>>
218
306
  invalid: Ref<DeepReadonly<boolean>>
307
+ readonly: Ref<boolean>
219
308
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
220
309
  }) => any
221
310
  }
@@ -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}`>,
@@ -81,6 +81,10 @@ export const defineFormField = <Schema extends FormSchema>(
81
81
  type: Boolean,
82
82
  default: false,
83
83
  },
84
+ readonly: {
85
+ type: Boolean,
86
+ default: undefined,
87
+ },
84
88
  },
85
89
  emits: ['invalid', 'valid', 'update:formData', 'update:modelValue'],
86
90
  expose: ['invalid', 'invalidLabel', 'errors'],
@@ -186,6 +190,12 @@ export const defineFormField = <Schema extends FormSchema>(
186
190
  {},
187
191
  )
188
192
  })
193
+ const isReadonly = computed(() => {
194
+ if (injectedFormData?.readonly.value) {
195
+ return true
196
+ }
197
+ return hasFieldProps.value.readonly ?? props.readonly
198
+ })
189
199
  const hasProps = computed(() => ({
190
200
  ...hasFieldProps.value,
191
201
  name: hasFieldProps.value.name ?? props.name,
@@ -217,14 +227,17 @@ export const defineFormField = <Schema extends FormSchema>(
217
227
  })(props.type as FormFieldType),
218
228
  invalidLabel: invalidLabel.value,
219
229
  modelValue: modelValue.value,
230
+ readonly: isReadonly.value,
220
231
  'onUpdate:modelValue': onUpdate,
221
232
  }))
222
233
 
234
+ // provide data to children
223
235
  provide(formFieldInjectionKey, {
224
236
  name: readonly(fieldName as Ref<string>),
225
237
  errors: readonly(errors),
226
238
  })
227
239
 
240
+ // load component
228
241
  const component = computed(() => {
229
242
  if (props.type === FormFieldType.custom) {
230
243
  return {
@@ -240,6 +253,7 @@ export const defineFormField = <Schema extends FormSchema>(
240
253
  formData: injectedFormData?.formData.value,
241
254
  formErrors: injectedFormData?.errors.value,
242
255
  errors: errors.value,
256
+ readonly: isReadonly.value,
243
257
  }) ?? slots.defalut
244
258
  )
245
259
  },