11/**
22 * Internal dependencies.
33 */
4+ import { TPFormElement } from './tp-form' ;
45import { TPFormErrorElement } from './tp-form-error' ;
6+ import { TPFormSuspenseElement } from './tp-form-suspense' ;
57
68/**
79 * TP Form Field.
@@ -26,9 +28,16 @@ export class TPFormFieldElement extends HTMLElement {
2628 * Update validation when the field has changed.
2729 */
2830 handleFieldChanged ( ) : void {
31+ // Check if we want to ignore field revalidations.
32+ if ( 'no' === this . getAttribute ( 'revalidate-on-change' ) ) {
33+ // Yes we do, bail!
34+ return ;
35+ }
36+
2937 // Validate the field again if 'valid' or 'error' attribute is present.
3038 if ( this . getAttribute ( 'valid' ) || this . getAttribute ( 'error' ) ) {
31- this . validate ( ) ;
39+ const form : TPFormElement | null = this . closest ( 'tp-form' ) ;
40+ form ?. validateField ( this ) ;
3241 }
3342 }
3443
@@ -39,7 +48,7 @@ export class TPFormFieldElement extends HTMLElement {
3948 */
4049 static get observedAttributes ( ) : string [ ] {
4150 // Attributes observed in the TPFormFieldElement web-component.
42- return [ 'valid' , 'error' ] ;
51+ return [ 'valid' , 'error' , 'suspense' ] ;
4352 }
4453
4554 /**
@@ -53,7 +62,7 @@ export class TPFormFieldElement extends HTMLElement {
5362 // Check if the observed attributes 'valid' or 'error' have changed.
5463
5564 // Dispatch a custom 'validate' event.
56- if ( ( 'valid' === name || 'error' === name ) && oldValue !== newValue ) {
65+ if ( ( 'valid' === name || 'error' === name || 'suspense' === name ) && oldValue !== newValue ) {
5766 this . dispatchEvent ( new CustomEvent ( 'validate' , { bubbles : true } ) ) ;
5867 }
5968
@@ -70,7 +79,7 @@ export class TPFormFieldElement extends HTMLElement {
7079
7180 // Exit the function if validators are not available.
7281 if ( ! tpFormValidators ) {
73- //Early return
82+ // Early return.
7483 return ;
7584 }
7685
@@ -83,6 +92,17 @@ export class TPFormFieldElement extends HTMLElement {
8392 } else {
8493 this . removeErrorMessage ( ) ;
8594 }
95+
96+ // Get the 'suspense' attribute value.
97+ const suspense : string = this . getAttribute ( 'suspense' ) ?? '' ;
98+
99+ // Check if the suspense exists and has a corresponding suspense message function.
100+ if ( '' !== suspense && suspense in tpFormValidators && 'function' === typeof tpFormValidators [ suspense ] . getSuspenseMessage ) {
101+ // @ts -ignore
102+ this . setSuspenseMessage ( tpFormValidators [ suspense ] ?. getSuspenseMessage ( this ) ) ;
103+ } else {
104+ this . removeSuspenseMessage ( ) ;
105+ }
86106 }
87107
88108 /**
@@ -100,7 +120,7 @@ export class TPFormFieldElement extends HTMLElement {
100120 *
101121 * @return {boolean } Whether this field passed validation.
102122 */
103- validate ( ) : boolean {
123+ async validate ( ) : Promise < boolean > {
104124 // Retrieve tpFormValidators from the window object.
105125 const { tpFormValidators } = window ;
106126
@@ -118,6 +138,7 @@ export class TPFormFieldElement extends HTMLElement {
118138
119139 // Prepare error and valid status.
120140 let valid : boolean = true ;
141+ let suspense : Promise < boolean > | null = null ;
121142 let error : string = '' ;
122143 const allAttributes : string [ ] = this . getAttributeNames ( ) ;
123144
@@ -126,14 +147,64 @@ export class TPFormFieldElement extends HTMLElement {
126147 // Check if the attribute is a validator.
127148 if ( attributeName in tpFormValidators && 'function' === typeof tpFormValidators [ attributeName ] . validate ) {
128149 // We found one, lets validate the field.
129- const isValid : boolean = tpFormValidators [ attributeName ] . validate ( this ) ;
150+ const isValid : boolean | Promise < boolean > = tpFormValidators [ attributeName ] . validate ( this ) ;
151+ error = attributeName ;
152+
153+ // First check for a Promise.
154+ if ( isValid instanceof Promise ) {
155+ // Yes it is an async validation.
156+ valid = false ;
130157
131- // Looks like we found an error!
132- if ( false === isValid ) {
158+ // Dispatch a custom 'validation-suspense-start' event.
159+ this . dispatchEvent ( new CustomEvent ( 'validation-suspense-start' ) ) ;
160+
161+ // Create the promise.
162+ suspense = new Promise ( ( resolve , reject ) : void => {
163+ // Validate it.
164+ isValid
165+ . then ( ( suspenseIsValid : boolean ) => {
166+ // Validation is complete.
167+ if ( true === suspenseIsValid ) {
168+ this . setAttribute ( 'valid' , 'yes' ) ;
169+ this . removeAttribute ( 'error' ) ;
170+
171+ // Resolve the promise.
172+ resolve ( true ) ;
173+ } else {
174+ this . removeAttribute ( 'valid' ) ;
175+ this . setAttribute ( 'error' , error ) ;
176+
177+ // Resolve the promise.
178+ resolve ( false ) ;
179+ }
180+
181+ // Dispatch a custom 'validation-suspense-success' event.
182+ this . dispatchEvent ( new CustomEvent ( 'validation-suspense-success' ) ) ;
183+ } )
184+ . catch ( ( ) : void => {
185+ // There was an error.
186+ this . removeAttribute ( 'valid' ) ;
187+ this . setAttribute ( 'error' , error ) ;
188+
189+ // Dispatch a custom 'validation-suspense-error' event.
190+ this . dispatchEvent ( new CustomEvent ( 'validation-suspense-error' ) ) ;
191+
192+ // Reject the promise.
193+ reject ( false ) ;
194+ } )
195+ . finally ( ( ) : void => {
196+ // Clean up.
197+ this . removeAttribute ( 'suspense' ) ;
198+ } ) ;
199+ } ) ;
200+
201+ // Return.
202+ return false ;
203+ } else if ( false === isValid ) {
204+ // Not a Promise, but looks like we found an error!
133205 valid = false ;
134- error = attributeName ;
135206
136- // return false;
207+ // Return.
137208 return false ;
138209 }
139210 }
@@ -146,12 +217,27 @@ export class TPFormFieldElement extends HTMLElement {
146217 if ( valid ) {
147218 this . setAttribute ( 'valid' , 'yes' ) ;
148219 this . removeAttribute ( 'error' ) ;
220+ this . removeAttribute ( 'suspense' ) ;
149221 } else {
150222 this . removeAttribute ( 'valid' ) ;
151- this . setAttribute ( 'error' , error ) ;
223+
224+ // Check for suspense.
225+ if ( suspense ) {
226+ this . setAttribute ( 'suspense' , error ) ;
227+ this . removeAttribute ( 'error' ) ;
228+ } else {
229+ this . removeAttribute ( 'suspense' ) ;
230+ this . setAttribute ( 'error' , error ) ;
231+ }
152232 }
153233
154- // Return validity.
234+ // Do we have a suspense?
235+ if ( suspense ) {
236+ // Yes we do, return the promise.
237+ return suspense ;
238+ }
239+
240+ // No we don't, return a resolved promise.
155241 return valid ;
156242 }
157243
@@ -187,4 +273,31 @@ export class TPFormFieldElement extends HTMLElement {
187273 // Dispatch a custom 'validation-success' event.
188274 this . dispatchEvent ( new CustomEvent ( 'validation-success' ) ) ;
189275 }
276+
277+ /**
278+ * Set the suspense message.
279+ *
280+ * @param {string } message Suspense message.
281+ */
282+ setSuspenseMessage ( message : string = '' ) : void {
283+ // Look for an existing tp-form-error element.
284+ const suspense : TPFormSuspenseElement | null = this . querySelector ( 'tp-form-suspense' ) ;
285+
286+ // If found, update its innerHTML with the suspense message. Otherwise, create a new tp-form-suspense element and append it to the component.
287+ if ( suspense ) {
288+ suspense . innerHTML = message ;
289+ } else {
290+ const suspenseElement : TPFormSuspenseElement = document . createElement ( 'tp-form-suspense' ) ;
291+ suspenseElement . innerHTML = message ;
292+ this . appendChild ( suspenseElement ) ;
293+ }
294+ }
295+
296+ /**
297+ * Remove the suspense message.
298+ */
299+ removeSuspenseMessage ( ) : void {
300+ // Find and remove the tp-form-suspense element.
301+ this . querySelector ( 'tp-form-suspense' ) ?. remove ( ) ;
302+ }
190303}
0 commit comments