@@ -18,6 +18,7 @@ import {
1818 FormMessage as RadixFormMessage ,
1919 Label as RadixLabel ,
2020 Submit as RadixSubmit ,
21+ ValidityState as RadixValidityState ,
2122} from '@radix-ui/react-form' ;
2223import { Slot } from '@radix-ui/react-slot' ;
2324import * as React from 'react' ;
@@ -43,7 +44,7 @@ import { isReactFragment } from '~/react/utils/is-react-fragment';
4344
4445import type { OTPInputProps } from './otp' ;
4546import { OTP_LENGTH_DEFAULT , OTPInput } from './otp' ;
46- import { type ClerkFieldId , FIELD_STATES , FIELD_VALIDITY , type FieldStates } from './types' ;
47+ import { type ClerkFieldId , FIELD_STATES , type FieldStates } from './types' ;
4748
4849/* -------------------------------------------------------------------------------------------------
4950 * Context
@@ -52,26 +53,13 @@ import { type ClerkFieldId, FIELD_STATES, FIELD_VALIDITY, type FieldStates } fro
5253const FieldContext = React . createContext < Pick < FieldDetails , 'name' > | null > ( null ) ;
5354const useFieldContext = ( ) => React . useContext ( FieldContext ) ;
5455
56+ const ValidityStateContext = React . createContext < ValidityState | undefined > ( undefined ) ;
57+ const useValidityStateContext = ( ) => React . useContext ( ValidityStateContext ) ;
58+
5559/* -------------------------------------------------------------------------------------------------
56- * Hooks
60+ * Utils
5761 * -----------------------------------------------------------------------------------------------*/
5862
59- const useGlobalErrors = ( ) => {
60- const errors = useFormSelector ( globalErrorsSelector ) ;
61-
62- return {
63- errors,
64- } ;
65- } ;
66-
67- const useFieldFeedback = ( { name } : Partial < Pick < FieldDetails , 'name' > > ) => {
68- const feedback = useFormSelector ( fieldFeedbackSelector ( name ) ) ;
69-
70- return {
71- feedback,
72- } ;
73- } ;
74-
7563const determineInputTypeFromName = ( name : FormFieldProps [ 'name' ] ) => {
7664 if ( name === 'password' || name === 'confirmPassword' || name === 'currentPassword' || name === 'newPassword' ) {
7765 return 'password' as const ;
@@ -89,6 +77,36 @@ const determineInputTypeFromName = (name: FormFieldProps['name']) => {
8977 return 'text' as const ;
9078} ;
9179
80+ /**
81+ * Radix can return the ValidityState object, which contains the validity of the field. We need to merge this with our existing fieldState.
82+ * When the ValidityState is valid: false, the fieldState should be overriden. Otherwise, it shouldn't change at all.
83+ * @see https://www.radix-ui.com/primitives/docs/components/form#validitystate
84+ * @see https://developer.mozilla.org/en-US/docs/Web/API/ValidityState
85+ */
86+ const enrichFieldState = ( validity : ValidityState | undefined , fieldState : FieldStates ) => {
87+ return validity ?. valid === false ? FIELD_STATES . error : fieldState ;
88+ } ;
89+
90+ /* -------------------------------------------------------------------------------------------------
91+ * Hooks
92+ * -----------------------------------------------------------------------------------------------*/
93+
94+ const useGlobalErrors = ( ) => {
95+ const errors = useFormSelector ( globalErrorsSelector ) ;
96+
97+ return {
98+ errors,
99+ } ;
100+ } ;
101+
102+ const useFieldFeedback = ( { name } : Partial < Pick < FieldDetails , 'name' > > ) => {
103+ const feedback = useFormSelector ( fieldFeedbackSelector ( name ) ) ;
104+
105+ return {
106+ feedback,
107+ } ;
108+ } ;
109+
92110/**
93111 * Given a field name, determine the current state of the field
94112 */
@@ -133,7 +151,6 @@ const useFieldState = ({ name }: Partial<Pick<FieldDetails, 'name'>>) => {
133151 */
134152const useForm = ( { flowActor } : { flowActor ?: BaseActorRef < { type : 'SUBMIT' } > } ) => {
135153 const { errors } = useGlobalErrors ( ) ;
136- const validity = errors . length > 0 ? FIELD_VALIDITY . invalid : FIELD_VALIDITY . valid ;
137154
138155 // Register the onSubmit handler for form submission
139156 // TODO: merge user-provided submit handler
@@ -149,7 +166,7 @@ const useForm = ({ flowActor }: { flowActor?: BaseActorRef<{ type: 'SUBMIT' }> }
149166
150167 return {
151168 props : {
152- [ ` data-${ validity } ` ] : true ,
169+ ... ( errors . length > 0 ? { ' data-global-error' : true } : { } ) ,
153170 onSubmit,
154171 } ,
155172 } ;
@@ -161,12 +178,10 @@ const useField = ({ name }: Partial<Pick<FieldDetails, 'name'>>) => {
161178
162179 const shouldBeHidden = false ; // TODO: Implement clerk-js utils
163180 const hasError = feedback ? feedback . type === 'error' : false ;
164- const validity = hasError ? FIELD_VALIDITY . invalid : FIELD_VALIDITY . valid ;
165181
166182 return {
167183 hasValue,
168184 props : {
169- [ `data-${ validity } ` ] : true ,
170185 'data-hidden' : shouldBeHidden ? true : undefined ,
171186 serverInvalid : hasError ,
172187 } ,
@@ -186,6 +201,7 @@ const useInput = ({
186201 const fieldContext = useFieldContext ( ) ;
187202 const name = inputName || fieldContext ?. name ;
188203 const { state : fieldState } = useFieldState ( { name } ) ;
204+ const validity = useValidityStateContext ( ) ;
189205
190206 if ( ! name ) {
191207 throw new Error ( 'Clerk: <Input /> must be wrapped in a <Field> component or have a name prop.' ) ;
@@ -342,7 +358,7 @@ const useInput = ({
342358 onFocus,
343359 'data-hidden' : shouldBeHidden ? true : undefined ,
344360 'data-has-value' : hasValue ? true : undefined ,
345- 'data-state' : fieldState ,
361+ 'data-state' : enrichFieldState ( validity , fieldState ) ,
346362 ...props ,
347363 ...rest ,
348364 } ,
@@ -444,7 +460,17 @@ const FieldInner = React.forwardRef<FormFieldElement, FormFieldProps>((props, fo
444460 { ...rest }
445461 ref = { forwardedRef }
446462 >
447- { typeof children === 'function' ? children ( fieldState ) : children }
463+ < RadixValidityState >
464+ { validity => {
465+ const enrichedFieldState = enrichFieldState ( validity , fieldState ) ;
466+
467+ return (
468+ < ValidityStateContext . Provider value = { validity } >
469+ { typeof children === 'function' ? children ( enrichedFieldState ) : children }
470+ </ ValidityStateContext . Provider >
471+ ) ;
472+ } }
473+ </ RadixValidityState >
448474 </ RadixField >
449475 ) ;
450476} ) ;
@@ -493,11 +519,12 @@ function FieldState({ children }: FieldStateRenderFn) {
493519 const field = useFieldContext ( ) ;
494520 const { feedback } = useFieldFeedback ( { name : field ?. name } ) ;
495521 const { state } = useFieldState ( { name : field ?. name } ) ;
522+ const validity = useValidityStateContext ( ) ;
496523
497524 const message = feedback ?. message instanceof ClerkElementsFieldError ? feedback . message . message : feedback ?. message ;
498525 const codes = feedback ?. codes ;
499526
500- const fieldState = { state, message, codes } ;
527+ const fieldState = { state : enrichFieldState ( validity , state ) , message, codes } ;
501528
502529 return children ( fieldState ) ;
503530}
0 commit comments