PluginProbe
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz / 2.12.8
SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz v2.12.8
2.12.8 2.12.7 2.12.6 2.12.5 2.12.4 2.12.3 2.12.2 2.12.1 2.12.0 2.11.1 2.11.0 2.10.1 2.10.0 2.9.1 2.9.0 2.8.2 2.8.1 2.7.0 2.7.1 2.8.0 trunk 0.0.10 0.0.11 0.0.12 0.0.13 All 98 releases
sureforms / inc / page-builders / elementor / assets / elementor-preview-styling.js

elementor-preview-styling.js in SureForms – Contact Form Builder, AI Forms, Payment Form, Survey & Quiz 2.12.8, at inc/page-builders/elementor/assets/elementor-preview-styling.js

633 lines 17.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Elementor Styling Preview
3 *
4 * Handles live preview of styling changes in the Elementor editor.
5 * Listens for control value changes and applies CSS variables directly to the DOM.
6 *
7 * @package
8 * @since 2.7.0
9 */
10
11 import {
12 PRIMARY_COLOR_MAP,
13 TEXT_COLOR_MAP,
14 SIMPLE_CSS_MAP,
15 DIMENSIONS_CSS_MAP,
16 applyColorMap,
17 resetColorMap,
18 applyDimensions,
19 resetDimensions,
20 buildGradientCSS,
21 initGradientFromSettings,
22 } from '@Utils/styling-utils';
23
24 ( function () {
25 'use strict';
26
27 /**
28 * Background-related controls that use dataset storage.
29 */
30 const BACKGROUND_CONTROLS = [ 'bgType', 'bgColor', 'bgImage' ];
31
32 /**
33 * Gradient group control field names.
34 * These come from Elementor's Group_Control_Background with name 'bgGradient'.
35 * Position control is hidden - radial gradients use 'center center' position.
36 */
37 const GRADIENT_CONTROLS = [
38 'bgGradient_color',
39 'bgGradient_color_b',
40 'bgGradient_gradient_type',
41 'bgGradient_gradient_angle',
42 'bgGradient_color_stop',
43 'bgGradient_color_b_stop',
44 ];
45
46 /**
47 * All free plugin control names for reset functionality.
48 */
49 const ALL_FREE_CONTROLS = [
50 'primaryColor',
51 'textColor',
52 'fieldSpacing',
53 'buttonAlignment',
54 ...Object.keys( SIMPLE_CSS_MAP ),
55 ...Object.keys( DIMENSIONS_CSS_MAP ),
56 ...BACKGROUND_CONTROLS,
57 ...GRADIENT_CONTROLS,
58 ];
59
60 /**
61 * Resolve a global color key to its actual color value.
62 *
63 * When a user selects a global color in Elementor, the value is stored as
64 * a reference like 'globals/colors?id=primary'. This function resolves
65 * that reference to the actual color value.
66 *
67 * @param {string} globalKey The global color key (e.g., 'globals/colors?id=primary').
68 * @return {string|null} The resolved color value or null if not found.
69 */
70 function resolveGlobalColor( globalKey ) {
71 if ( ! globalKey || typeof globalKey !== 'string' ) {
72 return null;
73 }
74
75 // Parse the global key to extract the color ID.
76 // Format: 'globals/colors?id=primary' -> id: 'primary'
77 const match = globalKey.match( /id=([^&]+)/ );
78 if ( ! match ) {
79 return null;
80 }
81
82 const colorId = match[ 1 ];
83
84 try {
85 // Method 1: Get from CSS variable (most reliable).
86 // Elementor outputs global colors as CSS variables: --e-global-color-{id}
87 const cssVarName = '--e-global-color-' + colorId;
88 const cssValue = getComputedStyle( document.documentElement )
89 .getPropertyValue( cssVarName )
90 .trim();
91 if ( cssValue ) {
92 return cssValue;
93 }
94
95 // Method 2: Try to get from $e.data cache (fallback).
96 if (
97 typeof $e !== 'undefined' &&
98 $e.data &&
99 $e.components &&
100 $e.components.get
101 ) {
102 const globalsComponent = $e.components.get( 'globals' );
103 if ( globalsComponent ) {
104 const data = $e.data.getCache(
105 globalsComponent,
106 'globals/colors',
107 { id: colorId }
108 );
109 if ( data && data.value ) {
110 return data.value;
111 }
112 }
113 }
114 } catch ( e ) {
115 // Silently fail and return null.
116 }
117
118 return null;
119 }
120
121 /**
122 * Apply background based on type.
123 *
124 * @param {HTMLElement} container The form container element.
125 */
126 function applyBackground( container ) {
127 const bgType = container.dataset.bgType || 'color';
128 const bgColor = container.dataset.bgColor || '#FFFFFF';
129 const bgImage = container.dataset.bgImage || '';
130
131 // Reset background.
132 container.style.removeProperty( 'background' );
133 container.style.removeProperty( 'background-color' );
134 container.style.removeProperty( 'background-image' );
135
136 if ( bgType === 'color' ) {
137 container.style.backgroundColor = bgColor;
138 } else if ( bgType === 'gradient' ) {
139 const gradient = buildGradientCSS( container, 'bgGradient' );
140 if ( gradient ) {
141 container.style.background = gradient;
142 }
143 } else if ( bgType === 'image' && bgImage ) {
144 // Validate URL protocol to prevent CSS injection.
145 if (
146 bgImage.startsWith( 'https://' ) ||
147 bgImage.startsWith( 'http://' )
148 ) {
149 container.style.backgroundImage = `url(${ bgImage })`;
150 }
151 }
152 }
153
154 /**
155 * Apply a style update for a control.
156 *
157 * @param {HTMLElement} container The form container element.
158 * @param {string} name The control name.
159 * @param {*} value The control value.
160 */
161 function applyStyle( container, name, value ) {
162 // Handle simple CSS variable mappings.
163 if ( SIMPLE_CSS_MAP[ name ] ) {
164 container.style.setProperty( SIMPLE_CSS_MAP[ name ], value );
165 return;
166 }
167
168 // Handle DIMENSIONS controls.
169 if ( DIMENSIONS_CSS_MAP[ name ] ) {
170 applyDimensions( container, DIMENSIONS_CSS_MAP[ name ], value );
171 return;
172 }
173
174 // Handle special controls.
175 switch ( name ) {
176 case 'primaryColor':
177 applyColorMap( container, PRIMARY_COLOR_MAP, value );
178 break;
179
180 case 'textColor':
181 applyColorMap( container, TEXT_COLOR_MAP, value );
182 break;
183
184 case 'fieldSpacing': {
185 const fieldSpacingVars =
186 window.srfmElementorStyling?.fieldSpacingVars;
187 if ( fieldSpacingVars ) {
188 // Merge base (small) with size-specific overrides.
189 const baseSize = fieldSpacingVars.small || {};
190 const overrideSize = fieldSpacingVars[ value ] || {};
191 const finalSize = Object.assign(
192 {},
193 baseSize,
194 overrideSize
195 );
196
197 for ( const key in finalSize ) {
198 if ( Object.hasOwn( finalSize, key ) && key.startsWith( '--' ) ) {
199 container.style.setProperty(
200 key,
201 finalSize[ key ]
202 );
203 }
204 }
205 }
206 break;
207 }
208
209 case 'buttonAlignment': {
210 const submitContainer = container.querySelector(
211 '.srfm-submit-container .wp-block-button'
212 );
213 if ( submitContainer ) {
214 submitContainer.style.textAlign =
215 value === 'justify' ? 'center' : value;
216 const btn = submitContainer.querySelector( 'button' );
217 if ( btn ) {
218 btn.style.width = value === 'justify' ? '100%' : '';
219 }
220 }
221 break;
222 }
223
224 // Background controls - store in dataset and apply.
225 case 'bgType':
226 container.dataset.bgType = value;
227 applyBackground( container );
228 break;
229
230 case 'bgColor':
231 container.dataset.bgColor = value;
232 applyBackground( container );
233 break;
234
235 // Gradient group control fields - store in dataset and rebuild gradient.
236 case 'bgGradient_color':
237 container.dataset.bgGradientColor = value;
238 applyBackground( container );
239 break;
240
241 case 'bgGradient_color_b':
242 container.dataset.bgGradientColorB = value;
243 applyBackground( container );
244 break;
245
246 case 'bgGradient_gradient_type':
247 container.dataset.bgGradientType = value;
248 applyBackground( container );
249 break;
250
251 case 'bgGradient_gradient_angle': {
252 // Elementor slider returns object with size and unit.
253 const angleSize =
254 typeof value === 'object' ? value.size : value;
255 const angleUnit =
256 typeof value === 'object' ? value.unit : 'deg';
257 container.dataset.bgGradientAngle = angleSize;
258 container.dataset.bgGradientAngleUnit = angleUnit;
259 applyBackground( container );
260 break;
261 }
262
263 case 'bgGradient_color_stop': {
264 // Elementor slider returns object with size and unit.
265 const stopSize = typeof value === 'object' ? value.size : value;
266 const stopUnit = typeof value === 'object' ? value.unit : '%';
267 container.dataset.bgGradientColorStop = stopSize;
268 container.dataset.bgGradientColorStopUnit = stopUnit;
269 applyBackground( container );
270 break;
271 }
272
273 case 'bgGradient_color_b_stop': {
274 // Elementor slider returns object with size and unit.
275 const stopBSize =
276 typeof value === 'object' ? value.size : value;
277 const stopBUnit = typeof value === 'object' ? value.unit : '%';
278 container.dataset.bgGradientColorBStop = stopBSize;
279 container.dataset.bgGradientColorBStopUnit = stopBUnit;
280 applyBackground( container );
281 break;
282 }
283
284 case 'bgImage': {
285 // val is an object with url property from Elementor.
286 const url = typeof value === 'object' ? value.url : value;
287 container.dataset.bgImage = url || '';
288 applyBackground( container );
289 break;
290 }
291 }
292 }
293
294 /**
295 * Reset a control to its original state.
296 *
297 * @param {HTMLElement} container The form container element.
298 * @param {string} name The control name.
299 */
300 function resetControl( container, name ) {
301 // Handle simple CSS variable mappings.
302 if ( SIMPLE_CSS_MAP[ name ] ) {
303 container.style.removeProperty( SIMPLE_CSS_MAP[ name ] );
304 return;
305 }
306
307 // Handle DIMENSIONS controls.
308 if ( DIMENSIONS_CSS_MAP[ name ] ) {
309 resetDimensions( container, DIMENSIONS_CSS_MAP[ name ] );
310 return;
311 }
312
313 // Handle special controls.
314 switch ( name ) {
315 case 'primaryColor':
316 resetColorMap( container, PRIMARY_COLOR_MAP );
317 break;
318
319 case 'textColor':
320 resetColorMap( container, TEXT_COLOR_MAP );
321 break;
322
323 case 'fieldSpacing': {
324 // Reset field spacing by removing all related CSS variables.
325 const fieldSpacingVars =
326 window.srfmElementorStyling?.fieldSpacingVars;
327 if ( fieldSpacingVars && fieldSpacingVars.small ) {
328 for ( const key in fieldSpacingVars.small ) {
329 if ( Object.hasOwn( fieldSpacingVars.small, key ) ) {
330 container.style.removeProperty( key );
331 }
332 }
333 }
334 break;
335 }
336
337 case 'buttonAlignment': {
338 const submitContainer = container.querySelector(
339 '.srfm-submit-container .wp-block-button'
340 );
341 if ( submitContainer ) {
342 submitContainer.style.removeProperty( 'text-align' );
343 const btn = submitContainer.querySelector( 'button' );
344 if ( btn ) {
345 btn.style.removeProperty( 'width' );
346 }
347 }
348 break;
349 }
350
351 // Background controls.
352 case 'bgType':
353 delete container.dataset.bgType;
354 container.style.removeProperty( 'background' );
355 container.style.removeProperty( 'background-color' );
356 container.style.removeProperty( 'background-image' );
357 break;
358
359 case 'bgColor':
360 delete container.dataset.bgColor;
361 container.style.removeProperty( 'background-color' );
362 break;
363
364 // Gradient group control fields.
365 case 'bgGradient_color':
366 delete container.dataset.bgGradientColor;
367 container.style.removeProperty( 'background' );
368 break;
369
370 case 'bgGradient_color_b':
371 delete container.dataset.bgGradientColorB;
372 container.style.removeProperty( 'background' );
373 break;
374
375 case 'bgGradient_gradient_type':
376 delete container.dataset.bgGradientType;
377 container.style.removeProperty( 'background' );
378 break;
379
380 case 'bgGradient_gradient_angle':
381 delete container.dataset.bgGradientAngle;
382 delete container.dataset.bgGradientAngleUnit;
383 container.style.removeProperty( 'background' );
384 break;
385
386 case 'bgGradient_color_stop':
387 delete container.dataset.bgGradientColorStop;
388 delete container.dataset.bgGradientColorStopUnit;
389 container.style.removeProperty( 'background' );
390 break;
391
392 case 'bgGradient_color_b_stop':
393 delete container.dataset.bgGradientColorBStop;
394 delete container.dataset.bgGradientColorBStopUnit;
395 container.style.removeProperty( 'background' );
396 break;
397
398 case 'bgImage':
399 delete container.dataset.bgImage;
400 container.style.removeProperty( 'background-image' );
401 break;
402 }
403 }
404
405 /**
406 * Reset all styles when formTheme is set to inherit.
407 *
408 * @param {HTMLElement} container The form container element.
409 * @param {string} originalClasses Original container classes.
410 */
411 function resetAllStyles( container, originalClasses ) {
412 ALL_FREE_CONTROLS.forEach( function ( controlName ) {
413 resetControl( container, controlName );
414 } );
415
416 // Reset container classes to original.
417 container.className = originalClasses;
418 }
419
420 /**
421 * Dispatch event for Pro and other extensions to listen to.
422 *
423 * @param {string} name The control name.
424 * @param {string} value The control value.
425 * @param {HTMLElement} container The form container element.
426 * @param {Object} widgetSettings The Elementor widget settings object (optional).
427 */
428 function dispatchUpdateEvent(
429 name,
430 value,
431 container,
432 widgetSettings = null
433 ) {
434 document.dispatchEvent(
435 new CustomEvent( 'srfm-elementor-styling-update', {
436 detail: { name, value, container, widgetSettings },
437 } )
438 );
439 }
440
441 /**
442 * Apply style update based on control name and value.
443 *
444 * @param {HTMLElement} container The form container element.
445 * @param {string} name The control name.
446 * @param {string} value The control value.
447 * @param {string} originalClasses Original container classes for reset.
448 * @param {Object} widgetSettings The Elementor widget settings object (optional).
449 */
450 function applyStyleUpdate(
451 container,
452 name,
453 value,
454 originalClasses,
455 widgetSettings = null
456 ) {
457 // Handle formTheme change - reset all styles when set to inherit.
458 if ( name === 'formTheme' ) {
459 if ( value === 'inherit' ) {
460 resetAllStyles( container, originalClasses );
461 }
462 dispatchUpdateEvent( name, value, container, widgetSettings );
463 return;
464 }
465
466 // Handle empty/default values - reset to original.
467 if ( value === '' || value === 'default' ) {
468 resetControl( container, name );
469 dispatchUpdateEvent( name, value, container, widgetSettings );
470 return;
471 }
472
473 // Apply the style update.
474 applyStyle( container, name, value );
475
476 // Dispatch event for Pro to extend.
477 dispatchUpdateEvent( name, value, container, widgetSettings );
478 }
479
480 // Main initialization.
481 window.addEventListener( 'elementor/frontend/init', function () {
482 if (
483 typeof elementorFrontend === 'undefined' ||
484 ! elementorFrontend.hooks
485 ) {
486 return;
487 }
488
489 elementorFrontend.hooks.addAction(
490 'frontend/element_ready/sureforms_form.default',
491 function ( $scope ) {
492 const container = $scope
493 .find( '.srfm-form-container' )
494 .get( 0 );
495 if ( ! container ) {
496 return;
497 }
498
499 // Store original classes for reset functionality.
500 const originalClasses = container.className;
501
502 // Listen for control changes in editor.
503 if (
504 typeof elementor !== 'undefined' &&
505 elementor.channels &&
506 elementor.channels.editor
507 ) {
508 const widgetId = $scope.data( 'id' );
509
510 // Initialize dataset from current widget settings.
511 // This ensures values like bgType are available before any change events.
512 const widgetContainer = elementor.getContainer
513 ? elementor.getContainer( widgetId )
514 : null;
515
516 if ( widgetContainer && widgetContainer.settings ) {
517 const settings = widgetContainer.settings;
518
519 // Initialize bgType from current settings.
520 const bgType = settings.get( 'bgType' );
521 if ( bgType ) {
522 container.dataset.bgType = bgType;
523 }
524
525 // Initialize gradient values if bgType is gradient.
526 if ( bgType === 'gradient' ) {
527 initGradientFromSettings(
528 container,
529 settings,
530 'bgGradient',
531 'bgGradient_'
532 );
533 }
534
535 // Dispatch init event for Pro plugin to initialize its controls.
536 document.dispatchEvent(
537 new CustomEvent( 'srfm-elementor-styling-init', {
538 detail: { container, widgetSettings: settings },
539 } )
540 );
541 }
542
543 // Regular control change listener.
544 elementor.channels.editor.on(
545 'change',
546 function ( controlView ) {
547 if (
548 ! controlView.container ||
549 ! controlView.container.id
550 ) {
551 return;
552 }
553
554 if ( controlView.container.id !== widgetId ) {
555 return;
556 }
557
558 const name = controlView.model.get( 'name' );
559 let value =
560 controlView.container.settings.get( name );
561
562 // Check for global color value and resolve it.
563 const globalKey =
564 controlView.container.globals.get( name );
565 if ( globalKey ) {
566 value = resolveGlobalColor( globalKey );
567 }
568
569 // Initialize gradient values when any gradient control changes.
570 // This ensures colors are in dataset when location/angle sliders change.
571 if ( name.startsWith( 'bgGradient_' ) ) {
572 initGradientFromSettings(
573 container,
574 controlView.container.settings,
575 'bgGradient',
576 'bgGradient_'
577 );
578 }
579
580 applyStyleUpdate(
581 container,
582 name,
583 value,
584 originalClasses,
585 controlView.container.settings
586 );
587 }
588 );
589
590 // Listen for global color changes.
591 // When a global color is selected, Elementor updates the globals model
592 // instead of firing the regular change event.
593 if ( widgetContainer && widgetContainer.globals ) {
594 widgetContainer.globals.on(
595 'change',
596 function ( model ) {
597 // Get all changed attributes.
598 const changed = model.changed;
599 for ( const name in changed ) {
600 if ( ! Object.hasOwn( changed, name ) ) {
601 continue;
602 }
603 const globalKey = changed[ name ];
604 let value = null;
605
606 if ( globalKey ) {
607 // Global color selected - resolve it.
608 value = resolveGlobalColor( globalKey );
609 } else {
610 // Global color cleared - get direct value.
611 value =
612 widgetContainer.settings.get(
613 name
614 );
615 }
616
617 applyStyleUpdate(
618 container,
619 name,
620 value,
621 originalClasses,
622 widgetContainer.settings
623 );
624 }
625 }
626 );
627 }
628 }
629 }
630 );
631 } );
632 }() );
633