PluginProbe
Booking Calendar / 11.0
Booking Calendar v11.0
11.8.3 11.8.2 11.8.1 11.8 11.7 11.6.1 11.6 11.5 11.4.3 11.4.2 11.4.1 11.4 11.3 11.2.1 11.2 11.1 11.0 10.15.7 10.15.6 10.1.3 10.10 10.10.1 10.10.2 10.11 10.11.2 All 203 releases
booking / includes / page-form-builder / form-settings / _src / settings_effects.js

settings_effects.js in Booking Calendar 11.0, at includes/page-form-builder/form-settings/_src/settings_effects.js

384 lines 11.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Applies effects in Canvas, after changing some settings in the right sidebar in BFB.
3 *
4 * @file ../includes/page-form-builder/form-settings/_src/settings_effects.js
5 */
6 (function (w, d) {
7 'use strict';
8
9 const Effects = (w.WPBC_BFB_Settings_Effects = w.WPBC_BFB_Settings_Effects || {});
10 const map = (Effects.map = Effects.map || Object.create( null ));
11
12 Effects.register = function (key, fn) {
13 if ( key && typeof fn === 'function' ) {
14 map[String( key )] = fn;
15 }
16 };
17
18 function get_canvas_root() {
19 return (
20 d.querySelector( '#wpbc_bfb__pages_container' ) ||
21 d.querySelector( '.wpbc_bfb__panel--preview' ) ||
22 d.getElementById( 'wpbc_bfb__preview' ) ||
23 d.body || d.documentElement
24 );
25 }
26
27 Effects.apply_one = function (key, value, ctx) {
28 const fn = map[String( key )];
29 if ( ! fn ) {
30 return;
31 }
32 try {
33 fn( value, Object.assign( { key, value, canvas: get_canvas_root() }, ctx || {} ) );
34 } catch ( e ) {
35 // keep silent in production if you prefer
36 console.error( 'WPBC Effects error:', key, e );
37 }
38 };
39
40 Effects.apply_all = function (options, ctx) {
41 if ( ! options || typeof options !== 'object' ) {
42 return;
43 }
44 Object.keys( options ).forEach( function (k) {
45 Effects.apply_one( k, options[k], ctx );
46 } );
47 };
48
49 /**
50 * Normalize settings pack to the minimum required shape:
51 * { options: {}, css_vars: {} }
52 *
53 * @param {*} pack
54 * @return {{options:Object, css_vars:Object, bfb_options?:Object}|null}
55 */
56 Effects.normalize_pack = function (pack) {
57
58 if ( pack === null || typeof pack === 'undefined' || pack === '' ) {
59 return null;
60 }
61
62 // Parse JSON string if needed.
63 if ( typeof pack === 'string' ) {
64 try {
65 pack = JSON.parse( pack );
66 } catch ( _e ) {
67 return null;
68 }
69 }
70
71 if ( ! pack || typeof pack !== 'object' ) {
72 return null;
73 }
74
75 // If user passed just {key:value} options map, wrap it.
76 const has_shape =
77 Object.prototype.hasOwnProperty.call( pack, 'options' ) ||
78 Object.prototype.hasOwnProperty.call( pack, 'css_vars' ) ||
79 Object.prototype.hasOwnProperty.call( pack, 'bfb_options' );
80
81 if ( ! has_shape ) {
82 pack = { options: pack, css_vars: {} };
83 }
84
85 if ( ! pack.options || typeof pack.options !== 'object' ) {
86 pack.options = {};
87 }
88 if ( ! pack.css_vars || typeof pack.css_vars !== 'object' ) {
89 pack.css_vars = {};
90 }
91
92 // bfb_options is optional; keep if valid.
93 if ( pack.bfb_options && typeof pack.bfb_options !== 'object' ) {
94 delete pack.bfb_options;
95 }
96
97 return pack;
98 };
99
100 /**
101 * Re-apply settings effects after a canvas rebuild / structure load.
102 *
103 * This is needed because structure loading can replace DOM nodes that effects target.
104 *
105 * @param {*} settings_pack string|object settings_json pack (or plain options map)
106 * @param {Object} [ctx]
107 */
108 Effects.reapply_after_canvas = function (settings_pack, ctx) {
109
110 const pack = Effects.normalize_pack( settings_pack );
111 if ( ! pack ) {
112 return;
113 }
114
115 // Apply immediately (best effort).
116 Effects.apply_all( pack.options, Object.assign( { source: 'reapply_after_canvas' }, ctx || {} ) );
117
118 // Some modules/hydration may run shortly after; do one more pass.
119 setTimeout( function () {
120 Effects.apply_all( pack.options, Object.assign( { source: 'reapply_after_canvas_delayed' }, ctx || {} ) );
121 }, 60 );
122 };
123
124 // 1) Apply from AJAX load.
125 d.addEventListener( 'wpbc:bfb:form_settings:apply', function (e) {
126 const pack = e && e.detail ? e.detail.settings : null;
127 if ( pack && pack.options ) {
128 Effects.apply_all( pack.options, { source: 'apply' } );
129 }
130 } );
131
132 // 2) Apply live from UI change (delegated).
133 function css_escape(value) {
134 const v = String( value == null ? '' : value );
135 if ( w.CSS && typeof w.CSS.escape === 'function' ) {
136 return w.CSS.escape( v );
137 }
138 return v.replace( /[^a-zA-Z0-9_\-]/g, '\\$&' );
139 }
140
141 function find_fs_root(el) {
142 if ( ! el || ! el.closest ) {
143 return null;
144 }
145
146 // 1) Direct: element or ancestor carries FS key (input/select/textarea writer, radio wrapper, etc.)
147 const direct = el.closest( '[data-wpbc-bfb-fs-key]' );
148 if ( direct ) {
149 return direct;
150 }
151
152 // 2) Length: event came from number/unit/range inside .wpbc_slider_len_group
153 const len_group = el.closest( '.wpbc_slider_len_group' );
154 if ( len_group ) {
155 return (
156 len_group.querySelector( 'input[data-wpbc_slider_len_writer][data-wpbc-bfb-fs-key]' ) ||
157 len_group.querySelector( 'input[data-wpbc-bfb-fs-type="length"][data-wpbc-bfb-fs-key]' ) ||
158 null
159 );
160 }
161
162 // 3) Range: event came from range input inside .wpbc_slider_range_group
163 const range_group = el.closest( '.wpbc_slider_range_group' );
164 if ( range_group ) {
165 return (
166 range_group.querySelector( 'input[data-wpbc_slider_range_writer][data-wpbc-bfb-fs-key]' ) ||
167 range_group.querySelector( 'input[data-wpbc_slider_range_writer]' ) ||
168 null
169 );
170 }
171
172 return null;
173 }
174
175 function read_value_from_fs_root(fs_root, original_target) {
176 if ( ! fs_root ) {
177 return '';
178 }
179
180 const fs_type = String( fs_root.getAttribute( 'data-wpbc-bfb-fs-type' ) || '' );
181
182 // RADIO: read checked within wrapper.
183 if ( fs_type === 'radio' ) {
184 const control_id = fs_root.getAttribute( 'data-wpbc-bfb-fs-controlid' ) || '';
185 const selector = control_id
186 ? 'input[type="radio"][name="' + css_escape( control_id ) + '"]:checked'
187 : 'input[type="radio"]:checked';
188
189 const checked = fs_root.querySelector( selector );
190 return checked ? String( checked.value || '' ) : '';
191 }
192
193 // CHECKBOX / TOGGLE
194 if ( (original_target && original_target.type === 'checkbox') || fs_root.type === 'checkbox' ) {
195 const cb = (original_target && original_target.type === 'checkbox') ? original_target : fs_root;
196 return cb.checked ? 'On' : 'Off';
197 }
198
199 // DEFAULT: writer/input/textarea/select
200 if ( fs_root.value != null ) {
201 return String( fs_root.value );
202 }
203 if ( original_target && original_target.value != null ) {
204 return String( original_target.value );
205 }
206
207 return '';
208 }
209
210 function on_change(ev) {
211 // Ignore events dispatched by code (apply/reapply, slider sync, etc.). Effects should react only to real user input + explicit wpbc:bfb:form_settings:apply.
212 if ( ev && ev.isTrusted === false ) { return; }
213 const target = ev && ev.target;
214 if ( ! target ) { return; }
215
216 const fs_root = find_fs_root( target );
217 if ( ! fs_root ) { return; }
218
219
220 // Normalize events so each control produces exactly one effect call.
221 // - toggle/select/radio/checkbox => "change" only.
222 // - everything else => "input" only.
223 const fs_type = String( fs_root.getAttribute( 'data-wpbc-bfb-fs-type' ) || '' );
224 const tag = String( target.tagName || '' ).toLowerCase();
225 const type = String( target.type || '' ).toLowerCase();
226
227 const use_change = (fs_type === 'toggle') || (fs_type === 'select') || (fs_type === 'radio') || (type === 'checkbox') || (type === 'radio') || (tag === 'select');
228
229 if ( use_change && ev.type !== 'change' ) { return; }
230 if ( ! use_change && ev.type !== 'input' ) { return; }
231 // -------------------------------------------------------------------------------------------
232
233 const key = fs_root.getAttribute( 'data-wpbc-bfb-fs-key' );
234 if ( ! key ) { return; }
235
236 const scope = fs_root.getAttribute( 'data-wpbc-bfb-fs-scope' ) || '';
237 const value = read_value_from_fs_root( fs_root, target );
238
239 Effects.apply_one( key, value, { source: 'ui', scope: scope, control: target, fs_root: fs_root } );
240 }
241
242 d.addEventListener( 'input', on_change, false );
243 d.addEventListener( 'change', on_change, false );
244
245 })( window, document );
246
247
248 // BOOKING_FORM_THEME.
249 WPBC_BFB_Settings_Effects.register( 'booking_form_theme', function (value, ctx) {
250 // const root = ctx.canvas;
251 const root = document.getElementById( 'wpbc_bfb__theme_scope' );
252 if ( ! root || ! root.querySelectorAll ) {
253 return;
254 }
255
256 // const wraps = root.querySelectorAll( '.wpbc_container.wpbc_form' );
257 const wraps = root.querySelectorAll( '.wpbc_bfb__pages_panel' );
258 if ( ! wraps.length ) {
259 return;
260 }
261
262 wraps.forEach( function (wrap) {
263 // remove any previous theme classes (simple + future-proof).
264 Array.from( wrap.classList ).forEach( function (cls) {
265 if ( /^wpbc_theme_/.test( cls ) ) {
266 wrap.classList.remove( cls );
267 }
268 } );
269
270 if ( value ) {
271 wrap.classList.add( String( value ) );
272 }
273 } );
274 } );
275
276
277 // BOOKING_FORM_LAYOUT_WIDTH — Form width: applies combined "100%" / "600px" / "40rem" to the booking form containers.
278 WPBC_BFB_Settings_Effects.register( 'booking_form_layout_width', function (value, ctx) {
279 const root = ctx && ctx.canvas;
280 if ( ! root || ! root.querySelectorAll ) {
281 return;
282 }
283
284 const wraps = root.querySelectorAll( '.wpbc_bfb__form_preview_section_container' );
285 if ( ! wraps.length ) {
286 return;
287 }
288
289 const v = String( value == null ? '' : value ).trim();
290
291 // allow only "number + unit".
292 if ( v && ! /^-?\d+(?:\.\d+)?(?:%|px|rem|em|vw|vh)$/.test( v ) ) {
293 return;
294 }
295
296 wraps.forEach(
297 function (wrap) {
298 if ( ! wrap || ! wrap.style ) {
299 return;
300 }
301
302 if ( ! v ) {
303 wrap.style.removeProperty( '--wpbc-bfb-booking_form_layout_width' );
304 } else {
305 wrap.style.setProperty( '--wpbc-bfb-booking_form_layout_width', v );
306 }
307 }
308 );
309 } );
310
311
312 // Debug Preview Mode.
313 WPBC_BFB_Settings_Effects.register( 'booking_bfb_preview_mode', function (value, ctx) {
314 const root = ctx.canvas;
315 if ( ! root || ! root.querySelectorAll ) {
316 return;
317 }
318
319 const wraps = root.querySelectorAll( '.wpbc_container.wpbc_form' );
320 if ( ! wraps.length ) {
321 return;
322 }
323
324 // Get builder async.
325 wpbc_bfb_api.with_builder(
326 function (Builder) {
327
328 /**
329 * Capture active right sidebar tab and return restore handle.
330 *
331 * @return {{restore:function():void}|null}
332 */
333 function capture_right_sidebar_active_tab_restore_handle() {
334
335 var tablist_el = document.querySelector( '.wpbc_bfb__rightbar_tabs[role="tablist"]' );
336 if ( ! tablist_el ) {
337 return null;
338 }
339
340 var active_tab_el = tablist_el.querySelector( '[role="tab"][aria-selected="true"]' );
341
342 if ( ! active_tab_el ) {
343 active_tab_el = tablist_el.querySelector( '[role="tab"][aria-controls="wpbc_bfb__palette_add_new"]' );
344 }
345
346 if ( ! active_tab_el || typeof active_tab_el.click !== 'function' ) {
347 return null;
348 }
349
350 return {
351 restore: function () {
352 try { active_tab_el.click(); } catch ( _e ) {}
353 }
354 };
355 }
356
357 var tab_restore_handle = capture_right_sidebar_active_tab_restore_handle();
358
359 let restored = false;
360 var EVS = window.WPBC_BFB_Core && window.WPBC_BFB_Core.WPBC_BFB_Events ? window.WPBC_BFB_Core.WPBC_BFB_Events : {};
361 var EV_DONE = EVS.STRUCTURE_LOADED || EVS.CANVAS_REFRESHED || 'wpbc:bfb:structure-loaded';
362
363
364 function do_restore() {
365 if ( restored ) { return; }
366 restored = true;
367 try { Builder?.bus?.off?.( EV_DONE, do_restore ); } catch ( _ ) {}
368 requestAnimationFrame( function () {
369 if ( ! tab_restore_handle ) { return; }
370 tab_restore_handle.restore();
371 } );
372 }
373
374 // Listen once (best), plus a fallback in case event isn't fired.
375 try { Builder?.bus?.on?.( EV_DONE, do_restore ); } catch ( _ ) {}
376
377
378 var enabled = ('On' === value);
379 Builder.set_preview_mode( enabled, { rebuild: true, reinit: true, source: 'settings-effects' } );
380 }
381 );
382
383 } );
384