{ "$schema": "https://ui.shadcn.com/schema/registry-item.json", "name": "use-proto-form-tanstack-v2", "title": "Experimental useProtoForm for TanStack Form v2", "description": "Experimental TanStack Form v2 alpha adapter with the native atom, validator-array, and schema-output APIs plus protobuf messages, update masks, and oneofs.", "dependencies": [ "@bufbuild/protobuf", "@tanstack/react-form-v2@npm:@tanstack/react-form@2.0.0-alpha.0" ], "registryDependencies": [ "@protoform/protobuf-provider" ], "files": [ { "path": "registry/base-nova/protoform/hooks/use-proto-form-tanstack-v2/index.ts", "content": "'use client';\n\nimport {\n isMessage,\n type DescMessage,\n type MessageShape,\n type MessageValidType,\n} from '@bufbuild/protobuf';\nimport type { FieldMask } from '@bufbuild/protobuf/wkt';\nimport {\n type DeepKeys,\n type DefaultReactFormComponentMap,\n type FieldUpdateOptions,\n type FormOptions,\n type FormValidators,\n type ReactFormApi,\n type StandardSchemaV1,\n type ToFormErrorTypes,\n useForm,\n} from '@tanstack/react-form-v2';\nimport {\n createUpdateMask as createDirtyUpdateMask,\n createProtoFormSchema,\n dirtyFieldsFromValues,\n formValuesToProto,\n type ProtoConversionOptions,\n} from '../../lib/protobuf-provider';\n\ntype FormValues = Record;\n\ntype ProtoFormValidator = {\n run: StandardSchemaV1>;\n triggers: [];\n};\n\ntype ProtoFormValidators<\n Values extends FormValues,\n Desc extends DescMessage,\n TValidators extends FormValidators,\n> = readonly [...TValidators, ProtoFormValidator];\n\nexport type UseProtoFormOptions<\n Values extends FormValues,\n Desc extends DescMessage,\n TValidators extends FormValidators,\n TSubmitReturn,\n> = Omit<\n FormOptions<\n Values,\n ProtoFormValidators,\n TSubmitReturn\n >,\n 'validators'\n> & {\n emptyRepeatedStringPolicies?: ProtoConversionOptions['emptyRepeatedStringPolicies'];\n validators?: TValidators;\n};\n\nexport type UseProtoFormReturn<\n Values extends FormValues,\n Desc extends DescMessage,\n TValidators extends FormValidators,\n TSubmitReturn,\n> = ReactFormApi<\n Values,\n ToFormErrorTypes<\n ProtoFormValidators,\n TSubmitReturn\n >,\n DefaultReactFormComponentMap\n> & {\n createMessage: (values?: Values) => MessageShape;\n createUpdateMask: () => FieldMask;\n setOneofValue: >(\n path: TPath,\n oneofCase: string,\n value: unknown,\n options?: FieldUpdateOptions\n ) => void;\n};\n\nfunction appendProtoValidator<\n Values extends FormValues,\n Desc extends DescMessage,\n TValidators extends FormValidators,\n>(\n validators: TValidators | undefined,\n validator: ProtoFormValidator\n): ProtoFormValidators {\n return [...(validators ?? []), validator] as ProtoFormValidators<\n Values,\n Desc,\n TValidators\n >;\n}\n\nexport function useProtoForm<\n Desc extends DescMessage,\n Values extends FormValues,\n const TValidators extends FormValidators = [],\n TSubmitReturn = unknown,\n>(\n schema: Desc,\n options: UseProtoFormOptions\n): UseProtoFormReturn {\n const { emptyRepeatedStringPolicies, validators, ...nativeOptions } = options;\n const conversionOptions: ProtoConversionOptions = {\n emptyRepeatedStringPolicies,\n };\n const protoValidator: ProtoFormValidator = {\n run: createProtoFormSchema(schema, conversionOptions),\n triggers: [],\n };\n const composedOptions: FormOptions<\n Values,\n ProtoFormValidators,\n TSubmitReturn\n > = {\n ...nativeOptions,\n validators: appendProtoValidator(validators, protoValidator),\n };\n const form = useForm(composedOptions);\n const sourceMessage = isMessage(options.defaultValues, schema)\n ? options.defaultValues\n : undefined;\n\n const setOneofValue = >(\n path: TPath,\n oneofCase: string,\n value: unknown,\n updateOptions?: FieldUpdateOptions\n ) => {\n const current = form.getFieldValue(path);\n const isOneof =\n current === undefined ||\n current === null ||\n (typeof current === 'object' && 'case' in current);\n if (!isOneof) {\n throw new Error(\n `setOneofValue(\"${path}\"): target is not a oneof field. Expected { case, value } shape. Use setFieldValue() for regular fields.`\n );\n }\n const previousCase =\n typeof current === 'object' && current !== null\n ? Reflect.get(current, 'case')\n : undefined;\n if (typeof previousCase === 'string' && previousCase !== oneofCase) {\n Reflect.apply(form.setFieldValue, form, [\n path,\n { case: undefined, value: undefined },\n updateOptions,\n ]);\n }\n Reflect.apply(form.setFieldValue, form, [\n path,\n { case: oneofCase, value },\n updateOptions,\n ]);\n };\n\n return Object.assign(form, {\n createMessage: (values?: Values) =>\n formValuesToProto(\n schema,\n values ?? form.state.values,\n sourceMessage,\n conversionOptions\n ),\n createUpdateMask: () =>\n createDirtyUpdateMask(\n schema,\n dirtyFieldsFromValues(form.state.values, form.defaultValues),\n form.state.values,\n form.defaultValues\n ),\n setOneofValue,\n });\n}\n", "type": "registry:hook" } ], "type": "registry:hook" }