zod-auto-form
Version:
Auto-generate typed React forms from Zod schemas
3 lines (2 loc) • 11.6 kB
JavaScript
import{jsx as e,jsxs as r}from"react/jsx-runtime";import t,{createContext as a,useContext as n,useState as i,useEffect as l,useRef as o,useMemo as s,useCallback as u}from"react";import{z as d}from"zod";import{ZodFormClassed as c,extractZodErrorMessages as p}from"zod-helper";import{createFormHookContexts as m,createFormHook as f}from"@tanstack/react-form";const y={Input:()=>null,Select:()=>null,MultiSelect:()=>null,RadioGroup:()=>null,NumberInput:()=>null,Checkbox:()=>null,Switch:()=>null,TextArea:()=>null,DateTimePicker:()=>null,ObjectWrapper:()=>null,ArrayWrapper:()=>null,Form:()=>null,SubmitButton:()=>null,GroupedFieldsWrapper:()=>null,ArrayInputFieldWrapper:()=>null,arrayString:()=>null,arrayNumber:()=>null,arrayBoolean:()=>null,arrayDate:()=>null},h=a(y),useComponentContext=()=>n(h),AutoFormComponentsProvider=({children:r,components:t})=>{const a={...y,...t};return e(h.Provider,{value:a,children:r})};function GroupedFieldsWrapper({children:r,groupName:t}){const{GroupedFieldsWrapper:a}=useComponentContext();return e(a,{groupName:t,children:r})}const generateRandomKey=()=>"undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID?crypto.randomUUID():Math.random().toString(36).substring(2,15)+Math.random().toString(36).substring(2,15);function generateEmptyData(e){const r={};for(const t of e)switch(t.type){case"string":r[t.name]=t.defaultValue||"";break;case"boolean":r[t.name]=t.defaultValue||!1;break;case"number":r[t.name]=Number(t.defaultValue??0)||0;break;case"object":r[t.name]=generateEmptyData(t.fields||[]);break;case"array":r[t.name]=t.defaultValue||[];break;default:r[t.name]=t.defaultValue||null}return r}function getArrayFieldError(e,r,t){if(!Array.isArray(e))return;const a=`${r}[${t}]`,findErrorDeep=e=>{if("object"==typeof e&&null!==e)for(const r in e){if(r===a){const t=e[r];if(Array.isArray(t)&&t[0]?.message)return t[0].message}const t=e[r],n=findErrorDeep(t);if(n)return n}};for(const r of e){const e=findErrorDeep(r);if(e)return e}}const{fieldContext:g,formContext:b,useFieldContext:F,useFormContext:A}=m(),v={FieldComponent:function GenericFormField({componentKey:r,...t}){const a=F(),n=useComponentContext()[r],i=a.state.meta.isTouched&&a.state.meta.errors.length>0;return e(n,{id:a.name,onChange:a.handleChange,value:a.state.value,errorMessages:a.state.meta.errors,isError:i,onBlur:a.handleBlur,...t})},ArrayField:function ArrayFieldForArrayOfObjects({children:e}){const r=F(),[t,a]=i([]),n=o([]),s=o([]);return l((()=>{s.current=t}),[t]),l((()=>{const e=r.state.value;if(!Array.isArray(e))return;if(JSON.stringify(e)===JSON.stringify(n.current))return;const t=e.map(((e,r)=>{const t=s.current[r],a=t?.j1sjhghgtytfjiu971||generateRandomKey();return"object"!=typeof e||null===e||e instanceof Date?{j1sjhghgtytfjiu971:a,value:e}:{j1sjhghgtytfjiu971:a,...e}}));a(t),n.current=e}),[r.state.value]),e({fields:t,append:e=>{const i=generateEmptyData(e),l="object"!=typeof i||null===i||i instanceof Date?{j1sjhghgtytfjiu971:generateRandomKey(),value:i}:{j1sjhghgtytfjiu971:generateRandomKey(),...i},o=[...t,l];a(o);const s=o.map((e=>"value"in e?e.value:e));r.handleChange(s),n.current=s},remove:e=>{const i=[...t];i.splice(e,1),a(i);const l=i.map((e=>"value"in e?e.value:e));r.handleChange(l),n.current=l}})},ArrayFieldForArrayOfStrings:function ArrayFieldForArrayOfStrings({children:e}){const r=F(),[t,a]=i([]);return l((()=>{if(Array.isArray(r.state.value)){const e=r.state.value.map((e=>"object"!=typeof e||null===e||e instanceof Date?{j1sjhghgtytfjiu971:generateRandomKey(),value:e}:{j1sjhghgtytfjiu971:generateRandomKey(),...e}));a(e)}}),[r.state.value]),e({fields:t,append:e=>{const n="object"!=typeof e||null===e||e instanceof Date?{j1sjhghgtytfjiu971:generateRandomKey(),value:e}:{j1sjhghgtytfjiu971:generateRandomKey(),...e},i=[...t,n];a(i),r.handleChange(i.map((e=>"value"in e?e.value:e)))},remove:e=>{const n=[...t];n.splice(e,1),a(n),r.handleChange(n.map((e=>"value"in e?e.value:e)))}})},ObjectWrapper:function ObjectWrapper({children:r,title:t,description:a,label:n,required:i}){const{ObjectWrapper:l}=useComponentContext();return e(l,{title:t,description:a,label:n,required:i,children:r})},ArrayWrapper:function ArrayWrapper({children:r,label:t,title:a,description:n,onAddField:i,onRemoveField:l}){const{ArrayWrapper:o}=useComponentContext();return e(o,{label:t,title:a,description:n,onAddField:i,onRemoveField:l,children:r})},GroupedFieldsWrapper:GroupedFieldsWrapper,ArrayInputFieldWrapper:function ArrayInputFieldWrapper({label:r,title:t,description:a,onAddField:n,onRemoveField:o,disabled:s,placeholder:u,fields:d,componentKey:c,all_errors:p,path:m}){const f=F(),[y,h]=i([]),[g,b]=i({});l((()=>{Array.isArray(f.state.value)&&h(f.state.value)}),[f.state.value]);const{ArrayInputFieldWrapper:A,...v}=useComponentContext(),j=v[c],getFieldValue=(e,r,t)=>{let a;return a=void 0!==e?.value?e.value:t[r],a instanceof Date||void 0!==a?a:""};return l((()=>{p?.form?.errors&&b({})}),[p?.form?.errors]),e(A,{onAddField:n,onRemoveField:o,description:a,label:r,title:t,children:d.map(((n,i)=>{const l=getArrayFieldError(p?.form?.errors,m??"",i)&&!g[i];return e(j,{id:n.id,value:getFieldValue(n,i,y),onChange:e=>{((e,r)=>{const t=[...y];t[e]=r,h(t),f.handleChange(t)})(i,e),b((e=>({...e,[i]:!0})))},onBlur:()=>{f.handleChange(y)},errorMessages:l?[{message:getArrayFieldError(p?.form?.errors,m??"",i)||""}]:[],isError:!!l,label:r,title:t,description:a,disabled:s,placeholder:u},n.id??i)}))})}},{useAppForm:j,withForm:S}=f({fieldComponents:v,formComponents:{},fieldContext:g,formContext:b}),shouldApplyRule=(e,r)=>{const t=e.sourceFields.map((e=>{return t=r,a=e,(Array.isArray(a)?a:a.replace(/\.\[/g,"[").replace(/\[(\d+)\]/g,".$1").split(".").filter(Boolean)).reduce(((e,r)=>{if(null!=e&&"object"==typeof e){if(Array.isArray(e)){const t=Number(r);return Number.isInteger(t)&&t>=0&&t<e.length?e[t]:void 0}if(r in e)return e[r]}}),t);var t,a})),a=Object.fromEntries(e.sourceFields.map(((e,r)=>[Array.isArray(e)?e.join("."):e,t[r]])));return e.when(a)},cleanPath=e=>e.replace(/\.\[/g,"[").replace(/\[(\d+)\]/g,".$1"),mergeDefaultValues=(e,r)=>{const t={...r};for(const[r,a]of Object.entries(e))a&&"object"==typeof a&&!Array.isArray(a)&&t[r]&&"object"==typeof t[r]&&!Array.isArray(t[r])?t[r]=mergeDefaultValues(a,t[r]):t[r]=a;return t};function removeNullValues(e){if(Array.isArray(e)){return e.map((e=>removeNullValues(e))).filter((e=>null!=e))}if(null!=e&&"object"==typeof e){const r=Object.entries(e).filter((([,e])=>null!=e)).map((([e,r])=>[e,removeNullValues(r)]));return Array.isArray(e)?[]:Object.fromEntries(r)}return e}const C=Object.getPrototypeOf((async()=>{})).constructor;function useFormData({schema:e,changePositionOfFields:r,fieldTypeOverrides:t,groupByZodKeys:a,visibilityRules:n,defaultValues:l,onSubmit:m,onSubmitError:f,onValuesChange:y,isRemoveNullValues:h}){const[g,b]=i(0),[F,A]=i(!1),v=o(!1),S=s((()=>{const n=new c(e);return r&&n.changeJsonFieldPositions(r),t&&n.overrideFieldTypes(t),a&&n.groupByZodKeys(a),n}),[e,t,r,a]),O=s((()=>mergeDefaultValues(l||{},S.getZodDefaultValues()||{})),[l,S]),D=u((r=>{A(!0);try{if(!m)throw new Error("onSubmit is required");v.current=!0;let t=r.value;h&&(t=removeNullValues(t));const a=e.parse(t);return!function isAsync(e){return e instanceof C}(m)?(m?.(a),A(!1)):m(a).then((()=>{A(!1)})).catch((e=>{A(!1)})),a}catch(e){throw e instanceof d.ZodError&&f?.(e,p(e.errors),r.value),A(!1),e}}),[e,m,f,h]),V=j({defaultState:{values:O},onSubmit:D,onSubmitInvalid:e=>{f?.(e.formApi.state.errors,p(e.formApi.state.errors),e.value)},validators:{onSubmit:e,onChange:e=>{n?.length&&b((e=>e+1)),y&&y(e.value)}}}),I=u(((e,r)=>{if(!n?.length)return{hidden:!1,disabled:!1};const t=n.filter((r=>Array.isArray(r.targetField)?r.targetField.some((r=>cleanPath(r)===cleanPath(e))):cleanPath(r.targetField)===cleanPath(e)));if(!t.length)return{hidden:!1,disabled:!1};const a=t.some((e=>"hide"===e.type&&shouldApplyRule(e,r))),i=t.some((e=>"disable"===e.type&&shouldApplyRule(e,r))),l=t.some((e=>"show"===e.type&&shouldApplyRule(e,r)));return{hidden:a||!l&&t.some((e=>"show"===e.type)),disabled:i}}),[n]);return{zodFormData:S,formVersion:g,setFormVersion:b,mergedDefaultValues:O,formSubmittedRef:v,handleSubmit:D,appForm:V,getFieldVisibilityState:I,isLoading:F,all_errors:V.getAllErrors()}}function getComponentKey(e){switch(e){case"string":default:return"Input";case"number":return"NumberInput";case"boolean":return"Checkbox";case"select":return"Select";case"multi-select":return"MultiSelect";case"date":return"DateTimePicker";case"textarea":return"TextArea";case"switch":return"Switch";case"radio":return"RadioGroup";case"array-string":return"arrayString";case"array-number":return"arrayNumber";case"array-boolean":return"arrayBoolean";case"array-date":return"arrayDate"}}const RenderField=({field:r,fieldPath:a,getFieldVisibilityState:n,form:i,all_errors:l})=>{const o=a?`${a}.${r.name}`:r.name,{hidden:s,disabled:u}=n(o,i.state.values);if(s)return null;const d={label:r.label,placeholder:r.placeholder,title:r.title,description:r.description,disabled:u,required:r.required,selectOptions:r?.selectOptions};return e(i.AppField,{name:o,children:a=>{const s=e(a.FieldComponent,{componentKey:getComponentKey(r.type),...d,..."multi-select"===r.type?{value:Array.isArray(a.state.value)?a.state.value:[]}:{}});return"object"===r.type?e(a.ObjectWrapper,{label:r.label,title:r.title,description:r.description,required:r.required,children:r.fields.map((e=>RenderField({field:e,fieldPath:o,getFieldVisibilityState:n,form:i,all_errors:l})))}):"array"===r.type?e(a.ArrayField,{children:({fields:s,append:u,remove:d})=>e(a.ArrayWrapper,{label:r.label,title:r.title,description:r.description,onAddField:()=>{u(r.fields)},onRemoveField:e=>d(e),children:s.map(((a,s)=>e(t.Fragment,{children:r.fields?.map((e=>RenderField({field:e,fieldPath:`${o}[${s}]`,getFieldVisibilityState:n,form:i,all_errors:l})))},a.j1sjhghgtytfjiu971)))})}):r.type.includes("array-")?e(a.ArrayFieldForArrayOfStrings,{children:({fields:t,append:n,remove:i})=>e(a.ArrayInputFieldWrapper,{path:o,name:r.name,componentKey:getComponentKey(r.type),label:r.label,title:r.title,description:r.description,disabled:u,required:r.required,placeholder:r.placeholder,selectOptions:r?.selectOptions,all_errors:l,onAddField:()=>n({value:"array-date"===r.type?new Date:"array-boolean"!==r.type&&("array-string"===r.type?"":"array-number"===r.type?0:"")}),onRemoveField:e=>i(e),fields:t})}):s}},r.name)};function AutoForm(t){const{zodFormData:a,appForm:n,getFieldVisibilityState:i,isLoading:l,all_errors:o}=useFormData(t),{SubmitButton:s,Form:u}=useComponentContext();if(!u)throw new Error("Please provide a SubmitButton and Form component and all the components are required");return a.getGroupedFields&&a.getGroupedFields.length>0?r(u,{formId:t.formId,onSubmit:e=>{e.preventDefault(),n.handleSubmit()},children:[a.getGroupedFields.map((r=>e(GroupedFieldsWrapper,{groupName:r.name,children:r.fields.map((e=>RenderField({field:e,fieldPath:"",getFieldVisibilityState:i,form:n,all_errors:o})))},r.name))),!t.hideSubmitButton&&e(s,{isLoading:t.isLoadingButton||l,formId:t.formId,children:t?.submitButtonText||"Submit"})]}):r(u,{formId:t.formId,onSubmit:e=>{e.preventDefault(),n.handleSubmit()},children:[a.getInputFields.map((r=>e(RenderField,{field:r,fieldPath:"",getFieldVisibilityState:i,form:n,all_errors:o},r.name))),!t.hideSubmitButton&&e(s,{isLoading:t.isLoadingButton||l,formId:t.formId,children:t?.submitButtonText||"Submit"})]})}const ComponentRegistry=e=>e;export{AutoForm,AutoFormComponentsProvider,ComponentRegistry};
//# sourceMappingURL=index.mjs.map