@@ -26,15 +26,21 @@ interface FormValidationProps<T> extends Validation<T> {
26
26
export function useFormValidation < T > ( props : FormValidationProps < T > , state : FormValidationState , ref : RefObject < ValidatableElement | null > | undefined ) : void {
27
27
let { validationBehavior, focus} = props ;
28
28
29
- let timeoutId = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
29
+ let justBlurredRef = useRef ( false ) ;
30
+ let timeoutIdRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
30
31
function announceErrorMessage ( errorMessage : string = '' ) : void {
31
- if ( timeoutId . current != null ) {
32
- clearTimeout ( timeoutId . current ) ;
32
+ if ( timeoutIdRef . current != null ) {
33
+ clearTimeout ( timeoutIdRef . current ) ;
34
+ timeoutIdRef . current = null ;
33
35
}
34
36
if ( ref ?. current &&
35
37
errorMessage !== '' &&
36
- ref . current . contains ( getActiveElement ( getOwnerDocument ( ref . current ) ) ) ) {
37
- timeoutId . current = setTimeout ( ( ) => announce ( errorMessage , 'polite' ) , 250 ) ;
38
+ (
39
+ ref . current . contains ( getActiveElement ( getOwnerDocument ( ref . current ) ) ) ||
40
+ justBlurredRef . current
41
+ )
42
+ ) {
43
+ timeoutIdRef . current = setTimeout ( ( ) => announce ( errorMessage , 'polite' ) , 250 ) ;
38
44
}
39
45
}
40
46
@@ -44,8 +50,6 @@ export function useFormValidation<T>(props: FormValidationProps<T>, state: FormV
44
50
let errorMessage = state . realtimeValidation . isInvalid ? state . realtimeValidation . validationErrors . join ( ' ' ) || 'Invalid value.' : '' ;
45
51
ref . current . setCustomValidity ( errorMessage ) ;
46
52
47
- announceErrorMessage ( errorMessage ) ;
48
-
49
53
// Prevent default tooltip for validation message.
50
54
// https://bugzilla.mozilla.org/show_bug.cgi?id=605277
51
55
if ( ! ref . current . hasAttribute ( 'title' ) ) {
@@ -72,7 +76,10 @@ export function useFormValidation<T>(props: FormValidationProps<T>, state: FormV
72
76
// Auto focus the first invalid input in a form, unless the error already had its default prevented.
73
77
let form = ref ?. current ?. form ;
74
78
if ( ! e . defaultPrevented && ref && form ) {
79
+
80
+ // Announce the current error message
75
81
announceErrorMessage ( ref ?. current ?. validationMessage || '' ) ;
82
+
76
83
if ( getFirstInvalidInput ( form ) === ref . current ) {
77
84
if ( focus ) {
78
85
focus ( ) ;
@@ -93,23 +100,36 @@ export function useFormValidation<T>(props: FormValidationProps<T>, state: FormV
93
100
state . commitValidation ( ) ;
94
101
} ) ;
95
102
103
+ let onBlur = useEffectEvent ( ( ) => {
104
+ justBlurredRef . current = true ;
105
+ // Announce the current error message
106
+ announceErrorMessage ( ref ?. current ?. validationMessage || '' ) ;
107
+ justBlurredRef . current = false ;
108
+ } ) ;
109
+
96
110
useEffect ( ( ) => {
97
111
let input = ref ?. current ;
98
112
if ( ! input ) {
99
113
return ;
100
114
}
101
115
102
116
let form = input . form ;
117
+ input . addEventListener ( 'blur' , onBlur ) ;
103
118
input . addEventListener ( 'invalid' , onInvalid ) ;
104
119
input . addEventListener ( 'change' , onChange ) ;
105
120
form ?. addEventListener ( 'reset' , onReset ) ;
106
121
return ( ) => {
107
- clearTimeout ( timeoutId . current ! ) ;
122
+ if ( timeoutIdRef . current != null ) {
123
+ clearTimeout ( timeoutIdRef . current ) ;
124
+ timeoutIdRef . current = null ;
125
+ }
126
+ justBlurredRef . current = false ;
127
+ input ! . removeEventListener ( 'blur' , onBlur ) ;
108
128
input ! . removeEventListener ( 'invalid' , onInvalid ) ;
109
129
input ! . removeEventListener ( 'change' , onChange ) ;
110
130
form ?. removeEventListener ( 'reset' , onReset ) ;
111
131
} ;
112
- } , [ ref , onInvalid , onChange , onReset , validationBehavior ] ) ;
132
+ } , [ justBlurredRef , onBlur , onChange , onInvalid , onReset , ref , validationBehavior ] ) ;
113
133
}
114
134
115
135
function getValidity ( input : ValidatableElement ) {
0 commit comments