| 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 |
|