PluginProbe
Booking Calendar / 11.8
Booking Calendar v11.8
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 10.11.3 All 202 releases
booking / includes / page-form-builder / _src / export / debug-ui.js

debug-ui.js in Booking Calendar 11.8, at includes/page-form-builder/_src/export/debug-ui.js

735 lines 22.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Debug UI — populate existing textareas, bind existing buttons.
3 *
4 * IMPORTANT:
5 * - This file does NOT create any markup anymore.
6 * - All blocks/textareas/buttons must exist in PHP template.
7 *
8 * @file: ../includes/page-form-builder/_out/export/debug-ui.js
9 */
10 (function (w, d) {
11 'use strict';
12
13 // Prevent double init (if file is injected twice).
14 if ( w.__wpbc_bfb__debug_ui_inited === '1' ) {
15 return;
16 }
17 w.__wpbc_bfb__debug_ui_inited = '1';
18
19 const DEBUG_PANEL_ID = 'wpbc_bfb__debug_export_panel';
20 const DEBUG_TAB_SEL = '.wpbc_bfb__tab_section__debug_tab';
21
22 function has_debug_ui() {
23 return !! ( d.getElementById( DEBUG_PANEL_ID ) || d.querySelector( DEBUG_TAB_SEL ) );
24 }
25
26 if ( ! has_debug_ui() ) {
27 return;
28 }
29
30 // -----------------------------------------------------------------------------------------------------------------
31 // Helpers
32 // -----------------------------------------------------------------------------------------------------------------
33 function show_message( msg, type, timeout ) {
34 const m = String( msg || '' );
35 const t = String( type || 'info' );
36
37 if ( typeof w.wpbc_admin_show_message === 'function' ) {
38 try {
39 w.wpbc_admin_show_message( m, t, timeout || 6000 );
40 return;
41 } catch ( _e ) {}
42 }
43
44 // Fallback.
45 if ( t === 'error' ) {
46 console.error( m );
47 } else {
48 console.log( m );
49 }
50 }
51
52 function safe_json_parse( text ) {
53 const raw = String( text == null ? '' : text ).trim();
54 if ( ! raw ) {
55 return null;
56 }
57 try {
58 return JSON.parse( raw );
59 } catch ( e ) {
60 return null;
61 }
62 }
63
64 // ---------------------------
65 // Safe event dispatch helper (reuse preview contract)
66 // ---------------------------
67 function dispatch_event_safe( event_name, detail ) {
68 if ( typeof w.wpbc_bfb__dispatch_event_safe === 'function' ) {
69 try {
70 w.wpbc_bfb__dispatch_event_safe( event_name, detail );
71 return;
72 } catch ( _e ) {}
73 }
74 try {
75 d.dispatchEvent( new CustomEvent( event_name, { detail: detail || {} } ) );
76 } catch ( _e2 ) {}
77 }
78
79 function with_builder( cb ) {
80 if ( w.wpbc_bfb_api && w.wpbc_bfb_api.ready && typeof w.wpbc_bfb_api.ready.then === 'function' ) {
81 w.wpbc_bfb_api.ready.then( function ( b ) {
82 cb( b || w.wpbc_bfb || null );
83 } );
84 return;
85 }
86 cb( w.wpbc_bfb || null );
87 }
88
89 function is_debug_tab_visible() {
90 const root = d.querySelector( DEBUG_TAB_SEL );
91 if ( ! root ) {
92 return true;
93 }
94 try {
95 if ( root.hasAttribute( 'hidden' ) ) {
96 return false;
97 }
98 const aria = String( root.getAttribute( 'aria-hidden' ) || '' ).toLowerCase();
99 if ( aria === 'true' ) {
100 return false;
101 }
102 } catch ( _e ) {}
103 return true;
104 }
105
106 function get_debug_panel() {
107 return d.getElementById( DEBUG_PANEL_ID ) || null;
108 }
109
110 function set_value( textarea_id, value ) {
111 const el = d.getElementById( textarea_id );
112 if ( ! el ) {
113 return;
114 }
115 el.value = String( value || '' );
116 try {
117 el.dispatchEvent( new Event( 'input', { bubbles: true } ) );
118 } catch ( _ ) {}
119 }
120
121 function set_value_if_empty( textarea_id, value ) {
122 const el = d.getElementById( textarea_id );
123 if ( ! el ) {
124 return;
125 }
126 if ( String( el.value || '' ).trim() !== '' ) {
127 return;
128 }
129 set_value( textarea_id, value );
130 }
131
132 function copy_text_to_clipboard( text, btn ) {
133 const raw = String( text || '' );
134 if ( ! raw ) {
135 return;
136 }
137
138 if ( typeof w.wpbc_copy_to_clipboard === 'function' ) {
139 try {
140 const orig = btn ? String( btn.textContent || '' ) : '';
141 w.wpbc_copy_to_clipboard( raw ).then(
142 function ( ok ) {
143 if ( ! btn ) {
144 return;
145 }
146 btn.textContent = ok ? 'Copied!' : 'Press Ctrl/Cmd+C to copy';
147 w.setTimeout( function () {
148 btn.textContent = orig;
149 }, 1500 );
150 },
151 function () {
152 if ( btn ) {
153 btn.textContent = 'Press Ctrl/Cmd+C to copy';
154 }
155 }
156 );
157 return;
158 } catch ( _e ) {}
159 }
160
161 show_message( 'Copy helper is not available. Please copy manually.', 'info', 4000 );
162 }
163
164 // -----------------------------------------------------------------------------------------------------------------
165 // Data getters
166 // -----------------------------------------------------------------------------------------------------------------
167 function get_current_form_name() {
168 const cfg = w.WPBC_BFB_Ajax || {};
169 const v = cfg && cfg.form_name ? String( cfg.form_name ) : '';
170 return v ? v : 'standard';
171 }
172
173 // ---------------------------
174 // Builder structure getter
175 // ---------------------------
176 function get_current_structure() {
177 if ( w.wpbc_bfb && typeof w.wpbc_bfb.get_structure === 'function' ) {
178 try {
179 return w.wpbc_bfb.get_structure();
180 } catch ( _e ) {
181 return [];
182 }
183 }
184 return [];
185 }
186
187 // ---------------------------
188 // Settings collector (reuses Preview contract)
189 // ---------------------------
190 function get_current_form_settings( form_name ) {
191 let form_settings = { options: {}, css_vars: {} };
192
193 dispatch_event_safe( 'wpbc:bfb:form_settings:collect', {
194 settings : form_settings,
195 form_name: form_name || 'standard'
196 } );
197
198 // Strict: require correct shape.
199 if ( ! form_settings || typeof form_settings !== 'object' ) {
200 form_settings = { options: {}, css_vars: {} };
201 }
202 if ( ! form_settings.options || typeof form_settings.options !== 'object' ) {
203 form_settings.options = {};
204 }
205 if ( ! form_settings.css_vars || typeof form_settings.css_vars !== 'object' ) {
206 form_settings.css_vars = {};
207 }
208
209 return form_settings;
210 }
211
212 /**
213 * Parse combined length value like "100%", "320px", "12.5rem".
214 *
215 * @param {string} value
216 * @return {{num:string, unit:string}}
217 */
218 function parse_length_value( value ) {
219 const raw = String( value == null ? '' : value ).trim();
220 const m = raw.match( /^\s*(-?\d+(?:\.\d+)?)\s*([a-z%]*)\s*$/i );
221 if ( ! m ) {
222 return { num: raw, unit: '' };
223 }
224 return { num: ( m[1] || '' ), unit: ( m[2] || '' ) };
225 }
226
227 // -----------------------------------------------------------------------------------------------------------------
228 // Import logic (unchanged)
229 // -----------------------------------------------------------------------------------------------------------------
230 function normalize_imported_settings( parsed ) {
231
232 // Accept either:
233 // 1) { options:{}, css_vars:{}, bfb_options:{} }
234 // 2) { options:{...} } (css_vars optional)
235 // 3) { ...optionsMap... } (we wrap into {options:...})
236 var out = parsed;
237
238 if ( ! out || typeof out !== 'object' ) {
239 return { options: {}, css_vars: {} };
240 }
241
242 var has_shape =
243 Object.prototype.hasOwnProperty.call( out, 'options' ) ||
244 Object.prototype.hasOwnProperty.call( out, 'css_vars' ) ||
245 Object.prototype.hasOwnProperty.call( out, 'bfb_options' );
246
247 if ( ! has_shape ) {
248 out = { options: out, css_vars: {} };
249 }
250
251 if ( ! out.options || typeof out.options !== 'object' ) {
252 out.options = {};
253 }
254 if ( ! out.css_vars || typeof out.css_vars !== 'object' ) {
255 out.css_vars = {};
256 }
257 if ( out.bfb_options && typeof out.bfb_options !== 'object' ) {
258 out.bfb_options = {};
259 }
260
261 return out;
262 }
263
264 /**
265 * Apply canvas effects immediately (without relying on DOM events).
266 * This fixes the case when canvas was rebuilt after settings apply.
267 *
268 * @param {Object} settings_obj Normalized settings pack {options, css_vars, ...}
269 * @param {Object} ctx
270 */
271 function run_settings_effects_now( settings_obj, ctx ) {
272 try {
273 var eff = w.WPBC_BFB_Settings_Effects || null;
274 if ( ! eff || typeof eff.apply_all !== 'function' ) {
275 return;
276 }
277 if ( ! settings_obj || ! settings_obj.options || typeof settings_obj.options !== 'object' ) {
278 return;
279 }
280 eff.apply_all( settings_obj.options, ctx || { source: 'debug-import' } );
281 } catch ( e ) {
282 console.error( 'Debug UI: run_settings_effects_now error', e );
283 }
284 }
285
286 function apply_imported_settings( settings_obj ) {
287 var form_name = get_current_form_name();
288
289 // 1) Update sidebar UI + let modules react.
290 dispatch_event_safe( 'wpbc:bfb:form_settings:apply', {
291 settings : settings_obj,
292 form_name: form_name
293 } );
294
295 // Optional hint for any listeners that want to refresh debug outputs.
296 dispatch_event_safe( 'wpbc:bfb:form_settings:changed', {
297 settings : settings_obj,
298 form_name: form_name,
299 source : 'debug-import'
300 } );
301
302 // 2) Apply effects right away to current canvas (no rebuild yet).
303 // (Even if rebuild happens later, this gives immediate feedback.)
304 run_settings_effects_now( settings_obj, { source: 'debug-import-apply' } );
305 }
306
307 function apply_imported_structure( structure_arr, on_done ) {
308 with_builder( function ( builder ) {
309 if ( ! builder ) {
310 show_message( 'Debug UI: Builder is not ready.', 'error', 8000 );
311 if ( typeof on_done === 'function' ) {
312 on_done( false, null );
313 }
314 return;
315 }
316
317 try {
318 // Prefer canonical loader if present (keeps compatibility with legacy callers).
319 if ( typeof w.wpbc_bfb__on_structure_loaded === 'function' ) {
320 w.wpbc_bfb__on_structure_loaded( structure_arr );
321 } else if ( typeof builder.load_saved_structure === 'function' ) {
322 builder.load_saved_structure( structure_arr, { deferIfTyping: false } );
323 } else if ( w.wpbc_bfb_api && typeof w.wpbc_bfb_api.load_structure === 'function' ) {
324 w.wpbc_bfb_api.load_structure( structure_arr );
325 } else {
326 show_message( 'Debug UI: No structure loader found.', 'error', 8000 );
327 if ( typeof on_done === 'function' ) {
328 on_done( false, builder );
329 }
330 return;
331 }
332
333 // IMPORTANT:
334 // Do NOT do rebuild:true here, because load_saved_structure() already rebuilt DOM.
335 // Rebuild:true would rebuild twice and can wipe effects.
336 if ( typeof builder.refresh_canvas === 'function' ) {
337 builder.refresh_canvas( {
338 hard : true,
339 rebuild: false,
340 reinit : true,
341 source : 'debug-import-structure'
342 } );
343 }
344
345 if ( typeof on_done === 'function' ) {
346 on_done( true, builder );
347 }
348 } catch ( e ) {
349 show_message( 'Debug UI: Failed to apply structure. Check console.', 'error', 8000 );
350 console.error( 'Debug UI apply_imported_structure error', e );
351 if ( typeof on_done === 'function' ) {
352 on_done( false, builder );
353 }
354 }
355 } );
356 }
357
358 function import_from_textareas( mode ) {
359 var s_text = ( d.getElementById( 'wpbc_bfb__structure_import' ) || {} ).value || '';
360 var o_text = ( d.getElementById( 'wpbc_bfb__settings_import' ) || {} ).value || '';
361
362 var structure = null;
363 var settings = null;
364
365 if ( mode === 'structure' || mode === 'both' ) {
366 var parsed_s = safe_json_parse( s_text );
367 if ( ! Array.isArray( parsed_s ) ) {
368 show_message( 'Debug UI: Structure JSON must be an array.', 'error', 8000 );
369 return;
370 }
371 structure = parsed_s;
372 }
373
374 if ( mode === 'settings' || mode === 'both' ) {
375 var parsed_o = safe_json_parse( o_text );
376 if ( ! parsed_o || typeof parsed_o !== 'object' ) {
377 show_message( 'Debug UI: Settings JSON must be an object.', 'error', 8000 );
378 return;
379 }
380 settings = normalize_imported_settings( parsed_o );
381 }
382
383 // Apply settings first (updates sidebar + immediate effects).
384 if ( settings ) {
385 apply_imported_settings( settings );
386 }
387
388 // Structure path: structure load rebuilds DOM -> re-apply settings AFTER load.
389 if ( structure ) {
390 apply_imported_structure( structure, function ( ok ) {
391 if ( ! ok ) {
392 return;
393 }
394
395 // Re-dispatch settings apply AFTER structure rebuild, so settings_effects.js
396 // applies to the FINAL DOM (new wrappers).
397 if ( settings ) {
398 var form_name = get_current_form_name();
399
400 // 1) Dispatch apply again (this is what settings_effects.js listens for).
401 dispatch_event_safe( 'wpbc:bfb:form_settings:apply', {
402 settings : settings,
403 form_name: form_name
404 } );
405
406 // 2) Direct call fallback (in case the effects listener is not ready at that moment).
407 run_settings_effects_now( settings, { source: 'debug-import-after-structure' } );
408
409 setTimeout( function () {
410 run_settings_effects_now( settings, { source: 'debug-import-after-structure-delayed' } );
411 }, 60 );
412 }
413
414 show_message( 'Imported into Builder (structure/settings).', 'success', 3000 );
415
416 setTimeout( function () {
417 schedule_update();
418 }, 160 );
419 } );
420
421 return;
422 }
423
424 // Settings-only import: DO NOT rebuild structure.
425 with_builder( function ( builder ) {
426 if ( builder && typeof builder.refresh_canvas === 'function' ) {
427 builder.refresh_canvas( {
428 hard : true,
429 rebuild: false,
430 reinit : true,
431 source : 'debug-import-settings-only'
432 } );
433 }
434
435 // Ensure effects apply even if refresh_canvas rebuilt some preview markup.
436 if ( settings ) {
437 run_settings_effects_now( settings, { source: 'debug-import-settings-only' } );
438 setTimeout( function () {
439 run_settings_effects_now( settings, { source: 'debug-import-settings-only-delayed' } );
440 }, 60 );
441 }
442
443 show_message( 'Imported settings into UI.', 'success', 2500 );
444
445 setTimeout( function () {
446 schedule_update();
447 }, 160 );
448 } );
449 }
450
451 // -----------------------------------------------------------------------------------------------------------------
452 // Populate outputs (NO UI creation)
453 // -----------------------------------------------------------------------------------------------------------------
454 function render_outputs( structure ) {
455 if ( ! is_debug_tab_visible() ) {
456 return;
457 }
458
459 const form_name = get_current_form_name();
460 const form_settings = get_current_form_settings( form_name );
461
462 // Exporter outputs (Advanced + Content).
463 if ( w.WPBC_BFB_Exporter && typeof w.WPBC_BFB_Exporter.export_all === 'function' ) {
464 let export_options = { gapPercent: 3 };
465
466 // Best-effort: match Preview export options (width + slug) for accurate output.
467 try {
468 const width_combined = ( form_settings && form_settings.options )
469 ? ( form_settings.options.booking_form_layout_width || '' )
470 : '';
471
472 const parsed_width = parse_length_value( width_combined );
473 const width_unit = parsed_width.unit ? parsed_width.unit : '%';
474
475 export_options = {
476 gapPercent : 3,
477 form_slug : form_name,
478 form_width_value: parsed_width.num,
479 form_width_unit : width_unit
480 };
481 } catch ( _e1 ) {}
482
483 try {
484 const pkg = w.WPBC_BFB_Exporter.export_all( structure || [], export_options );
485
486 set_value( 'wpbc_bfb__advanced_form_output', ( pkg && pkg.advanced_form ) ? pkg.advanced_form : '' );
487 set_value( 'wpbc_bfb__content_form_output', ( pkg && pkg.fields_data ) ? pkg.fields_data : '' );
488 } catch ( e2 ) {
489 set_value( 'wpbc_bfb__advanced_form_output', '// Exporter error: ' + ( ( e2 && e2.message ) ? e2.message : String( e2 ) ) );
490 set_value( 'wpbc_bfb__content_form_output', '' );
491 }
492 } else {
493 set_value( 'wpbc_bfb__advanced_form_output', '// WPBC_BFB_Exporter not loaded.' );
494 set_value( 'wpbc_bfb__content_form_output', '// WPBC_BFB_Exporter not loaded.' );
495 }
496
497 // Structure JSON export + prefill import.
498 try {
499 const s = JSON.stringify( structure || [], null, 2 );
500 set_value( 'wpbc_bfb__structure_output', s );
501 set_value_if_empty( 'wpbc_bfb__structure_import', s );
502 } catch ( e3 ) {
503 set_value( 'wpbc_bfb__structure_output', '// Error serializing structure: ' + ( ( e3 && e3.message ) ? e3.message : String( e3 ) ) );
504 }
505
506 // Settings JSON export + prefill import.
507 try {
508 const o = JSON.stringify( form_settings || { options: {}, css_vars: {} }, null, 2 );
509 set_value( 'wpbc_bfb__settings_output', o );
510 set_value_if_empty( 'wpbc_bfb__settings_import', o );
511 } catch ( e4 ) {
512 set_value( 'wpbc_bfb__settings_output', '// Error serializing settings: ' + ( ( e4 && e4.message ) ? e4.message : String( e4 ) ) );
513 }
514 }
515
516 // Debounce to avoid heavy exports on rapid drag/drop.
517 let update_timer = 0;
518
519 function schedule_update( structure ) {
520 w.clearTimeout( update_timer );
521 update_timer = w.setTimeout( function () {
522 render_outputs( structure || get_current_structure() );
523 }, 80 );
524 }
525
526 // -----------------------------------------------------------------------------------------------------------------
527 // Bind existing buttons (no UI creation)
528 // -----------------------------------------------------------------------------------------------------------------
529 function bind_debug_buttons_once() {
530 const panel = get_debug_panel();
531 if ( ! panel ) {
532 return;
533 }
534
535 // 1) If you later add data attributes, we support them.
536 const data_btns = panel.querySelectorAll( '[data-wpbc-bfb-debug-action]' );
537 if ( data_btns && data_btns.length ) {
538 data_btns.forEach( function ( btn ) {
539 if ( ! btn || ! btn.addEventListener ) {
540 return;
541 }
542 if ( btn.getAttribute( 'data-wpbc-bfb-debug-bound' ) === '1' ) {
543 return;
544 }
545 btn.setAttribute( 'data-wpbc-bfb-debug-bound', '1' );
546
547 btn.addEventListener( 'click', function ( e ) {
548 e.preventDefault();
549
550 const action = String( btn.getAttribute( 'data-wpbc-bfb-debug-action' ) || '' ).toLowerCase();
551 const target = String( btn.getAttribute( 'data-wpbc-bfb-debug-target' ) || '' );
552
553 if ( action === 'copy' && target ) {
554 const ta = d.getElementById( target );
555 if ( ta ) {
556 copy_text_to_clipboard( ta.value, btn );
557 }
558 return;
559 }
560
561 if ( action === 'apply_structure' ) {
562 import_from_textareas( 'structure' );
563 return;
564 }
565 if ( action === 'apply_settings' ) {
566 import_from_textareas( 'settings' );
567 return;
568 }
569 if ( action === 'apply_both' ) {
570 import_from_textareas( 'both' );
571 return;
572 }
573 if ( action === 'refresh' ) {
574 schedule_update();
575 return;
576 }
577 } );
578 } );
579
580 return; // data-attrs are the preferred binding method.
581 }
582
583 // 2) Fallback binding for your current HTML (by textarea id + button order in block).
584 const blocks = panel.querySelectorAll( '.wpbc_bfb__debug_block' );
585 if ( ! blocks || ! blocks.length ) {
586 return;
587 }
588
589 blocks.forEach( function ( block ) {
590 const ta = block.querySelector( 'textarea' );
591 if ( ! ta || ! ta.id ) {
592 return;
593 }
594
595 const btns = block.querySelectorAll( 'button' );
596 if ( ! btns || ! btns.length ) {
597 return;
598 }
599
600 // Copy = always first button.
601 const btn_copy = btns[0];
602 if ( btn_copy && btn_copy.getAttribute( 'data-wpbc-bfb-debug-bound' ) !== '1' ) {
603 btn_copy.setAttribute( 'data-wpbc-bfb-debug-bound', '1' );
604 btn_copy.addEventListener( 'click', function ( e ) {
605 e.preventDefault();
606 copy_text_to_clipboard( ta.value, btn_copy );
607 } );
608 }
609
610 // Import blocks have more buttons.
611 if ( ta.id === 'wpbc_bfb__structure_import' ) {
612 if ( btns[1] && btns[1].getAttribute( 'data-wpbc-bfb-debug-bound' ) !== '1' ) {
613 btns[1].setAttribute( 'data-wpbc-bfb-debug-bound', '1' );
614 btns[1].addEventListener( 'click', function ( e ) {
615 e.preventDefault();
616 import_from_textareas( 'structure' );
617 } );
618 }
619 if ( btns[2] && btns[2].getAttribute( 'data-wpbc-bfb-debug-bound' ) !== '1' ) {
620 btns[2].setAttribute( 'data-wpbc-bfb-debug-bound', '1' );
621 btns[2].addEventListener( 'click', function ( e ) {
622 e.preventDefault();
623 import_from_textareas( 'both' );
624 } );
625 }
626 }
627
628 if ( ta.id === 'wpbc_bfb__settings_import' ) {
629 if ( btns[1] && btns[1].getAttribute( 'data-wpbc-bfb-debug-bound' ) !== '1' ) {
630 btns[1].setAttribute( 'data-wpbc-bfb-debug-bound', '1' );
631 btns[1].addEventListener( 'click', function ( e ) {
632 e.preventDefault();
633 import_from_textareas( 'settings' );
634 } );
635 }
636 if ( btns[2] && btns[2].getAttribute( 'data-wpbc-bfb-debug-bound' ) !== '1' ) {
637 btns[2].setAttribute( 'data-wpbc-bfb-debug-bound', '1' );
638 btns[2].addEventListener( 'click', function ( e ) {
639 e.preventDefault();
640 import_from_textareas( 'both' );
641 } );
642 }
643 }
644 } );
645 }
646
647 // -----------------------------------------------------------------------------------------------------------------
648 // Wiring
649 // -----------------------------------------------------------------------------------------------------------------
650 d.addEventListener( 'DOMContentLoaded', function () {
651 bind_debug_buttons_once();
652 schedule_update();
653 } );
654
655 // Refresh when switching to Debug tab OR debug internal tabs.
656 d.addEventListener( 'wpbc:bfb:top-tab', function ( ev ) {
657 const tab_id = ( ev && ev.detail && ev.detail.tab ) ? String( ev.detail.tab ) : '';
658 if ( tab_id === 'debug_tab' || tab_id.indexOf( 'debug_mode__' ) === 0 ) {
659 bind_debug_buttons_once();
660 schedule_update();
661 }
662 } );
663
664 // Fallback event (if you still dispatch it somewhere).
665 d.addEventListener( 'wpbc:bfb:structure:change', function () {
666 schedule_update();
667 } );
668
669 // Settings apply/changed should refresh debug output.
670 d.addEventListener( 'wpbc:bfb:form_settings:apply', function () {
671 schedule_update();
672 }, { passive: true } );
673
674 d.addEventListener( 'wpbc:bfb:form_settings:changed', function () {
675 schedule_update();
676 }, { passive: true } );
677
678 // Low-cost fallback: refresh on any change to SO controls (data-wpbc-u-save-*).
679 d.addEventListener(
680 'change',
681 function ( e ) {
682 const t = e ? e.target : null;
683 if ( ! t || ! t.getAttribute ) {
684 return;
685 }
686
687 const attrs = t.attributes || null;
688 if ( ! attrs || ! attrs.length ) {
689 return;
690 }
691
692 for ( let i = 0; i < attrs.length; i++ ) {
693 const n = attrs[i] ? String( attrs[i].name || '' ) : '';
694 if ( n && n.indexOf( 'data-wpbc-u-save-' ) === 0 ) {
695 schedule_update();
696 return;
697 }
698 }
699 },
700 { passive: true }
701 );
702
703 // Prefer EventBus when available.
704 (function hook_bus() {
705 const Core = w.WPBC_BFB_Core || {};
706 const EV = Core.WPBC_BFB_Events || null;
707
708 if ( ! EV || ! w.wpbc_bfb_api || ! w.wpbc_bfb_api.ready ) {
709 return;
710 }
711
712 w.wpbc_bfb_api.ready.then( function ( builder ) {
713 if ( ! builder || ! builder.bus || typeof builder.bus.on !== 'function' ) {
714 return;
715 }
716
717 [
718 EV.STRUCTURE_CHANGE,
719 EV.FIELD_ADD,
720 EV.FIELD_REMOVE,
721 EV.STRUCTURE_LOADED
722 ].filter( Boolean ).forEach( function ( event_name ) {
723 try {
724 builder.bus.on( event_name, function () {
725 schedule_update();
726 } );
727 } catch ( e ) {
728 if ( w._wpbc && w._wpbc.dev && typeof w._wpbc.dev.error === 'function' ) {
729 w._wpbc.dev.error( 'debug-ui: bus.on failed', event_name, e );
730 }
731 }
732 } );
733 } );
734 })();
735 })( window, document );