PageSourceSearch

https://www.repaircafe-orsay.org/wp-content/plugins/wpforms/assets/js/wpforms.js?ver=1.7.0

js repaircafe-orsay.org collected 2026-09-24 10:41:37 UTC 67,045 bytes, 2,250 lines download raw bytes

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