{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "auto-form", "title": "Protoform AutoForm", "description": "React Hook Form-first AutoForm with the complete native form API plus Protoform rendering and protobuf helpers.", "dependencies": [ "react-hook-form" ], "registryDependencies": [ "@protoform/auto-form-core", "@protoform/use-proto-form" ], "files": [ { "path": "registry/base-nova/protoform/components/auto-form/index.tsx", "content": "'use client';\n\nimport { createProtoResolver } from '../../hooks/use-proto-form';\nimport type { Resolver, UseFormProps, UseFormReturn } from 'react-hook-form';\n\nimport { ReactHookFormEngine } from './adapters/react-hook-form';\nimport { AutoFormCore } from './auto-form-core';\nimport { isProtoMessageDescriptor, isProtoProvider } from './proto';\nimport { protoConversionOptionsFromFieldConfig } from './schema';\nimport type { AutoFormProps as BaseAutoFormProps } from './types';\n\ntype FormValues = Record;\n\nexport type AutoFormProps<\n T extends FormValues = FormValues,\n TCustomFieldType extends string = never,\n> = BaseAutoFormProps<\n T,\n UseFormReturn,\n UseFormProps,\n Resolver,\n TCustomFieldType\n>;\n\nexport {\n compileCelExpression,\n DEFAULT_CEL_MAX_COST,\n type CelEvaluation,\n type CompileCelExpressionOptions,\n type CompiledCelExpression,\n} from './cel-runtime';\nexport { useAutoForm } from './context';\nexport {\n inspectAutoFormConfiguration,\n type AutoFormConfigurationDiagnostic,\n type AutoFormConfigurationDiagnosticCode,\n type InspectAutoFormConfigurationInput,\n} from './configuration';\nexport { defaultRegistry } from './fields';\nexport { defaultClassifyField } from './helpers';\nexport { type FieldMatchContext, type FieldTypeDefinition, FieldTypeRegistry } from './registry';\nexport { AutoFormSlot } from './slot';\nexport type {\n AutoFormMode,\n AutoFormRevalidationMode,\n AutoFormRootHeaderMetadata,\n AutoFormRootHeaderMode,\n AutoFormStep,\n AutoFormStepperConfig,\n AutoFormStepperOrientation,\n AutoFormSubmitContext,\n AutoFormValidationMode,\n BuiltInFieldType,\n DeprecatedFieldPolicy,\n FieldTypes,\n} from './types';\nexport { ShadcnAutoFormFieldComponents } from './auto-form-core';\nexport type { AutoFormFieldComponents, AutoFormFieldProps } from './core-types';\nexport type { AutoFormEngineHandle } from './engine';\n\nexport function AutoForm<\n T extends FormValues = FormValues,\n TCustomFieldType extends string = never,\n>({\n formOptions,\n resolver,\n ...props\n}: AutoFormProps) {\n const protoDescriptor = isProtoMessageDescriptor(props.schema)\n ? props.schema\n : isProtoProvider(props.schema)\n ? props.schema.getMessageDescriptor()\n : undefined;\n const conversionOptions = protoConversionOptionsFromFieldConfig(props.fieldConfig);\n const resolvedResolver =\n resolver ??\n (protoDescriptor\n ? (createProtoResolver(protoDescriptor, conversionOptions) as unknown as Resolver)\n : undefined);\n const engineOptions: UseFormProps = {\n ...(formOptions ?? {}),\n ...(props.validationMode\n ? {\n mode:\n props.validationMode === 'change'\n ? 'onChange'\n : props.validationMode === 'blur'\n ? 'onBlur'\n : 'onSubmit',\n }\n : {}),\n ...(props.revalidationMode\n ? {\n reValidateMode:\n props.revalidationMode === 'change' ? 'onChange' : 'onBlur',\n }\n : {}),\n };\n\n return (\n , TCustomFieldType>\n {...props}\n renderEngine={({ children, defaultValues, values }) => (\n \n defaultValues={defaultValues}\n formOptions={engineOptions}\n onDirtyChange={props.onDirtyChange}\n resolver={resolvedResolver}\n values={values}\n >\n {children}\n \n )}\n />\n );\n}\n", "type": "registry:component" }, { "path": "registry/base-nova/protoform/components/auto-form/adapters/react-hook-form.tsx", "content": "'use client';\n\nimport React from 'react';\nimport {\n FormProvider,\n type Resolver,\n type UseFormProps,\n type UseFormReturn,\n useController,\n useFieldArray,\n useForm,\n useFormContext,\n useWatch,\n} from 'react-hook-form';\n\nimport { getPathInObject } from '../field-utils';\nimport {\n type AutoFormArrayController,\n type AutoFormEngine,\n AutoFormEngineProvider,\n type AutoFormFieldController,\n useDirtyStateNotification,\n} from '../engine';\nimport { getRootErrorMessage } from '../helpers';\nimport { PROTO_FORM_ROOT_ERROR_KEY } from '../proto';\n\ntype FormValues = Record;\n\nfunction ReactHookFormFieldController({\n children,\n name,\n}: {\n children: (controller: AutoFormFieldController) => React.ReactNode;\n name: string;\n}) {\n const form = useFormContext();\n const { field, fieldState } = useController({ name });\n const messages = [\n fieldState.error?.message,\n ...Object.values(fieldState.error?.types ?? {}),\n ].filter((message): message is string => typeof message === 'string');\n\n return children({\n errors: [...new Set(messages)],\n name: field.name,\n onBlur: field.onBlur,\n onChange: (value, options) => {\n if (options) {\n form.setValue(name, value, options);\n return;\n }\n field.onChange(value);\n },\n ref: (element) => field.ref(element),\n value: field.value,\n });\n}\n\nfunction ReactHookFormArrayController({\n children,\n name,\n}: {\n children: (controller: AutoFormArrayController) => React.ReactNode;\n name: string;\n}) {\n const form = useFormContext();\n const { append, fields, remove } = useFieldArray({\n control: form.control,\n name: name as never,\n });\n const values = getPathInObject(form.getValues(), name.split('.'));\n const items = fields.map((field, index) => ({\n key: field.id,\n value: Array.isArray(values) ? values[index] : undefined,\n }));\n\n return children({\n append: (value) => append(value as never),\n items,\n remove,\n });\n}\n\nfunction applyValidationErrors(\n form: UseFormReturn,\n errors: Array<{ message: string; path: Array }>\n) {\n form.clearErrors();\n const rootMessages: string[] = [];\n let shouldFocus = true;\n\n for (const error of errors) {\n if (error.path.length === 0) {\n rootMessages.push(error.message);\n continue;\n }\n\n form.setError(\n error.path.join('.'),\n { message: error.message, type: 'validation' },\n { shouldFocus }\n );\n shouldFocus = false;\n }\n\n if (rootMessages.length > 0) {\n form.setError('root', {\n message: rootMessages.join('\\n'),\n type: 'validation',\n });\n }\n}\n\nexport type ReactHookFormEngineProps = {\n children: (engine: AutoFormEngine) => React.ReactNode;\n defaultValues: FormValues;\n formOptions?: UseFormProps;\n resolver?: Resolver;\n values?: FormValues;\n onDirtyChange?: (isDirty: boolean) => void;\n};\n\nexport function ReactHookFormEngine({\n children,\n defaultValues,\n formOptions,\n resolver,\n values,\n onDirtyChange,\n}: ReactHookFormEngineProps) {\n const form = useForm({\n ...(formOptions ?? {}),\n defaultValues,\n resolver,\n values,\n });\n const watchedValues = (useWatch({ control: form.control }) as FormValues | undefined) ?? {};\n const errors = form.formState.errors as Record;\n const rootError =\n getRootErrorMessage(form.formState.errors.root) ||\n getRootErrorMessage(errors[PROTO_FORM_ROOT_ERROR_KEY]);\n const notifyDirtyChange = useDirtyStateNotification(form.formState.isDirty, onDirtyChange);\n\n const engine: AutoFormEngine = {\n ArrayController: ReactHookFormArrayController,\n FieldController: ReactHookFormFieldController,\n clearErrors: (paths) => form.clearErrors(paths),\n defaultValues: form.formState.defaultValues,\n dirtyFields: form.formState.dirtyFields,\n errors,\n focus: (path) => form.setFocus(path),\n getFieldInvalid: (path) => form.getFieldState(path).invalid,\n getValues: form.getValues,\n handleSubmit: (onValid) => form.handleSubmit(onValid),\n isDirty: form.formState.isDirty,\n isSubmitting: form.formState.isSubmitting,\n markClean: () => {\n form.reset(form.getValues());\n notifyDirtyChange(false);\n },\n nativeForm: form,\n reset: (nextValues, options) => form.reset(nextValues, options),\n rootError,\n setRootError: (message) => form.setError('root', { message, type: 'submit' }),\n setValidationErrors: (validationErrors) => applyValidationErrors(form, validationErrors),\n setValue: (path, value, options) => form.setValue(path, value, options),\n trigger: async (paths) => form.trigger(paths),\n validatesSchema: Boolean(resolver),\n values: watchedValues,\n };\n\n return (\n \n {children(engine)}\n \n );\n}\n", "type": "registry:component" } ], "type": "registry:block" }