// source --> https://landoll.com/wp-content/plugins/formidable-pro/js/formidablepro.js?ver=6.29 
function frmProFormJS() {
	/* globals frm_js, frmFrontForm, google, jQuery */
	/* globals __frmDatepicker, __frmDropzone, __frmUniqueTimes */
	/* globals __FRMLOOKUP, __FRMCALC, __FRMRULES, __FRMCURR */
	/* globals __frmChosen, __frmHideOrShowFields, __frmDepDynamicFields */
	/* globals __frmDepLookupFields, __frmMasks, __FRMTABLES */
	/* globals frmCheckboxI18n */

	'use strict';
	let currentlyAddingRow = false;
	let action = '';
	let processesRunning = 0;
	const lookupQueues = {};
	let hiddenSubmitButtons = [];
	let pendingDynamicFieldAjax = [];
	const pendingLookupFieldAjax = [];
	let listWrappersOriginal = {};
	const intlPhoneInputs = {};
	let autoId = 0;

	function setNextPage( e ) {
		var closestButton;
		if ( this.className.includes('frm_rootline_title') ) {
			closestButton = this.previousElementSibling;
			closestButton.click();
			return;
		}

		if ( this.className.includes('frm_rootline_single') ) {
			this.querySelector( 'input' ).click();
			return;
		}

		/*jshint validthis:true */
		var $thisObj = jQuery( this );
		var thisType = $thisObj.attr( 'type' );
		if ( thisType !== 'submit' ) {
			e.preventDefault();
		}

		var f = $thisObj.parents( 'form' ).first(),
			v = '',
			d = '',
			thisName = this.name;

		if ( thisName === 'frm_prev_page' || this.className.includes('frm_prev_page') ) {
			v = jQuery( f ).find( '.frm_next_page' ).attr( 'id' ).replace( 'frm_next_p_', '' );
			if ( f.length ) {
				maybeAddEmptyHiddenInputsForCheckboxes( f.get( 0 ) );
			}
		} else if ( thisName === 'frm_save_draft' || this.className.includes('frm_save_draft') ) {
			d = 1;
		} else if ( this.className.includes('frm_page_skip') ) {
			var goingTo = $thisObj.data( 'page' );
			var formId = jQuery( f ).find( 'input[name="form_id"]' ).val();
			var orderField = jQuery( f ).find( 'input[name="frm_page_order_' + formId + '"]' );
			jQuery( f ).append( '<input name="frm_last_page" type="hidden" value="' + orderField.val() + '" />' );

			if ( goingTo === '' ) {
				orderField.remove();
			} else {
				orderField.val( goingTo );
			}
		} else if ( this.className.includes('frm_page_back') ) {
			v = $thisObj.data( 'page' );
		}

		if ( 1 === d ) {
			resetTinyMceOnDraftSave();
		} else {
			resetTinyMceOnPageTurn();
		}

		jQuery( '.frm_next_page' ).val( v );
		jQuery( '.frm_saving_draft' ).val( d );

		if ( thisType !== 'submit' ) {
			f.trigger( 'submit' );
		}
	}

	/**
	 * When no checkboxes are checked, no $_POST data is sent.
	 * This causes issues when going back when drafts are enabled.
	 * The draft gets saved when the next button is pressed, and then
	 * gets reloaded after being unchecked because there is no $_POST data.
	 * This adds a hidden input with no value in its place so $_POST data does get sent.
	 *
	 * @since 6.8.3
	 *
	 * @param {HTMLElement} form
	 * @returns {void}
	 */
	function maybeAddEmptyHiddenInputsForCheckboxes( form ) {
		form.querySelectorAll( '.frm_opt_container' ).forEach(
			function( optContainer ) {
				var checkboxes, hiddenInput;
				checkboxes = optContainer.querySelectorAll( 'input[type="checkbox"]' );
				if ( checkboxes.length && ! jQuery( checkboxes ).filter( ':checked' ).length ) {
					hiddenInput = document.createElement( 'input' );
					hiddenInput.setAttribute( 'type', 'hidden' );
					hiddenInput.setAttribute( 'name', checkboxes[ 0 ].getAttribute( 'name' ) );
					optContainer.append( hiddenInput );
				}
			}
		);
	}

	function resetTinyMceOnDraftSave() {
		jQuery( document ).one( 'frmFormComplete', function() {
			jQuery( '.wp-editor-area' ).each( function() {
				reInitializeRichText( this.id );
			});
		});
	}

	function resetTinyMceOnPageTurn() {
		var removeIds = [];
		jQuery( '.frm_form_field .wp-editor-area' ).each(
			function() {
				removeIds.push( this.id );
			}
		);
		jQuery( document ).one( 'frmPageChanged', function() {
			var removeIndex, removeId;
			// Remove tinymce from RTE fields on page update so they can initialize properly when the page becomes active again
			for ( removeIndex = 0; removeIndex < removeIds.length; ++removeIndex ) {
				removeId = removeIds[ removeIndex ];
				removeRichText( removeId );
			}
			checkConditionalLogic();
		});
	}

	function toggleSection( e ) {
		var $toggleContainer, togglingOn;

		if ( e.key !== undefined ) {
			if ( e.key !== ' ' ) {
				return;
			}
		} else if ( e.keyCode !== undefined && e.keyCode !== 32 ) {
			return;
		}

		e.preventDefault();

		/*jshint validthis:true */
		$toggleContainer = jQuery( this ).parent().children( '.frm_toggle_container' );
		togglingOn       = 'none' === $toggleContainer.get( 0 ).style.display;

		if ( togglingOn ) {
			$toggleContainer.show();
		}

		triggerEvent( document, 'frmBeforeToggleSection', {
			toggleButton: this
		});

		if ( togglingOn ) {
			$toggleContainer.hide();
		}

		$toggleContainer.slideToggle( 'fast' );

		if ( togglingOn ) {
			this.className += ' active';
			this.setAttribute( 'aria-expanded', 'true' );
		} else {
			this.className = this.className.replace( ' active', '' );
			this.setAttribute( 'aria-expanded', 'false' );
		}
	}

	function loadDateFields() {
		jQuery( document ).on( 'focusin', '.frm_date', triggerDateField );
		document.addEventListener( 'focusout', keepFloatingLabelOnDateBlur, true );

		loadUniqueTimeFields();
	}

	function frmDatepickerPro( input, settings ) {

		if ( input._flatpickr ) {
			return input._flatpickr;
		}

		const _this = this;

		// Init the static methods
		this.initAccessiblity    = frmDatepickerPro.initAccessiblity;
		this.initMonthSelector   = frmDatepickerPro.initMonthSelector;
		this.initYearSelector    = frmDatepickerPro.initYearSelector;
		this.parseFunctionConfig = frmDatepickerPro.parseFunctionConfig;
		this.getDateRange        = frmDatepickerPro.getDateRange;
		this.callbacks           = frmDatepickerPro.callbacks;
		this.getDefaultConfigs   = frmDatepickerPro.getDefaultConfigs;
		this.isUsingACustomTheme = frmDatepickerPro.isUsingACustomTheme;

		this.getConfigs = function() {
			if ( 'undefined' === typeof settings.datepickerJsOptions ) {
				return _this.getDefaultConfigs( settings, input );
			}
			return { ..._this.getDefaultConfigs( settings, input ), ..._this.parseFunctionConfig( settings.datepickerJsOptions ) };
		};
		return flatpickr( input, this.getConfigs() );
	}

	frmDatepickerPro.getThemeType = function() {
		const themeFile      = document.querySelector( 'link[href*="flatpickr/dist/themes/"]' );
		const themeSelectors = [
			{ name: 'frm-dark', selector: 'dark.css' },
			{ name: 'frm-material-blue', selector: 'material_blue.css' },
			{ name: 'frm-material-green', selector: 'material_green.css' },
			{ name: 'frm-material-orange', selector: 'material_orange.css' },
			{ name: 'frm-material-red', selector: 'material_red.css' },
			{ name: 'frm-airbnb', selector: 'airbnb.css' },
			{ name: 'frm-confetti', selector: 'confetti.css' }
		];

		if ( themeFile && themeFile.href ) {
			for ( const theme of themeSelectors ) {
				if ( themeFile.href.includes( theme.selector ) ) {
					return theme.name;
				}
			}
		}

		return '';
	};
	frmDatepickerPro.themeType = frmDatepickerPro.getThemeType();
	frmDatepickerPro.isUsingACustomTheme = frmDatepickerPro.themeType !== '';
	frmDatepickerPro.callbacks = {};
	frmDatepickerPro.callbacks.onOpen = function( selectedDates, dateStr, instance ) {
		instance.calendarContainer.classList.add( 'frm-datepicker', 'with_frm_style' );
		if ( frmDatepickerPro.isUsingACustomTheme ) {
			instance.calendarContainer.classList.add( 'frm-datepicker-custom-theme', frmDatepickerPro.themeType );
		}
	};
	frmDatepickerPro.callbacks.onClose = function( selectedDates, dateStr, instance ) {
		if ( ! instance.config.inline ) {
			instance.calendarContainer.classList.remove( 'frm-datepicker', 'with_frm_style' );
			if ( frmDatepickerPro.isUsingACustomTheme ) {
				instance.calendarContainer.classList.remove( 'frm-datepicker-custom-theme', frmDatepickerPro.themeType );
			}
		}

		triggerEvent( instance.input, 'frmFlatpickrClosed', {
			instance,
		});
	};

	/**
	 * Get the default configs for the datepicker.
	 *
	 * @since 6.19
	 *
	 * @returns {object}
	 */
	frmDatepickerPro.getDefaultConfigs = function( config, input ) {

		this.getSettings = function() {
			const result = {};
			if ( config.formidable_dates || config.options ) {
				return config;
			}

			result.formidable_dates = config;
			result.options = config.datepickerOptions;
			return result;
		};

		this.getDatesDisabled = function( settings ) {
			const disabledDates = settings?.formidable_dates?.datesDisabled || settings?.options?.datesDisabled;

			if ( ! Array.isArray( disabledDates ) ) {
				return [];
			}

			return disabledDates.map( date => new Date( date + 'T00:00:00' ) );
		};

		this.getInstanceElement = ( instance ) => {
			if ( instance.config.inline && 'INPUT' !== instance.element.nodeName ) {
				const fieldId = instance.element.dataset.fieldId;
				const element = document.querySelector( `input[name="item_meta[${ fieldId }]"]` );
				if ( element ) {
					return element;
				}
				return instance.element;
			}
			return instance.element;
		};

		/**
		 * Update the range fields(start field and the related end field) on change.
		 *
		 * @since 6.24
		 *
		 * @param {string} mode The mode of the datepicker.
		 * @param {object} instance The instance of the datepicker.
		 * @param {string} dateStr The date string.
		 * @param {array} selectedDates The selected dates.
		 * @returns {void}
		 */
		this.updateRangeFieldsOnChange = ( mode, instance, dateStr, selectedDates ) => {
			if ( 'range' !== mode ) {
				return;
			}

			const fieldId                = input.dataset.fieldId || input.dataset.rangeStartFieldId;
			const startDateField         = document.querySelector( `input[data-field-id="${ fieldId }"]` ) || document.querySelector( `input[type="hidden"][name="item_meta[${ fieldId }]"]` );
			const [ startDate, endDate ] = selectedDates;
			const endDateField           = 'undefined' !== typeof endDate ? document.querySelector( `input[data-range-start-field-id="${ fieldId }"]` ) : null;
			const instanceElement        = this.getInstanceElement( instance );

			instanceElement.dataset.rangeValue = dateStr;

			if ( instanceElement === startDateField && null !== endDateField ) {
				instanceElement.value              = flatpickr.formatDate( startDate, settings.options.fpDateFormat );
				endDateField.dataset.rangeValue    = dateStr;

				endDateField._flatpickr.setDate( dateStr );
				endDateField.value = flatpickr.formatDate( endDate, settings.options.fpDateFormat );

				// Trigger the change event to end range field to update the calculation
				endDateField.dispatchEvent( new Event( 'change', { bubbles: true } ) );
			}

			if ( instanceElement === endDateField ) {
				instanceElement.value              = flatpickr.formatDate( endDate, settings.options.fpDateFormat );
				startDateField.dataset.rangeValue  = dateStr;
				startDateField._flatpickr.setDate( dateStr );
				startDateField.value               = flatpickr.formatDate( startDate, settings.options.fpDateFormat );
			}
		};

		const settings      = this.getSettings();
		const mode          = input.classList.contains( 'frm_date_range' ) || ( settings.formidable_dates && settings.formidable_dates.isRangeEnabled ) ? 'range' : 'single';
		const inline        = settings.formidable_dates && settings.formidable_dates.inline;
		const showMonths    = inline && 'range' === mode ? 2 : 1;

		const { min, max }  = frmDatepickerPro.getDateRange( settings );
		const disabledDates = this.getDatesDisabled( settings );
		const firstDay      = settings.options.firstDay || 1;

		const flatpickrOptions = {
			dateFormat: settings.options.fpDateFormat,
			inline: settings.formidable_dates && settings.formidable_dates.inline,
			minDate: min,
			maxDate: max,
			monthSelectorType: 'true' === settings.options.changeMonth ? 'dropdown' : 'static',
			changeYear: settings.options.changeYear,
			closeOnSelect: true,
			showMonths: showMonths,
			locale: {
				...flatpickr.l10ns[ settings.locale || settings.options.locale ],
				firstDayOfWeek: firstDay,
			},
			mode: mode,
			disable: [
				function( date ) {
					return settings.formidable_dates && settings.formidable_dates.daysEnabled && -1 === settings.formidable_dates.daysEnabled.indexOf( date.getDay() );
				},
				...( disabledDates ? disabledDates : [] )
			],
			onReady: function( selectedDates, dateStr, instance ) {
				setTimeout( () => frmDatepickerPro.setDefaultRangeValue( instance.element ) );
				if ( instance.config.inline ) {
					instance.calendarContainer.classList.add( 'frm-datepicker', 'with_frm_style', 'frm_date_inline' );
					if ( frmDatepickerPro.isUsingACustomTheme ) {
						instance.calendarContainer.classList.add( 'frm-datepicker-custom-theme', frmDatepickerPro.themeType );
					}
				}
				frmDatepickerPro.initMonthSelector( instance );
				frmDatepickerPro.initYearSelector( instance );
				frmDatepickerPro.initAccessiblity( instance );
			},
			onChange: ( selectedDates, dateStr, instance ) => {
				if ( instance.config.inline ) {
					instance.config.altInputElement.value = dateStr;
					instance.config.altInputElement.dispatchEvent( new Event( 'change', { bubbles: true } ) );
				}

				this.updateRangeFieldsOnChange( mode, instance, dateStr, selectedDates );

			},
			onOpen: function( selectedDates, dateStr, instance ) {
				frmDatepickerPro.callbacks.onOpen( selectedDates, dateStr, instance );
			},
			onClose: frmDatepickerPro.callbacks.onClose,
			shorthandCurrentMonth: true,
			altInputClass: ''
		};

		if ( settings.formidable_dates && settings.formidable_dates.inline ) {
			flatpickrOptions.altInput = true;
			flatpickrOptions.altInputElement = document.querySelector( settings.options.altField );
		}

		return flatpickrOptions;
	};

	/**
	 * Set the default range value for the datepicker.
	 *
	 * @since 6.24
	 *
	 * @param {object} input
	 * @returns {void}
	 */
	frmDatepickerPro.setDefaultRangeValue = function( instanceElement ) {
		let input = instanceElement;

		if ( instanceElement._flatpickr.config.inline ) {
			const fieldId = instanceElement.dataset.fieldId;
			input = document.querySelector( `input[name="item_meta[${ fieldId }]"]` );
			input.dataset.fieldId = fieldId;
		}

		if ( ! input.classList.contains( 'frm_date_range' ) ) {
			return null;
		}

		const startDate = input.dataset.rangeStartFieldId ? document.querySelector( `input[data-field-id="${ input.dataset.rangeStartFieldId }"]` ) : input;
		const startDateValue = startDate.value;
		const endDate = input.dataset.rangeStartFieldId ? input : document.querySelector( `input[data-range-start-field-id="${ input.dataset.fieldId }"]` );
		const endDateValue = endDate.value;

		if ( instanceElement._flatpickr.config.inline ) {
			instanceElement._flatpickr.setDate( startDateValue + ' to ' + endDateValue );
			startDate.value = startDateValue;
			endDate.value = endDateValue;
			return;
		}

		// When the inline datepicker is enabled, the range’s default value is controlled by the “Start Date” field.
		// If the Flatpickr instance for “Start Date” isn’t available, setDefaultRangeValue is executed for the “End Date” field when the "Display Inline" is activated - the default dates are already updated by the "Start Date" field
		if ( 'undefined' === typeof startDate._flatpickr ) {
			return;
		}

		startDate._flatpickr.setDate( startDateValue + ' to ' + endDateValue );
		startDate.value = startDateValue;
		endDate._flatpickr.setDate( startDateValue + ' to ' + endDateValue );
		endDate.value = endDateValue;
	};

	/**
	 * Initialize the month selector for the datepicker.
	 *
	 * @since 6.19
	 *
	 * @param {object} instance
	 * @returns {void}
	 */
	frmDatepickerPro.initMonthSelector = function( instance ) {
		if ( 'static' !== instance.config.monthSelectorType ) {
			return;
		}
		instance.calendarContainer.classList.add( 'frm-date-no-month-select' );
	};

	/**
	 * Initialize the year selector for the datepicker.
	 *
	 * @since 6.19
	 *
	 * @param {object} instance
	 * @returns {void}
	 */
	frmDatepickerPro.initYearSelector = function( instance ) {
		if ( 'false' !== instance.config.changeYear ) {
			return;
		}
		instance.calendarContainer.classList.add( 'frm-date-no-year-select' );
	};

	/**
	 * Initialize the accessibility for the datepicker.
	 *
	 * @since 6.19
	 *
	 * @param {object} instance
	 * @returns {void}
	 */
	frmDatepickerPro.initAccessiblity = function( instance ) {

		/**
		 * Initialize the accessibility for the datepicker.
		 *
		 * @since 6.19
		 *
		 * @returns {void}
		 */
		this.init = function() {
			instance.calendarContainer.setAttribute( 'role', 'dialog' );
			instance.calendarContainer.setAttribute( 'tabindex', '0' );
			this.prevButton();
			this.nextButton();
		};

		/**
		 * Initialize the previous button for the datepicker.
		 *
		 * @since 6.19
		 *
		 * @returns {void}
		 */
		this.prevButton = function(  ) {
			const prevArrow = instance.calendarContainer.querySelector( '.flatpickr-prev-month' );
			if ( null === prevArrow ) {
				return;
			}
			prevArrow.setAttribute( 'tabindex', '0' );
			prevArrow.setAttribute( 'role', 'button' );
			prevArrow.addEventListener( 'keydown', function( event ) {
				if ( event.key === 'Enter' ) {
					prevArrow.click();
				}
			});
		};

		/**
		 * Initialize the next button for the datepicker.
		 *
		 * @since 6.19
		 *
		 * @returns {void}
		 */
		this.nextButton = function(  ) {
			const nextArrow = instance.calendarContainer.querySelector( '.flatpickr-next-month' );
			if ( null === nextArrow ) {
				return;
			}
			nextArrow.setAttribute( 'tabindex', '0' );
			nextArrow.setAttribute( 'role', 'button' );
			nextArrow.addEventListener( 'keydown', function( event ) {
				if ( event.key === 'Enter' ) {
					nextArrow.click();
				}
			});
		};

		this.init();
	};

	/**
	 * Get the date range for the datepicker.
	 *
	 * @since 6.19
	 *
	 * @param {object} optionsData
	 * @returns {object}
	 */
	frmDatepickerPro.getDateRange = function( optionsData ) {
		const dates = {
			min: null,
			max: null,
		};
		const [ minYear, maxYear ] = optionsData.options.yearRange.split( ':' );

		dates.min = new Date( minYear + '-01-01T00:00:00' );
		dates.max = new Date( maxYear + '-12-31T23:59:59' );

		if ( ! optionsData.formidable_dates ) {
			return dates;
		}

		if ( optionsData.formidable_dates.maximum_date_cond ) {
			if ( optionsData.maxDate ) {
				dates.max = optionsData.maxDate;
			} else {
				dates.max = frmDatepickerPro.parseOffsetDate( optionsData.formidable_dates.maximum_date_val );
			}
		}
		if ( optionsData.formidable_dates.minimum_date_cond ) {
			if ( optionsData.minDate ) {
				dates.min = optionsData.minDate;
			} else {
				dates.min = frmDatepickerPro.parseOffsetDate( optionsData.formidable_dates.minimum_date_val );
			}
		}

		return dates;
	};

	/**
	 * Parse the offset date from the datepickerJsOptions.
	 *
	 * @since 6.19
	 *
	 * @param {string} date
	 * @returns {object}
	 */
	frmDatepickerPro.parseOffsetDate = function( date ) {
		const cleanedDate = date.toLowerCase().replace(/\s+/g, '');
		const regex       = /^([+-]\d+)(day|days|month|months|year|years)$/;
		const match       = cleanedDate.match( regex );
		if ( ! match ) {
			return date;
		}
		const [, number, unit ] = match;
		const amount = parseInt( number );
		const currentDate = new Date();

		if ( unit.startsWith( 'day' ) ) {
			currentDate.setDate( currentDate.getDate() + amount );
			return currentDate;
		}
		if ( unit.startsWith( 'month' ) ) {
			currentDate.setMonth( currentDate.getMonth() + amount );
			return currentDate;
		}
		if ( unit.startsWith( 'year' ) ) {
			currentDate.setFullYear( currentDate.getFullYear() + amount );
			return currentDate;
		}

		return currentDate;
	};

	/**
	 * Parse the function config from the datepickerJsOptions.
	 *
	 * @since 6.19
	 *
	 * @param {string} config
	 * @returns {object}
	 */
	frmDatepickerPro.parseFunctionConfig = function( config ) {
		const parsed = JSON.parse( config );

		const stringToFunction = ( str ) => {
			if ( typeof str === 'string' && str.startsWith( 'function' ) ) {
				return new Function( 'return ' + str )();
			}
			return str;
		};

		Object.keys( parsed ).forEach( key => {
			if ( Array.isArray( parsed[ key ] ) ) {
				parsed[ key ] = parsed[ key ].map( item => stringToFunction( item ) );
			} else {
				parsed[ key ] = stringToFunction( parsed[ key ] );
			}
		});

		return parsed;
	};

	/**
	 * Check if the flatpickr library is available.
	 *
	 * @since 6.19
	 */
	frmDatepickerPro.useFlatpickr = () => window.frm_js && window.frm_js.datepickerLibrary === 'flatpickr';

	function triggerDateField() {
		/*jshint validthis:true */
		if ( this.className.includes('frm_custom_date') || typeof __frmDatepicker === 'undefined' ) {
			return;
		}

		var dateFields = __frmDatepicker,
			id = this.id,
			idParts = id.split( '-' ),
			altID = '';

		if ( isRepeatingFieldByName( this.name ) ) {
			altID = 'input[id^="' + idParts[ 0 ] + '"]';
		} else {
			altID = 'input[id^="' + idParts.join( '-' ) + '"]';
		}

		var optKey = 0;
		for ( var i = 0; i < dateFields.length; i++ ) {
			if ( dateFields[ i ].triggerID === '#' + id || dateFields[ i ].triggerID == altID ) {
				optKey = i;
				break;
			}
		}

		if ( dateFields[ optKey ].options.defaultDate !== '' ) {
			dateFields[ optKey ].options.defaultDate = new Date( dateFields[ optKey ].options.defaultDate );
		}

		if ( frmDatepickerPro.useFlatpickr() ) {
			new frmDatepickerPro( this, dateFields[ optKey ] );
		} else {
			// Legacy datepicker.
			// TO DO: Remove this once the legacy datepicker is removed.
			dateFields[ optKey ].options.beforeShow = frmProForm.addFormidableClassToDatepicker;
			dateFields[ optKey ].options.onClose = frmProForm.removeFormidableClassFromDatepicker;

			jQuery( this ).datepicker( jQuery.extend(
				{},
				jQuery.datepicker.regional[ dateFields[ optKey ].locale ],
				dateFields[ optKey ].options
			));
		}
	}

	function loadDropzones( repeatRow ) {
		if ( typeof __frmDropzone === 'undefined' ) {
			return;
		}

		var uploadFields = __frmDropzone;
		for ( var i = 0; i < uploadFields.length; i++ ) {
			loadDropzone( i, repeatRow );
		}
	}

	function loadDropzone( i, repeatRow ) {
		var field, max, uploadedCount, form,
			uploadFields = __frmDropzone,
			uploadField = uploadFields[ i ],
			selector = '#' + uploadField.htmlID + '_dropzone',
			fieldName = uploadField.fieldName;

		if ( repeatRow !== undefined && selector.includes('-0_dropzone') ) {
			selector = selector.replace( '-0_dropzone', '-' + repeatRow + '_dropzone' );
			fieldName = fieldName.replace( '[0]', '[' + repeatRow + ']' );
			delete uploadField.mockFiles;
		}

		field = jQuery( selector );
		if ( field.length < 1 || field.hasClass( 'dz-clickable' ) || field.hasClass( 'dz-started' ) ) {
			return;
		}

		max = uploadField.maxFiles;
		if ( uploadField.mockFiles !== undefined ) {
			uploadedCount = uploadField.mockFiles.length;
			if ( max > 0 ) {
				max = max - uploadedCount;
			}
		}

		form = field.closest( 'form' );
		uploadField = uploadFields[ i ];

		field.dropzone({
			url: getAjaxUrl( form.get( 0 ) ),
			headers: {
				'Frm-Dropzone': 1
			},
			addRemoveLinks: false,
			paramName: field.attr( 'id' ).replace( '_dropzone', '' ),
			maxFilesize: uploadField.maxFilesize,
			minFilesize: uploadField.minFilesize,
			maxFiles: max,
			uploadMultiple: uploadField.uploadMultiple,
			hiddenInputContainer: field.parent()[0],
			dictDefaultMessage: uploadField.defaultMessage,
			dictFallbackMessage: uploadField.fallbackMessage,
			dictFallbackText: uploadField.fallbackText,
			dictFileTooBig: uploadField.fileTooBig,
			dictFileTooSmall: uploadField.fileTooSmall,
			dictInvalidFileType: uploadField.invalidFileType,
			dictResponseError: uploadField.responseError,
			dictCancelUpload: uploadField.cancel,
			dictCancelUploadConfirmation: uploadField.cancelConfirm,
			dictRemoveFile: uploadField.remove,
			dictMaxFilesExceeded: uploadField.maxFilesExceeded,
			resizeMethod: 'contain',
			resizeWidth: uploadField.resizeWidth,
			resizeHeight: uploadField.resizeHeight,
			thumbnailWidth: 60,
			thumbnailHeight: 60,
			timeout: uploadField.timeout,
			previewTemplate: filePreviewHTML( uploadField ),
			acceptedFiles: uploadField.acceptedFiles,
			/**
			 * Fixes Chrome Android bug where cloud storage files (Google Drive, etc.) fail to upload.
			 *
			 * @see https://bugs.chromium.org/p/chromium/issues/detail?id=1063576
			 */
			transformFile: ( file, done ) => {
				// Skip if not Android Chrome where this bug occurs.
				if ( ! /Android/i.test( navigator.userAgent ) || ! /Chrome/i.test( navigator.userAgent ) ) {
					done( file );
					return;
				}

				const reader = new FileReader();
				reader.onload = () => {
					const blob = new Blob( [ reader.result ], { type: file.type } );
					blob.name = file.name;
					blob.lastModified = file.lastModified;
					done( blob );
				};
				reader.onerror = () => done( file );
				reader.readAsArrayBuffer( file );
			},
			fallback: function() {
				// Force ajax submit to turn off
				jQuery( this.element ).closest( 'form' ).removeClass( 'frm_ajax_submit' );
			},
			init: function() {
				var hidden, mockFileIndex, mockFileData, mockFile;

				hidden = field.parent().find( '.dz-hidden-input' );

				if ( hidden.attr( 'id' ) === undefined ) {
					hidden.attr( 'id', uploadFields[ i ].label );
				}

				this.on( 'thumbnail', function( file ) {
					if ( file.size < 1024 * 1024 * ( this.options.minFilesize ) ) {
						if ( 'function' === typeof file.rejectSize ) {
							file.rejectSize();
						}
					}
				});

				this.on( 'sending', function( file, xhr, formData ) {

					if ( isSpam( uploadFields[ i ].parentFormID, uploadField.checkHoneypot ) ) {
						this.removeFile( file );
						alert( frm_js.file_spam );
						return false;
					}

					formData.append( 'action', 'frm_submit_dropzone' );
					formData.append( 'field_id', uploadFields[ i ].fieldID );
					formData.append( 'form_id', uploadFields[ i ].formID );
					formData.append( 'nonce', frm_js.nonce );

					if ( form.get( 0 ).hasAttribute( 'data-token' ) ) {
						formData.append( 'antispam_token', form.get( 0 ).getAttribute( 'data-token' ) );
					}
				});

				this.on( 'processing', function() {
					if ( ! this.options.uploadMultiple ) {
						this.removeEventListeners();
					}
				});

				this.on( 'success', function( file, response ) {
					var mediaIDs, m, mediaID;

					mediaIDs = JSON.parse( response );
					for ( m = 0; m < mediaIDs.length; m++ ) {
						if ( uploadFields[ i ].uploadMultiple !== true ) {
							mediaID = mediaIDs[ m ];
							jQuery( 'input[name="' + fieldName + '"]' ).val( mediaID );
						}
					}

					if ( this.options.uploadMultiple === false ) {
						this.disable();
					}

					// Clear the errors here so it only happens on upload.
					// complete and addedfile both get triggered when reloading a form with a file ID.
					clearErrorsOnUpload( file.previewElement );
				});

				this.on( 'successmultiple', function( files, response ) {
					var mediaIDs = JSON.parse( response );
					for ( var m = 0; m < files.length; m++ ) {
						jQuery( files[ m ].previewElement ).append( getHiddenUploadHTML( uploadFields[ i ], mediaIDs[ m ], fieldName ) );
					}
				});

				this.on( 'complete', function( file ) {
					var fileName, node, img, thumbnail;

					processesRunning--;

					if ( form.length ) {
						removeSubmitLoading( form.get( 0 ), uploadFields[ i ].formID, processesRunning );
					}

					if ( file.mediaID === undefined ) {
						return;
					}

					if ( uploadFields[ i ].uploadMultiple ) {
						jQuery( file.previewElement ).append( getHiddenUploadHTML( uploadFields[ i ], file.mediaID, fieldName ) );
					}

					// Add download link to the file
					fileName = file.previewElement.querySelectorAll( '[data-dz-name]' );
					for ( var _i = 0, _len = fileName.length; _i < _len; _i++ ) {
						node = fileName[ _i ];
						if ( file.accessible ) {
							node.innerHTML = '<a href="' + file.url + '" target="_blank" rel="noopener">' + file.name + '</a>';
						} else {
							node.innerHTML = file.name;
						}

						if ( file.ext ) {
							img = file.previewElement.querySelector( '.dz-image img' );
							if ( null !== img ) {
								thumbnail = maybeGetExtensionThumbnail( file.ext );
								if ( false !== thumbnail ) {
									img.setAttribute( 'src', thumbnail );
								}
							}
						}
					}
				});

				this.on( 'addedfile', function( file ) {
					var ext, thumbnail;
					ext = file.name.split( '.' ).pop();
					thumbnail = maybeGetExtensionThumbnail( ext );
					processesRunning++;
					frmFrontForm.showSubmitLoading( form );
					if ( false !== thumbnail ) {
						jQuery( file.previewElement ).find( '.dz-image img' ).attr( 'src', thumbnail );
					}
				});

				function clearErrorsOnUpload( fileElement ) {
					var container = fileElement.closest( '.frm_form_field' );
					if ( ! container ) {
						return;
					}

					container.classList.remove( 'frm_blank_field', 'has-error' );
					container.querySelectorAll( '.form-field .frm_error, .frm_error_style' ).forEach(
						function( error ) {
							if ( error.parentNode ) {
								error.parentNode.removeChild( error );
							}
						}
					);
				}

				this.on( 'removedfile', function( file ) {
					var fileCount = this.files.length;

					if ( this.options.uploadMultiple === false && fileCount < 1 ) {
						this.enable();
					}

					if ( file.accepted !== false && uploadFields[ i ].uploadMultiple !== true ) {
						jQuery( 'input[name="' + fieldName + '"]' ).val( '' );
					}

					if ( file.accepted !== false && file.mediaID !== undefined ) {
						jQuery( file.previewElement ).remove();
						fileCount = this.files.length;
						this.options.maxFiles = uploadFields[ i ].maxFiles - fileCount;
					}
				});

				if ( uploadFields[ i ].mockFiles !== undefined ) {
					for ( mockFileIndex = 0; mockFileIndex < uploadFields[ i ].mockFiles.length; mockFileIndex++ ) {
						mockFileData = uploadFields[ i ].mockFiles[ mockFileIndex ];
						mockFile = {
							name: mockFileData.name,
							size: mockFileData.size,
							url: mockFileData.file_url,
							mediaID: mockFileData.id,
							accessible: mockFileData.accessible,
							ext: mockFileData.ext,
							type: mockFileData.type
						};

						this.emit( 'addedfile', mockFile );
						if ( mockFile.accessible && 'string' === typeof mockFile.type && 0 === mockFile.type.indexOf( 'image/' ) ) {
							this.emit( 'thumbnail', mockFile, mockFileData.url );
						}

						this.emit( 'complete', mockFile );
						this.files.push( mockFile );
					}
				}
			},
			accept: function( file, done ) {
				var message = this.options.dictFileTooSmall.replace( '{{minFilesize}}', this.options.minFilesize );
				file.rejectSize = function() {
					done( message );
				};

				return done();
			}
		});
	}

	/**
	 * Load imask for the given container.
	 *
	 * @since 6.23
	 *
	 * @param {string|undefined} containerId
	 * @returns {void}
	 */
	function loadImask( containerId ) {
		if ( ! window.IMask ) {
			return;
		}

		let inputs;
		if ( containerId ) {
			inputs = document.querySelectorAll( '#' + containerId + ' input[data-frmmask]' );
		} else {
			inputs = document.querySelectorAll( 'input[data-frmmask]' );
		}

		inputs.forEach(
			( input ) => {
				if ( input.getAttribute( 'data-frm-imask-initialized' ) ) {
					return;
				}

				input.setAttribute( 'data-frm-imask-initialized', 1 );

				const mask = IMask(
					input,
					{
						mask: input.dataset.frmmask,
						lazy: true,
						definitions: {
							'*': /[a-zA-Z0-9]/
						}
					}
				);

				input.addEventListener( 'change', () => mask.updateValue() );

				// Show placeholder on focus
				input.addEventListener( 'focus', () => {
					mask.updateOptions({ lazy: false });

					// Extend the maxlength by 1 to fix an issue where the placeholder characters would prevent
					// characters from being added.
					if ( input.hasAttribute( 'maxlength' ) && input.value.length >= input.maxLength ) {
						if ( ! input.hasAttribute( 'original-maxlength' ) ) {
							input.setAttribute( 'original-maxlength', input.maxLength );
						}
						input.maxLength = parseInt( input.getAttribute( 'original-maxlength' ) ) + 1;
					}

					if ( '' === mask.unmaskedValue ) {
						// This fixes an issue in Chrome, where it
						// automatically would move the cursor to the end of the input.
						mask.alignCursor( 0 );
					}
				} );

				input.addEventListener( 'blur', () => {
					input.value = input.value.trim();

					if ( '' === mask.unmaskedValue ) {
						mask.updateOptions({ lazy: true });
					}

					if ( input.hasAttribute( 'original-maxlength' ) ) {
						input.maxLength = parseInt( input.getAttribute( 'original-maxlength' ) );
					}
				} );
			}
		);
	}

	/**
	 * Remove the submit loading class from a form and
	 * only enable the submit button if it is not conditionally disabled.
	 *
	 * @param {HTMLElement} form
	 * @param {string} formId
	 * @param {number} processesRunning
	 * @returns {void}
	 */
	function removeSubmitLoading( form, formId, processesRunning ) {
		var isFinalSubmitButton, enable;

		isFinalSubmitButton = form.querySelector( '.frm_submit .frm_button_submit.frm_final_submit' );
		enable              = ! isFinalSubmitButton || ! submitButtonIsConditionallyDisabled( formId ) ? 'enable' : '';

		if ( '' === enable ) {
			// If "enable" is not passed, the save draft button stays disabled.
			// This code enables the save draft button.
			jQuery( '.frm_loading_form' ).find( 'a.frm_save_draft' ).css( 'pointer-events', '' );
		}

		frmFrontForm.removeSubmitLoading( jQuery( form ), enable, processesRunning );
	}

	/**
	 * Check if the submit button is conditionally disabled.
	 * This is required for Stripe link so the button does not get enabled at the wrong time after completing the Stripe elements.
	 *
	 * @since 6.7.1
	 *
	 * @param {String} formId
	 * @returns {bool}
	 */
	function submitButtonIsConditionallyDisabled( formId ) {
		return submitButtonIsConditionallyNotAvailable( formId ) && 'disable' === __FRMRULES[ 'submit_' + formId ].hideDisable;
	}

	/**
	 * Check submit button is conditionally "hidden". This is also used for the enabled check and is used in submitButtonIsConditionallyDisabled.
	 *
	 * @since 6.7.1
	 *
	 * @param {String} formId
	 * @returns bool
	 */
	function submitButtonIsConditionallyNotAvailable( formId ) {
		var hideFields = document.getElementById( 'frm_hide_fields_' + formId );
		return hideFields && -1 !== hideFields.value.indexOf( '"frm_form_' + formId + '_container .frm_final_submit"' );
	}

	function maybeGetExtensionThumbnail( ext ) {
		if ( -1 !== [ 'jpg', 'jpeg', 'png' ].indexOf( ext ) ) {
			return false;
		}

		if ( 'pdf' === ext ) {
			return getProPluginUrl() + '/images/pdf.svg';
		}

		if ( -1 !== ext.indexOf( 'xls' ) ) {
			return getProPluginUrl() + '/images/xls.svg';
		}

		return getProPluginUrl() + '/images/doc.svg';
	}

	function getProPluginUrl() {
		var freePluginUrlSplitBySlashes = frm_js.images_url.split( '/' );
		freePluginUrlSplitBySlashes.pop();
		freePluginUrlSplitBySlashes.pop();
		freePluginUrlSplitBySlashes.push( 'formidable-pro' );
		return freePluginUrlSplitBySlashes.join( '/' );
	}

	function filePreviewHTML( field ) {
		return '<div class="dz-preview dz-file-preview">\n' +
		'<div class="dz-image"><img data-dz-thumbnail /></div>\n' +
		'<div class="dz-column">\n' +
		'<div class="dz-details">\n' +
		'<div class="dz-filename"><span data-dz-name></span></div>\n' +
		' ' + // add white space between file name and file size.
		'<div class="dz-size"><span data-dz-size></span></div>\n' +
		'<a class="dz-remove frm_remove_link" href="javascript:undefined;" data-dz-remove title="' + field.remove + '">' +
		'<svg width="16" height="16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="m11.5 4.5-7 7M4.5 4.5l7 7" stroke="#667085" stroke-linecap="round" stroke-linejoin="round"/></svg>' +
		'</a>' +
		'</div>\n' +
		'<div class="dz-progress"><span class="dz-upload" data-dz-uploadprogress></span></div>\n' +
		'<div class="dz-error-message"><span data-dz-errormessage></span></div>\n' +
		'</div>\n' +
		'</div>';
	}

	function getHiddenUploadHTML( field, mediaID, fieldName ) {
		return '<input name="' + fieldName + '[]" type="hidden" value="' + mediaID + '" data-frmfile="' + field.fieldID + '" />';
	}

	function removeFile() {
		/*jshint validthis:true */
		var fieldName = jQuery( this ).data( 'frm-remove' );
		fadeOut( jQuery( this ).closest( '.dz-preview' ) );
		var singleField = jQuery( 'input[name="' + fieldName + '"]' );
		if ( singleField.length ) {
			singleField.val( '' );
		}
	}

	function postToAjaxUrl( form, data, success, error, extraParams ) {
		var ajaxParams = 'object' === typeof extraParams ? extraParams : {};

		ajaxParams.type = 'POST';
		ajaxParams.url = getAjaxUrl( form );
		ajaxParams.data = data;
		ajaxParams.success = success;

		if ( 'function' === typeof error ) {
			ajaxParams.error = error;
		}

		jQuery.ajax( ajaxParams );
	}

	function getAjaxUrl( form ) {
		var ajaxUrl, action;

		ajaxUrl = frm_js.ajax_url;
		action = form.getAttribute( 'action' );

		if ( 'string' === typeof action && -1 !== action.indexOf( '?action=frm_forms_preview' ) ) {
			ajaxUrl = action.split( '?action=frm_forms_preview' )[0];
		}

		return ajaxUrl;
	}

	function isSpam( formID, checkHoneypot ) {
		if ( isHeadless() ) {
			return true;
		}
		return checkHoneypot && isHoneypotSpam( formID );
	}

	/**
	 * Check if submission is Honeypot spam
	 *
	 * @since 2.03.08
	 *
	 * @returns {boolean}
	 */
	function isHoneypotSpam( formID ) {
		var honeypotField = document.getElementById( 'frm_email_' + formID );
		if ( honeypotField === null ) {
			honeypotField = document.getElementById( 'frm_form_' + formID + '_container' )?.querySelector( '.frm_verify[id^="field_"]' );
		}
		return honeypotField !== null && honeypotField.value !== '';
	}

	function isHeadless() {
		return (
			window._phantom || window.callPhantom || //phantomjs
			window.__phantomas || //PhantomJS-based web perf metrics
			window.Buffer || //nodejs
			window.emit || //couchjs
			window.spawn  //rhino
		);
	}

	/**
	 * Show "Other" text box when Other item is checked/selected
	 * Hide and clear text box when item is unchecked/unselected
	 */
	function showOtherText() {
        /*jshint validthis:true */
        var type = this.type,
			other = false,
			select = false;

        // Dropdowns
        if ( type === 'select-one' ) {
            select = true;
            var curOpt = this.options[ this.selectedIndex ];
            if ( curOpt !== undefined && curOpt.className === 'frm_other_trigger' ) {
                other = true;
            }
        } else if ( type === 'select-multiple' ) {
            select = true;
            var allOpts = this.options;
            other = false;
            for ( var i = 0; i < allOpts.length; i++ ) {
                if ( allOpts[ i ].className === 'frm_other_trigger' ) {
                    if ( allOpts[ i ].selected ) {
                        other = true;
                        break;
                    }
                }
            }
        }
        if ( select ) {
			var otherField = jQuery( this ).parent().children( '.frm_other_input' );

			if ( otherField.length ) {
				if ( other ) {
					// Remove frm_pos_none
					otherField[ 0 ].className = otherField[ 0 ].className.replace( 'frm_pos_none', '' );
				} else {
					// Add frm_pos_none
					if ( otherField[ 0 ].className.indexOf( 'frm_pos_none' ) < 1 ) {
						otherField[ 0 ].className = otherField[ 0 ].className + ' frm_pos_none';
					}
					otherField[ 0 ].value = '';
				}
			}

        // Radio
        } else if ( type === 'radio' ) {
			if ( jQuery( this ).is( ':checked' ) ) {
				jQuery( this ).closest( '.frm_radio' ).children( '.frm_other_input' ).removeClass( 'frm_pos_none' );
				jQuery( this ).closest( '.frm_radio' ).siblings().children( '.frm_other_input' ).addClass( 'frm_pos_none' ).val( '' );
			}
        // Checkboxes
        } else if ( type === 'checkbox' ) {
            if ( this.checked ) {
                jQuery( this ).closest( '.frm_checkbox' ).children( '.frm_other_input' ).removeClass( 'frm_pos_none' );
            } else {
                jQuery( this ).closest( '.frm_checkbox' ).children( '.frm_other_input' ).addClass( 'frm_pos_none' ).val( '' );
            }
        }
	}

	function setToggleAriaChecked() {
		this.nextElementSibling.setAttribute( 'aria-checked', this.checked ? 'true' : 'false' );
	}

	function maybeCheckDependent( _, field, fieldId, e ) {
		var $field, originalEvent;

		$field = jQuery( field );
		checkFieldsWithConditionalLogicDependentOnThis( fieldId, $field );

		originalEvent = getOriginalEvent( e );
		checkFieldsWatchingLookup( fieldId, $field, originalEvent );
		doCalculation( fieldId, $field );
	}

	function getOriginalEvent( e ) {
		var originalEvent;
		if ( e.originalEvent !== undefined || e.currentTarget.className.includes('frm_chzn') || e.currentTarget.className.includes('frm_slimselect') ) {
			originalEvent = 'value changed';
		} else {
			originalEvent = 'other';
		}
		return originalEvent;
	}

	/*****************************************************
	 Conditional Logic Functions
	 ******************************************************/

	/**
	 * Check if a changed field has other fields depending on it.
	 */
	function checkFieldsWithConditionalLogicDependentOnThis( fieldId, changedInput ) {
		if ( typeof __FRMRULES  === 'undefined' ||
			__FRMRULES[ fieldId ] === undefined ||
			__FRMRULES[ fieldId ].dependents.length < 1 ||
			changedInput === null ||
			changedInput === undefined
		) {
			return;
		}

		var triggerFieldArgs = __FRMRULES[ fieldId ];
		var repeatArgs       = getRepeatArgsFromFieldName( changedInput[ 0 ].name );

		pendingDynamicFieldAjax = [];

		for ( var i = 0, l = triggerFieldArgs.dependents.length; i < l; i++ ) {
			hideOrShowFieldById( triggerFieldArgs.dependents[ i ], repeatArgs );
		}

		processPendingAjax();
	}

	function processPendingAjax() {
		var fieldsToProcess, postData, data, formId, form;

		if ( ! pendingDynamicFieldAjax.length ) {
			return;
		}

		// Copy pendingDynamicFieldAjax as it gets reset to [] every time checkFieldsWithConditionalLogicDependentOnThis is called.
		fieldsToProcess = pendingDynamicFieldAjax.slice();
		postData        = [];

		for ( data in fieldsToProcess ) {
			postData.push( fieldsToProcess[ data ].data );
		}

		formId = fieldsToProcess[0].args.depFieldArgs.formId;

		function processDynamicField( html, depFieldArgs, onCurrentPage ) {
			var $fieldDiv, $optContainer, $listInputs, listVal;

			if ( onCurrentPage ) {
				$fieldDiv = jQuery( '#' + depFieldArgs.containerId );
				addLoadingIcon( $fieldDiv );

				$optContainer = $fieldDiv.find( '.frm_opt_container, .frm_data_container' );
				$optContainer.html( html );
				$listInputs = $optContainer.children( 'input' );
				listVal = $listInputs.val();
				removeLoadingIcon( $optContainer );
				if ( '' === html || '' === listVal ) {
					hideDynamicField( depFieldArgs );
				} else {
					showDynamicField( depFieldArgs, $fieldDiv, $listInputs, true );
				}
			} else {
				updateHiddenDynamicListField( depFieldArgs, html );
			}
		}

		function ajaxHandler( response ) {
			var i;
			for ( i = 0; i < fieldsToProcess.length; i++ ) {
				processDynamicField(
					'undefined' === typeof response[ i ] ? '' : response[ i ],
					fieldsToProcess[ i ].args.depFieldArgs,
					fieldsToProcess[ i ].args.onCurrentPage
				);
			}
		}

		form = getFormById( formId );
		if ( form ) {
			postToAjaxUrl(
				form,
				{
					action: 'frm_fields_ajax_get_data_arr',
					postData: postData
				},
				ajaxHandler,
				function( response ) {
					console.error( response );
				},
				{
					dataType: 'json'
				}
			);
		}
	}

	/**
	 * Hide or show all instances of a field using the field ID
	 *
	 * @param fieldId
	 * @param triggerFieldRepeatArgs
     */
	function hideOrShowFieldById( fieldId, triggerFieldRepeatArgs ) {
		var depFieldArgs = getRulesForSingleField( fieldId );

		if ( depFieldArgs === false || depFieldArgs.conditions.length < 1 ) {
			// If field has no logic on it, stop now
			return;
		}

		var childFieldDivIds = getAllFieldDivIds( depFieldArgs, triggerFieldRepeatArgs );

		var childFieldNum = childFieldDivIds.length;
		for ( var i = 0; i < childFieldNum; i++ ) {
			depFieldArgs.containerId = childFieldDivIds[ i ];
			addRepeatRow( depFieldArgs, childFieldDivIds[ i ]);
			hideOrShowSingleField( depFieldArgs );
		}
	}

	/**
	 * Get all the field divs that should be hidden or shown, regardless of whether they're on the current page
	 *
	 * @param {Object} depFieldArgs
	 * @param {bool} depFieldArgs.isRepeating
	 * @param {string} depFieldArgs.fieldId
	 * @param {object} triggerFieldArgs
	 * @param {string} triggerFieldArgs.repeatingSection
	 * @param {string} triggerFieldArgs.repeatRow
	 * @param {string} depFieldArgs.inEmbedForm
	 * @returns {Array}
     */
	function getAllFieldDivIds( depFieldArgs, triggerFieldArgs ) {
		var childFieldDivs = [];

		if ( depFieldArgs.isRepeating ) {
			if ( triggerFieldArgs.repeatingSection !== '' ) {
				// If trigger field is repeating/embedded, use its section row in selector
				var container = 'frm_field_' + depFieldArgs.fieldId + '-';
				container += triggerFieldArgs.repeatingSection + '-' + triggerFieldArgs.repeatRow + '_container';
				childFieldDivs.push( container );
			} else {
				// If trigger field is not repeating/embedded, get all repeating/embedded field divs
				childFieldDivs = getAllRepeatingFieldDivIds( depFieldArgs );
			}
		} else if ( depFieldArgs.fieldType === 'submit' ) {
			childFieldDivs.push( getSubmitButtonContainerID( depFieldArgs ) );
		} else {
			childFieldDivs.push( 'frm_field_' + depFieldArgs.fieldId + '_container' );
		}

		return childFieldDivs;
	}

	/**
	 * Return selector for submit button for use in Conditional Logic.
	 *
	 * @param depFieldArgs
	 * @returns {string}
	 */
	function getSubmitButtonContainerID( depFieldArgs ) {
		return 'frm_form_' + depFieldArgs.formId + '_container .frm_final_submit';
	}

	/**
	 * Get all instances of a repeating field
	 *
	 * @since 2.01.0
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldId
     */
	function getAllRepeatingFieldDivIds( depFieldArgs ) {
		var childFieldDivs = [],
			containerFieldId = getContainerFieldId( depFieldArgs );

		// TODO: what if section is inside embedded form?

		if ( isFieldDivOnPage( 'frm_field_' + containerFieldId + '_container' ) ) {
			childFieldDivs = getRepeatingFieldDivIdsOnCurrentPage( depFieldArgs.fieldId );
		} else {
			childFieldDivs = getRepeatingFieldDivIdsAcrossPage( depFieldArgs );
		}

		return childFieldDivs;
	}

	/**
	 * Get all repeating field divs on the current page
	 *
	 * @since 2.02.06
	 * @param string fieldId
	 * @returns {Array}
     */
	function getRepeatingFieldDivIdsOnCurrentPage( fieldId ) {
		var childFieldDivs = [],
			childFields = document.querySelectorAll( '.frm_field_' + fieldId + '_container' );

		for ( var i = 0, l = childFields.length; i < l; i++ ) {
			childFieldDivs.push( childFields[ i ].id );
		}

		return childFieldDivs;
	}

	/**
	 * Get the field divs for repeating fields across a page
	 *
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldId
	 * @returns {Array}
	 */
	function getRepeatingFieldDivIdsAcrossPage( depFieldArgs ) {
		var childFieldDivs = [],
			containerFieldId = getContainerFieldId( depFieldArgs ),
			fieldDiv = 'frm_field_' + depFieldArgs.fieldId + '-' + containerFieldId + '-',
			allRows = document.querySelectorAll( '[name="item_meta[' + containerFieldId + '][row_ids][]"]' );

        for ( var i = 0, l = allRows.length; i < l; i++ ) {
            if ( allRows[ i ].value !== '' ) {
                childFieldDivs.push( fieldDiv + allRows[ i ].value + '_container' );
            }
        }

        if ( childFieldDivs.length < 1 ) {
			childFieldDivs.push( fieldDiv + '0_container' );
		}

		return childFieldDivs;
	}

	/**
	 *
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.inSection
	 * @param {string} depFieldArgs.inEmbedForm
	 * @returns {string}
     */
	function getContainerFieldId( depFieldArgs ) {
		var containerFieldId = '';
		if ( depFieldArgs.inEmbedForm !== '0' ) {
			containerFieldId = depFieldArgs.inEmbedForm;
		} else if ( depFieldArgs.inSection !== '0' ) {
			containerFieldId = depFieldArgs.inSection;
		}

		return containerFieldId;
	}

	/**
	 * @param depFieldArgs
	 * @param {bool} depFieldArgs.isRepeating
	 * @param childFieldDivId
     */
	function addRepeatRow( depFieldArgs, childFieldDivId ) {
		if ( depFieldArgs.isRepeating ) {
			var divParts = childFieldDivId.replace( '_container', '' ).split( '-' );
			depFieldArgs.repeatRow = divParts[ 2 ];
		} else {
			depFieldArgs.repeatRow = '';
		}
	}

	/**
	 * @param {object} depFieldArgs
	 * @return {void}
	 */
	function hideOrShowSingleField( depFieldArgs ) {
		var i, add,
			logicOutcomes = [],
			len = depFieldArgs.conditions.length;

		for ( i = 0; i < len; i++ ) {
			add = checkLogicCondition( depFieldArgs.conditions[ i ], depFieldArgs );
			if ( add !== null ) {
				// Prevent fields not on the page from being hidden without a chance.
				logicOutcomes.push( add );
			}
		}

		if ( logicOutcomes.length ) {
			// Only continue if the field was found on the page.
			routeToHideOrShowField( depFieldArgs, logicOutcomes );
		}
	}

	function getRulesForSingleField( fieldId ) {
		if ( typeof __FRMRULES  === 'undefined' || __FRMRULES[ fieldId ] === undefined ) {
			return false;
		}

		return __FRMRULES[ fieldId ];
	}

	/**
	 * @param {Array} logicCondition
	 * @param {string} logicCondition.fieldId
	 * @param depFieldArgs
	 * @returns {*}
     */
	function checkLogicCondition( logicCondition, depFieldArgs ) {
		var fieldId = logicCondition.fieldId,
			logicFieldArgs = getRulesForSingleField( fieldId ),
			fieldValue = getFieldValue( logicFieldArgs, depFieldArgs );

		if ( fieldValue === null ) {
			// The field wasn't found.
			return null;
		}

		return getLogicConditionOutcome( logicCondition, fieldValue, depFieldArgs, logicFieldArgs );
	}

	/**
	 * Get the value from any field.
	 *
	 * @param {object} logicFieldArgs
	 * @param {string} logicFieldArgs.inputType
	 * @param {object} depFieldArgs
	 * @returns {string}
     */
	function getFieldValue( logicFieldArgs, depFieldArgs ) {
		var fieldValue = '';

		if ( 'name' === logicFieldArgs.fieldType ) {
			fieldValue = getValueFromNameField( logicFieldArgs, depFieldArgs );
		} else if ( logicFieldArgs.inputType === 'radio' || logicFieldArgs.inputType === 'checkbox' || logicFieldArgs.inputType === 'toggle' ) {
			fieldValue = getValueFromRadioOrCheckbox( logicFieldArgs, depFieldArgs );
		} else if ( logicFieldArgs.inputType === 'date' ) {
			fieldValue = getValueForDateField( logicFieldArgs, depFieldArgs );
		} else {
			fieldValue = getValueFromTextOrDropdown( logicFieldArgs, depFieldArgs );
		}

		fieldValue = cleanFinalFieldValue( fieldValue );

		return fieldValue;
	}

	/**
	 * @since 6.21
	 *
	 * @param {object} logicFieldArgs
	 * @param {object} depFieldArgs
	 * @return {string}
	 */
	function getValueForDateField( logicFieldArgs, depFieldArgs ) {
		const fieldId        = logicFieldArgs.fieldId;
		const fieldContainer = document.getElementById( 'frm_field_' + fieldId + '_container' );

		if ( fieldContainer && fieldContainer.querySelector( '.frm_date_inline' ) ) {
			const altField = fieldContainer.querySelector( '#field_' + logicFieldArgs.fieldKey + '_alt' );
			if ( altField ) {
				return altField.value;
			}
		}

		return getValueFromTextOrDropdown( logicFieldArgs, depFieldArgs );
	}

	function getValueFromNameField( logicFieldArgs, depFieldArgs ) {
		var inputName, inputs, nameValues;

		inputName  = buildLogicFieldInputName( logicFieldArgs, depFieldArgs );
		inputs     = document.querySelectorAll( '[name^="' + inputName + '"]' );
		nameValues = [];

		inputs.forEach(
			function( input ) {
				nameValues.push( input.value );
			}
		);

		return nameValues.join( ' ' );
	}

	/**
	 * Get the value from a Text or Dropdown field
	 *
	 * @param logicFieldArgs
	 * @param {string} logicFieldArgs.fieldKey
	 * @param {bool} logicFieldArgs.isRepeating
	 * @param {bool} logicFieldArgs.isMultiSelect
	 * @param depFieldArgs
	 * @param {string} depFieldArgs.repeatRow
 	 */
	function getValueFromTextOrDropdown( logicFieldArgs, depFieldArgs ) {
		var logicFieldValue = '';

		if ( logicFieldArgs.isMultiSelect === true ) {
			return getValueFromMultiSelectDropdown( logicFieldArgs, depFieldArgs );
		}

		var fieldCall = 'field_' + logicFieldArgs.fieldKey;
		if ( logicFieldArgs.isRepeating ) {
			// If trigger field is repeating, dependent field is repeating too
			fieldCall += '-' + depFieldArgs.repeatRow;
		}

		var logicFieldInput = document.getElementById( fieldCall );

		if ( logicFieldInput === null ) {
			logicFieldValue = parseTimeValue( logicFieldArgs, fieldCall );
			if ( logicFieldValue === '' ) {
				// Check for hidden fields.
				logicFieldValue = getValueFromMultiSelectDropdown( logicFieldArgs, depFieldArgs );
			}
		} else {
			logicFieldValue = logicFieldInput.value;
		}

		return logicFieldValue;
	}

	function parseTimeValue( logicFieldArgs, fieldCall ) {
		var logicFieldValue = '';
		if ( logicFieldArgs.fieldType === 'time' ) {
			var hour = document.getElementById( fieldCall + '_H' );
			if ( hour !== null ) {
				var minute = document.getElementById( fieldCall + '_m' );
				logicFieldValue = hour.value + ':' + minute.value;

				var pm = document.getElementById( fieldCall + '_A' );
				if ( logicFieldValue == ':' ) {
					logicFieldValue = '';
				} else if ( pm !== null ) {
					logicFieldValue += ' ' + pm.value;
				}
			}
		}
		return logicFieldValue;
	}

	function getValueFromMultiSelectDropdown( logicFieldArgs, depFieldArgs ) {
		var inputName = buildLogicFieldInputName( logicFieldArgs, depFieldArgs ),
			logicFieldInputs = document.querySelectorAll( '[name^="' + inputName + '"]' ),
			selectedVals = [];

		// TODO: What about if it's read-only?

		if ( logicFieldInputs.length == 1 && logicFieldInputs[ 0 ].type !== 'hidden' ) {
			selectedVals = jQuery( '[name^="' + inputName + '"]' ).val();
			if ( selectedVals === null ) {
				selectedVals = '';
			}
		} else {
			selectedVals = getValuesFromCheckboxInputs( logicFieldInputs );
		}

		return selectedVals;
	}

	/**
	 * Get the value from a Radio or Checkbox field trigger field
	 *
	 * @param {Object} logicFieldArgs
	 * @param {string} logicFieldArgs.inputType
	 * @param {Object} depFieldArgs
	 * @returns {String|Array}
     */
	function getValueFromRadioOrCheckbox( logicFieldArgs, depFieldArgs ) {
		var logicFieldValue,
			inputName = buildLogicFieldInputName( logicFieldArgs, depFieldArgs ),
			logicFieldInputs = document.querySelectorAll( 'input[name^="' + inputName + '"]' );

		if ( logicFieldInputs.length === 0 ) {
			// Don't continue if the field doesn't exist.
			return null;
		}

		if ( logicFieldArgs.inputType === 'checkbox' || logicFieldArgs.inputType === 'toggle' ) {
			logicFieldValue = getValuesFromCheckboxInputs( logicFieldInputs );
		} else {
			logicFieldValue = getValueFromRadioInputs( logicFieldInputs );
		}

		return logicFieldValue;
	}

	/**
	 * Build a logic field's input name
	 * Does not include full name for checkbox, address, or multi-select fields
	 *
	 * @param {object} logicFieldArgs
	 * @param {boolean} logicFieldArgs.isRepeating
	 * @param {string} logicFieldArgs.fieldId
	 * @param {object} depFieldArgs
	 * @param {string} depFieldArgs.inEmbedForm
	 * @param {string} depFieldArgs.inSection
	 * @param {string} depFieldArgs.repeatRow
	 * @returns {string}
     */
	function buildLogicFieldInputName( logicFieldArgs, depFieldArgs ) {
		var inputName = '';

		if ( logicFieldArgs.isRepeating ) {
			// If the trigger field is repeating, the child must be repeating as well
			var sectionId = '';
			if ( depFieldArgs.inEmbedForm !== '0' ) {
				sectionId = depFieldArgs.inEmbedForm;
			} else {
				sectionId = depFieldArgs.inSection;
			}
			var rowId = depFieldArgs.repeatRow;
			inputName = 'item_meta[' + sectionId + '][' + rowId + '][' + logicFieldArgs.fieldId + ']';
		} else {
			inputName = 'item_meta[' + logicFieldArgs.fieldId + ']';
		}

		return inputName;
	}

	function getValuesFromCheckboxInputs( inputs ) {
		var checkedVals = [];

		for ( var i = 0, l = inputs.length; i < l; i++ ) {
			if ( inputs[ i ].type === 'hidden' || inputs[ i ].checked ) {
				checkedVals.push( inputs[ i ].value );
			} else if ( inputs[ i ].dataset.off !== undefined ) {
				checkedVals.push( inputs[ i ].dataset.off );
			}
		}

		if ( checkedVals.length === 0 ) {
			checkedVals = false;
		}

		return checkedVals;
	}

	function cleanFinalFieldValue( fieldValue ) {
		if ( fieldValue === undefined ) {
			fieldValue = '';
		} else if ( typeof fieldValue === 'string' ) {
			fieldValue = fieldValue.trim();
		}

		return fieldValue;
	}

	/**
	 * Check whether a particular conditional logic condition is true or false
	 *
	 * @param {Array} logicCondition
	 * @param {operator:string} logicCondition.operator
	 * @param {value:string} logicCondition.value
	 * @param {String|Array} fieldValue
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldType
	 * @param {Object} logicFieldArgs
	 * @param {fieldType:string} logicFieldArgs.fieldType
     * @returns {Boolean}
     */
	function getLogicConditionOutcome( logicCondition, fieldValue, depFieldArgs, logicFieldArgs ) {
		var outcome;

		if ( depFieldArgs.fieldType === 'data' && logicFieldArgs.fieldType === 'data' ) {
			// If dep field is Dynamic and logic field is Dynamic
			outcome = getDynamicFieldLogicOutcome( logicCondition, fieldValue, depFieldArgs );
		} else {
			const values = maybePrepareValuesCompared( logicCondition, fieldValue, depFieldArgs.formId );
			outcome = operators( logicCondition.operator, values.logicValue, values.fieldValue );
		}

		return outcome;
	}

	/**
	 * @param {Array} logicCondition
	 * @param {string} logicCondition.operator
	 * @param {string} logicCondition.value
	 * @param {string|Array} fieldValue
	 * @param {object} depFieldArgs
	 * @param {Array} depFieldArgs.dataLogic
	 * @returns {boolean}
     */
	function getDynamicFieldLogicOutcome( logicCondition, fieldValue, depFieldArgs ) {
		var outcome = false;
		if ( logicCondition.value === '' ) {
			// Logic: "Dynamic field is equal to/not equal to anything"

			if ( fieldValue === '' || ( fieldValue.length == 1 && fieldValue[ 0 ] === '' ) ) {
				outcome = false;
			} else {
				outcome = true;
			}
		} else {
			const values = maybePrepareValuesCompared( logicCondition, fieldValue, depFieldArgs.formId );
			// Logic: "Dynamic field is equal to/not equal to specific option"
			outcome = operators( logicCondition.operator, values.logicValue, values.fieldValue );
		}
		depFieldArgs.dataLogic = logicCondition;
		depFieldArgs.dataLogic.actualValue = fieldValue;

		return outcome;
	}

	/**
	 * @since 6.19
	 *
	 * @param {object}        logicCondition
	 * @param {number|string} fieldValue
	 * @param {string}        formId
	 * @returns {object}
	 */
	function maybePrepareValuesCompared( logicCondition, fieldValue, formId ) {
		let logicValue = logicCondition.value;
		if ( 'undefined' === typeof __FRMRULES || 'undefined' === typeof __FRMRULES[ logicCondition.fieldId ] ) {
			return { logicValue, fieldValue };
		}
		const logicConditionFieldType = __FRMRULES[ logicCondition.fieldId ].fieldType;
		if ( 'total' === logicConditionFieldType ) {
			const currency = getCurrency( formId );
			logicValue     = preparePrice( logicCondition.value, currency );
			fieldValue     = preparePrice( fieldValue, currency );
		} else if ( 'date' === logicConditionFieldType ) {
			const dateFormat = getDateFormatForField( logicCondition.fieldId );
			if ( false !== dateFormat ) {
				logicValue = prepareDate( logicValue, dateFormat );
				fieldValue = prepareDate( fieldValue, dateFormat );
			}
		}
		return { logicValue, fieldValue };
	}

	/**
	 * Change date to Y-m-d format if the current format is not sortable.
	 *
	 * @since 6.21
	 *
	 * @param {string} date
	 * @param {string} format
	 * @return {string}
	 */
	function prepareDate( date, format ) {
		if ( 'Y-m-d' === format ) {
			return date;
		}

		const possibleSeparators = [ '/', '.', '-' ];
		let separator            = false;
		possibleSeparators.forEach( ( sep ) => {
			if ( format.includes( sep ) ) {
				separator = sep;
			}
		} );
		if ( separator === false ) {
			return date;
		}

		const split = date.split( separator );
		if ( 3 !== split.length ) {
			return date;
		}

		let yearPart, monthPart, dayPart;

		switch ( format ) {
			case 'Y/m/d':
				yearPart  = split[ 0 ];
				monthPart = split[ 1 ];
				dayPart   = split[ 2 ];
				break;
			case 'n/j/Y':
			case 'm/d/Y':
				monthPart = split[ 0 ];
				dayPart   = split[ 1 ];
				yearPart  = split[ 2 ];
				break;
			case 'd/m/Y':
			case 'd.m.Y':
			case 'j/m/Y':
			case 'j/n/Y':
			case 'j-m-Y':
			default:
				dayPart   = split[ 0 ];
				monthPart = split[ 1 ];
				yearPart  = split[ 2 ];
				break;
		}

		if ( yearPart.length !== 4 ) {
			return date;
		}

		if ( monthPart.length < 2 ) {
			monthPart = '0' + monthPart;
		}

		if ( dayPart.length < 2 ) {
			dayPart = '0' + dayPart;
		}

		return yearPart + '-' + monthPart + '-' + dayPart;
	}

	/**
	 * @since 6.21
	 *
	 * @param {string} fieldId
	 * @return {string|false}
	 */
	function getDateFormatForField( fieldId ) {
		if ( ! window.__frmDatepicker ) {
			return false;
		}

		let fieldRule = false;
		__frmDatepicker.forEach( ( rule ) => {
			if ( ! rule.fieldId || rule.fieldId === fieldId ) {
				fieldRule = rule;
			}
		} );

		if ( false === fieldRule ) {
			return false;
		}

		return fieldRule.options.fpDateFormat;
	}

	function operators( op, a, b ) {
		var theOperators;

		a = prepareLogicValueForComparison( a );
		b = prepareEnteredValueForComparison( a, b );

		if ( typeof a === 'string' && a.indexOf( '&quot;' ) != '-1' && operators( op, a.replace( '&quot;', '"' ), b ) ) {
			return true;
		}

		theOperators = {
			'==': function( c, d ) {
				return c === d;
			},
			'!=': function( c, d ) {
				return c !== d;
			},
			'<': function( c, d ) {
				return c > d;
			},
			'<=': function( c, d ) {
				return c >= d;
			},
			'>': function( c, d ) {
				return c < d;
			},
			'>=': function( c, d ) {
				return c <= d;
			},
			'LIKE': function( c, d ) {
				if ( ! d ) {
					/* If no value, then assume no match */
					return false;
				}

				c = prepareLogicValueForLikeComparison( c );
				d = prepareEnteredValueForLikeComparison( c, d );

				return d.includes(c);
			},
			'not LIKE': function( c, d ) {
				if ( ! d ) {
					/* If no value, then assume no match */
					return true;
				}

				c = prepareLogicValueForLikeComparison( c );
				d = prepareEnteredValueForLikeComparison( c, d );

				return !d.includes(c);
			},
			// Starts with
			'LIKE%': function( c, d ) {
				if ( ! d ) {
					/* If no value, then assume no match */
					return false;
				}

				c = prepareLogicValueForLikeComparison( c );
				d = prepareEnteredValueForLikeComparison( c, d );

				if ( Array.isArray( d ) ) {
					// If d is still an array after calling prepareEnteredValueForLikeComparison, there is no match.
					return false;
				}

				return d.substr( 0, c.length ) === c;
			},
			// Ends with
			'%LIKE': function( c, d ) {
				if ( ! d ) {
					/* If no value, then assume no match */
					return false;
				}

				c = prepareLogicValueForLikeComparison( c );
				d = prepareEnteredValueForLikeComparison( c, d );

				if ( Array.isArray( d ) ) {
					// If d is still an array after calling prepareEnteredValueForLikeComparison, there is no match.
					return false;
				}

				return d.substr( -c.length ) === c;
			}
		};

		if ( 'function' !== typeof theOperators[ op ]) {
			op = '=='; // Fallback to Equals to.
		}

		return theOperators[ op ]( a, b );
	}

	function prepareLogicValueForComparison( a ) {
		if ( shouldParseFloat( a ) ) {
			a = parseFloat( a );
		} else if ( typeof a === 'string' ) {
			a = a.trim();
		}

		return a;
	}

	/**
	 * @param {string} value
	 * @returns {bool}
	 */
	function shouldParseFloat( value ) {
		return String( value ).search( /^\s*(\+|-)?((\d+(\.\d+)?)|(\.\d+))\s*$/ ) !== -1;
	}

	function prepareEnteredValueForComparison( a, b ) {
		if ( b === undefined || b === null || b === false ) {
			b = '';
		}

		if ( Array.isArray( b ) && jQuery.inArray( String( a ), b ) > -1 ) {
			b = a;
		}

		if ( typeof a === 'number' && typeof b === 'string' && shouldParseFloat( b ) ) {
			b = parseFloat( b );
		}

		if ( typeof b === 'string' ) {
			b = b.trim();
		}

		return b;
	}

	function prepareLogicValueForLikeComparison( val ) {
		return prepareValueForLikeComparison( val );
	}

	function prepareEnteredValueForLikeComparison( logicValue, enteredValue ) {
		enteredValue = prepareValueForLikeComparison( enteredValue );

		var currentValue = '';
		if ( Array.isArray( enteredValue ) ) {
			for ( var i = 0, l = enteredValue.length; i < l; i++ ) {
				currentValue = enteredValue[ i ].toLowerCase();
				if ( currentValue.includes(logicValue) ) {
					enteredValue = logicValue;
					break;
				}
			}
		}

		return enteredValue;
	}

	function prepareValueForLikeComparison( val ) {
		if ( typeof val === 'string' ) {
			val = val.toLowerCase();
		} else if ( typeof val === 'number' ) {
			val = val.toString();
		}
		return val;
	}

	/**
	 *
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.containerId
	 * @param {string} depFieldArgs.fieldType
	 * @param logicOutcomes
     */
	function routeToHideOrShowField( depFieldArgs, logicOutcomes ) {
		var onCurrentPage,
			action = getHideOrShowAction( depFieldArgs, logicOutcomes );

		if ( depFieldArgs.fieldType === 'submit' ) {
			onCurrentPage = isSubmitButtonOnPage( depFieldArgs.containerId );
		} else {
			onCurrentPage = isFieldDivOnPage( depFieldArgs.containerId );
		}

		if ( action === 'show' ) {
			if ( depFieldArgs.fieldType === 'data' && depFieldArgs.hasOwnProperty( 'dataLogic' ) ) {
				// Only update dynamic field options/value if it is dependent on another Dynamic field
				updateDynamicField( depFieldArgs, onCurrentPage );
			} else {
				showFieldAndSetValue( depFieldArgs, onCurrentPage );
			}
		} else {
			hideFieldAndClearValue( depFieldArgs, onCurrentPage );
		}
	}

	function isFieldDivOnPage( containerId ) {
		var fieldDiv = document.getElementById( containerId );

		return fieldDiv !== null;
	}

	/**
	 * Checks if specified submit button is on the current page.
	 *
	 * @param depFieldArgs
	 * @returns {boolean}
	 */
	function isSubmitButtonOnPage( container ) {
		var submitButton = document.querySelector( '#' + container );

		return submitButton != null;
	}

	/**
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.anyAll
	 * @param {string} depFieldArgs.showHide
	 * @param {Array} logicOutcomes
	 * @returns {string}
     */
	function getHideOrShowAction( depFieldArgs, logicOutcomes ) {
		if ( depFieldArgs.anyAll === 'any' ) {
			// If any of the following match logic
			if ( logicOutcomes.includes(true) ) {
				action = depFieldArgs.showHide;
			} else {
				action = reverseAction( depFieldArgs.showHide );
			}
		} else {
			// If all of the following match logic
			if ( logicOutcomes.includes(false) ) {
				action = reverseAction( depFieldArgs.showHide );
			} else {
				action = depFieldArgs.showHide;
			}
		}

		return action;
	}

	function reverseAction( action ) {
		if ( action === 'show' ) {
			action = 'hide';
		} else {
			action = 'show';
		}
		return action;
	}

	/**
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.containerId
	 * @param {string} depFieldArgs.formId
	 * @param {bool} onCurrentPage
     */
	function showFieldAndSetValue( depFieldArgs, onCurrentPage ) {
		if ( isFieldCurrentlyShown( depFieldArgs.containerId, depFieldArgs.formId ) ) {
			return;
		}

		removeFromHideFields( depFieldArgs.containerId, depFieldArgs.formId );

		if ( depFieldArgs.fieldType === 'submit' ) {
			if ( onCurrentPage ) {
				showOrEnableSubmitButton( depFieldArgs );
			}
			return;
		}
		if ( onCurrentPage ) {
			// Set value, then show field
			setValuesInsideFieldOnPage( depFieldArgs.containerId, depFieldArgs );
			showFieldContainer( depFieldArgs.containerId );
			triggerEvent( document, 'frmShowField' );
			if ( depFieldArgs.inputType === 'rte' ) {
				reInitializeRichText( 'field_' + depFieldArgs.fieldKey );
			}
		} else {
			setValuesInsideFieldAcrossPage( depFieldArgs );
		}
	}

	/**
	 * @param {string} fieldId
	 */
	function reInitializeRichText( fieldId ) {
		var isVisible = 'undefined' !== typeof tinyMCE.editors[ fieldId ] && ! tinyMCE.editors[ fieldId ].isHidden();
		if ( ! isVisible ) {
			return;
		}
		removeRichText( fieldId );
		initRichText( fieldId );
	}

	/**
	 * Show or enable submit button, as appropriate based on submit Conditional Logic.
	 *
	 * @param depFieldArgs
	 */
	function showOrEnableSubmitButton( depFieldArgs ) {
		if ( depFieldArgs.hideDisable && depFieldArgs.hideDisable === 'disable' ) {
			enableButton( '#' + depFieldArgs.containerId );
		} else {
			showFieldContainer( depFieldArgs.containerId );
		}

		removeSubmitButtonFromHiddenList( depFieldArgs );
	}

	/**
	 * Remove submit button from list of hidden submit buttons on page
	 *
	 * @param depFieldArgs
	 */
	function removeSubmitButtonFromHiddenList( depFieldArgs ) {
		hiddenSubmitButtons = hiddenSubmitButtons.filter( function( button ) {
			return button !== depFieldArgs.formKey;
		});
	}

	/**
	 * Enable button with given selector.
	 *
	 * @param {string} buttonSelector
	 *
	 * @returns {void}
	 */
	function enableButton( buttonSelector ) {
		var button = document.querySelector( buttonSelector );
		if ( button && ! button.closest( '.frm_loading_form' ) ) {
			button.disabled = false;
		}
	}

	/**
	 * Set the value for all inputs inside of a field div on the current page
	 *
	 * @param {string} container
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldType
	 * @param {string} depFieldArgs.formId
 	 */
	function setValuesInsideFieldOnPage( container, depFieldArgs ) {
		var inputs = getInputsInFieldOnPage( container ),
			inContainer = ( depFieldArgs.fieldType === 'divider' || depFieldArgs.fieldType === 'form' );

		setValueForInputs( inputs, inContainer, depFieldArgs.formId, 'required' );
	}

	/**
	 * Set the value for all inputs inside of a field across a page
	 *
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldType
	 * @param {string} depFieldArgs.formId
	 */
	function setValuesInsideFieldAcrossPage( depFieldArgs ) {
		var inputs = getInputsInFieldAcrossPage( depFieldArgs ),
			inContainer = depFieldArgs.fieldType === 'divider' || depFieldArgs.fieldType === 'form' || depFieldArgs.isRepeating;

		setValueForInputs( inputs, inContainer, depFieldArgs.formId );
	}

	/**
	 * Gets inputs in field on page.
	 *
	 * @since 5.4 Accepted HTML element as param.
	 *
	 * @param {String|HTMLElement} containerId ID of container or container element.
	 * @return {Array}
	 */
	function getInputsInFieldOnPage( containerId ) {
		var container = 'string' === typeof containerId ? document.getElementById( containerId ) : containerId;
		return container.querySelectorAll( 'select[name^="item_meta"], textarea[name^="item_meta"], input[name^="item_meta"]' );
	}

	/**
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldType
	 * @returns {Array}
     */
	function getInputsInFieldAcrossPage( depFieldArgs ) {
		var inputs = [];

		if ( depFieldArgs.fieldType === 'divider' ) {
			inputs = getInputsInHiddenSection( depFieldArgs );
		} else if ( depFieldArgs.fieldType === 'form' ) {
			inputs = getInputsInHiddenEmbeddedForm( depFieldArgs );
		} else {
			inputs = getHiddenInputs( depFieldArgs );
		}

		return inputs;
	}

	/**
	 * Get the inputs for a non-repeating field that is type=hidden
	 * @param {object} depFieldArgs
	 * @param {bool} depFieldArgs.isRepeating
	 * @param {string} depFieldArgs.inSection
	 * @param {string} depFieldArgs.repeatRow
	 * @param {string} depFieldArgs.fieldId
	 * @returns {NodeList}
     */
	function getHiddenInputs( depFieldArgs ) {
		var name = '';
		if ( depFieldArgs.isRepeating ) {
			//item_meta[section-id][row-id][field-id]
			var containerFieldId = getContainerFieldId( depFieldArgs );
			name = 'item_meta[' + containerFieldId + '][' + depFieldArgs.repeatRow + '][' + depFieldArgs.fieldId + ']';
		} else {
			// item_meta[field-id]
			name = 'item_meta[' + depFieldArgs.fieldId + ']';
		}
		return document.querySelectorAll( '[name^="' + name + '"]' );
	}

	function setValueForInputs( inputs, inContainer, formId, setRequired ) {
		var input, prevInput, i;

		if ( ! inputs.length ) {
			return;
		}

		for ( i = 0; i < inputs.length; i++ ) {
			input = inputs[ i ];

			// Don't set the value if the field is in a section and it's conditionally hidden
			if ( inContainer && isChildInputConditionallyHidden( input, formId ) ) {
				continue;
			}

			if ( setRequired === 'required' ) {
				maybeAddRequiredTag( input );
			}

			if ( skipSetValue( i, prevInput, inputs ) ) {
				continue;
			}

			setDefaultValue( input, inContainer );
			maybeSetWatchingFieldValue( input );
			setShownProduct( input );
			maybeDoCalcForSingleField( input );

			prevInput = input;
		}
	}

	/**
	 * When a field is shown with logic, add the html and aria
	 * required attributes if the field is required.
	 */
	function maybeAddRequiredTag( input ) {
		var isRequired, isOptional;

		if ( input.type === 'checkbox' || input.type === 'radio' || input.type === 'file' ) {
			return;
		}

		isRequired = input.parentElement.className.indexOf( 'frm_required_field' );
		isOptional = input.className.indexOf( 'frm_optional' );

		if ( isRequired > -1 && isOptional === -1 ) {
			input.setAttribute( 'aria-required', true );
		}
	}

	/**
	 * Don't loop through every input in a radio/checkbox/other field
	 * TODO: Improve this for checkboxes and address fields
	 */
	function skipSetValue( i, prevInput, inputs ) {
		var typeArray = [ 'checkbox', 'radio' ];

		if ( i < 1 || prevInput === undefined ) {
			return false;
		}

		if ( null !== inputs[ i ].getAttribute( 'data-frmprice' ) ) {
			// never skip calculating a product price.
			return false;
		}

		var isOther = inputs[ i ].className.includes('frm_other_input');
		return isOther || ( prevInput.name == inputs[ i ].name && typeArray.includes(prevInput.type) );
	}

	// Check if a field input inside of a section or embedded form is conditionally hidden
	function isChildInputConditionallyHidden( input, formId ) {
		var fieldDivPart = frmFrontForm.getFieldId( input, true ),
			fieldDivId = 'frm_field_' + fieldDivPart + '_container';

		return isFieldConditionallyHidden( fieldDivId, formId );
	}

	function showFieldContainer( containerId ) {
		var $container = jQuery( '#' + containerId ).show();
		if ( $container.hasClass( 'frm_inside_container' ) && null === $container.find( 'select' ).val() ) {
			$container.find( 'select' ).val( '' ).trigger( 'change' );
		}
	}

	/**
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.containerId
	 * @param {string} depFieldArgs.formId
	 * @param onCurrentPage
     */
	function hideFieldAndClearValue( depFieldArgs, onCurrentPage ) {
		if ( isFieldConditionallyHidden( depFieldArgs.containerId, depFieldArgs.formId ) ) {
			return;
		}

		addToHideFields( depFieldArgs.containerId, depFieldArgs.formId );

		if ( depFieldArgs.fieldType === 'submit' ) {
			if ( onCurrentPage ) {
				hideOrDisableSubmitButton( depFieldArgs );
			}
			return;
		}

		if ( onCurrentPage ) {
			hideFieldContainer( depFieldArgs.containerId );
			clearInputsInFieldOnPage( depFieldArgs.containerId );
		} else {
			clearInputsInFieldAcrossPage( depFieldArgs );
		}
	}

	/**
	 * Hide or disable given submit button as appropriate, based on submit Conditional Logic
	 *
	 * @param depFieldArgs
	 */
	function hideOrDisableSubmitButton( depFieldArgs ) {
		if ( depFieldArgs.containerId == undefined ) {
			depFieldArgs.containerId = getSubmitButtonContainerID( depFieldArgs );
		}

		addSubmitButtonToHiddenList( depFieldArgs );

		if ( depFieldArgs.hideDisable && depFieldArgs.hideDisable === 'disable' ) {
			disableButton( '#' + depFieldArgs.containerId );
		} else {
			hideFieldContainer( depFieldArgs.containerId );
		}
	}

	/**
	 * Add submit button to list of hidden submit buttons on page
	 *
	 * @param depFieldArgs
	 */
	function addSubmitButtonToHiddenList( depFieldArgs ) {
		hiddenSubmitButtons.push( depFieldArgs.formKey );
	}

	/**
	 * Checks if a particular submit button on the current page is hidden
	 *
	 * @param {string} formKey
	 * @returns {boolean}
	 */
	function isOnPageSubmitButtonHidden( formKey ) {
		return hiddenSubmitButtons.includes(formKey);
	}

	/**
	 * Hide submit button with specified container ID that was previously hidden.
	 *
	 * @param submitContainerID
	 */
	function hidePreviouslyHiddenSubmitButton( submitContainerID ) {
		var formId = submitContainerID.replace( 'frm_form_', '' );
		formId = formId.replace( '_container .frm_final_submit', '' );

		var depFieldArgs = getRulesForSingleField( 'submit_' + formId );
		if ( depFieldArgs ) {
			hideOrDisableSubmitButton( depFieldArgs );
		}
	}

	/**
	 * Get the form key (e.g. t3cq5) from the form element id (e.g. form_t3cq5)
	 *
	 * @param elementId
	 * @returns {string} form key
	 */
	function getFormKeyFromFormElementID( elementId ) {
		return elementId.replace( 'form_', '' );
	}

	function hideFieldContainer( containerId ) {
		jQuery( '#' + containerId ).hide();
	}

	/**
	 * Disable button with specified selector
	 *
	 * @param buttonSelector
	 */
	function disableButton( buttonSelector ) {
		jQuery( buttonSelector ).prop( 'disabled', true );
	}

	function jsonParse( str ) {
		try {
			var obj = JSON.parse( str );
			return obj;
		} catch ( e ) {
			return false;
		}
	}

	function clearInputsInFieldOnPage( containerId ) {
		var inputs = getInputsInFieldOnPage( containerId );
		clearValueForInputs( inputs, 'required' );
	}

	function clearInputsInFieldAcrossPage( depFieldArgs ) {
		var inputs = getInputsInFieldAcrossPage( depFieldArgs );
		clearValueForInputs( inputs );
	}

	/**
	 * Get all the child inputs in a hidden section (across a page)
	 *
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldType
	 * @param {string} depFieldArgs.fieldId
 	 */
	function getInputsInHiddenSection( depFieldArgs ) {
		// If a section, get all inputs with data attribute
		var inputs = [];
		if ( depFieldArgs.fieldType === 'divider' ) {
			inputs = document.querySelectorAll( '[data-sectionid="' + depFieldArgs.fieldId + '"]' );
		}

		return inputs;
	}

	// Get all the child inputs in a hidden embedded form (across a page)
	function getInputsInHiddenEmbeddedForm( depFieldArgs ) {
		// TODO: maybe remove form and [0]?
		// TODO: what if someone is using embeddedformfieldkey-moretext as their field key? Add data attribute for this
		return document.querySelectorAll( '[id^="field_' + depFieldArgs.fieldKey + '-"]' );
	}

	/**
	 * Changes dropdown placeholder option color.
	 *
	 * @since 6.16.1
	 *
	 * @param {HTMLElement} select
	 *
	 * @returns {Void}
	 */
	function changeSelectColor( select ) {
		if ( select.options[select.selectedIndex]?.classList.contains( 'frm-select-placeholder' ) ) {
			const styleElement = select.closest( '.with_frm_style' );
			const textColorDisabled = styleElement ? getComputedStyle( styleElement ).getPropertyValue( '--text-color-disabled' ).trim() : '';
			select.style.setProperty( 'color', textColorDisabled, 'important' );
		}
	}

	/**
	 * Clears value of field inputs.
	 *
	 * @since 5.4 Added the third param.
	 *
	 * @param {Array}   inputs         Array of inputs.
	 * @param {String}  required       Required string.
	 * @param {Boolean} resetToDefault Is `true` if reset to default value. Otherwise, reset to empty value.
	 */
	function clearValueForInputs( inputs, required, resetToDefault ) {
		var prevInput, blankSelect, valueChanged, l, i, input, defaultVal, reset, linkedRadioInput;

		if ( inputs.length < 1 ) {
			return;
		}

		valueChanged = true;

		l = inputs.length;
		for ( i = 0; i < l; i++ ) {
			input = inputs[ i ];

			defaultVal = input.getAttribute( 'data-frmval' );
			reset      = resetToDefault && defaultVal;

			// Don't remove values from some fields.
			if ( input.className.includes('frm_dnc') || input.name.includes('[row_ids]') ) {
				prevInput = input;
				continue;
			}

			if ( i > 0 && prevInput.name != input.name && valueChanged === true ) {
				// Only trigger a change after all inputs in a field are cleared
				triggerChange( jQuery( prevInput ) );
			}

			valueChanged = true;

			if ( input.type === 'radio' || input.type === 'checkbox' ) {
				if ( ! reset ) {
					input.checked = false;
				} else if ( 'radio' === input.type ) {
					input.checked = defaultVal === input.value;
				} else {
					resetCheckboxInputToValue( input, defaultVal );
				}

				maybeClearStarRatingInput( input );
			} else if ( input.tagName === 'SELECT' ) {
				if ( isSlimSelect( input ) ) {
					setSlimValue( input, reset ? defaultVal : '' );
				} else {
					if ( ! reset && resetToDefault && input.getAttribute( 'data-placeholder' ) ) {
						// When a dropdown has no default value, a placeholder option is added.
						// We want to reset to that, as it works the same as an empty default value.
						reset      = true;
						defaultVal = '';
					}

					if ( ! reset ) {
						blankSelect = input.selectedIndex === 0 && input.options[ 0 ].text.trim() === '';
						if ( blankSelect || ( input.selectedIndex === -1 ) ) {
							valueChanged = false;
						} else {
							input.selectedIndex = -1;
						}
					} else {
						valueChanged = resetSelectInputToValue( input, defaultVal );
					}

					const chosenId     = input.id.replace( /[^\w]/g, '_' ); // Match what the script is doing.
					const autocomplete = document.getElementById( chosenId + '_chosen' );
					if ( autocomplete !== null ) {
						jQuery( input ).trigger( 'chosen:updated' );
					}
				}
			} else if ( input.type === 'range' ) {
				let sliderDefault = defaultVal;
				if ( ! reset ) {
					// Reset to mid value
					const min = parseFloat( input.getAttribute( 'min' ) );
					const max = parseFloat( input.getAttribute( 'max' ) );
					const mid = ( ( max - min ) / 2 ) + min;
					const step  = parseFloat( input.getAttribute( 'step' ), 10 );
					const midSteps = Math.round( mid / step );
					sliderDefault = midSteps * step;
					input.parentElement.querySelector( '.frm_range_value' ).textContent = sliderDefault;
				}
				input.value = sliderDefault;
				initRangeInput( [ input ] );
			} else if ( input.getAttribute( 'data-frmprice' ) !== null ) {
				setHiddenProduct( input );
			} else {
				if ( ! reset ) {
					input.value = '';
				} else {
					input.value = defaultVal;
				}

				linkedRadioInput = input.id.includes('-otext') ? document.getElementById( input.id.replace( '-otext', '' ) ) : null;
				if ( linkedRadioInput && linkedRadioInput.checked === false ) {
					input.classList.add( 'frm_pos_none' );
				}

				if ( null !== input.getAttribute( 'data-frmfile' ) ) {
					clearDropzoneFiles( input );
				}
				if ( input.type === 'hidden' && input.closest( '.frm_range_container ' ) ) {
					resetRangeInput( input );
				}
			}

			if ( required === 'required' ) {
				input.required = false;
				input.setAttribute( 'aria-required', false );
			}

			prevInput = inputs[ i ];
		}

		// trigger a change for the final input in the loop
		if ( valueChanged === true ) {
			triggerChange( jQuery( prevInput ) );
		}
	}

	function resetRangeInput( input ) {
		const rangeContainer          = input.parentElement;
		const rangeSliderWrapperClone = rangeContainer.querySelector( '.frm-slider-wrapper' ).cloneNode( true );
		// Replace old range slider elements with cloned ones to prevent jerky behavior after reset when initializeRangeSlider is called.
		rangeContainer.querySelector( 'div:last-of-type' ).replaceChild( rangeSliderWrapperClone, rangeContainer.querySelector( '.frm-slider-wrapper' ) );
		initializeRangeSlider( input, rangeContainer );
	}

	function setSlimValue( input, value ) {
		if ( value.length && '[' === value[0] && -1 !== value.indexOf( ',' ) && ']' === value[ value.length - 1 ] && 'multiple' === input.getAttribute( 'multiple' ) ) {
			// Array values are stored as a JSON string.
			value = JSON.parse( value );
		}
		input.slim.setSelected( value );
	}

	/**
	 * Maybe clear star rating.
	 *
	 * @since 5.4
	 *
	 * @param {HTMLElement} input This should be the last input in star rating.
	 */
	function maybeClearStarRatingInput( input ) {
		var starGroup, checkedInput;
		if ( 'radio' !== input.type || ! input.matches( '.frm-star-group input:last-of-type' ) ) {
			return;
		}

		starGroup    = input.closest( '.frm-star-group' );
		checkedInput = starGroup.querySelector( 'input:checked' );
		if ( checkedInput ) {
			updateStars( checkedInput );
		} else {
			clearStars( starGroup, true );
		}
	}

	/**
	 * Resets checkbox input to a value.
	 *
	 * @since 5.4
	 *
	 * @param {HTMLElement} input Checkbox element.
	 * @param {Object}      val   The value.
	 */
	function resetCheckboxInputToValue( input, val ) {
		var i;

		val = jsonParse( val );

		if ( ! val ) {
			return;
		}

		for ( i in val ) {
			if ( val[ i ] === input.value ) {
				input.checked = true;
				return;
			}
		}

		input.checked = false;
	}

	/**
	 * Resets select input to a value.
	 *
	 * @since 5.4
	 *
	 * @param {HTMLElement}   input Select element.
	 * @param {String|Object} val   The value. Is object if multiselect.
	 * @return {Boolean}            Return `true` if value is changed.
	 */
	function resetSelectInputToValue( input, val ) {
		if ( input.multiple ) {
			return resetMultiSelectInputToValue( input, val );
		}

		var i,
			valueChanged = false,
			options = input.querySelectorAll( 'option' );

		for ( i = 0; i < options.length; i++ ) {
			if ( val === options[ i ].value && ! options[ i ].selected ) {
				options[ i ].selected = true;
				valueChanged = true;
				continue;
			}

			if ( val !== options[ i ].value && options[ i ].selected ) {
				options[ i ].selected = false;
				valueChanged = true;
			}
		}

		return valueChanged;
	}

	/**
	 * Resets multiselect input to a value.
	 *
	 * @since 5.4
	 *
	 * @param {HTMLElement} input Select element.
	 * @param {Object}      val   The value.
	 * @return {Boolean}          Return `true` if value is changed.
	 */
	function resetMultiSelectInputToValue( input, val ) {
		val = jsonParse( val );

		if ( ! val ) {
			return false;
		}

		var i, contained,
			valueChanged = false,
			options = input.querySelectorAll( 'option' );

		for ( i = 0; i < options.length; i++ ) {
			contained = objectContainValue( val, options[ i ].value );
			if ( contained && ! options[ i ].selected ) {
				options[ i ].selected = true;
				valueChanged = true;
				continue;
			}

			if ( ! contained && options[ i ].selected ) {
				options[ i ].selected = false;
				valueChanged = true;
			}
		}

		return valueChanged;
	}

	/**
	 * Checks if object contains a value.
	 *
	 * @since 5.4
	 *
	 * @param {Object} obj Object.
	 * @param {Mixed}  val Value.
	 * @return {Boolean}
	 */
	function objectContainValue( obj, val ) {
		var x;

		for ( x in obj ) {
			if ( obj[ x ] === val ) {
				return true;
			}
		}

		return false;
	}

	function clearDropzoneFiles( hiddenFileIdField ) {
		var dropzoneElement = hiddenFileIdField.nextElementSibling;
		if ( dropzoneElement && -1 !== dropzoneElement.className.indexOf( 'frm_dropzone' ) && 'object' === typeof dropzoneElement.dropzone && 'function' === typeof dropzoneElement.dropzone.removeAllFiles ) {
			dropzoneElement.dropzone.removeAllFiles( true );
		}
	}

	function isFieldCurrentlyShown( containerId, formId ) {
		return isFieldConditionallyHidden( containerId, formId ) === false;
	}

	function isFieldConditionallyHidden( containerId, formId ) {
		var hidden = false,
			hiddenFields = getHiddenFields( formId );

		if ( hiddenFields.includes(containerId) ) {
			hidden = true;
		}

		return hidden;
	}

	function clearHideFields() {
		var hideFieldInputs = document.querySelectorAll( '[id^="frm_hide_fields_"]' );
		clearValueForInputs( hideFieldInputs );
	}

	function addToHideFields( htmlFieldId, formId ) { //TODO: why is this run on submit?
		// Get all currently hidden fields
		var hiddenFields = getHiddenFields( formId );

		if ( hiddenFields.includes(htmlFieldId) ) {
			// If field id is already in the array, move on
		} else {
			// Add new conditionally hidden field to array
			hiddenFields.push( htmlFieldId );

			// Set the hiddenFields value in the frm_hide_field_formID input
			hiddenFields = JSON.stringify( hiddenFields );
			var frmHideFieldsInput = document.getElementById( 'frm_hide_fields_' + formId );
			if ( frmHideFieldsInput !== null ) {
				frmHideFieldsInput.value = hiddenFields;
			}
		}
	}

	function getAllHiddenFields() {
		var formId, i,
			hiddenFields = [],
			hideFieldInputs = document.querySelectorAll( '*[id^="frm_hide_fields_"]' ),
			formTotal = hideFieldInputs.length;

		for ( i = 0; i < formTotal; i++ ) {
			formId = hideFieldInputs[ i ].id.replace( 'frm_hide_fields_', '' );
			hiddenFields = hiddenFields.concat( getHiddenFields( formId ) );
		}

		return hiddenFields;
	}

	function getHiddenFields( formId ) {
		var hiddenFields = [];

		// Fetch the hidden fields from the frm_hide_fields_formId input
		var frmHideFieldsInput = document.getElementById( 'frm_hide_fields_' + formId );
		if ( frmHideFieldsInput === null ) {
			return hiddenFields;
		}

		hiddenFields = frmHideFieldsInput.value;
		if ( hiddenFields ) {
			hiddenFields = JSON.parse( hiddenFields );
		} else {
			hiddenFields = [];
		}

		return hiddenFields;
	}

	function setDefaultValue( input, inContainer ) {
		var placeholder, isMultipleSelect,
			$input = jQuery( input ),
			defaultValue = $input.data( 'frmval' );

		if ( defaultValue === undefined && input.classList.contains( 'wp-editor-area' ) ) {
			// set value in rich text
			var defaultField = document.getElementById( input.id + '-frmval' );
			if ( defaultField !== null ) {
				defaultValue = defaultField.value;
				var targetTinyMceEditor = tinymce.get( input.id );
				if ( null !== targetTinyMceEditor ) {
					targetTinyMceEditor.setContent( defaultValue );
				}
			}
		} else if ( defaultValue === undefined && input.type === 'hidden' ) {
			//with read only select input, value is in sibling select
			var $select = $input.next( 'select[disabled]' );
			if ( $select.length > 0 ) {
				defaultValue = $select.data( 'frmval' );
			}
		}

		placeholder = defaultValue;
		defaultValue = setDropdownPlaceholder( defaultValue, input );
		if ( placeholder !== defaultValue ) {
			placeholder = true;
		}

		if ( defaultValue !== undefined ) {
			var numericKey = new RegExp( /\[\d*\]$/i );

			if ( input.type === 'checkbox' || input.type === 'radio' ) {
				setCheckboxOrRadioDefaultValue( input.name, defaultValue );

			} else if ( input.type === 'hidden' && input.name.includes('[]') ) {
				setHiddenCheckboxDefaultValue( input.name, defaultValue );

			// Set for hidden checkbox fields that aren't on the current page. Skip hidden fields in a repeater.
			} else if ( ! inContainer && input.type === 'hidden' && input.name.includes('][') && numericKey.test( input.name ) ) {
				setHiddenCheckboxDefaultValue( input.name.replace( numericKey, '' ), defaultValue );

			} else {
				isMultipleSelect = false;

				if ( placeholder && 'boolean' !== typeof placeholder && input.tagName === 'SELECT' && -1 !== input.className.indexOf( 'frm_chzn' ) ) {
					placeholder = false;
				}

				if ( 'SELECT' === input.tagName && 'multiple' === input.getAttribute( 'multiple' ) ) {
					isMultipleSelect = true;
				}

				if ( defaultValue.constructor === Object ) {
					if ( ! isMultipleSelect ) {
						var addressType = input.getAttribute( 'name' ).split( '[' ).slice( -1 )[ 0 ];
						if ( addressType !== null ) {
							addressType = addressType.replace( ']', '' );
							defaultValue = defaultValue[ addressType ];
							if ( defaultValue === undefined ) {
								defaultValue = '';
							}
						}
					}
				}

				if ( isMultipleSelect ) {
					selectMultiselectOptions( input, defaultValue );
				} else {
					if ( typeof defaultValue === 'object' ) {
						// Don't replace a field shortcode with the id.
						defaultValue = '[' + defaultValue + ']';
					}

					if ( isSlimSelect( input ) ) {
						// This is true even when no placeholder or default is set when autocomplete is enabled.
						// So we need to check if the default value is an empty string and remove the hidden placeholder.
						if ( '' === defaultValue ) {
							maybeRemoveHiddenPlaceholder( input );
						}

						input.slim.setSelected( defaultValue );
					} else if ( input.classList.contains( 'frm_chzn' ) ) {
						if ( '' === defaultValue ) {
							maybeRemoveHiddenPlaceholder( input );
						}

						jQuery( input ).val( defaultValue ).trigger( 'chosen:updated' );
					} else {
						input.value = defaultValue;

						// Trigger change event for range slider input so it knows to sync the handles.
						if ( 'hidden' === input.type && input.dataset.isRangeSliderInitialized ) {
							input.dispatchEvent( new Event( 'change' ) );
						}

						if ( 'range' === input.type ) {
							// Trigger input event
							input.dispatchEvent( new Event( 'input' ) );
						}
					}
				}
				if ( 'SELECT' === input.tagName ) {
					changeSelectColor( input );
				}
			}

			if ( ! placeholder && input.tagName === 'SELECT' ) {
				maybeUpdateChosenOptions( input );
				if ( input.value === '' ) {
					setOtherSelectValue( input, defaultValue );
				}
			}

			triggerChange( $input );
		} else if ( 'SELECT' === input.tagName ) {
			maybeRemoveHiddenPlaceholder( input );
		}
	}

	/**
	 * @since 6.16.2
	 *
	 * @param {HTMLElement} input A select element.
	 * @return {boolean} True if the hidden placeholder is detected and removed.
	 */
	function maybeRemoveHiddenPlaceholder( input ) {
		const hiddenPlaceholder = input.querySelector( 'option[value=""].frm_hidden.frm_hidden_placeholder' );
		if ( ! hiddenPlaceholder ) {
			return false;
		}

		hiddenPlaceholder.remove();
		return true;
	}

	function isSlimSelect( input ) {
		return input.classList.contains( 'frm_slimselect' ) && 'object' === typeof input.slim;
	}

	function selectMultiselectOptions( select, values ) {
		if ( isSlimSelect( select ) && 'function' === typeof Object.values ) {
			select.slim.setSelected( Object.values( values ) );
			return;
		}

		var valueKey, option;
		for ( valueKey in values ) {
			option = select.querySelector( 'option[value="' + values[ valueKey ] + '"]' );
			if ( option ) {
				option.selected = true;
			}
		}
	}

	/**
	 * Select the option with placeholder if applicable.
	 */
	function setDropdownPlaceholder( defaultValue, input ) {
		var placeholder;
		if ( defaultValue === undefined && input.tagName === 'SELECT' ) {
			placeholder = input.getAttribute( 'data-placeholder' );
			if ( placeholder !== null ) {
				defaultValue = '';
			}
		}
		return defaultValue;
	}

	function setCheckboxOrRadioDefaultValue( inputName, defaultValue ) {
		// Get all checkbox/radio inputs for this field
		var radioInputs = document.getElementsByName( inputName ),
			isSet = false,
			firstInput = false;

		if ( typeof defaultValue === 'object' ) {
			// Convert the object to an array.
			defaultValue = Object.keys( defaultValue ).map(
				function( key ) {
					return defaultValue[ key ];
				}
			);
		}

		let hiddenFieldIndex = 0;

		// Loop through options and set the default value
		for ( var i = 0, l = radioInputs.length; i < l; i++ ) {
			if ( firstInput === false ) {
				firstInput = radioInputs[ i ];
			}

			if ( radioInputs[ i ].type === 'hidden' ) {
				// If field is read-only and there is a hidden input
				if ( Array.isArray( defaultValue ) && defaultValue[ hiddenFieldIndex ] !== null && 'undefined' !== typeof defaultValue[ hiddenFieldIndex ] ) {
					radioInputs[ i ].value = defaultValue[ hiddenFieldIndex ];
				} else {
					radioInputs[ i ].value = defaultValue;
				}
				hiddenFieldIndex++;
				isSet = true;
			} else if ( radioInputs[ i ].value == defaultValue ||
				( Array.isArray( defaultValue ) && defaultValue.includes(radioInputs[ i ].value) ) ) {
				// If input's value matches the default value, set checked to true

				radioInputs[ i ].checked = true;
				isSet = true;
				if ( radioInputs[ i ].type === 'radio' ) {
					break;
				}
			}
		}

		if ( ! isSet && firstInput !== false ) {
			setOtherValueLimited( firstInput, defaultValue );
		}
	}

	// Set the default value for hidden checkbox or multi-select dropdown fields
	function setHiddenCheckboxDefaultValue( inputName, defaultValue ) {
		// Get all the hidden inputs with the same name
		var hiddenInputs = jQuery( 'input[name^="' + inputName + '"]' ).get();

		if ( typeof defaultValue === 'object' ) {
			// Convert the object to an array.
			defaultValue = Object.keys( defaultValue ).map(
				function( key ) {
					return defaultValue[ key ];
				}
			);
		}

		if ( Array.isArray( defaultValue ) ) {
			for ( var i = 0, l = defaultValue.length; i < l; i++ ) {
				if ( i in hiddenInputs ) {
					hiddenInputs[ i ].value = defaultValue[ i ];
				} else {
					// TODO: accommodate for when there are multiple default values but the user has removed some
				}
			}
		} else if ( hiddenInputs[ 0 ] !== null && hiddenInputs[ 0 ] !== undefined ) {
			hiddenInputs[ 0 ].value = defaultValue;
		}
	}

	function removeFromHideFields( htmlFieldId, formId ) {
		// Get all currently hidden fields
		var hiddenFields = getHiddenFields( formId );

		// If field id is in the array, delete it
		var itemIndex = hiddenFields.indexOf( htmlFieldId );
		if ( itemIndex > -1 ) {
			// Remove field from the hiddenFields array
			hiddenFields.splice( itemIndex, 1 );

			// Update frm_hide_fields_formId input
			hiddenFields = JSON.stringify( hiddenFields );
			var frmHideFieldsInput = document.getElementById( 'frm_hide_fields_' + formId );
			frmHideFieldsInput.value = hiddenFields;
		}
	}

	/*****************************************************
	 * Lookup Field Functions
	 ******************************************************/

	/**
	 * Check all fields that are "watching" a lookup field that changed
 	 */
	function checkFieldsWatchingLookup( fieldId, changedInput, originalEvent ) {
		if ( typeof __FRMLOOKUP  === 'undefined' ||
			__FRMLOOKUP[ fieldId ] === undefined ||
			__FRMLOOKUP[ fieldId ].dependents.length < 1 ||
			changedInput === null ||
			changedInput === undefined
		) {
			return;
		}

		var triggerFieldArgs = __FRMLOOKUP[ fieldId ];

		var parentRepeatArgs = getRepeatArgsFromFieldName( changedInput[ 0 ].name );

		for ( var i = 0, l = triggerFieldArgs.dependents.length; i < l; i++ ) {
			updateWatchingFieldById( triggerFieldArgs.dependents[ i ], parentRepeatArgs, originalEvent );
		}
	}

	/**
	 * Update all instances of a "watching" field
	 *
	 * @since 2.01.0
	 *
	 * @param {string} fieldId
	 * @param {Object} parentRepeatArgs
	 * @param {string} originalEvent
	 * @return {void}
     */
	function updateWatchingFieldById( fieldId, parentRepeatArgs, originalEvent ) {
		var childFieldArgs = getLookupArgsForSingleField( fieldId );

		// If lookup field has no parents, no need to update this field
		if ( childFieldArgs === false || childFieldArgs.parents.length < 1 ) {
			return;
		}

		if ( childFieldArgs.fieldType === 'lookup' ) {
			updateLookupFieldOptions( childFieldArgs, parentRepeatArgs );
		} else if ( originalEvent === 'value changed' ) {
			// If the original event was NOT triggered from a direct value change to the Lookup field,
			// do not update the text field value.
			updateWatchingFieldValue( childFieldArgs, parentRepeatArgs );
		}
	}

	/**
	 * Update a Lookup field's options
	 *
	 * @param {Object} childFieldArgs
	 * @param {Object} parentRepeatArgs
	 * @param {String} parentRepeatArgs.repeatRow
     */
	function updateLookupFieldOptions( childFieldArgs, parentRepeatArgs ) {
		var childFieldElements = [];
		if ( parentRepeatArgs.repeatRow !== '' ) {
			childFieldElements = getRepeatingFieldDivOnCurrentPage( childFieldArgs, parentRepeatArgs );
		} else {
			childFieldElements = getAllFieldDivsOnCurrentPage( childFieldArgs );
		}

		for ( var i = 0, l = childFieldElements.length; i < l; i++ ) {
			addRepeatRow( childFieldArgs, childFieldElements[ i ].id );
			updateSingleLookupField( childFieldArgs, childFieldElements[ i ]);
		}

		processPendingLookups();
	}

	/**
	 * Go through the list of pending look ups and process them all.
	 * These are done in batches of 20 at a time.
	 *
	 * @since 6.15
	 *
	 * @return {void}
	 */
	function processPendingLookups() {
		const unprocessedPendingLookups = getUnprocessedPendingLookups();

		if ( ! unprocessedPendingLookups.length ) {
			return;
		}

		const formId = unprocessedPendingLookups[0].childFieldArgs.formId;

		// We want to allow duplicate form ids (inside repeaters) here because the number of calls
		// to enableFormAfterLookup matters for handling the processesRunning variable.
		const allFormIds = [];

		const batchSize = 20;
		const batches   = Math.ceil( unprocessedPendingLookups.length / batchSize );
		for ( let batchNumber = 0; batchNumber < batches; ++batchNumber ) {
			const start = batchNumber * batchSize;
			const end   = start + batchSize;

			const currentBatch = unprocessedPendingLookups.slice( start, end );

			currentBatch.forEach(
				function( pendingLookup ) {
					pendingLookup.pending = false;
					allFormIds.push( pendingLookup.childFieldArgs.formId );
				}
			);

			postToAjaxUrl(
				getFormById( formId ),
				{
					action: 'frm_replace_lookup_field_options_arr',
					postData: currentBatch.map( pendingLookup => pendingLookup.childFieldArgs ),
					nonce: frm_js.nonce
				},
				function( newOptionsByFieldId ) {
					allFormIds.forEach( enableFormAfterLookup );

					Object.entries( newOptionsByFieldId ).forEach( entry => {
						const key          = entry[0];
						const newOptions   = entry[1];
						const optionLabels = 'undefined' !== typeof newOptionsByFieldId[ key + '_label' ] ? newOptionsByFieldId[ key + '_label' ] : [];

						currentBatch.forEach(
							function( pendingLookup ) {
								if ( ! isNaN( key ) && pendingLookup.childFieldArgs.unique === parseInt( key ) ) {
									pendingLookup.callback( newOptions, optionLabels );
								}
							}
						);
					});
				},
				false,
				{
					dataType: 'json'
				}
			);
		}
	}

	/**
	 * @return {Array}
	 */
	function getUnprocessedPendingLookups() {
		return pendingLookupFieldAjax.filter( pendingLookup => pendingLookup.pending );
	}

	/**
	 * Get the div for a repeating field on the current page
	 * @param {Object} childFieldArgs
	 * @param {string} childFieldArgs.fieldId
	 * @param {Object} parentRepeatArgs
	 * @param {string} parentRepeatArgs.repeatingSection
	 * @param {string} parentRepeatArgs.repeatRow
	 * @returns {Array}
     */
	function getRepeatingFieldDivOnCurrentPage( childFieldArgs, parentRepeatArgs ) {
		var childFieldDivs = [],
			selector = 'frm_field_' + childFieldArgs.fieldId + '-';

		selector += parentRepeatArgs.repeatingSection + '-' + parentRepeatArgs.repeatRow + '_container';
		var container = document.getElementById( selector );
		if ( container !== null ) {
			childFieldDivs.push( container );
		}

		return childFieldDivs;
	}

	function updateWatchingFieldValue( childFieldArgs, parentRepeatArgs ) {
		var childFieldElements = getAllTextFieldInputs( childFieldArgs, parentRepeatArgs );

		for ( var i = 0, l = childFieldElements.length; i < l; i++ ) {
			addRepeatRowForInput( childFieldElements[ i ].name, childFieldArgs );
			updateSingleWatchingField( childFieldArgs, childFieldElements[ i ]);
		}
	}

	/**
	 * Get the Lookup Args for a field ID
	 *
	 * @param {string} fieldId
	 * @return {boolean|Object}
     */
	function getLookupArgsForSingleField( fieldId ) {
		if ( typeof __FRMLOOKUP  === 'undefined' || __FRMLOOKUP[ fieldId ] === undefined ) {
			return false;
		}

		return __FRMLOOKUP[ fieldId ];
	}

	/**
	 * Update a single Lookup field
	 *
	 * @since 2.01.0
	 * @param {Object} childFieldArgs
	 * @param {string} childFieldArgs.inputType
	 * @param {object} childElement
     */
	function updateSingleLookupField( childFieldArgs, childElement ) {
		childFieldArgs.parentVals = getParentLookupFieldVals( childFieldArgs );

		if ( childFieldArgs.inputType === 'select' ) {
			maybeReplaceSelectLookupFieldOptions( childFieldArgs, childElement );
		} else if ( childFieldArgs.inputType === 'radio' || childFieldArgs.inputType === 'checkbox' ) {
			maybeReplaceCbRadioLookupOptions( childFieldArgs, childElement );
		} else if ( childFieldArgs.inputType === 'data' ) {
			maybeReplaceLookupList( childFieldArgs, childElement );
		}
	}

	/**
	 * Update a standard field that is "watching" a Lookup
	 *
	 * @since 2.01.0
	 *
	 * @param {Object} childFieldArgs
	 * @param {object} childElement
	 * @return {void}
	 */
	function updateSingleWatchingField( childFieldArgs, childElement ) {
		childFieldArgs.parentVals = getParentLookupFieldVals( childFieldArgs );

		if ( currentLookupHasQueue( childElement.id ) ) {
			addLookupToQueueOfTwo( childFieldArgs, childElement );
			return;
		}

		addLookupToQueueOfTwo( childFieldArgs, childElement );

		maybeInsertValueInFieldWatchingLookup( childFieldArgs, childElement );
	}

	/**
	 * Get all the occurrences of a specific Text field
	 *
	 * @since 2.01.0
	 * @param {Object} childFieldArgs
	 * @param {boolean} childFieldArgs.isRepeating
	 * @param {string} childFieldArgs.fieldKey
	 * @param {Object} parentRepeatArgs
	 * @param {string} parentRepeatArgs.repeatingSection
	 * @param {string} parentRepeatArgs.repeatRow
	 * @return {NodeList}
 	 */
	function getAllTextFieldInputs( childFieldArgs, parentRepeatArgs ) {
		var selector = 'field_' + childFieldArgs.fieldKey;
		if ( childFieldArgs.isRepeating ) {
			if ( parentRepeatArgs.repeatingSection !== '' ) {
				// If trigger field is repeating/embedded, use its section row in selector
				selector = '[id="' + selector + '-' + parentRepeatArgs.repeatRow + '"]';
			} else {
				// If trigger field is not repeating/embedded, get all repeating field inputs
				selector = '[id^="' + selector + '-"]';
			}
		} else {
			selector = '[id="' + selector + '"]';
		}

		return document.querySelectorAll( selector );
	}

	// Set the value in a regular field that is watching a lookup field when it is conditionally shown
	function maybeSetWatchingFieldValue( input ) {
		var fieldId = frmFrontForm.getFieldId( input, false ),
			childFieldArgs = getLookupArgsForSingleField( fieldId );

		// If lookup field has no parents, no need to update this field
		if ( childFieldArgs === false || childFieldArgs.fieldType === 'lookup' ) {
			return;
		}

		updateSingleWatchingField( childFieldArgs, input );
	}

	/**
	 * Get all divs on the current page for a given field
	 *
	 * @since 2.01.0
	 * @param {Object} childFieldArgs
	 * @param {boolean} childFieldArgs.isRepeating
	 * @param {string} childFieldArgs.fieldId
	 * @returns {Array}
     */
	function getAllFieldDivsOnCurrentPage( childFieldArgs ) {
		var childFieldDivs = [];

		if ( childFieldArgs.isRepeating ) {
			childFieldDivs = document.querySelectorAll( '.frm_field_' + childFieldArgs.fieldId + '_container' );
		} else {
			var container = document.getElementById( 'frm_field_' + childFieldArgs.fieldId + '_container' );
			if ( container !== null ) {
				childFieldDivs.push( container );
			}
		}

		return childFieldDivs;
	}

	// Get the field values from all parents
	function getParentLookupFieldVals( childFieldArgs ) {
		var parentFieldArgs,
			parentVals = [],
			parentIds = childFieldArgs.parents,
			parentValue = false;

		for ( var i = 0, l = parentIds.length; i < l; i++ ) {
			parentFieldArgs = getLookupArgsForSingleField( parentIds[ i ]);
			parentValue = getFieldValue( parentFieldArgs, childFieldArgs );

			// If any parents have blank values, don't waste time looking for values
			if ( parentValue === '' || parentValue === false ) {
				parentVals = false;
				break;
			}

			parentVals[ i ] = parentValue;
		}

		return parentVals;
	}

	// Get the value from array of radio inputs (could be type="hidden" or type="radio")
	function getValueFromRadioInputs( radioInputs ) {
		var radioValue = false,
			l = radioInputs.length;

		for ( var i = 0; i < l; i++ ) {
			if ( radioInputs[ i ].type === 'hidden' || radioInputs[ i ].checked ) {
				radioValue = radioInputs[ i ].value;
				break;
			}
		}

		return radioValue;
	}

	/**
	 * Maybe replace the options in a Select Lookup field
	 *
	 * @since 2.01.0
	 * @param {Object} childFieldArgs
	 * @param {Array} childFieldArgs.parents
	 * @param {Array} childFieldArgs.parentVals
	 * @param {string} childFieldArgs.fieldId
	 * @param {string} childFieldArgs.fieldKey
	 * @param {string} childFieldArgs.formId
	 * @param {object} childDiv
	 */
	function maybeReplaceSelectLookupFieldOptions( childFieldArgs, childDiv ) {
		// Get select within childDiv
		var childSelect = childDiv.getElementsByTagName( 'SELECT' )[ 0 ];
		if ( childSelect === null ) {
			return;
		}

		var currentValue = childSelect.value;

		if ( childFieldArgs.parentVals === false  ) {
			// If any parents have blank values, don't waste time looking for values
			childSelect.options.length = 1;
			childSelect.value = '';
			maybeUpdateChosenOptions( childSelect );

			if ( currentValue !== '' ) {
				triggerChange( jQuery( childSelect ), childFieldArgs.fieldKey );
			}

		} else {
			disableLookup( childSelect );
			disableFormPreLookup( childFieldArgs.formId );

			getLookupValues(
				childFieldArgs,
				function( newOptions, optionLabels ) {
					replaceSelectLookupFieldOptions( childFieldArgs, childSelect, newOptions, optionLabels );
					triggerLookupOptionsLoaded( jQuery( childDiv ) );
					enableFormAfterLookup( childFieldArgs.formId );
				}
			);
		}
	}

	// Update chosen options if autocomplete is enabled
	function maybeUpdateChosenOptions( childSelect ) {
		if ( childSelect.className.includes('frm_chzn') && jQuery().chosen ) {
			jQuery( childSelect ).trigger( 'chosen:updated' );
		}
	}

	/**
	 * Disable a Select Lookup field and add loading image
	 *
	 * @since 2.02.11
	 * @param {object} childSelect
	 */
	function disableLookup( childSelect ) {
		childSelect.className = childSelect.className + ' frm_loading_lookup';
		childSelect.disabled = true;
		maybeUpdateChosenOptions( childSelect );
	}

	/**
	 * Disable a form prior to a Lookup field's Ajax request
	 *
	 * @since 2.03.02
	 * @param {String} formId
     */
	function disableFormPreLookup( formId ) {
		processesRunning++;

		if ( processesRunning === 1 ) {

			var form = getFormById( formId );
			if ( form !== null ) {
				frmFrontForm.showSubmitLoading( jQuery( form ) );
			}
		}
	}

	/**
	 * Enable a form if all Lookup field requests are completed
	 *
	 * @since 2.03.02
	 * @param {String} formId
	 */
	function enableFormAfterLookup( formId ) {
		var form;

		processesRunning--;
		if ( processesRunning <= 0 ) {
			form = getFormById( formId );
			if ( form !== null ) {
				removeSubmitLoading( form, formId, processesRunning );
			}
		}
	}

	/**
	 * Get a form element by the ID number
	 *
	 * @since 2.03.02
	 * @param {string} formId
	 * @returns {Element}
     */
	function getFormById( formId ) {
		var form = document.querySelector( '#frm_form_' + formId + '_container form' );
		if ( form === null ) {
			// The order is switched on the back end.
			form = document.getElementById( 'frm_form_' + formId + '_container' );
			if ( form !== null ) {
				form = form.closest( 'form' );
			}
		}
		return form;
	}

	/**
	 * Enable a Select Lookup field and remove loading image
	 *
	 * @since 2.02.11
	 * @param {object} childSelect
	 * @param {boolean} isReadOnly
	 */
	function enableLookup( childSelect, isReadOnly ) {
		if ( isReadOnly === false ) {
			childSelect.disabled = false;
		}
		childSelect.className = childSelect.className.replace( ' frm_loading_lookup', '' );
	}

	/**
	 * @param {HTMLElement} element
	 * @returns {bool}
	 */
	function isMultiSelect( element ) {
		return element.tagName.toLowerCase() === 'select' && element.multiple;
	}

	/**
	 * Gets selected options for a multiselect element.
	 *
	 * @param {HTMLElement} element
	 * @returns {Array}
	 */
	function getSelectedOptions( element ) {
		if ( isSlimSelect( element ) ) {
			return element.slim.getSelected();
		}

		if ( ! isMultiSelect( element ) ) {
			return element.value;
		}

		var i, option,
			selectedOptions = [];
		for ( i = 0; i < element.options.length; i++ ) {
			option = element.options[i];
			if ( option.selected ) {
				selectedOptions.push( option.value );
			}
		}

		return selectedOptions;
	}

	/**
	 * Sets selected options for a dropdown element.
	 *
	 * @param {HTMLElement} element
	 * @param {Array} values
	 */
	function setSelectedOptions( element, values ) {
		var option;

		if ( ! isMultiSelect( element ) ) {
			element.value = values;
			return;
		}

		// Reset all options to unselected
		Array.from( element.options ).forEach( function( option ) {
			option.selected = false;
		});

		// Set selected attribute for the specified values
		values.forEach( function( value ) {
			option = element.querySelector( 'option[value="' + value + '"]' );
			if ( option ) {
				option.selected = true;
			}
		});
	}

	/**
	 * Replace the options in a Select Lookup field
	 *
	 * @since 2.01.0
	 * @param {Object} fieldArgs
	 * @param {string} fieldArgs.fieldKey
	 * @param {boolean} fieldArgs.isReadOnly
	 * @param {object} childSelect
	 * @param {Array} newOptions
	 */
	function replaceSelectLookupFieldOptions( fieldArgs, childSelect, newOptions, optionLabels ) {
		var origVal, i, optsLength, newOption, optionData;

		// Get selected options
		origVal = getSelectedOptions( childSelect );

		// Remove old options
		for ( i = childSelect.options.length; i > 0; i-- ) {
			childSelect.remove( i );
		}

		// Add new options
		optsLength = newOptions.length;
		optionData = [];

		if ( childSelect.options.length > 0 && '' === childSelect.options[0].value ) {
			optionData.push({
				text: childSelect.options[0].textContent,
				value: childSelect.options[0].value,
				placeholder: true
			});
		}

		for ( i = 0; i < optsLength; i++ ) {
			const optionLabel            = optionLabels[ i ] || newOptions[ i ];
			newOption                    = new Option( optionLabel, newOptions[ i ], false, false );
			childSelect.options[ i + 1 ] = newOption;
			optionData.push( newOption );
		}

		if ( childSelect.slim ) {
			childSelect.slim.setData( optionData );
		}

		setSelectLookupVal( childSelect, origVal );

		enableLookup( childSelect, fieldArgs.isReadOnly );

		maybeUpdateChosenOptions( childSelect );

		// Trigger a change if the new value is different from the old value
		if ( getSelectedOptions( childSelect ).toString() !== origVal.toString() ) {
			triggerChange( jQuery( childSelect ), fieldArgs.fieldKey );
		}
	}

	/**
	 * Set the value in a refreshed Lookup Field.
	 *
	 * @param {HTMLElement} childSelect
	 * @param {Array|string} origVal
	 * @returns {void}
	 */
	function setSelectLookupVal( childSelect, origVal ) {
		if ( isSlimSelect( childSelect ) ) {
			if ( ! valueIsSingleItemArrayWithEmptyString( origVal ) ) {
				setSlimValue( childSelect, origVal );
			}
			return;
		}

		// Try setting the dropdown to the original value
		setSelectedOptions( childSelect, origVal );

		if ( childSelect.value === '' ) {
			// If the original value is no longer present, try setting to default value
			var defaultValue = childSelect.getAttribute( 'data-frmval' );
			if ( defaultValue !== null ) {
				childSelect.value = defaultValue;
			}
		}

		if ( childSelect.value === '' ) {
			// Set the value to empty string again to fix the issue with blank option show.
			childSelect.value = '';
		}
	}

	/**
	 * @since 6.8.3
	 *
	 * @param {Array|string} value
	 * @returns {boolean}
	 */
	function valueIsSingleItemArrayWithEmptyString( value ) {
		return Array.isArray( value ) && 1 === value.length && '' === value[0];
	}

	/**
	 * Either hide checkbox/radio Lookup field or update its options
	 *
	 * @since 2.01.01
	 * @param {Object} childFieldArgs
	 * @param {Array} childFieldArgs.parentVals
	 * @param {object} childDiv
     */
	function maybeReplaceCbRadioLookupOptions( childFieldArgs, childDiv ) {
		if ( childFieldArgs.parentVals === false  ) {
			// If any parents have blank values, don't waste time looking for values

			var inputs = childDiv.getElementsByTagName( 'input' );
			maybeHideRadioLookup( childFieldArgs, childDiv );
			clearValueForInputs( inputs );

		} else {
			replaceCbRadioLookupOptions( childFieldArgs, childDiv );
		}
	}

	/**
	 * Update the options in a checkbox/radio lookup field
	 *
	 * @since 2.01.01
	 * @param {Object} childFieldArgs
	 * @param {string} childFieldArgs.inputType
	 * @param {Array} childFieldArgs.parents
	 * @param {Array} childFieldArgs.parentVals
	 * @param {string} childFieldArgs.fieldId
	 * @param {string} childFieldArgs.repeatRow
	 * @param {string} childFieldArgs.fieldKey
	 * @param {string} childFieldArgs.formId
	 * @param {object} childDiv
     */
	function replaceCbRadioLookupOptions( childFieldArgs, childDiv ) {
		var optContainer, inputs, currentValue, defaultValue, form, data, success;

		optContainer = childDiv.getElementsByClassName( 'frm_opt_container' )[ 0 ];
		inputs = optContainer.getElementsByTagName( 'input' );
		currentValue = '';

		addLoadingIconJS( childDiv, optContainer );

		if ( childFieldArgs.inputType == 'radio' ) {
			currentValue = getValueFromRadioInputs( inputs );
		} else {
			currentValue = getValuesFromCheckboxInputs( inputs );
		}

		defaultValue = jQuery( inputs[ 0 ]).data( 'frmval' );
		disableFormPreLookup( childFieldArgs.formId );

		form = getFormById( childFieldArgs.formId );

		data = {
			action: 'frm_replace_cb_radio_lookup_options',
			parent_fields: childFieldArgs.parents,
			parent_vals: childFieldArgs.parentVals,
			field_id: childFieldArgs.fieldId,
			container_field_id: getContainerFieldId( childFieldArgs ),
			row_index: childFieldArgs.repeatRow,
			current_value: currentValue,
			default_value: defaultValue,
			nonce: frm_js.nonce
		};

		success = function( newHtml ) {
			var input;
			optContainer.innerHTML = newHtml;

			removeLoadingIconJS( childDiv, optContainer );

			if ( inputs.length == 1 && inputs[ 0 ].value === '' ) {
				maybeHideRadioLookup( childFieldArgs, childDiv );
			} else {
				maybeShowRadioLookup( childFieldArgs, childDiv );
				maybeSetDefaultCbRadioValue( childFieldArgs, inputs, defaultValue );
			}

			input = inputs[0];
			triggerChange( jQuery( input ), childFieldArgs.fieldKey );
			triggerLookupOptionsLoaded( jQuery( childDiv ) );

			enableFormAfterLookup( childFieldArgs.formId );
		};

		postToAjaxUrl( form, data, success );
	}

	/**
	 * @since 4.0.03
	 */
	function maybeReplaceLookupList( childFieldArgs, childDiv ) {
		var inputs = childDiv.getElementsByTagName( 'input' ),
			content = inputs[0].previousElementSibling;

		if ( childFieldArgs.parentVals === false  ) {
			// If any parents have blank values, don't waste time looking for values
			maybeHideRadioLookup( childFieldArgs, childDiv );
			if ( content !== undefined ) {
				content.innerHTML = '';
			}
		} else {
			getLookupValues( childFieldArgs, function( response, optionLabels ) {
				content.innerHTML = optionLabels.length ? optionLabels.join( ', ' ) : response.join( ', ' );
				inputs[0].value = response;

				maybeShowRadioLookup( childFieldArgs, childDiv );
				triggerLookupOptionsLoaded( jQuery( childDiv ) );
			});
		}
	}

	/**
	 * Get the lookup options in an array.
	 * @since 4.0.03
	 */
	function getLookupValues( childFieldArgs, callback ) {
		disableFormPreLookup( childFieldArgs.formId );

		const pendingLookupArgs = {
			formId: childFieldArgs.formId,
			fieldId: childFieldArgs.fieldId,
			parents: childFieldArgs.parents,
			parentVals: childFieldArgs.parentVals,
			unique: getAutoId()
		};

		pendingLookupFieldAjax.push({ childFieldArgs: pendingLookupArgs, callback, pending: true });
	}

	function getAutoId() {
		return autoId++;
	}

	/**
	 * Trigger the frm_lookup_options_loaded event on the field div
	 *
	 * @since 2.03.05
	 *
	 * @param {Object} $fieldDiv
	 */
	function triggerLookupOptionsLoaded( $fieldDiv ) {
		$fieldDiv.trigger( 'frmLookupOptionsLoaded' );
	}

	/**
	 * Select the defatul value in a radio/checkbox field if no value is selected
	 *
	 * @since 2.02.11
	 *
	 * @param {Object} inputs
	 * @param {Object} childFieldArgs
	 * @param {string} childFieldArgs.inputType
	 * @param {(string|Array)} defaultValue
     */
	function maybeSetDefaultCbRadioValue( childFieldArgs, inputs, defaultValue ) {
		if ( defaultValue === undefined ) {
			return;
		}

		var currentValue = false;
		if ( childFieldArgs.inputType === 'radio' ) {
			currentValue = getValueFromRadioInputs( inputs );
		} else {
			currentValue = getValuesFromCheckboxInputs( inputs );
		}

		if ( currentValue !== false || inputs.length < 1 ) {
			return;
		}

		var inputName = inputs[ 0 ].name;
		setCheckboxOrRadioDefaultValue( inputName, defaultValue );
	}

	/**
	 * Hide a Radio Lookup field if it doesn't have any options
	 *
	 * @since 2.01.01
	 * @param {Object} childFieldArgs
	 * @param {string} childFieldArgs.formId
	 * @param {object} childDiv
	 */
	function maybeHideRadioLookup( childFieldArgs, childDiv ) {
		if ( isFieldConditionallyHidden( childDiv.id, childFieldArgs.formId ) ) {
			return;
		}

		hideFieldContainer( childDiv.id );
		addToHideFields( childDiv.id, childFieldArgs.formId );
	}

	/**
	 * Show a radio Lookup field if it has options and conditional logic says it should be shown
	 *
	 * @since 2.01.01
	 * @param {Object} childFieldArgs
	 * @param {string} childFieldArgs.formId
	 * @param {string} childFieldArgs.fieldId
	 * @param {string} childFieldArgs.repeatRow
	 * @param {object} childDiv
	 * @return {void}
	 */
	function maybeShowRadioLookup( childFieldArgs, childDiv ) {
		if ( isFieldCurrentlyShown( childDiv.id, childFieldArgs.formId ) ) {
			return;
		}

		var logicArgs = getRulesForSingleField( childFieldArgs.fieldId );
		if ( logicArgs === false || logicArgs.conditions.length < 1 ) {
			removeFromHideFields( childDiv.id, childFieldArgs.formId );
			showFieldContainer( childDiv.id );
		} else {
			logicArgs.containerId = childDiv.id;
			logicArgs.repeatRow   = childFieldArgs.repeatRow;
			hideOrShowSingleField( logicArgs );
		}
	}

	/**
	 * Get new value for a text field if all Lookup Field parents have a value
	 *
	 * @since 2.01.0
	 * @param {Object} childFieldArgs
	 * @param {string} childFieldArgs.formId
	 * @param {Array} childFieldArgs.parents
	 * @param {Array} childFieldArgs.parentVals
	 * @param {string} childFieldArgs.fieldKey
	 * @param {string} childFieldArgs.fieldId
	 * @param {object} childInput
     */
	function maybeInsertValueInFieldWatchingLookup( childFieldArgs, childInput ) {
		if ( isChildInputConditionallyHidden( childInput, childFieldArgs.formId ) ) {
			// TODO: What if field is in conditionally hidden section?
			checkQueueAfterLookupCompleted( childInput.id );
			return;
		}

		if ( childFieldArgs.parentVals === false  ) {
			// If any parents have blank values, set the field value to the default value
			var newValue = childInput.getAttribute( 'data-frmval' );
			if ( newValue === null ) {
				newValue = '';
			}
			insertValueInFieldWatchingLookup( childFieldArgs, childInput, newValue );
			checkQueueAfterLookupCompleted( childInput.id );
		} else {
			// If all parents have values, check for a new value

			disableFormPreLookup( childFieldArgs.formId );

			postToAjaxUrl(
				getFormById( childFieldArgs.formId ),
				{
					action: 'frm_get_lookup_text_value',
					parent_fields: childFieldArgs.parents,
					parent_vals: childFieldArgs.parentVals,
					field_id: childFieldArgs.fieldId,
					nonce: frm_js.nonce
				},
				function( newValue ) {
					if ( ! isChildInputConditionallyHidden( childInput, childFieldArgs.formId ) && childInput.value != newValue ) {
						insertValueInFieldWatchingLookup( childFieldArgs.fieldKey, childInput, newValue );
					}

					enableFormAfterLookup( childFieldArgs.formId );
					checkQueueAfterLookupCompleted( childInput.id );
				}
			);
		}
	}

	/**
	 * Check if the current Lookup watcher field has a queue
	 *
	 * @since 2.03.05
	 *
	 * @param {string} elementId
	 * @returns {boolean}
     */
	function currentLookupHasQueue( elementId ) {
		return ( elementId in lookupQueues && lookupQueues[ elementId ].length > 0 );
	}

	/**
	 * Add the current Lookup watcher to a queue of size two
	 *
	 * @since 2.03.05
	 *
	 * @param {Object} childFieldArgs
	 * @param {Object} childInput
     */
	function addLookupToQueueOfTwo( childFieldArgs, childInput ) {
		var elementId = childInput.id;

		if ( elementId in lookupQueues ) {
			if ( lookupQueues[ elementId ].length >= 2 ) {
				lookupQueues[ elementId ] = lookupQueues[ elementId ].slice( 0, 1 );
			}
		} else {
			lookupQueues[ elementId ] = [];
		}

		lookupQueues[ elementId ].push({ childFieldArgs: childFieldArgs, childInput: childInput });
	}

	/**
	 * Check the lookupQueue after a value lookup is completed
	 *
	 * @since 2.03.05
	 *
	 * @param {string} elementId
     */
	function checkQueueAfterLookupCompleted( elementId ) {
		removeLookupFromQueue( elementId );
		doNextItemInLookupQueue( elementId );
	}

	/**
	 * Remove a Lookup from the queue
	 *
	 * @since 2.03.05
	 *
	 * @param {string} elementId
	 */
	function removeLookupFromQueue( elementId ) {
		lookupQueues[ elementId ].shift();
	}

	/**
	 * Check the current Lookup queue
	 *
	 * @since 2.03.05
	 *
	 * @param {string} elementId
	 * @return {void}
	 */
	function doNextItemInLookupQueue( elementId ) {
		if ( ! currentLookupHasQueue( elementId ) ) {
			return;
		}

		const childFieldArgs = lookupQueues[ elementId ][ 0 ].childFieldArgs;
		const childInput     = lookupQueues[ elementId ][ 0 ].childInput;

		updateParentValsForLookup( childInput.name, childFieldArgs );
		maybeInsertValueInFieldWatchingLookup( childFieldArgs, childInput );
	}

	/**
	 * Ensures Lookup field parentVals are updated for the current field.
	 * Fixes issue #5611
	 *
	 * @since 6.19
	 *
	 * @param {string} childInputName
	 * @param {object} childFieldArgs
	 *
	 * @returns {void}
	 */
	function updateParentValsForLookup( childInputName, childFieldArgs ) {
		if ( ! childInputName ) {
			return;
		}
		childFieldArgs.repeatRow  = getRepeatArgsFromFieldName( childInputName ).repeatRow;
		childFieldArgs.parentVals = getParentLookupFieldVals( childFieldArgs );
	}

	/**
	 * Convert html entities back into their character values
	 *
	 * @param {string} string
	 * @returns {string}
	 */
	function decodeEntities( string ) {
		var decoded = string.replace( /&amp;/g, '&' )
			.replace( /&lt;/g, '<' )
			.replace( /&gt;/g, '>' )
			.replace( /&quot;/g, '"' )
			.replace( /&#039;/g, '\'' );
		return decoded;
	}

	/**
	 * Insert a new text field Lookup value
	 *
	 * @since 2.01.0
	 * @param {string} fieldKey
	 * @param {object} childInput
	 * @param {string} newValue
 	 */
	function insertValueInFieldWatchingLookup( fieldKey, childInput, newValue ) {
		childInput.value = decodeEntities( newValue );
		triggerChange( jQuery( childInput ), fieldKey );
	}

	/**
	 * Add the repeat Row to the child field args
	 *
	 * @since 2.01.0
	 * @param {string} fieldName
	 * @param {Object} childFieldArgs
     */
	function addRepeatRowForInput( fieldName, childFieldArgs ) {
		var repeatArgs = getRepeatArgsFromFieldName( fieldName );

		if ( repeatArgs.repeatRow !== '' ) {
			childFieldArgs.repeatRow = repeatArgs.repeatRow;
		} else {
			childFieldArgs.repeatRow = '';
		}
	}

	/*******************************************************
	 Dynamic Field Functions
	 *******************************************************/

	// Update a Dynamic field's data or options
	function updateDynamicField( depFieldArgs, onCurrentPage ) {
		var depFieldArgsCopy = cloneObjectForDynamicFields( depFieldArgs );

		if ( depFieldArgsCopy.inputType === 'data' ) {
			updateDynamicListData( depFieldArgsCopy, onCurrentPage );
		} else if ( onCurrentPage ) {
			// Only update the options if field is on the current page
			updateDynamicFieldOptions( depFieldArgsCopy );
		}
	}

	/**
	 * Clone the depFieldArgs object for use in ajax requests
	 *
	 * @since 2.01.0
	 * @param {Object} depFieldArgs
	 * @param {string|Array} depFieldArgs.dataLogic.actualValue
	 * @param {string} depFieldArgs.fieldId
	 * @param {string} depFieldArgs.fieldKey
	 * @param {string} depFieldArgs.formId
	 * @param {string} depFieldArgs.containerId
	 * @param {string} depFieldArgs.repeatRow
	 * @param {string} depFieldArgs.inputType
	 * @return {Object} dynamicFieldArgs
	 */
	function cloneObjectForDynamicFields( depFieldArgs ) {
		var dataLogic = {
			actualValue: depFieldArgs.dataLogic.actualValue,
			fieldId: depFieldArgs.dataLogic.fieldId
		};

		var dynamicFieldArgs = {
			fieldId: depFieldArgs.fieldId,
			fieldKey: depFieldArgs.fieldKey,
			formId: depFieldArgs.formId,
			containerId: depFieldArgs.containerId,
			repeatRow: depFieldArgs.repeatRow,
			dataLogic: dataLogic,
			children: '',
			inputType: depFieldArgs.inputType
		};

		return dynamicFieldArgs;
	}

	pendingDynamicFieldAjax = [];

	/**
	 * Update a Dynamic List field
	 *
	 * @since 2.01
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.containerId
	 * @param {string|Array} depFieldArgs.dataLogic.actualValue
	 * @param {string} depFieldArgs.fieldId
 	 */
	function updateDynamicListData( depFieldArgs, onCurrentPage ) {
		var $fieldDiv;

		if ( onCurrentPage ) {
			$fieldDiv = jQuery( '#' + depFieldArgs.containerId );
			addLoadingIcon( $fieldDiv );
		}

		pendingDynamicFieldAjax.push({
			args: {
				depFieldArgs: depFieldArgs,
				onCurrentPage: onCurrentPage
			},
			data: {
				entry_id: depFieldArgs.dataLogic.actualValue,
				current_field: depFieldArgs.fieldId,
				hide_id: depFieldArgs.containerId,
				on_current_page: onCurrentPage,
				nonce: frm_js.nonce
			}
		});
	}

	/**
	 * Update a Dynamic dropdown, radio, or checkbox options
	 *
	 * @since 2.01
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.containerId
	 * @param {string} depFieldArgs.dataLogic.fieldId
	 * @param {string|Array} depFieldArgs.dataLogic.actualValue
	 * @param {string} depFieldArgs.fieldId
	 */
	function updateDynamicFieldOptions( depFieldArgs ) {
		var $fieldDiv = jQuery( '#' + depFieldArgs.containerId ),
			$fieldInputs = $fieldDiv.find( 'select[name^="item_meta"], input[name^="item_meta"]' ),
			prevValue = getFieldValueFromInputs( $fieldInputs ),
			defaultVal = $fieldInputs.data( 'frmval' ),
			editingEntry = $fieldDiv.closest( 'form' ).find( 'input[name="id"]' ).val();

		addLoadingIcon( $fieldDiv );

		postToAjaxUrl(
			getFormById( depFieldArgs.formId ),
			{
				action: 'frm_fields_ajax_data_options',
				trigger_field_id: depFieldArgs.dataLogic.fieldId,
				entry_id: depFieldArgs.dataLogic.actualValue,
				field_id: depFieldArgs.fieldId,
				default_value: defaultVal,
				container_id: depFieldArgs.containerId,
				editing_entry: editingEntry,
				prev_val: prevValue,
				nonce: frm_js.nonce
			},
			function( html ) {
				var $optContainer = $fieldDiv.find( '.frm_opt_container, .frm_data_container' );
				$optContainer.html( html );
				var $dynamicFieldInputs = $optContainer.find( 'select, input[type="checkbox"], input[type="radio"]' );

				removeLoadingIcon( $optContainer );

				if ( html === '' || $dynamicFieldInputs.length < 1 ) {
					hideDynamicField( depFieldArgs );
				} else {
					var valueChanged = dynamicFieldValueChanged( depFieldArgs, $dynamicFieldInputs, prevValue );
					showDynamicField( depFieldArgs, $fieldDiv, $dynamicFieldInputs, valueChanged );
				}
			}
		);
	}

	function dynamicFieldValueChanged( depFieldArgs, $dynamicFieldInputs, prevValue ) {
		var newValue = getFieldValueFromInputs( $dynamicFieldInputs );
		return ( prevValue !== newValue );
	}

	/**
	 * Update the value in a hidden Dynamic List field
	 *
	 * @since 2.01.01
	 * @param {Object} depFieldArgs
	 * @param {string} depFieldArgs.fieldKey
	 * @param {string} depFieldArgs.repeatRow
	 * @param {string} depFieldArgs.containerId
	 * @param {string} depFieldArgs.formId
     */
	function updateHiddenDynamicListField( depFieldArgs, newValue ) {
		// Get the Dynamic List input
		var inputId = 'field_' + depFieldArgs.fieldKey;
		if ( depFieldArgs.repeatRow !== '' ) {
			inputId += '-' + depFieldArgs.repeatRow;
		}
		var listInput = document.getElementById( inputId );

		if ( listInput === null ) {
			return;
		}

		// Set the new value
		listInput.value = newValue;

		// Remove field from hidden field list
		if ( isFieldConditionallyHidden( depFieldArgs.containerId, depFieldArgs.formId ) ) {
			removeFromHideFields( depFieldArgs.containerId, depFieldArgs.formId );
		}

		triggerChange( jQuery( listInput ) );
	}

	// Add the loading icon with jQuery
	function addLoadingIcon( $fieldDiv ) {
		var currentHTML = $fieldDiv.html();

		if ( currentHTML.includes('frm-loading-img') ) {
			// Loading image already present
		} else {
			var loadingIcon = '<span class="frm-loading-img"></span>';
			$fieldDiv.html( currentHTML + loadingIcon );

			var $optContainer = $fieldDiv.find( '.frm_opt_container, .frm_data_container' );
			$optContainer.hide();
		}
	}

	// Add the loading icon with JavaScript
	function addLoadingIconJS( fieldDiv, optContainer ) {
		var currentHTML = fieldDiv.innerHTML;

		if ( currentHTML.includes('frm-loading-img') ) {
			// Loading image already present
		} else {
			optContainer.classList.add( 'frm_hidden' );

			var loadingIcon = document.createElement( 'span' );
			loadingIcon.setAttribute( 'class', 'frm-loading-img' );
			fieldDiv.insertBefore( loadingIcon, optContainer.nextSibling );
		}
	}

	// Remove the loading icon with jQuery
	function removeLoadingIcon( $optContainer ) {
		$optContainer.parent().children( '.frm-loading-img' ).remove();
		$optContainer.show();
	}

	// Remove the loading icon with JavaScript
	function removeLoadingIconJS( fieldDiv, optContainer ) {
		var loadingIcon = fieldDiv.getElementsByClassName( 'frm-loading-img' )[ 0 ];
		if ( loadingIcon !== null && loadingIcon !== undefined ) {
			loadingIcon.parentNode.removeChild( loadingIcon );
		}

		optContainer.classList.remove( 'frm_hidden' );
	}

	// Get the field value from all the inputs
	function getFieldValueFromInputs( $inputs ) {
		var fieldValue = [],
			currentValue = '';

		$inputs.each(
			function() {
				currentValue = this.value;
				if ( this.type === 'radio' || this.type === 'checkbox' ) {
					if ( this.checked === true ) {
						fieldValue.push( currentValue );
					}
				} else if ( currentValue !== '' ) {
					fieldValue.push( currentValue );
				}
			}
		);

		if ( fieldValue.length === 0 ) {
			fieldValue = '';
		}

		return fieldValue;
	}

	// Hide and clear a Dynamic Field
	function hideDynamicField( depFieldArgs ) {
		hideFieldAndClearValue( depFieldArgs, true );
	}

	// Show Dynamic field
	function showDynamicField( depFieldArgs, $fieldDiv, $fieldInputs, valueChanged ) {
		if ( isFieldConditionallyHidden( depFieldArgs.containerId, depFieldArgs.formId ) ) {
			removeFromHideFields( depFieldArgs.containerId, depFieldArgs.formId );
			$fieldDiv.show();
		}

		if ( $fieldInputs.hasClass( 'frm_chzn' ) || $fieldInputs.hasClass( 'frm_slimselect' ) ) {
			loadAutocomplete();
		}

		if ( valueChanged === true ) {
			triggerChange( $fieldInputs );
		}
	}

	/*************************************************
	 Calculations
	 ************************************************/

	function triggerCalc() {
		if ( typeof __FRMCALC === 'undefined' ) {
			// there are no calculations on this page
			return;
		}

		var triggers = __FRMCALC.triggers;
		if ( triggers ) {
			jQuery( triggers.join() ).trigger({ type: 'change', selfTriggered: true });
		}

		triggerCalcWithoutFields();
	}

	function triggerCalcWithoutFields() {
		var calcs = __FRMCALC.calc,
			vals = [];

		for ( var fieldKey in calcs ) {
			if ( calcs[ fieldKey ].fields.length < 1 && calcs[ fieldKey ].calc && '0' !== calcs[ fieldKey ].calc ) {
				var totalField = document.getElementById( 'field_' + fieldKey );
				if ( totalField !== null && ! isChildInputConditionallyHidden( totalField, calcs[ fieldKey ].form_id ) ) {
					// if field is not hidden, do calculation
					doSingleCalculation( __FRMCALC, fieldKey, vals );
				}
			}
		}
	}

	function doCalculation( fieldId, triggerField ) {
		if ( typeof __FRMCALC === 'undefined' ) {
			// there are no calculations on this page
			return;
		}

		var allCalcs = __FRMCALC,
			calc = allCalcs.fields[ fieldId ],
			vals = [];

		if ( calc === undefined ) {
			// this field is not used in a calculation
			return;
		}

		var keys = calc.total;
		var len = keys.length;
		var pages = getStartEndPage( allCalcs.calc[ keys[ 0 ] ]);

		// loop through each calculation this field is used in
		for ( var i = 0, l = len; i < l; i++ ) {
			var totalOnPage = isTotalFieldOnPage( allCalcs.calc[ keys[ i ] ], pages );

			// Proceed with calculation if total field is not conditionally hidden
			if ( totalOnPage && isTotalFieldConditionallyHidden( allCalcs.calc[ keys[ i ] ], triggerField.attr( 'name' ) ) === false ) {
				doSingleCalculation( allCalcs, keys[ i ], vals, triggerField );
			}
		}
	}

	/**
	 * @param thisField
	 * @since 2.05.06
	 */
	function getStartEndPage( thisField ) {
		var formId = thisField.form_id,
			formContainer = document.getElementById( 'frm_form_' + formId + '_container' );

		if ( formContainer === null && thisField.in_section ) {
			var fieldContainer = document.getElementById( 'frm_field_' + thisField.in_section + '_container' );

			if ( fieldContainer === null ) {
				// bail now and run the calculation if we can't determine if it's on the page
				return [];
			}

			formContainer = closest( fieldContainer, function( el ) {
				return el.tagName === 'FORM';
			});

			formId = formContainer.elements.namedItem( 'form_id' ).value;
		}

		var hasPreviousPage = formContainer.getElementsByClassName( 'frm_next_page' );
		var hasAnotherPage = document.getElementById( 'frm_page_order_' + formId );

		var pages = [];
		if ( hasPreviousPage.length > 0 ) {
			pages.start = hasPreviousPage[ 0 ];
		}
		if ( hasAnotherPage !== null ) {
			pages.end = hasAnotherPage;
		}

		return pages;
	}

	function closest( el, fn ) {
		return el && ( fn( el ) ? el : closest( el.parentNode, fn ) );
	}

	/**
	 * If the total field is not on the current page, don't trigger the calculation
	 *
	 * @param calcDetails
	 * @param pages
	 * @since 2.05.06
	 */
	function isTotalFieldOnPage( calcDetails, pages ) {
		if ( pages.start !== undefined || pages.end !== undefined ) {
			// the form has pages
			var hiddenTotalField = jQuery( 'input[type=hidden][name*="[' + calcDetails.field_id + ']"]' );
			if ( hiddenTotalField.length ) {
				// the total field is a hidden field
				return isHiddenTotalOnPage( hiddenTotalField, pages );
			}
		}

		return true;
	}

	function isHiddenTotalOnPage( hiddenTotalField, pages ) {
		var onPage,
			hiddenParent = hiddenTotalField.closest( '.frm_form_field' );

		if ( hiddenParent.length ) {
			// the field is in a section
			return true;
		}

		var totalPos = hiddenTotalField.index();
		var isAfterStart = true;
		var isBeforeEnd = true;
		if ( pages.start !== undefined ) {
			isAfterStart = jQuery( pages.start ).index() < totalPos;
		}
		if ( pages.end !== undefined ) {
			isBeforeEnd = jQuery( pages.end ).index() > totalPos;
		}

		onPage = ( isAfterStart && isBeforeEnd );
		if ( ! onPage ) {
			onPage = hiddenTotalField.closest( '.do-calculation' ).length > 0;
		}

		return onPage;
	}

	/**
	 * Check if a total field is conditionally hidden
	 * @param {Object} calcDetails
	 * @param {string} calcDetails.field_id
	 * @param {string} calcDetails.form_id
	 * @param {string} calcDetails.inSection
	 * @param {string} calcDetails.inEmbedForm
	 * @param {string} triggerFieldName
	 * @returns {boolean}
     */
	function isTotalFieldConditionallyHidden( calcDetails, triggerFieldName ) {
		var hidden = false,
			fieldId = calcDetails.field_id,
			formId = calcDetails.form_id,
			hiddenFields = getHiddenFields( formId );

		// Check if there are any conditionally hidden fields
		if ( hiddenFields.length < 1 ) {
			return hidden;
		}

		if ( calcDetails.inSection === '0' && calcDetails.inEmbedForm === '0' ) {
			// Field is not in a section or embedded form
			hidden = isNonRepeatingFieldConditionallyHidden( fieldId, hiddenFields );
		} else {
			// Field is in a section or embedded form
			var repeatArgs = getRepeatArgsFromFieldName( triggerFieldName );

			if ( isNonRepeatingFieldConditionallyHidden( fieldId, hiddenFields ) ) {
				// Check standard field
				hidden = true;
			} else if ( isRepeatingFieldConditionallyHidden( fieldId, repeatArgs, hiddenFields ) ) {
				// Check repeating field
				hidden = true;
			} else if ( calcDetails.inSection !== '0' && calcDetails.inEmbedForm !== '0' ) {
				// Check section in embedded form
				hidden = isRepeatingFieldConditionallyHidden( calcDetails.inSection, repeatArgs, hiddenFields );
			} else if ( calcDetails.inSection !== '0' ) {
				// Check section
				hidden = isNonRepeatingFieldConditionallyHidden( calcDetails.inSection, hiddenFields );
			} else if ( calcDetails.inEmbedForm !== '0' ) {
				// Check embedded form
				hidden = isNonRepeatingFieldConditionallyHidden( calcDetails.inEmbedForm, hiddenFields );
			}
		}

		return hidden;
	}

	// Check if a non-repeating field is conditionally hidden
	function isNonRepeatingFieldConditionallyHidden( fieldId, hiddenFields ) {
		var htmlID = 'frm_field_' + fieldId + '_container';
		return ( hiddenFields.includes(htmlID) );
	}

	// Check if a repeating field is conditionally hidden
	function isRepeatingFieldConditionallyHidden( fieldId, repeatArgs, hiddenFields ) {
		var hidden = false;

		if ( repeatArgs.repeatingSection ) {
			var fieldRepeatId = 'frm_field_' + fieldId + '-' + repeatArgs.repeatingSection;
			fieldRepeatId += '-' + repeatArgs.repeatRow + '_container';
			hidden = ( hiddenFields.includes(fieldRepeatId) );
		}

		return hidden;
	}

	function maybeShowCalculationsErrorAlert( err, fieldKey, thisFullCalc ) {
		var alertMessage = '';

		if ( ( ! jQuery( 'form' ).hasClass( 'frm-admin-viewing' ) ) ) {
			return;
		}

		alertMessage += frm_js.calc_error + ' ' + fieldKey + ':\n\n';
		alertMessage += thisFullCalc + '\n\n';

		if ( err.message ) {
			alertMessage += err.message + '\n\n';
		}
		alert( alertMessage );
	}

	/**
	 * Adjust a Date for timezone offset
	 *
	 * @param {Date} date
	 * @returns {Date}
	 */
	function treatAsUTC( date ) {
		var copy = new Date( date.valueOf() );
		copy.setMinutes( copy.getMinutes() - copy.getTimezoneOffset() );
		return copy;
	}

	/**
	 * Try to change date strings and ints to Date objects before calculations
	 *
	 * @param {mixed} date
	 * @returns {mixed}
	 */
	function normalizeDate( date ) {
		switch ( typeof date ) {
			case 'number': return new Date( date * 86400000 ); // 1000 * 60 * 60 * 24 (milliseconds per day)
			case 'string': return new Date( date );
			default:       return date;
		}
	}

	/**
	 * Calculate the difference between two dates.
	 * This is triggered by the [age] and [date_calc] shortcodes.
	 *
	 * @param {mixed} a                    Field date.
	 * @param {mixed} b                    Compare date.
	 * @param {string} format              Either 'days' or 'years'.
	 * @param {number|undefined} fieldId   This is defined by the datepicker add-on.
	 * @param {number|undefined} compareId This is defined by the datepicker add-on.
	 * @returns {int}
	 */
	function calculateDateDifference( a, b, format, fieldId, compareId ) {
		a = normalizeDate( a );
		b = normalizeDate( b );

		switch ( format ) {
			case 'days': {
				if ( 'function' === typeof window.frmCalcDateDifferenceDays && fieldId && compareId ) {
					return window.frmCalcDateDifferenceDays( treatAsUTC( a ), treatAsUTC( b ), fieldId, compareId );
				}

				return Math.floor( ( treatAsUTC( b ) - treatAsUTC( a ) ) / 86400000 ); // 1000 * 60 * 60 * 24 (milliseconds per day)
			}

			case 'years': default: {
				var years = b.getFullYear() - a.getFullYear();
				if ( b.getMonth() < a.getMonth() || b.getMonth() === a.getMonth() && b.getDate() < a.getDate() ) {
					years--;
				}

				return years;
			}
		}
	}

	/**
	 * @param {object} triggerField
	 * @param {string} fieldKey
	 *
	 * @returns {object}
	 */
	function getTotalOrCalcField( triggerField, fieldKey ) {
		const form = document.contains( triggerField[0] ) ? triggerField.closest( 'form' ) : jQuery( `#frm_field_${triggerField[0].dataset.sectionid}_container` ).closest( 'form' );
		return form.find( fieldKey );
	}

	function doSingleCalculation( allCalcs, fieldKey, vals, triggerField ) {
		var currency, total, dec, updatedTotal, totalField,
			thisCalc = allCalcs.calc[ fieldKey ],
			thisFullCalc = thisCalc.calc,
			fieldInfo = {
				triggerField: triggerField,
				inSection: false,
				thisFieldCall: 'input[id^="field_' + fieldKey + '-"]'
			};

		if ( triggerField !== undefined && triggerField && triggerField.length ) {
			totalField = getTotalOrCalcField( triggerField, '#field_' + fieldKey );
		} else {
			totalField = jQuery( document.getElementById( 'field_' + fieldKey ) );
		}

		if ( totalField.get( 0 )?.dataset.minGap ) { // Avoid running calculation for Range sliders.
			return;
		}

		// exit early for sliders as there is no calculation for such types and avoid errors that might rise due to formatting the value
		if ( totalField.attr( 'type' ) === 'range' ) {
			return;
		}

		// TODO: update this to work more like conditional logic
		if ( totalField.length < 1 && triggerField !== undefined ) {
			// check if the total field is inside of a repeating/embedded form
			fieldInfo.inSection = true;
			fieldInfo.thisFieldId = objectSearch( allCalcs.fieldsWithCalc, fieldKey );
			totalField = getSiblingField( fieldInfo );
		}

		if ( totalField === null || totalField.length < 1 ) {
			return;
		}

		// loop through the fields in this calculation
		thisFullCalc = getValsForSingleCalc( thisCalc, thisFullCalc, allCalcs, vals, fieldInfo );

		total = '';
		dec = '';

		if ( 'function' === typeof window[ 'frmProGetCalcTotal' + thisCalc.calc_type ]) {
			total = window[ 'frmProGetCalcTotal' + thisCalc.calc_type ].call( thisCalc, thisFullCalc, { totalField });
		} else if ( thisCalc.calc_type === 'text' ) {
			total = thisFullCalc;
		} else {
			if ( 'date' === thisCalc.calc_type ) {
				// If the datepicker JS did not load, skip date calculations.
				return;
			}

			// Set the number of decimal places
			dec = thisCalc.calc_dec;

			// allow .toFixed for reverse compatibility
			if ( thisFullCalc.includes(').toFixed(') ) {
				var calcParts = thisFullCalc.split( ').toFixed(' );
				if ( isNumeric( calcParts[ 1 ]) ) {
					dec = calcParts[ 1 ];
					thisFullCalc = thisFullCalc.replace( ').toFixed(' + dec, '' );
				}
			}

			thisFullCalc = trimNumericCalculation( thisFullCalc );

			if ( thisFullCalc !== '' ) {
				try {
					total = parseFloat( eval( thisFullCalc ) );
				} catch ( err ) {
					maybeShowCalculationsErrorAlert( err, fieldKey, thisFullCalc );
				}
			}

			if ( total === undefined || isNaN( total ) ) {
				total = 0;
			}

			// Set decimal points
			if ( isNumeric( dec ) && total !== '' ) {
				total = total.toFixed( dec );
			}
		}

		if ( thisCalc.is_currency === true && isNumeric( total ) ) {
			currency = getCurrencyFromCalcRule( thisCalc );
			if ( currency.decimals > 0 ) {
				total = Math.round10( total, currency.decimals );
				total = maybeAddTrailingZeroToPrice( total, currency );
				dec = currency.decimals;
			}
		}

		if ( totalField.val() === total ) {
			setDisplayedTotal( totalField, total, currency );
			return;
		}

		updatedTotal = false;
		if ( ( isNumeric( dec ) || thisCalc.is_currency ) && [ 'number', 'text' ].includes(totalField.attr( 'type' )) ) {
			if ( total.toString().slice( -1 ) == '0' && navigator.userAgent.toLowerCase().includes('firefox') ) {
				// Change the input to text in Firefox. Otherwise, trailing decimals will fail.
				totalField[0].setAttribute( 'type', 'text' );
			}

			if ( totalField.parent().is( '.frm_with_box.frm_hidden' ) && 'string' === typeof total ) {
				updatedTotal = true;
				totalField.val( total.replace( ',', '.' ) );
			}
		}

		if ( ! updatedTotal ) {
			totalField.val( total );
		}

		triggerEvent( document, 'frmCalcUpdatedTotal', {
			totalField: totalField,
			total: total
		});

		if ( triggerField === null || triggerField === undefined || totalField.attr( 'name' ) != triggerField.attr( 'name' ) ) {
			totalField.trigger({ type: 'change', selfTriggered: true, frmTriggered: fieldKey });
		}

		setDisplayedTotal( totalField, total, currency );
	}

	/**
	 * Show the total for frm_total fields.
	 */
	function setDisplayedTotal( totalField, total, currency ) {
		var prepend, append,
			showTotal = totalField.parent().prev();

		if ( ! showTotal.hasClass( 'frm_total_formatted' ) ) {
			return;
		}

		prepend = showTotal.data( 'prepend' );
		append = showTotal.data( 'append' );
		if ( prepend === undefined ) {
			prepend = '';
		}
		if ( append === undefined ) {
			append = '';
		}

		if ( typeof currency === 'object' ) {
			total = formatCurrency( total, currency );
			if ( currency.symbol_left === prepend ) {
				prepend = '';
			}
			if ( currency.symbol_right === append ) {
				append = '';
			}
		}

		if ( prepend !== '' ) {
			prepend = '<span class="frm_inline_pre">' + prepend + '</span>';
		}

		if ( append !== '' ) {
			append = '<span class="frm_inline_pre">' + append + '</span>';
		}

		showTotal.html( prepend + '<span class="frm_inline_total">' + total + '</span>' + append );
	}

	function getValsForSingleCalc( thisCalc, thisFullCalc, allCalcs, vals, fieldInfo ) {
		var fCount, f, field, date, findVar;

		fCount = thisCalc.fields.length;

		for ( f = 0; f < fCount; f++ ) {
			field = {
				triggerField: fieldInfo.triggerField,
				thisFieldId: thisCalc.fields[ f ],
				inSection: fieldInfo.inSection,
				valKey: fieldInfo.inSection + '' + thisCalc.fields[ f ],
				thisField: allCalcs.fields[ thisCalc.fields[ f ] ],
				thisFieldCall: 'input' + allCalcs.fieldKeys[ thisCalc.fields[ f ] ],
				formID: thisCalc.form_id
			};

			field = getCallForField( field, allCalcs );

			if ( ! thisCalc.calc_type ) {
				// Math calc type.
				field.valKey = 'num' + field.valKey;
				vals = getCalcFieldId( field, allCalcs, vals );

				if ( vals[ field.valKey ] === undefined || isNaN( vals[ field.valKey ]) ) {
					vals[ field.valKey ] = 0;

					if ( field.thisField.type === 'date' ) {
						date = tryToGetDateValue( field );
						if ( null !== date ) {
							vals[ field.valKey ] = Math.floor( date.getTime() / 86400000 );
						} else {
							thisFullCalc = '';
						}
					}
				} else if ( 0 === vals[ field.valKey ] && field.thisField.type === 'date' && dateValueShouldBeClearedForDateCalculation( field, fieldInfo ) ) {
					thisFullCalc = '';
				}
			} else {
				// Text calc type.
				field.valKey = 'text' + field.valKey;
				vals = getTextCalcFieldId( field, vals );
				if ( vals[ field.valKey ] === undefined ) {
					vals[ field.valKey ] = '';
				}
			}

			if ( thisCalc.calc_type === 'text' ) {
				thisFullCalc = replaceShortcodesWithShowOptions( thisFullCalc, vals, field );
			}

			findVar = '[' + field.thisFieldId + ']';
			findVar = findVar.replace( /([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1' );
			thisFullCalc = thisFullCalc.replace( new RegExp( findVar, 'g' ), vals[ field.valKey ]);
		}

		return thisFullCalc;
	}

	/**
	 * Support show=label (for field with options), show=first and show=last (for name fields)
	 * in text calculations.
	 *
	 * @param {String} fullCalc
	 * @param {Array} vals
	 * @param {Object} field
	 * @returns {String}
	 */
	function replaceShortcodesWithShowOptions( fullCalc, vals, field ) {
		fullCalc = replaceShowShortcode(
			fullCalc, vals, field, 'label',
			function() {
				return getOptionLabelsFromValues( vals[ field.valKey ], field );
			}
		);

		Array.prototype.forEach.call(
			[ 'first', 'middle', 'last' ],
			function( nameFieldPart ) {
				fullCalc = replaceNameShortcode( fullCalc, vals, field, nameFieldPart );
			}
		);

		return fullCalc;
	}

	function replaceNameShortcode( fullCalc, vals, field, show ) {
		var valueCallback = function() {
			var match = false;
			document.querySelectorAll( field.thisFieldCall ).forEach(
				function( input ) {
					if ( show === input.id.substr( -show.length ) ) {
						match = input;
					}
				}
			);
			return match ? match.value : '';
		};
		return replaceShowShortcode( fullCalc, vals, field, show, valueCallback );
	}

	function replaceShowShortcode( fullCalc, vals, field, show, valueCallback ) {
		var findVar;

		findVar = '[' + field.thisFieldId + ' show=' + show + ']';
		if ( -1 === fullCalc.indexOf( findVar ) ) {
			return fullCalc;
		}

		vals[ field.valKey + show ] = valueCallback();
		findVar                     = findVar.replace( /([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1' );
		fullCalc                    = fullCalc.replace( new RegExp( findVar, 'g' ), vals[ field.valKey + show ]);

		return fullCalc;
	}

	function tryToGetDateValue( field ) {
		var $element = jQuery( field.thisField.key );
		return $element.hasClass( 'hasDatepicker' ) ? $element.datepicker( 'getDate' ) : null;
	}

	/**
	 * Check if a date value is ready for use in a calculation.
	 */
	function dateValueShouldBeClearedForDateCalculation( field, fieldInfo ) {
		if ( fieldInfo.triggerField !== null ) {
			if ( fieldInfo.triggerField.is( 'input' ) ) {
				if ( datepickerFieldShouldBeClearedForDateCalculation( fieldInfo.triggerField ) ) {
					return fieldShouldBeClearedForDateCalculation( field.thisFieldCall, field.thisField.key );
				}
				return '' === fieldInfo.triggerField.val();
			}
			return fieldInfo.triggerField.is( 'div' ) && fieldInfo.triggerField.end().is( 'input' ) && '' === fieldInfo.triggerField.end().val();
		} else if ( fieldShouldBeClearedForDateCalculation( field.thisFieldCall, field.thisField.key ) ) {
			return true;
		}
		return false;
	}

	function datepickerFieldShouldBeClearedForDateCalculation( field ) {
		var dateValue = field.hasClass( 'hasDatepicker' ) ? field.datepicker( 'getDate' ) : null;
		return null !== dateValue && -72000000 !== dateValue.getTime();
	}

	function fieldShouldBeClearedForDateCalculation( fieldCall, fieldKey ) {
		return 0 === fieldCall.indexOf( 'input' ) && 0 === fieldKey.indexOf( '[id=' ) && '' === jQuery( fieldKey ).val();
	}

	function getOptionLabelsFromValues( value, field ) {
		var fieldId, options, split, labels, length, index;

		fieldId = field.thisFieldId;
		if ( 'undefined' === typeof __FRMCALC.options || 'undefined' === typeof __FRMCALC.options[ fieldId ]) {
			return value;
		}

		options = __FRMCALC.options[ fieldId ];

		if ( 'checkbox' === field.thisField.type ) {
			split = value.split( ', ' );
			labels = [];
			length = split.length;
			for ( index = 0; index < length; ++index ) {
				if ( 'undefined' !== typeof options[ split[ index ] ]) {
					labels.push( options[ split[ index ] ]);
				}
			}
			return labels.join( ', ' );
		}

		return 'undefined' !== typeof options[ value ] ? options[ value ] : '';
	}

	/**
	 * Trim non-numeric characters from the end of a numeric calculation
	 *
	 * @since 2.03.02
	 * @param {String} numericCalc
	 * @returns {String}
	 */
	function trimNumericCalculation( numericCalc ) {
		var lastChar = numericCalc.charAt( numericCalc.length - 1 );
		if ( lastChar === '+' || lastChar === '-' ) {
			numericCalc = numericCalc.substr( 0, numericCalc.length - 1 );
		}

		return numericCalc;
	}

	function getCallForField( field, allCalcs ) {
		if ( field.thisField.type === 'checkbox' || field.thisField.type === 'radio' || field.thisField.type === 'scale' || field.thisField.type === 'star' ) {
			field.thisFieldCall = field.thisFieldCall + ':checked,' + field.thisFieldCall + '[type=hidden]';
		} else if ( field.thisField.type === 'select' || field.thisField.type === 'time' ) {
			field.thisFieldCall = 'select' + allCalcs.fieldKeys[ field.thisFieldId ] + ' option:selected,' + field.thisFieldCall + '[type=hidden]';
		} else if ( field.thisField.type === 'textarea' ) {
			field.thisFieldCall = field.thisFieldCall + ',textarea' + allCalcs.fieldKeys[ field.thisFieldId ];
		}
		return field;
	}

	function maybeDoCalcForSingleField( fieldInput ) {
		if ( typeof __FRMCALC === 'undefined' ) {
			// there are no calculations on this page
			return;
		}

		// Exit now if field is a type that can't do calculations
		if ( ! fieldCanDoCalc( fieldInput.type ) ) {
			return;
		}

		var allCalcs = __FRMCALC,
			fieldKey = getFieldKey( fieldInput.id, fieldInput.name ),
			triggerField = maybeGetTriggerField( fieldInput ),
			vals = [];

		if ( allCalcs.calc[ fieldKey ] === undefined ) {
			// This field doesn't have any calculations
			return;
		}

		doSingleCalculation( allCalcs, fieldKey, vals, triggerField );
	}

	function fieldCanDoCalc( fieldType ) {
		return -1 !== [ 'text', 'hidden', 'number', 'textarea' ].indexOf( fieldType );
	}

	function getFieldKey( fieldHtmlId, fieldName ) {
		var fieldKey = fieldHtmlId.replace( 'field_', '' ),
			newFieldKey = '';

		if ( isRepeatingFieldByName( fieldName ) ) {
			var fieldKeyParts = fieldKey.split( '-' );
			for ( var i = 0; i < fieldKeyParts.length - 1; i++ ) {
				if ( newFieldKey === '' ) {
					newFieldKey = fieldKeyParts[ i ];
				} else {
					newFieldKey = newFieldKey + '-' + fieldKeyParts[ i ];
				}
			}
			fieldKey = newFieldKey;
		}

		return fieldKey;
	}

	function maybeGetTriggerField( fieldInput ) {
		var triggerField = null;
		if ( isRepeatingFieldByName( fieldInput.name ) ) {
			if ( fieldInput.type !== 'hidden' ) {
				triggerField = jQuery( fieldInput ).closest( '.frm_form_field' );
			} else {
				triggerField = jQuery( fieldInput );
			}
		}

		return triggerField;
	}

	function isRepeatingFieldByName( fieldName ) {
		var fieldNameParts = fieldName.split( '][' );
		return fieldNameParts.length >= 3;
	}

	function getCalcFieldId( field, allCalcs, vals ) {
		if ( vals[ field.valKey ] !== undefined && vals[ field.valKey ] !== 0 ) {
			return vals;
		}

		vals[ field.valKey ] = 0;

		var currency,
			calcField = getCalcField( field );

		if ( calcField === false ) {
			return vals;
		}

		calcField.each( function() {
			const thisVal = getOptionValue( field.thisField, this );
			const hasCustomFormat = this.classList.contains( 'frm-has-number-format' );

			if ( field.thisField.type === 'date' ) {
				var d = getDateFieldValue( allCalcs.date, thisVal );
                if ( d !== null ) {
					vals[ field.valKey ] = Math.ceil( d / ( 1000 * 60 * 60 * 24 ) );
                }
			} else if ( 'data' === field.thisField.type ) {
				vals[ field.valKey ] = 0;
				if ( '' !== thisVal ) {
					if ( 'SELECT' === this.tagName ) {
						vals[ field.valKey ] = parseFloat( this.querySelector( 'option[value="' + thisVal + '"]' ).textContent );
					} else if ( null !== this.closest( '.frm_checkbox' ) ) {
						vals[ field.valKey ] = 0;
						jQuery( this.closest( '.frm_opt_container' ).querySelectorAll( 'input:checked' ) ).each(
							function() {
								vals[ field.valKey ] += parseFloat( this.parentNode.textContent );
							}
						);
					} else {
						vals[ field.valKey ] = parseFloat( this.parentNode.textContent );
					}
				}
			} else if ( this.hasAttribute( 'data-frmprice' ) || field.thisField.type === 'total' || hasCustomFormat ) {
				// data-frmprice means product field.
				currency = getCurrency( field.formID );
				vals[ field.valKey ] += parseFloat(
					! currency
						? thisVal
						: ( hasCustomFormat && String( thisVal ).trim().startsWith( '-' ) ? '-' : '' ) + preparePrice( thisVal, currency )
					);
			} else {
				var n = thisVal;

				if ( n !== '' && n !== 0 ) {
					n = n.trim();
					// Include e to support scientific notation like 10e2.
					n = parseFloat( n.replace( /,/g, '' ).match( /-?[\d\.e]+$/ ) );
				}

				if ( n === undefined || isNaN( n ) || n === '' ) {
					n = 0;
				}
				vals[ field.valKey ] += n;
			}
		});

		return vals;
	}

	function getTextCalcFieldId( field, vals ) {
		if ( vals[ field.valKey ] !== undefined && vals[ field.valKey ] !== '' ) {
			return vals;
		}

		vals[ field.valKey ] = '';

		var calcField = getCalcField( field );
		if ( calcField === false ) {
			return vals;
		}

		var count = 0;
		var sep = '';

		calcField.each( function() {
			var thisVal = getOptionValue( field.thisField, this );
			thisVal = thisVal.trim();

			sep = getCalcSep( field, count );

			if ( thisVal !== '' ) {
				vals[ field.valKey ] += sep + thisVal;
				count++;
			}
		});

		return vals;
	}

	function getCalcSep( field, count ) {
		var sep = '';
		if ( count > 0 ) {
			if ( field.thisField.type === 'time' ) {
				if ( count == 1 ) {
					sep = ':';
				} else if ( count == 2 ) {
					sep = ' ';
				}
			} else {
				sep = ', ';
			}

			var customSep = jQuery( document ).triggerHandler( 'frmCalSeparation', [ field.thisField, count ]);
			if ( customSep !== undefined ) {
				sep = customSep;
			}
		}
		return sep;
	}

	function getCalcField( field ) {
		var calcField;
		if ( field.inSection === false ) {
			if ( 'name' === field.thisField.type ) {
				return getOffScreenFieldForName( field );
			}

			if ( 'undefined' !== typeof field.triggerField && field.triggerField && field.triggerField.length ) {
				calcField = getTotalOrCalcField( field.triggerField, field.thisFieldCall );
			} else {
				calcField = jQuery( field.thisFieldCall );
			}
			if ( ! calcField.length && -1 !== [ 'date', 'data' ].indexOf( field.thisField.type ) ) {
				calcField = jQuery( field.thisField.key );
				if ( ! calcField.length && 'data' === field.thisField.type ) {
					// radio and checkbox fields have additional hyphenated values in their ids.
					calcField = jQuery( field.thisField.key.replace( '="', '^=' ).replace( '"]', '-' ) + ']:checked' );
				}
			}
		} else {
			calcField = getSiblingField( field );
		}

		if ( calcField === null || calcField === undefined || calcField.length < 1 ) {
			calcField = false;
		}

		if ( calcField.length > 1 ) {
			calcField = filterCalcField( calcField, field.thisFieldId );
		}

		return calcField;
	}

	/**
	 * Filters calculation fields (elements) to be involved in the current calculation. This is so that elements that are in calcField just
	 * because their id starts with a similar string while they don't relate to the calculation are removed from the list.
	 *
	 * Fixes #1337.
	 *
	 * @param {Object} $calcField    A jQuery object that contains a set of `elements to be involved in the current calculation.
	 * @param {String} thisFieldCall The query selector used to select calculation elements.
	 */
	function filterCalcField( $calcField, thisFieldId ) {
		return $calcField.filter( function() {
			var target = 'OPTION' === this.nodeName ? this.closest( 'select' ) : this;
			return target && target.name && target.name.includes(thisFieldId);
		});
	}

	/**
	 * Create an input with both first and last name.
	 * This isn't actually added to the page, but is used for Name calculations.
	 *
	 * @param {Object} field
	 * @returns {Object}
	 */
	function getOffScreenFieldForName( field ) {
		var nameParts, input;

		nameParts = [];
		document.querySelectorAll( field.thisFieldCall ).forEach(
			function( input ) {
				nameParts.push( input.value );
			}
		);

		input       = document.createElement( 'input' );
		input.value = nameParts.join( ' ' );
		return jQuery( input );
	}

	/**
	* Get the value from a date field regardless of whether datepicker is defined for it
	* Limitations: If using a format with a 2-digit date, '20' will be added to the front if the year is prior to 70
	*/
	function getDateFieldValue( dateFormat, thisVal ) {
		var d = 0;

		if ( ! thisVal ) {
			// If no value was selected in date field, use 0
		} else if ( jQuery.datepicker === undefined ) {
			// If date field is not on the current page

			const splitAt = [ '/', '.' ].find( char => dateFormat.includes( char ) ) || '-';
			var year = '',
				month = '',
				day = '',
				formatPieces = dateFormat.split( splitAt ),
				datePieces = thisVal.split( splitAt );

			for ( var i = 0; i < formatPieces.length; i++ ) {
				if ( formatPieces[ i ] === 'y' ) {
					var currentYear = new Date().getFullYear() + 15;
					var currentYearPlusFifteen = currentYear.toString().substr( 2, 2 );

					if ( datePieces[ i ] > currentYearPlusFifteen ) {
						year = '19' + datePieces[ i ];
					} else {
						year = '20' + datePieces[ i ];
					}
				} else if ( formatPieces[ i ] === 'yy' ) {
					year = datePieces[ i ];
				} else if ( formatPieces[ i ] === 'm' || formatPieces[ i ] === 'mm' ) {
					month = datePieces[ i ];
					if ( month.length < 2 ) {
						month = '0' + month;
					}
				} else if ( formatPieces[ i ] === 'd' || formatPieces[ i ] === 'dd' ) {
					day = datePieces[ i ];
					if ( day.length < 2 ) {
						day = '0' + day;
					}
				}
			}

			d = Date.parse( year + '-' + month + '-' + day );
		} else {
			d = jQuery.datepicker.parseDate( dateFormat, thisVal );
		}
		return d;
	}

	function getSiblingField( field ) {
		if ( field.triggerField === undefined ) {
			return null;
		}

		var fields = null,
			container = field.triggerField.closest( '.frm_repeat_sec, .frm_repeat_inline, .frm_repeat_grid' ),
			repeatArgs = getRepeatArgsFromFieldName( field.triggerField.attr( 'name' ) ),
			siblingFieldCall = field.thisFieldCall.replace( '[id=', '[id^=' ).replace( /-"]/g, '-' + repeatArgs.repeatRow + '"]' );

		if ( container.length || repeatArgs.repeatRow !== '' ) {
			if ( container.length ) {
				fields = container.find( siblingFieldCall + ',' + siblingFieldCall.replace( 'input[', 'select[' ) );
			} else {
				fields = jQuery( siblingFieldCall );
			}

			if ( fields === null || fields === undefined || fields.length < 1 ) {
				fields = uncheckedSiblingOrOutsideSection( field, container, siblingFieldCall );
			}
		} else {
			// the trigger is not in the repeating section
			fields = getNonSiblingField( field );
		}

		return fields;
	}

	function uncheckedSiblingOrOutsideSection( field, container, siblingFieldCall ) {
		var fields = null;
		if ( siblingFieldCall.indexOf( ':checked' ) ) {
			// check if the field has nothing selected, or is not inside the section
			var inSection = container.find( siblingFieldCall.replace( ':checked', '' ) );
			if ( inSection.length < 1 ) {
				fields = getNonSiblingField( field );
			}
		} else {
			// the field holding the value is outside of the section
			fields = getNonSiblingField( field );
		}
		return fields;
	}

	function getNonSiblingField( field ) {
		var nonSiblingField = jQuery( field.thisFieldCall + ',' + field.thisFieldCall.replace( 'input[', 'select[' ) );
		if ( ! nonSiblingField.length && 'input[' === field.thisFieldCall.substr( 0, 6 ) ) {
			if ( 'undefined' !== typeof field.triggerField && field.triggerField.is( 'div' ) && field.triggerField.hasClass( 'frm_form_field' ) ) {
				nonSiblingField = field.triggerField.find( field.thisFieldCall.replace( 'input[', 'textarea[' ) );
			} else {
				nonSiblingField = jQuery( field.thisFieldCall.replace( 'input[', 'textarea[' ) );
			}
		}
		return nonSiblingField;
	}

	function getOptionValue( thisField, currentOpt ) {
		var thisVal;

		// If current option is an other option, get other value
		if ( isOtherOption( thisField, currentOpt ) ) {
			thisVal = getOtherValueAnyField( thisField, currentOpt );
		} else if ( currentOpt.type === 'checkbox' || currentOpt.type === 'radio' ) {

			if ( currentOpt.checked ) {
				// hasAttribute( 'data-frmprice' ) is a product field.
				thisVal = currentOpt.hasAttribute( 'data-frmprice' ) ? currentOpt.dataset.frmprice : currentOpt.value;
			} else {
				thisVal = currentOpt.dataset.off;
			}
		} else if ( currentOpt.hasAttribute( 'data-frmprice' ) ) {
			thisVal = currentOpt.dataset.frmprice;
		} else if ( isCurrencyFormatField( thisField ) ) {
			const fieldKey = thisField.key.replace( '[id="field_', '' ).replace( '"]', '' );
			const calcRule = window.__FRMCALC?.calc?.[ fieldKey ];

			thisVal = jQuery( currentOpt ).val();

			if ( ! calcRule ) {
				return thisVal;
			}

			const resultFieldKey = thisField.total[0];
			const totalField     = resultFieldKey ? window.__FRMCALC?.calc?.[ resultFieldKey ] : false;

			if ( ! totalField || totalField.calc_type !== 'text' ) {
				const isNegative = '-' === thisVal.charAt( 0 );
				const currency   = getCurrencyFromCalcRule( calcRule );

				thisVal = '' + preparePrice( thisVal, currency );

				// Calling preparePrice() will remove the negative sign, so add it back if needed
				if ( isNegative ) {
					thisVal = '-' + thisVal;
				}
			}
		} else {
			thisVal = jQuery( currentOpt ).val();
		}

		if ( thisVal === undefined ) {
			thisVal = '';
		}
		return thisVal;
	}

	function isCurrencyFormatField( field ) {
		const fieldKey = field.key.replace( '[id="field_', '' ).replace( '"]', '' );
		const isCalcField = window.__FRMCALC?.calc?.[ fieldKey ]?.is_currency;
		return isCalcField ? true : false;
	}

	/* Check if current option is an "Other" option (not an Other text field) */
	function isOtherOption( thisField, currentOpt ) {
		var isOtherOpt = false;

		// If hidden, check for a value
		if ( currentOpt.type === 'hidden' ) {
			if ( getOtherValueLimited( currentOpt ) !== '' ) {
				isOtherOpt = true;
			}
		} else if ( thisField.type === 'select' ) {
			// If a visible dropdown field
			var optClass = currentOpt.className;
			if ( optClass && optClass.includes('frm_other_trigger') ) {
				isOtherOpt = true;
			}
		} else if ( thisField.type === 'checkbox' || thisField.type === 'radio' ) {
			// If visible checkbox/radio field
			if ( currentOpt.id.includes('-other_') && !currentOpt.id.includes('-otext') ) {
				isOtherOpt = true;
			}
		}

		return isOtherOpt;
	}

	/* Get the value from an "Other" text field */
	/* Does NOT work for visible select fields */
	function getOtherValueLimited( currentOpt ) {
		var otherVal = '',
			otherText = document.getElementById( currentOpt.id + '-otext' );

		if ( otherText !== null && otherText.value !== '' ) {
			otherVal = otherText.value;
		}
		return otherVal;
	}

	/* Get value from Other text field */
	function getOtherValueAnyField( thisField, currentOpt ) {
		var otherVal = 0;

		if ( thisField.type === 'select' ) {
			if ( currentOpt.type === 'hidden' ) {
				if ( isCurrentOptRepeating( currentOpt ) ) {
					// Do nothing because regular doCalculation code takes care of it
				} else {
					otherVal = getOtherValueLimited( currentOpt );
				}
			} else {
				otherVal = getOtherSelectValue( currentOpt );
			}
		} else if ( thisField.type === 'checkbox' || thisField.type === 'radio' ) {
			if ( currentOpt.type === 'hidden' ) {
				// Do nothing because regular doCalculation code takes care of it
			} else {
				otherVal = getOtherValueLimited( currentOpt );
			}
		}

		return otherVal;
	}

	/* Check if current option is in a repeating section */
	function isCurrentOptRepeating( currentOpt ) {
		var isRepeating = false,
			parts = currentOpt.name.split( '[' );

		if ( parts.length > 2 ) {
			isRepeating = true;
		}
		return isRepeating;
	}

	/* Get value from Other text field in a visible dropdown field */
	function getOtherSelectValue( currentOpt ) {
		var fields = getOtherSelects( currentOpt );
		return fields.val();
	}

	/**
	 * Fill in the other text, and select the other option from a dropdown
	 */
	function setOtherSelectValue( thisField, value ) {
		var i,
			fields = getOtherSelects( thisField );

		if ( fields.length < 1 ) {
			return;
		}

		fields.val( value );

		for ( i = 0; i < thisField.options.length; i++ ) {
			if ( thisField.options[ i ].className.includes('frm_other_trigger') ) {
				thisField.options[ i ].selected = true;
			}
		}
	}

	function getOtherSelects( currentOpt ) {
		return jQuery( currentOpt ).closest( '.frm_other_container' ).find( '.frm_other_input' );
	}

	function setOtherValueLimited( thisField, value ) {
		var otherText, baseId, parentInput,
			i = 0,
			idParts = thisField.id.split( '-' );
		idParts.pop(); //remove the last id
		baseId = idParts.join( '-' );
		otherText = document.querySelectorAll( '[id^=' + baseId + '-other][id$=otext]' );

		if ( otherText.length > 0 ) {
			for ( i = 0; i < otherText.length; i++ ) {
				if ( otherText[ i ].value === '' ) {
					otherText[ i ].value = value;
					parentInput = document.getElementById( otherText[ i ].id.replace( '-otext', '' ) );
					if ( parentInput !== null ) {
						parentInput.checked = true;
					}
				}
			}
		}
	}

	function savingDraftEntry( object ) {
		var isDraft = false,
			savingDraft = jQuery( object ).find( '.frm_saving_draft' );

		if ( savingDraft.length ) {
			isDraft = savingDraft.val();
		}
		return isDraft;
	}

	function goingToPrevPage( object ) {
		var goingBack = false,
			nextPage = jQuery( object ).find( '.frm_next_page' );

		if ( nextPage.length && nextPage.val() ) {
			var formID = jQuery( object ).find( 'input[name="form_id"]' ).val();
			var prevPage = jQuery( object ).find( 'input[name="frm_page_order_' + formID + '"]' );
			if ( prevPage.length ) {
				prevPage = parseInt( prevPage.val() );
			} else {
				prevPage = 0;
			}

			if ( ! prevPage || ( parseInt( nextPage.val() ) < prevPage ) ) {
				goingBack = true;
			}
		}

		return goingBack;
	}

	function afterFormSubmitted( event, form ) {
		// if the success message is showing, run the logic
		checkConditionalLogic( 'pageLoad' );
		doEditInPlaceCleanUp( form );
		checkFieldsOnPage();
		maybeShowMoreStepsButton();
	}

	function afterPageChanged() {
		checkFieldsOnPage();
		addTopAddRowBtnForRepeater();
		maybeDisableCheckboxesWithLimit();
		// make sure this comes last so that the total is calculated only after knowing the visible fields
		calcProductsTotal();
		maybeShowMoreStepsButton();
		// calculations in a repeater need to be done again after page is changed to fix issue #3414
		triggerChangeOnCalcTriggers();
		maybeAddIntlTelInput( document.querySelectorAll( '.frm-intl-tel-input' ) );
		initRangeInput( document.querySelectorAll( '.with_frm_style input[type=range]' ) );
		initFormatFieldValueNumbers();
	}

	function triggerChangeOnCalcTriggers() {
		if ( 'undefined' === typeof __FRMCALC || 'undefined' === typeof __FRMCALC.fieldKeys ) {
			return;
		}

		Object.values( __FRMCALC.fieldKeys ).forEach( function( key ) {
			jQuery( key + ':not(label):not([type=hidden])' ).each( function() {
				jQuery( this ).trigger({ type: 'change', selfTriggered: true });
			});
		});
	}

	/** Google Tables **/

	function generateGoogleTables( graphs, graphType ) {
		for ( var num = 0; num < graphs.length; num++ ) {
			generateSingleGoogleTable( graphs[ num ], graphType );
		}
	}

	function generateSingleGoogleTable( opts, type ) {
		google.load( 'visualization', '1.0', { packages: [ type ], callback: function() {
			compileGoogleTable( opts );
		} });
	}

	function compileGoogleTable( opts ) {
		var data = new google.visualization.DataTable(),
			showID = false;

		const ownerDocument = opts.document || document;

		if ( jQuery.inArray( 'id', opts.options.fields ) !== -1 ) {
			showID = true;
			data.addColumn( 'number', frm_js.id );
		}

		var colCount = opts.fields.length;
		var type = 'string';
		for ( var i = 0, l = colCount; i < l; i++ ) {
			var thisCol = opts.fields[ i ];
			type = getGraphType( thisCol );

			data.addColumn( type, thisCol.name );
		}

		var showEdit = false;
		if ( opts.options.edit_link ) {
			showEdit = true;
			data.addColumn( 'string', opts.options.edit_link );
		}

		var showDelete = false;
		if ( opts.options.delete_link ) {
			showDelete = true;
			data.addColumn( 'string', opts.options.delete_link );
		}

		var col = 0;
		if ( opts.entries !== null ) {
			var entryCount = opts.entries.length;
			data.addRows( entryCount );

			var row = 0;

			for ( var e = 0, len = entryCount; e < len; e++ ) {
				col = 0;
				var entry = opts.entries[ e ];
				if ( showID ) {
					data.setCell( row, col, entry.id );
					col++;
				}

				for ( var field = 0, fieldCount = colCount; field < fieldCount; field++ ) {
					var thisEntryCol = opts.fields[ field ];
					type = getGraphType( thisEntryCol );

					var fieldVal = entry.metas[ thisEntryCol.id ];
					if ( type === 'number' && ( fieldVal === null || fieldVal === '' ) ) {
						fieldVal = 0;
					} else if ( type === 'boolean' ) {
						if ( fieldVal === null || fieldVal == 'false' || fieldVal === false ) {
							fieldVal = false;
						} else {
							fieldVal = true;
						}
					}

					data.setCell( row, col, fieldVal );

					col++;
				}

				if ( showEdit ) {
					if ( entry.editLink !== undefined ) {
						data.setCell( row, col, '<a href="' + entry.editLink + '">' + opts.options.edit_link + '</a>' );
					} else {
						data.setCell( row, col, '' );
					}
					col++;
				}

				if ( showDelete ) {
					if ( entry.deleteLink !== undefined ) {
						data.setCell( row, col, '<a href="' + entry.deleteLink + '" class="frm_delete_link" data-frmconfirm="' + opts.options.confirm + '">' + opts.options.delete_link + '</a>' );
					} else {
						data.setCell( row, col, '' );
					}
				}

				row++;
			}
		} else {
			data.addRows( 1 );
			col = 0;

			for ( i = 0, l = colCount; i < l; i++ ) {
				if ( col > 0 ) {
					data.setCell( 0, col, '' );
				} else {
					data.setCell( 0, col, opts.options.no_entries );
				}
				col++;
			}
		}

		var chart = new google.visualization.Table( ownerDocument.getElementById( 'frm_google_table_' + opts.options.form_id ) );
		chart.draw( data, opts.graphOpts );
	}

	/** Google Graphs **/

	function generateGoogleGraphs( graphs ) {
		var l, i;
		l = graphs.length;
		for ( i = 0; i < l; i++ ) {
			generateSingleGoogleGraph( graphs[ i ]);

			if ( 'string' === typeof graphs[ i ].options.width && '%' === graphs[ i ].options.width.substr( -1 ) ) {
				addResponsiveGraphListener( graphs[ i ]);
			}
		}
	}

	function addResponsiveGraphListener( graphData ) {
		window.addEventListener(
			'resize',
			function() {
				generateSingleGoogleGraph( graphData );
			}
		);
	}

	function generateSingleGoogleGraph( graphData ) {
		google.charts.load( 'current', { packages: [ graphData.package ] });
		google.charts.setOnLoadCallback( function() {
			compileGoogleGraph( graphData );
		});
	}

	function compileGoogleGraph( graphData ) {
		var data = new google.visualization.DataTable();
		data = google.visualization.arrayToDataTable( graphData.data );

		const ownerDocument = graphData.document || document;

		var chartDiv = ownerDocument.getElementById( 'chart_' + graphData.graph_id );
		if ( chartDiv === null ) {
			return;
		}

		var type = ( graphData.type.charAt( 0 ).toUpperCase() + graphData.type.slice( 1 ) );
		if ( type !== 'Histogram' && type !== 'Table' ) {
			type += 'Chart';
		}

		var chart = new google.visualization[ type ]( chartDiv );

		chart.draw( data, graphData.options );
		jQuery( ownerDocument ).trigger( 'frmDrawChart', [ chart, 'chart_' + graphData.graph_id, data ]);
	}

	function getGraphType( field ) {
		var type = 'string';
		if ( field.type === 'number' ) {
			type = 'number';
		} else if ( field.type === 'checkbox' || field.type === 'select' ) {
			var optCount = field.options.length;
			if ( field.type === 'select' && field.options[ 0 ] === '' ) {
				if ( field.field_options.post_field === 'post_status' ) {
					optCount = 3;
				} else {
					optCount = optCount - 1;
				}
			}
			if ( optCount == 1 ) {
				type = 'boolean';
			}
		}
		return type;
	}

	/** Repeating Fields **/

	function removeRow() {
		if ( ! confirmRowRemoval() ) {
			return;
		}

		/*jshint validthis:true */
		var rowNum = jQuery( this ).data( 'key' ),
			sectionID = jQuery( this ).data( 'parent' ),
			id = 'frm_section_' + sectionID + '-' + rowNum,
			thisRow = jQuery( this ).parents( 'div[id^="frm_section_"]' ),
			fields = thisRow.find( 'input, select, textarea, .frm_html_container' ),
			formId = jQuery( this ).closest( 'form' ).find( 'input[name="form_id"]' ).val();

		thisRow.fadeOut( 'slow', function() {
			const repeaterRow = thisRow[0].closest( '.frm_section_heading');
			if ( repeaterRow.querySelectorAll( '.frm_repeat_sec, .frm_repeat_inline, .frm_repeat_grid' )?.length === 1 ) {
				repeaterRow.querySelector( '.frm_hidden_container.frm_repeat_buttons.frm_hidden' ).style.display = 'inline-block';
			}
			thisRow.remove();

			fields.each( function() {
				/* update calculations when a row is removed */
				var fieldID;
				if ( this.matches( '.frm_html_container' ) ) {
					fieldID = getHtmlFieldID( this );
				} else {
					fieldID = frmFrontForm.getFieldId( this, false );
				}

				if ( this.type != 'file' ) {
					doCalculation( fieldID, jQuery( this ) );
				}

				var container = 'frm_field_' + fieldID + '-' + sectionID + '-' + rowNum + '_container';
				removeFromHideFields( container, formId );

				// If the field is an RTE, remove the editor associated with it
				//  this is done so that if/when the field is Added again it get initialized
				if ( this.classList.contains( 'wp-editor-area' ) ) {
					removeRichText( this.id );
				}
			});

			showAddButton( sectionID );
			maybeHideRemoveButtons( sectionID );

			/* eslint-disable camelcase */
			if ( typeof frmThemeOverride_frmRemoveRow === 'function' ) {
				frmThemeOverride_frmRemoveRow( id, thisRow );
			}
			/* eslint-enable camelcase */

			jQuery( document ).trigger( 'frmAfterRemoveRow' );
		});

		return false;
	}

	/**
	 * When a repeater row is removed, check for the minimum number of rows and maybe hide remove buttons.
	 *
	 * @param {string|number} sectionID
	 * @returns {void}
	 */
	function maybeHideRemoveButtons( sectionID ) {
		var sectionContainer, repeatButtons, minRows, currentRows;

		sectionContainer = document.querySelector( '#frm_field_' + sectionID + '_container' );
		if ( ! sectionContainer ) {
			return;
		}

		repeatButtons = sectionContainer.querySelector( '.frm_repeat_buttons[data-repeat-min]' );
		if ( ! repeatButtons || ! repeatButtons.dataset.repeatMin ) {
			return;
		}

		minRows = repeatButtons.dataset.repeatMin;
		currentRows = document.querySelectorAll( '.frm_repeat_' + sectionID ).length;
		if ( currentRows <= minRows ) {
			hideRemoveButtons( sectionID );
		}
	}

	/**
	 * Gets the html field ID from the container in a repeater. Ex. frm_field_151-152-0_container (151 is the field ID in this case).
	 *
	 * @param {Array} field
	 * @returns {Number}
	 */
	function getHtmlFieldID( field ) {
		var parentIDParts;

		parentIDParts = field.id.split( '_' );

		if ( parentIDParts.length < 3 ) {
			return 0;
		}

		parentIDParts = parentIDParts[2];
		parentIDParts = parentIDParts.split( '-' );

		if ( ! parentIDParts.length ) {
			return 0;
		}

		return parentIDParts[0];
	}

	function confirmRowRemoval() {
		if ( ! frm_js.repeaterRowDeleteConfirmation ) {
			return true;
		}
		return confirm( frm_js.repeaterRowDeleteConfirmation );
	}

	function hideAddButton( sectionID ) {
		getRepeaterAddButtons( sectionID ).forEach(
			function( button ) {
				button.classList.add( 'frm_hide_add_button' );
			}
		);
	}

	function showAddButton( sectionID ) {
		getRepeaterAddButtons( sectionID ).forEach(
			function( button ) {
				button.classList.remove( 'frm_hide_add_button' );
			}
		);
	}

	function getRepeaterAddButtons( sectionID ) {
		return getFieldContainerChildren( sectionID, '.frm_add_form_row' );
	}

	function hideRemoveButtons( sectionID ) {
		getRepeaterRemoveButtons( sectionID ).forEach(
			function( button ) {
				button.classList.add( 'frm_hide_remove_button' );
			}
		);
	}

	function showRemoveButtons( sectionID ) {
		getRepeaterRemoveButtons( sectionID ).forEach(
			function( button ) {
				button.classList.remove( 'frm_hide_remove_button' );
			}
		);
	}

	function getRepeaterRemoveButtons( sectionID ) {
		return getFieldContainerChildren( sectionID, '.frm_remove_form_row' );
	}

	/**
	 * @param {string|number} fieldD
	 * @returns {HTMLElement|null}
	 */
	function getFieldContainer( fieldD ) {
		return document.getElementById( 'frm_field_' + fieldD + '_container' );
	}

	function getFieldContainerChildren( fieldID, childSelector ) {
		var container = getFieldContainer( fieldID );
		if ( ! container ) {
			return [];
		}
		return container.querySelectorAll( childSelector );
	}

	function addRow() {
		/*jshint validthis:true */
		var thisBtn, id, i, numberOfSections, lastRowIndex, stateField, state, form, data, success, error, extraParams;

		// If row is currently being added, leave now
		if ( currentlyAddingRow === true ) {
			return false;
		}

		// Indicate that a row is being added (so double clicking Add button doesn't cause problems)
		currentlyAddingRow = true;

		thisBtn = jQuery( this );
		id = thisBtn.data( 'parent' );
		i = 0;
		numberOfSections = jQuery( '.frm_repeat_' + id ).length;

		if ( numberOfSections > 0 ) {
			lastRowIndex = false;
			document.querySelectorAll( '.frm_repeat_' + id ).forEach(
				function( element ) {
					var strippedId = element.id.replace( 'frm_section_' + id + '-', '' ),
						parsedId;

					if ( ! strippedId.length || 'i' === strippedId[0]) {
						return;
					}

					parsedId = parseInt( strippedId );
					// strippedId should be a unique number index.
					// Since there is no "i" prefix, this is not an entry ID.
					if ( parsedId && ( false === lastRowIndex || parsedId > lastRowIndex ) ) {
						lastRowIndex = parsedId;
					}
				}
			);

			if ( false === lastRowIndex ) {
				i = 1;
			} else {
				i = lastRowIndex + 1;
			}
		}

		stateField = document.querySelector( 'input[name="frm_state"]' );
		state = null !== stateField ? stateField.value : '';

		form = jQuery( this ).closest( 'form' ).get( 0 );
		data = {
			action: 'frm_add_form_row',
			field_id: id,
			i: i,
			numberOfSections: numberOfSections,
			nonce: frm_js.nonce,
			frm_state: state
		};

		/**
		 * Allow other add-ons to add additional data when a new repeater row is added.
		 *
		 * @since 6.25
		 *
		 * @param {Object} data
		 */
		triggerEvent( form, 'frmBeforeNewRepeaterRow', data );

		success = function( r ) {
			var html, item, checked, fieldID, fieldObject, repeatArgs, j, inputRanges;

			//only process row if row actually added
			if ( r.html ) {
				html = r.html;
				item = jQuery( html ).addClass( 'frm-fade-in' );
				const repeaterSection = thisBtn[0].closest( '.frm_section_heading' );
				const toggleContainer = repeaterSection.querySelector( '.frm_toggle_container.frm_grid_container' );
				if ( toggleContainer ) {
					toggleContainer.append( item[0] );
				} else {
					repeaterSection.append( item[0] );
				}
				const repeatButtons = repeaterSection.querySelector( '.frm_hidden_container.frm_repeat_buttons.frm_hidden' );
				if ( repeatButtons ) {
					repeatButtons.style.display = 'none';
				}

				inputRanges = item[0].querySelectorAll( 'input[type=range]' );
				for ( j = 0; j < inputRanges.length; j++ ) {
					handleSliderEvent.call( inputRanges[j]);
				}

				if ( r.is_repeat_limit_reached ) {
					hideAddButton( id );
				}

				if ( ! r.passes_repeat_min_check ) {
					hideRemoveButtons( id );
				} else {
					showRemoveButtons( id );
				}

				checked = [ 'other' ];
				repeatArgs = {
					repeatingSection: id.toString(),
					repeatRow: i.toString()
				};

				// hide fields with conditional logic
				jQuery( html ).find( 'input, select, textarea' ).each( function() {
					// Readonly dropdown fields won't have a name attribute
					if ( this.name === '' ) {
						return true;
					}
					if ( this.type === 'file' ) {
						fieldID = this.name.replace( 'file', '' ).split( '-' )[0];
					} else {
						fieldID = this.name.replace( 'item_meta[', '' ).split( ']' )[ 2 ].replace( '[', '' );
					}
					if ( jQuery.inArray( fieldID, checked ) == -1 ) {
						if ( this.id === false || this.id === '' ) {
							return;
						}

						fieldObject = jQuery( '#' + this.id );
						checked.push( fieldID );
						hideOrShowFieldById( fieldID, repeatArgs );
						updateWatchingFieldById( fieldID, repeatArgs, 'value changed' );
						// TODO: maybe trigger a change instead of running these three functions
						checkFieldsWithConditionalLogicDependentOnThis( fieldID, fieldObject );
						checkFieldsWatchingLookup( fieldID, fieldObject, 'value changed' );
						doCalculation( fieldID, fieldObject );
						maybeDoCalcForSingleField( fieldObject.get( 0 ) );
					}
				});

				jQuery( html ).find( '.frm_html_container' ).each(
					function() {
						// check heading logic
						var fieldID = this.id.replace( 'frm_field_', '' ).split( '-' )[ 0 ];
						checked.push( fieldID );
						hideOrShowFieldById( fieldID, repeatArgs );
					}
				);

				maybeAddIntlTelInput( item.find( '.frm-intl-tel-input' ).get() );
				if ( item.closest( '.with_frm_style' ).length > 0 ) {
					initRangeInput( item.find( 'input[type=range]' ).get() );
				}

				initFormatFieldValueNumbers();
				loadDropzones( repeatArgs.repeatRow );
				loadSliders();
				loadAutocomplete();
				loadImask();

				// Load rich textboxes.
				jQuery( html ).find( '.frm_html_container' ).each( function() {
					// check heading logic
					var fieldID = this.id.replace( 'frm_field_', '' ).split( '-' )[ 0 ];
					checked.push( fieldID );
					hideOrShowFieldById( fieldID, repeatArgs );
				});

				// Find any RTEs in the new row/repeat and initialize the editor for them
				//  there is an assumption here that tinyMCEPreInit.mceInit[0] refers to
				//  an RTE that was initially loaded on the page. It's a safe assumption as
				//  adding a row/repeat wouldn't have any RTEs if there wasn't one on the
				//  page in the first place.
				jQuery( html ).find( '.wp-editor-area' ).each( function() {
					initRichText( this.id );
				});
			}

			/* eslint-disable camelcase */
			if ( typeof frmThemeOverride_frmAddRow === 'function' ) {
				frmThemeOverride_frmAddRow( id, r );
			}
			/* eslint-enable camelcase */

			jQuery( document ).trigger( 'frmAfterAddRow' );

			handleRangeSliders();
			triggerEvent( document, 'frmAfterAddRepeaterRow', { repeater: item.get(0) });

			jQuery( '.frm_repeat_' + id ).each( function( i ) {
				this.style.zIndex = 999 - i;
			});

			currentlyAddingRow = false;
		};

		error = function() {
			currentlyAddingRow = false;
		};

		extraParams = { dataType: 'json' };

		postToAjaxUrl( form, data, success, error, extraParams );

		return false;
	}

	function triggerToggleClickOnSpace( e ) {
		if ( 32 === e.which ) {
			this.click();
		}
	}

	function removeRichText( id ) {
		tinymce.EditorManager.execCommand( 'mceRemoveEditor', true, id );
	}

	function initRichText( id ) {
		var key = Object.keys( tinyMCEPreInit.mceInit )[0],
			orgSettings = tinyMCEPreInit.mceInit[ key ],
			newValues = {
				selector: '#' + id,
				body_class: orgSettings.body_class.replace( key, id )
			},
			newSettings = Object.assign({}, orgSettings, newValues );
		tinymce.init( newSettings );
	}

	/*****************************************************
	 * In-place edit
	 ******************************************************/

	function editEntry() {
		/*jshint validthis:true */
		var $edit         = jQuery( this ),
			entryId       = $edit.data( 'entryid' ),
			prefix        = $edit.data( 'prefix' ),
			postId        = $edit.data( 'pageid' ),
			formId        = $edit.data( 'formid' ),
			cancel        = $edit.data( 'cancel' ),
			fields        = $edit.data( 'fields' ),
			excludeFields = $edit.data( 'excludefields' ),
			startPage     = $edit.data( 'startpage' ),
			$cont         = jQuery( document.getElementById( prefix + entryId ) ),
			orig          = $cont.html();

		$cont.html( '<span class="frm-loading-img" id="' + prefix + entryId + '"></span><div class="frm_orig_content" style="display:none">' + orig + '</div>' );
		jQuery.ajax({
			type: 'POST',
			url: getUrlForInPlaceEdit(),
			dataType: 'html',
			data: {
				action: 'frm_entries_edit_entry_ajax',
				post_id: postId,
				entry_id: entryId,
				id: formId,
				nonce: frm_js.nonce,
				fields: fields,
				exclude_fields: excludeFields,
				start_page: startPage
			},
			success: function( html ) {
				$cont.children( '.frm-loading-img' ).replaceWith( html );
				$edit.removeClass( 'frm_inplace_edit' ).addClass( 'frm_cancel_edit' );
				$edit.html( cancel );
				checkConditionalLogic( 'editInPlace' );

				// Make sure fields just loaded are properly bound
				if ( typeof frmFrontForm.fieldValueChanged === 'function' ) {
					jQuery( document ).on( 'change', '.frm-show-form input[name^="item_meta"], .frm-show-form select[name^="item_meta"], .frm-show-form textarea[name^="item_meta"]', frmFrontForm.fieldValueChanged );
				}
				checkFieldsOnPage( prefix + entryId );
				triggerEvent( document, 'frmInPlaceEdit' );
			}
		});
		return false;
	}

	function getUrlForInPlaceEdit() {
		var url         = frm_js.ajax_url,
			queryParams = getQueryParamsForInPlaceEdit();

		Array.prototype.forEach.call(
			Object.keys( queryParams ),
			function( queryParamKey ) {
				url += -1 === url.indexOf( '?' ) ? '?' : '&';
				url += queryParamKey + '=' + queryParams[ queryParamKey ];
			}
		);

		return url;
	}

	function getQueryParamsForInPlaceEdit() {
		var queryParams, queryString, pairs, keysToSkip, length, i, pair, key, value;

		queryParams = {};
		queryString = window.location.search.substring( 1 );
		pairs       = queryString.split( '&' );
		keysToSkip  = [
			'action',
			'start_page',
			'nonce',
			'post_id',
			'entry_id',
			'id',
			'fields',
			'doing_wp_cron'
		];

		length = pairs.length;
		for ( i = 0; i < length; i++ ) {
			pair = pairs[ i ].split( '=' );
			key  = decodeURIComponent( pair[0]);

			if ( -1 !== keysToSkip.indexOf( key ) ) {
				continue;
			}

			value = decodeURIComponent( pair[1]);

			// Check if the key already exists in the object.
			if ( queryParams.hasOwnProperty( key ) ) {
				// If it's an array, push the new value.
				if ( Array.isArray( queryParams[ key ]) ) {
					queryParams[ key ].push( value );
				} else {
					// If it's not an array, convert it to an array and push the new value.
					queryParams[ key ] = [ queryParams[ key ], value ];
				}
			} else {
				// If the key doesn't exist, create a new entry.
				queryParams[ key ] = value;
			}
		}

		return queryParams;
	}

	function cancelEdit( event ) {
		/*jshint validthis:true */

		event.preventDefault();

		const $cancelLink = jQuery( this );
		const prefix      = $cancelLink.data( 'prefix' );
		const entryId     = $cancelLink.data( 'entryid' );
		const $cont       = jQuery( document.getElementById( prefix + entryId ) );

		$cont.children( '.frm_forms' ).replaceWith( '' );
		$cont.children( '.frm_orig_content' ).fadeIn( 'slow' ).removeClass( 'frm_orig_content' );

		switchCancelToEdit( $cancelLink );
	}

	/**
	 * Switch a Cancel Link back to Edit
	 *
	 * @since 2.03.08
	 *
	 * @param {object} $link
     */
	function switchCancelToEdit( $link ) {
		var label = $link.data( 'edit' );
		$link.removeClass( 'frm_cancel_edit' ).addClass( 'frm_inplace_edit' );
		$link.html( label );
	}

	function deleteEntry() {
		/*jshint validthis:true */
		var entryId, prefix,
			$link = jQuery( this ),
			confirmText = $link.data( 'deleteconfirm' );

		if ( confirm( confirmText ) ) {
			entryId = $link.data( 'entryid' );
			prefix = $link.data( 'prefix' );

			$link.replaceWith( '<span class="frm-loading-img" id="frm_delete_' + entryId + '"></span>' );
			jQuery.ajax({
				type: 'POST',
				url: frm_js.ajax_url,
				data: { action: 'frm_entries_destroy', entry: entryId, nonce: frm_js.nonce },
				success: function( html ) {
					if ( html.replace( /^\s+|\s+$/g, '' ) === 'success' ) {
						var container = jQuery( document.getElementById( prefix + entryId ) );
						container.fadeOut( 'slow', function() {
							container.remove();
						});
						jQuery( document.getElementById( 'frm_delete_' + entryId ) ).fadeOut( 'slow' );
						jQuery( document ).trigger( 'frmEntryDeleted', [ entryId ]);
					} else {
						jQuery( document.getElementById( 'frm_delete_' + entryId ) ).replaceWith( html );
					}
				}
			});
		}
		return false;
	}

	/**
	 * Switch Cancel link back to Edit link after entry is updated in-place
	 *
	 * @since 2.03.08
	 *
	 * @param {object} form
     */
	function doEditInPlaceCleanUp( form ) {
		var entryIdField = jQuery( form ).find( 'input[name="id"]' );

		if ( entryIdField.length ) {
			var link = document.getElementById( 'frm_edit_' + entryIdField.val() );

			if ( isCancelLink( link ) ) {
				switchCancelToEdit( jQuery( link ) );
			}
		}
	}

	/**
	 * Check if a link is a cancel link
	 *
	 * @since 2.03.08
	 *
	 * @param {object} link
	 * @returns {boolean}
     */
	function isCancelLink( link ) {
		return ( link !== null && link.className.includes('frm_cancel_edit') );
	}

	/**********************************************
	 * Ajax time field unique check
	 *********************************************/

	function loadUniqueTimeFields() {
		var timeFields, i, dateField;

		if ( typeof __frmUniqueTimes === 'undefined' ) {
			return;
		}

		timeFields = __frmUniqueTimes;
		for ( i = 0; i < timeFields.length; i++ ) {
			dateField = document.getElementById( timeFields[ i ].dateID );
			jQuery( dateField ).on( 'change', maybeTriggerUniqueTime );
			if ( '' !== dateField.value ) {
				jQuery( dateField ).trigger( 'change' );
			}
		}
	}

	function maybeTriggerUniqueTime() {
		/*jshint validthis:true */
		var timeFields = __frmUniqueTimes;
		for ( var i = 0; i < timeFields.length; i++ ) {
			if ( timeFields[ i ].dateID == this.id ) {
				frmProForm.removeUsedTimes( this, timeFields[ i ].timeID );
			}
		}
	}

	/**********************************************
	 * General Helpers
	 *********************************************/

	function checkFieldsOnPage( containerId, event ) {
		if ( 'undefined' === typeof event ) {
			event = '';
		}
		checkPreviouslyHiddenFields();
		loadDateFields();
		loadCustomInputMasks();
		loadSliders();
		loadAutocomplete( containerId );
		checkDynamicFields( event );
		checkLookupFields();
		// Use a timeout to avoid conflicts with Datepicker Calculationns.
		// If this runs right away, the Datepicker Calculation function may not be available yet.
		setTimeout( triggerCalc, 0 );
		loadDropzones();
		loadImask( containerId );
		checkPasswordFields();
		triggerLookupWatchUpdates();
	}

	function triggerLookupWatchUpdates() {
		var i, fieldId, keys, value, $changedInput;

		if ( typeof __FRMLOOKUP  === 'undefined' || document.querySelector( 'form input[name="id"]' ) ) {
			return;
		}

		keys = Object.keys( __FRMLOOKUP );
		for ( i = 0; i < keys.length; i++ ) {
			fieldId = keys[ i ];
			value   = __FRMLOOKUP[ fieldId ];
			if ( value.dependents.length <= 0 ) {
				continue;
			}

			$changedInput = jQuery( '#field_' + value.fieldKey );
			if ( $changedInput.length ) {
				checkFieldsWatchingLookup( fieldId, $changedInput, 'value changed' );
			}
		}
	}

	function checkPasswordFields() {
		var passwordFields = document.querySelectorAll( '.frm_strength_meter' ),
			event = document.createEvent( 'HTMLEvents' );

		event.initEvent( 'keyup', true, true );

		for ( var i = 0; i < passwordFields.length; i++ ) {
			passwordFields[ i ].dispatchEvent( event );
		}
	}

	function checkPreviouslyHiddenFields() {
		if ( typeof __frmHideFields !== 'undefined' ) {
			frmProForm.hidePreviouslyHiddenFields();
		}
	}

	/**
	 * @param {string|undefined} containerId
	 * @returns {void}
	 */
	function loadAutocomplete( containerId ) {
		loadChosen( containerId );
		loadSlimSelect( containerId );
	}

	/**
	 * @param {string|undefined} chosenContainer
	 * @returns {void}
	 */
	function loadChosen( chosenContainer ) {
		var opts;

		if ( ! jQuery().chosen ) {
			return;
		}

		opts = {
			allow_single_deselect: true,
			no_results_text: frm_js.no_results,
			search_contains: true
		};
		if ( typeof __frmChosen !== 'undefined' ) {
			opts = '{' + __frmChosen + '}';
		}

		if ( chosenContainer !== undefined ) {
			jQuery( '#' + chosenContainer ).find( '.frm_chzn' ).chosen( opts );
		} else {
			jQuery( '.frm_chzn' ).chosen( opts );
		}
	}

	function loadSlimSelect( containerId ) {
		var container, dropdowns;

		if ( 'undefined' === typeof SlimSelect ) {
			return;
		}

		if ( 'undefined' !== typeof containerId ) {
			container = document.getElementById( containerId );
			if ( ! container ) {
				return;
			}

			dropdowns = container.querySelectorAll( 'select.frm_slimselect' );
		} else {
			dropdowns = document.querySelectorAll( 'select.frm_slimselect' );
		}

		dropdowns.forEach(
			function( autocompleteInput ) {
				var emptyOption, allowDeselect, isMultiSelect, tabindex;

				if ( 'none' === autocompleteInput.style.display || autocompleteInput.classList.contains( 'ss-main' ) || autocompleteInput.classList.contains( 'ss-content' ) ) {
					// Skip anything that is already initialized.
					return;
				}

				emptyOption   = autocompleteInput.querySelector( 'option[value=""]' );
				allowDeselect = false;
				isMultiSelect = 'multiple' === autocompleteInput.getAttribute( 'multiple' );

				if ( emptyOption && '' === emptyOption.textContent.trim() ) {
					allowDeselect = true;
					if ( isMultiSelect ) {
						emptyOption.parentElement.removeChild( emptyOption );
					} else {
						emptyOption.setAttribute( 'data-placeholder', 'true' );
					}
				} else if ( autocompleteInput.querySelector( 'option[data-placeholder="true"]' ) ) {
					allowDeselect = true;
				}

				tabindex = autocompleteInput.getAttribute( 'tabindex' );

				autocompleteInput.style.color = '';
				new SlimSelect({
					select: autocompleteInput,
					settings: {
						placeholderText: '',
						searchText: frm_js.no_results,
						searchPlaceholder: ' ', // Avoid default "Search" placeholder.
						allowDeselect: allowDeselect,
						closeOnSelect: ! isMultiSelect,
						keepOrder: true
					},
					events: {
						afterOpen: function() {
							// Make some accessibility adjustments to the .ss-content pop up.
							var ssContent, ssContentSearchInput, ssList, label;

							ssContent = document.querySelector( '.ss-content[data-id="' + autocompleteInput.dataset.id + '"]' );
							if ( ! ssContent ) {
								return;
							}

							// The listbox role should be on the .ss-list element.
							ssContent.removeAttribute( 'role' );

							ssContentSearchInput = ssContent.querySelector( '.ss-search input' );
							if ( ! ssContentSearchInput ) {
								return;
							}

							ssContentSearchInput.removeAttribute( 'aria-label' );

							ssList = ssContent.querySelector( '.ss-list' );
							if ( ssList ) {
								// Set a height so the list is scrollable.
								ssList.style.height = getHeightForSlimSelectList( ssContent ) + 'px';
								ssList.setAttribute( 'role', 'listbox' );
							}

							// The search input and list elements should have labels.
							label = document.querySelector( 'label[for="' + autocompleteInput.id + '"]' );
							if ( label ) {
								ssContentSearchInput.setAttribute( 'aria-labelledby', label.id );
								if ( ssList ) {
									ssList.setAttribute( 'aria-labelledby', label.id );
								}
							}
						}
					}
				});

				if ( tabindex ) {
					copyTabIndexAttributeToSlimSelectElement( autocompleteInput, tabindex );
				}

				makeSlimSelectAccessibilityChanges( autocompleteInput );
				maybeApplySlimSelectAutoWidth( autocompleteInput, allowDeselect );

				autocompleteInput.addEventListener(
					'change',
					function() {
						// This listener is required to toggle the Other input, and for conditional logic based on this field.
						jQuery( autocompleteInput ).trigger( 'change' );
					}
				);
			}
		);
	}

	function copyTabIndexAttributeToSlimSelectElement( autocompleteInput, tabindex ) {
		var slimselectElement = getSlimSelectMainElement( autocompleteInput );
		if ( slimselectElement ) {
			slimselectElement.setAttribute( 'tabindex', tabindex );
		}
	}

	function getSlimSelectMainElement( autocompleteInput ) {
		return document.querySelector( '.ss-main[data-id="' + autocompleteInput.getAttribute( 'data-id' ) + '"]' );
	}

	/**
	 * @param {HTMLElement} ssContent
	 * @returns {number}
	 */
	function getHeightForSlimSelectList( ssContent ) {
		var ssSearch, listHeight;

		listHeight = jQuery( ssContent ).outerHeight();
		ssSearch   = ssContent.querySelector( '.ss-search' );

		if ( ssSearch ) {
			listHeight -= jQuery( ssSearch ).outerHeight();
		}

		return listHeight;
	}

	/**
	 * @since 6.8
	 *
	 * @param {HTMLElement} autocompleteInput
	 * @returns {void}
	 */
	function makeSlimSelectAccessibilityChanges( autocompleteInput ) {
		addAriaLabelledByToSlimSelectMainElement( autocompleteInput );
		openSlimSelectOnLabelClick( autocompleteInput );
	}

	/**
	 * If the autocomplete input is set to "Automatic width", use the target dropdown width as reference.
	 *
	 * @since 6.8.4
	 *
	 * @param {HTMLElement} autocompleteInput
	 * @param {boolean} allowDeselect
	 * @returns {void}
	 */
	function maybeApplySlimSelectAutoWidth( autocompleteInput, allowDeselect ) {
		var slimselectElement, extraRequiredWidth;

		if ( ! autocompleteInput.classList.contains( 'auto_width' ) ) {
			return;
		}

		slimselectElement = getSlimSelectMainElement( autocompleteInput );
		if ( slimselectElement ) {
			// The Slim Select dropdown is a bit larger than a regular dropdown because of the "X" icon for deselecting.
			// If we are not showing the "X", we don't need as much extra space.
			extraRequiredWidth = allowDeselect ? 36 : 14;
			slimselectElement.style.minWidth = ( jQuery( autocompleteInput ).outerWidth() + extraRequiredWidth ) + 'px';
		}
	}

	/**
	 * @since 6.8
	 *
	 * @param {HTMLElement} autocompleteInput
	 * @returns {void}
	 */
	function addAriaLabelledByToSlimSelectMainElement( autocompleteInput ) {
		var label, slimselectElement;

		label = document.querySelector( 'label[for="' + autocompleteInput.id + '"]' );
		if ( label ) {
			slimselectElement = getSlimSelectMainElement( autocompleteInput );
			if ( slimselectElement ) {
				slimselectElement.setAttribute( 'aria-labelledby', label.id );
			}
		}
	}

	/**
	 * @since 6.8
	 *
	 * @param {HTMLElement} autocompleteInput
	 * @returns {void}
	 */
	function openSlimSelectOnLabelClick( autocompleteInput ) {
		var label = document.querySelector( 'label[for="' + autocompleteInput.id + '"]' );
		if ( label ) {
			label.addEventListener( 'click', labelListener );
			function labelListener() {
				setTimeout(
					function() {
						autocompleteInput.slim.open();
					},
					0
				);
			}
		}
	}

	function isPreviousSelectedStar( input ) {
		return null !== input.getAttribute( 'data-frm-star-selected' );
	}

	function loadStars() {
		if ( isPreviousSelectedStar( this ) ) {
			clearStars( this.parentElement, false );
			return;
		}
		/*jshint validthis:true */
		updateStars( this, true );
	}

	function hoverStars() {
		/*jshint validthis:true */
		var input = this.previousSibling;
		if ( input.checked || isPreviousSelectedStar( this ) ) {
			// Do not need to update stars when hovering on the current checked star.
			return;
		}
		updateStars( input, false );
	}

	/**
	 * Updates star rating.
	 *
	 * @param {HTMLElement} hovered Hovered or clicked input.
	 * @param {Boolean}     onClick Is true if this update is on click, false if is on hover.
	 */
	function updateStars( hovered, onClick ) {
		var starGroup = hovered.parentElement,
			stars = starGroup.children,
			current = parseInt( hovered.value ),
			starClass = 'star-rating',
			selectLabel = false;

		starGroup.className += ' frm-star-hovered';

		for ( var i = 0; i < stars.length; i++ ) {
			if ( stars[ i ].matches( '.' + starClass ) ) {
				if ( selectLabel ) {
					stars[ i ].className += ' star-rating-hover';
				} else {
					stars[ i ].classList.remove( 'star-rating-hover', 'star-rating-on' );
				}
			} else {
				selectLabel = ( parseInt( stars[ i ].value ) <= current );

				if ( onClick ) {
					// Remove selected attribute from all star inputs.
					stars[ i ].removeAttribute( 'data-frm-star-selected' );
				}
			}
		}

		if ( onClick ) {
			// Add selected class to clicked star input.
			hovered.setAttribute( 'data-frm-star-selected', '' );
		}
	}

	function unhoverStars() {
		/*jshint validthis:true */
		var input = this.previousSibling,
			starGroup = input.parentElement;
		starGroup.classList.remove( 'frm-star-hovered' );
		var stars = starGroup.children;
		var selected = jQuery( starGroup ).find( 'input:checked' ).attr( 'id' );
		var isSelected = '';
		var starClass = 'star-rating';

		for ( var i = stars.length - 1; i > 0; i-- ) {
			if ( stars[ i ].matches( '.' + starClass ) ) {
				stars[ i ].classList.remove( 'star-rating-hover' );
				if ( isSelected === '' && selected !== undefined && stars[ i ].getAttribute( 'for' ) == selected ) {
					isSelected = ' star-rating-on';
				}
				if ( isSelected !== '' ) {
					stars[ i ].className += isSelected;
				}
			}
		}
	}

	/**
	 * Clears star rating.
	 *
	 * @since 5.4
	 *
	 * @param {HTMLElement} starGroup    Star group element.
	 * @param {Boolean}     noClearInput Whether to clear input value or not. Default is `false`.
	 */
	function clearStars( starGroup, noClearInput ) {
		var labels, input;

		labels = starGroup.querySelectorAll( '.star-rating-on' );
		if ( labels && labels.length ) {
			labels.forEach( function( el ) {
				el.classList.remove( 'star-rating-on' );
			});
		}

		if ( ! noClearInput ) {
			input = starGroup.querySelector( 'input[type="radio"]:checked' );
			if ( input ) {
				input.checked = false;
				input.removeAttribute( 'data-frm-star-selected' );
			}
		}
	}

	/**
	 * Formats a value as currency.
	 */
	function formatValueAsCurrency( value, fieldKey ) {
		const currency = getCurrencyFromCalcRule( __FRMCALC.calc[ fieldKey ]);
		return new DOMParser().parseFromString(
			formatCurrency( normalizeTotal( value, currency ), currency ),
			'text/html'
		).documentElement.textContent;
	}

	function handleSliderEvent() {
		if ( this.type !== 'range' ) {
			return;
		}
		const sliderValueEl = this.parentNode.querySelector( '.frm_range_value' );
		const fieldKey = getFieldKey( this.id, this.name );
		if ( window.__FRMCALC?.calc[ fieldKey ]) {
			sliderValueEl.textContent = formatValueAsCurrency( this.value, fieldKey );
		} else {
			sliderValueEl.textContent = this.value;
		}
	}

	function loadSliders() {
		handleRangeSliders();
	}

	function getCurrencyFromCalcRule( calcRule ) {
		return 'undefined' !== typeof calcRule.custom_currency ? calcRule.custom_currency : getCurrency( calcRule.form_id );
	}

	function setInlineFormWidth() {
		var children, f, inlineForm,
			inlineForms = jQuery( '.frm_inline_form .frm_fields_container' );

		if ( inlineForms.length ) {
			for ( f = 0; f < inlineForms.length; f++ ) {
				inlineForm = jQuery( inlineForms[ f ]);
				children = inlineForm.children( '.frm_form_field' );

				if ( children.length <= 12 && ! fieldHasLayoutClass( children.last() ) ) {
					addAutoInlineLayout( inlineForm, children );
				}
			}
		}
	}

	function fieldHasLayoutClass( field ) {
		var i,
			classList = field.attr( 'class' ),
			layoutClasses = [ 'frm_full', 'half', 'third', 'fourth', 'fifth', 'sixth', 'seventh', 'eighth' ];

		if ( classList === undefined ) {
			return false;
		}
		for ( i = 1; i <= 12; i++ ) {
			if ( field.hasClass( 'frm' + i ) ) {
				return true;
			}

			if ( i === 12 ) {
				for ( var c = 0; c < layoutClasses.length; c++ ) {
					if ( classList.includes(layoutClasses[ c ]) ) {
						return true;
					}

					if ( c === layoutClasses.length - 1 ) {
						return false;
					}
				}
			}
		}
	}

	function addAutoInlineLayout( inlineForm, children ) {
		var fieldCount, colCount, i;

		fieldCount = children.length + 1; // include the submit button
		colCount = Math.max( 2, Math.ceil( 12 / fieldCount ) );
		for ( i = 0; i < children.length; i++ ) {
			if ( ! fieldHasLayoutClass( jQuery( children[ i ]) ) ) {
				jQuery( children[ i ]).addClass( 'frm' + colCount );
			}
		}

		inlineForm.children( '.frm_submit' ).addClass( 'frm' + colCount );
	}

	function checkConditionalLogic( event ) {
		if ( typeof __frmHideOrShowFields !== 'undefined' ) {
			frmProForm.hideOrShowFields( __frmHideOrShowFields, event );
		} else {
			showForm();
		}
	}

	function showForm() {
		document.querySelectorAll( '.frm_pro_form' ).forEach(
			form => {
				jQuery( form ).fadeIn( 'slow' );
				triggerEvent( form, 'frmProAfterFormFadeIn' );
			}
		);
	}

	function checkDynamicFields( event ) {
		if ( typeof __frmDepDynamicFields !== 'undefined' ) {
			if ( 'pageLoad' === event && typeof __frmHideOrShowFields === 'undefined' ) {
				clearHideFields();
			}

			frmProForm.checkDependentDynamicFields( __frmDepDynamicFields );
		}
	}

	function checkLookupFields() {
		if ( typeof __frmDepLookupFields !== 'undefined' ) {
			frmProForm.checkDependentLookupFields( __frmDepLookupFields );
		}
	}

	function triggerChange( input, fieldKey ) {
		if ( fieldKey === undefined ) {
			fieldKey = 'dependent';
		}

		if ( input.length > 1 ) {
			input = input.eq( 0 );
		}

		input.trigger({ type: 'change', selfTriggered: true, frmTriggered: fieldKey });
	}

	function loadCustomInputMasks() {
		if ( typeof __frmMasks === 'undefined' ) {
			return;
		}

		var maskFields = __frmMasks;
		for ( var i = 0; i < maskFields.length; i++ ) {
			jQuery( maskFields[ i ].trigger ).attr( 'data-frmmask', maskFields[ i ].mask );
		}
	}

	// Get the section ID and repeat row from a field name
	function getRepeatArgsFromFieldName( fieldName ) {
		var repeatArgs = { repeatingSection: '', repeatRow: '' };

		if ( fieldName !== undefined && isRepeatingFieldByName( fieldName ) ) {
			var inputNameParts = fieldName.split( '][' );
			repeatArgs.repeatingSection = inputNameParts[ 0 ].replace( 'item_meta[', '' );
			repeatArgs.repeatRow = inputNameParts[ 1 ];
		}

		return repeatArgs;
	}

	function fadeOut( $remove ) {
		$remove.fadeOut( 'slow', function() {
			$remove.remove();
		});
	}

	function objectSearch( array, value ) {
		for ( var prop in array ) {
			if ( array.hasOwnProperty( prop ) ) {
				if ( array[ prop ] === value ) {
					return prop;
				}
			}
		}
		return null;
	}

	function isNumeric( obj ) {
		return ! Array.isArray( obj ) && ( obj - parseFloat( obj ) + 1 ) >= 0;
	}

	function checkPasswordField() {
		var fieldId,
			fieldIdSplit,
			checks,
			split,
			suffix,
			check,
			span;

		/*jshint validthis:true */
		if ( this.className.includes('frm_strength_meter') ) {
			fieldId = this.name.substr( this.name.indexOf( '[' ) + 1 ).replace( /\]\[\d\]\[/, '-' );
			if ( fieldId[ fieldId.length - 1 ] === ']' ) {
				fieldId = fieldId.substr( 0, fieldId.length - 1 );
			}
			fieldIdSplit = fieldId.split( '-' );

			if ( fieldIdSplit.length === 2 ) {
				fieldId = fieldIdSplit[1] + '-' + fieldIdSplit[0];
			}

			checks = passwordChecks();
			split = this.id.split( '-' );
			suffix = split.length > 1 && ! isNaN( split[ split.length - 1 ]) ? '-' + split[ split.length - 1 ] : '';

			for ( check in checks ) {
				span = document.getElementById( 'frm-pass-' + check + '-' + fieldId + suffix );
				addOrRemoveVerifyPass( checks[ check ], this.value, span );
			}
		}
	}

	function passwordChecks() {
		return {
			'eight-char': /^.{8,}$/,
			number: /\d/,
			uppercase: /[A-Z]/,
			lowercase: /[a-z]/,
			'special-char': /(?=.*[^a-zA-Z0-9])/
		};
	}

	function addOrRemoveVerifyPass( regEx, password, span ) {
		if ( span !== null ) {
			var remove = regEx.test( password );
			if ( remove ) {
				maybeRemovePassReq( span );
			} else {
				maybeRemovePassVerified( span );
			}
		}
	}

	function maybeRemovePassReq( span ) {
		if ( span.classList.contains( 'frm-pass-req' ) ) {
			span.classList.remove( 'frm-pass-req' );
			span.classList.add( 'frm-pass-verified' );
		}
	}

	function maybeRemovePassVerified( span ) {
		if ( span.classList.contains( 'frm-pass-verified' ) ) {
			span.classList.remove( 'frm-pass-verified' );
			span.classList.add( 'frm-pass-req' );
		}
	}

	function checkCheckboxSelectionLimit() {
		/*jshint validthis:true */
		var limit = parseInt( this.getAttribute( 'data-frmlimit' ) ),
			checked = this.checked;

		if ( ! limit ) {
			return;
		}

		var allBoxes = jQuery( this ).parents( '.frm_opt_container' ).find( 'input[type="checkbox"]' );
		if ( limit >= allBoxes.length ) {
			return;
		}

		var checkedBoxes = allBoxes.filter( function() {
			return this.checked;
		});

		if ( checked ) {
			if ( checkedBoxes.length >= limit ) {
				allBoxes.filter( function() {
					return ! this.checked;
				}).attr( 'disabled', 'disabled' );
			}
		} else {
			const checkboxesLimitNotReached = allBoxes.filter( function() {
				return ! this.getAttribute( 'data-max-reached' );
			});

			checkboxesLimitNotReached.prop( 'disabled', false );
		}
	}

	/**
	 * Set up 'Add Row' buttons that will show up when all repeated fields have been removed.
	 * Do it this way while we await CSS4 selectors that can do sth like .frm_repeat_sec:first-of-class.
	 *
	 * @since 4.02.01
	 * @return {void}
	 */
	function addTopAddRowBtnForRepeater() {
		jQuery( '.frm_section_heading:has(div[class*="frm_repeat_"])' ).each( function() {
			const firstRepeatedSection = jQuery( this ).find( 'div[class*="frm_repeat_"]' ).first()[0];
			const addButtonWrapper     = document.createElement( 'div' );

			addButtonWrapper.classList.add( 'frm_form_field', 'frm_hidden_container', 'frm_repeat_buttons', 'frm_hidden' );

			const addButton = firstRepeatedSection.querySelector( '.frm_add_form_row' );
			if ( addButton ) {
				addButtonWrapper.append( addButton.cloneNode( true ) );
			}

			const buttonContainer = firstRepeatedSection.closest( '.frm_toggle_container.frm_grid_container' );
			if ( buttonContainer ) {
				buttonContainer.prepend( addButtonWrapper );
			} else {
				firstRepeatedSection.parentNode.insertBefore( addButtonWrapper, firstRepeatedSection );
			}
		});
	}

	function maybeDisableCheckboxesWithLimit() {
		// Disable unchecked ones after form submit error if limit is already reached.
		jQuery( 'input[type="checkbox"][data-frmlimit]:not(:checked)' ).each( function() {
			var limit = parseInt( this.getAttribute( 'data-frmlimit' ) );

			if ( ! limit ) {
				return;
			}

			var allBoxes = jQuery( this ).parents( '.frm_opt_container' ).find( 'input[type="checkbox"]' );
			if ( limit >= allBoxes.length ) {
				// Unreasonable limit
				return;
			}

			var checkedBoxes = allBoxes.filter( function() {
				return this.checked;
			});

			if ( limit > checkedBoxes.length ) {
				return;
			}

			this.setAttribute( 'disabled', 'disabled' );
		});
	}

	/**
	 * Check to make sure sure the quantity field value is within the min and max values.
	 *
	 * @param {HTMLElement} input
	 * @return {number}
	 */
	function checkQuantityFieldMinMax( input ) {
		if ( '' === input.value ) {
			// Leave the value if it is empty.
			return 0;
		}

		const val = parseFloat( input.value ? input.value.trim() : 0 );
		if ( isNaN( val ) ) {
			return 0;
		}

		let max = input.hasAttribute( 'max' ) ? parseFloat( input.getAttribute( 'max' ) ) : 0;
		let min = input.hasAttribute( 'min' ) ? parseFloat( input.getAttribute( 'min' ) ) : 0;

		max = isNaN( max ) ? 0 : max;
		min = isNaN( min ) ? 0 : ( min < 0 ? 0 : min );

		if ( val < min ) {
			input.value = min;
			return min;
		}

		if ( 0 !== max && val > max ) {
			input.value = max;
			return max;
		}

		return val;
	}

	/**
	 * @since 4.04.01
	 */
	function setHiddenProduct( input ) {
		input.setAttribute( 'data-frmhidden', '1' );
		triggerChange( jQuery( input ) );
	}

	/**
	 * @since 4.04.01
	 */
	function setHiddenProductContainer( container ) {
		if ( container.innerHTML.includes('data-frmprice') ) {
			jQuery( container ).find( 'input[data-frmprice], select:has([data-frmprice])' ).attr( 'data-frmhidden', '1' );
		}
	}

	/**
	 * @since 4.04.01
	 */
	function setShownProduct( input ) {
		var wasHidden = input.getAttribute( 'data-frmhidden' );

		if ( wasHidden !== null ) {
			input.removeAttribute( 'data-frmhidden' );
			triggerChange( jQuery( input ) );
		}
	}

	/**
	 * @since 4.04
	 */
	function calcProductsTotal( e ) {
		var formTotals = [],
			totalFields;

		if ( typeof __FRMCURR  === 'undefined' ) {
			return;
		}

		if ( undefined !== e && 'undefined' !== typeof e.target && ( 'keyup' === e.type || 'change' === e.type ) ) {
			// an event has been fired
			var el = e.target;
			if ( el.hasAttribute( 'data-frmprice' ) && el instanceof HTMLInputElement && 'text' === el.type ) {
				// user-defined product
				el.setAttribute( 'data-frmprice', el.value.trim() );
			}
		}

		totalFields = jQuery( '[data-frmtotal]' );
		if ( ! totalFields.length ) {
			return;
		}

		totalFields.each( function() {
			var currency, formId, formatted,
				total = 0,
				totalField = jQuery( this ),
				$form = totalField.closest( 'form' ),
				isRepeatingTotal = isRepeatingFieldByName( this.name );

			if ( ! $form.length ) {
				return;
			}

			formId = $form.find( 'input[name="form_id"]' ).val();
			currency = getCurrency( formId );

			if ( formTotals[ formId ] !== undefined && ! isRepeatingTotal ) {
				total = formTotals[ formId ];
			} else {

				$form.find( 'input[data-frmprice],select:has([data-frmprice])' ).each( function() {
					var quantity, $this,
						price = 0,
						isUserDef = false,
						isSingle = false;

					// total fields inside repeaters are for their corresponding rows only.
					if ( isRepeatingTotal && ! isRepeatingWithTotal( this, totalField[0]) ) {
						return;
					}

					if ( this.hasAttribute( 'data-frmhigherpg' ) || isProductFieldHidden( this ) ) {
						return;
					}

					if ( this.tagName === 'SELECT' ) {
						if ( this.selectedIndex !== -1 ) {
							price = this.options[ this.selectedIndex ].getAttribute( 'data-frmprice' );
						}
					} else {
						isUserDef = 'text' === this.type;
						isSingle = 'hidden' === this.type;
						$this = jQuery( this );
						if ( ( ! isUserDef && ! isSingle ) && ! $this.is( ':checked' ) ) {
							return;
						}
						price = this.getAttribute( 'data-frmprice' );
					}

					if ( ! price ) {
						price = 0;
					} else {
						price = preparePrice( price, currency );
						quantity = getQuantity( isUserDef, this );
						price = parseFloat( quantity ) * parseFloat( price );
					}

					if ( 'true' === this.getAttribute( 'data-frmdiscount' ) ) {
						price = price * -1;
					}

					total += price;
				});

				if ( ! isRepeatingTotal ) {
					formTotals[ formId ] = total;
				}
			}

			total = isNaN( total ) ? 0 : total;

			// Set a decimal separator for currency if no default for it
			currency.decimal_separator = currency.decimal_separator.trim(); // first remove unnecessary space(s)
			if ( ! currency.decimal_separator.length ) {
				currency.decimal_separator = '.';
			}

			total = normalizeTotal( total, currency );
			totalField.val( total );

			// because of e.g. fields that might be using this field for calculations
			triggerChange( totalField );

			total = formatCurrency( total, currency );
			formatted = totalField.prev( '.frm_total_formatted' );
			if ( formatted.length < 1 ) {
				// In case paragraphs have been added to the form.
				formatted = totalField.closest( '.frm_form_field' ).find( '.frm_total_formatted' );
			}
			if ( formatted.length ) {
				formatted.html( total );
			}
		});
	}

	/**
	 * Round total and maybe add trailing zeros so formatCurrency has a proper format to work with.
	 *
	 * @param {number} total The total amount to normalize.
	 * @param {Object} currency The currency object containing decimal information.
	 * @returns {number}
	 */
	function normalizeTotal( total, currency ) {
		const isLargeTotal = total > Number.MAX_SAFE_INTEGER;

		if ( ! isLargeTotal ) {
			const { decimals } = currency;
			total = decimals > 0 ? Math.round10( total, decimals ) : Math.ceil( total );
		}

		return maybeAddTrailingZeroToPrice( total, currency, isLargeTotal );
	}

	/**
	 * Format a numeric value according to the specified currency format settings.
	 *
	 * @since 4.05.01
	 *
	 * @param {string} total The numeric value to format.
	 * @param {Object} currency The currency formatting configuration.
	 * @returns {string} The formatted currency string.
	 */
	function formatCurrency( total, currency ) {
		total = maybeAddTrailingZeroToPrice( total, currency );
		if ( total.length && ( total[ total.length - 1 ] === '.' || total[ total.length - 1 ] === currency.decimal_separator ) ) {
			total = total.substr( 0, total.length - 1 );
		}

		total = maybeRemoveTrailingZerosFromPrice( total, currency );
		total = addThousands( total, currency );

		const leftSymbol = currency.symbol_left ? ( currency.symbol_left + currency.symbol_padding ) : '';
		const rightSymbol = currency.symbol_right ? ( currency.symbol_padding + currency.symbol_right ) : '';

		return leftSymbol + total + rightSymbol;
	}

	/**
	 * @since 5.0.15
	 */
	function maybeRemoveTrailingZerosFromPrice( total, currency ) {
		var split = total.split( currency.decimal_separator );
		if ( 2 !== split.length || split[1].length <= currency.decimals ) {
			return total;
		}
		if ( 0 === currency.decimals ) {
			return split[0];
		}
		return split[0] + currency.decimal_separator + split[1].substr( 0, currency.decimals );
	}

	/**
	 * @since 5.0.10
	 */
	function addRteRequiredMessages() {
		var keys, length, index, key, field;
		if ( 'undefined' === typeof __FRMRTEREQMESSAGES ) {
			return;
		}
		keys = Object.keys( __FRMRTEREQMESSAGES );
		length = keys.length;
		for ( index = 0; index < length; ++index ) {
			key = keys[ index ];
			field = document.getElementById( key );
			if ( field ) {
				field.setAttribute( 'data-reqmsg', __FRMRTEREQMESSAGES[ key ]);
			}
		}
	}

	/**
	 * @since 4.04.01
	 *
	 * This function is most suited for (product) fields that are
	 * on their own page i.e. not hidden (in a multi-paged form).
	 *
	 * As for fields that are conditionally hidden - themselves or
	 * their parent - and are not on their own page, the PHP side
	 * handles them well; either their HTML is not included on the
	 * page at all (e.g. fields in a conditionally hidden section)
	 * or their value is empty & price is thus 0, so no worries here.
	 */
	function isProductFieldHidden( input ) {
		return input.getAttribute( 'data-frmhidden' ) !== null;
	}

	/**
	 * @since 4.04
	 */
	function isRepeatingWithTotal( input, total ) {
		// .+ is safer than d+ because field keys might be used at times.
		var regex = /item_meta\[.+?\]\[.+?\]/;

		return isRepeatingFieldByName( input.name ) && ( total.name.match( regex )[0] === input.name.match( regex )[0]);
	}

	/**
	 * @since 4.04
	 */
	function getCurrency( formId ) {
		if ( typeof __FRMCURR  !== 'undefined' && __FRMCURR[ formId ] !== undefined ) {
			return __FRMCURR[ formId ];
		}
	}

	/**
	 * @since 4.04
	 */
	function getQuantity( isUserDef, field ) {
		var quantity, quantityFields, isRepeating, fieldID,
			$this = jQuery( field );

		fieldID = frmFrontForm.getFieldId( field, false );
		if ( ! fieldID ) {
			return 0;
		}

		isRepeating = isRepeatingFieldByName( field.name );

		if ( isRepeating ) {
			// .+ is safer than d+ because field keys might be used at times.
			var match = field.name.match( /item_meta\[.+?\]\[.+?\]/ );
			if ( null === match ) {
				// very unlikely though
				return 0;
			}
			// we may need this in getQuantityFields
			$this.nameMatch = match[0];
		}

		quantity = getQuantityField( $this, fieldID, isRepeating );

		if ( quantity ) {
			quantity = checkQuantityFieldMinMax( quantity );
		} else {
			quantityFields = getQuantityFields( $this, isRepeating );
			if ( 1 === quantityFields.length && '' === quantityFields[0].getAttribute( 'data-frmproduct' ).trim() ) {
				quantity = checkQuantityFieldMinMax( quantityFields[0]);
			} else {
				// If there is no quantity field, assume 1.
				quantity = 1;
			}
		}

		if ( 0 === quantity && isUserDef ) {
			// only user-defined fields may not have attached quantity fields
			quantity = 1;
		}

		return quantity;
	}

	function getQuantityField( elementObj, fieldID, isRepeating ) {
		var quantity,
			quantityFields = elementObj.closest( 'form' ).find( '[data-frmproduct]' );

		fieldID = fieldID.toString();

		quantityFields.each( function() {
			var ids;

			if ( isRepeating && -1 === this.name.indexOf( elementObj.nameMatch ) ) {
				return true;
			}

			ids = JSON.parse( this.getAttribute( 'data-frmproduct' ).trim() );
			if ( '' === ids ) {
				return true;
			}

			// convert to array if necessary cos of existing fields that are already using single product fields
			ids = 'string' === typeof ids ? [ ids.toString() ] : ids;
			if ( ids.includes(fieldID) ) {
				quantity = this;
				return false;
			}
		});

		return quantity;
	}

	/**
	 * @since 4.04
	 */
	function getQuantityFields( elementObj, isRepeating ) {
		var quantityFields;
		if ( isRepeating ) {
			quantityFields = elementObj.closest( 'form' ).find( '[name^="' + elementObj.nameMatch + '"]' + '[data-frmproduct]' );
		} else {
			// make sure the search is form-based (i.e. per form) cos there could be more than 1 form on the page
			// not([id*="-"]) means : not inside a repeater
			quantityFields = elementObj.closest( 'form' ).find( '[data-frmproduct]:not([id*="-"])' );
		}

		return quantityFields;
	}

	/**
	 * @since 4.04
	 */
	function preparePrice( price, currency ) {
		var matches;

		if ( ! price ) {
			return 0;
		}
		price = price + ''; // convert to string just to be sure

		const regex = getRegexForPrice( currency );

		matches = price.match( regex );
		if ( null === matches ) {
			return 0;
		}

		price = matches.length ? matches[ matches.length - 1 ] : 0;
		price = price.trim();

		// Fix issues with parsing Fr.15.00. The regex catches .15.00.
		// This checks for the leading decimal and removes it.
		if ( currency.decimal_separator === '.' && 3 === price.split( '.' ).length && price[0] === '.' ) {
			price = price.substr( 1 );
		}

		if ( price ) {
			price = maybeUseDecimal( price, currency );
			price = price.split( currency.thousand_separator ).join( '' ).replace( currency.decimal_separator, '.' );
		}

		return price;
	}

	/**
	 * @param {Object} currency The currency object.
	 * @return {RegExp}
	 */
	function getRegexForPrice( currency ) {
		let regexString = '[0-9,.';

		if ( currency.thousand_separator !== '.' && currency.thousand_separator !== ',' ) {
			regexString += currency.thousand_separator;
		}
		if ( currency.decimal_separator !== '.' && currency.decimal_separator !== ',' ) {
			regexString += currency.decimal_separator;
		}

		regexString += ']*\\.?\\,?[0-9]+';

		return new RegExp( regexString, 'g' );
	}

	/**
	 * @since 4.04
	 */
	function maybeUseDecimal( amount, currency ) {
		var usedForDecimal, amountParts;
		if ( currency.thousand_separator == '.' ) {
			amountParts = amount.split( '.' );
			usedForDecimal = ( 2 == amountParts.length && 2 == amountParts[1].length );
			if ( usedForDecimal ) {
				amount = amount.replace( '.', currency.decimal_separator );
			}
		}
		return amount;
	}

	/**
	 * Add trailing zeros to a price if necessary and replace the decimal separator.
	 *
	 * @since 4.04
	 *
	 * @param {number|string} price The price to format.
	 * @param {Object} currency The currency object containing the decimal separator.
	 * @param {boolean} [force=false] Whether to force processing even if the price is not a number.
	 * @returns {string}
	 */
	function maybeAddTrailingZeroToPrice( price, currency, force = false ) {
		if ( 'number' !== typeof price && ! force ) {
			return price;
		}

		price += ''; // first convert to string
		var pos = price.indexOf( '.' );

		if ( pos === -1 ) {
			price = price + '.';

			for ( let n = 0; n < currency.decimals; ++n ) {
				price += '0';
			}
		} else {
			const decimalsString = price.substring( pos + 1 );
			if ( decimalsString.length < currency.decimals ) {
				if ( decimalsString.length < 2 ) {
					price += '0';
				}

				for ( let n = 2; n < currency.decimals; ++n ) {
					price += '0';
				}
			}
		}

		return price.replace( '.', currency.decimal_separator );
	}

	/**
	 * Format a numeric string by adding thousand separators.
	 *
	 * @since 4.04.04
	 *
	 * @param {string|number} total The numeric value to format.
	 * @param {Object} options Formatting options.
	 * @param {string} options.decimal_separator Character used as decimal separator.
	 * @param {string} options.thousand_separator Character used as thousand separator.
	 *
	 * @returns {string}
	 */
	function addThousands( total, { decimal_separator, thousand_separator } ) {
		const split = decimal_separator === ''
			? [ total.toString() ]
			: total.split( decimal_separator );

		if ( thousand_separator ) {
			split[0] = split[0].replace( /\B(?=(\d{3})+(?!\d))/g, thousand_separator );
		}

		return split.join( decimal_separator );
	}

	/**
	 * @since 5.1
	 */
	function setAutoHeightForTextArea() {
		document.querySelectorAll( '.frm-show-form textarea' ).forEach(
			function( element ) {
				var minHeight, callback;

				if ( element.dataset.autoGrow === undefined || element.getAttribute( 'frm-autogrow' ) ) {
					return;
				}

				minHeight = getElementHeight( element );
				element.style.overflowY = 'hidden';
				element.style.transition = 'none';

				callback = function() {
					adjustHeight( element, minHeight );
				};

				callback();
				element.addEventListener( 'input', callback );

				if ( shouldCallResizeCallback() ) {
					window.addEventListener( 'resize', callback );
				}

				document.addEventListener( 'frmShowField', callback );
				element.setAttribute( 'frm-autogrow', 1 );
			}
		);
	}

	/**
	 * Returns true if window resize callback should be called.
	 *
	 * Avoid this resize callback on Android and iPhone devices because it causes shaking issues. See issue #5349 for more information.
	 *
	 * @since 6.16.1
	 *
	 * @return {Boolean}
	 */
	function shouldCallResizeCallback() {
		if ( ! ( 'userAgent' in navigator ) ) {
			return true;
		}
		const userAgent = navigator.userAgent.toLowerCase();
		return !userAgent.includes('android') && !userAgent.includes('iphone');
	}

	function getElementHeight( element ) {
		var clone, container, height;

		clone = element.cloneNode( true );
		clone.style.position = 'absolute';
		clone.style.left = '-10000px';
		clone.style.top = '-10000px';

		container = jQuery( element ).closest( '.frm_forms' ).get( 0 );
		container.append( clone );

		height = clone.clientHeight;

		container.removeChild( clone );

		return height;
	}

	/**
	 * @since 5.1
	 */
	function adjustHeight( el, minHeight ) {
		if ( minHeight >=  el.scrollHeight ) {
			return;
		}

		el.style.height = 0;
		el.style.height = Math.max( minHeight, el.scrollHeight ) + 'px';
	}

	function updateContentLength() {
		function onChange( e ) {
			var length, max, type,
				messageEl = e.target.nextElementSibling,
				countEl = messageEl.querySelector( 'span' );

			if ( ! countEl ) {
				return;
			}

			type = messageEl.getAttribute( 'data-max-type' );
			max  = parseInt( messageEl.getAttribute( 'data-max' ) );
			if ( 'word' === type ) {
				length = e.target.value.split( /\s+/ ).filter(
					function( word ) {
						return word;
					}
				).length;
			} else {
				length = e.target.value.length;
			}

			countEl.innerText = length;

			messageEl.classList.toggle( 'frm_limit_error', length > max );
		}

		document.addEventListener( 'input', function( e ) {
			var target;
			// loop parent nodes from the target to the delegation node
			for ( target = e.target; target && target != this; target = target.parentNode ) {
				if ( target.matches( 'textarea' ) && target.nextElementSibling && target.nextElementSibling.matches( '.frm_pro_max_limit_desc' ) ) {
					onChange( e );
					break;
				}
			}
		}, false );
	}

	/**
	 * Triggers custom event.
	 *
	 * @since 5.1
	 * @since 5.3.3 Added the third parameter.
	 *
	 * @param {HTMLElement} element   Element.
	 * @param {String}      eventType Event name.
	 * @param {Object}      data      Data to add to the event.
	 */
	function triggerEvent( element, eventType, data ) {
		if ( 'function' === typeof frmFrontForm.triggerCustomEvent ) {
			frmFrontForm.triggerCustomEvent( element, eventType, data );
		}
	}

	/**
	 * Handles start over button.
	 *
	 * @since 5.4
	 */
	function startOverButton() {
		/**
		 * Gets all field inputs.
		 *
		 * @param {HTMLElement} formEl Form element.
		 * @return {Array}
		 */
		function getInputs( formEl ) {
			return getInputsInFieldOnPage( formEl );
		}

		/**
		 * Resets field inputs.
		 *
		 * @param {HTMLElement} formEl Form element.
		 */
		function resetInputs( formEl ) {
			var inputs = getInputs( formEl );

			/**
			 * Resets a single repeater.
			 *
			 * @param {HTMLElement} repeatBtns Repeater buttons.
			 */
			function resetRepeater( repeatBtns ) {
				var repeater, items;

				repeater = repeatBtns.parentElement;
				items    = repeater.querySelectorAll( '.frm_repeat_sec, .frm_repeat_inline, .frm_repeat_grid' );
				if ( ! items.length ) {
					// Add the first row if all rows are deleted.
					currentlyAddingRow = false;
					repeatBtns.querySelector( '.frm_add_form_row' ).click();
				} else if ( items.length > 1 ) {
					// Remove all rows except the first one.
					items.forEach( function( item, index ) {
						if ( index ) {
							item.parentElement.removeChild( item );
						} else {
							item.querySelector( '.frm_add_form_row' ).classList.remove( 'frm_hide_add_button' );
						}
					});
				}
			}

			// Remove repeater sections.
			formEl.querySelectorAll( '.frm_section_heading > .frm_repeat_buttons' ).forEach( resetRepeater );

			// Clear field value.
			clearValueForInputs( inputs, '', true );

			// Remove "disabled" attribute added by limit selections option.
			inputs.forEach( function( input ) {
				if ( input.disabled && input.getAttribute( 'data-frmlimit' ) ) {
					input.removeAttribute( 'disabled' );
				}
			});
		}

		/**
		 * Checks if given form is a multiple pages form.
		 *
		 * @param {Number} formId Form Id.
		 * @return {Boolean}
		 */
		function isMultiPagesForm( formId ) {
			return document.getElementById( 'frm_page_order_' + formId ) || document.querySelector( '#frm_form_' + formId + '_container input[name="frm_next_page"]' );
		}

		/**
		 * Reloads form.
		 *
		 * @param {Number}      formId Form ID.
		 * @param {HTMLElement} formEl Form element.
		 */
		function reloadForm( formId, formEl ) {
			const stateField = document.querySelector( 'input[name="frm_state"]' );
			const state      = null !== stateField ? stateField.value : '';

			formEl.classList.add( 'frm_loading_form' );
			postToAjaxUrl(
				formEl,
				{
					action: 'frm_load_form',
					form: formId,
					_ajax_nonce: frm_js.nonce,
					frm_state: state
				},
				function( response ) {
					var idValueMapping;
					if ( ! response.success ) {
						console.warn( response );
						return;
					}

					idValueMapping = getDefaultValuesFromForm( formEl );

					jQuery( formEl.closest( '.frm_forms' ) ).replaceWith( response.data );

					setDefaultValues( idValueMapping );
					maybeShowMoreStepsButton();

					if ( 'undefined' !== typeof __frmAjaxDropzone ) {
						window.__frmDropzone = __frmAjaxDropzone;
					}

					checkConditionalLogic( 'pageLoad' );
					checkFieldsOnPage( 'frm_form_' + formId + '_container' );

					triggerCompletedEvent( formId );
				},
				function( response ) {
					console.warn( response );
				}
			);
		}

		/**
		 * Gets the default values of given form.
		 *
		 * @param {HTMLElement} formEl Form element.
		 * @return {Object}            Object with keys are input IDs, values are the default values.
		 */
		function getDefaultValuesFromForm( formEl ) {
			var inputs,
				values = {};

			inputs = formEl.querySelectorAll( '[data-frmval]' );
			inputs.forEach( function( input ) {
				values[ input.id ] = input.getAttribute( 'data-frmval' );
			});

			return values;
		}

		/**
		 * Sets default values of fields in given form from the id value mapping built before.
		 *
		 * @param {Object} idValueMapping Object with keys are input IDs, values are the default values.
		 */
		function setDefaultValues( idValueMapping ) {
			Object.keys( idValueMapping ).forEach( function( id ) {
				var input = document.getElementById( id );
				if ( ! input ) {
					return;
				}

				input.setAttribute( 'data-frmval', idValueMapping[ id ]);

				if ( 'checkbox' === input.type || 'radio' === input.type ) {
					// Every checkbox and radio input has data-frmval, the one has value === data-frmval is the default.
					if ( input.value === idValueMapping[ id ]) {
						input.checked = true;
					}
					return;
				}

				input.value = idValueMapping[ id ];
			});
		}

		/**
		 * Checks if form has Save draft enabled.
		 *
		 * @param {HTMLElement} formEl Form element.
		 * @return {Boolean}
		 */
		function hasSaveDraft( formEl ) {
			return ! ! formEl.querySelector( '.frm_save_draft' );
		}

		/**
		 * Deletes draft version.
		 *
		 * @param {Number}      formId Form ID.
		 * @param {HTMLElement} formEl Form element.
		 */
		function deleteDraft( formId, formEl ) {
			postToAjaxUrl(
				formEl,
				{
					action: 'frm_delete_draft_entry',
					form: formId,
					_ajax_nonce: frm_js.nonce
				}
			);
		}

		function onClickStartOver( e ) {
			e.preventDefault();

			var formEl, formId, draftIdInput;
			formEl = e.target.closest( 'form' );
			if ( ! formEl ) {
				return;
			}

			formId = formEl.querySelector( 'input[name="form_id"]' ).value;

			if ( hasSaveDraft( formEl ) ) {
				deleteDraft( formId, formEl );

				// Clear draft inputs.
				draftIdInput = formEl.querySelector( 'input[name="id"]' );
				if ( draftIdInput ) {
					draftIdInput.remove();
				}

				formEl.querySelector( 'input[name="frm_action"]' ).value = 'create';
			}

			if ( isMultiPagesForm( formId ) ) {
				reloadForm( formId, formEl );
			} else {
				resetInputs( formEl );
				triggerCompletedEvent( formId );
			}
		}

		function triggerCompletedEvent( formId ) {
			triggerEvent( document, 'frm_after_start_over', { formId: formId });
		}

		document.addEventListener( 'click', function( e ) {
			var target;

			// loop parent nodes from the target to the delegation node.
			for ( target = e.target; target && target != this; target = target.parentNode ) {
				if ( target.matches( '.frm_start_over' ) ) {
					onClickStartOver.call( target, e );
					break;
				}
			}
		}, false );
	}

	function maybeShowMoreStepsButton() {
		var i, listWrappers, listWrapper, rootlineSteps,
			wrappingElementsCount, startIndex, hiddenSteps,
			showMoreButtonLi, showMoreButton, hiddenStepsWrapper,
			oldIE;

		listWrappers = document.getElementsByClassName( 'frm_rootline' );
		copyRootlines( listWrappers );
		oldIE = isOldIEVersion( 10 );

		for ( i = 0; i < listWrappers.length; i++ ) {
			listWrapper = listWrappers[ i ];
			if ( oldIE ) {
				// Hide the rootlines in < IE 11.
				listWrapper.className += ' frm_hidden';
				continue;
			}

			rootlineSteps = listWrapper.children;

			wrappingElementsCount = countOverflowPages( rootlineSteps );

			if ( ! wrappingElementsCount ) {
				continue;
			}

			showMoreButton = listWrapper.querySelector( '.frm_rootline_show_more_btn' );
			if ( ! showMoreButton ) {
				continue;
			}

			showMoreButton.addEventListener( 'click', showMoreSteps );

			// Show the existing "..." button.
			showMoreButtonLi = showMoreButton.parentNode;
			showMoreButtonLi.className = showMoreButtonLi.className.replace( ' frm_hidden', '' );

			// Don't count the overflow step in the HTML.
			startIndex  = rootlineSteps.length - wrappingElementsCount > 1 ? rootlineSteps.length - wrappingElementsCount - 3 : 0;
			hiddenSteps = [].slice.call( rootlineSteps, Math.max( startIndex, 1 ), rootlineSteps.length - 2 );

			hiddenStepsWrapper = showMoreButtonLi.querySelector( '.frm_rootline_hidden_steps' );

			hiddenSteps.forEach( function( hiddenStep ) {
				hiddenStepsWrapper.append( hiddenStep );
			});

			moveRootlineTitles( hiddenStepsWrapper, listWrapper, showMoreButton );

			listWrapper.insertBefore( showMoreButtonLi, listWrapper.children[ listWrapper.children.length - 1 ]);

			if ( listWrapper.children[ listWrapper.children.length - 1 ].className.includes('frm_current_page') ) {
				updateRootlineStyle( hiddenStepsWrapper );
			}
		}
	}

	/**
	 * Check if the browser is IE. If it matches the max or is lower, return true.
	 *
	 * @param int max
	 * @returns bool
	 */
	function isOldIEVersion( max ) {
		var version,
			myNav = navigator.userAgent.toLowerCase();
		version = myNav.includes('msie') ? parseInt( myNav.split( 'msie' )[1]) : false;
		return version !== false && max >= version;
	}

	function countOverflowPages( rootlineSteps ) {
		var j,
			wrappingElementsCount = 0;

		for ( j = 0; j < rootlineSteps.length; j++ ) {
			if ( rootlineSteps[ j ].offsetTop !== rootlineSteps[0].offsetTop && !rootlineSteps[ j ].className.includes('frm_rootline_show_hidden_steps_btn') ) {
				wrappingElementsCount++;
			}
		}
		return wrappingElementsCount;
	}

	function moveRootlineTitles( hiddenStepsWrapper, listWrapper, showMoreButton ) {
		var currentPageTitle, currentStepTitle, rootlineGroup,
			activeHiddenStepLink = hiddenStepsWrapper.querySelector( 'input:not(.frm_page_back):not(.frm_page_skip)' );

		if ( activeHiddenStepLink ) {
			currentPageTitle = activeHiddenStepLink.parentElement.querySelector( '.frm_rootline_title' );

			maybeUpdateRootlineTitles( activeHiddenStepLink.parentElement.previousElementSibling, hiddenStepsWrapper );

			showMoreButton.parentElement.className += ' active';
			showMoreButton.className += ' active';
		} else {
			currentPageTitle = listWrapper.querySelector( '.frm_current_page' ).querySelector( '.frm_rootline_title' );
		}

		if ( ! currentPageTitle ) {
			return;
		}

		currentStepTitle = currentPageTitle.textContent;
		if ( ! currentStepTitle ) {
			return;
		}

		// Add current step on top of rootline bar.
		rootlineGroup = listWrapper.closest( '.frm_rootline_group' );

		showCurrentHiddenStepText( currentStepTitle );
	}

	function showCurrentHiddenStepText( currentStepTitle ) {
		var rootlineCurrentStep = document.createElement( 'span' );
		rootlineCurrentStep.className = 'frm_rootline_title';
		rootlineCurrentStep.textContent = currentStepTitle;
		document.querySelector( '.frm_rootline_show_hidden_steps_btn' ).append( rootlineCurrentStep );
	}

	function copyRootlines( listWrappers ) {
		var i, listWrapper, rootlinesBackup;
		rootlinesBackup = {};
		for ( i = 0; i < listWrappers.length; i++ ) {
			listWrapper = listWrappers[ i ];
			rootlinesBackup[ listWrapper.closest( 'form' ).getAttribute( 'id' ) ] = listWrapper.cloneNode( true );
		}
		listWrappersOriginal = rootlinesBackup;
	}

	function maybeUpdateRootlineTitles( previousPageLink, hiddenStepsWrapper ) {
		var i;
		if ( previousPageLink ) {
			i = 0;
			while ( previousPageLink ) {
				i++;
				previousPageLink = previousPageLink.previousElementSibling;
			}
			updateRootlineStyle( hiddenStepsWrapper, i );
		}
	}

	function updateRootlineStyle( hiddenStepsWrapper, uptoIndex ) {
		var rootlineTitles, rootlineTitle;

		if ( ! uptoIndex ) {
			uptoIndex = hiddenStepsWrapper.children.length;
		}
		rootlineTitles = [].slice.call( hiddenStepsWrapper.children, 0, uptoIndex );
		rootlineTitles.forEach( function( el ) {
			rootlineTitle = el.querySelector( '.frm_rootline_title' );
			if ( rootlineTitle ) {
				rootlineTitle.className += ' frm_prev_page_title';
			}
		});
	}

	function showMoreSteps( e ) {
		var hiddenStepsWrapper = e.target.parentElement.querySelector( 'ul' );
		if ( hiddenStepsWrapper.className.includes('frm_hidden') ) {
			hiddenStepsWrapper.className = hiddenStepsWrapper.className.replace( ' frm_hidden', '' );
		} else {
			hiddenStepsWrapper.className += ' frm_hidden';
		}
	}

	/**
	 * Validates form values when submitting form.
	 */
	function validateForm() {
		document.addEventListener( 'frm_get_ajax_form_errors', function( event ) {
			if ( ! event.frmData.formEl ) {
				return;
			}

			validateCheckboxMinSelections( event.frmData.formEl, event.frmData.errors );
		});
	}

	/**
	 * @since 6.14
	 *
	 * @param {HTMLElement|Object} formEl Form object. This might be a jQuery object.
	 * @param {Array} errors
	 * @return {void}
	 */
	function validateCheckboxMinSelections( formEl, errors ) {
		var checkboxes,
			checkedField = {},
			errorMsg = frmCheckboxI18n.errorMsg.min_selections;

		if ( 'function' === typeof formEl.get ) {
			// Get the HTMLElement from a jQuery object.
			formEl = formEl.get( 0 );
		}

		checkboxes = formEl.querySelectorAll( 'input[type="checkbox"][data-frmmin]:checked' );
		checkboxes.forEach( function( checkbox ) {
			var min, fieldEl, checkedCheckboxes, key;

			min = parseInt( checkbox.dataset.frmmin, 10 );
			if ( ! min ) {
				return;
			}
			fieldEl = checkbox.closest( '.frm_form_field' );
			key     = fieldEl.id.replace( 'frm_field_', '' ).replace( '_container', '' );

			if ( checkedField[ key ]) {
				// This field is processed before.
				return;
			}

			checkedCheckboxes = fieldEl.querySelectorAll( 'input[type="checkbox"]:checked' );
			if ( ! checkedCheckboxes.length ) {
				// If no checkbox is checked, let required field validation do its job.
				return;
			}

			if ( checkedCheckboxes.length < min ) {
				errors[ key ] = errorMsg.replace( '%1$d', min ).replace( '%2$d', checkedCheckboxes.length );
			}

			checkedField[ key ] = true;
		});
	}

	function validateFieldValue() {
		document.addEventListener( 'frm_validate_field_value', function( event ) {
			if ( 'object' !== typeof event.frmData.field || 'object' !== typeof event.frmData.errors ) {
				return;
			}

			if ( 'password' === event.frmData.field.type ) {
				validatePasswordStrength( event.frmData.field, event.frmData.errors );
			}
		});
	}

	function validatePasswordStrength( field, errors ) {
		var check, regex, checks;

		if ( 'object' !== typeof window.frm_password_checks ) {
			return;
		}

		if ( -1 === field.className.indexOf( 'frm_strong_pass' ) || 0 === field.id.indexOf( 'field_conf_' ) ) {
			return;
		}

		checks = window.frm_password_checks;

		for ( check in checks ) {
			regex = checks[ check ].regex.slice( 1, checks[ check ].regex.length - 1 );
			regex = new RegExp( regex );
			if ( ! regex.test( field.value ) ) {
				errors[ frmFrontForm.getFieldId( field ) ] = checks[ check ].message;
				return;
			}
		}
	}

	function maybeTriggerCalc( event ) {
		if ( event.persisted || ( window.performance && window.performance.getEntriesByType( 'navigation' )[0].type === 'back_forward' ) ) {
			triggerCalc();
		}
	}

	function showMoreStepsButtonEvents() {
		var timeout;

		window.addEventListener( 'resize', function() {
			var i, listWrappers, listWrapper, form;
			listWrappers = document.getElementsByClassName( 'frm_rootline' );
			for ( i = 0; i < listWrappers.length; i++ ) {
				listWrapper = listWrappers[ i ];
				form = listWrapper.closest( 'form' );
				form.querySelector( '.frm_rootline_group' ).replaceChild( listWrappersOriginal[ form.getAttribute( 'id' ) ], listWrapper );
			}

			clearTimeout( timeout );
			timeout = setTimeout( maybeShowMoreStepsButton(), 100 );
		});
	}

	function handleShowPasswordBtn() {
		documentOn( 'click', '.frm_show_password_btn', function( event ) {
			var input = event.target.closest( '.frm_show_password_wrapper' ).querySelector( 'input' ),
				button = input.nextElementSibling;

			if ( 'password' === input.type ) {
				input.type = 'text';
				button.setAttribute( 'data-show-password-label', button.title );
				button.title = button.getAttribute( 'data-hide-password-label' );
			} else {
				input.type = 'password';
				button.title = button.getAttribute( 'data-show-password-label' );
			}

			button.setAttribute( 'aria-label', button.title );
		});
	}

	function handleRangeSliders() {
		document.querySelectorAll( '.frm_range_container' ).forEach( function( rangeContainer ) {
			const rangeInput = rangeContainer.querySelector( 'input[type="hidden"]' );
			if ( ! rangeInput || rangeInput.dataset.isRangeSliderInitialized ) {
				return;
			}

			initializeRangeSlider( rangeInput, rangeContainer );
		});
	}

	/**
	 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
	 *
	 * @since 6.3.1
	 *
	 * @param {String}         event    Event name.
	 * @param {String}         selector Selector.
	 * @param {Function}       handler  Handler.
	 * @param {Boolean|Object} options  Options to be added to `addEventListener()` method. Default is `false`.
	 */
	function documentOn( event, selector, handler, options ) {
		if ( 'undefined' === typeof options ) {
			options = false;
		}

		document.addEventListener( event, function( e ) {
			var target;

			// loop parent nodes from the target to the delegation node.
			for ( target = e.target; target && target != this; target = target.parentNode ) {
				if ( target && target.matches && target.matches( selector ) ) {
					handler.call( target, e );
					break;
				}
			}
		}, options );
	}

	/**
	 * @since 6.5.1
	 */
	function handleElementorPopupConflicts() {
		var elementorPopupWrapper = document.querySelector( '.elementor-popup-modal' );

		if ( null !== elementorPopupWrapper ) {
			// Make dropzone items clickable.
			elementorPopupWrapper.querySelectorAll( '.frm_dropzone' ).forEach( function( item ) {
				item.classList.remove( 'dz-clickable' );
			});

			// Remove field chosen containers.
			elementorPopupWrapper.querySelectorAll( '.frm_form_field .chosen-container' ).forEach( function( chosenContainer ) {
				chosenContainer.remove();
			});
		}

		loadDropzones();
		loadAutocomplete();
	}

	function getAllFormClasses( input ) {
		var formContainer, formClasses;

		formContainer = input.closest( '.with_frm_style' );
		if ( ! formContainer ) {
			return [];
		}

		formClasses = [];
		Array.prototype.forEach.call(
			formContainer.className.split( ' ' ),
			function( className ) {
				var trimmedClassName = className.trim();
				if ( '' !== trimmedClassName && 'frm_forms' !== trimmedClassName ) {
					formClasses.push( trimmedClassName );
				}
			}
		);

		return formClasses;
	}

	function maybeAddIntlTelInput( phoneInputs ) {
		if ( document.body.classList.contains( 'wp-admin' ) || typeof window.intlTelInput !== 'function' ) {
			return;
		}

		phoneInputs.forEach( function( phoneInput ) {
			const parentContainer = document.createElement( 'div' );
			parentContainer.classList.add( 'frm_forms', 'with_frm_style' );

			document.body.append( parentContainer );

			const intlPhoneConfig = {
				initialCountry: 'auto',
				formatOnDisplay: false,
				dropdownContainer: parentContainer,
				geoIpLookup: function( callback ) {
					if ( 'function' === typeof window.fetch ) {
						fetch( 'https://ipapi.co/json' )
							.then( function( res ) { return res.json(); } )
							.then( function( data ) { callback( data.country_code ); } )
							.catch( function() { callback( 'us' ); } );
					}
				},
				loadUtils: () => import( getProPluginUrl() + '/js/intl-tel-input-utils.min.js' ),
			};

			// Allow someone to specify which countries to show in the dropdown
			// by adding data-only-countries in a shortcode.
			if ( phoneInput.dataset.onlyCountries ) {
				intlPhoneConfig.onlyCountries = phoneInput.dataset.onlyCountries.split( ',' );

				if ( 1 === intlPhoneConfig.onlyCountries.length ) {
					intlPhoneConfig.initialCountry = intlPhoneConfig.onlyCountries[0];
					intlPhoneConfig.allowDropdown  = false;
				}
			}

			if ( phoneInput.dataset.countryOrder ) {
				intlPhoneConfig.countryOrder = phoneInput.dataset.countryOrder.split( ',' );
			}

			const intlTelInputInstance = window.intlTelInput( phoneInput, intlPhoneConfig );

			phoneInput.addEventListener( 'countrychange', function() {
				if ( ! this.value ) {
					return;
				}
				this.dispatchEvent( new Event( 'change', { bubbles: true } ) );
			});

			// Update the input value with the full number including the country code
			phoneInput.addEventListener( 'blur', function() {
				if ( phoneInput.dataset.numberFormat ) {
					phoneInput.value = intlTelInputInstance.getNumber( determineNumberFormat( phoneInput.dataset.numberFormat ) );
				} else {
					phoneInput.value = intlTelInputInstance.getNumber();
				}
			});

			intlPhoneInputs[ phoneInput.id ] = intlTelInputInstance;
		});
	}

	/**
	 * Determines the number format based on the provided format string.
	 *
	 * @param {String} numberFormat The number format string used in the shortcode.
	 * @returns {Number} The number format number.
	 */
	function determineNumberFormat( numberFormat ) {
		numberFormat = numberFormat.toLowerCase();
		const map = {
			e164: 0,
			international: 1,
			national: 2,
			rfc3966: 3,
		};
		return map[ numberFormat ] ?? 0;
	}

	/**
	 * We have set 'overflow-x = clip;' initially to fix horizontal animation issue but it should be reverted
	 * after that to avoid another style break.
	 *
	 * See issue #4895.
	 */
	function maybeUpdateFormsOverflowX() {
		document.querySelectorAll('.frm-show-form').forEach(
			function ( slideinForm ) {
				slideinForm.style['overflow-x'] = 'visible';
			}
		);
	}

	/**
	 * Updates the background gradient of a range input field based on its current value.
	 *
	 * @since 6.12
	 */
	function initRangeInput( rangeInputs ) {
		rangeInputs.forEach( function( rangeInput ) {
			updateRangeInputBackground( rangeInput );
			rangeInput.addEventListener( 'input', function() {
				updateRangeInputBackground( rangeInput );
			});
			rangeInput.addEventListener( 'onchange', function() {
				updateRangeInputBackground( rangeInput );
			});
		});

		function updateRangeInputBackground( rangeInput ) {
			// Calculate the percentage of the current slider value
			const sliderValuePercent = ( rangeInput.value - rangeInput.min ) / ( rangeInput.max - rangeInput.min ) * 100;
			// Update the background gradient of the slider
			rangeInput.style.background = `linear-gradient(to right, var(--slider-color) 0%, var(--slider-color) ${sliderValuePercent}%, var(--slider-bar-color) ${sliderValuePercent}% 100%)`;
		}
	}

	/**
	 * Initialize number formatting and input restrictions for `.frm-has-number-format` fields.
	 */
	function initFormatFieldValueNumbers() {
		document.querySelectorAll( '.frm-has-number-format:not([data-has-number-format-events])' ).forEach( field => {
			field.dataset.hasNumberFormatEvents = true;

			document.addEventListener( 'frmShowField', () => onShowField( field ) );
			field.addEventListener( 'focus', handleFocus );
			field.addEventListener( 'blur', handleBlur );

			if ( field.classList.contains( 'frm_field_number' ) ) {
				field.addEventListener( 'keydown', restrictToNumericInput );
			}

			triggerFormatting( field );
		});

		/**
		 * Trigger formatting manually.
		 *
		 * @param {HTMLElement} field Field element.
		 */
		function triggerFormatting( field ) {
			field.dispatchEvent( new Event( 'blur', { bubbles: true } ) );
		}

		/**
		 * Handle the 'frmShowField' event.
		 *
		 * @param {HTMLElement} field Field element.
		 */
		function onShowField( field ) {
			if ( ! field.offsetHeight || field.value !== '' ) {
				return;
			}

			// Restore raw value and apply formatting when field becomes visible.
			const { rawValue, frmval } = field.dataset;
			const valueToUse           = rawValue || frmval;
			if ( valueToUse ?? false ) {
				field.value = valueToUse;
				triggerFormatting( field );
			}
		}

		/**
		 * Restore the unformatted value when the field gains focus.
		 *
		 * @param {FocusEvent} event Focus event for the field.
		 */
		function handleFocus( { target: field } ) {
			const { rawValue } = field.dataset;

			if ( rawValue !== null ) {
				field.value = rawValue;
			}
		}

		/**
		 * Save the raw value and apply formatting when the field loses focus.
		 *
		 * @param {FocusEvent} event Blur event for the field.
		 */
		function handleBlur( { target: field } ) {
			const previousValue    = field.value;
			const previousRawValue = field.getAttribute( 'data-raw-value' ) ?? '';
			const fieldKey         = getFieldKey( field.id, field.name );
			const userTyped        = field.value;

			field.setAttribute( 'data-raw-value', userTyped );
			field.value = applyNumberFormatting( userTyped, fieldKey );

			if ( previousValue !== field.value || previousRawValue !== userTyped ) {
				field.dispatchEvent( new Event( 'change', { bubbles: true } ) );
			}
		}

		/**
		 * Restrict input to valid numeric characters based on custom separators.
		 *
		 * @param {KeyboardEvent} event Keydown event for the field.
		 */
		function restrictToNumericInput( event ) {
			const { target: field, key, ctrlKey, metaKey } = event;
			const fieldKey = getFieldKey( field.id, field.name );
			const formatConfig = getCurrencyFromCalcRule( __FRMCALC.calc?.[ fieldKey ] ) || {};
			const decimalSeparator = formatConfig.decimal_separator ?? '.';
			const thousandSeparator = formatConfig.thousand_separator ?? '';
			const decimals = parseInt(formatConfig.decimals ?? 2, 10);

			const allowedKeys = [
				'ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown',
				'Backspace', 'Delete', 'Tab', 'Home', 'End', 'Enter',
			];

			if ( allowedKeys.includes( key ) || ctrlKey || metaKey ) {
				return;
			}

			// Build regex based on allowed chars
			let regexPattern = '^[0-9\\+\\-';

			// Only add decimal separator if decimals > 0
			if ( decimals > 0 ) {
				regexPattern += escapeForRegex( decimalSeparator );
			}

			if ( thousandSeparator ) {
				regexPattern += escapeForRegex( thousandSeparator );
			}

			regexPattern += ']$';

			const oneCharRegex = new RegExp( regexPattern );

			if ( ! oneCharRegex.test( key ) ) {
				event.preventDefault();
				return;
			}

			// Prevent decimal input if decimals = 0
			if ( decimals === 0 && key === ( decimalSeparator !== '' ? decimalSeparator : '.' ) ) {
				event.preventDefault();
				return;
			}

			// Only allow one decimal point
			if ( key === decimalSeparator && field.value.includes( decimalSeparator ) ) {
				event.preventDefault();
				return;
			}

			// If user typed '+' or '-', allow only if it's the first character
			if ( ( key === '+' || key === '-' ) && field.selectionStart !== 0 ) {
				event.preventDefault();
			}
		}

		/**
		 * Format numeric substrings in the provided text based on the custom format configuration.
		 *
		 * @param {string} text The text containing numbers to format.
		 * @param {string} fieldKey The field key used to retrieve the format configuration.
		 * @returns {string} The text with formatted numbers.
		 */
		function applyNumberFormatting( text, fieldKey ) {
			if ( ! __FRMCALC?.calc?.[ fieldKey ] ) {
				return text;
			}

			const formatConfig = getCurrencyFromCalcRule( __FRMCALC.calc[fieldKey] );
			const {
				decimal_separator: decimalSeparator = '.',
				thousand_separator: thousandSeparator = ',',
				symbol_left: symbolLeft = '',
				symbol_right: symbolRight = '',
				symbol_padding: symbolPadding = ''
			} = formatConfig;

			const escapedDecimalSeparator = escapeForRegex( decimalSeparator );
			const escapedThousandSeparator = escapeForRegex( thousandSeparator );
			const escapedSymbolLeft = escapeForRegex( symbolLeft );
			const escapedSymbolRight = escapeForRegex( symbolRight );
			const escapedSymbolPadding = escapeForRegex( symbolPadding );

			// Build the regex pattern
			let pattern = '(?<!\\S)';

			// Add left symbol with padding if there is a left symbol
			if ( symbolLeft ) {
				pattern += `(?:${escapedSymbolLeft}${symbolPadding ? escapedSymbolPadding : ''})?`;
			}

			// Add the core numeric pattern
			pattern += `([-+]?\\d+(?:${escapedThousandSeparator}\\d+)*(?:${escapedDecimalSeparator}\\d+)?)`;

			// Add right symbol with padding if there is a right symbol
			if ( symbolRight ) {
				pattern += `(?:${symbolPadding ? escapedSymbolPadding : ''}${escapedSymbolRight})?`;
			}

			pattern += '(?!\\S)';

			const numericRegex = new RegExp( pattern, 'g' );

			return text.replace( numericRegex, ( match ) => {
				const sign = match.startsWith( '-' ) ? '-' : ( match.startsWith( '+' ) ? '+' : '' );
				const signRemoved = match.replace( /^[-+]/, '' );

				const number = stripSymbolsAndFormatNumber( signRemoved, formatConfig );
				const formatted = formatCurrency( normalizeTotal( number, formatConfig ), formatConfig );

				return `${sign}${formatted}`;
			});
		}

		/**
		 * Strip currency symbols and convert a numeric string to a standard decimal format by removing custom separators.
		 *
		 * @param {string} value The numeric string.
		 * @param {Object} config The format configuration.
		 * @param {string} [config.decimal_separator] The custom decimal separator.
		 * @param {string} [config.thousand_separator] The custom thousand separator.
		 * @param {string} [config.symbol_left] The left currency symbol.
		 * @param {string} [config.symbol_right] The right currency symbol.
		 * @returns {string} The numeric string in standard decimal format.
		 */
		function stripSymbolsAndFormatNumber( value, config = {} ) {
			const {
				symbol_left: symbolLeft = '',
				symbol_right: symbolRight = '',
				decimal_separator: decimalSeparator = '.',
				thousand_separator: thousandSeparator = ','
			} = config;

			let cleanedValue = value.trim();

			// Remove left and right symbols if present
			if ( symbolLeft ) {
				cleanedValue = cleanedValue.replace( symbolLeft, '' );
			}

			if ( symbolRight ) {
				cleanedValue = cleanedValue.replace( symbolRight, '' );
			}

			cleanedValue = cleanedValue.trim();

			// Remove thousand separators and replace custom decimal separator
			if ( thousandSeparator ) {
				const split = cleanedValue.split( thousandSeparator );
				// Make sure that it isn't actually a decimal separator first.
				if ( split.length !== 2 || split[1].length > 2 ) {
					cleanedValue = split.join( '' );
				} else {
					cleanedValue = split[0] + '.' + split[1];
				}
			}

			if ( decimalSeparator.trim() && decimalSeparator !== '.' ) {
				cleanedValue = cleanedValue.split( decimalSeparator ).join( '.' );
			}

			return cleanedValue.trim();
		}

		/**
		 * Escape special characters in a string for use in regular expressions.
		 *
		 * @param {string} str The string to escape.
		 * @returns {string} The escaped string.
		 */
		function escapeForRegex( str ) {
			return str.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
		}
	}

	/**
	 * Initializes a range slider.
	 *
	 * @param {HTMLInputElement} rangeInput     The input element of the range slider.
	 * @param {HTMLElement}      rangeContainer The container element of the range slider.
	 * @return {void}
	 */
	function initializeRangeSlider( rangeInput, rangeContainer ) {
		const parseDataset = dataset => {
			const parsed = {};
			for ( const [ key, value ] of Object.entries( dataset ) ) {
			  parsed[ key ] = parseFloat( value, 10 );
			}
			return parsed;
		};
		const { minGap, maxGap } = parseDataset( rangeInput.dataset );

		const min      = parseFloat( rangeInput.min, 10 );
		const max      = parseFloat( rangeInput.max, 10 );
		const minRange = minGap || 0;
		const maxRange = maxGap || max - min;

		// Validate inputs
		if ( min >= max ) {
			throw new Error( 'Min value must be less than max value' );
		}

		if ( minRange <= 0 || minRange > ( max - min ) ) {
			throw new Error( 'Invalid minimum range value' );
		}

		if ( maxRange < minRange || maxRange > ( max - min ) ) {
			throw new Error( 'Invalid maximum range value' );
		}

		const values          = rangeInput.value.split( ',' );
		values[0]             = values[0] || min;
		values[1]             = values[1] || max;
		const step            = parseFloat( rangeInput.step, 10 );
		const sliderRange     = max - min;
		const stepInPercent   = step / sliderRange * 100;
		const minRangePercent = ( minRange / sliderRange ) * 100;
		const maxRangePercent = ( maxRange / sliderRange ) * 100;
		const minHandle       = rangeContainer.querySelector( '.min-handle' );
		const maxHandle       = rangeContainer.querySelector( '.max-handle' );
		const rangeElement    = rangeContainer.querySelector( '.frm-slider-range' );
		const minValueDisplay = rangeContainer.querySelector( '.min-value' );
		const maxValueDisplay = rangeContainer.querySelector( '.max-value' );
		const track           = rangeContainer.querySelector( '.frm-slider-track' );

		let decimalsToUse = 0;

		[ step, min, max, minRange, maxRange ].forEach( value => {
			const decimal = countDecimals( value );
			if ( decimal > decimalsToUse ) {
				decimalsToUse = decimal;
			}
		});

		const initialMinValue = Math.max( values[0], min );
		const initialMaxValue = Math.min( values[1], initialMinValue + maxRange, max );

		const getWidthWithHiddenParent = element => {
			if ( ! element.isConnected ) { // If element is deleted
				return 0;
			}
			if (
				element.closest( '.frm_form_field' ).style.display !== 'none' &&
				element.closest( '.frm_toggle_container.frm_grid_container' )?.style.display !== 'none'
			) {
				const trackRect = track.getBoundingClientRect();
				return trackRect.width;
			}

			const originalParent      = element.parentNode;
			const originalNextSibling = element.nextSibling;

			// Move to body to avoid parent's `display: none`
			document.body.append( element) ;

			const width = element.offsetWidth;

			// Move back
			if ( originalNextSibling ) {
			  originalParent.insertBefore( element, originalNextSibling );
			} else {
			  originalParent.append( element );
			}

			return width;
		};

		let trackWidth = getWidthWithHiddenParent( track );

		// Initialize positions
		let minPos = ( initialMinValue - min ) / sliderRange * 100;
		let maxPos = ( initialMaxValue - min ) / sliderRange * 100;

		updateHandlesPositionAndValue();

		// Update track dimensions on resize
		window.addEventListener( 'resize', function() {
			trackWidth = getWidthWithHiddenParent( track );
		} );

		// State variables for dragging
		let isDragging = false;
		let activeHandle = null;
		let startX = 0;
		let startMinPos = 0;
		let startMaxPos = 0;

		// Setup both handles
		setupHandle( minHandle );
		setupHandle( maxHandle );

		rangeInput.addEventListener( 'change', setHandlesBasedOnValue );

		function setupHandle( handle ) {
			handle.addEventListener( 'touchstart', handleTouchStart, { passive: false });
			handle.addEventListener( 'mousedown', handleMouseDown );
		}

		function handleTouchStart( e ) {
			isDragging = true;
			activeHandle = e.target;
			const touch = e.touches[0];
			startX = touch.clientX;
			startMinPos = minPos;
			startMaxPos = maxPos;
			document.addEventListener( 'touchmove', handleTouchMove, {passive: false});
			document.addEventListener( 'touchend', handleTouchEnd );
			e.preventDefault();
		}

		function handleMouseDown( e ) {
			isDragging = true;
			activeHandle = e.target;
			startX = e.clientX;
			startMinPos = minPos;
			startMaxPos = maxPos;
			document.addEventListener( 'mousemove', onMouseMove );
			document.addEventListener( 'mouseup', onMouseUp );
			e.preventDefault();
		}

		function handleTouchMove( e ) {
			if ( ! isDragging ) {
				return;
			}

			const touch = e.touches[0];
			const mouseEvent = new MouseEvent( 'mousemove', {
				clientX: touch.clientX,
				clientY: touch.clientY
			});
			onMouseMove( mouseEvent, e );
			e.preventDefault();
		}

		function handleTouchEnd() {
			document.removeEventListener( 'touchmove', handleTouchMove );
			document.removeEventListener( 'touchend', handleTouchEnd );
		}

		function onMouseMove( e ) {
			if ( ! isDragging ) {
				return;
			}
			const deltaX       = e.clientX - startX;
			const deltaPercent = ( deltaX / trackWidth ) * 100;
			const minHandleIsMoved = activeHandle === minHandle;
			let newMinPos, newMaxPos, hasMovedMinimumSteps;

			if ( minHandleIsMoved ) {
				newMinPos = startMinPos + deltaPercent; // The new left handle's position in % -- at the current mouse position.
				hasMovedMinimumSteps = Math.abs( newMinPos - minPos ) >= stepInPercent;
			} else {
				newMaxPos = startMaxPos + deltaPercent;
				hasMovedMinimumSteps = Math.abs( newMaxPos - maxPos ) >= stepInPercent;
			}

			if ( ! hasMovedMinimumSteps ) {
				return;
			}

			const movingRight      = minHandleIsMoved ? newMinPos > minPos : newMaxPos > maxPos;
			const numberOfNewSteps = movingRight ? Math.floor( deltaPercent / stepInPercent ) : Math.ceil( deltaPercent / stepInPercent );
			const newRange         = activeHandle === minHandle ? maxPos - newMinPos : newMaxPos - minPos; // The distance between the right and left handles in %.
			const stepsDelta       = numberOfNewSteps * stepInPercent;

			if ( activeHandle === minHandle ) {
				if ( newRange < minRangePercent ) { // Move the whole range to right.
					const tempMinPos = Math.max( startMinPos + stepsDelta, 0 );
					if ( tempMinPos + minRangePercent <= 100 ) {
						minPos = tempMinPos;
						maxPos = Math.max( 0, minPos + minRangePercent );
					}
				} else if ( newRange > maxRangePercent ) { // Move the whole range to left.
					minPos = Math.max( 0, startMinPos + stepsDelta );
					maxPos = minPos + maxRangePercent;
				} else {
					// Normal movement within constraints
					minPos = Math.max( 0, startMinPos + stepsDelta );
				}
				updateHandlesPositionAndValue();
				return;
			}

			// Max handle is moved
			if ( newRange < minRangePercent ) { // Move the whole range to left.
				const tempMaxPos = Math.min( startMaxPos + stepsDelta, 100 );
				if ( tempMaxPos - minRangePercent >= 0 ) {
					maxPos = tempMaxPos;
					minPos = Math.max( 0, maxPos - minRangePercent );
				}
			} else if ( newRange > maxRangePercent ) { // Move the whole range to right.
				maxPos = Math.min( 100, startMaxPos + stepsDelta );
				minPos = maxPos - maxRangePercent;
			} else {
				// Normal movement within constraints
				maxPos = Math.min( 100, startMaxPos + stepsDelta );
			}
			updateHandlesPositionAndValue();
		}

		function updateHandlesPositionAndValue() {
			minPos = parseFloat( minPos );
			maxPos = parseFloat( maxPos );

			// Update min and max handles positions.
			minHandle.style.left  = `${minPos}%`;
			maxHandle.style.right = `${100-maxPos}%`;

			// Update range element.
			updateRangeElement();

			// Update displayed values.
			const { currentMin, currentMax } = prepareCurrentValues();
			updateDisplayedValues( currentMin, currentMax );

			// Update input value.
			rangeContainer.querySelector( 'input' ).value = `${currentMin},${currentMax}`;
		}

		function countDecimals( num ) {
			const str          = num.toString();
			const decimalIndex = str.indexOf( '.' );
			return decimalIndex === -1 ? 0 : str.length - decimalIndex - 1;
		}

		function formatSliderValue ( value ) {
			const fieldKey = getFieldKey( rangeInput.id, rangeInput.name );
			if ( window.__FRMCALC?.calc[ fieldKey ]) {
				return formatValueAsCurrency( value, fieldKey );
			}
			return value;
		}

		function updateDisplayedValues( currentMin, currentMax ) {
			minValueDisplay.textContent = formatSliderValue( currentMin );
			maxValueDisplay.textContent = formatSliderValue( currentMax );
		}

		function prepareCurrentValues() {
			let currentMin = ( min + ( minPos / stepInPercent ) * step ).toFixed( decimalsToUse );
			let currentMax = ( min + ( maxPos / stepInPercent ) * step ).toFixed( decimalsToUse );
			currentMin     = Number( currentMin ).toPrecision().toString();
			currentMax     = Number( currentMax ).toPrecision().toString();

			return { currentMin, currentMax };
		}

		function onMouseUp() {
			isDragging = false;
			document.removeEventListener( 'mousemove', onMouseMove );
			document.removeEventListener( 'mouseup', onMouseUp );
		}

		function updateRangeElement() {
			rangeElement.style.left  = `${minPos}%`;
			rangeElement.style.width = `${maxPos - minPos}%`;
		}

		/**
		 * Change events are only triggered for the range slider when the default value is set
		 * The default value is set when the field is conditionally shown.
		 */
		function setHandlesBasedOnValue() {
			const { minGap }      = parseDataset( rangeInput.dataset );
			const min             = parseFloat( rangeInput.min, 10 );
			const max             = parseFloat( rangeInput.max, 10 );
			const minRange        = minGap || 0;
			const sliderRange     = max - min;
			const values          = rangeInput.value.split( ',' );
			values[0]             = values[0] || min;
			values[1]             = values[1] || max;
			const initialMinValue = Math.max( values[0], min );
			const initialMaxValue = Math.min( Math.max( values[1], initialMinValue + minRange ), max );

			minPos = ( initialMinValue - min ) / sliderRange * 100;
			maxPos = ( initialMaxValue - min ) / sliderRange * 100;
			updateHandlesPositionAndValue();
		}
	}

	return {
		init: function() {
			maybeUpdateFormsOverflowX();

			maybeAddIntlTelInput( document.querySelectorAll( '.frm-intl-tel-input' ) );
			initRangeInput( document.querySelectorAll( '.with_frm_style input[type=range]' ) );
			initFormatFieldValueNumbers();

			addEventListener( 'pageshow', maybeTriggerCalc );

			jQuery( document ).on( 'frmFormComplete', afterFormSubmitted );
			jQuery( document ).on( 'frmPageChanged', afterPageChanged );
			jQuery( document ).on( 'frmAfterAddRow frmAfterRemoveRow', calcProductsTotal );

			jQuery( document ).on( 'click', '.frm_trigger', toggleSection );
			jQuery( document ).on( 'keydown', '.frm_trigger', toggleSection );

			var $blankField = jQuery( '.frm_blank_field' );
			if ( $blankField.length ) {
				$blankField.closest( '.frm_toggle_container' ).prev( '.frm_trigger' ).trigger( 'click' );
			}

			jQuery( document ).on( 'click', '.frm_remove_link', removeFile );

			jQuery( document ).on( 'frmFieldChanged', maybeCheckDependent );

			jQuery( document ).on( 'keyup', 'input.frm_strength_meter', checkPasswordField );
			jQuery( document ).on( 'keydown', '.frm_switch', triggerToggleClickOnSpace );

			jQuery( document ).on( 'mouseenter click', '.frm-star-group input', loadStars );
			jQuery( document ).on( 'mouseenter', '.frm-star-group .star-rating:not(.star-rating-readonly)', hoverStars );
			jQuery( document ).on( 'mouseleave', '.frm-star-group .star-rating:not(.star-rating-readonly)', unhoverStars );

			jQuery( document ).on(
				'click',
				'.frm-show-form input[type="submit"], .frm-show-form input[name="frm_prev_page"], .frm_page_back, .frm_page_skip, .frm-show-form .frm_save_draft, .frm_prev_page, .frm_button_submit, .frm_rootline_show_hidden_steps_btn .frm_rootline_single',
				setNextPage
			);

			jQuery( document ).on( 'change', '.frm_other_container input[type="checkbox"], .frm_other_container input[type="radio"], .frm_other_container select', showOtherText );
			jQuery( document ).on( 'change', '.frm_switch_block input[type="checkbox"]', setToggleAriaChecked );

			jQuery( document ).on( 'click', '.frm_remove_form_row', removeRow );
			jQuery( document ).on( 'click', '.frm_add_form_row', addRow );

			// In place edit.
			jQuery( document ).on( 'click', '.frm_edit_link_container a.frm_inplace_edit', editEntry );
			jQuery( document ).on( 'click', '.frm_edit_link_container a.frm_cancel_edit', cancelEdit );
			jQuery( document ).on( 'click', '.frm_ajax_delete', deleteEntry );

			// toggle collapsible entries shortcode
			jQuery( '.frm_month_heading, .frm_year_heading' ).on( 'click', function() {
				var content = jQuery( this ).children( '.ui-icon-triangle-1-e, .ui-icon-triangle-1-s' );
				if ( content.hasClass( 'ui-icon-triangle-1-e' ) ) {
					content.addClass( 'ui-icon-triangle-1-s' ).removeClass( 'ui-icon-triangle-1-e' );
					jQuery( this ).next( '.frm_toggle_container' ).fadeIn( 'slow' );
				} else {
					content.addClass( 'ui-icon-triangle-1-e' ).removeClass( 'ui-icon-triangle-1-s' );
					jQuery( this ).next( '.frm_toggle_container' ).hide();
				}
			});

			// Elementor popup show event. Fix Elementor Popup && FF PRO fields( file upload | dynamic field ) conflicts
			jQuery( document ).on( 'elementor/popup/show', handleElementorPopupConflicts );

			addTopAddRowBtnForRepeater();

			// Checkbox Selection Limit
			jQuery( document ).on( 'click', 'input[type="checkbox"][data-frmlimit]', checkCheckboxSelectionLimit );

			// Total field calc
			jQuery( document ).on( 'change', '[type="checkbox"][data-frmprice],[type="radio"][data-frmprice],[type="hidden"][data-frmprice],select:has([data-frmprice])', calcProductsTotal );
			jQuery( document ).on( 'keyup change', '[data-frmproduct],[type="text"][data-frmprice]', calcProductsTotal );

			jQuery( document ).on( 'frmFormComplete frmPageChanged frmInPlaceEdit frmAfterAddRow', setAutoHeightForTextArea );

			maybeDisableCheckboxesWithLimit();

			setInlineFormWidth();
			checkConditionalLogic( 'pageLoad' );
			checkFieldsOnPage( undefined, 'pageLoad' );
			processPendingAjax();

			addRteRequiredMessages();
			setAutoHeightForTextArea();
			updateContentLength();

			// make sure this comes last, particularly after checkConditionalLogic & checkFieldsOnPage
			calcProductsTotal();

			startOverButton();

			maybeShowMoreStepsButton();

			showMoreStepsButtonEvents();

			validateFieldValue();
			validateForm();

			handleShowPasswordBtn();

			jQuery( document ).on( 'input change', 'input[data-frmrange]', handleSliderEvent );
		},

		savingDraft: function( object ) {
			return savingDraftEntry( object );
		},

		goingToPreviousPage: function( object ) {
			return goingToPrevPage( object );
		},

		hideOrShowFields: function( ids, event ) {
			if ( 'pageLoad' === event ) {
				clearHideFields();
			}
			var len = ids.length,
				repeatArgs = { repeatingSection: '', repeatRow: '' };
			for ( var i = 0, l = len; i < l; i++ ) {
				hideOrShowFieldById( ids[ i ], repeatArgs );
				if ( i == ( l - 1 ) ) {
					showForm();
				}
			}
		},

		hidePreviouslyHiddenFields: function() {
			var hiddenFields = getAllHiddenFields(),
				len = hiddenFields.length;
			for ( var i = 0, l = len; i < l; i++ ) {
				var container = document.getElementById( hiddenFields[ i ]);

				//check for submit button
				if ( container == null ) {
					container = document.querySelector( '#' + hiddenFields[ i ]);
					if ( container != null && hiddenFields[ i ].includes('frm_final_submit') ) {
						hidePreviouslyHiddenSubmitButton( hiddenFields[ i ]);
						continue;
					}
				}

				if ( container !== null ) {
					container.style.display = 'none';
					setHiddenProductContainer( container );
				}
			}
		},

		submitAllowed: function( object ) {
			var formElementId = object.getAttribute( 'id' );

			if ( ! isSubmitButtonOnPage( formElementId + ' .frm_final_submit' ) || goingToPrevPage( object ) || savingDraftEntry( object ) ) {
				return true;
			}

			var formKey = getFormKeyFromFormElementID( formElementId );

			return ! isOnPageSubmitButtonHidden( formKey );
		},

		checkDependentDynamicFields: function( ids ) {
			var len = ids.length,
				repeatArgs = { repeatingSection: '', repeatRow: '' };
			for ( var i = 0, l = len; i < l; i++ ) {
				hideOrShowFieldById( ids[ i ], repeatArgs );
			}
		},

		checkDependentLookupFields: function( ids ) {
			var fieldId,
				repeatArgs = { repeatingSection: '', repeatRow: '' };
			for ( var i = 0, l = ids.length; i < l; i++ ) {
				fieldId = ids[ i ];
				updateWatchingFieldById( fieldId, repeatArgs, 'value changed' );
			}
		},

		loadGoogle: function() {
			var graphs, packages, i;

			if ( typeof google === 'undefined' || ! google || ! google.load ) {
				setTimeout( frmProForm.loadGoogle, 30 );
				return;
			}

			graphs = __FRMTABLES;
			packages = Object.keys( graphs );

			for ( i = 0; i < packages.length; i++ ) {
				if ( packages[ i ] === 'graphs' ) {
					generateGoogleGraphs( graphs[ packages[ i ] ]);
				} else {
					generateGoogleTables( graphs[ packages[ i ] ], packages[ i ]);
				}
			}
		},

		removeUsedTimes: function( obj, timeField ) {
			var $form, form, e, data, success, extraParams;

			$form = jQuery( obj ).parents( 'form' ).first();
			form = $form.get( 0 );
			e = $form.find( 'input[name="id"]' );

			data = {
				action: 'frm_fields_ajax_time_options',
				time_field: timeField,
				date_field: obj.id,
				entry_id: ( e ? e.val() : '' ), date: jQuery( obj ).val(),
				nonce: frm_js.nonce
			};

			success = function( opts ) {
				if ( null !== document.getElementById( timeField + '_H' ) ) {
					frmProForm.removeUsedTimesForMultipleDropdown( opts, timeField );
					return;
				}

				var $timeField = jQuery( document.getElementById( timeField ) );
				$timeField.find( 'option' ).prop( 'disabled', false );
				if ( opts.length > 0 ) {
					for ( var i = 0, l = opts.length; i < l; i++ ) {
						$timeField.get( 0 ).querySelectorAll( 'option[value="' + opts[ i ] + '"]' ).forEach(
							function( option ) {
								option.disabled = true;
								if ( option.selected ) {
									option.selected = false;
								}
							}
						);
					}
				}
			};

			extraParams = { dataType: 'json' };

			postToAjaxUrl( form, data, success, false, extraParams );
		},

		removeUsedTimesForMultipleDropdown: function( times, timeField ) {
			const self = this;

			self.hDropdown = null;
			self.mDropdown = null;
			self.ampmDropdown = null;

			function getTimeDropdownsElements( timeField ) {
				return {
					h: getTimeDropdownsElements.prototype.h[ timeField ],
					m: getTimeDropdownsElements.prototype.m[ timeField ],
					ampm: getTimeDropdownsElements.prototype.ampm[ timeField ]
				};
			}

			this.init = function() {
				if ( 'undefined' === typeof getTimeDropdownsElements.prototype.h ) {
					getTimeDropdownsElements.prototype.h = [];
					getTimeDropdownsElements.prototype.m = [];
					getTimeDropdownsElements.prototype.ampm = [];
				}

				if ( 'undefined' !== typeof getTimeDropdownsElements.prototype.h[ timeField ] ) {
					return;
				}

				getTimeDropdownsElements.prototype.h[ timeField ] = document.getElementById( timeField + '_H' );
				getTimeDropdownsElements.prototype.m[ timeField ] = document.getElementById( timeField + '_m' );
				getTimeDropdownsElements.prototype.ampm[ timeField ] = document.getElementById( timeField + '_A' );

				if ( null === getTimeDropdownsElements( timeField ).h ) {
					return;
				}

				getTimeDropdownsElements( timeField ).h.addEventListener( 'change', function( event ){
					self.disableMinutes( event.target.value );
				});
				getTimeDropdownsElements( timeField ).m.addEventListener( 'change', function( event ){
				});
				getTimeDropdownsElements( timeField ).ampm.addEventListener( 'change', function(){
					self.disableHours();
				});

				self.disableHours();
				self.disableAmpm();

			};

			this.checkIfTimeIsDisabled =  time => times.includes( time );

			this.getActiveTime = function() {
				var h = getTimeDropdownsElements( timeField ).h.value || '12',
					m = getTimeDropdownsElements( timeField ).m.value || '00',
					ampm = getTimeDropdownsElements( timeField ).ampm.value || 'AM';

				return h + ':' + m + ' ' + ampm;
			};

			this.getMeridiem = function() {
				return getTimeDropdownsElements( timeField ).ampm.value || 'AM';
			};

			this.listPossibleHourTimes = function( hour ) {
				const minutes = [];
				const ampm = [];
				const minutesElement = getTimeDropdownsElements( timeField ).m;
				const ampmElement = getTimeDropdownsElements( timeField ).ampm;

				for ( let i = 0; i < ampmElement.options.length; i++ ) {
					ampm.push( ampmElement.options[ i ].value );
				}

				for ( let i = 0; i < minutesElement.options.length; i++ ) {
					if ( '' !== minutesElement.options[ i ].value ) {
						minutes.push( minutesElement.options[ i ].value );
					}
				}

				return ampm.reduce( ( array, ampm ) => {
					array[ ampm ] = minutes.map( ( minute ) => {
						return hour + ':' + minute + ' ' + ampm;
					});
					return array;
				}, {} );
			};

			this.hourHasEmptySlots = function( hour ) {
				const possibleTimes = this.listPossibleHourTimes( hour );
				const ampm = this.getMeridiem();
				const allPossibleTimes = possibleTimes[ ampm ];
				return ! allPossibleTimes.every( ( time ) => {
					return times.includes( time );
				});
			};

			this.disableHours = function() {
				if ( null === getTimeDropdownsElements( timeField ).h ) {
					return;
				}

				getTimeDropdownsElements( timeField ).h.querySelectorAll( 'option' ).forEach( function( option ) {
					option.disabled = ! self.hourHasEmptySlots( option.value );
				});
			};

			this.disableMinutes = function( hour ) {
				if ( null === getTimeDropdownsElements( timeField ).m ) {
					return;
				}

				getTimeDropdownsElements( timeField ).m.querySelectorAll( 'option' ).forEach( function( option ) {
					const timeString = hour + ':' + option.value + ' AM';
					option.disabled = times.includes( timeString );
				});
			};

			this.disableAmpm = function() {
				if ( null === getTimeDropdownsElements( timeField ).ampm ) {
					return;
				}
				getTimeDropdownsElements( timeField ).ampm.querySelectorAll( 'option' ).forEach( function( option ) {
					if ( times.includes( option.value ) ) {
						option.disabled = true;
					}
				});
			};

			this.init();
		},

		changeRte: function( editor ) {
			editor.on( 'change', function() {
				var content = editor.getBody().innerHTML;
				jQuery( '#' + editor.id ).val( content ).trigger( 'change' );
			});
		},

		addFormidableClassToDatepicker: function( _, options ) {
			if ( options.dpDiv ) {
				Array.prototype.forEach.call(
					getAllFormClasses( options.input.get( 0 ) ),
					function( formClass ) {
						options.dpDiv.get( 0 ).classList.add( formClass );
					}
				);

				options.dpDiv.addClass( 'frm-datepicker' );
				options.dpDiv.get( 0 ).setAttribute( 'is-formidable-datepicker', 1 );
				options.input.get( 0 )?.classList.add( 'active' );
			}
			return options;
		},

		removeFormidableClassFromDatepicker: function( _, options ) {
			var dpDiv;
			if ( options.dpDiv ) {
				dpDiv = options.dpDiv.get( 0 );
				dpDiv.removeAttribute( 'is-formidable-datepicker' );
				options.input.get( 0 )?.classList.remove( 'active' );

				// Delay it slightly so it doesn't use styling while it's fading out.
				setTimeout(
					function() {
						if ( dpDiv.hasAttribute( 'is-formidable-datepicker' ) ) {
							// Avoid removing it the class if the datepicker was immediately opened again.
							return;
						}

						Array.prototype.forEach.call(
							getAllFormClasses( options.input.get( 0 ) ),
							function( formClass ) {
								options.dpDiv.get( 0 ).classList.remove( formClass );
							}
						);

						jQuery( dpDiv ).removeClass( 'frm-datepicker' );
					},
					400
				);
			}
		},

		isIntlPhoneInput: function( field ) {
			var pattern = field.getAttribute( 'pattern' );
			if ( '\\+?\\d{1,4}[\\s\\-]?(?:\\(\\d{1,3}\\)[\\s\\-]?)?\\d{1,4}[\\s\\-]?\\d{1,4}[\\s\\-]?\\d{1,4}$' !== pattern ) {
				return false;
			}
			return 'undefined' !== typeof intlPhoneInputs && 'undefined' !== typeof intlPhoneInputs[ field.id ];
		},

		validateIntlPhoneInput: function( field ) {
			return intlPhoneInputs[ field.id ].isValidNumber();
		},

		frmDatepicker: frmDatepickerPro,
	};

	/**
	 * Keep label floating if datepicker is open (prevents jumping when clicking date)
	 *
	 * @param {Event} event Focusout event
	 * @return {void}
	 */
	function keepFloatingLabelOnDateBlur( event ) {
		const input = event.target.closest( '.frm_date' );
		if ( input?.classList.contains( 'active' ) ) {
			input.closest( '.frm_inside_container' )?.classList.add( 'frm_label_float_top' );
		}
	}
}
var frmProForm = frmProFormJS();

document.addEventListener(
	'frmMaybeDelayFocus',
	function( event ) {
		if ( 'object' !== typeof event.frmData || 'undefined' === typeof event.frmData.input ) {
			return;
		}

		const input = event.frmData.input;

		// Possibly delay focus.
		const form = input.closest( 'form' );
		if ( form ) {
			const focusHandler = () => {
				input.focus();
				form.removeEventListener( 'frmProAfterFormFadeIn', focusHandler );
			};
			form.addEventListener( 'frmProAfterFormFadeIn', focusHandler );
		}
	}
);

jQuery( document ).ready( function() {
	frmProForm.init();
});

if ( ! Math.round10 ) {
	// https://www.jacklmoore.com/notes/rounding-in-javascript/
	Math.round10 = function( value, decimals ) {
		return Number( Math.round( value + 'e' + decimals ) + 'e-' + decimals );
	};
}

window.frmUpdateField = function( entryId, fieldId, value, message, num ) {
	jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).html( '<span class="frm-loading-img"></span>' );
	jQuery.ajax({
		type: 'POST',
		url: frm_js.ajax_url, // eslint-disable-line camelcase
		data: {
			action: 'frm_entries_update_field_ajax',
			entry_id: entryId,
			field_id: fieldId,
			value: value,
			nonce: frm_js.nonce // eslint-disable-line camelcase
		},
		success: function() {
			if ( message.replace( /^\s+|\s+$/g, '' ) === '' ) {
				jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).fadeOut( 'slow' );
			} else {
				jQuery( document.getElementById( 'frm_update_field_' + entryId + '_' + fieldId + '_' + num ) ).replaceWith( message );
			}
		}
	});
};
// source --> https://landoll.com/wp-content/plugins/wp-store-locator/assets/vendor/markerclusterer/google/index.min.js?ver=2.3.21 
var markerClusterer=function(t){"use strict";var r,e,n="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},o={};function i(){if(e)return r;e=1;var t=function(t){return t&&t.Math===Math&&t};return r=t("object"==typeof globalThis&&globalThis)||t("object"==typeof window&&window)||t("object"==typeof self&&self)||t("object"==typeof n&&n)||t("object"==typeof r&&r)||function(){return this}()||Function("return this")()}var s,a,u,c,f,l,h,p,m={};function d(){return a?s:(a=1,s=function(t){try{return!!t()}catch(t){return!0}})}function g(){if(c)return u;c=1;var t=d();return u=!t(function(){return 7!==Object.defineProperty({},1,{get:function(){return 7}})[1]})}function v(){if(l)return f;l=1;var t=d();return f=!t(function(){var t=function(){}.bind();return"function"!=typeof t||t.hasOwnProperty("prototype")})}function y(){if(p)return h;p=1;var t=v(),r=Function.prototype.call;return h=t?r.bind(r):function(){return r.apply(r,arguments)},h}var w,b,E,k,x,M,O,P,C,S,j,A,I,_,T,L,q,R,Z,z,D,F,N,U,B,W,$,V,G,H,K,Y,X,J,Q,tt,rt,et,nt,ot,it,st={};function at(){return E?b:(E=1,b=function(t,r){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:r}})}function ut(){if(x)return k;x=1;var t=v(),r=Function.prototype,e=r.call,n=t&&r.bind.bind(e,e);return k=t?n:function(t){return function(){return e.apply(t,arguments)}},k}function ct(){if(O)return M;O=1;var t=ut(),r=t({}.toString),e=t("".slice);return M=function(t){return e(r(t),8,-1)}}function ft(){return j?S:(j=1,S=function(t){return null==t})}function lt(){if(I)return A;I=1;var t=ft(),r=TypeError;return A=function(e){if(t(e))throw new r("Can't call method on "+e);return e}}function ht(){if(T)return _;T=1;var t=function(){if(C)return P;C=1;var t=ut(),r=d(),e=ct(),n=Object,o=t("".split);return P=r(function(){return!n("z").propertyIsEnumerable(0)})?function(t){return"String"===e(t)?o(t,""):n(t)}:n}(),r=lt();return _=function(e){return t(r(e))}}function pt(){if(q)return L;q=1;var t="object"==typeof document&&document.all;return L=void 0===t&&void 0!==t?function(r){return"function"==typeof r||r===t}:function(t){return"function"==typeof t}}function mt(){if(Z)return R;Z=1;var t=pt();return R=function(r){return"object"==typeof r?null!==r:t(r)}}function dt(){if(D)return z;D=1;var t=i(),r=pt();return z=function(e,n){return arguments.length<2?(o=t[e],r(o)?o:void 0):t[e]&&t[e][n];var o},z}function gt(){if(N)return F;N=1;var t=ut();return F=t({}.isPrototypeOf)}function vt(){if($)return W;$=1;var t,r,e=i(),n=function(){if(B)return U;B=1;var t=i().navigator,r=t&&t.userAgent;return U=r?String(r):""}(),o=e.process,s=e.Deno,a=o&&o.versions||s&&s.version,u=a&&a.v8;return u&&(r=(t=u.split("."))[0]>0&&t[0]<4?1:+(t[0]+t[1])),!r&&n&&(!(t=n.match(/Edge\/(\d+)/))||t[1]>=74)&&(t=n.match(/Chrome\/(\d+)/))&&(r=+t[1]),W=r}function yt(){if(G)return V;G=1;var t=vt(),r=d(),e=i().String;return V=!!Object.getOwnPropertySymbols&&!r(function(){var r=Symbol("symbol detection");return!e(r)||!(Object(r)instanceof Symbol)||!Symbol.sham&&t&&t<41})}function wt(){if(K)return H;K=1;var t=yt();return H=t&&!Symbol.sham&&"symbol"==typeof Symbol.iterator}function bt(){if(X)return Y;X=1;var t=dt(),r=pt(),e=gt(),n=wt(),o=Object;return Y=n?function(t){return"symbol"==typeof t}:function(n){var i=t("Symbol");return r(i)&&e(i.prototype,o(n))}}function Et(){if(Q)return J;Q=1;var t=String;return J=function(r){try{return t(r)}catch(t){return"Object"}}}function kt(){if(rt)return tt;rt=1;var t=pt(),r=Et(),e=TypeError;return tt=function(n){if(t(n))return n;throw new e(r(n)+" is not a function")}}function xt(){if(nt)return et;nt=1;var t=kt(),r=ft();return et=function(e,n){var o=e[n];return r(o)?void 0:t(o)}}function Mt(){if(it)return ot;it=1;var t=y(),r=pt(),e=mt(),n=TypeError;return ot=function(o,i){var s,a;if("string"===i&&r(s=o.toString)&&!e(a=t(s,o)))return a;if(r(s=o.valueOf)&&!e(a=t(s,o)))return a;if("string"!==i&&r(s=o.toString)&&!e(a=t(s,o)))return a;throw new n("Can't convert object to primitive value")}}var Ot,Pt,Ct,St,jt,At,It,_t,Tt,Lt,qt,Rt,Zt,zt,Dt,Ft,Nt,Ut,Bt,Wt,$t,Vt,Gt,Ht,Kt={exports:{}};function Yt(){return Pt?Ot:(Pt=1,Ot=!1)}function Xt(){if(St)return Ct;St=1;var t=i(),r=Object.defineProperty;return Ct=function(e,n){try{r(t,e,{value:n,configurable:!0,writable:!0})}catch(r){t[e]=n}return n}}function Jt(){if(jt)return Kt.exports;jt=1;var t=Yt(),r=i(),e=Xt(),n="__core-js_shared__",o=Kt.exports=r[n]||e(n,{});return(o.versions||(o.versions=[])).push({version:"3.44.0",mode:t?"pure":"global",copyright:"© 2014-2025 Denis Pushkarev (zloirock.ru)",license:"https://github.com/zloirock/core-js/blob/v3.44.0/LICENSE",source:"https://github.com/zloirock/core-js"}),Kt.exports}function Qt(){if(It)return At;It=1;var t=Jt();return At=function(r,e){return t[r]||(t[r]=e||{})}}function tr(){if(Tt)return _t;Tt=1;var t=lt(),r=Object;return _t=function(e){return r(t(e))}}function rr(){if(qt)return Lt;qt=1;var t=ut(),r=tr(),e=t({}.hasOwnProperty);return Lt=Object.hasOwn||function(t,n){return e(r(t),n)}}function er(){if(Zt)return Rt;Zt=1;var t=ut(),r=0,e=Math.random(),n=t(1.1.toString);return Rt=function(t){return"Symbol("+(void 0===t?"":t)+")_"+n(++r+e,36)}}function nr(){if(Dt)return zt;Dt=1;var t=i(),r=Qt(),e=rr(),n=er(),o=yt(),s=wt(),a=t.Symbol,u=r("wks"),c=s?a.for||a:a&&a.withoutSetter||n;return zt=function(t){return e(u,t)||(u[t]=o&&e(a,t)?a[t]:c("Symbol."+t)),u[t]}}function or(){if(Nt)return Ft;Nt=1;var t=y(),r=mt(),e=bt(),n=xt(),o=Mt(),i=nr(),s=TypeError,a=i("toPrimitive");return Ft=function(i,u){if(!r(i)||e(i))return i;var c,f=n(i,a);if(f){if(void 0===u&&(u="default"),c=t(f,i,u),!r(c)||e(c))return c;throw new s("Can't convert object to primitive value")}return void 0===u&&(u="number"),o(i,u)}}function ir(){if(Bt)return Ut;Bt=1;var t=or(),r=bt();return Ut=function(e){var n=t(e,"string");return r(n)?n:n+""}}function sr(){if($t)return Wt;$t=1;var t=i(),r=mt(),e=t.document,n=r(e)&&r(e.createElement);return Wt=function(t){return n?e.createElement(t):{}}}function ar(){if(Gt)return Vt;Gt=1;var t=g(),r=d(),e=sr();return Vt=!t&&!r(function(){return 7!==Object.defineProperty(e("div"),"a",{get:function(){return 7}}).a})}function ur(){if(Ht)return m;Ht=1;var t=g(),r=y(),e=function(){if(w)return st;w=1;var t={}.propertyIsEnumerable,r=Object.getOwnPropertyDescriptor,e=r&&!t.call({1:2},1);return st.f=e?function(t){var e=r(this,t);return!!e&&e.enumerable}:t,st}(),n=at(),o=ht(),i=ir(),s=rr(),a=ar(),u=Object.getOwnPropertyDescriptor;return m.f=t?u:function(t,c){if(t=o(t),c=i(c),a)try{return u(t,c)}catch(t){}if(s(t,c))return n(!r(e.f,t,c),t[c])},m}var cr,fr,lr,hr,pr,mr,dr,gr={};function vr(){if(fr)return cr;fr=1;var t=g(),r=d();return cr=t&&r(function(){return 42!==Object.defineProperty(function(){},"prototype",{value:42,writable:!1}).prototype})}function yr(){if(hr)return lr;hr=1;var t=mt(),r=String,e=TypeError;return lr=function(n){if(t(n))return n;throw new e(r(n)+" is not an object")}}function wr(){if(pr)return gr;pr=1;var t=g(),r=ar(),e=vr(),n=yr(),o=ir(),i=TypeError,s=Object.defineProperty,a=Object.getOwnPropertyDescriptor,u="enumerable",c="configurable",f="writable";return gr.f=t?e?function(t,r,e){if(n(t),r=o(r),n(e),"function"==typeof t&&"prototype"===r&&"value"in e&&f in e&&!e[f]){var i=a(t,r);i&&i[f]&&(t[r]=e.value,e={configurable:c in e?e[c]:i[c],enumerable:u in e?e[u]:i[u],writable:!1})}return s(t,r,e)}:s:function(t,e,a){if(n(t),e=o(e),n(a),r)try{return s(t,e,a)}catch(t){}if("get"in a||"set"in a)throw new i("Accessors not supported");return"value"in a&&(t[e]=a.value),t},gr}function br(){if(dr)return mr;dr=1;var t=g(),r=wr(),e=at();return mr=t?function(t,n,o){return r.f(t,n,e(1,o))}:function(t,r,e){return t[r]=e,t}}var Er,kr,xr,Mr,Or,Pr,Cr,Sr,jr,Ar,Ir,_r,Tr,Lr,qr,Rr={exports:{}};function Zr(){if(Mr)return xr;Mr=1;var t=ut(),r=pt(),e=Jt(),n=t(Function.toString);return r(e.inspectSource)||(e.inspectSource=function(t){return n(t)}),xr=e.inspectSource}function zr(){if(Sr)return Cr;Sr=1;var t=Qt(),r=er(),e=t("keys");return Cr=function(t){return e[t]||(e[t]=r(t))}}function Dr(){return Ar?jr:(Ar=1,jr={})}function Fr(){if(_r)return Ir;_r=1;var t,r,e,n=function(){if(Pr)return Or;Pr=1;var t=i(),r=pt(),e=t.WeakMap;return Or=r(e)&&/native code/.test(String(e))}(),o=i(),s=mt(),a=br(),u=rr(),c=Jt(),f=zr(),l=Dr(),h="Object already initialized",p=o.TypeError,m=o.WeakMap;if(n||c.state){var d=c.state||(c.state=new m);d.get=d.get,d.has=d.has,d.set=d.set,t=function(t,r){if(d.has(t))throw new p(h);return r.facade=t,d.set(t,r),r},r=function(t){return d.get(t)||{}},e=function(t){return d.has(t)}}else{var g=f("state");l[g]=!0,t=function(t,r){if(u(t,g))throw new p(h);return r.facade=t,a(t,g,r),r},r=function(t){return u(t,g)?t[g]:{}},e=function(t){return u(t,g)}}return Ir={set:t,get:r,has:e,enforce:function(n){return e(n)?r(n):t(n,{})},getterFor:function(t){return function(e){var n;if(!s(e)||(n=r(e)).type!==t)throw new p("Incompatible receiver, "+t+" required");return n}}}}function Nr(){if(Tr)return Rr.exports;Tr=1;var t=ut(),r=d(),e=pt(),n=rr(),o=g(),i=function(){if(kr)return Er;kr=1;var t=g(),r=rr(),e=Function.prototype,n=t&&Object.getOwnPropertyDescriptor,o=r(e,"name"),i=o&&"something"===function(){}.name,s=o&&(!t||t&&n(e,"name").configurable);return Er={EXISTS:o,PROPER:i,CONFIGURABLE:s}}().CONFIGURABLE,s=Zr(),a=Fr(),u=a.enforce,c=a.get,f=String,l=Object.defineProperty,h=t("".slice),p=t("".replace),m=t([].join),v=o&&!r(function(){return 8!==l(function(){},"length",{value:8}).length}),y=String(String).split("String"),w=Rr.exports=function(t,r,e){"Symbol("===h(f(r),0,7)&&(r="["+p(f(r),/^Symbol\(([^)]*)\).*$/,"$1")+"]"),e&&e.getter&&(r="get "+r),e&&e.setter&&(r="set "+r),(!n(t,"name")||i&&t.name!==r)&&(o?l(t,"name",{value:r,configurable:!0}):t.name=r),v&&e&&n(e,"arity")&&t.length!==e.arity&&l(t,"length",{value:e.arity});try{e&&n(e,"constructor")&&e.constructor?o&&l(t,"prototype",{writable:!1}):t.prototype&&(t.prototype=void 0)}catch(t){}var s=u(t);return n(s,"source")||(s.source=m(y,"string"==typeof r?r:"")),t};return Function.prototype.toString=w(function(){return e(this)&&c(this).source||s(this)},"toString"),Rr.exports}function Ur(){if(qr)return Lr;qr=1;var t=pt(),r=wr(),e=Nr(),n=Xt();return Lr=function(o,i,s,a){a||(a={});var u=a.enumerable,c=void 0!==a.name?a.name:i;if(t(s)&&e(s,c,a),a.global)u?o[i]=s:n(i,s);else{try{a.unsafe?o[i]&&(u=!0):delete o[i]}catch(t){}u?o[i]=s:r.f(o,i,{value:s,enumerable:!1,configurable:!a.nonConfigurable,writable:!a.nonWritable})}return o}}var Br,Wr,$r,Vr,Gr,Hr,Kr,Yr,Xr,Jr,Qr,te,re,ee,ne,oe,ie,se={};function ae(){if(Vr)return $r;Vr=1;var t=function(){if(Wr)return Br;Wr=1;var t=Math.ceil,r=Math.floor;return Br=Math.trunc||function(e){var n=+e;return(n>0?r:t)(n)}}();return $r=function(r){var e=+r;return e!=e||0===e?0:t(e)}}function ue(){if(Hr)return Gr;Hr=1;var t=ae(),r=Math.max,e=Math.min;return Gr=function(n,o){var i=t(n);return i<0?r(i+o,0):e(i,o)}}function ce(){if(Yr)return Kr;Yr=1;var t=ae(),r=Math.min;return Kr=function(e){var n=t(e);return n>0?r(n,9007199254740991):0}}function fe(){if(Jr)return Xr;Jr=1;var t=ce();return Xr=function(r){return t(r.length)}}function le(){if(ee)return re;ee=1;var t=ut(),r=rr(),e=ht(),n=function(){if(te)return Qr;te=1;var t=ht(),r=ue(),e=fe(),n=function(n){return function(o,i,s){var a=t(o),u=e(a);if(0===u)return!n&&-1;var c,f=r(s,u);if(n&&i!=i){for(;u>f;)if((c=a[f++])!=c)return!0}else for(;u>f;f++)if((n||f in a)&&a[f]===i)return n||f||0;return!n&&-1}};return Qr={includes:n(!0),indexOf:n(!1)}}().indexOf,o=Dr(),i=t([].push);return re=function(t,s){var a,u=e(t),c=0,f=[];for(a in u)!r(o,a)&&r(u,a)&&i(f,a);for(;s.length>c;)r(u,a=s[c++])&&(~n(f,a)||i(f,a));return f}}function he(){return oe?ne:(oe=1,ne=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"])}var pe,me,de,ge,ve,ye,we,be,Ee,ke,xe,Me,Oe,Pe,Ce,Se,je,Ae,Ie,_e={};function Te(){if(de)return me;de=1;var t=dt(),r=ut(),e=function(){if(ie)return se;ie=1;var t=le(),r=he().concat("length","prototype");return se.f=Object.getOwnPropertyNames||function(e){return t(e,r)},se}(),n=(pe||(pe=1,_e.f=Object.getOwnPropertySymbols),_e),o=yr(),i=r([].concat);return me=t("Reflect","ownKeys")||function(t){var r=e.f(o(t)),s=n.f;return s?i(r,s(t)):r},me}function Le(){if(ve)return ge;ve=1;var t=rr(),r=Te(),e=ur(),n=wr();return ge=function(o,i,s){for(var a=r(i),u=n.f,c=e.f,f=0;f<a.length;f++){var l=a[f];t(o,l)||s&&t(s,l)||u(o,l,c(i,l))}},ge}function qe(){if(Ee)return be;Ee=1;var t=i(),r=ur().f,e=br(),n=Ur(),o=Xt(),s=Le(),a=function(){if(we)return ye;we=1;var t=d(),r=pt(),e=/#|\.prototype\./,n=function(e,n){var u=i[o(e)];return u===a||u!==s&&(r(n)?t(n):!!n)},o=n.normalize=function(t){return String(t).replace(e,".").toLowerCase()},i=n.data={},s=n.NATIVE="N",a=n.POLYFILL="P";return ye=n}();return be=function(i,u){var c,f,l,h,p,m=i.target,d=i.global,g=i.stat;if(c=d?t:g?t[m]||o(m,{}):t[m]&&t[m].prototype)for(f in u){if(h=u[f],l=i.dontCallGetSet?(p=r(c,f))&&p.value:c[f],!a(d?f:m+(g?".":"#")+f,i.forced)&&void 0!==l){if(typeof h==typeof l)continue;s(h,l)}(i.sham||l&&l.sham)&&e(h,"sham",!0),n(c,f,h,i)}}}function Re(){if(xe)return ke;xe=1;var t=gt(),r=TypeError;return ke=function(e,n){if(t(n,e))return e;throw new r("Incorrect invocation")}}function Ze(){if(Ce)return Pe;Ce=1;var t=rr(),r=pt(),e=tr(),n=zr(),o=function(){if(Oe)return Me;Oe=1;var t=d();return Me=!t(function(){function t(){}return t.prototype.constructor=null,Object.getPrototypeOf(new t)!==t.prototype})}(),i=n("IE_PROTO"),s=Object,a=s.prototype;return Pe=o?s.getPrototypeOf:function(n){var o=e(n);if(t(o,i))return o[i];var u=o.constructor;return r(u)&&o instanceof u?u.prototype:o instanceof s?a:null}}function ze(){if(je)return Se;je=1;var t=Nr(),r=wr();return Se=function(e,n,o){return o.get&&t(o.get,n,{getter:!0}),o.set&&t(o.set,n,{setter:!0}),r.f(e,n,o)}}function De(){if(Ie)return Ae;Ie=1;var t=g(),r=wr(),e=at();return Ae=function(n,o,i){t?r.f(n,o,e(0,i)):n[o]=i}}var Fe,Ne,Ue,Be,We,$e,Ve,Ge,He,Ke,Ye,Xe={};function Je(){if(Ne)return Fe;Ne=1;var t=le(),r=he();return Fe=Object.keys||function(e){return t(e,r)}}function Qe(){if(We)return Be;We=1;var t=dt();return Be=t("document","documentElement")}function tn(){if(Ve)return $e;Ve=1;var t,r=yr(),e=function(){if(Ue)return Xe;Ue=1;var t=g(),r=vr(),e=wr(),n=yr(),o=ht(),i=Je();return Xe.f=t&&!r?Object.defineProperties:function(t,r){n(t);for(var s,a=o(r),u=i(r),c=u.length,f=0;c>f;)e.f(t,s=u[f++],a[s]);return t},Xe}(),n=he(),o=Dr(),i=Qe(),s=sr(),a=zr(),u="prototype",c="script",f=a("IE_PROTO"),l=function(){},h=function(t){return"<"+c+">"+t+"</"+c+">"},p=function(t){t.write(h("")),t.close();var r=t.parentWindow.Object;return t=null,r},m=function(){try{t=new ActiveXObject("htmlfile")}catch(t){}var r,e,o;m="undefined"!=typeof document?document.domain&&t?p(t):(e=s("iframe"),o="java"+c+":",e.style.display="none",i.appendChild(e),e.src=String(o),(r=e.contentWindow.document).open(),r.write(h("document.F=Object")),r.close(),r.F):p(t);for(var a=n.length;a--;)delete m[u][n[a]];return m()};return o[f]=!0,$e=Object.create||function(t,n){var o;return null!==t?(l[u]=r(t),o=new l,l[u]=null,o[f]=t):o=m(),void 0===n?o:e.f(o,n)}}function rn(){if(He)return Ge;He=1;var t,r,e,n=d(),o=pt(),i=mt(),s=tn(),a=Ze(),u=Ur(),c=nr(),f=Yt(),l=c("iterator"),h=!1;return[].keys&&("next"in(e=[].keys())?(r=a(a(e)))!==Object.prototype&&(t=r):h=!0),!i(t)||n(function(){var r={};return t[l].call(r)!==r})?t={}:f&&(t=s(t)),o(t[l])||u(t,l,function(){return this}),Ge={IteratorPrototype:t,BUGGY_SAFARI_ITERATORS:h}}Ye||(Ye=1,function(){if(Ke)return o;Ke=1;var t=qe(),r=i(),e=Re(),n=yr(),s=pt(),a=Ze(),u=ze(),c=De(),f=d(),l=rr(),h=nr(),p=rn().IteratorPrototype,m=g(),v=Yt(),y="constructor",w="Iterator",b=h("toStringTag"),E=TypeError,k=r[w],x=v||!s(k)||k.prototype!==p||!f(function(){k({})}),M=function(){if(e(this,p),a(this)===p)throw new E("Abstract class Iterator not directly constructable")},O=function(t,r){m?u(p,t,{configurable:!0,get:function(){return r},set:function(r){if(n(this),this===p)throw new E("You can't redefine this property");l(this,t)?this[t]=r:c(this,t,r)}}):p[t]=r};l(p,b)||O(b,w),!x&&l(p,y)&&p[y]!==Object||O(y,M),M.prototype=p,t({global:!0,constructor:!0,forced:x},{Iterator:M})}());var en,nn,on,sn,an,un,cn,fn,ln,hn,pn,mn,dn,gn,vn,yn,wn,bn,En,kn,xn={};function Mn(){return nn?en:(nn=1,en=function(t){return{iterator:t,next:t.next,done:!1}})}function On(){if(sn)return on;sn=1;var t=Ur();return on=function(r,e,n){for(var o in e)t(r,o,e[o],n);return r}}function Pn(){return un?an:(un=1,an=function(t,r){return{value:t,done:r}})}function Cn(){if(fn)return cn;fn=1;var t=y(),r=yr(),e=xt();return cn=function(n,o,i){var s,a;r(n);try{if(!(s=e(n,"return"))){if("throw"===o)throw i;return i}s=t(s,n)}catch(t){a=!0,s=t}if("throw"===o)throw i;if(a)throw s;return r(s),i}}function Sn(){if(hn)return ln;hn=1;var t=Cn();return ln=function(r,e,n){for(var o=r.length-1;o>=0;o--)if(void 0!==r[o])try{n=t(r[o].iterator,e,n)}catch(t){e="throw",n=t}if("throw"===e)throw n;return n}}function jn(){if(mn)return pn;mn=1;var t=y(),r=tn(),e=br(),n=On(),o=nr(),i=Fr(),s=xt(),a=rn().IteratorPrototype,u=Pn(),c=Cn(),f=Sn(),l=o("toStringTag"),h="IteratorHelper",p="WrapForValidIterator",m="normal",d="throw",g=i.set,v=function(e){var o=i.getterFor(e?p:h);return n(r(a),{next:function(){var t=o(this);if(e)return t.nextHandler();if(t.done)return u(void 0,!0);try{var r=t.nextHandler();return t.returnHandlerResult?r:u(r,t.done)}catch(r){throw t.done=!0,r}},return:function(){var r=o(this),n=r.iterator;if(r.done=!0,e){var i=s(n,"return");return i?t(i,n):u(void 0,!0)}if(r.inner)try{c(r.inner.iterator,m)}catch(t){return c(n,d,t)}if(r.openIters)try{f(r.openIters,m)}catch(t){return c(n,d,t)}return n&&c(n,m),u(void 0,!0)}})},w=v(!0),b=v(!1);return e(b,l,"Iterator Helper"),pn=function(t,r,e){var n=function(n,o){o?(o.iterator=n.iterator,o.next=n.next):o=n,o.type=r?p:h,o.returnHandlerResult=!!e,o.nextHandler=t,o.counter=0,o.done=!1,g(this,o)};return n.prototype=r?w:b,n}}function An(){if(gn)return dn;gn=1;var t=yr(),r=Cn();return dn=function(e,n,o,i){try{return i?n(t(o)[0],o[1]):n(o)}catch(t){r(e,"throw",t)}}}function In(){return yn?vn:(yn=1,vn=function(t,r){var e="function"==typeof Iterator&&Iterator.prototype[t];if(e)try{e.call({next:null},r).next()}catch(t){return!0}})}function _n(){if(bn)return wn;bn=1;var t=i();return wn=function(r,e){var n=t.Iterator,o=n&&n.prototype,i=o&&o[r],s=!1;if(i)try{i.call({next:function(){return{done:!0}},return:function(){s=!0}},-1)}catch(t){t instanceof e||(s=!1)}if(!s)return i}}function Tn(t,r){var e={};for(var n in t)Object.prototype.hasOwnProperty.call(t,n)&&r.indexOf(n)<0&&(e[n]=t[n]);if(null!=t&&"function"==typeof Object.getOwnPropertySymbols){var o=0;for(n=Object.getOwnPropertySymbols(t);o<n.length;o++)r.indexOf(n[o])<0&&Object.prototype.propertyIsEnumerable.call(t,n[o])&&(e[n[o]]=t[n[o]])}return e}kn||(kn=1,function(){if(En)return xn;En=1;var t=qe(),r=y(),e=kt(),n=yr(),o=Mn(),i=jn(),s=An(),a=Cn(),u=In(),c=_n(),f=Yt(),l=!f&&!u("map",function(){}),h=!f&&!l&&c("map",TypeError),p=f||l||h,m=i(function(){var t=this.iterator,e=n(r(this.next,t));if(!(this.done=!!e.done))return s(t,this.mapper,[e.value,this.counter++],!0)});t({target:"Iterator",proto:!0,real:!0,forced:p},{map:function(t){n(this);try{e(t)}catch(t){a(this,"throw",t)}return h?r(h,this,t):new m(o(this),{mapper:t})}})}()),"function"==typeof SuppressedError&&SuppressedError;var Ln,qn,Rn={};qn||(qn=1,function(){if(Ln)return Rn;Ln=1;var t=qe(),r=y(),e=kt(),n=yr(),o=Mn(),i=jn(),s=An(),a=Yt(),u=Cn(),c=In(),f=_n(),l=!a&&!c("filter",function(){}),h=!a&&!l&&f("filter",TypeError),p=a||l||h,m=i(function(){for(var t,e,o=this.iterator,i=this.predicate,a=this.next;;){if(t=n(r(a,o)),this.done=!!t.done)return;if(e=t.value,s(o,i,[e,this.counter++],!0))return e}});t({target:"Iterator",proto:!0,real:!0,forced:p},{filter:function(t){n(this);try{e(t)}catch(t){u(this,"throw",t)}return h?r(h,this,t):new m(o(this),{predicate:t})}})}());class Zn{static isAdvancedMarkerAvailable(t){return google.maps.marker&&!0===t.getMapCapabilities().isAdvancedMarkersAvailable}static isAdvancedMarker(t){return google.maps.marker&&t instanceof google.maps.marker.AdvancedMarkerElement}static setMap(t,r){this.isAdvancedMarker(t)?t.map=r:t.setMap(r)}static getPosition(t){if(this.isAdvancedMarker(t)){if(t.position){if(t.position instanceof google.maps.LatLng)return t.position;if(Number.isFinite(t.position.lat)&&Number.isFinite(t.position.lng))return new google.maps.LatLng(t.position.lat,t.position.lng)}return new google.maps.LatLng(null)}return t.getPosition()}static getVisible(t){return!!this.isAdvancedMarker(t)||t.getVisible()}}class zn{constructor(t){let{markers:r,position:e}=t;this.markers=[],r&&(this.markers=r),e&&(e instanceof google.maps.LatLng?this._position=e:this._position=new google.maps.LatLng(e))}get bounds(){if(0===this.markers.length&&!this._position)return;const t=new google.maps.LatLngBounds(this._position,this._position);for(const r of this.markers)t.extend(Zn.getPosition(r));return t}get position(){return this._position||this.bounds.getCenter()}get count(){return this.markers.filter(t=>Zn.getVisible(t)).length}push(t){this.markers.push(t)}delete(){this.marker&&(Zn.setMap(this.marker,null),this.marker=void 0),this.markers.length=0}}function Dn(t){if(null==t)throw Error(arguments.length>1&&void 0!==arguments[1]?arguments[1]:"assertion failed")}const Fn=(t,r,e,n)=>{const o=t.getBounds();Dn(o);const i=Nn(o,r,n);return e.filter(t=>i.contains(Zn.getPosition(t)))},Nn=(t,r,e)=>{const{northEast:n,southWest:o}=Wn(t,r),i=$n({northEast:n,southWest:o},e);return Vn(i,r)},Un=(t,r,e)=>{const n=Nn(t,r,e),o=n.getNorthEast(),i=n.getSouthWest();return[i.lng(),i.lat(),o.lng(),o.lat()]},Bn=(t,r)=>{const e=(r.lat-t.lat)*Math.PI/180,n=(r.lng-t.lng)*Math.PI/180,o=Math.sin(e/2),i=Math.sin(n/2),s=o*o+Math.cos(t.lat*Math.PI/180)*Math.cos(r.lat*Math.PI/180)*i*i;return 6371*(2*Math.atan2(Math.sqrt(s),Math.sqrt(1-s)))},Wn=(t,r)=>{const e=r.fromLatLngToDivPixel(t.getNorthEast()),n=r.fromLatLngToDivPixel(t.getSouthWest());return Dn(e),Dn(n),{northEast:e,southWest:n}},$n=(t,r)=>{let{northEast:e,southWest:n}=t;return e.x+=r,e.y-=r,n.x-=r,n.y+=r,{northEast:e,southWest:n}},Vn=(t,r)=>{let{northEast:e,southWest:n}=t;const o=r.fromDivPixelToLatLng(n),i=r.fromDivPixelToLatLng(e);return new google.maps.LatLngBounds(o,i)};class Gn{constructor(t){let{maxZoom:r=16}=t;this.maxZoom=r}noop(t){let{markers:r}=t;return Kn(r)}}class Hn extends Gn{constructor(t){var{viewportPadding:r=60}=t;super(Tn(t,["viewportPadding"])),this.viewportPadding=60,this.viewportPadding=r}calculate(t){let{markers:r,map:e,mapCanvasProjection:n}=t;const o=e.getZoom();return Dn(o),o>=this.maxZoom?{clusters:this.noop({markers:r}),changed:!1}:{clusters:this.cluster({markers:Fn(e,n,r,this.viewportPadding),map:e,mapCanvasProjection:n})}}}const Kn=t=>t.map(t=>new zn({position:Zn.getPosition(t),markers:[t]}));var Yn,Xn,Jn,Qn,to,ro,eo,no,oo,io,so,ao,uo,co,fo,lo,ho,po,mo,go,vo={};function yo(){if(Qn)return Jn;Qn=1;var t=function(){if(Xn)return Yn;Xn=1;var t=ct(),r=ut();return Yn=function(e){if("Function"===t(e))return r(e)}}(),r=kt(),e=v(),n=t(t.bind);return Jn=function(t,o){return r(t),void 0===o?t:e?n(t,o):function(){return t.apply(o,arguments)}},Jn}function wo(){return ro?to:(ro=1,to={})}function bo(){if(no)return eo;no=1;var t=nr(),r=wo(),e=t("iterator"),n=Array.prototype;return eo=function(t){return void 0!==t&&(r.Array===t||n[e]===t)}}function Eo(){if(ao)return so;ao=1;var t=function(){if(io)return oo;io=1;var t={};return t[nr()("toStringTag")]="z",oo="[object z]"===String(t)}(),r=pt(),e=ct(),n=nr()("toStringTag"),o=Object,i="Arguments"===e(function(){return arguments}());return so=t?e:function(t){var s,a,u;return void 0===t?"Undefined":null===t?"Null":"string"==typeof(a=function(t,r){try{return t[r]}catch(t){}}(s=o(t),n))?a:i?e(s):"Object"===(u=e(s))&&r(s.callee)?"Arguments":u}}function ko(){if(co)return uo;co=1;var t=Eo(),r=xt(),e=ft(),n=wo(),o=nr()("iterator");return uo=function(i){if(!e(i))return r(i,o)||r(i,"@@iterator")||n[t(i)]}}function xo(){if(lo)return fo;lo=1;var t=y(),r=kt(),e=yr(),n=Et(),o=ko(),i=TypeError;return fo=function(s,a){var u=arguments.length<2?o(s):a;if(r(u))return e(t(u,s));throw new i(n(s)+" is not iterable")},fo}function Mo(){if(po)return ho;po=1;var t=yo(),r=y(),e=yr(),n=Et(),o=bo(),i=fe(),s=gt(),a=xo(),u=ko(),c=Cn(),f=TypeError,l=function(t,r){this.stopped=t,this.result=r},h=l.prototype;return ho=function(p,m,d){var g,v,y,w,b,E,k,x=d&&d.that,M=!(!d||!d.AS_ENTRIES),O=!(!d||!d.IS_RECORD),P=!(!d||!d.IS_ITERATOR),C=!(!d||!d.INTERRUPTED),S=t(m,x),j=function(t){return g&&c(g,"normal"),new l(!0,t)},A=function(t){return M?(e(t),C?S(t[0],t[1],j):S(t[0],t[1])):C?S(t,j):S(t)};if(O)g=p.iterator;else if(P)g=p;else{if(!(v=u(p)))throw new f(n(p)+" is not iterable");if(o(v)){for(y=0,w=i(p);w>y;y++)if((b=A(p[y]))&&s(h,b))return b;return new l(!1)}g=a(p,v)}for(E=O?p.next:g.next;!(k=r(E,g)).done;){try{b=A(k.value)}catch(t){c(g,"throw",t)}if("object"==typeof b&&b&&s(h,b))return b}return new l(!1)}}go||(go=1,function(){if(mo)return vo;mo=1;var t=qe(),r=y(),e=Mo(),n=kt(),o=yr(),i=Mn(),s=Cn(),a=_n()("forEach",TypeError);t({target:"Iterator",proto:!0,real:!0,forced:a},{forEach:function(t){o(this);try{n(t)}catch(t){s(this,"throw",t)}if(a)return r(a,this,t);var u=i(this),c=0;e(u,function(r){t(r,c++)},{IS_RECORD:!0})}})}());var Oo=Object.getOwnPropertyNames,Po=Object.getOwnPropertySymbols,Co=Object.prototype.hasOwnProperty;function So(t,r){return function(e,n,o){return t(e,n,o)&&r(e,n,o)}}function jo(t){return function(r,e,n){if(!r||!e||"object"!=typeof r||"object"!=typeof e)return t(r,e,n);var o=n.cache,i=o.get(r),s=o.get(e);if(i&&s)return i===e&&s===r;o.set(r,e),o.set(e,r);var a=t(r,e,n);return o.delete(r),o.delete(e),a}}function Ao(t){return Oo(t).concat(Po(t))}var Io=Object.hasOwn||function(t,r){return Co.call(t,r)};function _o(t,r){return t===r||!t&&!r&&t!=t&&r!=r}var To=Object.getOwnPropertyDescriptor,Lo=Object.keys;function qo(t,r,e){var n=t.length;if(r.length!==n)return!1;for(;n-- >0;)if(!e.equals(t[n],r[n],n,n,t,r,e))return!1;return!0}function Ro(t,r){return _o(t.getTime(),r.getTime())}function Zo(t,r){return t.name===r.name&&t.message===r.message&&t.cause===r.cause&&t.stack===r.stack}function zo(t,r){return t===r}function Do(t,r,e){var n=t.size;if(n!==r.size)return!1;if(!n)return!0;for(var o,i,s=new Array(n),a=t.entries(),u=0;(o=a.next())&&!o.done;){for(var c=r.entries(),f=!1,l=0;(i=c.next())&&!i.done;)if(s[l])l++;else{var h=o.value,p=i.value;if(e.equals(h[0],p[0],u,l,t,r,e)&&e.equals(h[1],p[1],h[0],p[0],t,r,e)){f=s[l]=!0;break}l++}if(!f)return!1;u++}return!0}var Fo=_o;function No(t,r,e){var n=Lo(t),o=n.length;if(Lo(r).length!==o)return!1;for(;o-- >0;)if(!Ho(t,r,e,n[o]))return!1;return!0}function Uo(t,r,e){var n,o,i,s=Ao(t),a=s.length;if(Ao(r).length!==a)return!1;for(;a-- >0;){if(!Ho(t,r,e,n=s[a]))return!1;if(o=To(t,n),i=To(r,n),(o||i)&&(!o||!i||o.configurable!==i.configurable||o.enumerable!==i.enumerable||o.writable!==i.writable))return!1}return!0}function Bo(t,r){return _o(t.valueOf(),r.valueOf())}function Wo(t,r){return t.source===r.source&&t.flags===r.flags}function $o(t,r,e){var n=t.size;if(n!==r.size)return!1;if(!n)return!0;for(var o,i,s=new Array(n),a=t.values();(o=a.next())&&!o.done;){for(var u=r.values(),c=!1,f=0;(i=u.next())&&!i.done;){if(!s[f]&&e.equals(o.value,i.value,o.value,i.value,t,r,e)){c=s[f]=!0;break}f++}if(!c)return!1}return!0}function Vo(t,r){var e=t.length;if(r.length!==e)return!1;for(;e-- >0;)if(t[e]!==r[e])return!1;return!0}function Go(t,r){return t.hostname===r.hostname&&t.pathname===r.pathname&&t.protocol===r.protocol&&t.port===r.port&&t.hash===r.hash&&t.username===r.username&&t.password===r.password}function Ho(t,r,e,n){return!("_owner"!==n&&"__o"!==n&&"__v"!==n||!t.$$typeof&&!r.$$typeof)||Io(r,n)&&e.equals(t[n],r[n],n,n,t,r,e)}var Ko=Array.isArray,Yo="function"==typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView:null,Xo=Object.assign,Jo=Object.prototype.toString.call.bind(Object.prototype.toString);var Qo=ti();function ti(t){void 0===t&&(t={});var r,e=t.circular,n=void 0!==e&&e,o=t.createInternalComparator,i=t.createState,s=t.strict,a=void 0!==s&&s,u=function(t){var r=t.circular,e=t.createCustomConfig,n=t.strict,o={areArraysEqual:n?Uo:qo,areDatesEqual:Ro,areErrorsEqual:Zo,areFunctionsEqual:zo,areMapsEqual:n?So(Do,Uo):Do,areNumbersEqual:Fo,areObjectsEqual:n?Uo:No,arePrimitiveWrappersEqual:Bo,areRegExpsEqual:Wo,areSetsEqual:n?So($o,Uo):$o,areTypedArraysEqual:n?Uo:Vo,areUrlsEqual:Go};if(e&&(o=Xo({},o,e(o))),r){var i=jo(o.areArraysEqual),s=jo(o.areMapsEqual),a=jo(o.areObjectsEqual),u=jo(o.areSetsEqual);o=Xo({},o,{areArraysEqual:i,areMapsEqual:s,areObjectsEqual:a,areSetsEqual:u})}return o}(t),c=function(t){var r=t.areArraysEqual,e=t.areDatesEqual,n=t.areErrorsEqual,o=t.areFunctionsEqual,i=t.areMapsEqual,s=t.areNumbersEqual,a=t.areObjectsEqual,u=t.arePrimitiveWrappersEqual,c=t.areRegExpsEqual,f=t.areSetsEqual,l=t.areTypedArraysEqual,h=t.areUrlsEqual;return function(t,p,m){if(t===p)return!0;if(null==t||null==p)return!1;var d=typeof t;if(d!==typeof p)return!1;if("object"!==d)return"number"===d?s(t,p,m):"function"===d&&o(t,p,m);var g=t.constructor;if(g!==p.constructor)return!1;if(g===Object)return a(t,p,m);if(Ko(t))return r(t,p,m);if(null!=Yo&&Yo(t))return l(t,p,m);if(g===Date)return e(t,p,m);if(g===RegExp)return c(t,p,m);if(g===Map)return i(t,p,m);if(g===Set)return f(t,p,m);var v=Jo(t);return"[object Date]"===v?e(t,p,m):"[object RegExp]"===v?c(t,p,m):"[object Map]"===v?i(t,p,m):"[object Set]"===v?f(t,p,m):"[object Object]"===v?"function"!=typeof t.then&&"function"!=typeof p.then&&a(t,p,m):"[object URL]"===v?h(t,p,m):"[object Error]"===v?n(t,p,m):"[object Arguments]"===v?a(t,p,m):("[object Boolean]"===v||"[object Number]"===v||"[object String]"===v)&&u(t,p,m)}}(u);return function(t){var r=t.circular,e=t.comparator,n=t.createState,o=t.equals,i=t.strict;if(n)return function(t,s){var a=n(),u=a.cache,c=void 0===u?r?new WeakMap:void 0:u,f=a.meta;return e(t,s,{cache:c,equals:o,meta:f,strict:i})};if(r)return function(t,r){return e(t,r,{cache:new WeakMap,equals:o,meta:void 0,strict:i})};var s={cache:void 0,equals:o,meta:void 0,strict:i};return function(t,r){return e(t,r,s)}}({circular:n,comparator:c,createState:i,equals:o?o(c):(r=c,function(t,e,n,o,i,s,a){return r(t,e,a)}),strict:a})}ti({strict:!0}),ti({circular:!0}),ti({circular:!0,strict:!0}),ti({createInternalComparator:function(){return _o}}),ti({strict:!0,createInternalComparator:function(){return _o}}),ti({circular:!0,createInternalComparator:function(){return _o}}),ti({circular:!0,createInternalComparator:function(){return _o},strict:!0});const ri=[Int8Array,Uint8Array,Uint8ClampedArray,Int16Array,Uint16Array,Int32Array,Uint32Array,Float32Array,Float64Array];class ei{static from(t){if(!(t instanceof ArrayBuffer))throw new Error("Data must be an instance of ArrayBuffer.");const[r,e]=new Uint8Array(t,0,2);if(219!==r)throw new Error("Data does not appear to be in a KDBush format.");const n=e>>4;if(1!==n)throw new Error(`Got v${n} data when expected v1.`);const o=ri[15&e];if(!o)throw new Error("Unrecognized array type.");const[i]=new Uint16Array(t,2,1),[s]=new Uint32Array(t,4,1);return new ei(s,i,o,t)}constructor(t,r=64,e=Float64Array,n){if(isNaN(t)||t<0)throw new Error(`Unpexpected numItems value: ${t}.`);this.numItems=+t,this.nodeSize=Math.min(Math.max(+r,2),65535),this.ArrayType=e,this.IndexArrayType=t<65536?Uint16Array:Uint32Array;const o=ri.indexOf(this.ArrayType),i=2*t*this.ArrayType.BYTES_PER_ELEMENT,s=t*this.IndexArrayType.BYTES_PER_ELEMENT,a=(8-s%8)%8;if(o<0)throw new Error(`Unexpected typed array class: ${e}.`);n&&n instanceof ArrayBuffer?(this.data=n,this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+s+a,2*t),this._pos=2*t,this._finished=!0):(this.data=new ArrayBuffer(8+i+s+a),this.ids=new this.IndexArrayType(this.data,8,t),this.coords=new this.ArrayType(this.data,8+s+a,2*t),this._pos=0,this._finished=!1,new Uint8Array(this.data,0,2).set([219,16+o]),new Uint16Array(this.data,2,1)[0]=r,new Uint32Array(this.data,4,1)[0]=t)}add(t,r){const e=this._pos>>1;return this.ids[e]=e,this.coords[this._pos++]=t,this.coords[this._pos++]=r,e}finish(){const t=this._pos>>1;if(t!==this.numItems)throw new Error(`Added ${t} items when expected ${this.numItems}.`);return ni(this.ids,this.coords,this.nodeSize,0,this.numItems-1,0),this._finished=!0,this}range(t,r,e,n){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:o,coords:i,nodeSize:s}=this,a=[0,o.length-1,0],u=[];for(;a.length;){const c=a.pop()||0,f=a.pop()||0,l=a.pop()||0;if(f-l<=s){for(let s=l;s<=f;s++){const a=i[2*s],c=i[2*s+1];a>=t&&a<=e&&c>=r&&c<=n&&u.push(o[s])}continue}const h=l+f>>1,p=i[2*h],m=i[2*h+1];p>=t&&p<=e&&m>=r&&m<=n&&u.push(o[h]),(0===c?t<=p:r<=m)&&(a.push(l),a.push(h-1),a.push(1-c)),(0===c?e>=p:n>=m)&&(a.push(h+1),a.push(f),a.push(1-c))}return u}within(t,r,e){if(!this._finished)throw new Error("Data not yet indexed - call index.finish().");const{ids:n,coords:o,nodeSize:i}=this,s=[0,n.length-1,0],a=[],u=e*e;for(;s.length;){const c=s.pop()||0,f=s.pop()||0,l=s.pop()||0;if(f-l<=i){for(let e=l;e<=f;e++)ai(o[2*e],o[2*e+1],t,r)<=u&&a.push(n[e]);continue}const h=l+f>>1,p=o[2*h],m=o[2*h+1];ai(p,m,t,r)<=u&&a.push(n[h]),(0===c?t-e<=p:r-e<=m)&&(s.push(l),s.push(h-1),s.push(1-c)),(0===c?t+e>=p:r+e>=m)&&(s.push(h+1),s.push(f),s.push(1-c))}return a}}function ni(t,r,e,n,o,i){if(o-n<=e)return;const s=n+o>>1;oi(t,r,s,n,o,i),ni(t,r,e,n,s-1,1-i),ni(t,r,e,s+1,o,1-i)}function oi(t,r,e,n,o,i){for(;o>n;){if(o-n>600){const s=o-n+1,a=e-n+1,u=Math.log(s),c=.5*Math.exp(2*u/3),f=.5*Math.sqrt(u*c*(s-c)/s)*(a-s/2<0?-1:1);oi(t,r,e,Math.max(n,Math.floor(e-a*c/s+f)),Math.min(o,Math.floor(e+(s-a)*c/s+f)),i)}const s=r[2*e+i];let a=n,u=o;for(ii(t,r,n,e),r[2*o+i]>s&&ii(t,r,n,o);a<u;){for(ii(t,r,a,u),a++,u--;r[2*a+i]<s;)a++;for(;r[2*u+i]>s;)u--}r[2*n+i]===s?ii(t,r,n,u):(u++,ii(t,r,u,o)),u<=e&&(n=u+1),e<=u&&(o=u-1)}}function ii(t,r,e,n){si(t,e,n),si(r,2*e,2*n),si(r,2*e+1,2*n+1)}function si(t,r,e){const n=t[r];t[r]=t[e],t[e]=n}function ai(t,r,e,n){const o=t-e,i=r-n;return o*o+i*i}const ui={minZoom:0,maxZoom:16,minPoints:2,radius:40,extent:512,nodeSize:64,log:!1,generateId:!1,reduce:null,map:t=>t},ci=Math.fround||(fi=new Float32Array(1),t=>(fi[0]=+t,fi[0]));var fi;class li{constructor(t){this.options=Object.assign(Object.create(ui),t),this.trees=new Array(this.options.maxZoom+1),this.stride=this.options.reduce?7:6,this.clusterProps=[]}load(t){const{log:r,minZoom:e,maxZoom:n}=this.options;r&&console.time("total time");const o=`prepare ${t.length} points`;r&&console.time(o),this.points=t;const i=[];for(let r=0;r<t.length;r++){const e=t[r];if(!e.geometry)continue;const[n,o]=e.geometry.coordinates,s=ci(mi(n)),a=ci(di(o));i.push(s,a,1/0,r,-1,1),this.options.reduce&&i.push(0)}let s=this.trees[n+1]=this._createTree(i);r&&console.timeEnd(o);for(let t=n;t>=e;t--){const e=+Date.now();s=this.trees[t]=this._createTree(this._cluster(s,t)),r&&console.log("z%d: %d clusters in %dms",t,s.numItems,+Date.now()-e)}return r&&console.timeEnd("total time"),this}getClusters(t,r){let e=((t[0]+180)%360+360)%360-180;const n=Math.max(-90,Math.min(90,t[1]));let o=180===t[2]?180:((t[2]+180)%360+360)%360-180;const i=Math.max(-90,Math.min(90,t[3]));if(t[2]-t[0]>=360)e=-180,o=180;else if(e>o){const t=this.getClusters([e,n,180,i],r),s=this.getClusters([-180,n,o,i],r);return t.concat(s)}const s=this.trees[this._limitZoom(r)],a=s.range(mi(e),di(i),mi(o),di(n)),u=s.data,c=[];for(const t of a){const r=this.stride*t;c.push(u[r+5]>1?hi(u,r,this.clusterProps):this.points[u[r+3]])}return c}getChildren(t){const r=this._getOriginId(t),e=this._getOriginZoom(t),n="No cluster with the specified id.",o=this.trees[e];if(!o)throw new Error(n);const i=o.data;if(r*this.stride>=i.length)throw new Error(n);const s=this.options.radius/(this.options.extent*Math.pow(2,e-1)),a=i[r*this.stride],u=i[r*this.stride+1],c=o.within(a,u,s),f=[];for(const r of c){const e=r*this.stride;i[e+4]===t&&f.push(i[e+5]>1?hi(i,e,this.clusterProps):this.points[i[e+3]])}if(0===f.length)throw new Error(n);return f}getLeaves(t,r,e){r=r||10,e=e||0;const n=[];return this._appendLeaves(n,t,r,e,0),n}getTile(t,r,e){const n=this.trees[this._limitZoom(t)],o=Math.pow(2,t),{extent:i,radius:s}=this.options,a=s/i,u=(e-a)/o,c=(e+1+a)/o,f={features:[]};return this._addTileFeatures(n.range((r-a)/o,u,(r+1+a)/o,c),n.data,r,e,o,f),0===r&&this._addTileFeatures(n.range(1-a/o,u,1,c),n.data,o,e,o,f),r===o-1&&this._addTileFeatures(n.range(0,u,a/o,c),n.data,-1,e,o,f),f.features.length?f:null}getClusterExpansionZoom(t){let r=this._getOriginZoom(t)-1;for(;r<=this.options.maxZoom;){const e=this.getChildren(t);if(r++,1!==e.length)break;t=e[0].properties.cluster_id}return r}_appendLeaves(t,r,e,n,o){const i=this.getChildren(r);for(const r of i){const i=r.properties;if(i&&i.cluster?o+i.point_count<=n?o+=i.point_count:o=this._appendLeaves(t,i.cluster_id,e,n,o):o<n?o++:t.push(r),t.length===e)break}return o}_createTree(t){const r=new ei(t.length/this.stride|0,this.options.nodeSize,Float32Array);for(let e=0;e<t.length;e+=this.stride)r.add(t[e],t[e+1]);return r.finish(),r.data=t,r}_addTileFeatures(t,r,e,n,o,i){for(const s of t){const t=s*this.stride,a=r[t+5]>1;let u,c,f;if(a)u=pi(r,t,this.clusterProps),c=r[t],f=r[t+1];else{const e=this.points[r[t+3]];u=e.properties;const[n,o]=e.geometry.coordinates;c=mi(n),f=di(o)}const l={type:1,geometry:[[Math.round(this.options.extent*(c*o-e)),Math.round(this.options.extent*(f*o-n))]],tags:u};let h;h=a||this.options.generateId?r[t+3]:this.points[r[t+3]].id,void 0!==h&&(l.id=h),i.features.push(l)}}_limitZoom(t){return Math.max(this.options.minZoom,Math.min(Math.floor(+t),this.options.maxZoom+1))}_cluster(t,r){const{radius:e,extent:n,reduce:o,minPoints:i}=this.options,s=e/(n*Math.pow(2,r)),a=t.data,u=[],c=this.stride;for(let e=0;e<a.length;e+=c){if(a[e+2]<=r)continue;a[e+2]=r;const n=a[e],f=a[e+1],l=t.within(a[e],a[e+1],s),h=a[e+5];let p=h;for(const t of l){const e=t*c;a[e+2]>r&&(p+=a[e+5])}if(p>h&&p>=i){let t,i=n*h,s=f*h,m=-1;const d=(e/c<<5)+(r+1)+this.points.length;for(const n of l){const u=n*c;if(a[u+2]<=r)continue;a[u+2]=r;const f=a[u+5];i+=a[u]*f,s+=a[u+1]*f,a[u+4]=d,o&&(t||(t=this._map(a,e,!0),m=this.clusterProps.length,this.clusterProps.push(t)),o(t,this._map(a,u)))}a[e+4]=d,u.push(i/p,s/p,1/0,d,-1,p),o&&u.push(m)}else{for(let t=0;t<c;t++)u.push(a[e+t]);if(p>1)for(const t of l){const e=t*c;if(!(a[e+2]<=r)){a[e+2]=r;for(let t=0;t<c;t++)u.push(a[e+t])}}}}return u}_getOriginId(t){return t-this.points.length>>5}_getOriginZoom(t){return(t-this.points.length)%32}_map(t,r,e){if(t[r+5]>1){const n=this.clusterProps[t[r+6]];return e?Object.assign({},n):n}const n=this.points[t[r+3]].properties,o=this.options.map(n);return e&&o===n?Object.assign({},o):o}}function hi(t,r,e){return{type:"Feature",id:t[r+3],properties:pi(t,r,e),geometry:{type:"Point",coordinates:[(n=t[r],360*(n-.5)),gi(t[r+1])]}};var n}function pi(t,r,e){const n=t[r+5],o=n>=1e4?`${Math.round(n/1e3)}k`:n>=1e3?Math.round(n/100)/10+"k":n,i=t[r+6],s=-1===i?{}:Object.assign({},e[i]);return Object.assign(s,{cluster:!0,cluster_id:t[r+3],point_count:n,point_count_abbreviated:o})}function mi(t){return t/360+.5}function di(t){const r=Math.sin(t*Math.PI/180),e=.5-.25*Math.log((1+r)/(1-r))/Math.PI;return e<0?0:e>1?1:e}function gi(t){const r=(180-360*t)*Math.PI/180;return 360*Math.atan(Math.exp(r))/Math.PI-90}class vi extends Gn{constructor(t){var{maxZoom:r,radius:e=60}=t,n=Tn(t,["maxZoom","radius"]);super({maxZoom:r}),this.markers=[],this.clusters=[],this.state={zoom:-1},this.superCluster=new li(Object.assign({maxZoom:this.maxZoom,radius:e},n))}calculate(t){let r=!1,e=t.map.getZoom();Dn(e),e=Math.round(e);const n={zoom:e};if(!Qo(t.markers,this.markers)){r=!0,this.markers=[...t.markers];const e=this.markers.map(t=>{const r=Zn.getPosition(t);return{type:"Feature",geometry:{type:"Point",coordinates:[r.lng(),r.lat()]},properties:{marker:t}}});this.superCluster.load(e)}return r||(this.state.zoom<=this.maxZoom||n.zoom<=this.maxZoom)&&(r=!Qo(this.state,n)),this.state=n,0===t.markers.length?(this.clusters=[],{clusters:this.clusters,changed:r}):(r&&(this.clusters=this.cluster(t)),{clusters:this.clusters,changed:r})}cluster(t){let{map:r}=t;const e=r.getZoom();return Dn(e),this.superCluster.getClusters([-180,-90,180,90],Math.round(e)).map(t=>this.transformCluster(t))}transformCluster(t){let{geometry:{coordinates:[r,e]},properties:n}=t;if(n.cluster)return new zn({markers:this.superCluster.getLeaves(n.cluster_id,1/0).map(t=>t.properties.marker),position:{lat:e,lng:r}});const o=n.marker;return new zn({markers:[o],position:Zn.getPosition(o)})}}var yi,wi,bi,Ei,ki,xi={};function Mi(){if(bi)return xi;bi=1;var t=qe(),r=Mo(),e=kt(),n=yr(),o=Mn(),i=Cn(),s=_n(),a=function(){if(wi)return yi;wi=1;var t=v(),r=Function.prototype,e=r.apply,n=r.call;return yi="object"==typeof Reflect&&Reflect.apply||(t?n.bind(e):function(){return n.apply(e,arguments)}),yi}(),u=d(),c=TypeError,f=u(function(){[].keys().reduce(function(){},void 0)}),l=!f&&s("reduce",c);return t({target:"Iterator",proto:!0,real:!0,forced:f||l},{reduce:function(t){n(this);try{e(t)}catch(t){i(this,"throw",t)}var s=arguments.length<2,u=s?void 0:arguments[1];if(l)return a(l,this,s?[t]:[t,u]);var f=o(this),h=0;if(r(f,function(r){s?(s=!1,u=r):u=t(u,r,h),h++},{IS_RECORD:!0}),s)throw new c("Reduce of empty iterator with no initial value");return u}}),xi}Ei||(Ei=1,Mi());class Oi{constructor(t,r){this.markers={sum:t.length};const e=r.map(t=>t.count),n=e.reduce((t,r)=>t+r,0);this.clusters={count:r.length,markers:{mean:n/r.length,sum:n,min:Math.min(...e),max:Math.max(...e)}}}}class Pi{render(t,r,e){let{count:n,position:o}=t;const i=`<svg fill="${n>Math.max(10,r.clusters.markers.mean)?"#ff0000":"#0000ff"}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 240 240" width="50" height="50">\n<circle cx="120" cy="120" opacity=".6" r="70" />\n<circle cx="120" cy="120" opacity=".3" r="90" />\n<circle cx="120" cy="120" opacity=".2" r="110" />\n<text x="50%" y="50%" style="fill:#fff" text-anchor="middle" font-size="50" dominant-baseline="middle" font-family="roboto,arial,sans-serif">${n}</text>\n</svg>`,s=`Cluster of ${n} markers`,a=Number(google.maps.Marker.MAX_ZINDEX)+n;if(Zn.isAdvancedMarkerAvailable(e)){const t=(new DOMParser).parseFromString(i,"image/svg+xml").documentElement;t.setAttribute("transform","translate(0 25)");const r={map:e,position:o,zIndex:a,title:s,content:t};return new google.maps.marker.AdvancedMarkerElement(r)}const u={position:o,zIndex:a,title:s,icon:{url:`data:image/svg+xml;base64,${btoa(i)}`,anchor:new google.maps.Point(25,25)}};return new google.maps.Marker(u)}}class Ci{constructor(){!function(t,r){for(let e in r.prototype)t.prototype[e]=r.prototype[e]}(Ci,google.maps.OverlayView)}}t.MarkerClustererEvents=void 0,(ki=t.MarkerClustererEvents||(t.MarkerClustererEvents={})).CLUSTERING_BEGIN="clusteringbegin",ki.CLUSTERING_END="clusteringend",ki.CLUSTER_CLICK="click",ki.GMP_CLICK="gmp-click";const Si=(t,r,e)=>{r.bounds&&e.fitBounds(r.bounds)};return t.AbstractAlgorithm=Gn,t.AbstractViewportAlgorithm=Hn,t.Cluster=zn,t.ClusterStats=Oi,t.DefaultRenderer=Pi,t.GridAlgorithm=class extends Hn{constructor(t){var{maxDistance:r=4e4,gridSize:e=40}=t;super(Tn(t,["maxDistance","gridSize"])),this.clusters=[],this.state={zoom:-1},this.maxDistance=r,this.gridSize=e}calculate(t){let{markers:r,map:e,mapCanvasProjection:n}=t;const o=e.getZoom();Dn(o);const i={zoom:o};let s=!1;return this.state.zoom>=this.maxZoom&&i.zoom>=this.maxZoom||(s=!Qo(this.state,i)),this.state=i,o>=this.maxZoom?{clusters:this.noop({markers:r}),changed:s}:{clusters:this.cluster({markers:Fn(e,n,r,this.viewportPadding),map:e,mapCanvasProjection:n})}}cluster(t){let{markers:r,map:e,mapCanvasProjection:n}=t;return this.clusters=[],r.forEach(t=>{this.addToClosestCluster(t,e,n)}),this.clusters}addToClosestCluster(t,r,e){let n=this.maxDistance,o=null;for(let r=0;r<this.clusters.length;r++){const e=this.clusters[r];Dn(e.bounds);const i=Bn(e.bounds.getCenter().toJSON(),Zn.getPosition(t).toJSON());i<n&&(n=i,o=e)}if(o)if(Dn(o.bounds),Nn(o.bounds,e,this.gridSize).contains(Zn.getPosition(t)))o.push(t);else{const r=new zn({markers:[t]});this.clusters.push(r)}else{const r=new zn({markers:[t]});this.clusters.push(r)}}},t.MarkerClusterer=class extends Ci{constructor(t){let{map:r,markers:e=[],algorithmOptions:n={},algorithm:o=new vi(n),renderer:i=new Pi,onClusterClick:s=Si}=t;super(),this.map=null,this.idleListener=null,this.markers=[...e],this.clusters=[],this.algorithm=o,this.renderer=i,this.onClusterClick=s,r&&this.setMap(r)}addMarker(t,r){this.markers.includes(t)||(this.markers.push(t),r||this.render())}addMarkers(t,r){t.forEach(t=>{this.addMarker(t,!0)}),r||this.render()}removeMarker(t,r){const e=this.markers.indexOf(t);return-1!==e&&(Zn.setMap(t,null),this.markers.splice(e,1),r||this.render(),!0)}removeMarkers(t,r){let e=!1;return t.forEach(t=>{e=this.removeMarker(t,!0)||e}),e&&!r&&this.render(),e}clearMarkers(t){this.markers.length=0,t||this.render()}render(){const r=this.getMap();if(r instanceof google.maps.Map&&r.getProjection()){google.maps.event.trigger(this,t.MarkerClustererEvents.CLUSTERING_BEGIN,this);const{clusters:e,changed:n}=this.algorithm.calculate({markers:this.markers,map:r,mapCanvasProjection:this.getProjection()});if(n||null==n){const t=new Set;for(const r of e)1==r.markers.length&&t.add(r.markers[0]);const r=[];for(const e of this.clusters)null!=e.marker&&(1==e.markers.length?t.has(e.marker)||Zn.setMap(e.marker,null):r.push(e.marker));this.clusters=e,this.renderClusters(),requestAnimationFrame(()=>r.forEach(t=>Zn.setMap(t,null)))}google.maps.event.trigger(this,t.MarkerClustererEvents.CLUSTERING_END,this)}}onAdd(){const t=this.getMap();Dn(t),this.idleListener=t.addListener("idle",this.render.bind(this)),this.render()}onRemove(){this.idleListener&&google.maps.event.removeListener(this.idleListener),this.reset()}reset(){this.markers.forEach(t=>Zn.setMap(t,null)),this.clusters.forEach(t=>t.delete()),this.clusters=[]}renderClusters(){const r=new Oi(this.markers,this.clusters),e=this.getMap();this.clusters.forEach(n=>{if(1===n.markers.length)n.marker=n.markers[0];else if(n.marker=this.renderer.render(n,r,e),n.markers.forEach(t=>Zn.setMap(t,null)),this.onClusterClick){const r=Zn.isAdvancedMarker(n.marker)?t.MarkerClustererEvents.GMP_CLICK:t.MarkerClustererEvents.CLUSTER_CLICK;n.marker.addListener(r,r=>{google.maps.event.trigger(this,t.MarkerClustererEvents.CLUSTER_CLICK,n),this.onClusterClick(r,n,e)})}Zn.setMap(n.marker,e)})}},t.MarkerUtils=Zn,t.NoopAlgorithm=class extends Gn{constructor(t){super(Tn(t,[]))}calculate(t){let{markers:r,map:e,mapCanvasProjection:n}=t;return{clusters:this.cluster({markers:r,map:e,mapCanvasProjection:n}),changed:!1}}cluster(t){return this.noop(t)}},t.SuperClusterAlgorithm=vi,t.SuperClusterViewportAlgorithm=class extends Hn{constructor(t){var{maxZoom:r,radius:e=60,viewportPadding:n=60}=t,o=Tn(t,["maxZoom","radius","viewportPadding"]);super({maxZoom:r,viewportPadding:n}),this.markers=[],this.clusters=[],this.superCluster=new li(Object.assign({maxZoom:this.maxZoom,radius:e},o)),this.state={zoom:-1,view:[0,0,0,0]}}calculate(t){const r=this.getViewportState(t);let e=!Qo(this.state,r);if(!Qo(t.markers,this.markers)){e=!0,this.markers=[...t.markers];const r=this.markers.map(t=>{const r=Zn.getPosition(t);return{type:"Feature",geometry:{type:"Point",coordinates:[r.lng(),r.lat()]},properties:{marker:t}}});this.superCluster.load(r)}return e&&(this.clusters=this.cluster(t),this.state=r),{clusters:this.clusters,changed:e}}cluster(t){const r=this.getViewportState(t);return this.superCluster.getClusters(r.view,r.zoom).map(t=>this.transformCluster(t))}transformCluster(t){let{geometry:{coordinates:[r,e]},properties:n}=t;if(n.cluster)return new zn({markers:this.superCluster.getLeaves(n.cluster_id,1/0).map(t=>t.properties.marker),position:{lat:e,lng:r}});const o=n.marker;return new zn({markers:[o],position:Zn.getPosition(o)})}getViewportState(t){const r=t.map.getZoom(),e=t.map.getBounds();return Dn(r),Dn(e),{zoom:Math.round(r),view:Un(e,t.mapCanvasProjection,this.viewportPadding)}}},t.defaultOnClusterClickHandler=Si,t.distanceBetweenPoints=Bn,t.extendBoundsToPaddedViewport=Nn,t.extendPixelBounds=$n,t.filterMarkersToPaddedViewport=Fn,t.getPaddedViewport=Un,t.noop=Kn,t.pixelBoundsToLatLngBounds=Vn,t}({});
//# sourceMappingURL=index.min.js.map;