Add massive collection of CSS, JavaScript and theme assets that were previously excluded: **CSS Files (681 total):** - HVAC plugin-specific styles (hvac-*.css): 34 files including dashboard, certificates, registration, mobile nav, accessibility fixes, animations, and welcome popup - Theme framework files (Astra, builder systems, layouts): 200+ files - Plugin compatibility styles (WooCommerce, WPForms, Elementor, Contact Form 7): 150+ files - WordPress core and editor styles: 50+ files - Responsive and RTL language support: 200+ files **JavaScript Files (400+ total):** - HVAC plugin functionality (hvac-*.js): 27 files including menu systems, dashboard enhancements, profile sharing, mobile responsive features, accessibility, and animations - Framework and library files: jQuery plugins, GSAP, AOS, Swiper, Chart.js, Lottie, Isotope - Plugin compatibility scripts: WPForms, WooCommerce, Elementor, Contact Form 7, LifterLMS - WordPress core functionality: customizer, admin, block editor compatibility - Third-party integrations: Stripe, SMTP, analytics, search functionality **Assets:** - Certificate background images and logos - Comprehensive theme styling infrastructure - Mobile-responsive design systems - Cross-browser compatibility assets - Performance-optimized minified versions **Updated .gitignore:** - Fixed asset directory whitelisting patterns to properly include CSS/JS/images - Added proper directory structure recognition (!/assets/css/, !/assets/js/, etc.) - Maintains security by excluding sensitive files while including essential assets This commit provides the complete frontend infrastructure needed for: - Full theme functionality and styling - Plugin feature implementations - Mobile responsiveness and accessibility - Cross-browser compatibility - Performance optimization - Developer workflow support
		
			
				
	
	
		
			2040 lines
		
	
	
	
		
			56 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			2040 lines
		
	
	
	
		
			56 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* global jconfirm, wpforms_gutenberg_form_selector, Choices, JSX, DOM, WPFormsUtils */
 | ||
| /* jshint es3: false, esversion: 6 */
 | ||
| 
 | ||
| /**
 | ||
|  * @param strings.copy_paste_error
 | ||
|  * @param strings.error_message
 | ||
|  * @param strings.form_edit
 | ||
|  * @param strings.form_entries
 | ||
|  * @param strings.form_keywords
 | ||
|  * @param strings.form_select
 | ||
|  * @param strings.form_selected
 | ||
|  * @param strings.form_settings
 | ||
|  * @param strings.label_styles
 | ||
|  * @param strings.other_styles
 | ||
|  * @param strings.page_break
 | ||
|  * @param strings.panel_notice_head
 | ||
|  * @param strings.panel_notice_link
 | ||
|  * @param strings.panel_notice_link_text
 | ||
|  * @param strings.panel_notice_text
 | ||
|  * @param strings.show_description
 | ||
|  * @param strings.show_title
 | ||
|  * @param strings.sublabel_hints
 | ||
|  * @param strings.form_not_available_message
 | ||
|  * @param urls.entries_url
 | ||
|  * @param urls.form_url
 | ||
|  * @param window.wpforms_choicesjs_config
 | ||
|  * @param wpforms_education.upgrade_bonus
 | ||
|  * @param wpforms_gutenberg_form_selector.block_empty_url
 | ||
|  * @param wpforms_gutenberg_form_selector.block_preview_url
 | ||
|  * @param wpforms_gutenberg_form_selector.get_started_url
 | ||
|  * @param wpforms_gutenberg_form_selector.is_full_styling
 | ||
|  * @param wpforms_gutenberg_form_selector.is_modern_markup
 | ||
|  * @param wpforms_gutenberg_form_selector.logo_url
 | ||
|  * @param wpforms_gutenberg_form_selector.wpforms_guide
 | ||
|  */
 | ||
| 
 | ||
| /**
 | ||
|  * Gutenberg editor block.
 | ||
|  *
 | ||
|  * Common module.
 | ||
|  *
 | ||
|  * @since 1.8.8
 | ||
|  */
 | ||
| export default ( function( document, window, $ ) {
 | ||
| 	/**
 | ||
| 	 * WP core components.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.8
 | ||
| 	 */
 | ||
| 	const { serverSideRender: ServerSideRender = wp.components.ServerSideRender } = wp;
 | ||
| 	const { createElement, Fragment, createInterpolateElement } = wp.element;
 | ||
| 	const { registerBlockType } = wp.blocks;
 | ||
| 	const { InspectorControls, PanelColorSettings, useBlockProps } = wp.blockEditor || wp.editor;
 | ||
| 	const { SelectControl, ToggleControl, PanelBody, Placeholder } = wp.components;
 | ||
| 	const { __ } = wp.i18n;
 | ||
| 	const { useState, useEffect } = wp.element;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Localized data aliases.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.8
 | ||
| 	 */
 | ||
| 	const { strings, defaults, sizes, urls, isPro, isLicenseActive, isAdmin } = wpforms_gutenberg_form_selector;
 | ||
| 	const defaultStyleSettings = defaults;
 | ||
| 
 | ||
| 	// noinspection JSUnusedLocalSymbols
 | ||
| 	/**
 | ||
| 	 * WPForms Education script.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.8
 | ||
| 	 */
 | ||
| 	const WPFormsEducation = window.WPFormsEducation || {}; // eslint-disable-line no-unused-vars
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * List of forms.
 | ||
| 	 *
 | ||
| 	 * The default value is localized in FormSelector.php.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.4
 | ||
| 	 *
 | ||
| 	 * @type {Object}
 | ||
| 	 */
 | ||
| 	let formList = wpforms_gutenberg_form_selector.forms;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Blocks runtime data.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.1
 | ||
| 	 *
 | ||
| 	 * @type {Object}
 | ||
| 	 */
 | ||
| 	const blocks = {};
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Whether it is needed to trigger server rendering.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.1
 | ||
| 	 *
 | ||
| 	 * @type {boolean}
 | ||
| 	 */
 | ||
| 	let triggerServerRender = true;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Popup container.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.3
 | ||
| 	 *
 | ||
| 	 * @type {Object}
 | ||
| 	 */
 | ||
| 	let $popup = {};
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Track fetch status.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.4
 | ||
| 	 *
 | ||
| 	 * @type {boolean}
 | ||
| 	 */
 | ||
| 	let isFetching = false;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Elements holder.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.8
 | ||
| 	 *
 | ||
| 	 * @type {Object}
 | ||
| 	 */
 | ||
| 	const el = {};
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Common block attributes.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.8
 | ||
| 	 *
 | ||
| 	 * @type {Object}
 | ||
| 	 */
 | ||
| 	let commonAttributes = {
 | ||
| 		clientId: {
 | ||
| 			type: 'string',
 | ||
| 			default: '',
 | ||
| 		},
 | ||
| 		formId: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.formId,
 | ||
| 		},
 | ||
| 		displayTitle: {
 | ||
| 			type: 'boolean',
 | ||
| 			default: defaultStyleSettings.displayTitle,
 | ||
| 		},
 | ||
| 		displayDesc: {
 | ||
| 			type: 'boolean',
 | ||
| 			default: defaultStyleSettings.displayDesc,
 | ||
| 		},
 | ||
| 		preview: {
 | ||
| 			type: 'boolean',
 | ||
| 		},
 | ||
| 		theme: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.theme,
 | ||
| 		},
 | ||
| 		themeName: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.themeName,
 | ||
| 		},
 | ||
| 		labelSize: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.labelSize,
 | ||
| 		},
 | ||
| 		labelColor: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.labelColor,
 | ||
| 		},
 | ||
| 		labelSublabelColor: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.labelSublabelColor,
 | ||
| 		},
 | ||
| 		labelErrorColor: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.labelErrorColor,
 | ||
| 		},
 | ||
| 		pageBreakColor: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.pageBreakColor,
 | ||
| 		},
 | ||
| 		customCss: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.customCss,
 | ||
| 		},
 | ||
| 		copyPasteJsonValue: {
 | ||
| 			type: 'string',
 | ||
| 			default: defaultStyleSettings.copyPasteJsonValue,
 | ||
| 		},
 | ||
| 	};
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Handlers for custom styles settings, defined outside this module.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.8
 | ||
| 	 *
 | ||
| 	 * @type {Object}
 | ||
| 	 */
 | ||
| 	let customStylesHandlers = {};
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Dropdown timeout.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.8
 | ||
| 	 *
 | ||
| 	 * @type {number}
 | ||
| 	 */
 | ||
| 	let dropdownTimeout;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Whether copy-paste content was generated on edit.
 | ||
| 	 *
 | ||
| 	 * @since 1.9.1
 | ||
| 	 *
 | ||
| 	 * @type {boolean}
 | ||
| 	 */
 | ||
| 	let isCopyPasteGeneratedOnEdit = false;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Whether the background is selected.
 | ||
| 	 *
 | ||
| 	 * @since 1.9.3
 | ||
| 	 *
 | ||
| 	 * @type {boolean}
 | ||
| 	 */
 | ||
| 	let backgroundSelected = false;
 | ||
| 
 | ||
| 	/**
 | ||
| 	 * Public functions and properties.
 | ||
| 	 *
 | ||
| 	 * @since 1.8.1
 | ||
| 	 *
 | ||
| 	 * @type {Object}
 | ||
| 	 */
 | ||
| 	const app = {
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Panel modules.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @type {Object}
 | ||
| 		 */
 | ||
| 		panels: {},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Start the engine.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} blockOptions Block options.
 | ||
| 		 */
 | ||
| 		init( blockOptions ) {
 | ||
| 			el.$window = $( window );
 | ||
| 			app.panels = blockOptions.panels;
 | ||
| 			app.education = blockOptions.education;
 | ||
| 
 | ||
| 			app.initDefaults( blockOptions );
 | ||
| 			app.registerBlock( blockOptions );
 | ||
| 
 | ||
| 			app.initJConfirm();
 | ||
| 
 | ||
| 			$( app.ready );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Document ready.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 */
 | ||
| 		ready() {
 | ||
| 			app.events();
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Events.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 */
 | ||
| 		events() {
 | ||
| 			el.$window
 | ||
| 				.on( 'wpformsFormSelectorEdit', _.debounce( app.blockEdit, 250 ) )
 | ||
| 				.on( 'wpformsFormSelectorFormLoaded', app.formLoaded );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Init jConfirm.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 */
 | ||
| 		initJConfirm() {
 | ||
| 			// jquery-confirm defaults.
 | ||
| 			jconfirm.defaults = {
 | ||
| 				closeIcon: false,
 | ||
| 				backgroundDismiss: false,
 | ||
| 				escapeKey: true,
 | ||
| 				animationBounce: 1,
 | ||
| 				useBootstrap: false,
 | ||
| 				theme: 'modern',
 | ||
| 				boxWidth: '400px',
 | ||
| 				animateFromElement: false,
 | ||
| 			};
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get a fresh list of forms via REST-API.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.4
 | ||
| 		 *
 | ||
| 		 * @see https://developer.wordpress.org/block-editor/reference-guides/packages/packages-api-fetch/
 | ||
| 		 */
 | ||
| 		async getForms() {
 | ||
| 			// If a fetch is already in progress, exit the function.
 | ||
| 			if ( isFetching ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			// Set the flag to true indicating a fetch is in progress.
 | ||
| 			isFetching = true;
 | ||
| 
 | ||
| 			try {
 | ||
| 				// Fetch forms.
 | ||
| 				formList = await wp.apiFetch( {
 | ||
| 					path: wpforms_gutenberg_form_selector.route_namespace + 'forms/',
 | ||
| 					method: 'GET',
 | ||
| 					cache: 'no-cache',
 | ||
| 				} );
 | ||
| 			} catch ( error ) {
 | ||
| 				// eslint-disable-next-line no-console
 | ||
| 				console.error( error );
 | ||
| 			} finally {
 | ||
| 				isFetching = false;
 | ||
| 			}
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Open builder popup.
 | ||
| 		 *
 | ||
| 		 * @since 1.6.2
 | ||
| 		 *
 | ||
| 		 * @param {string} clientID Block Client ID.
 | ||
| 		 */
 | ||
| 		openBuilderPopup( clientID ) {
 | ||
| 			if ( $.isEmptyObject( $popup ) ) {
 | ||
| 				const parent = $( '#wpwrap' );
 | ||
| 				const canvasIframe = $( 'iframe[name="editor-canvas"]' );
 | ||
| 				const isFseMode = Boolean( canvasIframe.length );
 | ||
| 				const tmpl = isFseMode ? canvasIframe.contents().find( '#wpforms-gutenberg-popup' ) : $( '#wpforms-gutenberg-popup' );
 | ||
| 
 | ||
| 				parent.after( tmpl );
 | ||
| 
 | ||
| 				$popup = parent.siblings( '#wpforms-gutenberg-popup' );
 | ||
| 			}
 | ||
| 
 | ||
| 			const url = wpforms_gutenberg_form_selector.get_started_url,
 | ||
| 				$iframe = $popup.find( 'iframe' );
 | ||
| 
 | ||
| 			app.builderCloseButtonEvent( clientID );
 | ||
| 			$iframe.attr( 'src', url );
 | ||
| 			$popup.fadeIn();
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Close button (inside the form builder) click event.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.3
 | ||
| 		 *
 | ||
| 		 * @param {string} clientID Block Client ID.
 | ||
| 		 */
 | ||
| 		builderCloseButtonEvent( clientID ) {
 | ||
| 			$popup
 | ||
| 				.off( 'wpformsBuilderInPopupClose' )
 | ||
| 				.on( 'wpformsBuilderInPopupClose', function( e, action, formId, formTitle ) {
 | ||
| 					if ( action !== 'saved' || ! formId ) {
 | ||
| 						return;
 | ||
| 					}
 | ||
| 
 | ||
| 					// Insert a new block when a new form is created from the popup to update the form list and attributes.
 | ||
| 					const newBlock = wp.blocks.createBlock( 'wpforms/form-selector', {
 | ||
| 						formId: formId.toString(), // Expects string value, make sure we insert string.
 | ||
| 					} );
 | ||
| 
 | ||
| 					// eslint-disable-next-line camelcase
 | ||
| 					formList = [ { ID: formId, post_title: formTitle } ];
 | ||
| 
 | ||
| 					// Insert a new block.
 | ||
| 					wp.data.dispatch( 'core/block-editor' ).removeBlock( clientID );
 | ||
| 					wp.data.dispatch( 'core/block-editor' ).insertBlocks( newBlock );
 | ||
| 				} );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Register block.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} blockOptions Additional block options.
 | ||
| 		 */
 | ||
| 		// eslint-disable-next-line max-lines-per-function
 | ||
| 		registerBlock( blockOptions ) {
 | ||
| 			registerBlockType( 'wpforms/form-selector', {
 | ||
| 				title: strings.title,
 | ||
| 				description: strings.description,
 | ||
| 				icon: app.getIcon(),
 | ||
| 				keywords: strings.form_keywords,
 | ||
| 				category: 'widgets',
 | ||
| 				attributes: app.getBlockAttributes(),
 | ||
| 				supports: {
 | ||
| 					customClassName: app.hasForms(),
 | ||
| 				},
 | ||
| 				example: {
 | ||
| 					attributes: {
 | ||
| 						preview: true,
 | ||
| 					},
 | ||
| 				},
 | ||
| 				// eslint-disable-next-line max-lines-per-function,complexity
 | ||
| 				edit( props ) {
 | ||
| 					const { attributes } = props;
 | ||
| 					const formOptions = app.getFormOptions();
 | ||
| 					const handlers = app.getSettingsFieldsHandlers( props );
 | ||
| 
 | ||
| 					const [ isNotDisabled ] = useState( isPro && isLicenseActive ); // eslint-disable-line react-hooks/rules-of-hooks
 | ||
| 					const [ isProEnabled ] = useState( isPro ); // eslint-disable-line react-hooks/rules-of-hooks, no-unused-vars
 | ||
| 					const [ showBackgroundPreview, setShowBackgroundPreview ] = useState( blockOptions.panels.background._showBackgroundPreview( props ) ); // eslint-disable-line react-hooks/rules-of-hooks
 | ||
| 					const [ lastBgImage, setLastBgImage ] = useState( '' ); // eslint-disable-line react-hooks/rules-of-hooks
 | ||
| 
 | ||
| 					const uiState = {
 | ||
| 						isNotDisabled,
 | ||
| 						isProEnabled,
 | ||
| 						showBackgroundPreview,
 | ||
| 						setShowBackgroundPreview,
 | ||
| 						lastBgImage,
 | ||
| 						setLastBgImage,
 | ||
| 					};
 | ||
| 
 | ||
| 					useEffect( () => { // eslint-disable-line react-hooks/rules-of-hooks
 | ||
| 						if ( attributes.formId ) {
 | ||
| 							setShowBackgroundPreview(
 | ||
| 								props.attributes.backgroundImage !== 'none' &&
 | ||
| 								props.attributes.backgroundUrl &&
 | ||
| 								props.attributes.backgroundUrl !== 'url()'
 | ||
| 							);
 | ||
| 						}
 | ||
| 					}, [ backgroundSelected, props.attributes.backgroundImage, props.attributes.backgroundUrl ] ); // eslint-disable-line react-hooks/exhaustive-deps
 | ||
| 
 | ||
| 					// Get block properties.
 | ||
| 					const blockProps = useBlockProps(); // eslint-disable-line react-hooks/rules-of-hooks, no-unused-vars
 | ||
| 
 | ||
| 					// Store block clientId in attributes.
 | ||
| 					if ( ! attributes.clientId || ! app.isClientIdAttrUnique( props ) ) {
 | ||
| 						// We just want the client ID to update once.
 | ||
| 						// The block editor doesn't have a fixed block ID, so we need to get it on the initial load, but only once.
 | ||
| 						props.setAttributes( { clientId: props.clientId } );
 | ||
| 					}
 | ||
| 
 | ||
| 					// Main block settings.
 | ||
| 					const jsx = [
 | ||
| 						app.jsxParts.getMainSettings( attributes, handlers, formOptions ),
 | ||
| 					];
 | ||
| 
 | ||
| 					// Block preview picture.
 | ||
| 					if ( ! app.hasForms() ) {
 | ||
| 						jsx.push(
 | ||
| 							app.jsxParts.getEmptyFormsPreview( props ),
 | ||
| 						);
 | ||
| 
 | ||
| 						return <div { ...blockProps }>{ jsx }</div>;
 | ||
| 					}
 | ||
| 
 | ||
| 					const sizeOptions = app.getSizeOptions();
 | ||
| 
 | ||
| 					// Show placeholder when form is not available (trashed, deleted etc.).
 | ||
| 					if ( attributes && attributes.formId && app.isFormAvailable( attributes.formId ) === false ) {
 | ||
| 						// Block placeholder (form selector).
 | ||
| 						jsx.push(
 | ||
| 							app.jsxParts.getBlockPlaceholder( props.attributes, handlers, formOptions ),
 | ||
| 						);
 | ||
| 
 | ||
| 						return <div { ...blockProps }>{ jsx }</div>;
 | ||
| 					}
 | ||
| 
 | ||
| 					// Form style settings & block content.
 | ||
| 					if ( attributes.formId ) {
 | ||
| 						// Subscribe to block events.
 | ||
| 						app.maybeSubscribeToBlockEvents( props, handlers, blockOptions );
 | ||
| 
 | ||
| 						jsx.push(
 | ||
| 							app.jsxParts.getStyleSettings( props, handlers, sizeOptions, blockOptions, uiState ),
 | ||
| 							app.jsxParts.getBlockFormContent( props )
 | ||
| 						);
 | ||
| 
 | ||
| 						if ( ! isCopyPasteGeneratedOnEdit ) {
 | ||
| 							handlers.updateCopyPasteContent();
 | ||
| 
 | ||
| 							isCopyPasteGeneratedOnEdit = true;
 | ||
| 						}
 | ||
| 
 | ||
| 						el.$window.trigger( 'wpformsFormSelectorEdit', [ props ] );
 | ||
| 
 | ||
| 						return <div { ...blockProps }>{ jsx }</div>;
 | ||
| 					}
 | ||
| 
 | ||
| 					// Block preview picture.
 | ||
| 					if ( attributes.preview ) {
 | ||
| 						jsx.push(
 | ||
| 							app.jsxParts.getBlockPreview(),
 | ||
| 						);
 | ||
| 
 | ||
| 						return <div { ...blockProps }>{ jsx }</div>;
 | ||
| 					}
 | ||
| 
 | ||
| 					// Block placeholder (form selector).
 | ||
| 					jsx.push(
 | ||
| 						app.jsxParts.getBlockPlaceholder( props.attributes, handlers, formOptions ),
 | ||
| 					);
 | ||
| 
 | ||
| 					return <div { ...blockProps }>{ jsx }</div>;
 | ||
| 				},
 | ||
| 				save: () => null,
 | ||
| 			} );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Init default style settings.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 * @since 1.8.8 Added blockOptions parameter.
 | ||
| 		 *
 | ||
| 		 * @param {Object} blockOptions Additional block options.
 | ||
| 		 */
 | ||
| 		initDefaults( blockOptions = {} ) {
 | ||
| 			commonAttributes = {
 | ||
| 				...commonAttributes,
 | ||
| 				...blockOptions.getCommonAttributes(),
 | ||
| 			};
 | ||
| 			customStylesHandlers = blockOptions.setStylesHandlers;
 | ||
| 
 | ||
| 			[ 'formId', 'copyPasteJsonValue' ].forEach( ( key ) => delete defaultStyleSettings[ key ] );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Check if the site has forms.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.3
 | ||
| 		 *
 | ||
| 		 * @return {boolean} Whether site has at least one form.
 | ||
| 		 */
 | ||
| 		hasForms() {
 | ||
| 			return formList.length > 0;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Check if form is available to be previewed.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.9
 | ||
| 		 *
 | ||
| 		 * @param {number} formId Form ID.
 | ||
| 		 *
 | ||
| 		 * @return {boolean} Whether form is available.
 | ||
| 		 */
 | ||
| 		isFormAvailable( formId ) {
 | ||
| 			return formList.find( ( { ID } ) => ID === Number( formId ) ) !== undefined;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Set triggerServerRender flag.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {boolean} $flag The value of the triggerServerRender flag.
 | ||
| 		 */
 | ||
| 		setTriggerServerRender( $flag ) {
 | ||
| 			triggerServerRender = Boolean( $flag );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Maybe subscribe to block events.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {Object} subscriberProps        Subscriber block properties.
 | ||
| 		 * @param {Object} subscriberHandlers     Subscriber block event handlers.
 | ||
| 		 * @param {Object} subscriberBlockOptions Subscriber block options.
 | ||
| 		 */
 | ||
| 		maybeSubscribeToBlockEvents( subscriberProps, subscriberHandlers, subscriberBlockOptions ) {
 | ||
| 			const id = subscriberProps.clientId;
 | ||
| 
 | ||
| 			// Unsubscribe from block events.
 | ||
| 			// This is needed to avoid multiple subscriptions when the block is re-rendered.
 | ||
| 			el.$window
 | ||
| 				.off( 'wpformsFormSelectorDeleteTheme.' + id )
 | ||
| 				.off( 'wpformsFormSelectorUpdateTheme.' + id )
 | ||
| 				.off( 'wpformsFormSelectorSetTheme.' + id );
 | ||
| 
 | ||
| 			// Subscribe to block events.
 | ||
| 			el.$window
 | ||
| 				.on( 'wpformsFormSelectorDeleteTheme.' + id, app.subscriberDeleteTheme( subscriberProps, subscriberBlockOptions ) )
 | ||
| 				.on( 'wpformsFormSelectorUpdateTheme.' + id, app.subscriberUpdateTheme( subscriberProps, subscriberBlockOptions ) )
 | ||
| 				.on( 'wpformsFormSelectorSetTheme.' + id, app.subscriberSetTheme( subscriberProps, subscriberBlockOptions ) );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Block event `wpformsFormSelectorDeleteTheme` handler.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {Object} subscriberProps        Subscriber block properties
 | ||
| 		 * @param {Object} subscriberBlockOptions Subscriber block options.
 | ||
| 		 *
 | ||
| 		 * @return {Function} Event handler.
 | ||
| 		 */
 | ||
| 		subscriberDeleteTheme( subscriberProps, subscriberBlockOptions ) {
 | ||
| 			return function( e, themeSlug, triggerProps ) {
 | ||
| 				if ( subscriberProps.clientId === triggerProps.clientId ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ( subscriberProps?.attributes?.theme !== themeSlug ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ( ! subscriberBlockOptions?.panels?.themes ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				// Reset theme to default one.
 | ||
| 				subscriberBlockOptions.panels.themes.setBlockTheme( subscriberProps, 'default' );
 | ||
| 			};
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Block event `wpformsFormSelectorDeleteTheme` handler.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {Object} subscriberProps        Subscriber block properties
 | ||
| 		 * @param {Object} subscriberBlockOptions Subscriber block options.
 | ||
| 		 *
 | ||
| 		 * @return {Function} Event handler.
 | ||
| 		 */
 | ||
| 		subscriberUpdateTheme( subscriberProps, subscriberBlockOptions ) {
 | ||
| 			return function( e, themeSlug, themeData, triggerProps ) {
 | ||
| 				if ( subscriberProps.clientId === triggerProps.clientId ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ( subscriberProps?.attributes?.theme !== themeSlug ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ( ! subscriberBlockOptions?.panels?.themes ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				// Reset theme to default one.
 | ||
| 				subscriberBlockOptions.panels.themes.setBlockTheme( subscriberProps, themeSlug );
 | ||
| 			};
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Block event `wpformsFormSelectorSetTheme` handler.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {Object} subscriberProps        Subscriber block properties
 | ||
| 		 * @param {Object} subscriberBlockOptions Subscriber block options.
 | ||
| 		 *
 | ||
| 		 * @return {Function} Event handler.
 | ||
| 		 */
 | ||
| 		subscriberSetTheme( subscriberProps, subscriberBlockOptions ) {
 | ||
| 			// noinspection JSUnusedLocalSymbols
 | ||
| 			return function( e, block, themeSlug, triggerProps ) { // eslint-disable-line no-unused-vars
 | ||
| 				if ( subscriberProps.clientId === triggerProps.clientId ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ( ! subscriberBlockOptions?.panels?.themes ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				// Set theme.
 | ||
| 				app.onSetTheme( subscriberProps );
 | ||
| 			};
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Block JSX parts.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @type {Object}
 | ||
| 		 */
 | ||
| 		jsxParts: {
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get main settings JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.1
 | ||
| 			 *
 | ||
| 			 * @param {Object} attributes  Block attributes.
 | ||
| 			 * @param {Object} handlers    Block event handlers.
 | ||
| 			 * @param {Object} formOptions Form selector options.
 | ||
| 			 *
 | ||
| 			 * @return {JSX.Element} Main setting JSX code.
 | ||
| 			 */
 | ||
| 			getMainSettings( attributes, handlers, formOptions ) {
 | ||
| 				if ( ! app.hasForms() ) {
 | ||
| 					return app.jsxParts.printEmptyFormsNotice( attributes.clientId );
 | ||
| 				}
 | ||
| 
 | ||
| 				return (
 | ||
| 					<InspectorControls key="wpforms-gutenberg-form-selector-inspector-main-settings">
 | ||
| 						<PanelBody className="wpforms-gutenberg-panel wpforms-gutenberg-panel-form-settings" title={ strings.form_settings }>
 | ||
| 							<SelectControl
 | ||
| 								label={ strings.form_selected }
 | ||
| 								value={ attributes.formId }
 | ||
| 								options={ formOptions }
 | ||
| 								onChange={ ( value ) => handlers.attrChange( 'formId', value ) }
 | ||
| 							/>
 | ||
| 							{ attributes.formId ? (
 | ||
| 								<>
 | ||
| 									<p className="wpforms-gutenberg-form-selector-actions">
 | ||
| 										<a href={ urls.form_url.replace( '{ID}', attributes.formId ) } rel="noreferrer" target="_blank">
 | ||
| 											{ strings.form_edit }
 | ||
| 										</a>
 | ||
| 										{ isPro && isLicenseActive && (
 | ||
| 											<>
 | ||
| 												  |  
 | ||
| 												<a
 | ||
| 													href={ urls.entries_url.replace( '{ID}', attributes.formId ) }
 | ||
| 													rel="noreferrer"
 | ||
| 													target="_blank"
 | ||
| 												>{ strings.form_entries }</a>
 | ||
| 											</>
 | ||
| 										) }
 | ||
| 									</p>
 | ||
| 									<ToggleControl
 | ||
| 										label={ strings.show_title }
 | ||
| 										checked={ attributes.displayTitle }
 | ||
| 										onChange={ ( value ) => handlers.attrChange( 'displayTitle', value ) }
 | ||
| 									/>
 | ||
| 									<ToggleControl
 | ||
| 										label={ strings.show_description }
 | ||
| 										checked={ attributes.displayDesc }
 | ||
| 										onChange={ ( value ) => handlers.attrChange( 'displayDesc', value ) }
 | ||
| 									/>
 | ||
| 								</>
 | ||
| 							) : null }
 | ||
| 							<p className="wpforms-gutenberg-panel-notice">
 | ||
| 								<strong>{ strings.panel_notice_head }</strong>
 | ||
| 								{ strings.panel_notice_text }
 | ||
| 								<a href={ strings.panel_notice_link } rel="noreferrer" target="_blank">{ strings.panel_notice_link_text }</a>
 | ||
| 							</p>
 | ||
| 						</PanelBody>
 | ||
| 					</InspectorControls>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Print empty forms notice.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.3
 | ||
| 			 *
 | ||
| 			 * @param {string} clientId Block client ID.
 | ||
| 			 *
 | ||
| 			 * @return {JSX.Element} Field styles JSX code.
 | ||
| 			 */
 | ||
| 			printEmptyFormsNotice( clientId ) {
 | ||
| 				return (
 | ||
| 					<InspectorControls key="wpforms-gutenberg-form-selector-inspector-main-settings">
 | ||
| 						<PanelBody className="wpforms-gutenberg-panel" title={ strings.form_settings }>
 | ||
| 							<p className="wpforms-gutenberg-panel-notice wpforms-warning wpforms-empty-form-notice" style={ { display: 'block' } }>
 | ||
| 								<strong>{ __( 'You haven’t created a form, yet!', 'wpforms-lite' ) }</strong>
 | ||
| 								{ __( 'What are you waiting for?', 'wpforms-lite' ) }
 | ||
| 							</p>
 | ||
| 							<button type="button" className="get-started-button components-button is-secondary"
 | ||
| 								onClick={
 | ||
| 									() => {
 | ||
| 										app.openBuilderPopup( clientId );
 | ||
| 									}
 | ||
| 								}
 | ||
| 							>
 | ||
| 								{ __( 'Get Started', 'wpforms-lite' ) }
 | ||
| 							</button>
 | ||
| 						</PanelBody>
 | ||
| 					</InspectorControls>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get Label styles JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.1
 | ||
| 			 *
 | ||
| 			 * @param {Object} props       Block properties.
 | ||
| 			 * @param {Object} handlers    Block event handlers.
 | ||
| 			 * @param {Object} sizeOptions Size selector options.
 | ||
| 			 *
 | ||
| 			 * @return {Object} Label styles JSX code.
 | ||
| 			 */
 | ||
| 			getLabelStyles( props, handlers, sizeOptions ) {
 | ||
| 				return (
 | ||
| 					<PanelBody className={ app.getPanelClass( props ) } title={ strings.label_styles }>
 | ||
| 						<SelectControl
 | ||
| 							label={ strings.size }
 | ||
| 							value={ props.attributes.labelSize }
 | ||
| 							className="wpforms-gutenberg-form-selector-fix-bottom-margin"
 | ||
| 							options={ sizeOptions }
 | ||
| 							onChange={ ( value ) => handlers.styleAttrChange( 'labelSize', value ) }
 | ||
| 						/>
 | ||
| 
 | ||
| 						<div className="wpforms-gutenberg-form-selector-color-picker">
 | ||
| 							<div className="wpforms-gutenberg-form-selector-control-label">{ strings.colors }</div>
 | ||
| 							<PanelColorSettings
 | ||
| 								__experimentalIsRenderedInSidebar
 | ||
| 								enableAlpha
 | ||
| 								showTitle={ false }
 | ||
| 								className="wpforms-gutenberg-form-selector-color-panel"
 | ||
| 								colorSettings={ [
 | ||
| 									{
 | ||
| 										value: props.attributes.labelColor,
 | ||
| 										onChange: ( value ) => handlers.styleAttrChange( 'labelColor', value ),
 | ||
| 										label: strings.label,
 | ||
| 									},
 | ||
| 									{
 | ||
| 										value: props.attributes.labelSublabelColor,
 | ||
| 										onChange: ( value ) => handlers.styleAttrChange( 'labelSublabelColor', value ),
 | ||
| 										label: strings.sublabel_hints.replace( '&', '&' ),
 | ||
| 									},
 | ||
| 									{
 | ||
| 										value: props.attributes.labelErrorColor,
 | ||
| 										onChange: ( value ) => handlers.styleAttrChange( 'labelErrorColor', value ),
 | ||
| 										label: strings.error_message,
 | ||
| 									},
 | ||
| 								] }
 | ||
| 							/>
 | ||
| 						</div>
 | ||
| 					</PanelBody>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get Page Indicator styles JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.7
 | ||
| 			 *
 | ||
| 			 * @param {Object} props    Block properties.
 | ||
| 			 * @param {Object} handlers Block event handlers.
 | ||
| 			 *
 | ||
| 			 * @return {Object} Page Indicator styles JSX code.
 | ||
| 			 */
 | ||
| 			getPageIndicatorStyles( props, handlers ) { // eslint-disable-line complexity
 | ||
| 				const hasPageBreak = app.hasPageBreak( formList, props.attributes.formId );
 | ||
| 				const hasRating = app.hasRating( formList, props.attributes.formId );
 | ||
| 
 | ||
| 				if ( ! hasPageBreak && ! hasRating ) {
 | ||
| 					return null;
 | ||
| 				}
 | ||
| 
 | ||
| 				let label = '';
 | ||
| 				if ( hasPageBreak && hasRating ) {
 | ||
| 					label = `${ strings.page_break } / ${ strings.rating }`;
 | ||
| 				} else if ( hasPageBreak ) {
 | ||
| 					label = strings.page_break;
 | ||
| 				} else if ( hasRating ) {
 | ||
| 					label = strings.rating;
 | ||
| 				}
 | ||
| 
 | ||
| 				return (
 | ||
| 					<PanelBody className={ app.getPanelClass( props ) } title={ strings.other_styles }>
 | ||
| 						<div className="wpforms-gutenberg-form-selector-color-picker">
 | ||
| 							<div className="wpforms-gutenberg-form-selector-control-label">{ strings.colors }</div>
 | ||
| 							<PanelColorSettings
 | ||
| 								__experimentalIsRenderedInSidebar
 | ||
| 								enableAlpha
 | ||
| 								showTitle={ false }
 | ||
| 								className="wpforms-gutenberg-form-selector-color-panel"
 | ||
| 								colorSettings={ [
 | ||
| 									{
 | ||
| 										value: props.attributes.pageBreakColor,
 | ||
| 										onChange: ( value ) => handlers.styleAttrChange( 'pageBreakColor', value ),
 | ||
| 										label,
 | ||
| 									},
 | ||
| 								] } />
 | ||
| 						</div>
 | ||
| 					</PanelBody>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get style settings JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.1
 | ||
| 			 *
 | ||
| 			 * @param {Object} props        Block properties.
 | ||
| 			 * @param {Object} handlers     Block event handlers.
 | ||
| 			 * @param {Object} sizeOptions  Size selector options.
 | ||
| 			 * @param {Object} blockOptions Block options loaded from external modules.
 | ||
| 			 *
 | ||
| 			 * @param {Object} uiState 	UI state.
 | ||
| 			 *
 | ||
| 			 * @return {Object} Inspector controls JSX code.
 | ||
| 			 */
 | ||
| 			getStyleSettings( props, handlers, sizeOptions, blockOptions, uiState ) {
 | ||
| 				return (
 | ||
| 					<InspectorControls key="wpforms-gutenberg-form-selector-style-settings">
 | ||
| 						{ blockOptions.getThemesPanel( props, app, blockOptions.stockPhotos ) }
 | ||
| 						{ blockOptions.getFieldStyles( props, handlers, sizeOptions, app ) }
 | ||
| 						{ app.jsxParts.getLabelStyles( props, handlers, sizeOptions ) }
 | ||
| 						{ blockOptions.getButtonStyles( props, handlers, sizeOptions, app ) }
 | ||
| 						{ blockOptions.getContainerStyles( props, handlers, app, uiState ) }
 | ||
| 						{ blockOptions.getBackgroundStyles( props, handlers, app, blockOptions.stockPhotos, uiState ) }
 | ||
| 						{ app.jsxParts.getPageIndicatorStyles( props, handlers ) }
 | ||
| 					</InspectorControls>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get block content JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.1
 | ||
| 			 *
 | ||
| 			 * @param {Object} props Block properties.
 | ||
| 			 *
 | ||
| 			 * @return {JSX.Element} Block content JSX code.
 | ||
| 			 */
 | ||
| 			getBlockFormContent( props ) {
 | ||
| 				if ( triggerServerRender ) {
 | ||
| 					return (
 | ||
| 						<ServerSideRender
 | ||
| 							key="wpforms-gutenberg-form-selector-server-side-renderer"
 | ||
| 							block="wpforms/form-selector"
 | ||
| 							attributes={ props.attributes }
 | ||
| 						/>
 | ||
| 					);
 | ||
| 				}
 | ||
| 
 | ||
| 				const clientId = props.clientId;
 | ||
| 				const block = app.getBlockContainer( props );
 | ||
| 
 | ||
| 				// In the case of empty content, use server side renderer.
 | ||
| 				// This happens when the block is duplicated or converted to a reusable block.
 | ||
| 				if ( ! block?.innerHTML ) {
 | ||
| 					triggerServerRender = true;
 | ||
| 
 | ||
| 					return app.jsxParts.getBlockFormContent( props );
 | ||
| 				}
 | ||
| 
 | ||
| 				blocks[ clientId ] = blocks[ clientId ] || {};
 | ||
| 				blocks[ clientId ].blockHTML = block.innerHTML;
 | ||
| 				blocks[ clientId ].loadedFormId = props.attributes.formId;
 | ||
| 
 | ||
| 				return (
 | ||
| 					<Fragment key="wpforms-gutenberg-form-selector-fragment-form-html">
 | ||
| 						<div dangerouslySetInnerHTML={ { __html: blocks[ clientId ].blockHTML } } />
 | ||
| 					</Fragment>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get block preview JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.1
 | ||
| 			 *
 | ||
| 			 * @return {JSX.Element} Block preview JSX code.
 | ||
| 			 */
 | ||
| 			getBlockPreview() {
 | ||
| 				return (
 | ||
| 					<Fragment
 | ||
| 						key="wpforms-gutenberg-form-selector-fragment-block-preview">
 | ||
| 						<img src={ wpforms_gutenberg_form_selector.block_preview_url } style={ { width: '100%' } } alt="" />
 | ||
| 					</Fragment>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get block empty JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.3
 | ||
| 			 *
 | ||
| 			 * @param {Object} props Block properties.
 | ||
| 			 * @return {JSX.Element} Block empty JSX code.
 | ||
| 			 */
 | ||
| 			getEmptyFormsPreview( props ) {
 | ||
| 				const clientId = props.clientId;
 | ||
| 
 | ||
| 				return (
 | ||
| 					<Fragment
 | ||
| 						key="wpforms-gutenberg-form-selector-fragment-block-empty">
 | ||
| 						<div className="wpforms-no-form-preview">
 | ||
| 							<img src={ wpforms_gutenberg_form_selector.block_empty_url } alt="" />
 | ||
| 							<p>
 | ||
| 								{
 | ||
| 									createInterpolateElement(
 | ||
| 										__(
 | ||
| 											'You can use <b>WPForms</b> to build contact forms, surveys, payment forms, and more with just a few clicks.',
 | ||
| 											'wpforms-lite'
 | ||
| 										),
 | ||
| 										{
 | ||
| 											b: <strong />,
 | ||
| 										}
 | ||
| 									)
 | ||
| 								}
 | ||
| 							</p>
 | ||
| 							<button type="button" className="get-started-button components-button is-primary"
 | ||
| 								onClick={
 | ||
| 									() => {
 | ||
| 										app.openBuilderPopup( clientId );
 | ||
| 									}
 | ||
| 								}
 | ||
| 							>
 | ||
| 								{ __( 'Get Started', 'wpforms-lite' ) }
 | ||
| 							</button>
 | ||
| 							<p className="empty-desc">
 | ||
| 								{
 | ||
| 									createInterpolateElement(
 | ||
| 										__(
 | ||
| 											'Need some help? Check out our <a>comprehensive guide.</a>',
 | ||
| 											'wpforms-lite'
 | ||
| 										),
 | ||
| 										{
 | ||
| 											// eslint-disable-next-line jsx-a11y/anchor-has-content
 | ||
| 											a: <a href={ wpforms_gutenberg_form_selector.wpforms_guide } target="_blank" rel="noopener noreferrer" />,
 | ||
| 										}
 | ||
| 									)
 | ||
| 								}
 | ||
| 							</p>
 | ||
| 
 | ||
| 							{ /* Template for popup with builder iframe */ }
 | ||
| 							<div id="wpforms-gutenberg-popup" className="wpforms-builder-popup">
 | ||
| 								<iframe src="about:blank" width="100%" height="100%" id="wpforms-builder-iframe" title="WPForms Builder Popup"></iframe>
 | ||
| 							</div>
 | ||
| 						</div>
 | ||
| 					</Fragment>
 | ||
| 				);
 | ||
| 			},
 | ||
| 
 | ||
| 			/**
 | ||
| 			 * Get block placeholder (form selector) JSX code.
 | ||
| 			 *
 | ||
| 			 * @since 1.8.1
 | ||
| 			 *
 | ||
| 			 * @param {Object} attributes  Block attributes.
 | ||
| 			 * @param {Object} handlers    Block event handlers.
 | ||
| 			 * @param {Object} formOptions Form selector options.
 | ||
| 			 *
 | ||
| 			 * @return {JSX.Element} Block placeholder JSX code.
 | ||
| 			 */
 | ||
| 			getBlockPlaceholder( attributes, handlers, formOptions ) {
 | ||
| 				const isFormNotAvailable = attributes.formId && ! app.isFormAvailable( attributes.formId );
 | ||
| 
 | ||
| 				return (
 | ||
| 					<Placeholder
 | ||
| 						key="wpforms-gutenberg-form-selector-wrap"
 | ||
| 						className="wpforms-gutenberg-form-selector-wrap">
 | ||
| 						<img src={ wpforms_gutenberg_form_selector.logo_url } alt="" />
 | ||
| 						{ isFormNotAvailable && (
 | ||
| 							<p style={ { textAlign: 'center', marginTop: '0' } }>
 | ||
| 								{ strings.form_not_available_message }
 | ||
| 							</p>
 | ||
| 						) }
 | ||
| 						<SelectControl
 | ||
| 							key="wpforms-gutenberg-form-selector-select-control"
 | ||
| 							value={ attributes.formId }
 | ||
| 							options={ formOptions }
 | ||
| 							onChange={ ( value ) => handlers.attrChange( 'formId', value ) }
 | ||
| 						/>
 | ||
| 					</Placeholder>
 | ||
| 				);
 | ||
| 			},
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Determine if the form has a Page Break field.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.7
 | ||
| 		 *
 | ||
| 		 * @param {Object}        forms  The forms' data object.
 | ||
| 		 * @param {number|string} formId Form ID.
 | ||
| 		 *
 | ||
| 		 * @return {boolean} True when the form has a Page Break field, false otherwise.
 | ||
| 		 */
 | ||
| 		hasPageBreak( forms, formId ) {
 | ||
| 			const currentForm = forms.find( ( form ) => parseInt( form.ID, 10 ) === parseInt( formId, 10 ) );
 | ||
| 
 | ||
| 			if ( ! currentForm.post_content ) {
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 
 | ||
| 			const fields = JSON.parse( currentForm.post_content )?.fields;
 | ||
| 
 | ||
| 			return Object.values( fields ).some( ( field ) => field.type === 'pagebreak' );
 | ||
| 		},
 | ||
| 
 | ||
| 		hasRating( forms, formId ) {
 | ||
| 			const currentForm = forms.find( ( form ) => parseInt( form.ID, 10 ) === parseInt( formId, 10 ) );
 | ||
| 
 | ||
| 			if ( ! currentForm.post_content || ! isPro || ! isLicenseActive ) {
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 
 | ||
| 			const fields = JSON.parse( currentForm.post_content )?.fields;
 | ||
| 
 | ||
| 			return Object.values( fields ).some( ( field ) => field.type === 'rating' );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get Style Settings panel class.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} props Block properties.
 | ||
| 		 * @param {string} panel Panel name.
 | ||
| 		 *
 | ||
| 		 * @return {string} Style Settings panel class.
 | ||
| 		 */
 | ||
| 		getPanelClass( props, panel = '' ) {
 | ||
| 			let cssClass = 'wpforms-gutenberg-panel wpforms-block-settings-' + props.clientId;
 | ||
| 
 | ||
| 			if ( ! app.isFullStylingEnabled() ) {
 | ||
| 				cssClass += ' disabled_panel';
 | ||
| 			}
 | ||
| 
 | ||
| 			// Restrict styling panel for non-admins.
 | ||
| 			if ( ! ( isAdmin || panel === 'themes' ) ) {
 | ||
| 				cssClass += ' wpforms-gutenberg-panel-restricted';
 | ||
| 			}
 | ||
| 
 | ||
| 			return cssClass;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get color panel settings CSS class.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {string} borderStyle Border style value.
 | ||
| 		 *
 | ||
| 		 * @return {string} Style Settings panel class.
 | ||
| 		 */
 | ||
| 		getColorPanelClass( borderStyle ) {
 | ||
| 			let cssClass = 'wpforms-gutenberg-form-selector-color-panel';
 | ||
| 
 | ||
| 			if ( borderStyle === 'none' ) {
 | ||
| 				cssClass += ' wpforms-gutenberg-form-selector-border-color-disabled';
 | ||
| 			}
 | ||
| 
 | ||
| 			return cssClass;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Determine whether the full styling is enabled.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @return {boolean} Whether the full styling is enabled.
 | ||
| 		 */
 | ||
| 		isFullStylingEnabled() {
 | ||
| 			return wpforms_gutenberg_form_selector.is_modern_markup && wpforms_gutenberg_form_selector.is_full_styling;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Determine whether the block has lead forms enabled.
 | ||
| 		 *
 | ||
| 		 * @since 1.9.0
 | ||
| 		 *
 | ||
| 		 * @param {Object} block Gutenberg block
 | ||
| 		 *
 | ||
| 		 * @return {boolean} Whether the block has lead forms enabled
 | ||
| 		 */
 | ||
| 		isLeadFormsEnabled( block ) {
 | ||
| 			if ( ! block ) {
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 
 | ||
| 			const $form = $( block.querySelector( '.wpforms-container' ) );
 | ||
| 
 | ||
| 			return $form.hasClass( 'wpforms-lead-forms-container' );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get block container DOM element.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} props Block properties.
 | ||
| 		 *
 | ||
| 		 * @return {Element} Block container.
 | ||
| 		 */
 | ||
| 		getBlockContainer( props ) {
 | ||
| 			const blockSelector = `#block-${ props.clientId } > div`;
 | ||
| 			let block = document.querySelector( blockSelector );
 | ||
| 
 | ||
| 			// For FSE / Gutenberg plugin, we need to take a look inside the iframe.
 | ||
| 			if ( ! block ) {
 | ||
| 				const editorCanvas = document.querySelector( 'iframe[name="editor-canvas"]' );
 | ||
| 
 | ||
| 				block = editorCanvas?.contentWindow.document.querySelector( blockSelector );
 | ||
| 			}
 | ||
| 
 | ||
| 			return block;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get form container in Block Editor.
 | ||
| 		 *
 | ||
| 		 * @since 1.9.3
 | ||
| 		 *
 | ||
| 		 * @param {number} formId Form ID.
 | ||
| 		 *
 | ||
| 		 * @return {Element|null} Form container.
 | ||
| 		 */
 | ||
| 		getFormBlock( formId ) {
 | ||
| 			// First, try to find the iframe for blocks version 3.
 | ||
| 			const editorCanvas = document.querySelector( 'iframe[name="editor-canvas"]' );
 | ||
| 
 | ||
| 			// If the iframe is found, try to find the form.
 | ||
| 			return editorCanvas?.contentWindow.document.querySelector( `#wpforms-${ formId }` ) || $( `#wpforms-${ formId }` );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Update CSS variable(s) value(s) of the given attribute for given container on the preview.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {string}  attribute Style attribute: field-size, label-size, button-size, etc.
 | ||
| 		 * @param {string}  value     Property new value.
 | ||
| 		 * @param {Element} container Form container.
 | ||
| 		 * @param {Object}  props     Block properties.
 | ||
| 		 */
 | ||
| 		updatePreviewCSSVarValue( attribute, value, container, props ) { // eslint-disable-line complexity, max-lines-per-function
 | ||
| 			if ( ! container || ! attribute ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			const property = attribute.replace(
 | ||
| 				/[A-Z]/g,
 | ||
| 				( letter ) => `-${ letter.toLowerCase() }`
 | ||
| 			);
 | ||
| 
 | ||
| 			if ( typeof customStylesHandlers[ property ] === 'function' ) {
 | ||
| 				customStylesHandlers[ property ]( container, value );
 | ||
| 
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			switch ( property ) {
 | ||
| 				case 'field-size':
 | ||
| 				case 'label-size':
 | ||
| 				case 'button-size':
 | ||
| 				case 'container-shadow-size':
 | ||
| 					for ( const key in sizes[ property ][ value ] ) {
 | ||
| 						container.style.setProperty(
 | ||
| 							`--wpforms-${ property }-${ key }`,
 | ||
| 							sizes[ property ][ value ][ key ],
 | ||
| 						);
 | ||
| 					}
 | ||
| 
 | ||
| 					break;
 | ||
| 				case 'field-border-style':
 | ||
| 					if ( value === 'none' ) {
 | ||
| 						app.toggleFieldBorderNoneCSSVarValue( container, true );
 | ||
| 					} else {
 | ||
| 						app.toggleFieldBorderNoneCSSVarValue( container, false );
 | ||
| 						container.style.setProperty( `--wpforms-${ property }`, value );
 | ||
| 					}
 | ||
| 
 | ||
| 					break;
 | ||
| 				case 'button-background-color':
 | ||
| 					app.maybeUpdateAccentColor( props.attributes.buttonBorderColor, value, container );
 | ||
| 					value = app.maybeSetButtonAltBackgroundColor( value, props.attributes.buttonBorderColor, container );
 | ||
| 					app.maybeSetButtonAltTextColor( props.attributes.buttonTextColor, value, props.attributes.buttonBorderColor, container );
 | ||
| 					container.style.setProperty( `--wpforms-${ property }`, value );
 | ||
| 
 | ||
| 					break;
 | ||
| 				case 'button-border-color':
 | ||
| 					app.maybeUpdateAccentColor( value, props.attributes.buttonBackgroundColor, container );
 | ||
| 					app.maybeSetButtonAltTextColor( props.attributes.buttonTextColor, props.attributes.buttonBackgroundColor, value, container );
 | ||
| 					container.style.setProperty( `--wpforms-${ property }`, value );
 | ||
| 
 | ||
| 					break;
 | ||
| 				case 'button-text-color':
 | ||
| 					app.maybeSetButtonAltTextColor( value, props.attributes.buttonBackgroundColor, props.attributes.buttonBorderColor, container );
 | ||
| 					container.style.setProperty( `--wpforms-${ property }`, value );
 | ||
| 
 | ||
| 					break;
 | ||
| 				default:
 | ||
| 					container.style.setProperty( `--wpforms-${ property }`, value );
 | ||
| 					container.style.setProperty( `--wpforms-${ property }-spare`, value );
 | ||
| 			}
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Set/unset field border vars in case of border-style is none.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {Object}  container Form container.
 | ||
| 		 * @param {boolean} set       True when set, false when unset.
 | ||
| 		 */
 | ||
| 		toggleFieldBorderNoneCSSVarValue( container, set ) {
 | ||
| 			const cont = container.querySelector( 'form' );
 | ||
| 
 | ||
| 			if ( set ) {
 | ||
| 				cont.style.setProperty( '--wpforms-field-border-style', 'solid' );
 | ||
| 				cont.style.setProperty( '--wpforms-field-border-size', '1px' );
 | ||
| 				cont.style.setProperty( '--wpforms-field-border-color', 'transparent' );
 | ||
| 
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			cont.style.setProperty( '--wpforms-field-border-style', null );
 | ||
| 			cont.style.setProperty( '--wpforms-field-border-size', null );
 | ||
| 			cont.style.setProperty( '--wpforms-field-border-color', null );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Maybe set the button's alternative background color.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {string} value             Attribute value.
 | ||
| 		 * @param {string} buttonBorderColor Button border color.
 | ||
| 		 * @param {Object} container         Form container.
 | ||
| 		 *
 | ||
| 		 * @return {string|*} New background color.
 | ||
| 		 */
 | ||
| 		maybeSetButtonAltBackgroundColor( value, buttonBorderColor, container ) {
 | ||
| 			// Setting css property value to child `form` element overrides the parent property value.
 | ||
| 			const form = container.querySelector( 'form' );
 | ||
| 
 | ||
| 			form.style.setProperty( '--wpforms-button-background-color-alt', value );
 | ||
| 
 | ||
| 			if ( WPFormsUtils.cssColorsUtils.isTransparentColor( value ) ) {
 | ||
| 				return WPFormsUtils.cssColorsUtils.isTransparentColor( buttonBorderColor ) ? defaultStyleSettings.buttonBackgroundColor : buttonBorderColor;
 | ||
| 			}
 | ||
| 
 | ||
| 			return value;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Maybe set the button's alternative text color.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {string} value                 Attribute value.
 | ||
| 		 * @param {string} buttonBackgroundColor Button background color.
 | ||
| 		 * @param {string} buttonBorderColor     Button border color.
 | ||
| 		 * @param {Object} container             Form container.
 | ||
| 		 */
 | ||
| 		maybeSetButtonAltTextColor( value, buttonBackgroundColor, buttonBorderColor, container ) {
 | ||
| 			const form = container.querySelector( 'form' );
 | ||
| 
 | ||
| 			let altColor = null;
 | ||
| 
 | ||
| 			value = value.toLowerCase();
 | ||
| 
 | ||
| 			if (
 | ||
| 				WPFormsUtils.cssColorsUtils.isTransparentColor( value ) ||
 | ||
| 				value === buttonBackgroundColor ||
 | ||
| 				(
 | ||
| 					WPFormsUtils.cssColorsUtils.isTransparentColor( buttonBackgroundColor ) &&
 | ||
| 					value === buttonBorderColor
 | ||
| 				)
 | ||
| 			) {
 | ||
| 				altColor = WPFormsUtils.cssColorsUtils.getContrastColor( buttonBackgroundColor );
 | ||
| 			}
 | ||
| 
 | ||
| 			container.style.setProperty( `--wpforms-button-text-color-alt`, value );
 | ||
| 			form.style.setProperty( `--wpforms-button-text-color-alt`, altColor );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Maybe update accent color.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {string} color                 Color value.
 | ||
| 		 * @param {string} buttonBackgroundColor Button background color.
 | ||
| 		 * @param {Object} container             Form container.
 | ||
| 		 */
 | ||
| 		maybeUpdateAccentColor( color, buttonBackgroundColor, container ) {
 | ||
| 			// Setting css property value to child `form` element overrides the parent property value.
 | ||
| 			const form = container.querySelector( 'form' );
 | ||
| 
 | ||
| 			// Fallback to default color if the border color is transparent.
 | ||
| 			color = WPFormsUtils.cssColorsUtils.isTransparentColor( color ) ? defaultStyleSettings.buttonBackgroundColor : color;
 | ||
| 
 | ||
| 			if ( WPFormsUtils.cssColorsUtils.isTransparentColor( buttonBackgroundColor ) ) {
 | ||
| 				form.style.setProperty( '--wpforms-button-background-color-alt', 'rgba( 0, 0, 0, 0 )' );
 | ||
| 				form.style.setProperty( '--wpforms-button-background-color', color );
 | ||
| 			} else {
 | ||
| 				container.style.setProperty( '--wpforms-button-background-color-alt', buttonBackgroundColor );
 | ||
| 				form.style.setProperty( '--wpforms-button-background-color-alt', null );
 | ||
| 				form.style.setProperty( '--wpforms-button-background-color', null );
 | ||
| 			}
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get settings fields event handlers.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} props Block properties.
 | ||
| 		 *
 | ||
| 		 * @return {Object} Object that contains event handlers for the settings fields.
 | ||
| 		 */
 | ||
| 		getSettingsFieldsHandlers( props ) { // eslint-disable-line max-lines-per-function
 | ||
| 			return {
 | ||
| 				/**
 | ||
| 				 * Field style attribute change event handler.
 | ||
| 				 *
 | ||
| 				 * @since 1.8.1
 | ||
| 				 *
 | ||
| 				 * @param {string} attribute Attribute name.
 | ||
| 				 * @param {string} value     New attribute value.
 | ||
| 				 */
 | ||
| 				styleAttrChange( attribute, value ) {
 | ||
| 					const block = app.getBlockContainer( props ),
 | ||
| 						container = block.querySelector( `#wpforms-${ props.attributes.formId }` ),
 | ||
| 						setAttr = {};
 | ||
| 
 | ||
| 					// Unset the color means setting the transparent color.
 | ||
| 					if ( attribute.includes( 'Color' ) ) {
 | ||
| 						value = value ?? 'rgba( 0, 0, 0, 0 )';
 | ||
| 					}
 | ||
| 
 | ||
| 					app.updatePreviewCSSVarValue( attribute, value, container, props );
 | ||
| 
 | ||
| 					setAttr[ attribute ] = value;
 | ||
| 
 | ||
| 					app.setBlockRuntimeStateVar( props.clientId, 'prevAttributesState', props.attributes );
 | ||
| 					props.setAttributes( setAttr );
 | ||
| 
 | ||
| 					triggerServerRender = false;
 | ||
| 
 | ||
| 					this.updateCopyPasteContent();
 | ||
| 
 | ||
| 					app.panels.themes.updateCustomThemeAttribute( attribute, value, props );
 | ||
| 
 | ||
| 					this.maybeToggleDropdown( props, attribute );
 | ||
| 
 | ||
| 					// Trigger event for developers.
 | ||
| 					el.$window.trigger( 'wpformsFormSelectorStyleAttrChange', [ block, props, attribute, value ] );
 | ||
| 				},
 | ||
| 
 | ||
| 				/**
 | ||
| 				 * Handles the toggling of the dropdown menu's visibility.
 | ||
| 				 *
 | ||
| 				 * @since 1.8.8
 | ||
| 				 *
 | ||
| 				 * @param {Object} props     The block properties.
 | ||
| 				 * @param {string} attribute The name of the attribute being changed.
 | ||
| 				 */
 | ||
| 				maybeToggleDropdown( props, attribute ) { // eslint-disable-line no-shadow
 | ||
| 					const formId = props.attributes.formId;
 | ||
| 					const menu = document.querySelector( `#wpforms-form-${ formId } .choices__list.choices__list--dropdown` );
 | ||
| 					const classicMenu = document.querySelector( `#wpforms-form-${ formId } .wpforms-field-select-style-classic select` );
 | ||
| 
 | ||
| 					if ( attribute === 'fieldMenuColor' ) {
 | ||
| 						if ( menu ) {
 | ||
| 							menu.classList.add( 'is-active' );
 | ||
| 							menu.parentElement.classList.add( 'is-open' );
 | ||
| 						} else {
 | ||
| 							this.showClassicMenu( classicMenu );
 | ||
| 						}
 | ||
| 
 | ||
| 						clearTimeout( dropdownTimeout );
 | ||
| 
 | ||
| 						dropdownTimeout = setTimeout( () => {
 | ||
| 							const toClose = document.querySelector( `#wpforms-form-${ formId } .choices__list.choices__list--dropdown` );
 | ||
| 
 | ||
| 							if ( toClose ) {
 | ||
| 								toClose.classList.remove( 'is-active' );
 | ||
| 								toClose.parentElement.classList.remove( 'is-open' );
 | ||
| 							} else {
 | ||
| 								this.hideClassicMenu( document.querySelector( `#wpforms-form-${ formId } .wpforms-field-select-style-classic select` ) );
 | ||
| 							}
 | ||
| 						}, 5000 );
 | ||
| 					} else if ( menu ) {
 | ||
| 						menu.classList.remove( 'is-active' );
 | ||
| 					} else {
 | ||
| 						this.hideClassicMenu( classicMenu );
 | ||
| 					}
 | ||
| 				},
 | ||
| 
 | ||
| 				/**
 | ||
| 				 * Shows the classic menu.
 | ||
| 				 *
 | ||
| 				 * @since 1.8.8
 | ||
| 				 *
 | ||
| 				 * @param {Object} classicMenu The classic menu.
 | ||
| 				 */
 | ||
| 				showClassicMenu( classicMenu ) {
 | ||
| 					if ( ! classicMenu ) {
 | ||
| 						return;
 | ||
| 					}
 | ||
| 
 | ||
| 					classicMenu.size = 2;
 | ||
| 					classicMenu.style.cssText = 'padding-top: 40px; padding-inline-end: 0; padding-inline-start: 0; position: relative;';
 | ||
| 					classicMenu.querySelectorAll( 'option' ).forEach( ( option ) => {
 | ||
| 						option.style.cssText = 'border-left: 1px solid #8c8f94; border-right: 1px solid #8c8f94; padding: 0 10px; z-index: 999999; position: relative;';
 | ||
| 					} );
 | ||
| 					classicMenu.querySelector( 'option:last-child' ).style.cssText = 'border-bottom-left-radius: 4px; border-bottom-right-radius: 4px; padding: 0 10px; border-left: 1px solid #8c8f94; border-right: 1px solid #8c8f94; border-bottom: 1px solid #8c8f94; z-index: 999999; position: relative;';
 | ||
| 				},
 | ||
| 
 | ||
| 				/**
 | ||
| 				 * Hides the classic menu.
 | ||
| 				 *
 | ||
| 				 * @since 1.8.8
 | ||
| 				 *
 | ||
| 				 * @param {Object} classicMenu The classic menu.
 | ||
| 				 */
 | ||
| 				hideClassicMenu( classicMenu ) {
 | ||
| 					if ( ! classicMenu ) {
 | ||
| 						return;
 | ||
| 					}
 | ||
| 
 | ||
| 					classicMenu.size = 0;
 | ||
| 					classicMenu.style.cssText = 'padding-top: 0; padding-inline-end: 24px; padding-inline-start: 12px; position: relative;';
 | ||
| 					classicMenu.querySelectorAll( 'option' ).forEach( ( option ) => {
 | ||
| 						option.style.cssText = 'border: none;';
 | ||
| 					} );
 | ||
| 				},
 | ||
| 
 | ||
| 				/**
 | ||
| 				 * Field regular attribute change event handler.
 | ||
| 				 *
 | ||
| 				 * @since 1.8.1
 | ||
| 				 *
 | ||
| 				 * @param {string} attribute Attribute name.
 | ||
| 				 * @param {string} value     New attribute value.
 | ||
| 				 */
 | ||
| 				attrChange( attribute, value ) {
 | ||
| 					const setAttr = {};
 | ||
| 
 | ||
| 					setAttr[ attribute ] = value;
 | ||
| 
 | ||
| 					app.setBlockRuntimeStateVar( props.clientId, 'prevAttributesState', props.attributes );
 | ||
| 					props.setAttributes( setAttr );
 | ||
| 
 | ||
| 					triggerServerRender = true;
 | ||
| 
 | ||
| 					this.updateCopyPasteContent();
 | ||
| 				},
 | ||
| 
 | ||
| 				/**
 | ||
| 				 * Update content of the "Copy/Paste" fields.
 | ||
| 				 *
 | ||
| 				 * @since 1.8.1
 | ||
| 				 */
 | ||
| 				updateCopyPasteContent() {
 | ||
| 					const content = {};
 | ||
| 					const atts = wp.data.select( 'core/block-editor' ).getBlockAttributes( props.clientId );
 | ||
| 
 | ||
| 					for ( const key in defaultStyleSettings ) {
 | ||
| 						content[ key ] = atts[ key ];
 | ||
| 					}
 | ||
| 
 | ||
| 					props.setAttributes( { copyPasteJsonValue: JSON.stringify( content ) } );
 | ||
| 				},
 | ||
| 
 | ||
| 				/**
 | ||
| 				 * Paste settings handler.
 | ||
| 				 *
 | ||
| 				 * @since 1.8.1
 | ||
| 				 *
 | ||
| 				 * @param {string} value New attribute value.
 | ||
| 				 */
 | ||
| 				pasteSettings( value ) {
 | ||
| 					value = value.trim();
 | ||
| 
 | ||
| 					const pasteAttributes = app.parseValidateJson( value );
 | ||
| 
 | ||
| 					if ( ! pasteAttributes ) {
 | ||
| 						if ( value ) {
 | ||
| 							wp.data.dispatch( 'core/notices' ).createErrorNotice(
 | ||
| 								strings.copy_paste_error,
 | ||
| 								{ id: 'wpforms-json-parse-error' }
 | ||
| 							);
 | ||
| 						}
 | ||
| 
 | ||
| 						this.updateCopyPasteContent();
 | ||
| 
 | ||
| 						return;
 | ||
| 					}
 | ||
| 
 | ||
| 					pasteAttributes.copyPasteJsonValue = value;
 | ||
| 
 | ||
| 					const themeSlug = app.panels.themes.maybeCreateCustomThemeFromAttributes( pasteAttributes );
 | ||
| 
 | ||
| 					app.setBlockRuntimeStateVar( props.clientId, 'prevAttributesState', props.attributes );
 | ||
| 					props.setAttributes( pasteAttributes );
 | ||
| 					app.panels.themes.setBlockTheme( props, themeSlug );
 | ||
| 
 | ||
| 					triggerServerRender = false;
 | ||
| 				},
 | ||
| 			};
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Parse and validate JSON string.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {string} value JSON string.
 | ||
| 		 *
 | ||
| 		 * @return {boolean|object} Parsed JSON object OR false on error.
 | ||
| 		 */
 | ||
| 		parseValidateJson( value ) {
 | ||
| 			if ( typeof value !== 'string' ) {
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 
 | ||
| 			let atts;
 | ||
| 
 | ||
| 			try {
 | ||
| 				atts = JSON.parse( value.trim() );
 | ||
| 			} catch ( error ) {
 | ||
| 				atts = false;
 | ||
| 			}
 | ||
| 
 | ||
| 			return atts;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get WPForms icon DOM element.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @return {DOM.element} WPForms icon DOM element.
 | ||
| 		 */
 | ||
| 		getIcon() {
 | ||
| 			return createElement(
 | ||
| 				'svg',
 | ||
| 				{ width: 20, height: 20, viewBox: '0 0 612 612', className: 'dashicon' },
 | ||
| 				createElement(
 | ||
| 					'path',
 | ||
| 					{
 | ||
| 						fill: 'currentColor',
 | ||
| 						d: 'M544,0H68C30.445,0,0,30.445,0,68v476c0,37.556,30.445,68,68,68h476c37.556,0,68-30.444,68-68V68 C612,30.445,581.556,0,544,0z M464.44,68L387.6,120.02L323.34,68H464.44z M288.66,68l-64.26,52.02L147.56,68H288.66z M544,544H68 V68h22.1l136,92.14l79.9-64.6l79.56,64.6l136-92.14H544V544z M114.24,263.16h95.88v-48.28h-95.88V263.16z M114.24,360.4h95.88 v-48.62h-95.88V360.4z M242.76,360.4h255v-48.62h-255V360.4L242.76,360.4z M242.76,263.16h255v-48.28h-255V263.16L242.76,263.16z M368.22,457.3h129.54V408H368.22V457.3z',
 | ||
| 					},
 | ||
| 				),
 | ||
| 			);
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get WPForms blocks.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @return {Array} Blocks array.
 | ||
| 		 */
 | ||
| 		getWPFormsBlocks() {
 | ||
| 			const wpformsBlocks = wp.data.select( 'core/block-editor' ).getBlocks();
 | ||
| 
 | ||
| 			return wpformsBlocks.filter( ( props ) => {
 | ||
| 				return props.name === 'wpforms/form-selector';
 | ||
| 			} );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get WPForms blocks.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {Object} props Block properties.
 | ||
| 		 *
 | ||
| 		 * @return {Object} Block attributes.
 | ||
| 		 */
 | ||
| 		isClientIdAttrUnique( props ) {
 | ||
| 			const wpformsBlocks = app.getWPFormsBlocks();
 | ||
| 
 | ||
| 			for ( const key in wpformsBlocks ) {
 | ||
| 				// Skip the current block.
 | ||
| 				if ( wpformsBlocks[ key ].clientId === props.clientId ) {
 | ||
| 					continue;
 | ||
| 				}
 | ||
| 
 | ||
| 				if ( wpformsBlocks[ key ].attributes.clientId === props.attributes.clientId ) {
 | ||
| 					return false;
 | ||
| 				}
 | ||
| 			}
 | ||
| 
 | ||
| 			return true;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get block attributes.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @return {Object} Block attributes.
 | ||
| 		 */
 | ||
| 		getBlockAttributes() {
 | ||
| 			return commonAttributes;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get block runtime state variable.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {string} clientId Block client ID.
 | ||
| 		 * @param {string} varName  Block runtime variable name.
 | ||
| 		 *
 | ||
| 		 * @return {*} Block runtime state variable value.
 | ||
| 		 */
 | ||
| 		getBlockRuntimeStateVar( clientId, varName ) {
 | ||
| 			return blocks[ clientId ]?.[ varName ];
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Set block runtime state variable value.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.8
 | ||
| 		 *
 | ||
| 		 * @param {string} clientId Block client ID.
 | ||
| 		 * @param {string} varName  Block runtime state key.
 | ||
| 		 * @param {*}      value    State variable value.
 | ||
| 		 *
 | ||
| 		 * @return {boolean} True on success.
 | ||
| 		 */
 | ||
| 		setBlockRuntimeStateVar( clientId, varName, value ) { // eslint-disable-line complexity
 | ||
| 			if ( ! clientId || ! varName ) {
 | ||
| 				return false;
 | ||
| 			}
 | ||
| 
 | ||
| 			blocks[ clientId ] = blocks[ clientId ] || {};
 | ||
| 			blocks[ clientId ][ varName ] = value;
 | ||
| 
 | ||
| 			// Prevent referencing to object.
 | ||
| 			if ( typeof value === 'object' && ! Array.isArray( value ) && value !== null ) {
 | ||
| 				blocks[ clientId ][ varName ] = { ...value };
 | ||
| 			}
 | ||
| 
 | ||
| 			return true;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get form selector options.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @return {Array} Form options.
 | ||
| 		 */
 | ||
| 		getFormOptions() {
 | ||
| 			const formOptions = formList.map( ( value ) => (
 | ||
| 				{ value: value.ID, label: value.post_title }
 | ||
| 			) );
 | ||
| 
 | ||
| 			formOptions.unshift( { value: '', label: strings.form_select } );
 | ||
| 
 | ||
| 			return formOptions;
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Get size selector options.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @return {Array} Size options.
 | ||
| 		 */
 | ||
| 		getSizeOptions() {
 | ||
| 			return [
 | ||
| 				{
 | ||
| 					label: strings.small,
 | ||
| 					value: 'small',
 | ||
| 				},
 | ||
| 				{
 | ||
| 					label: strings.medium,
 | ||
| 					value: 'medium',
 | ||
| 				},
 | ||
| 				{
 | ||
| 					label: strings.large,
 | ||
| 					value: 'large',
 | ||
| 				},
 | ||
| 			];
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Event `wpformsFormSelectorEdit` handler.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} e     Event object.
 | ||
| 		 * @param {Object} props Block properties.
 | ||
| 		 */
 | ||
| 		blockEdit( e, props ) {
 | ||
| 			const block = app.getBlockContainer( props );
 | ||
| 
 | ||
| 			if ( ! block?.dataset ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			app.initLeadFormSettings( block.parentElement );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Init Lead Form Settings panels.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Element} block         Block element.
 | ||
| 		 * @param {Object}  block.dataset Block element.
 | ||
| 		 */
 | ||
| 		initLeadFormSettings( block ) {
 | ||
| 			if ( ! block?.dataset ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			if ( ! app.isFullStylingEnabled() ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			const clientId = block.dataset.block;
 | ||
| 			const $panel = $( `.wpforms-block-settings-${ clientId }` );
 | ||
| 
 | ||
| 			if ( app.isLeadFormsEnabled( block ) ) {
 | ||
| 				$panel
 | ||
| 					.addClass( 'disabled_panel' )
 | ||
| 					.find( '.wpforms-gutenberg-panel-notice.wpforms-lead-form-notice' )
 | ||
| 					.css( 'display', 'block' );
 | ||
| 
 | ||
| 				$panel
 | ||
| 					.find( '.wpforms-gutenberg-panel-notice.wpforms-use-modern-notice' )
 | ||
| 					.css( 'display', 'none' );
 | ||
| 
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			$panel
 | ||
| 				.removeClass( 'disabled_panel' )
 | ||
| 				.find( '.wpforms-gutenberg-panel-notice.wpforms-lead-form-notice' )
 | ||
| 				.css( 'display', 'none' );
 | ||
| 
 | ||
| 			$panel
 | ||
| 				.find( '.wpforms-gutenberg-panel-notice.wpforms-use-modern-notice' )
 | ||
| 				.css( 'display', null );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Event `wpformsFormSelectorFormLoaded` handler.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} e Event object.
 | ||
| 		 */
 | ||
| 		formLoaded( e ) {
 | ||
| 			app.initLeadFormSettings( e.detail.block );
 | ||
| 			app.updateAccentColors( e.detail );
 | ||
| 			app.loadChoicesJS( e.detail );
 | ||
| 			app.initRichTextField( e.detail.formId );
 | ||
| 			app.initRepeaterField( e.detail.formId );
 | ||
| 
 | ||
| 			$( e.detail.block )
 | ||
| 				.off( 'click' )
 | ||
| 				.on( 'click', app.blockClick );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Click on the block event handler.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} e Event object.
 | ||
| 		 */
 | ||
| 		blockClick( e ) {
 | ||
| 			app.initLeadFormSettings( e.currentTarget );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Update accent colors of some fields in GB block in Modern Markup mode.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} detail Event details object.
 | ||
| 		 */
 | ||
| 		updateAccentColors( detail ) {
 | ||
| 			if (
 | ||
| 				! wpforms_gutenberg_form_selector.is_modern_markup ||
 | ||
| 				! window.WPForms?.FrontendModern ||
 | ||
| 				! detail?.block
 | ||
| 			) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			const $form = $( detail.block.querySelector( `#wpforms-${ detail.formId }` ) ),
 | ||
| 				FrontendModern = window.WPForms.FrontendModern;
 | ||
| 
 | ||
| 			FrontendModern.updateGBBlockPageIndicatorColor( $form );
 | ||
| 			FrontendModern.updateGBBlockIconChoicesColor( $form );
 | ||
| 			FrontendModern.updateGBBlockRatingColor( $form );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Init Modern style Dropdown fields (<select>).
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {Object} detail Event details object.
 | ||
| 		 */
 | ||
| 		loadChoicesJS( detail ) {
 | ||
| 			if ( typeof window.Choices !== 'function' ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			const $form = $( detail.block.querySelector( `#wpforms-${ detail.formId }` ) );
 | ||
| 
 | ||
| 			$form.find( '.choicesjs-select' ).each( function( idx, selectEl ) {
 | ||
| 				const $el = $( selectEl );
 | ||
| 
 | ||
| 				if ( $el.data( 'choice' ) === 'active' ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				const args = window.wpforms_choicesjs_config || {},
 | ||
| 					searchEnabled = $el.data( 'search-enabled' ),
 | ||
| 					$field = $el.closest( '.wpforms-field' );
 | ||
| 
 | ||
| 				args.searchEnabled = 'undefined' !== typeof searchEnabled ? searchEnabled : true;
 | ||
| 				args.callbackOnInit = function() {
 | ||
| 					const self = this,
 | ||
| 						$element = $( self.passedElement.element ),
 | ||
| 						$input = $( self.input.element ),
 | ||
| 						sizeClass = $element.data( 'size-class' );
 | ||
| 
 | ||
| 					// Add CSS-class for size.
 | ||
| 					if ( sizeClass ) {
 | ||
| 						$( self.containerOuter.element ).addClass( sizeClass );
 | ||
| 					}
 | ||
| 
 | ||
| 					/**
 | ||
| 					 * If a multiple select has selected choices - hide a placeholder text.
 | ||
| 					 * In case if select is empty - we return placeholder text.
 | ||
| 					 */
 | ||
| 					if ( $element.prop( 'multiple' ) ) {
 | ||
| 						// On init event.
 | ||
| 						$input.data( 'placeholder', $input.attr( 'placeholder' ) );
 | ||
| 
 | ||
| 						if ( self.getValue( true ).length ) {
 | ||
| 							$input.hide();
 | ||
| 						}
 | ||
| 					}
 | ||
| 
 | ||
| 					this.disable();
 | ||
| 					$field.find( '.is-disabled' ).removeClass( 'is-disabled' );
 | ||
| 				};
 | ||
| 
 | ||
| 				try {
 | ||
| 					if ( ! ( selectEl instanceof parent.HTMLSelectElement ) ) {
 | ||
| 						Object.setPrototypeOf( selectEl, parent.HTMLSelectElement.prototype );
 | ||
| 					}
 | ||
| 
 | ||
| 					$el.data( 'choicesjs', new parent.Choices( selectEl, args ) );
 | ||
| 				} catch ( e ) {} // eslint-disable-line no-empty
 | ||
| 			} );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Initialize RichText field.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.1
 | ||
| 		 *
 | ||
| 		 * @param {number} formId Form ID.
 | ||
| 		 */
 | ||
| 		initRichTextField( formId ) {
 | ||
| 			const form = app.getFormBlock( formId );
 | ||
| 
 | ||
| 			if ( ! form ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			// Set default tab to `Visual`.
 | ||
| 			$( form ).find( '.wp-editor-wrap' ).removeClass( 'html-active' ).addClass( 'tmce-active' );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Initialize Repeater field.
 | ||
| 		 *
 | ||
| 		 * @since 1.8.9
 | ||
| 		 *
 | ||
| 		 * @param {number} formId Form ID.
 | ||
| 		 */
 | ||
| 		initRepeaterField( formId ) {
 | ||
| 			const form = app.getFormBlock( formId );
 | ||
| 
 | ||
| 			if ( ! form ) {
 | ||
| 				return;
 | ||
| 			}
 | ||
| 
 | ||
| 			const $rowButtons = $( form ).find( '.wpforms-field-repeater > .wpforms-field-repeater-display-rows .wpforms-field-repeater-display-rows-buttons' );
 | ||
| 
 | ||
| 			// Get the label height and set the button position.
 | ||
| 			$rowButtons.each( function() {
 | ||
| 				const $cont = $( this );
 | ||
| 				const $labels = $cont.siblings( '.wpforms-layout-column' )
 | ||
| 					.find( '.wpforms-field' )
 | ||
| 					.find( '.wpforms-field-label' );
 | ||
| 
 | ||
| 				if ( ! $labels.length ) {
 | ||
| 					return;
 | ||
| 				}
 | ||
| 
 | ||
| 				const $label = $labels.first();
 | ||
| 				const labelStyle = window.getComputedStyle( $label.get( 0 ) );
 | ||
| 				const margin = labelStyle?.getPropertyValue( '--wpforms-field-size-input-spacing' ) || 0;
 | ||
| 				const height = $label.outerHeight() || 0;
 | ||
| 				const top = height + parseInt( margin, 10 ) + 10;
 | ||
| 
 | ||
| 				$cont.css( { top } );
 | ||
| 			} );
 | ||
| 
 | ||
| 			// Init buttons and descriptions for each repeater in each form.
 | ||
| 			$( `.wpforms-form[data-formid="${ formId }"]` ).each( function() {
 | ||
| 				const $repeater = $( this ).find( '.wpforms-field-repeater' );
 | ||
| 
 | ||
| 				$repeater.find( '.wpforms-field-repeater-display-rows-buttons' ).addClass( 'wpforms-init' );
 | ||
| 				$repeater.find( '.wpforms-field-repeater-display-rows:last .wpforms-field-description' ).addClass( 'wpforms-init' );
 | ||
| 			} );
 | ||
| 		},
 | ||
| 
 | ||
| 		/**
 | ||
| 		 * Handle theme change.
 | ||
| 		 *
 | ||
| 		 * @since 1.9.3
 | ||
| 		 *
 | ||
| 		 * @param {Object} props Block properties.
 | ||
| 		 */
 | ||
| 		onSetTheme( props ) {
 | ||
| 			backgroundSelected = props.attributes.backgroundImage !== 'url()';
 | ||
| 		},
 | ||
| 	};
 | ||
| 
 | ||
| 	// Provide access to public functions/properties.
 | ||
| 	return app;
 | ||
| }( document, window, jQuery ) );
 |