1616 * under the License.
1717 */
1818
19- import { cx } from '@emotion/css' ;
19+ import { cx } from '@emotion/css' ;
2020import {
2121 withVendorCSSClassPrefix ,
2222 EmbeddedSignInFlowRequest ,
@@ -26,7 +26,7 @@ import {
2626 Preferences ,
2727 buildValidatorFromRules ,
2828} from '@thunderid/browser' ;
29- import { FC , useEffect , useMemo , useState , useCallback , useContext , ReactElement , ReactNode } from 'react' ;
29+ import { FC , useEffect , useMemo , useState , useCallback , useContext , ReactElement , ReactNode } from 'react' ;
3030import useStyles from './BaseSignIn.styles' ;
3131import ComponentRendererContext , {
3232 ComponentRendererMap ,
@@ -36,16 +36,16 @@ import useFlow from '../../../../contexts/Flow/useFlow';
3636import ComponentPreferencesContext from '../../../../contexts/I18n/ComponentPreferencesContext' ;
3737import useTheme from '../../../../contexts/Theme/useTheme' ;
3838import useThunderID from '../../../../contexts/ThunderID/useThunderID' ;
39- import { FormField , useForm } from '../../../../hooks/useForm' ;
39+ import { FormField , useForm } from '../../../../hooks/useForm' ;
4040import useTranslation from '../../../../hooks/useTranslation' ;
4141import composeAffixedInputs from '../../../../utils/composeAffixedInputs' ;
42- import { extractErrorMessage } from '../../../../utils/flowTransformer' ;
42+ import { extractErrorMessage } from '../../../../utils/flowTransformer' ;
4343import AlertPrimitive from '../../../primitives/Alert/Alert' ;
4444// eslint-disable-next-line import/no-named-as-default
45- import CardPrimitive , { CardProps } from '../../../primitives/Card/Card' ;
45+ import CardPrimitive , { CardProps } from '../../../primitives/Card/Card' ;
4646import Spinner from '../../../primitives/Spinner/Spinner' ;
4747import Typography from '../../../primitives/Typography/Typography' ;
48- import { renderSignInComponents } from '../AuthOptionFactory' ;
48+ import { renderSignInComponents } from '../AuthOptionFactory' ;
4949
5050/**
5151 * Render props for custom UI rendering
@@ -94,7 +94,7 @@ export interface BaseSignInRenderProps {
9494 /**
9595 * Flow messages
9696 */
97- messages : { message : string ; type : string } [ ] ;
97+ messages : { message : string ; type : string } [ ] ;
9898
9999 /**
100100 * Flow metadata returned by the platform (v2 only). `null` while loading or unavailable.
@@ -119,7 +119,7 @@ export interface BaseSignInRenderProps {
119119 /**
120120 * Function to validate the form
121121 */
122- validateForm : ( ) => { fieldErrors : Record < string , string > ; isValid : boolean } ;
122+ validateForm : ( ) => { fieldErrors : Record < string , string > ; isValid : boolean } ;
123123
124124 /**
125125 * Form values
@@ -226,6 +226,14 @@ export interface BaseSignInProps {
226226 */
227227 serverFieldErrors ?: FieldError [ ] | null ;
228228
229+ /**
230+ * When a field has been blurred at least once, re-run validation on every subsequent
231+ * keystroke so a rendered error clears the moment the value becomes valid. Doesn't
232+ * affect fields that have never been blurred — the user isn't shown errors while
233+ * initially typing. Default `false` preserves prior behavior.
234+ */
235+ revalidateOnChangeAfterBlur ?: boolean ;
236+
229237 /**
230238 * Size variant for the component.
231239 */
@@ -257,19 +265,34 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
257265 additionalData = { } ,
258266 isTimeoutDisabled = false ,
259267 serverFieldErrors = null ,
268+ revalidateOnChangeAfterBlur = false ,
260269} : BaseSignInProps ) : ReactElement => {
261- const { meta, vendor} = useThunderID ( ) ;
262- const { theme} = useTheme ( ) ;
270+ const { meta, vendor } = useThunderID ( ) ;
271+ const { theme } = useTheme ( ) ;
263272 const customRenderers : ComponentRendererMap = useContext ( ComponentRendererContext ) ;
264- const { t } = useTranslation ( ) ;
265- const { subtitle : flowSubtitle , title : flowTitle , messages : flowMessages , addMessage, clearMessages} = useFlow ( ) ;
273+ const { t } = useTranslation ( ) ;
274+ const { subtitle : flowSubtitle , title : flowTitle , messages : flowMessages , addMessage, clearMessages } = useFlow ( ) ;
266275 const styles : any = useStyles ( theme , theme . vars . colors . text . primary ) ;
267276
268277 const [ isSubmitting , setIsSubmitting ] = useState ( false ) ;
269278 const [ apiError , setApiError ] = useState < Error | null > ( null ) ;
270279
271280 const isLoading : boolean = externalIsLoading || isSubmitting ;
272281
282+ /**
283+ * Component type for which forms validation will be applicable
284+ */
285+ const acceptableType = [
286+ 'TEXT_INPUT' ,
287+ 'PASSWORD_INPUT' ,
288+ 'EMAIL_INPUT' ,
289+ 'PHONE_INPUT' ,
290+ 'OTP_INPUT' ,
291+ 'SELECT' ,
292+ 'DATE_INPUT' ,
293+ 'CUSTOM' ,
294+ ] ;
295+
273296 /**
274297 * Handle error responses and extract meaningful error messages
275298 * Uses the transformer's extractErrorMessage function for consistency
@@ -300,15 +323,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
300323
301324 const processComponents = ( comps : EmbeddedFlowComponent [ ] ) : any => {
302325 comps . forEach ( ( component : any ) => {
303- if (
304- component . type === 'TEXT_INPUT' ||
305- component . type === 'PASSWORD_INPUT' ||
306- component . type === 'EMAIL_INPUT' ||
307- component . type === 'PHONE_INPUT' ||
308- component . type === 'OTP_INPUT' ||
309- component . type === 'SELECT' ||
310- component . type === 'DATE_INPUT'
311- ) {
326+ if ( acceptableType . includes ( component . type ) ) {
312327 const identifier : string = component . ref ;
313328 const ruleValidator = buildValidatorFromRules ( component . validation ) ;
314329 fields . push ( {
@@ -360,6 +375,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
360375 fields : formFields ,
361376 initialValues : { } ,
362377 requiredMessage : t ( 'validations.required.field.error' ) ,
378+ revalidateOnChangeAfterBlur,
363379 validateOnBlur : true ,
364380 validateOnChange : false ,
365381 } ) ;
@@ -460,7 +476,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
460476 // For V2, we always send inputs and action
461477 payload = {
462478 ...payload ,
463- ...( component . id && { action : component . id } ) ,
479+ ...( component . id && { action : component . id } ) ,
464480 inputs : filteredInputs ,
465481 } ;
466482
@@ -573,7 +589,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
573589 touched : touchedFields ,
574590 validateForm : ( ) => {
575591 const result : any = validateForm ( ) ;
576- return { fieldErrors : result . errors , isValid : result . isValid } ;
592+ return { fieldErrors : result . errors , isValid : result . isValid } ;
577593 } ,
578594 values : formValues ,
579595 } ;
@@ -595,7 +611,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
595611 variant = { variant }
596612 >
597613 < CardPrimitive . Content >
598- < div style = { { display : 'flex' , justifyContent : 'center' , padding : '2rem' } } >
614+ < div style = { { display : 'flex' , justifyContent : 'center' , padding : '2rem' } } >
599615 < Spinner />
600616 </ div >
601617 </ CardPrimitive . Content >
@@ -703,7 +719,7 @@ const BaseSignInContent: FC<BaseSignInProps> = ({
703719 * </BaseSignIn>
704720 * ```
705721 */
706- const BaseSignIn : FC < BaseSignInProps > = ( { preferences, ...rest } : BaseSignInProps ) : ReactElement => {
722+ const BaseSignIn : FC < BaseSignInProps > = ( { preferences, ...rest } : BaseSignInProps ) : ReactElement => {
707723 const content : ReactElement = (
708724 < FlowProvider >
709725 < BaseSignInContent { ...rest } />
0 commit comments