PluginProbe
Booking Calendar / 11.6.1
Booking Calendar v11.6.1
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.js

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

785 lines 27.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /* globals window, document */
2 (function (w, d) {
3 'use strict';
4
5 /**
6 * BFB Form Settings UI bridge.
7 *
8 * Listens to:
9 * - wpbc:bfb:form_settings:apply (from AJAX load) -> apply to controls
10 * - wpbc:bfb:form_settings:collect (from AJAX save) -> collect from controls
11 *
12 * Optional:
13 * - re-apply after Builder STRUCTURE_LOADED (timing hook only)
14 */
15 const api = (w.WPBC_BFB_FormSettings = w.WPBC_BFB_FormSettings || {});
16 let pending_time_picker_skin_button = null;
17 let previous_time_picker_skin_url = '';
18
19 // Last received settings pack (from AJAX).
20 let last_settings_pack = null;
21
22 // Small retry, because DOM can be re-rendered after apply event.
23 let raf_id = 0;
24 let retry_count = 0;
25 const retry_max = 20;
26 const fallback_form_style_option_keys = [
27 'booking_form_style',
28 'booking_form_accent_enabled',
29 'booking_form_accent_color',
30 'booking_form_custom_background_color',
31 'booking_form_custom_border_color',
32 'booking_form_custom_border_width',
33 'booking_form_custom_border_radius',
34 'booking_form_custom_padding_vertical',
35 'booking_form_custom_padding_horizontal',
36 'booking_form_custom_text_color',
37 'booking_form_custom_field_background_color',
38 'booking_form_custom_field_text_color',
39 'booking_form_custom_field_border_color',
40 'booking_form_custom_button_background_color',
41 'booking_form_custom_button_text_color',
42 'booking_form_custom_button_border_color',
43 'booking_form_custom_button_hover_background_color',
44 'booking_form_custom_button_hover_text_color',
45 'booking_form_custom_button_hover_border_color',
46 'booking_form_custom_secondary_button_background_color',
47 'booking_form_custom_secondary_button_text_color',
48 'booking_form_custom_secondary_button_border_color',
49 'booking_form_custom_secondary_button_hover_background_color',
50 'booking_form_custom_secondary_button_hover_text_color',
51 'booking_form_custom_secondary_button_hover_border_color',
52 'booking_form_custom_button_border_width',
53 'booking_form_custom_button_border_radius',
54 'booking_form_theme',
55 'booking_form_container_style',
56 'booking_form_background_color',
57 'booking_form_border_color',
58 'booking_form_border_width',
59 'booking_form_border_radius',
60 'booking_form_padding',
61 'booking_form_text_color',
62 'booking_form_field_background_color',
63 'booking_form_field_text_color',
64 'booking_form_field_border_color'
65 ];
66
67 // -----------------------------------------------------------------------------------------------
68 // Small helpers
69 // -----------------------------------------------------------------------------------------------
70
71 function query_all(root, selector) {
72 return Array.from((root || d).querySelectorAll(selector));
73 }
74
75 function css_escape(value) {
76 const v = String(value == null ? '' : value);
77 if (w.CSS && typeof w.CSS.escape === 'function') return w.CSS.escape(v);
78 return v.replace(/[^a-zA-Z0-9_\-]/g, '\\$&');
79 }
80
81 function is_on(value) {
82 const v = String(value == null ? '' : value).trim().toLowerCase();
83 return (v === 'on' || v === '1' || v === 'true' || v === 'yes');
84 }
85
86 function set_initial_attr(el, value) {
87 if (!el) return;
88 el.setAttribute('data-wpbc-bfb-fs-initial', String(value == null ? '' : value));
89 }
90
91 function trigger_change(el) {
92 if (!el) return;
93 try { el.dispatchEvent(new Event('change', { bubbles: true })); } catch (_) {}
94 }
95
96 function trigger_input(el) {
97 if ( !el ) return;
98 try { el.dispatchEvent( new Event( 'input', { bubbles: true } ) ); } catch ( _ ) {}
99 }
100
101 function find_rows(scope) {
102 const rows = query_all(d, '.wpbc-setting[data-key]');
103 if (!scope) return rows;
104
105 return rows.filter(function (row) {
106 return String(row.getAttribute('data-scope') || '') === String(scope);
107 });
108 }
109
110 function has_any_rows() {
111 return query_all(d, '.wpbc-setting[data-key]').length > 0;
112 }
113
114 function init_coloris_pickers(root) {
115 if ( ! root || ! w.Coloris ) {
116 return;
117 }
118
119 const inputs = query_all(root, 'input[data-wpbc-bfb-fs-type="color"][data-coloris], input[data-inspector-type="color"][data-coloris]');
120 if ( ! inputs.length ) {
121 return;
122 }
123
124 inputs.forEach(function (input) {
125 if (input.classList.contains('wpbc_bfb_coloris')) return;
126 input.classList.add('wpbc_bfb_coloris');
127 });
128
129 try {
130 w.Coloris({
131 el : '.wpbc_bfb_coloris',
132 alpha : false,
133 format : 'hex',
134 themeMode: 'auto',
135 onChange : function (color, input) {
136 if ( ! input ) {
137 return;
138 }
139 try {
140 input.dispatchEvent( new CustomEvent( 'wpbc:bfb:coloris:change', {
141 bubbles: true,
142 detail : {
143 color : color,
144 currentEl: input
145 }
146 } ) );
147 } catch ( _e ) {}
148 }
149 });
150 } catch (e) {
151 console.warn('WPBC Form Settings: Coloris init failed:', e);
152 }
153 }
154
155 function get_default_custom_appearance_settings() {
156 const localized_custom_style = w.wpbc_bfb_settings_vars && w.wpbc_bfb_settings_vars.custom_form_style_defaults
157 ? w.wpbc_bfb_settings_vars.custom_form_style_defaults
158 : {};
159 const localized_form_accent = w.wpbc_bfb_settings_vars && w.wpbc_bfb_settings_vars.form_accent_defaults
160 ? w.wpbc_bfb_settings_vars.form_accent_defaults
161 : {};
162
163 return Object.assign( {
164 booking_form_accent_enabled: 'Off',
165 booking_form_custom_background_color : '#ffffff',
166 booking_form_custom_border_color : '#cccccc',
167 booking_form_custom_border_width : '1px',
168 booking_form_custom_border_radius : '2px',
169 booking_form_custom_padding_vertical : '10px',
170 booking_form_custom_padding_horizontal : '30px',
171 booking_form_custom_text_color : '#1d2327',
172 booking_form_custom_field_background_color : '#ffffff',
173 booking_form_custom_field_text_color : '#3c434a',
174 booking_form_custom_field_border_color : '#cccccc',
175 booking_form_custom_button_background_color: '#066aab',
176 booking_form_custom_button_text_color : '#ffffff',
177 booking_form_custom_button_border_color : '#066aab',
178 booking_form_custom_button_hover_background_color: '#055589',
179 booking_form_custom_button_hover_text_color: '#ffffff',
180 booking_form_custom_button_hover_border_color: '#055589',
181 booking_form_custom_secondary_button_background_color: '#fdfdfd',
182 booking_form_custom_secondary_button_text_color: '#444444',
183 booking_form_custom_secondary_button_border_color: '#eeeeee',
184 booking_form_custom_secondary_button_hover_background_color: '#fdfdfd',
185 booking_form_custom_secondary_button_hover_text_color: '#444444',
186 booking_form_custom_secondary_button_hover_border_color: '#4d91cd',
187 booking_form_custom_button_border_width : '1px',
188 booking_form_custom_button_border_radius : '3px'
189 }, localized_custom_style, localized_form_accent );
190 }
191
192 function get_form_style_option_keys() {
193 const localized = w.wpbc_bfb_settings_vars && Array.isArray( w.wpbc_bfb_settings_vars.form_style_option_keys )
194 ? w.wpbc_bfb_settings_vars.form_style_option_keys
195 : [];
196
197 return localized.length ? localized : fallback_form_style_option_keys;
198 }
199
200 function strip_form_style_options_from_pack(settings_pack) {
201 if (!settings_pack || typeof settings_pack !== 'object') return settings_pack;
202 if (!settings_pack.options || typeof settings_pack.options !== 'object') return settings_pack;
203
204 get_form_style_option_keys().forEach(function (key) {
205 delete settings_pack.options[key];
206 });
207
208 return settings_pack;
209 }
210
211 // -----------------------------------------------------------------------------------------------
212 // Row setter
213 // -----------------------------------------------------------------------------------------------
214
215 function set_value_for_row(row, value, opts) {
216 if (!row) return;
217
218 const row_type = String(row.getAttribute('data-type') || '');
219 const row_key = String(row.getAttribute('data-key') || '');
220 const do_trigger_events = !!(opts && opts.trigger_change);
221
222 if (!row_key) return;
223
224 // Radio group
225 if (row_type === 'radio') {
226 const wrap = row.querySelector('.wpbc_bfb__form_setting_radio[data-wpbc-bfb-fs-controlid]');
227 const control_id = wrap ? String(wrap.getAttribute('data-wpbc-bfb-fs-controlid') || '') : '';
228 if (!control_id) return;
229
230 const target_value = String(value == null ? '' : value);
231 const radios = query_all(row, 'input[type="radio"][name="' + css_escape(control_id) + '"]');
232
233 let checked_radio = null;
234 radios.forEach(function (radio) {
235 const should_check = (String(radio.value) === target_value);
236 radio.checked = should_check;
237 if (should_check) checked_radio = radio;
238
239 const choice = radio.closest ? radio.closest('.wpbc_theme_choice') : null;
240 if ( choice ) {
241 choice.classList.toggle('is-selected', should_check);
242 }
243 });
244
245 if (wrap) set_initial_attr(wrap, target_value);
246 if (do_trigger_events && checked_radio) trigger_change(checked_radio);
247 return;
248 }
249
250 // Toggle
251 if (row_type === 'toggle') {
252 const checkbox =
253 row.querySelector('input[type="checkbox"][data-wpbc-bfb-fs-type="toggle"]') ||
254 row.querySelector('input[type="checkbox"]');
255
256 if (!checkbox) return;
257
258 const checked = is_on(value);
259 checkbox.checked = checked;
260 checkbox.setAttribute('aria-checked', checked ? 'true' : 'false');
261
262 set_initial_attr(checkbox, checked ? 'On' : 'Off');
263 if (do_trigger_events) trigger_change(checkbox);
264 return;
265 }
266
267 // Select
268 if (row_type === 'select') {
269 const select =
270 row.querySelector('select[data-wpbc-bfb-fs-type="select"]') ||
271 row.querySelector('select');
272
273 if (!select) return;
274
275 select.value = String(value == null ? '' : value);
276 set_initial_attr(select, select.value);
277 if (do_trigger_events) trigger_change(select);
278 return;
279 }
280
281 // Length: hidden combined + num/unit
282 if ( row_type === 'length' ) {
283 // JS slider length control: - hidden writer carries FS markers and must receive input event so wpbc_slider_len_groups.js syncs UI.
284 const writer =
285 row.querySelector( 'input[data-wpbc_slider_len_writer][data-wpbc-bfb-fs-type="length"]' ) ||
286 row.querySelector( 'input[data-wpbc-bfb-fs-type="length"]' );
287 if ( !writer ) return;
288
289 const combined = String( value == null ? '' : value );
290 writer.value = combined;
291 set_initial_attr( writer, combined );
292 if ( do_trigger_events ) trigger_input( writer );
293 return;
294 }
295
296 // Spacing: two number inputs saved into a hidden CSS shorthand writer.
297 if ( row_type === 'spacing' ) {
298 const group = row.querySelector( '.wpbc_spacing_group' );
299 const vertical_input = group ? group.querySelector( 'input[data-wpbc_spacing_vertical]' ) : null;
300 const horizontal_input = group ? group.querySelector( 'input[data-wpbc_spacing_horizontal]' ) : null;
301 const writer = group ? group.querySelector( 'input[data-wpbc_spacing_writer]' ) : null;
302 const parsed = parse_spacing_value( value );
303
304 if ( ! writer ) {
305 return;
306 }
307
308 if ( vertical_input ) {
309 vertical_input.value = parsed.vertical;
310 }
311 if ( horizontal_input ) {
312 horizontal_input.value = parsed.horizontal;
313 }
314 writer.value = parsed.combined;
315 set_initial_attr( writer, parsed.combined );
316 if ( do_trigger_events ) trigger_input( writer );
317 return;
318 }
319
320 // Range (slider number): writer is the number input.
321 if (row_type === 'range') {
322 const writer =
323 row.querySelector('input[data-wpbc_slider_range_writer]') ||
324 row.querySelector('input[data-wpbc-bfb-fs-key="' + css_escape(row_key) + '"]') ||
325 row.querySelector('input[type="number"]');
326 if (!writer) return;
327
328 writer.value = String(value == null ? '' : value);
329 set_initial_attr(writer, writer.value);
330 if (do_trigger_events) trigger_input(writer);
331 return;
332 }
333
334 // Default: input/textarea
335 const control =
336 row.querySelector('[data-wpbc-bfb-fs-key="' + css_escape(row_key) + '"]') ||
337 row.querySelector('input,textarea');
338
339 if (!control) return;
340
341 control.value = String(value == null ? '' : value);
342 set_initial_attr(control, control.value);
343 // For normal inputs, "input" gives better reactivity than "change".
344 if (do_trigger_events) trigger_input(control);
345 }
346
347 // -----------------------------------------------------------------------------------------------
348 // Public API
349 // -----------------------------------------------------------------------------------------------
350
351 /**
352 * Apply FLAT object (key=>value) to rows of some scope.
353 */
354 function apply_flat_settings(flat_settings, scope, opts) {
355 if (!flat_settings || typeof flat_settings !== 'object') return;
356
357 find_rows(scope).forEach(function (row) {
358 const key = String(row.getAttribute('data-key') || '');
359 if (!key) return;
360 if (!Object.prototype.hasOwnProperty.call(flat_settings, key)) return;
361 set_value_for_row(row, flat_settings[key], opts);
362 });
363 }
364
365 /**
366 * Apply settings.
367 *
368 * Supports:
369 * - flat: { booking_form_layout_width: '100%', ... }
370 * - pack: { options: {...}, css_vars: {...} } (Option A)
371 */
372 api.apply = function (settings_pack, scope, opts) {
373 if (!settings_pack || typeof settings_pack !== 'object') return;
374 if (!settings_pack.options || typeof settings_pack.options !== 'object') return; // strict Option A
375 strip_form_style_options_from_pack(settings_pack);
376 apply_flat_settings(settings_pack.options, scope || 'form', opts);
377 };
378
379 api.reset_custom_appearance = function () {
380 const defaults = get_default_custom_appearance_settings();
381
382 if ( ! last_settings_pack || typeof last_settings_pack !== 'object' ) {
383 last_settings_pack = { options: {}, css_vars: {} };
384 }
385 if ( ! last_settings_pack.options || typeof last_settings_pack.options !== 'object' ) {
386 last_settings_pack.options = {};
387 }
388 Object.keys( defaults ).forEach( function (key) {
389 last_settings_pack.options[key] = defaults[key];
390 } );
391
392 apply_flat_settings( defaults, 'global', { trigger_change: true } );
393 init_coloris_pickers( d );
394
395 if ( w.WPBC_BFB_Settings_Effects && typeof w.WPBC_BFB_Settings_Effects.apply_all === 'function' ) {
396 w.WPBC_BFB_Settings_Effects.apply_all( defaults, { source: 'reset-custom-appearance', options: defaults } );
397 }
398
399 try {
400 d.dispatchEvent( new CustomEvent( 'wpbc:bfb:form_settings:changed', {
401 bubbles: true,
402 detail : {
403 source : 'reset-custom-appearance',
404 settings: { options: Object.assign( {}, defaults ) }
405 }
406 } ) );
407 } catch ( _e ) {}
408 };
409
410 /**
411 * Collect current values (flat object).
412 */
413 api.collect = function (scope) {
414 const out = {};
415
416 find_rows(scope || 'form').forEach(function (row) {
417 const key = String(row.getAttribute('data-key') || '');
418 const type = String(row.getAttribute('data-type') || '');
419 if (!key) return;
420
421 if (type === 'radio') {
422 const wrap = row.querySelector('.wpbc_bfb__form_setting_radio[data-wpbc-bfb-fs-controlid]');
423 const control_id = wrap ? String(wrap.getAttribute('data-wpbc-bfb-fs-controlid') || '') : '';
424 if (!control_id) return;
425
426 const checked = row.querySelector('input[type="radio"][name="' + css_escape(control_id) + '"]:checked');
427 out[key] = checked ? String(checked.value) : '';
428 return;
429 }
430
431 if (type === 'toggle') {
432 const checkbox =
433 row.querySelector('input[type="checkbox"][data-wpbc-bfb-fs-type="toggle"]') ||
434 row.querySelector('input[type="checkbox"]');
435 out[key] = checkbox && checkbox.checked ? 'On' : 'Off';
436 return;
437 }
438
439 if (type === 'select') {
440 const select = row.querySelector('select');
441 out[key] = select ? String(select.value) : '';
442 return;
443 }
444
445 if (type === 'length') {
446 const hidden = row.querySelector('input[data-wpbc-bfb-fs-type="length"]');
447 out[key] = hidden ? String(hidden.value || '') : '';
448 return;
449 }
450
451 if (type === 'spacing') {
452 out[key] = get_spacing_value(row);
453 return;
454 }
455
456 if (type === 'range') {
457 const writer =
458 row.querySelector('input[data-wpbc_slider_range_writer]') ||
459 row.querySelector('input[type="number"]') ||
460 row.querySelector('input[type="range"]');
461 out[key] = writer ? String(writer.value || '') : '';
462 return;
463 }
464 const control = row.querySelector('input,textarea');
465 out[key] = control ? String(control.value || '') : '';
466 });
467
468 return out;
469 };
470
471 function parse_spacing_value(value) {
472 const fallback = { vertical: '10', horizontal: '30', combined: '10px 30px' };
473 const matches = String(value == null ? '' : value).match(/-?\d+(?:\.\d+)?/g) || [];
474 let vertical = matches[0] != null ? String(matches[0]) : fallback.vertical;
475 let horizontal = matches[1] != null ? String(matches[1]) : vertical;
476
477 if (isNaN(Number(vertical))) {
478 vertical = fallback.vertical;
479 }
480 if (isNaN(Number(horizontal))) {
481 horizontal = fallback.horizontal;
482 }
483
484 return {
485 vertical : vertical,
486 horizontal: horizontal,
487 combined : vertical + 'px ' + horizontal + 'px'
488 };
489 }
490
491 function get_spacing_value(row) {
492 const group = row ? row.querySelector('.wpbc_spacing_group') : null;
493 const vertical_input = group ? group.querySelector('input[data-wpbc_spacing_vertical]') : null;
494 const horizontal_input = group ? group.querySelector('input[data-wpbc_spacing_horizontal]') : null;
495 const writer = group ? group.querySelector('input[data-wpbc_spacing_writer]') : null;
496 const vertical = vertical_input ? String(vertical_input.value || '0') : '0';
497 const horizontal = horizontal_input ? String(horizontal_input.value || vertical) : vertical;
498 const combined = parse_spacing_value(vertical + 'px ' + horizontal + 'px').combined;
499
500 if (writer) {
501 writer.value = combined;
502 }
503
504 return combined;
505 }
506
507 /**
508 * Re-apply last received settings (useful after DOM re-render).
509 */
510 api.reapply_last = function () {
511 if (!last_settings_pack) return;
512 api.apply(last_settings_pack, 'form', { trigger_change: true });
513 };
514
515 api.init = function () {
516 init_coloris_pickers(d);
517
518 // If apply event fired before init, try again now.
519 if (last_settings_pack) schedule_apply_retry();
520 };
521
522 // -----------------------------------------------------------------------------------------------
523 // DOM Events (AJAX layer)
524 // -----------------------------------------------------------------------------------------------
525
526 // Save: let modules contribute into { options:{}, css_vars:{} }
527 d.addEventListener('wpbc:bfb:form_settings:collect', function (e) {
528 const detail = (e && e.detail) ? e.detail : {};
529 const target_pack = detail.settings;
530
531 if (!target_pack || typeof target_pack !== 'object') return;
532
533 // Option A: write into target_pack.options
534 if (!target_pack.options || typeof target_pack.options !== 'object') {
535 target_pack.options = {};
536 }
537
538 const collected = api.collect('form');
539 Object.keys(collected).forEach(function (k) {
540 target_pack.options[k] = collected[k];
541 });
542 strip_form_style_options_from_pack(target_pack);
543 });
544
545 // Load: receive settings from AJAX and apply.
546 d.addEventListener('wpbc:bfb:form_settings:apply', function (e) {
547 const detail = (e && e.detail) ? e.detail : {};
548
549 last_settings_pack = detail.settings || null;
550
551 retry_count = 0;
552 schedule_apply_retry();
553 });
554
555 d.addEventListener( 'click', function (e) {
556 const btn = e && e.target && e.target.closest ? e.target.closest( '[data-wpbc-bfb-reset-custom-appearance]' ) : null;
557 if ( ! btn ) {
558 return;
559 }
560
561 e.preventDefault();
562 api.reset_custom_appearance();
563 }, false );
564
565 /**
566 * Switch the global time-picker skin to the form-aware Automatic skin.
567 *
568 * The option is saved through the existing nonce-protected option saver.
569 * Legacy skins are changed only by this explicit user action.
570 *
571 * @param {HTMLButtonElement} button Accent action button.
572 * @returns {boolean} Whether an AJAX option save was started.
573 */
574 function switch_time_picker_to_automatic(button) {
575 const automatic_skin = '/css/time_picker_skins/form_style.css';
576 const current_skin = button ? String( button.getAttribute( 'data-wpbc-time-picker-skin-current' ) || '' ) : '';
577 const automatic_url = button ? String( button.getAttribute( 'data-wpbc-time-picker-skin-url' ) || '' ) : '';
578
579 if ( ! button || current_skin.slice( -automatic_skin.length ) === automatic_skin ) {
580 return false;
581 }
582 if ( typeof w.wpbc_save_option_from_element !== 'function' ) {
583 return false;
584 }
585
586 const time_api = w.WPBC_BFB_Core && w.WPBC_BFB_Core.Time ? w.WPBC_BFB_Core.Time : null;
587 if ( automatic_url ) {
588 previous_time_picker_skin_url = d.getElementById( 'wpbc-time_picker-skin-css' )
589 ? String( d.getElementById( 'wpbc-time_picker-skin-css' ).getAttribute( 'href' ) || '' )
590 : '';
591 if ( time_api && typeof time_api.apply_picker_skin_url === 'function' ) {
592 time_api.apply_picker_skin_url( automatic_url );
593 } else if ( typeof w.wpbc__css__change_skin === 'function' && d.getElementById( 'wpbc-time_picker-skin-css' ) ) {
594 w.wpbc__css__change_skin( automatic_url, 'wpbc-time_picker-skin-css' );
595 }
596 }
597
598 pending_time_picker_skin_button = button;
599 button.setAttribute( 'data-wpbc-u-save-value', automatic_skin );
600 w.wpbc_save_option_from_element( button );
601
602 return true;
603 }
604
605 /**
606 * Mark the Automatic skin as current after the option saver confirms it.
607 *
608 * @returns {void}
609 */
610 w.wpbc_bfb_time_picker_skin_saved = function () {
611 if ( pending_time_picker_skin_button ) {
612 pending_time_picker_skin_button.setAttribute( 'data-wpbc-time-picker-skin-current', '/css/time_picker_skins/form_style.css' );
613 }
614 if ( w.WPBC_BFB_Core && w.WPBC_BFB_Core.Time && typeof w.WPBC_BFB_Core.Time.ui_set_picker_skin_value === 'function' ) {
615 w.WPBC_BFB_Core.Time.ui_set_picker_skin_value( '/css/time_picker_skins/form_style.css' );
616 }
617 pending_time_picker_skin_button = null;
618 previous_time_picker_skin_url = '';
619 };
620
621 if ( w.jQuery ) {
622 w.jQuery( d ).on( 'wpbc:option:afterSave.wpbcBfbTimePickerSkin', function (event, response) {
623 if ( ! pending_time_picker_skin_button || ( response && response.success ) ) {
624 return;
625 }
626 const previous_skin = String( pending_time_picker_skin_button.getAttribute( 'data-wpbc-time-picker-skin-current' ) || '' );
627
628 const time_api = w.WPBC_BFB_Core && w.WPBC_BFB_Core.Time ? w.WPBC_BFB_Core.Time : null;
629 if ( previous_time_picker_skin_url && time_api && typeof time_api.apply_picker_skin_url === 'function' ) {
630 time_api.apply_picker_skin_url( previous_time_picker_skin_url );
631 } else if ( previous_time_picker_skin_url && typeof w.wpbc__css__change_skin === 'function' ) {
632 w.wpbc__css__change_skin( previous_time_picker_skin_url, 'wpbc-time_picker-skin-css' );
633 }
634 if ( w.WPBC_BFB_Core && w.WPBC_BFB_Core.Time && typeof w.WPBC_BFB_Core.Time.ui_set_picker_skin_value === 'function' ) {
635 w.WPBC_BFB_Core.Time.ui_set_picker_skin_value( previous_skin );
636 }
637 pending_time_picker_skin_button = null;
638 previous_time_picker_skin_url = '';
639 } );
640 }
641
642 /**
643 * Ask accent-capable field packs to copy the current accent into their
644 * existing editable color properties. Field packs update the counters.
645 *
646 * @param {HTMLButtonElement} button Action button.
647 * @returns {void}
648 */
649 api.apply_accent_to_components = function (button) {
650 const toggle = d.getElementById( 'booking_form_accent_enabled' );
651 const color_control = d.getElementById( 'booking_form_accent_color' );
652 const status = d.querySelector( '[data-wpbc-bfb-accent-components-status="1"]' );
653 const i18n = w.wpbc_bfb_settings_vars && w.wpbc_bfb_settings_vars.i18n ? w.wpbc_bfb_settings_vars.i18n : {};
654 const default_appearance = get_default_custom_appearance_settings();
655 const accent_color = color_control && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test( String( color_control.value || '' ).trim() )
656 ? String( color_control.value ).trim()
657 : String( default_appearance.booking_form_accent_color || '' ).trim();
658
659 if ( ! toggle || ! toggle.checked ) {
660 if ( status ) {
661 status.textContent = i18n.accent_enable_first || 'Enable Custom accent color first.';
662 }
663 return;
664 }
665
666 if ( button ) {
667 button.disabled = true;
668 }
669
670 const apply_to_builder = function (builder) {
671 const detail = {
672 accent_color: accent_color,
673 builder : builder || w.wpbc_bfb || null,
674 matched : 0,
675 updated : 0
676 };
677
678 try {
679 d.dispatchEvent( new CustomEvent( 'wpbc:bfb:apply-accent-to-components', {
680 bubbles: true,
681 detail : detail
682 } ) );
683 } catch ( _e ) {}
684
685 const time_picker_save_started = switch_time_picker_to_automatic( button );
686
687 if ( status ) {
688 if ( detail.updated > 0 ) {
689 status.textContent = ( 1 === detail.updated )
690 ? ( i18n.accent_applied_one || 'Accent applied to one form element. Save Form to keep the change.' )
691 : String( i18n.accent_applied_many || 'Accent applied to %d form elements. Save Form to keep the changes.' ).replace( '%d', String( detail.updated ) );
692 } else if ( detail.matched > 0 ) {
693 status.textContent = i18n.accent_already_applied || 'All supported form elements already have the current accent color.';
694 } else {
695 status.textContent = i18n.accent_no_elements || 'No accent-capable form elements were found.';
696 }
697 if ( time_picker_save_started ) {
698 status.textContent += ' ' + ( i18n.accent_time_picker_automatic || 'Time slots now use Automatic — Match Booking Form.' );
699 }
700 }
701
702 if ( detail.updated > 0 ) {
703 try {
704 d.dispatchEvent( new CustomEvent( 'wpbc:bfb:structure:change', {
705 bubbles: true,
706 detail : { source: 'apply-accent-to-components', updated: detail.updated }
707 } ) );
708 } catch ( _e2 ) {}
709 }
710
711 if ( detail.builder && typeof detail.builder._announce === 'function' ) {
712 detail.builder._announce( status ? status.textContent : ( i18n.accent_applied_announcement || 'Form accent applied.' ) );
713 }
714 if ( button && ! time_picker_save_started ) {
715 button.disabled = false;
716 }
717 };
718
719 if ( w.wpbc_bfb_api && w.wpbc_bfb_api.ready && typeof w.wpbc_bfb_api.ready.then === 'function' ) {
720 w.wpbc_bfb_api.ready.then( apply_to_builder );
721 return;
722 }
723
724 apply_to_builder( w.wpbc_bfb || null );
725 };
726
727 d.addEventListener( 'click', function (e) {
728 const btn = e && e.target && e.target.closest ? e.target.closest( '[data-wpbc-bfb-apply-accent-components="1"]' ) : null;
729 if ( ! btn ) {
730 return;
731 }
732
733 e.preventDefault();
734 api.apply_accent_to_components( btn );
735 }, false );
736
737 function schedule_apply_retry() {
738 if (raf_id) return;
739
740 raf_id = w.requestAnimationFrame(function () {
741 raf_id = 0;
742
743 // If settings UI not present yet, retry a few frames.
744 if (!has_any_rows()) {
745 retry_count++;
746 if (retry_count < retry_max) schedule_apply_retry();
747 return;
748 }
749
750 api.reapply_last();
751 init_coloris_pickers(d);
752 });
753 }
754
755 // -----------------------------------------------------------------------------------------------
756 // Optional Builder timing hook (STRUCTURE_LOADED) -> reapply_last()
757 // -----------------------------------------------------------------------------------------------
758
759 function bind_builder_timing_hook(builder_instance) {
760 const core = w.WPBC_BFB_Core;
761 const events = (core && core.WPBC_BFB_Events) ? core.WPBC_BFB_Events : (w.WPBC_BFB_Events || null);
762
763
764 if (!builder_instance || !builder_instance.bus || !events || !events.STRUCTURE_LOADED) return;
765
766 builder_instance.bus.on(events.STRUCTURE_LOADED, function () {
767 // Builder may re-render settings panel after structure load.
768 // Re-apply last settings pack (if any).
769 retry_count = 0;
770 schedule_apply_retry();
771 });
772 }
773
774 if (w.wpbc_bfb_api && w.wpbc_bfb_api.ready && typeof w.wpbc_bfb_api.ready.then === 'function') {
775 w.wpbc_bfb_api.ready.then(bind_builder_timing_hook);
776 } else {
777 setTimeout(function () { if (w.__B) { bind_builder_timing_hook( w.__B ); } }, 0);
778 }
779
780 // DOM ready init.
781 if (d.readyState === 'loading') d.addEventListener('DOMContentLoaded', api.init);
782 else api.init();
783
784 })(window, document);
785