Uname: Linux premium294.web-hosting.com 4.18.0-553.45.1.lve.el8.x86_64 #1 SMP Wed Mar 26 12:08:09 UTC 2025 x86_64
Software: LiteSpeed
PHP version: 8.1.32 [ PHP INFO ] PHP os: Linux
Server Ip: 104.21.112.1
Your Ip: 216.73.216.223
User: mjbynoyq (1574) | Group: mjbynoyq (1570)
Safe Mode: OFF
Disable Function:
NONE

name : builder.js
( function() {

	/** globals wp */

	const hooks = wp.hooks;
	const hookNamespace = 'formidable-pro';

	const STEP_UNIT_SECOND = 'sec';

	const STEP_UNIT_MILLISECOND = 'millisec';

	function addEventListeners() {
		document.addEventListener( 'change', handleChangeEvent );
		document.addEventListener( 'frm_added_field', onFieldAdded );
	}

	hooks.addFilter( 'frm_conditional_logic_field_options', hookNamespace, updateFieldOptions );

	hooks.addFilter( 'frm_fields_with_shortcode_popup', hookNamespace, function( fieldsWithShortcodesBox ) {
		fieldsWithShortcodesBox.push( 'rte' );
		return fieldsWithShortcodesBox;
	});

	hooks.addAction( 'frm_after_delete_field', 'formidable-pro', function( fieldLi ) {
		const fieldID = getFieldIDFromHTMLID( fieldLi.id );
		document.querySelector( `a[data-code="${fieldID}"]` )?.closest( '.frm-customize-list' )?.remove();
	});

	hooks.addAction( 'frm_before_delete_field_option', hookNamespace, deleteConditionalLogicOptions );

	const onFieldAdded = event => {
		PageBreakField.onAddedField( event );
		maybeAddFieldToFieldShortcodes( event );
	};

	const maybeAddFieldToFieldShortcodes = params => {
		if ( [ 'data', 'divider', 'end_divider', 'captcha', 'break', 'html', 'form', 'summary' ].includes( params.frmType ) ) {
			return;
		}
		const insertCodeID  = createFieldsShortcodeRowLink( 'id', params );
		const insertCodeKey = createFieldsShortcodeRowLink( 'key', params );
		const shortcodeLink = frmDom.tag( 'li', {
			className: 'frm-customize-list dropdown-item show_frm_not_email_to',
			children: [
				insertCodeID,
				insertCodeKey,
			],
		});
		document.querySelector( '#frm-insert-fields-box .frm_code_list' )?.insertAdjacentElement( 'beforeend', shortcodeLink );
	};

	/**
	 * Returns the field id from html id.
	 *
	 * @param {string} htmlID
	 * @returns {string}
	 */
	const getFieldIDFromHTMLID = htmlID => htmlID.replace( 'frm_field_id_', '' );

	/**
	 * Creates <li> elements for the new field to be inserted in the shortcodes popup.
	 *
	 * @param {string} type 
	 * @param {Object} params 
	 * @returns {HTMLElement}
	 */
	const createFieldsShortcodeRowLink = ( type, params ) => {
		const fieldID   = getFieldIDFromHTMLID( params.frmField.id );
		const fieldKey  = document.getElementById( `field_options_field_key_${fieldID}` )?.value;
		const isIDLink  = type === 'id';
		const shortcode = isIDLink ? fieldID : fieldKey;
		const link = frmDom.a({
			className: ( isIDLink ? 'frmids ' : 'frmkeys ' ) + 'frm_insert_code',
			children: [
				document.querySelector( `.frm_t${params.frmType} .frmsvg` ).cloneNode( true ),
				document.getElementById( `frm_name_${fieldID}` ).value,
				frmDom.span( `[${shortcode}]` ),
			]
		});
		const idsTabIsActiveInShortcodesModal = document.querySelector( '#frm-insert-fields-box .subsubsub .frmids' )?.classList.contains( 'current' );
		if ( isIDLink && ! idsTabIsActiveInShortcodesModal || ! isIDLink && idsTabIsActiveInShortcodesModal ) {
			link.classList.add( 'frm_hidden' );
		}

		link.setAttribute( 'data-code', isIDLink ? fieldID : fieldKey );
		return link;
	};

	function updateFieldOptions( fieldOptions, hookArgs ) {
		if ( 'scale' === hookArgs.type ) {
			fieldOptions = getScaleFieldOptions( hookArgs.fieldId );
		}
		return fieldOptions;
	}

	function getScaleFieldOptions( fieldId ) {
		let opts = [];
		const optVals = document.querySelectorAll( 'input[name^="item_meta[' + fieldId + ']"]' );

		optVals.forEach( opt => {
			opts.push( opt.value );
		});

		return opts;
	}

	function updateConditionalLogicsDependentOnThis( target ) {
		setTimeout( function() {
			let fieldId = target.closest( '.frm-single-settings' ).dataset.fid;

			if ( ! fieldId ) {
				return;
			}

			frmAdminBuild.adjustConditionalLogicOptionOrders( fieldId, 'scale' );
		}, 0 );
	}

	function updateShortcodeTriggerLabel( shortcodeTrigger, value ) {
		const textshortcodeTriggerLabel = shortcodeTrigger.querySelector( 'svg.frmsvg' )?.nextSibling;
		if ( ! textshortcodeTriggerLabel || textshortcodeTriggerLabel.nodeType !== Node.TEXT_NODE ) {
			return;
		}
		textshortcodeTriggerLabel.textContent = value;
	}

	function maybeUpdateFieldsShortcodeModal( labelInput ) {
		const fieldId = labelInput.id.replace( 'frm_name_', '' );
		const fieldShortcodeTrigger = document.querySelector( 'a[data-code="' + fieldId + '"]');
		if ( ! fieldShortcodeTrigger ) {
			return;
		}
		updateShortcodeTriggerLabel( fieldShortcodeTrigger, labelInput.value );
		updateShortcodeTriggerLabel( fieldShortcodeTrigger.nextElementSibling, labelInput.value );
	}

	function handleChangeEvent( e ) {
		const target = e.target;

		if ( target.id.startsWith( 'frm_name_' ) ) {
			maybeUpdateFieldsShortcodeModal( target );
		}

		if ( isRootlineSettingInput( target ) ) {
			Rootline.updateRootline();
			return;
		}

		if ( target.matches( '.frm_page_transition_setting' ) ) {
			PageBreakField.onChangeTransition( e );
			return;
		}

		if ( target.matches( '.frm_scale_opt' ) ) {
			updateConditionalLogicsDependentOnThis( target );
		}

		if ( isACurrencySetting( target ) ) {
			const settingsContainer = target.closest( '.frm-type-range' );
			syncSliderFieldAfterCurrencyChange( settingsContainer.getAttribute( 'data-fid' ) );
			return;
		}

		if ( target.classList.contains( 'radio_maxnum' ) ) {
			setStarValues( target );
			return;
		}

		if ( 'INPUT' === target.nodeName && 'checkbox' === target.type ) {
			handleCheckboxToggleEvent( e );
		} else if ( target.classList.contains( 'frm_scale_opt' ) ) {
			setScaleValues( target );
		} else if ( 0 === target.id.indexOf( 'step_unit_' ) ) {
			onChangeStepUnit( e );
		}

		validateTimeFieldRangeValue( target );
	}

	/**
	 * Checks if the given target is a rootline setting input.
	 *
	 * @param {HTMLElement} target Target element.
	 * @return {Boolean}
	 */
	function isRootlineSettingInput( target ) {
		const rootlineSettingIds = [
			'frm-rootline-type',
			'frm-rootline-titles-on',
			'frm-rootline-numbers-off',
			'frm-rootline-lines-off'
		];

		return rootlineSettingIds.includes( target.id ) || target.matches( '.frm-rootline-title-setting input' );
	}

	function isACurrencySetting( input ) {
		return input.closest( '.frm_custom_currency_options_wrapper' ) && input.closest( '.frm-type-range' );
	}

	function handleCheckboxToggleEvent( e ) {
		const element = e.target;
		const name = element.name;

		if ( nameMatchesCurrencyOption( name ) ) {
			const calcBox = element.closest( '[id^="frm-calc-box-"]' );
			if ( calcBox ) {
				syncCalcBoxSettingVisibility( calcBox );
			} else {
				const settings = element.closest( '.frm-single-settings' );
				if ( null !== settings && settings.classList.contains( 'frm-type-range' ) ) {
					syncSliderFormatSettingVisibility( settings );
				}
			}
		}
	}

	function nameMatchesCurrencyOption( name ) {
		return -1 !== name.indexOf( 'field_options[calc_type_' ) ||
			-1 !== name.indexOf( 'field_options[is_currency_' ) ||
			-1 !== name.indexOf( 'field_options[custom_currency_' );
	}

	function syncCalcBoxSettingVisibility( calcBox ) {
		const typeToggle = calcBox.querySelector( '[name^="field_options[calc_type_"]' );
		const isMathType = ! typeToggle.checked;
		const decimalPlacesWrapper = calcBox.querySelector( '.frm_calc_dec' ).closest( '.frm_form_field' );
		const formatAsCurrencyOption = calcBox.querySelector( '[name^="field_options[is_currency_"]' );
		const isCurrency = formatAsCurrencyOption.checked;

		toggle( decimalPlacesWrapper, isMathType && ! isCurrency );
		syncCustomFormatSettings( calcBox, isMathType );
	}

	function syncSliderFormatSettingVisibility( settingsContainer ) {
		syncCustomFormatSettings( settingsContainer, true );

		const fieldId = settingsContainer.getAttribute( 'data-fid' );
		syncSliderFieldAfterCurrencyChange( fieldId );
	}

	function syncSliderFieldAfterCurrencyChange( fieldId ) {
		const fieldPreview = document.getElementById( 'frm_field_id_' + fieldId );
		const range = fieldPreview.querySelector( 'input[type="range"]' );
		updateSliderFieldPreview({
			field: range,
			att: 'value',
			newValue: range.value
		});
	}

	function syncCustomFormatSettings( container, showSettings ) {
		const formatAsCurrencyOption = container.querySelector( '[name^="field_options[is_currency_"]' );
		const formatAsCurrencyWrapper = formatAsCurrencyOption.closest( '.frm_form_field' );
		const isCustomCurrencyCheckbox = container.querySelector( '[name^="field_options[custom_currency_"]' );
		const isCustomCurrency = isCustomCurrencyCheckbox.checked;
		const customCurrencyCheckboxWrapper = isCustomCurrencyCheckbox.closest( '.frm_form_field' );
		const isCurrency = formatAsCurrencyOption.checked;
		const customCurrencyOptionsWrapper = container.querySelector( '.frm_custom_currency_options_wrapper' );
		const wasCustomCurrency = ! customCurrencyOptionsWrapper.classList.contains( 'frm_hidden' );

		toggle( formatAsCurrencyWrapper, showSettings );
		toggle( customCurrencyCheckboxWrapper, showSettings && isCurrency );
		toggle( customCurrencyOptionsWrapper, showSettings && isCurrency && isCustomCurrency );

		if ( ! wasCustomCurrency && isCustomCurrency ) {
			setCustomCurrencyDefaultsToMatchDefaultCurrency( container );
		}
	}

	function setCustomCurrencyDefaultsToMatchDefaultCurrency( container ) {
		const settings = [
			'custom_decimals',
			'custom_decimal_separator',
			'custom_thousand_separator',
			'custom_symbol_left',
			'custom_symbol_right'
		];
		settings.forEach( updateCustomCurrencySettingToMatchDefault );

		function updateCustomCurrencySettingToMatchDefault( setting ) {
			container.querySelector( '[name^="field_options[' + setting + '_"]' ).value = frmProBuilderVars.currency[ setting.replace( 'custom_', '' ) ];
		}
	}

	function setScaleValues( target ) {
		const fieldID = target.id.replace( 'scale_maxnum_', '' ).replace( 'scale_minnum_', '' ).replace( 'frm_step_', '' );
		let min = document.getElementById( 'scale_minnum_' + fieldID ).value;
		let max = document.getElementById( 'scale_maxnum_' + fieldID ).value;

		updateScaleValues( parseInt( min, 10 ), parseInt( max, 10 ), fieldID );
	}

	function updateScaleValues( min, max, fieldID ) {
		const container = jQuery( '#field_' + fieldID + '_inner_container .frm_form_fields' );
		const appendFieldToContainer = ( optionValue ) => {
			container.append( '<div class="frm_scale"><label><input type="hidden" name="field_options[options_' + fieldID + '][' + optionValue + ']" value="' + optionValue + '"> <input type="radio" name="item_meta[' + fieldID + ']" value="' + optionValue + '"> ' + optionValue + ' </label></div>' );
		};

		container.html( '' );
		let step = parseInt( document.getElementById( 'frm_step_' + fieldID ).value, 10 );
		if ( step === 0 ) {
			step = 1;
		}

		const ascending = min <= max;

		step = Math.abs( step );

		for ( let i = min; ascending ? i <= max : i >= max; i = ascending ? i + step : i - step ) {
			appendFieldToContainer( i );
		}

		container.append( '<div class="clear"></div>' );
	}

	function toggle( element, on ) {
		jQuery( element ).stop();
		element.style.opacity = 1;

		if ( on ) {
			if ( element.classList.contains( 'frm_hidden' ) ) {
				element.style.opacity = 0;
				element.classList.remove( 'frm_hidden' );
				jQuery( element ).animate({ opacity: 1 });
			}
		} else if ( ! element.classList.contains( 'frm_hidden' ) ) {
			jQuery( element ).animate({ opacity: 0 }, function() {
				element.classList.add( 'frm_hidden' );
			});
		}
	}

	hooks.addAction( 'frm_update_slider_field_preview', hookNamespace, updateSliderFieldPreview, 10 );

	function updateSliderFieldPreview({ field, att, newValue }) {
		if ( 'value' === att ) {
			if ( '' === newValue ) {
				newValue = getSliderMidpoint( field );
			}
			field.value = newValue;
		} else {
			field.setAttribute( att, newValue );
		}

		if ( -1 === [ 'value', 'min', 'max' ].indexOf( att ) ) {
			return;
		}

		if ( ( 'max' === att || 'min' === att ) && '' === getSliderDefaultValueInput( field.id ) ) {
			field.value = getSliderMidpoint( field );
		}

		const fieldId = field.getAttribute( 'name' ).replace( 'item_meta[', '' ).replace( ']', '' );
		const settingsContainer = document.getElementById( 'frm-single-settings-' + fieldId );
		const isCurrency = settingsContainer.querySelector( 'input[name="field_options[is_currency_' + fieldId + ']"]' ).checked;
		const sliderValueSpan = field.parentNode.querySelector( '.frm_range_value' );

		if ( ! isCurrency ) {
			sliderValueSpan.textContent = field.value;
			return;
		}

		const isCustomCurrency = settingsContainer.querySelector( 'input[name="field_options[custom_currency_' + fieldId + ']"]' ).checked;
		const currency = isCustomCurrency ? {
			decimals: parseInt( getValueFromSettingsContainerInput( 'select', 'custom_decimals' ) ),
			decimal_separator: getValueFromSettingsContainerInput( 'input', 'custom_decimal_separator' ),
			thousand_separator: getValueFromSettingsContainerInput( 'input', 'custom_thousand_separator' ),
			symbol_left: getValueFromSettingsContainerInput( 'input', 'custom_symbol_left' ),
			symbol_right: getValueFromSettingsContainerInput( 'input', 'custom_symbol_right' ),
			symbol_padding: ''
		} : frmProBuilderVars.currency;

		sliderValueSpan.textContent = formatCurrency( normalizeTotal( field.value, currency ), currency );

		function getValueFromSettingsContainerInput( type, name ) {
			let selector = type + '[name="field_options[' + name + '_' + fieldId + ']"]';
			if ( 'select' === type ) {
				selector += ' option:checked';
			}
			return settingsContainer.querySelector( selector ).value;
		}

		function getSliderDefaultValueInput( previewInputId ) {
			return document.querySelector( 'input[data-changeme="' + previewInputId + '"][data-changeatt="value"]' ).value;
		}

		function getSliderMidpoint( sliderInput ) {
			const max = parseFloat( sliderInput.getAttribute( 'max' ) );
			const min = parseFloat( sliderInput.getAttribute( 'min' ) );
			return ( max - min ) / 2 + min;
		}
	}

	function normalizeTotal( total, currency ) {
		total = currency.decimals > 0 ? round10( total, currency.decimals ) : Math.ceil( total );
		return maybeAddTrailingZeroToPrice( total, currency );
	}

	function round10( value, decimals ) {
		return Number( Math.round( value + 'e' + decimals ) + 'e-' + decimals );
	}

	function formatCurrency( total, currency ) {
		let leftSymbol, rightSymbol;

		total = maybeAddTrailingZeroToPrice( total, currency );
		total = maybeRemoveTrailingZerosFromPrice( total, currency );
		total = addThousands( total, currency );
		leftSymbol = currency.symbol_left + currency.symbol_padding;
		rightSymbol = currency.symbol_padding + currency.symbol_right;

		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 );
		}

		function addThousands( total, currency ) {
			if ( currency.thousand_separator ) {
				total = total.toString().replace( /\B(?=(\d{3})+(?!\d))/g, currency.thousand_separator );
			}
			return total;
		}

		return leftSymbol + total + rightSymbol;
	}

	function maybeAddTrailingZeroToPrice( price, currency ) {
		if ( 'number' !== typeof price ) {
			return price;
		}

		price += ''; // first convert to string

		const pos = price.indexOf( '.' );
		if ( pos === -1 ) {
			price = price + '.00';
		} else if ( price.substring( pos + 1 ).length < 2 ) {
			price += '0';
		}

		return price.replace( '.', currency.decimal_separator );
	}

	/**
	 * Wrap rich text logic into a function and initialize.
	 * A RTE field has uses TinyMCE for the preview, and for the default value input.
	 * The RTE needs to re-initialize at various points including:
	 * - when drag-and-dropped
	 * - when added with AJA
	 * - when a new field is inserted
	 * - when a group is broken into rows
	 * - when rows are merged into a group
	 *
	 * @returns {void}
	 */
	function initRichTextFields() {
		appendModalTriggersToRtePlaceholderSettings();

		document.addEventListener(
			'click',
			function( event ) {
				const classList = event.target.classList;
				if ( classList.contains( 'frm-break-field-group' ) || classList.contains( 'frm-row-layout-option' ) || classList.contains( 'frm-save-custom-field-group-layout' ) ) {
					initializeAllWysiwygsAfterSlightDelay();
				}
			}
		);

		document.addEventListener(
			'frm_added_field',
			/**
			 * Prepare an RTE field when a new field is added.
			 *
			 * @param {Event} event
			 * @returns {void}
			 */
			event => {
				if ( 'rte' !== event.frmType || ! event.frmField ) {
					return;
				}

				prepareDefaultValueInput( event.frmField.getAttribute( 'data-fid' ) )

				const wysiwyg = event.frmField.querySelector( '.wp-editor-area' );
				if ( wysiwyg ) {
					frmDom.wysiwyg.init( wysiwyg );
				}
			}
		);

		document.addEventListener(
			'frm_ajax_loaded_field',
			/**
			 * When new fields are loaded with AJAX, check if any are RTE fields and initialize.
			 *
			 * @param {Event} event
			 * @returns {void}
			 */
			event => {
				event.frmFields.forEach(
					/**
					 * Check if a single field is an RTE and possibly initialize.
					 *
					 * @param {Object} field {
					 *     @type {String} id Numeric field ID.
					 * }
					 * @returns {void}
					 */
					field => {
						if ( 'rte' !== field.type ) {
							return;
						}

						prepareDefaultValueInput( field.id );

						const wysiwyg = document.querySelector( '#frm_field_id_' + field.id + ' .wp-editor-area' );
						if ( wysiwyg ) {
							frmDom.wysiwyg.init( wysiwyg );
						}
					}
				);
			}
		);

		let draggable;
		// frm_sync_after_drag_and_drop does not pass along information about the draggable, so hook into dropdeactivate.
		jQuery( document ).on( 'dropdeactivate', function( _, ui ) {
			draggable = ui.draggable.get( 0 );
		});
		document.addEventListener(
			'frm_sync_after_drag_and_drop',
			() => {
				if ( draggable ) {
					// Use querySelectorAll as frm_sync_after_drag_and_drop is also called for field groups.
					draggable.querySelectorAll( '.wp-editor-area' ).forEach( frmDom.wysiwyg.init );
				}
			}
		);

		function prepareDefaultValueInput( fieldId ) {
			const defaultValueWrapper = document.getElementById( 'default-value-for-' + fieldId );
			addSmartValuesTriggerToDefaultValueWrapper( defaultValueWrapper );
			copyChangemeFromWrapperToInput( defaultValueWrapper );
		}

		function initializeAllWysiwygsAfterSlightDelay() {
			setTimeout(
				() => document.querySelectorAll( '#frm-show-fields .wp-editor-area' ).forEach( frmDom.wysiwyg.init ),
				1
			);
		}

		function appendModalTriggersToRtePlaceholderSettings() {
			if ( document.getElementById( 'frm_adv_info' ) ) {
				// Do not make the Smart Values popup if the fields popups is available.
				return;
			}
			const rtePlaceholderDefaults = document.querySelectorAll( '.frm-single-settings.frm-type-rte .frm-default-value-wrapper' );
			if ( ! rtePlaceholderDefaults.length ) {
				return;
			}

			rtePlaceholderDefaults.forEach(
				defaultValueWrapper => {
					addSmartValuesTriggerToDefaultValueWrapper( defaultValueWrapper );
					copyChangemeFromWrapperToInput( defaultValueWrapper );
				}
			);
		}

		function copyChangemeFromWrapperToInput( defaultValueWrapper ) {
			const fieldToChangeId = defaultValueWrapper.getAttribute( 'data-changeme' );

			document.getElementById( defaultValueWrapper.getAttribute( 'data-html-id' ) ).setAttribute( 'data-changeme', fieldToChangeId );
			defaultValueWrapper.removeAttribute( 'data-changeme' );

			const field = document.getElementById( fieldToChangeId );
			if ( field ) {
				jQuery( field ).on(
					'change',
					function() {
						if ( ! tinyMCE.editors[ field.id ] || tinyMCE.editors[ field.id ].isHidden() ) {
							return;
						}
						tinyMCE.editors[ field.id ].setContent( field.value );
					}
				);
			}
		}

		function addSmartValuesTriggerToDefaultValueWrapper( defaultValueWrapper ) {
			/*global frmDom */
			const { svg } = frmDom;

			const inputID = defaultValueWrapper.getAttribute( 'data-html-id' );

			const modalTrigger = svg({ href: '#frm_more_horiz_solid_icon', classList: [ 'frm_more_horiz_solid_icon', 'frm-show-inline-modal' ] });
			modalTrigger.setAttribute( 'data-open', 'frm-smart-values-box' );
			modalTrigger.setAttribute( 'title', defaultValueWrapper.getAttribute( 'data-modal-trigger-title' ) );

			document.getElementById( inputID ).parentElement.prepend( modalTrigger );

			// The icon should be wrapped in a 'p' tag, as the modal box is appended to the 'closest' p.
			const wrapper = document.createElement( 'p' );
			wrapper.prepend( document.getElementById( 'wp-' + inputID + '-wrap' ) );
			defaultValueWrapper.appendChild( wrapper );
		}
	}

	function validateTimeFieldRangeValue( target ) {
		if ( ! ( target.id.startsWith( 'start_time' ) || target.id.startsWith( 'end_time' ) ) ) {
			return;
		}

		const timeRangeInput = target;
		let isValid          = true;

		function getStepUnit() {
			const stepUnitEl = timeRangeInput.closest( '.frm-single-settings' ).querySelector( 'select[id^="step_unit_"]' );
			if ( ! stepUnitEl ) {
				return false;
			}
			return stepUnitEl.value;
		}

		if ( timeRangeInput.matches( '[id^=frm_step_]' ) ) {
			if ( timeRangeInput.value.match( /^\d{1,2}$/ ) ) {
				return;
			}
			frmAdminBuild.infoModal( 'Step value is invalid.' );
			isValid = false;

		} else if ( ! timeRangeInput.value.match( getTimeRangeRegex( getStepUnit() ) ) ) {
			let timeRangeString;
			if ( timeRangeInput.matches( '.frm-type-time [id^=start_time]' ) ) {
				timeRangeString = 'Start time';
			} else {
				timeRangeString = 'End time';
			}
			frmAdminBuild.infoModal( `${timeRangeString} is invalid.` );
			isValid = false;
		}

		if ( ! isValid ) {
			const modalDismissers = document.querySelectorAll( '#frm_info_modal .dismiss, #frm_info_modal #frm-info-click' );
			function handleModalDismiss() {
				timeRangeInput.classList.add( 'frm_invalid_field' );
				setTimeout( () => timeRangeInput.focus(), 0 );
				modalDismissers.forEach( el => {
					el.removeEventListener( 'click', handleModalDismiss );
				});
			}

			modalDismissers.forEach( el => {
				el.addEventListener( 'click', handleModalDismiss );
			});

		} else if ( timeRangeInput.classList.contains( 'frm_invalid_field' ) ) {
			timeRangeInput.classList.remove( 'frm_invalid_field' );
		}
	}

	const PageBreakField = {
		transition: false, // Track the transition value when one of page break transition changes.

		/**
		 * Handles change transition event.
		 *
		 * @param {Event} event Event object.
		 */
		onChangeTransition: function( event ) {
			// Store the updated value to update new page break field.
			PageBreakField.transition = event.target.value;

			// Update other page break fields.
			document.querySelectorAll( '.frm_page_transition_setting' ).forEach( el => {
				if ( el.id === event.target.id ) {
					// Do not update current setting.
					return;
				}

				el.value = event.target.value;
			});
		},

		/**
		 * Handlers added field.
		 *
		 * @param {Event} event Event object.
		 */
		onAddedField: function( event ) {
			if ( false === PageBreakField.transition || 'break' !== event.frmType ) {
				return;
			}

			const transitionSetting = document.getElementById( 'frm_transition_' + event.frmField.dataset.fid );
			transitionSetting.value = PageBreakField.transition;
		}
	};

	const Rootline = {
		init: function() {
			hooks.addAction( 'frmShowedFieldSettings', hookNamespace, this.showedFieldSettings );

			document.addEventListener( 'frm_added_field', function( event ) {
				if ( 'break' === event.frmType && Rootline.isRootlineAvailable() ) {
					Rootline.updateRootline();
				}
			});

			hooks.addAction( 'frm_renumber_page_breaks', hookNamespace, function( pages ) {
				if ( ! Rootline.isRootlineAvailable() ) {
					return;
				}

				if ( pages.length > 1 ) {
					Rootline.updateRootline();
				} else {
					Rootline.toggleRootline( false );
				}
			});

			// Listen for rootline settings change to update the rootline.
			const settingSelectors = '#frm-rootline-type,#frm-rootline-titles-on,.frm-rootline-title-setting input,#frm-rootline-numbers-off,#frm-lines-numbers-off';
			frmDom.util.documentOn( 'change', settingSelectors, this.updateRootline );

			this.makeRootlineResponsive();
		},

		/**
		 * Checks if rootline is available in form builder.
		 *
		 * @return {Boolean}
		 */
		isRootlineAvailable: function() {
			return document.getElementById( 'frm-rootline-type' );
		},

		/**
		 * Does after showed field settings.
		 *
		 * @param {HTMLElement} showBtn Show settings button.
		 * @param {HTMLElement} settingsEl Settings element.
		 */
		showedFieldSettings: function( showBtn, settingsEl ) {
			if ( 'rootline' !== showBtn.dataset.fid ) {
				return;
			}

			Rootline.loadPageTitlesSetting( settingsEl );
		},

		/**
		 * Loads titles setting for Rootline.
		 *
		 * @param {HTMLElement} settingsEl Settings element.
		 */
		loadPageTitlesSetting: function( settingsEl ) {
			const pageBreaks    = document.querySelectorAll( '.frm_field_box.edit_field_type_break' );
			const titleSettings = settingsEl.querySelectorAll( '.frm-rootline-title-setting' );
			const pagesCount    = pageBreaks.length + 1; // Plus the first page break.

			for ( let i = 1; i < pagesCount; i++ ) {
				if ( 'undefined' !== typeof titleSettings[ i ] ) {
					// Show it.
					titleSettings[ i ].classList.remove( 'frm_hidden' );
				} else {
					// Append new title setting.
					Rootline.appendPageTitleSetting( titleSettings[0], pageBreaks[ i - 1 ] );
				}
			}

			// Hide title exceeded settings.
			if ( pagesCount < titleSettings.length ) {
				for ( let i = pagesCount; i < titleSettings.length; i++ ) {
					titleSettings[ i ].classList.add( 'frm_hidden' );
				}
			}
		},

		/**
		 * Appends page title input in the rootline titles setting.
		 *
		 * @param {HTMLElement} firstSetting First title setting element.
		 * @param {HTMLElement} pageBreak    Corresponding page break element.
		 */
		appendPageTitleSetting: function( firstSetting, pageBreak ) {
			const cloneSetting = firstSetting.cloneNode( true );
			const label        = cloneSetting.querySelector( 'label' );
			const input        = cloneSetting.querySelector( 'input' );
			const pageId       = pageBreak.dataset.fid;

			frmDom.setAttributes( label, {
				for: label.getAttribute( 'for' ).replace( '1', pageId )
			});

			frmDom.setAttributes( input, {
				id: input.id.replace( '1', pageId ),
				name: input.name.replace( '[0]', '[' + pageId + ']' ),
				'data-page': pageId,
				value: pageBreak.querySelector( '.frm_button_submit' ).innerText
			});

			firstSetting.parentNode.appendChild( cloneSetting );
		},

		/**
		 * Gets rootline settings.
		 *
		 * @return {{titlesOn, numbersOn: boolean, linesOn: boolean, position, titles: any[], type}}
		 */
		getSettings: function() {
			return {
				type: document.getElementById( 'frm-rootline-type' ).value,
				position: document.getElementById( 'frm-pagination-position' ).value,
				titlesOn: document.getElementById( 'frm-rootline-titles-on' ).checked,
				numbersOn: ! document.getElementById( 'frm-rootline-numbers-off' ).checked,
				linesOn: ! document.getElementById( 'frm-rootline-lines-off' ).checked,
				titles: Array.from( document.querySelectorAll( '.frm-rootline-title-setting input' ) ).map( input => input.value )
			};
		},

		/**
		 * Updates rootline UI.
		 */
		updateRootline: function() {
			const settings = Rootline.getSettings();
			if ( ! settings.type ) {
				Rootline.toggleRootline( false );
				return;
			}

			Rootline.toggleRootline( true );

			/**
			 * Allows using custom handler for live updating rootline in form builder.
			 *
			 * @since 6.9
			 *
			 * @param {Boolean} skip     Return `true` to skip remaining updates.
			 * @param {Object}  settings Rootline settings.
			 * @param {Object}  Rootline Rootline class.
			 */
			const skip = hooks.applyFilters( 'frm_pro_backend_update_rootline', false, settings, Rootline );
			if ( skip ) {
				return;
			}

			const rootlineWrapper = document.getElementById( 'frm-backend-rootline' );
			const rootlineList    = rootlineWrapper.querySelector( 'ul' );
			const pages           = document.querySelectorAll( '.frm-page-num' );

			Array.from( rootlineList.children ).forEach( el => el.remove() );

			for ( let i = 0; i < pages.length; i++ ) {
				// Add "more" rootline item.
				if ( pages.length - 1 === i ) {
					rootlineList.appendChild(
						Rootline.getRootlineItem(
							{
								title: '',
								number: '...',
								className: 'frm-rootline-item-more frm_hidden'
							},
							settings
						)
					);
				}

				rootlineList.appendChild(
					Rootline.getRootlineItem(
						{
							title: settings.titles[ i ] || wp.i18n.sprintf( wp.i18n.__( 'Page %d', 'formidable-pro' ), i + 1 ),
							number: i + 1
						},
						settings
					)
				);
			}

			rootlineWrapper.setAttribute( 'data-type', settings.type );
			rootlineWrapper.classList.toggle( 'frm-rootline-no-titles', ! settings.titlesOn );
			rootlineWrapper.classList.toggle( 'frm-rootline-no-numbers', ! settings.numbersOn );
			rootlineWrapper.classList.toggle( 'frm-rootline-no-lines', ! settings.linesOn );

			rootlineWrapper.querySelector( '.frm_pages_total' ).innerText = pages.length;

			Rootline.resizeRootline();
		},

		/**
		 * Gets one rootline item.
		 *
		 * @param {Object} data     Rootline item data.
		 * @param {Object} settings Rootline settings.
		 * @return {HTMLElement}
		 */
		getRootlineItem: function( data, settings ) {
			return frmDom.tag( 'li', {
				children: [
					'rootline' === settings.type ? frmDom.tag(
						'span',
						{
							className: 'frm-rootline-number',
							text: data.number
						}
					) : '',
					frmDom.tag(
						'span',
						{
							className: 'frm-rootline-title',
							text: data.title
						}
					)
				],
				className: data.className
			});
		},

		/**
		 * Toggles rootline.
		 *
		 * @param {Boolean} show Show rootline or not.
		 */
		toggleRootline: function( show ) {
			document.getElementById( 'frm-backend-rootline-wrapper' ).classList.toggle( 'frm_hidden', ! show );
		},

		/**
		 * Resizes rootline.
		 */
		resizeRootline: function() {
			if ( ! Rootline.isRootlineAvailable() ) {
				return;
			}

			const rootlineWrapper = document.getElementById( 'frm-backend-rootline' );
			if ( 'rootline' !== rootlineWrapper.dataset.type ) {
				// Don't need to resize progress bar.
				return;
			}

			const width = rootlineWrapper.offsetWidth;
			const itemWidth = rootlineWrapper.classList.contains( 'frm-rootline-no-titles' ) ? 50 : 150;
			const showItems = Math.floor( width / itemWidth );
			const items = rootlineWrapper.querySelectorAll( 'li' );

			items.forEach( ( item, index ) => {
				if ( ! index || items.length - 1 === index ) {
					// Always show the first and last item.
					return;
				}

				if ( items.length < showItems + 2 ) {
					item.classList.toggle( 'frm_hidden', item.classList.contains( 'frm-rootline-item-more' ) );
					return;
				}

				if ( index < showItems - 2 ) {
					item.classList.remove( 'frm_hidden' );
					return;
				}

				item.classList.toggle( 'frm_hidden', ! item.classList.contains( 'frm-rootline-item-more' ) );
			});
		},

		/**
		 * Makes rootline responsive.
		 */
		makeRootlineResponsive: function() {
			window.addEventListener( 'resize', this.resizeRootline );

			window.dispatchEvent( new Event( 'resize' ) );
		}
	};

	function getTimeRangeRegex( stepUnit ) {
		let regex = '^(?:\\d|[01]\\d|2[0-3]):[0-5]\\d';
		if ( 'sec' === stepUnit ) {
			regex += ':[0-5]\\d';
		} else if ( 'millisec' === stepUnit ) {
			regex += ':[0-5]\\d\\:\\d\\d\\d';
		}

		regex += '$';

		return new RegExp( regex );
	}

	function onChangeStepUnit( event ) {
		const stepUnit = event.target.value;
		const regex = getTimeRangeRegex( stepUnit );
		const wrapper = event.target.closest( '.frm-single-settings' );
		const inputs = wrapper.querySelectorAll( '.frm-number-range input[type="text"]' );
		const stepInput = wrapper.querySelector( 'input[id^="frm_step_"]' );
		const singleCheckbox = wrapper.querySelector( 'label[for^="single_time_"]' );

		const getFormattedRangeValue = value => {
			let [ h, m, s, ms ] = value.split( ':' );

			if ( ! h || isNaN( h ) ) {
				h = '00';
			}

			if ( ! m || isNaN( m ) ) {
				m = '00';
			}

			if ( STEP_UNIT_SECOND !== stepUnit && STEP_UNIT_MILLISECOND !== stepUnit ) {
				return h + ':' + m;
			}

			if ( ! s || isNaN( s ) ) {
				s = '00';
			}

			if ( STEP_UNIT_SECOND === stepUnit ) {
				return [ h, m, s ].join( ':' );
			}

			if ( ! ms || isNaN( ms ) ) {
				ms = '000';
			}

			return [ h, m, s, ms ].join( ':' );
		};

		const changeValueFormat = input => {
			if ( input.value.match( regex ) ) {
				return;
			}

			input.value = getFormattedRangeValue( input.value );
		};

		// Change format of time range inputs.
		inputs.forEach( changeValueFormat );

		// Change format of step input. If step setting is empty or is a number, don't change.
		if ( stepInput.value && isNaN( stepInput.value ) ) {
			stepInput.value = getFormattedRangeValue( stepInput.value );
		}

		// Show or hide single time dropdown checkbox.
		if ( STEP_UNIT_SECOND === stepUnit || STEP_UNIT_MILLISECOND === stepUnit ) {
			singleCheckbox.classList.add( 'frm_hidden' );
		} else {
			singleCheckbox.classList.remove( 'frm_hidden' );
		}
	}

	/**
	 * @param {HTMLElement} input
	 * @returns {void}
	 */
	function setStarValues( input ) {
		/*jshint validthis:true */
		const fieldID   = input.id.replace( 'radio_maxnum_', '' );
		const container = document.querySelector( '#field_' + fieldID + '_inner_container .frm-star-group' );

		if ( ! container ) {
			return;
		}

		const fieldKey      = document.getElementsByName( 'field_options[field_key_' + fieldID + ']' )[0].value;
		container.innerHTML = '';

		const min = 1;
		let max   = input.value;
		if ( min > max ) {
			max = min;
		}

		let i, hiddenInput, radioInput, label;
		const fragment = document.createDocumentFragment();
		for ( i = min; i <= max; i++ ) {
			hiddenInput = frmDom.tag( 'input' );
			hiddenInput.setAttribute( 'name', 'field_options[options_' + fieldID + '][' + i + ']' );
			hiddenInput.setAttribute( 'type', 'hidden' );

			radioInput = frmDom.tag( 'input' );
			radioInput.id = 'field_' + fieldKey + '-' + i;
			radioInput.setAttribute( 'type', 'radio' );
			radioInput.setAttribute( 'name', 'item_meta[' + fieldID + ']"' );

			label = frmDom.tag(
				'label',
				{
					className: 'star-rating',
					children: [
						frmDom.svg({ href: '#frm_star_icon' }),
						frmDom.svg({ href: '#frm_star_full_icon' })
					]
				}
			);
			label.setAttribute( 'for', radioInput.id );

			fragment.appendChild( hiddenInput );
			fragment.appendChild( radioInput );
			fragment.appendChild( label );
			fragment.appendChild( document.createTextNode( ' ' ) );
		}

		container.appendChild( fragment );
	}

	/**
	 * Deletes all conditional logic dropdown option elements that correspond to the deleted field option.
	 *
	 * @since 6.12
	 * @param {HTMLElement} option
	 * @return {void}
	 */
	function deleteConditionalLogicOptions( option ) {
		const deletedOption = option.closest( '.frm_single_option' ).querySelector( '.frm_option_key input[type="text"]' );
		if ( ! deletedOption ) {
			return;
		}
		const deletedOptionValue = deletedOption.value;
		const rows               = document.querySelectorAll( '.frm_logic_row' );

		rows.forEach( row => {
			const fieldId = row.id.split( '_' )[ 2 ]; // row.id Example: frm_logic_1234_0 where 1234 is the field id and 0 the conditional logic row.
			const relatedConditionalLogicOption = row.querySelector( 'select[name="field_options[hide_opt_' + fieldId + '][]"] option[value="' + deletedOptionValue + '"]' );
			if ( relatedConditionalLogicOption ) {
				relatedConditionalLogicOption.remove();
			}
		});
	}
	 
	addEventListeners();
	initRichTextFields();
	Rootline.init();

}() );
© 2025 XylotrechusZ