PageSourceSearch

https://lightcoral-locust-884316.hostingersite.com/wp-content/plugins/wpforms/assets/js/wpforms.js?ver=1.6.4.1

js lightcoral-locust-884316.hostingersite.com collected 2026-10-03 04:33:17 UTC 63,678 bytes, 2,125 lines download raw bytes

1/* global wpforms_settings, grecaptcha, hcaptcha, wpformsRecaptchaCallback, wpforms_validate, wpforms_datepicker, wpforms_timepicker, Mailcheck, Choices */
2
3'use strict';
4
5var wpforms = window.wpforms || ( function( document, window, $ ) {
6
7	var app = {
8
9		/**
10		 * Start the engine.
11		 *
12		 * @since 1.2.3
13		 */
14		init: function() {
15
16			// Document ready.
17			$( app.ready );
18
19			// Page load.
20			$( window ).on( 'load', app.load );
21
22			app.bindUIActions();
23			app.bindOptinMonster();
24		},
25
26		/**
27		 * Document ready.
28		 *
29		 * @since 1.2.3
30		 */
31		ready: function() {
32
33			// Clear URL - remove wpforms_form_id.
34			app.clearUrlQuery();
35
36			// Set user identifier.
37			app.setUserIndentifier();
38
39			app.loadValidation();
40			app.loadDatePicker();
41			app.loadTimePicker();
42			app.loadInputMask();
43			app.loadSmartPhoneField();
44			app.loadPayments();
45			app.loadMailcheck();
46			app.loadChoicesJS();
47
48			// Randomize elements.
49			$( '.wpforms-randomize' ).each( function() {
50				var $list      = $( this ),
51					$listItems = $list.children();
52				while ( $listItems.length ) {
53					$list.append( $listItems.splice( Math.floor( Math.random() * $listItems.length ), 1 )[0] );
54				}
55			} );
56
57			// Unlock pagebreak navigation.
58			$( '.wpforms-page-button' ).prop( 'disabled', false );
59
60			$( document ).trigger( 'wpformsReady' );
61		},
62
63		/**
64		 * Page load.
65		 *
66		 * @since 1.2.3
67		 */
68		load: function() {
69
70		},
71
72		//--------------------------------------------------------------------//
73		// Initializing
74		//--------------------------------------------------------------------//
75
76		/**
77		 * Remove wpforms_form_id from URL.
78		 *
79		 * @since 1.5.2
80		 */
81		clearUrlQuery: function() {
82			var loc   = window.location,
83				query = loc.search;
84
85			if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) {
86				query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' );
87				history.replaceState( {}, null, loc.origin + loc.pathname + query );
88			}
89		},
90
91
92		/**
93		 * Load jQuery Validation.
94		 *
95		 * @since 1.2.3
96		 */
97		loadValidation: function() {
98
99			// Only load if jQuery validation library exists.
100			if ( typeof $.fn.validate !== 'undefined' ) {
101
102				// jQuery Validation library will not correctly validate
103				// fields that do not have a name attribute, so we use the
104				// `wpforms-input-temp-name` class to add a temporary name
105				// attribute before validation is initialized, then remove it
106				// before the form submits.
107				$( '.wpforms-input-temp-name' ).each( function( index, el ) {
108					var random = Math.floor( Math.random() * 9999 ) + 1;
109					$( this ).attr( 'name', 'wpf-temp-' + random );
110				} );
111
112				// Prepend URL field contents with http:// if user input doesn't contain a schema.
113				$( '.wpforms-validate input[type=url]' ).change( function() {
114					var url = $( this ).val();
115					if ( ! url ) {
116						return false;
117					}
118					if ( url.substr( 0, 7 ) !== 'http://' && url.substr( 0, 8 ) !== 'https://' ) {
119						$( this ).val( 'http://' + url );
120					}
121				} );
122
123				$.validator.messages.required = wpforms_settings.val_required;
124				$.validator.messages.url      = wpforms_settings.val_url;
125				$.validator.messages.email    = wpforms_settings.val_email;
126				$.validator.messages.number   = wpforms_settings.val_number;
127
128				// Payments: Validate method for Credit Card Number.
129				if ( typeof $.fn.payment !== 'undefined' ) {
130					$.validator.addMethod( 'creditcard', function( value, element ) {
131
132						//var type  = $.payment.cardType(value);
133						var valid = $.payment.validateCardNumber( value );
134						return this.optional( element ) || valid;
135					}, wpforms_settings.val_creditcard );
136
137					// @todo validate CVC and expiration
138				}
139
140				// Validate method for file extensions.
141				$.validator.addMethod( 'extension', function( value, element, param ) {
142					param = 'string' === typeof param ? param.replace( /,/g, '|' ) : 'png|jpe?g|gif';
143					return this.optional( element ) || value.match( new RegExp( '\\.(' + param + ')$', 'i' ) );
144				}, wpforms_settings.val_fileextension );
145
146				// Validate method for file size.
147				$.validator.addMethod( 'maxsize', function( value, element, param ) {
148					var maxSize = param,
149						optionalValue = this.optional( element ),
150						i, len, file;
151					if ( optionalValue ) {
152						return optionalValue;
153					}
154					if ( element.files && element.files.length ) {
155						i = 0;
156						len = element.files.length;
157						for ( ; i < len; i++ ) {
158							file = element.files[i];
159							if ( file.size > maxSize ) {
160								return false;
161							}
162						}
163					}
164					return true;
165				}, wpforms_settings.val_filesize );
166
167				// Validate email addresses.
168				$.validator.methods.email = function( value, element ) {
169					return this.optional( element ) || /^[a-z0-9.!#$%&'*+\/=?^_`{|}~-]+@((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}$/i.test( value );
170				};
171
172				// Validate email by allowlist/blocklist.
173				$.validator.addMethod( 'restricted-email', function( value, element ) {
174
175					var validator = this,
176						$el = $( element ),
177						$field = $el.closest( '.wpforms-field' ),
178						$form = $el.closest( '.wpforms-form' );
179
180					if ( ! $el.val().length ) {
181						return true;
182					}
183
184					this.startRequest( element );
185					$.post( {
186						url: wpforms_settings.ajaxurl,
187						type: 'post',
188						async: false,
189						data: {
190							'token': $form.data( 'token' ),
191							'action': 'wpforms_restricted_email',
192							'form_id': $form.data( 'formid' ),
193							'field_id': $field.data( 'field-id' ),
194							'email': $el.val(),
195						},
196						dataType: 'json',
197						success: function( response ) {
198
199							var isValid =  response.success && response.data,
200								errors = {};
201
202							if ( isValid ) {
203								validator.resetInternals();
204								validator.toHide = validator.errorsFor( element );
205								validator.showErrors();
206							} else {
207								errors[ element.name ] = wpforms_settings.val_email_restricted;
208								validator.showErrors( errors );
209							}
210							validator.stopRequest( element, isValid );
211						},
212					} );
213					return 'pending';
214				}, wpforms_settings.val_email_restricted );
215
216				// Validate confirmations.
217				$.validator.addMethod( 'confirm', function( value, element, param ) {
218					return value === $( element ).closest( '.wpforms-field' ).find( 'input:first-child' ).val();
219				}, wpforms_settings.val_confirm );
220
221				// Validate required payments.
222				$.validator.addMethod( 'required-payment', function( value, element ) {
223					return app.amountSanitize( value ) > 0;
224				}, wpforms_settings.val_requiredpayment );
225
226				// Validate 12-hour time.
227				$.validator.addMethod( 'time12h', function( value, element ) {
228					return this.optional( element ) || /^((0?[1-9]|1[012])(:[0-5]\d){1,2}(\ ?[AP]M))$/i.test( value );
229				}, wpforms_settings.val_time12h );
230
231				// Validate 24-hour time.
232				$.validator.addMethod( 'time24h', function( value, element ) {
233					return this.optional( element ) || /^(([0-1]?[0-9])|([2][0-3])):([0-5]?[0-9])(\ ?[AP]M)?$/i.test( value );
234				}, wpforms_settings.val_time24h );
235
236				// Validate checkbox choice limit.
237				$.validator.addMethod( 'check-limit', function( value, element ) {
238					var $ul = $( element ).closest( 'ul' ),
239						$checked = $ul.find( 'input[type="checkbox"]:checked' ),
240						choiceLimit = parseInt( $ul.attr( 'data-choice-limit' ) || 0, 10 );
241
242					if ( 0 === choiceLimit ) {
243						return true;
244					}
245					return $checked.length <= choiceLimit;
246				}, function( params, element ) {
247					var	choiceLimit = parseInt( $( element ).closest( 'ul' ).attr( 'data-choice-limit' ) || 0, 10 );
248					return wpforms_settings.val_checklimit.replace( '{#}', choiceLimit );
249				} );
250
251				// Validate Smart Phone Field.
252				if ( typeof $.fn.intlTelInput !== 'undefined' ) {
253					$.validator.addMethod( 'smart-phone-field', function( value, element ) {
254						if ( value.match( /[^\d()\-+\s]/ ) ) {
255							return false;
256						}
257						return this.optional( element ) || $( element ).intlTelInput( 'isValidNumber' );
258					}, wpforms_settings.val_phone );
259				}
260
261				// Validate Input Mask minimum length.
262				$.validator.addMethod( 'empty-blanks', function( value, element ) {
263					if ( typeof $.fn.inputmask === 'undefined' ) {
264						return true;
265					}
266					return ! ( value.indexOf( element.inputmask.opts.placeholder ) + 1 );
267				}, wpforms_settings.val_empty_blanks );
268
269				// Validate US Phone Field.
270				$.validator.addMethod( 'us-phone-field', function( value, element ) {
271					if ( value.match( /[^\d()\-+\s]/ ) ) {
272						return false;
273					}
274					return this.optional( element ) || value.replace( /[^\d]/g, '' ).length === 10;
275				}, wpforms_settings.val_phone );
276
277				// Validate International Phone Field.
278				$.validator.addMethod( 'int-phone-field', function( value, element ) {
279					if ( value.match( /[^\d()\-+\s]/ ) ) {
280						return false;
281					}
282					return this.optional( element ) || value.replace( /[^\d]/g, '' ).length > 0;
283				}, wpforms_settings.val_phone );
284
285				// Finally load jQuery Validation library for our forms.
286				$( '.wpforms-validate' ).each( function() {
287					var form   = $( this ),
288						formID = form.data( 'formid' ),
289						properties;
290
291					// TODO: cleanup this BC with wpforms_validate.
292					if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'validate' ) ) {
293						properties = window['wpforms_' + formID].validate;
294					} else if ( typeof wpforms_validate !== 'undefined' ) {
295						properties = wpforms_validate;
296					} else {
297						properties = {
298							errorClass: 'wpforms-error',
299							validClass: 'wpforms-valid',
300							errorPlacement: function( error, element ) {
301
302								if ( app.isLikertScaleField( element ) ) {
303									element.closest( 'table' ).hasClass( 'single-row' ) ?
304										element.closest( '.wpforms-field' ).append( error ) :
305										element.closest( 'tr' ).find( 'th' ).append( error );
306								} else if ( app.isWrappedField( element ) ) {
307									element.closest( '.wpforms-field' ).append( error );
308								} else if ( app.isDateTimeField( element ) ) {
309									app.dateTimeErrorPlacement( element, error );
310								} else if ( app.isFieldInColumn( element ) ) {
311									element.parent().append( error );
312								} else {
313									error.insertAfter( element );
314								}
315							},
316							highlight: function( element, errorClass, validClass ) {
317								var $element  = $( element ),
318									$field    = $element.closest( '.wpforms-field' ),
319									inputName = $element.attr( 'name' );
320								if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
321									$field.find( 'input[name="' + inputName + '"]' ).addClass( errorClass ).removeClass( validClass );
322								} else {
323									$element.addClass( errorClass ).removeClass( validClass );
324								}
325								$field.addClass( 'wpforms-has-error' );
326							},
327							unhighlight: function( element, errorClass, validClass ) {
328								var $element  = $( element ),
329									$field    = $element.closest( '.wpforms-field' ),
330									inputName = $element.attr( 'name' );
331								if ( 'radio' === $element.attr( 'type' ) || 'checkbox' === $element.attr( 'type' ) ) {
332									$field.find( 'input[name="' + inputName + '"]' ).addClass( validClass ).removeClass( errorClass );
333								} else {
334									$element.addClass( validClass ).removeClass( errorClass );
335								}
336								$field.removeClass( 'wpforms-has-error' );
337							},
338							submitHandler: function( form ) {
339
340								var $form       = $( form ),
341									$submit     = $form.find( '.wpforms-submit' ),
342									altText     = $submit.data( 'alt-text' ),
343									recaptchaID = $submit.get( 0 ).recaptchaID;
344
345								if ( $form.data( 'token' ) && 0 === $( '.wpforms-token', $form ).length ) {
346									$( '<input type="hidden" class="wpforms-token" name="wpforms[token]" />' )
347										.val( $form.data( 'token' ) )
348										.appendTo( $form );
349								}
350
351								$submit.prop( 'disabled', true );
352								$form.find( '#wpforms-field_recaptcha-error' ).remove();
353
354								// Display processing text.
355								if ( altText ) {
356									$submit.text( altText );
357								}
358
359								if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
360
361									// Form contains invisible reCAPTCHA.
362									grecaptcha.execute( recaptchaID ).then( null, function( reason ) {
363
364										reason = ( null === reason ) ? '' : '<br>' + reason;
365										$form.find( '.wpforms-recaptcha-container' ).append( '<label id="wpforms-field_recaptcha-error" class="wpforms-error"> ' + wpforms_settings.val_recaptcha_fail_msg + reason + '</label>' );
366										$submit.prop( 'disabled', false );
367									} );
368									return false;
369								}
370
371								// Remove name attributes if needed.
372								$( '.wpforms-input-temp-name' ).removeAttr( 'name' );
373
374								app.formSubmit( $form );
375							},
376							invalidHandler: function( event, validator ) {
377
378								if ( typeof validator.errorList[0] !== 'undefined' ) {
379									app.scrollToError( $( validator.errorList[0].element ) );
380								}
381							},
382							onkeyup: function( element, event ) {
383
384								// This code is copied from JQuery Validate 'onkeyup' method with only one change: 'wpforms-novalidate-onkeyup' class check.
385								var excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ];
386
387								if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) ) {
388									return; // Disable onkeyup validation for some elements (e.g. remote calls).
389								}
390
391								if ( 9 === event.which && '' === this.elementValue( element ) || $.inArray( event.keyCode, excludedKeys ) !== -1 ) {
392									return;
393								} else if ( element.name in this.submitted || element.name in this.invalid ) {
394									this.element( element );
395								}
396							},
397							onfocusout: function( element ) {
398
399								// This code is copied from JQuery Validate 'onfocusout' method with only one change: 'wpforms-novalidate-onkeyup' class check.
400								var validate = false;
401
402								if ( $( element ).hasClass( 'wpforms-novalidate-onkeyup' ) && ! element.value ) {
403									validate = true; // Empty value error handling for elements with onkeyup validation disabled.
404								}
405
406								if ( ! this.checkable( element ) && ( element.name in this.submitted || ! this.optional( element ) ) ) {
407									validate = true;
408								}
409
410								if ( validate ) {
411									this.element( element );
412								}
413							},
414							onclick: function( element ) {
415								var validate = false,
416									type = ( element || {} ).type,
417									$el = $( element );
418
419								if ( [ 'checkbox', 'radio' ].indexOf( type ) > -1 ) {
420									if ( $el.hasClass( 'wpforms-likert-scale-option' ) ) {
421										$el = $el.closest( 'tr' );
422									} else {
423										$el = $el.closest( '.wpforms-field' );
424									}
425									$el.find( 'label.wpforms-error' ).remove();
426									validate = true;
427								}
428
429								if ( validate ) {
430									this.element( element );
431								}
432							},
433						};
434					}
435					form.validate( properties );
436				} );
437			}
438		},
439
440		/**
441		 * Is field inside column.
442		 *
443		 * @since 1.6.3
444		 *
445		 * @param {jQuery} element current form element.
446		 *
447		 * @returns {boolean} true/false.
448		 */
449		isFieldInColumn: function( element ) {
450
451			return element.parent().hasClass( 'wpforms-one-half' ) ||
452				element.parent().hasClass( 'wpforms-two-fifths' ) ||
453				element.parent().hasClass( 'wpforms-one-fifth' );
454		},
455
456		/**
457		 * Is datetime field.
458		 *
459		 * @since 1.6.3
460		 *
461		 * @param {jQuery} element current form element.
462		 *
463		 * @returns {boolean} true/false.
464		 */
465		isDateTimeField: function( element ) {
466
467			return element.hasClass( 'wpforms-timepicker' ) ||
468				element.hasClass( 'wpforms-datepicker' ) ||
469				( element.is( 'select' ) && element.attr( 'class' ).match( /date-month|date-day|date-year/ ) );
470		},
471
472		/**
473		 * Is field wrapped in some container.
474		 *
475		 * @since 1.6.3
476		 *
477		 * @param {jQuery} element current form element.
478		 *
479		 * @returns {boolean} true/false.
480		 */
481		isWrappedField: function( element ) { // eslint-disable-line complexity
482
483			return 'checkbox' === element.attr( 'type' ) ||
484			'radio' === element.attr( 'type' ) ||
485			'range' === element.attr( 'type' ) ||
486			'select' === element.is( 'select' ) ||
487			element.parent().hasClass( 'iti' ) ||
488			element.hasClass( 'wpforms-validation-group-member' ) ||
489			element.hasClass( 'choicesjs-select' ) ||
490			element.hasClass( 'wpforms-net-promoter-score-option' );
491		},
492
493		/**
494		 * Is likert scale field.
495		 *
496		 * @since 1.6.3
497		 *
498		 * @param {jQuery} element current form element.
499		 *
500		 * @returns {boolean} true/false.
501		 */
502		isLikertScaleField: function( element ) {
503
504			return element.hasClass( 'wpforms-likert-scale-option' );
505		},
506
507		/**
508		 * Print error message into date time fields.
509		 *
510		 * @since 1.6.3
511		 *
512		 * @param {jQuery} element current form element.
513		 * @param {string} error Error message.
514		 */
515		dateTimeErrorPlacement: function( element, error ) {
516
517			var $wrapper = element.closest( '.wpforms-field-row-block, .wpforms-field-date-time' );
518			if ( $wrapper.length ) {
519				if ( ! $wrapper.find( 'label.wpforms-error' ).length ) {
520					$wrapper.append( error );
521				}
522			} else {
523				element.closest( '.wpforms-field' ).append( error );
524			}
525		},
526
527		/**
528		 * Load jQuery Date Picker.
529		 *
530		 * @since 1.2.3
531		 */
532		loadDatePicker: function() {
533
534			// Only load if jQuery datepicker library exists.
535			if ( typeof $.fn.flatpickr !== 'undefined' ) {
536				$( '.wpforms-datepicker-wrap' ).each( function() {
537
538					var element = $( this ),
539						$input  = element.find( 'input' ),
540						form    = element.closest( '.wpforms-form' ),
541						formID  = form.data( 'formid' ),
542						fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
543						properties;
544
545					if ( typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' && window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 'datepicker' ) ) {
546						properties = window['wpforms_' + formID + '_' + fieldID].datepicker;
547					} else if ( typeof window['wpforms_' + formID] !== 'undefined' && window['wpforms_' + formID].hasOwnProperty( 'datepicker' ) ) {
548						properties = window['wpforms_' + formID].datepicker;
549					} else if ( typeof wpforms_datepicker !== 'undefined' ) {
550						properties = wpforms_datepicker;
551					} else {
552						properties = {
553							disableMobile: true,
554						};
555					}
556
557					// Redefine locale only if user doesn't do that manually and we have the locale.
558					if (
559						! properties.hasOwnProperty( 'locale' ) &&
560						typeof wpforms_settings !== 'undefined' &&
561						wpforms_settings.hasOwnProperty( 'locale' )
562					) {
563						properties.locale = wpforms_settings.locale;
564					}
565
566					properties.wrap = true;
567					properties.dateFormat = $input.data( 'date-format' );
568					if ( $input.data( 'disable-past-dates' ) === 1 ) {
569						properties.minDate = 'today';
570					}
571
572					var limitDays = $input.data( 'limit-days' ),
573						weekDays = [ 'sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat' ];
574
575					if ( limitDays && limitDays !== '' ) {
576						limitDays = limitDays.split( ',' );
577
578						properties.disable = [ function( date ) {
579
580							var limitDay;
581							for ( var i in limitDays ) {
582								limitDay = weekDays.indexOf( limitDays[ i ] );
583								if ( limitDay === date.getDay() ) {
584									return false;
585								}
586							}
587
588							return true;
589						} ];
590					}
591
592					// Toggle clear date icon.
593					properties.onChange = function( selectedDates, dateStr, instance ) {
594
595						var display = dateStr === '' ? 'none' : 'block';
596						element.find( '.wpforms-datepicker-clear' ).css( 'display', display );
597					};
598
599					element.flatpickr( properties );
600				} );
601			}
602		},
603
604		/**
605		 * Load jQuery Time Picker.
606		 *
607		 * @since 1.2.3
608		 */
609		loadTimePicker: function() {
610
611			// Only load if jQuery timepicker library exists.
612			if ( typeof $.fn.timepicker !== 'undefined' ) {
613				$( '.wpforms-timepicker' ).each( function() {
614					var element = $( this ),
615						form    = element.closest( '.wpforms-form' ),
616						formID  = form.data( 'formid' ),
617						fieldID = element.closest( '.wpforms-field' ).data( 'field-id' ),
618						properties;
619
620					if (
621						typeof window['wpforms_' + formID + '_' + fieldID] !== 'undefined' &&
622						window['wpforms_' + formID + '_' + fieldID].hasOwnProperty( 'timepicker' )
623					) {
624						properties = window['wpforms_' + formID + '_' + fieldID].timepicker;
625					} else if (
626						typeof window['wpforms_' + formID] !== 'undefined' &&
627						window['wpforms_' + formID].hasOwnProperty( 'timepicker' )
628					) {
629						properties = window['wpforms_' + formID].timepicker;
630					} else if ( typeof wpforms_timepicker !== 'undefined' ) {
631						properties = wpforms_timepicker;
632					} else {
633						properties = {
634							scrollDefault: 'now',
635							forceRoundTime: true,
636						};
637					}
638
639					element.timepicker( properties );
640				} );
641			}
642		},
643
644		/**
645		 * Load jQuery input masks.
646		 *
647		 * @since 1.2.3
648		 */
649		loadInputMask: function() {
650
651			// Only load if jQuery input mask library exists.
652			if ( typeof $.fn.inputmask === 'undefined' ) {
653				return;
654			}
655
656			$( '.wpforms-masked-input' ).inputmask();
657		},
658
659		/**
660		 * Load smart phone field.
661		 *
662		 * @since 1.5.2
663		 */
664		loadSmartPhoneField: function() {
665
666			// Only load if library exists.
667			if ( typeof $.fn.intlTelInput === 'undefined' ) {
668				return;
669			}
670
671			var inputOptions = {};
672
673			// Determine the country by IP if no GDPR restrictions enabled.
674			if ( ! wpforms_settings.gdpr ) {
675				inputOptions.geoIpLookup = app.currentIpToCountry;
676			}
677
678			// Try to kick in an alternative solution if GDPR restrictions are enabled.
679			if ( wpforms_settings.gdpr ) {
680				var lang = this.getFirstBrowserLanguage(),
681					countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : '';
682			}
683
684			// Make sure the library recognizes browser country code to avoid console error.
685			if ( countryCode ) {
686				var countryData = window.intlTelInputGlobals.getCountryData();
687
688				countryData = countryData.filter( function( country ) {
689					return country.iso2 === countryCode.toLowerCase();
690				} );
691				countryCode = countryData.length ? countryCode : '';
692			}
693
694			// Set default country.
695			inputOptions.initialCountry = wpforms_settings.gdpr && countryCode ? countryCode : 'auto';
696
697			$( '.wpforms-smart-phone-field' ).each( function( i, el ) {
698
699				var $el = $( el );
700
701				// Hidden input allows to include country code into submitted data.
702				inputOptions.hiddenInput = $el.closest( '.wpforms-field-phone' ).data( 'field-id' );
703				inputOptions.utilsScript = wpforms_settings.wpforms_plugin_url + 'pro/assets/js/vendor/jquery.intl-tel-input-utils.js';
704
705				$el.intlTelInput( inputOptions );
706
707				// For proper validation, we should preserve the name attribute of the input field.
708				// But we need to modify original input name not to interfere with a hidden input.
709				$el.attr( 'name', 'wpf-temp-' + $el.attr( 'name' ) );
710
711				// Add special class to remove name attribute before submitting.
712				// So, only the hidden input value will be submitted.
713				$el.addClass( 'wpforms-input-temp-name' );
714
715				// Instantly update a hidden form input with a correct data.
716				// Previously "blur" only was used, which is broken in case Enter was used to submit the form.
717				$el.on( 'blur input', function() {
718					if ( $el.intlTelInput( 'isValidNumber' ) || ! app.empty( window.WPFormsEditEntry ) ) {
719						$el.siblings( 'input[type="hidden"]' ).val( $el.intlTelInput( 'getNumber' ) );
720					}
721				} );
722			} );
723
724			// Update hidden input of the `Smart` phone field to be sure the latest value will be submitted.
725			$( '.wpforms-form' ).on( 'wpformsBeforeFormSubmit', function() {
726
727				$( this ).find( '.wpforms-smart-phone-field' ).trigger( 'input' );
728			} );
729		},
730
731		/**
732		 * Payments: Do various payment-related tasks on load.
733		 *
734		 * @since 1.2.6
735		 */
736		loadPayments: function() {
737
738			// Update Total field(s) with latest calculation.
739			$( '.wpforms-payment-total' ).each( function( index, el ) {
740				app.amountTotal( this );
741			} );
742
743			// Credit card validation.
744			if ( typeof $.fn.payment !== 'undefined' ) {
745				$( '.wpforms-field-credit-card-cardnumber' ).payment( 'formatCardNumber' );
746				$( '.wpforms-field-credit-card-cardcvc' ).payment( 'formatCardCVC' );
747			}
748		},
749
750		/**
751		 * Load mailcheck.
752		 *
753		 * @since 1.5.3
754		 */
755		loadMailcheck: function() {
756
757			// Skip loading if `wpforms_mailcheck_enabled` filter return false.
758			if ( ! wpforms_settings.mailcheck_enabled ) {
759				return;
760			}
761
762			// Only load if library exists.
763			if ( typeof $.fn.mailcheck === 'undefined' ) {
764				return;
765			}
766
767			if ( wpforms_settings.mailcheck_domains.length > 0 ) {
768				Mailcheck.defaultDomains = Mailcheck.defaultDomains.concat( wpforms_settings.mailcheck_domains );
769			}
770			if ( wpforms_settings.mailcheck_toplevel_domains.length > 0 ) {
771				Mailcheck.defaultTopLevelDomains = Mailcheck.defaultTopLevelDomains.concat( wpforms_settings.mailcheck_toplevel_domains );
772			}
773
774			// Mailcheck suggestion.
775			$( document ).on( 'blur', '.wpforms-field-email input', function() {
776				var $t = $( this ),
777					id = $t.attr( 'id' );
778
779				$t.mailcheck( {
780					suggested: function( el, suggestion ) {
781						$( '#' + id + '_suggestion' ).remove();
782						var sugg = '<a href="#" class="mailcheck-suggestion" data-id="' + id + '" title="' + wpforms_settings.val_email_suggestion_title + '">' + suggestion.full + '</a>';
783						sugg = wpforms_settings.val_email_suggestion.replace( '{suggestion}', sugg );
784						$( el ).parent().append( '<label class="wpforms-error mailcheck-error" id="' + id + '_suggestion">' + sugg + '</label>' );
785					},
786					empty: function() {
787						$( '#' + id + '_suggestion' ).remove();
788					},
789				} );
790			} );
791
792			// Apply Mailcheck suggestion.
793			$( document ).on( 'click', '.wpforms-field-email .mailcheck-suggestion', function( e ) {
794				var $t = $( this ),
795					id = $t.attr( 'data-id' );
796				e.preventDefault();
797				$( '#' + id ).val( $t.text() );
798				$t.parent().remove();
799			} );
800
801		},
802
803		/**
804		 * Load Choices.js library for all Modern style Dropdown fields (<select>).
805		 *
806		 * @since 1.6.1
807		 */
808		loadChoicesJS: function() {
809
810			// Loads if function exists.
811			if ( ! $.isFunction( window.Choices ) ) {
812
813				return;
814			}
815
816			$( '.wpforms-field-select-style-modern .choicesjs-select, .wpforms-field-payment-select .choicesjs-select' ).each( function( idx, el ) {
817
818				var args          = window.wpforms_choicesjs_config || {},
819					searchEnabled = $( el ).data( 'search-enabled' );
820
821				args.searchEnabled  = 'undefined' !== typeof searchEnabled ? searchEnabled : true;
822				args.callbackOnInit = function() {
823
824					var self      = this,
825						$element  = $( self.passedElement.element ),
826						$input    = $( self.input.element ),
827						sizeClass = $element.data( 'size-class' );
828
829					// Remove hidden attribute and hide `<select>` like a screen-reader text.
830					// It's important for field validation.
831					$element
832						.removeAttr( 'hidden' )
833						.addClass( self.config.classNames.input + '--hidden' );
834
835					// Add CSS-class for size.
836					if ( sizeClass ) {
837						$( self.containerOuter.element ).addClass( sizeClass );
838					}
839
840					/**
841					 * If a multiple select has selected choices - hide a placeholder input.
842					 * We use custom styles like `.screen-reader-text` for it,
843					 * because it avoids an issue with closing a dropdown.
844					 */
845					if ( $element.prop( 'multiple' ) ) {
846
847						// On init event.
848						if ( self.getValue( true ).length ) {
849							$input.addClass( self.config.classNames.input + '--hidden' );
850						}
851					}
852
853					// On change event.
854					$element.on( 'change', function() {
855
856						var validator;
857
858						// Listen if multiple select has choices.
859						if ( $element.prop( 'multiple' ) ) {
860							self.getValue( true ).length > 0 ? $input.addClass( self.config.classNames.input + '--hidden' ) : $input.removeClass( self.config.classNames.input + '--hidden' );
861						}
862
863						validator = $element.closest( 'form' ).data( 'validator' );
864
865						if ( ! validator ) {
866							return;
867						}
868
869						validator.element( $element );
870					} );
871				};
872
873				args.callbackOnCreateTemplates = function() {
874
875					var self      = this,
876						$element  = $( self.passedElement.element );
877
878					return {
879
880						// Change default template for option.
881						option: function( item ) {
882
883							var opt = Choices.defaults.templates.option.call( this, item );
884
885							// Add a `.placeholder` class for placeholder option - it needs for WPForm CL.
886							if ( 'undefined' !== typeof item.placeholder && true === item.placeholder ) {
887								opt.classList.add( 'placeholder' );
888							}
889
890							// Add a `data-amount` attribute for payment dropdown.
891							// It will be copy from a Choices.js `data-custom-properties` attribute.
892							if ( $element.hasClass( 'wpforms-payment-price' ) && 'undefined' !== typeof item.customProperties && null !== item.customProperties ) {
893								opt.dataset.amount = item.customProperties;
894							}
895
896							return opt;
897						},
898					};
899				};
900
901				// Save choicesjs instance for future access.
902				$( el ).data( 'choicesjs', new Choices( el, args ) );
903			} );
904		},
905
906		//--------------------------------------------------------------------//
907		// Binds.
908		//--------------------------------------------------------------------//
909
910		/**
911		 * Element bindings.
912		 *
913		 * @since 1.2.3
914		 */
915		bindUIActions: function() {
916
917			// Pagebreak navigation.
918			$( document ).on( 'click', '.wpforms-page-button', function( event ) {
919				event.preventDefault();
920				app.pagebreakNav( this );
921			} );
922
923			// Payments: Update Total field(s) when latest calculation.
924			$( document ).on( 'change input', '.wpforms-payment-price', function() {
925				app.amountTotal( this, true );
926			} );
927
928			// Payments: Restrict user input payment fields.
929			$( document ).on( 'input', '.wpforms-payment-user-input', function() {
930				var $this = $( this ),
931					amount = $this.val();
932				$this.val( amount.replace( /[^0-9.,]/g, '' ) );
933			} );
934
935			// Payments: Sanitize/format user input amounts.
936			$( document ).on( 'focusout', '.wpforms-payment-user-input', function() {
937				var $this     = $( this ),
938					amount    = $this.val(),
939					sanitized = app.amountSanitize( amount ),
940					formatted = app.amountFormat( sanitized );
941				$this.val( formatted );
942			} );
943
944			// Payments: Update Total field(s) when conditials are processed.
945			$( document ).on( 'wpformsProcessConditionals', function( e, el ) {
946				app.amountTotal( el, true );
947			} );
948
949			// Payment radio/checkbox fields: preselect the selected payment (from dynamic/fallback population).
950			$( function() {
951
952				// Radios.
953				$( '.wpforms-field-radio .wpforms-image-choices-item input:checked' ).change();
954				$( '.wpforms-field-payment-multiple .wpforms-image-choices-item input:checked' ).change();
955
956				// Checkboxes.
957				$( '.wpforms-field-checkbox .wpforms-image-choices-item input' ).change();
958				$( '.wpforms-field-payment-checkbox .wpforms-image-choices-item input' ).change();
959			} );
960
961			// Rating field: hover effect.
962			$( '.wpforms-field-rating-item' ).hover(
963				function() {
964					$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
965					$( this ).prevAll().addBack().addClass( 'hover' );
966				},
967				function() {
968					$( this ).parent().find( '.wpforms-field-rating-item' ).removeClass( 'selected hover' );
969					$( this ).parent().find( 'input:checked' ).parent().prevAll().addBack().addClass( 'selected' );
970				}
971			);
972
973			// Rating field: toggle selected state.
974			$( document ).on( 'change', '.wpforms-field-rating-item input', function() {
975
976				var $this  = $( this ),
977					$wrap  = $this.closest( '.wpforms-field-rating-items' ),
978					$items = $wrap.find( '.wpforms-field-rating-item' );
979
980				$items.removeClass( 'hover selected' );
981				$this.parent().prevAll().addBack().addClass( 'selected' );
982			} );
983
984			// Rating field: preselect the selected rating (from dynamic/fallback population).
985			$( function() {
986				$( '.wpforms-field-rating-item input:checked' ).change();
987			} );
988
989			// Checkbox/Radio/Payment checkbox: make labels keyboard-accessible.
990			$( document ).on( 'keypress', '.wpforms-image-choices-item label', function( event ) {
991				var $this  = $( this ),
992					$field = $this.closest( '.wpforms-field' );
993
994				if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
995					event.preventDefault();
996					return false;
997				}
998
999				// Cause the input to be clicked when clicking the label.
1000				if ( 13 === event.which ) {
1001					$( '#' + $this.attr( 'for' ) ).click();
1002				}
1003			} );
1004
1005			// IE: Click on the `image choice` image should trigger the click event on the input (checkbox or radio) field.
1006			if ( window.document.documentMode ) {
1007				$( document ).on( 'click', '.wpforms-image-choices-item img', function() {
1008
1009					$( this ).closest( 'label' ).find( 'input' ).click();
1010				} );
1011			}
1012
1013			$( document ).on( 'change', '.wpforms-field-checkbox input, .wpforms-field-radio input, .wpforms-field-payment-multiple input, .wpforms-field-payment-checkbox input, .wpforms-field-gdpr-checkbox input', function( event ) {
1014
1015				var $this  = $( this ),
1016					$field = $this.closest( '.wpforms-field' );
1017
1018				if ( $field.hasClass( 'wpforms-conditional-hide' ) ) {
1019					event.preventDefault();
1020					return false;
1021				}
1022
1023				switch ( $this.attr( 'type' ) ) {
1024					case 'radio':
1025						$this.closest( 'ul' ).find( 'li' ).removeClass( 'wpforms-selected' ).find( 'input[type=radio]' ).removeProp( 'checked' );
1026						$this
1027							.prop( 'checked', true )
1028							.closest( 'li' ).addClass( 'wpforms-selected' );
1029						break;
1030
1031					case 'checkbox':
1032						if ( $this.is( ':checked' ) ) {
1033							$this.closest( 'li' ).addClass( 'wpforms-selected' );
1034							$this.prop( 'checked', true );
1035						} else {
1036							$this.closest( 'li' ).removeClass( 'wpforms-selected' );
1037							$this.prop( 'checked', false );
1038						}
1039						break;
1040				}
1041			} );
1042
1043			// Upload fields: Check combined file size.
1044			$( document ).on( 'change', '.wpforms-field-file-upload input[type=file]:not(".dropzone-input")', function() {
1045				var $this       = $( this ),
1046					$uploads    = $this.closest( 'form.wpforms-form' ).find( '.wpforms-field-file-upload input:not(".dropzone-input")' ),
1047					totalSize   = 0,
1048					postMaxSize = Number( wpforms_settings.post_max_size ),
1049					errorMsg    = '<div class="wpforms-error-container-post_max_size">' + wpforms_settings.val_post_max_size + '</div>',
1050					errorCntTpl = '<div class="wpforms-error-container">{errorMsg}</span></div>',
1051					$submitCnt  = $this.closest( 'form.wpforms-form' ).find( '.wpforms-submit-container' ),
1052					$submitBtn  = $submitCnt.find( 'button.wpforms-submit' ),
1053					$errorCnt   = $submitCnt.prev();
1054
1055				// Calculating totalSize.
1056				$uploads.each( function() {
1057					var $upload = $( this ),
1058						i = 0,
1059						len = $upload[0].files.length;
1060					for ( ; i < len; i++ ) {
1061						totalSize += $upload[0].files[i].size;
1062					}
1063				} );
1064
1065				// Checking totalSize.
1066				if ( totalSize > postMaxSize ) {
1067
1068					// Convert sizes to Mb.
1069					totalSize = Number( ( totalSize / 1048576 ).toFixed( 3 ) );
1070					postMaxSize = Number( ( postMaxSize / 1048576 ).toFixed( 3 ) );
1071
1072					// Preparing error message.
1073					errorMsg = errorMsg.replace( /{totalSize}/, totalSize ).replace( /{maxSize}/, postMaxSize );
1074
1075					// Output error message.
1076					if ( $errorCnt.hasClass( 'wpforms-error-container' ) ) {
1077						$errorCnt.find( '.wpforms-error-container-post_max_size' ).remove();
1078						$errorCnt.append( errorMsg );
1079					} else {
1080						$submitCnt.before( errorCntTpl.replace( /{errorMsg}/, errorMsg ) );
1081					}
1082
1083					// Disable submit button.
1084					$submitBtn.prop( 'disabled', true );
1085				} else {
1086
1087					// Remove error and release submit button.
1088					$errorCnt.find( '.wpforms-error-container-post_max_size' ).remove();
1089					$submitBtn.prop( 'disabled', false );
1090				}
1091
1092			} );
1093
1094			// Number Slider field: update hints.
1095			$( document ).on( 'change input', '.wpforms-field-number-slider input[type=range]', function( event ) {
1096				var hintEl = $( event.target ).siblings( '.wpforms-field-number-slider-hint' );
1097
1098				hintEl.html( hintEl.data( 'hint' ).replace( '{value}', '<b>' + event.target.value + '</b>' ) );
1099			} );
1100
1101			// Enter key event.
1102			$( document ).on( 'keydown', '.wpforms-form input', function( e ) {
1103
1104				if ( e.keyCode !== 13 ) {
1105					return;
1106				}
1107
1108				var $t = $( this ),
1109					$page = $t.closest( '.wpforms-page' );
1110
1111				if ( $page.length === 0 ) {
1112					return;
1113				}
1114
1115				if ( [ 'text', 'tel', 'number', 'email', 'url', 'radio', 'checkbox' ].indexOf( $t.attr( 'type' ) ) < 0 ) {
1116					return;
1117				}
1118
1119				if ( $t.hasClass( 'wpforms-datepicker' ) ) {
1120					$t.flatpickr( 'close' );
1121				}
1122
1123				e.preventDefault();
1124
1125				if ( $page.hasClass( 'last' ) ) {
1126					$page.closest( '.wpforms-form' ).find( '.wpforms-submit' ).click();
1127					return;
1128				}
1129
1130				$page.find( '.wpforms-page-next' ).click();
1131			} );
1132
1133			// Allow only numbers, minus and decimal point to be entered into the Numbers field.
1134			$( document ).on( 'keypress', '.wpforms-field-number input', function( e ) {
1135
1136				return /^[-0-9.]+$/.test( String.fromCharCode( e.keyCode || e.which ) );
1137			} );
1138		},
1139
1140		/**
1141		 * Scroll to and focus on the field with error.
1142		 *
1143		 * @since 1.5.8
1144		 *
1145		 * @param {jQuery} $el Form, container or input element jQuery object.
1146		 */
1147		scrollToError: function( $el ) {
1148
1149			if ( $el.length === 0 ) {
1150				return;
1151			}
1152
1153			// Look for a field with an error inside an $el.
1154			var $field = $el.find( '.wpforms-field.wpforms-has-error' );
1155
1156			// Look outside in not found inside.
1157			if ( $field.length === 0 ) {
1158				$field = $el.closest( '.wpforms-field' );
1159			}
1160
1161			if ( $field.length === 0 ) {
1162				return;
1163			}
1164
1165			var offset = $field.offset();
1166
1167			if ( typeof offset === 'undefined' ) {
1168				return;
1169			}
1170
1171			app.animateScrollTop( offset.top - 75, 750 ).done( function() {
1172				var $error = $field.find( '.wpforms-error' ).first();
1173				if ( app.isFunction( $error.focus ) ) {
1174					$error.focus();
1175				}
1176			} );
1177		},
1178
1179		/**
1180		 * Update Pagebreak navigation.
1181		 *
1182		 * @since 1.2.2
1183		 *
1184		 * @param {jQuery} el jQuery element object.
1185		 */
1186		pagebreakNav: function( el ) {
1187
1188			var $this      = $( el ),
1189				valid      = true,
1190				action     = $this.data( 'action' ),
1191				page       = $this.data( 'page' ),
1192				page2      = page,
1193				next       = page + 1,
1194				prev       = page - 1,
1195				$form      = $this.closest( '.wpforms-form' ),
1196				$page      = $form.find( '.wpforms-page-' + page ),
1197				$submit    = $form.find( '.wpforms-submit-container' ),
1198				$indicator = $form.find( '.wpforms-page-indicator' ),
1199				$reCAPTCHA = $form.find( '.wpforms-recaptcha-container' ),
1200				pageScroll = false;
1201
1202			// Page scroll.
1203			// TODO: cleanup this BC with wpform_pageScroll.
1204			if ( false === window.wpforms_pageScroll ) {
1205				pageScroll = false;
1206			} else if ( ! app.empty( window.wpform_pageScroll ) ) {
1207				pageScroll = window.wpform_pageScroll;
1208			} else {
1209				pageScroll = $indicator.data( 'scroll' ) !== 0 ? 75 : false;
1210			}
1211
1212			// Toggling between the pages.
1213			if ( 'next' === action ) {
1214
1215				// Validate.
1216				if ( typeof $.fn.validate !== 'undefined' ) {
1217					$page.find( ':input' ).each( function( index, el ) {
1218
1219						// Skip input fields without `name` attribute, which could have fields.
1220						// E.g. `Placeholder` input for Modern dropdown.
1221						if ( ! $( el ).attr( 'name' ) ) {
1222							return;
1223						}
1224
1225						if ( ! $( el ).valid() ) {
1226							valid = false;
1227						}
1228					} );
1229
1230					// Scroll to first/top error on page.
1231					app.scrollToError( $page );
1232				}
1233
1234				// Move to the next page.
1235				if ( valid ) {
1236					page2 = next;
1237					$page.hide();
1238					var $nextPage = $form.find( '.wpforms-page-' + next );
1239					$nextPage.show();
1240					if ( $nextPage.hasClass( 'last' ) ) {
1241						$reCAPTCHA.show();
1242						$submit.show();
1243					}
1244					if ( pageScroll ) {
1245
1246						// Scroll to top of the form.
1247						app.animateScrollTop( $form.offset().top - pageScroll, 750 );
1248					}
1249					$this.trigger( 'wpformsPageChange', [ page2, $form ] );
1250				}
1251			} else if ( 'prev' === action ) {
1252
1253				// Move to the prev page.
1254				page2 = prev;
1255				$page.hide();
1256				$form.find( '.wpforms-page-' + prev ).show();
1257				$reCAPTCHA.hide();
1258				$submit.hide();
1259				if ( pageScroll ) {
1260
1261					// Scroll to the top of the form.
1262					app.animateScrollTop( $form.offset().top - pageScroll );
1263				}
1264				$this.trigger( 'wpformsPageChange', [ page2, $form ] );
1265			}
1266
1267			if ( $indicator ) {
1268				var theme = $indicator.data( 'indicator' ),
1269					color = $indicator.data( 'indicator-color' );
1270				if ( 'connector' === theme || 'circles' === theme ) {
1271					$indicator.find( '.wpforms-page-indicator-page' ).removeClass( 'active' );
1272					$indicator.find( '.wpforms-page-indicator-page-' + page2 ).addClass( 'active' );
1273					$indicator.find( '.wpforms-page-indicator-page-number' ).removeAttr( 'style' );
1274					$indicator.find( '.active .wpforms-page-indicator-page-number' ).css( 'background-color', color );
1275					if ( 'connector' === theme ) {
1276						$indicator.find( '.wpforms-page-indicator-page-triangle' ).removeAttr( 'style' );
1277						$indicator.find( '.active .wpforms-page-indicator-page-triangle' ).css( 'border-top-color', color );
1278					}
1279				} else if ( 'progress' === theme ) {
1280					var $pageTitle = $indicator.find( '.wpforms-page-indicator-page-title' ),
1281						$pageSep   = $indicator.find( '.wpforms-page-indicator-page-title-sep' ),
1282						totalPages = $form.find( '.wpforms-page' ).length,
1283						width = ( page2 / totalPages ) * 100;
1284					$indicator.find( '.wpforms-page-indicator-page-progress' ).css( 'width', width + '%' );
1285					$indicator.find( '.wpforms-page-indicator-steps-current' ).text( page2 );
1286					if ( $pageTitle.data( 'page-' + page2 + '-title' ) ) {
1287						$pageTitle.css( 'display', 'inline' ).text( $pageTitle.data( 'page-' + page2 + '-title' ) );
1288						$pageSep.css( 'display', 'inline' );
1289					} else {
1290						$pageTitle.css( 'display', 'none' );
1291						$pageSep.css( 'display', 'none' );
1292					}
1293				}
1294			}
1295		},
1296
1297		/**
1298		 * OptinMonster compatibility.
1299		 *
1300		 * Re-initialize after OptinMonster loads to accommodate changes that
1301		 * have occurred to the DOM.
1302		 *
1303		 * @since 1.5.0
1304		 */
1305		bindOptinMonster: function() {
1306
1307			// OM v5.
1308			document.addEventListener( 'om.Campaign.load', function( event ) {
1309				app.ready();
1310				app.optinMonsterRecaptchaReset( event.detail.Campaign.data.id );
1311			} );
1312
1313			// OM Legacy.
1314			$( document ).on( 'OptinMonsterOnShow', function( event, data, object ) {
1315				app.ready();
1316				app.optinMonsterRecaptchaReset( data.optin );
1317			} );
1318		},
1319
1320		/**
1321		 * Reset/recreate hCaptcha/reCAPTCHA v2 inside OptinMonster.
1322		 *
1323		 * @since 1.5.0
1324		 * @since 1.6.4 Added hCaptcha support.
1325		 *
1326		 * @param {string} optinId OptinMonster ID.
1327		 */
1328		optinMonsterRecaptchaReset: function( optinId ) {
1329
1330			var $form             = $( '#om-' + optinId ).find( '.wpforms-form' ),
1331				$captchaContainer = $form.find( '.wpforms-recaptcha-container' ),
1332				$captcha          = $form.find( '.g-recaptcha' ),
1333				captchaSiteKey    = $captcha.attr( 'data-sitekey' ),
1334				captchaID         = 'recaptcha-' + Date.now(),
1335				apiVar            = $captchaContainer.hasClass( 'wpforms-is-hcaptcha' ) ? hcaptcha : grecaptcha;
1336
1337			if ( $form.length && $captcha.length ) {
1338
1339				$captcha.remove();
1340				$captchaContainer.prepend( '<div class="g-recaptcha" id="' + captchaID + '" data-sitekey="' + captchaSiteKey + '"></div>' );
1341
1342				apiVar.render(
1343					captchaID,
1344					{
1345						sitekey: captchaSiteKey,
1346						callback: function() {
1347							wpformsRecaptchaCallback( $( '#' + captchaID ) );
1348						},
1349					}
1350				);
1351			}
1352		},
1353
1354		//--------------------------------------------------------------------//
1355		// Other functions.
1356		//--------------------------------------------------------------------//
1357
1358		/**
1359		 * Payments: Calculate total.
1360		 *
1361		 * @since 1.2.3
1362		 * @since 1.5.1 Added support for payment-checkbox field.
1363		 *
1364		 * @param {object} el jQuery DOM object.
1365		 * @param {boolean} validate Whether to validate or not.
1366		 */
1367		amountTotal: function( el, validate ) {
1368
1369			validate = validate || false;
1370
1371			var $form = $( el ).closest( '.wpforms-form' ),
1372				total = 0,
1373				totalFormatted,
1374				totalFormattedSymbol,
1375				currency = app.getCurrency();
1376
1377			$( '.wpforms-payment-price', $form ).each( function( index, el ) {
1378
1379				var amount = 0,
1380					$this = $( this );
1381
1382				if ( $this.closest( '.wpforms-field-payment-single' ).hasClass( 'wpforms-conditional-hide' ) ) {
1383					return;
1384				}
1385				if ( 'text' === $this.attr( 'type' ) || 'hidden' === $this.attr( 'type' ) ) {
1386					amount = $this.val();
1387				} else if ( ( 'radio' === $this.attr( 'type' ) || 'checkbox' === $this.attr( 'type' ) ) && $this.is( ':checked' ) ) {
1388					amount = $this.data( 'amount' );
1389				} else if ( $this.is( 'select' ) && $this.find( 'option:selected' ).length > 0 ) {
1390					amount = $this.find( 'option:selected' ).data( 'amount' );
1391				}
1392				if ( ! app.empty( amount ) ) {
1393					amount = app.amountSanitize( amount );
1394					total = Number( total ) + Number( amount );
1395				}
1396			} );
1397
1398			totalFormatted = app.amountFormat( total );
1399
1400			if ( 'left' === currency.symbol_pos ) {
1401				totalFormattedSymbol = currency.symbol + ' ' + totalFormatted;
1402			} else {
1403				totalFormattedSymbol = totalFormatted + ' ' + currency.symbol;
1404			}
1405
1406			$form.find( '.wpforms-payment-total' ).each( function( index, el ) {
1407				if ( 'hidden' === $( this ).attr( 'type' ) || 'text' === $( this ).attr( 'type' ) ) {
1408					$( this ).val( totalFormattedSymbol );
1409					if ( 'text' === $( this ).attr( 'type' ) && validate && $form.data( 'validator' ) ) {
1410						$( this ).valid();
1411					}
1412				} else {
1413					$( this ).text( totalFormattedSymbol );
1414				}
1415			} );
1416		},
1417
1418		/**
1419		 * Sanitize amount and convert to standard format for calculations.
1420		 *
1421		 * @since 1.2.6
1422		 *
1423		 * @param {string} amount Amount to sanitize.
1424		 *
1425		 * @returns {string} Sanitized amount.
1426		 */
1427		amountSanitize: function( amount ) {
1428
1429			var currency = app.getCurrency();
1430
1431			amount = amount.toString().replace( /[^0-9.,]/g, '' );
1432
1433			if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) {
1434				if ( '.' === currency.thousands_sep && amount.indexOf( currency.thousands_sep ) !== -1 ) {
1435					amount = amount.replace( currency.thousands_sep, '' );
1436				} else if ( '' === currency.thousands_sep && amount.indexOf( '.' ) !== -1 ) {
1437					amount = amount.replace( '.', '' );
1438				}
1439				amount = amount.replace( currency.decimal_sep, '.' );
1440			} else if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) {
1441				amount = amount.replace( currency.thousands_sep, '' );
1442			}
1443
1444			return app.numberFormat( amount, 2, '.', '' );
1445		},
1446
1447		/**
1448		 * Format amount.
1449		 *
1450		 * @since 1.2.6
1451		 *
1452		 * @param {string} amount Amount to format.
1453		 *
1454		 * @returns {string} Formatted amount.
1455		 */
1456		amountFormat: function( amount ) {
1457
1458			var currency = app.getCurrency();
1459
1460			amount = String( amount );
1461
1462			// Format the amount
1463			if ( ',' === currency.decimal_sep && ( amount.indexOf( currency.decimal_sep ) !== -1 ) ) {
1464				var sepFound = amount.indexOf( currency.decimal_sep ),
1465					whole    = amount.substr( 0, sepFound ),
1466					part     = amount.substr( sepFound + 1, amount.strlen - 1 );
1467				amount = whole + '.' + part;
1468			}
1469
1470			// Strip , from the amount (if set as the thousands separator)
1471			if ( ',' === currency.thousands_sep && ( amount.indexOf( currency.thousands_sep ) !== -1 ) ) {
1472				amount = amount.replace( ',', '' );
1473			}
1474
1475			if ( app.empty( amount ) ) {
1476				amount = 0;
1477			}
1478
1479			return app.numberFormat( amount, 2, currency.decimal_sep, currency.thousands_sep );
1480		},
1481
1482		/**
1483		 * Get site currency settings.
1484		 *
1485		 * @since 1.2.6
1486		 *
1487		 * @returns {object} Currency settings.
1488		 */
1489		getCurrency: function() {
1490
1491			var currency = {
1492				code: 'USD',
1493				thousands_sep: ',',
1494				decimal_sep: '.',
1495				symbol: '$',
1496				symbol_pos: 'left',
1497			};
1498
1499			// Backwards compatibility.
1500			if ( typeof wpforms_settings.currency_code !== 'undefined' ) {
1501				currency.code = wpforms_settings.currency_code;
1502			}
1503			if ( typeof wpforms_settings.currency_thousands !== 'undefined' ) {
1504				currency.thousands_sep = wpforms_settings.currency_thousands;
1505			}
1506			if ( typeof wpforms_settings.currency_decimal !== 'undefined' ) {
1507				currency.decimal_sep = wpforms_settings.currency_decimal;
1508			}
1509			if ( typeof wpforms_settings.currency_symbol !== 'undefined' ) {
1510				currency.symbol = wpforms_settings.currency_symbol;
1511			}
1512			if ( typeof wpforms_settings.currency_symbol_pos !== 'undefined' ) {
1513				currency.symbol_pos = wpforms_settings.currency_symbol_pos;
1514			}
1515
1516			return currency;
1517		},
1518
1519		/**
1520		 * Format number.
1521		 *
1522		 * @see http://locutus.io/php/number_format/
1523		 *
1524		 * @since 1.2.6
1525		 *
1526		 * @param {string} number Number to format.
1527		 * @param {number} decimals How many decimals?
1528		 * @param {string} decimalSep Decimals separator.
1529		 * @param {string} thousandsSep Thousands separator.
1530		 *
1531		 * @returns {string} Formatted number.
1532		 */
1533		numberFormat: function( number, decimals, decimalSep, thousandsSep ) {
1534
1535			number = ( number + '' ).replace( /[^0-9+\-Ee.]/g, '' );
1536			var n = ! isFinite( +number ) ? 0 : +number;
1537			var prec = ! isFinite( +decimals ) ? 0 : Math.abs( decimals );
1538			var sep = ( 'undefined' === typeof thousandsSep ) ? ',' : thousandsSep;
1539			var dec = ( 'undefined' === typeof decimalSep ) ? '.' : decimalSep;
1540			var s;
1541
1542			var toFixedFix = function( n, prec ) {
1543				var k = Math.pow( 10, prec );
1544				return '' + ( Math.round( n * k ) / k ).toFixed( prec );
1545			};
1546
1547			// @todo: for IE parseFloat(0.55).toFixed(0) = 0;
1548			s = ( prec ? toFixedFix( n, prec ) : '' + Math.round( n ) ).split( '.' );
1549			if ( s[0].length > 3 ) {
1550				s[0] = s[0].replace( /\B(?=(?:\d{3})+(?!\d))/g, sep );
1551			}
1552			if ( ( s[1] || '' ).length < prec ) {
1553				s[1] = s[1] || '';
1554				s[1] += new Array( prec - s[1].length + 1 ).join( '0' );
1555			}
1556
1557			return s.join( dec );
1558		},
1559
1560		/**
1561		 * Empty check similar to PHP.
1562		 *
1563		 * @see http://locutus.io/php/empty/
1564		 *
1565		 * @since 1.2.6
1566		 *
1567		 * @param {mixed} mixedVar Variable to check.
1568		 *
1569		 * @returns {boolean} Whether the var is empty or not.
1570		 */
1571		empty: function( mixedVar ) {
1572
1573			var undef;
1574			var key;
1575			var i;
1576			var len;
1577			var emptyValues = [ undef, null, false, 0, '', '0' ];
1578
1579			for ( i = 0, len = emptyValues.length; i < len; i++ ) {
1580				if ( mixedVar === emptyValues[i] ) {
1581					return true;
1582				}
1583			}
1584
1585			if ( 'object' === typeof mixedVar ) {
1586				for ( key in mixedVar ) {
1587					if ( mixedVar.hasOwnProperty( key ) ) {
1588						return false;
1589					}
1590				}
1591				return true;
1592			}
1593
1594			return false;
1595		},
1596
1597		/**
1598		 * Set cookie container user UUID.
1599		 *
1600		 * @since 1.3.3
1601		 */
1602		setUserIndentifier: function() {
1603
1604			if ( ( ( ! window.hasRequiredConsent && typeof wpforms_settings !== 'undefined' && wpforms_settings.uuid_cookie ) || ( window.hasRequiredConsent && window.hasRequiredConsent() ) ) && ! app.getCookie( '_wpfuuid' ) ) {
1605
1606				// Generate UUID - http://stackoverflow.com/a/873856/1489528
1607				var s         = new Array( 36 ),
1608					hexDigits = '0123456789abcdef',
1609					uuid;
1610
1611				for ( var i = 0; i < 36; i++ ) {
1612					s[i] = hexDigits.substr( Math.floor( Math.random() * 0x10 ), 1 );
1613				}
1614				s[14] = '4';
1615				s[19] = hexDigits.substr( ( s[19] & 0x3 ) | 0x8, 1 );
1616				s[8]  = s[13] = s[18] = s[23] = '-';
1617
1618				uuid = s.join( '' );
1619
1620				app.createCookie( '_wpfuuid', uuid, 3999 );
1621			}
1622		},
1623
1624		/**
1625		 * Create cookie.
1626		 *
1627		 * @since 1.3.3
1628		 *
1629		 * @param {string} name  Cookie name.
1630		 * @param {string} value Cookie value.
1631		 * @param {string} days  Whether it should expire and when.
1632		 */
1633		createCookie: function( name, value, days ) {
1634
1635			var expires = '';
1636			var secure = '';
1637
1638			if ( wpforms_settings.is_ssl ) {
1639				secure = ';secure';
1640			}
1641
1642			// If we have a days value, set it in the expiry of the cookie.
1643			if ( days ) {
1644
1645				// If -1 is our value, set a session-based cookie instead of a persistent cookie.
1646				if ( '-1' === days ) {
1647					expires = '';
1648				} else {
1649					var date = new Date();
1650					date.setTime( date.getTime() + ( days * 24 * 60 * 60 * 1000 ) );
1651					expires = ';expires=' + date.toGMTString();
1652				}
1653			} else {
1654				expires = ';expires=Thu, 01 Jan 1970 00:00:01 GMT';
1655			}
1656
1657			// Write the cookie.
1658			document.cookie = name + '=' + value + expires + ';path=/;samesite=strict' + secure;
1659		},
1660
1661		/**
1662		 * Retrieve cookie.
1663		 *
1664		 * @since 1.3.3
1665		 *
1666		 * @param {string} name Cookie name.
1667		 *
1668		 * @returns {string|null} Cookie value or null when it doesn't exist.
1669		 */
1670		getCookie: function( name ) {
1671
1672			var nameEQ = name + '=',
1673				ca     = document.cookie.split( ';' );
1674
1675			for ( var i = 0; i < ca.length; i++ ) {
1676				var c = ca[i];
1677				while ( ' ' === c.charAt( 0 ) ) {
1678					c = c.substring( 1, c.length );
1679				}
1680				if ( 0 === c.indexOf( nameEQ ) ) {
1681					return c.substring( nameEQ.length, c.length );
1682				}
1683			}
1684
1685			return null;
1686		},
1687
1688		/**
1689		 * Delete cookie.
1690		 *
1691		 * @since 1.3.3
1692		 *
1693		 * @param {string} name Cookie name.
1694		 */
1695		removeCookie: function( name ) {
1696
1697			app.createCookie( name, '', -1 );
1698		},
1699
1700		/**
1701		 * Get user browser preferred language.
1702		 *
1703		 * @since 1.5.2
1704		 *
1705		 * @returns {string} Language code.
1706		 */
1707		getFirstBrowserLanguage: function() {
1708			var nav = window.navigator,
1709				browserLanguagePropertyKeys = [ 'language', 'browserLanguage', 'systemLanguage', 'userLanguage' ],
1710				i,
1711				language;
1712
1713			// Support for HTML 5.1 "navigator.languages".
1714			if ( Array.isArray( nav.languages ) ) {
1715				for ( i = 0; i < nav.languages.length; i++ ) {
1716					language = nav.languages[ i ];
1717					if ( language && language.length ) {
1718						return language;
1719					}
1720				}
1721			}
1722
1723			// Support for other well known properties in browsers.
1724			for ( i = 0; i < browserLanguagePropertyKeys.length; i++ ) {
1725				language = nav[ browserLanguagePropertyKeys[ i ] ];
1726				if ( language && language.length ) {
1727					return language;
1728				}
1729			}
1730
1731			return '';
1732		},
1733
1734		/**
1735		 * Asynchronously fetches country code using current IP
1736		 * and executes a callback provided with a country code parameter.
1737		 *
1738		 * @since 1.5.2
1739		 *
1740		 * @param {Function} callback Executes once the fetch is completed.
1741		 */
1742		currentIpToCountry: function( callback ) {
1743
1744			var fallback = function() {
1745
1746				$.get( 'https://ipapi.co/jsonp', function() {}, 'jsonp' )
1747					.always( function( resp ) {
1748						var countryCode = ( resp && resp.country ) ? resp.country : '';
1749						if ( ! countryCode ) {
1750							var lang = app.getFirstBrowserLanguage();
1751							countryCode = lang.indexOf( '-' ) > -1 ? lang.split( '-' ).pop() : '';
1752						}
1753						callback( countryCode );
1754					} );
1755			};
1756
1757			$.get( 'https://geo.wpforms.com/v3/geolocate/json' )
1758				.done( function( resp ) {
1759					if ( resp && resp.country_iso ) {
1760						callback( resp.country_iso );
1761					} else {
1762						fallback();
1763					}
1764				} )
1765				.fail( function( resp ) {
1766					fallback();
1767				} );
1768		},
1769
1770		/**
1771		 * Form submit.
1772		 *
1773		 * @since 1.5.3
1774		 *
1775		 * @param {jQuery} $form Form element.
1776		 */
1777		formSubmit: function( $form ) {
1778
1779			$form.trigger( 'wpformsBeforeFormSubmit' );
1780
1781			if ( $form.hasClass( 'wpforms-ajax-form' ) && typeof FormData !== 'undefined' ) {
1782				app.formSubmitAjax( $form );
1783			} else {
1784				app.formSubmitNormal( $form );
1785			}
1786		},
1787
1788		/**
1789		 * Normal form submit with page reload.
1790		 *
1791		 * @since 1.5.3
1792		 *
1793		 * @param {jQuery} $form Form element.
1794		 */
1795		formSubmitNormal: function( $form ) {
1796
1797			if ( ! $form.length ) {
1798				return;
1799			}
1800
1801			var $submit     = $form.find( '.wpforms-submit' ),
1802				recaptchaID = $submit.get( 0 ).recaptchaID;
1803
1804			if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
1805				$submit.get( 0 ).recaptchaID = false;
1806			}
1807
1808			$form.get( 0 ).submit();
1809		},
1810
1811		/**
1812		 * Reset form captcha.
1813		 *
1814		 * @since 1.5.3
1815		 * @since 1.6.4 Added hCaptcha support.
1816		 *
1817		 * @param {jQuery} $form Form element.
1818		 */
1819		resetFormRecaptcha: function( $form ) {
1820
1821			if ( ! $form || ! $form.length ) {
1822				return;
1823			}
1824
1825			if ( typeof hcaptcha === 'undefined' && typeof grecaptcha === 'undefined' ) {
1826				return;
1827			}
1828
1829			var $captchaContainer = $form.find( '.wpforms-recaptcha-container' ),
1830				apiVar            = $captchaContainer.hasClass( 'wpforms-is-hcaptcha' ) ? hcaptcha : grecaptcha,
1831				recaptchaID;
1832
1833			// Check for invisible recaptcha first.
1834			recaptchaID = $form.find( '.wpforms-submit' ).get( 0 ).recaptchaID;
1835
1836			// Check for hcaptcha/recaptcha v2, if invisible recaptcha is not found.
1837			if ( app.empty( recaptchaID ) && recaptchaID !== 0 ) {
1838				recaptchaID = $form.find( '.g-recaptcha' ).data( 'recaptcha-id' );
1839			}
1840
1841			// Reset captcha.
1842			if ( ! app.empty( recaptchaID ) || recaptchaID === 0 ) {
1843				apiVar.reset( recaptchaID );
1844			}
1845		},
1846
1847		/**
1848		 * Console log AJAX error.
1849		 *
1850		 * @since 1.5.3
1851		 *
1852		 * @param {string} error Error text (optional).
1853		 */
1854		consoleLogAjaxError: function( error ) {
1855
1856			if ( error ) {
1857				console.error( 'WPForms AJAX submit error:\n%s', error ); // eslint-disable-line no-console
1858			} else {
1859				console.error( 'WPForms AJAX submit error' ); // eslint-disable-line no-console
1860			}
1861		},
1862
1863		/**
1864		 * Display form AJAX errors.
1865		 *
1866		 * @since 1.5.3
1867		 *
1868		 * @param {jQuery} $form Form element.
1869		 * @param {object} errors Errors in format { general: { generalErrors }, field: { fieldErrors } }.
1870		 */
1871		displayFormAjaxErrors: function( $form, errors ) {
1872
1873			if ( 'string' === typeof errors ) {
1874				app.displayFormAjaxGeneralErrors( $form, errors );
1875				return;
1876			}
1877
1878			errors = errors && ( 'errors' in errors ) ? errors.errors : null;
1879
1880			if ( app.empty( errors ) || ( app.empty( errors.general ) && app.empty( errors.field ) ) ) {
1881				app.consoleLogAjaxError();
1882				return;
1883			}
1884
1885			if ( ! app.empty( errors.general ) ) {
1886				app.displayFormAjaxGeneralErrors( $form, errors.general );
1887			}
1888
1889			if ( ! app.empty( errors.field ) ) {
1890				app.displayFormAjaxFieldErrors( $form, errors.field );
1891			}
1892		},
1893
1894		/**
1895		 * Display form AJAX general errors that cannot be displayed using jQuery Validation plugin.
1896		 *
1897		 * @since 1.5.3
1898		 *
1899		 * @param {jQuery} $form Form element.
1900		 * @param {object} errors Errors in format { errorType: errorText }.
1901		 */
1902		displayFormAjaxGeneralErrors: function( $form, errors ) {
1903
1904			if ( ! $form || ! $form.length ) {
1905				return;
1906			}
1907
1908			if ( app.empty( errors ) ) {
1909				return;
1910			}
1911
1912			// Safety net for random errors thrown by a third-party code. Should never be used intentionally.
1913			if ( 'string' === typeof errors ) {
1914				$form.find( '.wpforms-submit-container' ).before( '<div class="wpforms-error-container">' + errors + '</div>' );
1915				return;
1916			}
1917
1918			$.each( errors, function( type, html ) {
1919				switch ( type ) {
1920					case 'header':
1921						$form.prepend( html );
1922						break;
1923					case 'footer':
1924						$form.find( '.wpforms-submit-container' ).before( html );
1925						break;
1926					case 'recaptcha':
1927						$form.find( '.wpforms-recaptcha-container' ).append( html );
1928						break;
1929				}
1930			} );
1931		},
1932
1933		/**
1934		 * Clear forms AJAX general errors that cannot be cleared using jQuery Validation plugin.
1935		 *
1936		 * @since 1.5.3
1937		 *
1938		 * @param {jQuery} $form Form element.
1939		 */
1940		clearFormAjaxGeneralErrors: function( $form ) {
1941
1942			$form.find( '.wpforms-error-container' ).remove();
1943			$form.find( '#wpforms-field_recaptcha-error' ).remove();
1944		},
1945
1946		/**
1947		 * Display form AJAX field errors using jQuery Validation plugin.
1948		 *
1949		 * @since 1.5.3
1950		 *
1951		 * @param {jQuery} $form Form element.
1952		 * @param {object} errors Errors in format { fieldName: errorText }.
1953		 */
1954		displayFormAjaxFieldErrors: function( $form, errors ) {
1955
1956			if ( ! $form || ! $form.length ) {
1957				return;
1958			}
1959
1960			if ( app.empty( errors ) ) {
1961				return;
1962			}
1963
1964			var validator = $form.data( 'validator' );
1965
1966			if ( ! validator ) {
1967				return;
1968			}
1969
1970			validator.showErrors( errors );
1971			validator.focusInvalid();
1972		},
1973
1974		/**
1975		 * Submit a form using AJAX.
1976		 *
1977		 * @since 1.5.3
1978		 *
1979		 * @param {jQuery} $form Form element.
1980		 *
1981		 * @returns {JQueryXHR|JQueryDeferred} Promise like object for async callbacks.
1982		 */
1983		formSubmitAjax: function( $form ) {
1984
1985			if ( ! $form.length ) {
1986				return $.Deferred().reject(); // eslint-disable-line new-cap
1987			}
1988
1989			var $container = $form.closest( '.wpforms-container' ),
1990				$spinner = $form.find( '.wpforms-submit-spinner' ),
1991				$confirmationScroll,
1992				formData,
1993				args;
1994
1995			$container.css( 'opacity', 0.6 );
1996			$spinner.show();
1997
1998			app.clearFormAjaxGeneralErrors( $form );
1999
2000			formData = new FormData( $form.get( 0 ) );
2001			formData.append( 'action', 'wpforms_submit' );
2002			formData.append( 'page_url', window.location.href );
2003
2004			args = {
2005				type       : 'post',
2006				dataType   : 'json',
2007				url        : wpforms_settings.ajaxurl,
2008				data       : formData,
2009				cache      : false,
2010				contentType: false,
2011				processData: false,
2012			};
2013
2014			args.success = function( json ) {
2015
2016				if ( ! json ) {
2017					app.consoleLogAjaxError();
2018					return;
2019				}
2020
2021				if ( json.data && json.data.action_required ) {
2022					$form.trigger( 'wpformsAjaxSubmitActionRequired', json );
2023					return;
2024				}
2025
2026				if ( ! json.success ) {
2027					app.resetFormRecaptcha( $form );
2028					app.displayFormAjaxErrors( $form, json.data );
2029					$form.trigger( 'wpformsAjaxSubmitFailed', json );
2030					return;
2031				}
2032
2033				$form.trigger( 'wpformsAjaxSubmitSuccess', json );
2034
2035				if ( ! json.data ) {
2036					return;
2037				}
2038
2039				if ( json.data.redirect_url ) {
2040					$form.trigger( 'wpformsAjaxSubmitBeforeRedirect', json );
2041					window.location = json.data.redirect_url;
2042					return;
2043				}
2044
2045				if ( json.data.confirmation ) {
2046					$container.html( json.data.confirmation );
2047					$confirmationScroll = $container.find( 'div.wpforms-confirmation-scroll' );
2048					if ( $confirmationScroll.length ) {
2049						app.animateScrollTop( $confirmationScroll.offset().top - 100 );
2050					}
2051				}
2052			};
2053
2054			args.error = function( jqHXR, textStatus, error ) {
2055
2056				app.consoleLogAjaxError( error );
2057
2058				$form.trigger( 'wpformsAjaxSubmitError', [ jqHXR, textStatus, error ] );
2059			};
2060
2061			args.complete = function( jqHXR, textStatus ) {
2062
2063				// Do not make form active if the action is required.
2064				if ( jqHXR.responseJSON && jqHXR.responseJSON.data && jqHXR.responseJSON.data.action_required ) {
2065					return;
2066				}
2067
2068				var $submit     = $form.find( '.wpforms-submit' ),
2069					submitText  = $submit.data( 'submit-text' );
2070
2071				if ( submitText ) {
2072					$submit.text( submitText );
2073				}
2074				$submit.prop( 'disabled', false );
2075
2076				$container.css( 'opacity', '' );
2077				$spinner.hide();
2078
2079				$form.trigger( 'wpformsAjaxSubmitCompleted', [ jqHXR, textStatus ] );
2080			};
2081
2082			$form.trigger( 'wpformsAjaxBeforeSubmit' );
2083
2084			return $.ajax( args );
2085		},
2086
2087		/**
2088		 * Scroll to position with animation.
2089		 *
2090		 * @since 1.5.3
2091		 *
2092		 * @param {number} position Position (in pixels) to scroll to,
2093		 * @param {number} duration Animation duration.
2094		 * @param {Function} complete Function to execute after animation is complete.
2095		 *
2096		 * @returns {JQueryPromise} Promise object for async callbacks.
2097		 */
2098		animateScrollTop: function( position, duration, complete ) {
2099
2100			duration = duration || 1000;
2101			complete = app.isFunction( complete ) ? complete : function() {};
2102			return $( 'html, body' ).animate( { scrollTop: parseInt( position, 10 ) }, { duration: duration, complete: complete } ).promise();
2103		},
2104
2105		/**
2106		 * Check if object is a function.
2107		 *
2108		 * @since 1.5.8
2109		 *
2110		 * @param {mixed} object Object to check if it is function.
2111		 *
2112		 * @returns {boolean} True if object is a function.
2113		 */
2114		isFunction: function( object ) {
2115
2116			return !! ( object && object.constructor && object.call && object.apply );
2117		},
2118	};
2119
2120	return app;
2121
2122}( document, window, jQuery ) );
2123
2124// Initialize.
2125wpforms.init();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.