| 1 |
/** |
| 2 |
* Drive the Setup Wizard Date Selection editor and real calendar preview. |
| 3 |
* |
| 4 |
* Canonical Booking Calendar helpers remain responsible for selection rules. |
| 5 |
* This adapter changes only request-local calendar parameters and draft fields; |
| 6 |
* it does not save settings or submit a booking. |
| 7 |
* |
| 8 |
* @package Booking Calendar |
| 9 |
*/ |
| 10 |
( function ( window, document ) { |
| 11 |
'use strict'; |
| 12 |
|
| 13 |
var module_config = window.wpbc_setup_wizard_date_selection || { i18n: {} }; |
| 14 |
|
| 15 |
/** |
| 16 |
* Create one isolated Date Selection step adapter. |
| 17 |
* |
| 18 |
* @param {Object} config Localized limits and messages. |
| 19 |
* @return {Object} Adapter accepted by the shared wizard shell. |
| 20 |
*/ |
| 21 |
function create_date_selection_adapter( config ) { |
| 22 |
var shell_api = null; |
| 23 |
var editor = null; |
| 24 |
var apply_timers = []; |
| 25 |
var preview_data_timer = null; |
| 26 |
var last_selected_mode = ''; |
| 27 |
|
| 28 |
/** |
| 29 |
* Return one localized message with a safe fallback. |
| 30 |
* |
| 31 |
* @param {string} key Translation key. |
| 32 |
* @param {string} fallback Fallback message. |
| 33 |
* @return {string} Translation or fallback. |
| 34 |
*/ |
| 35 |
function get_message( key, fallback ) { |
| 36 |
return config.i18n && config.i18n[ key ] ? String( config.i18n[ key ] ) : fallback; |
| 37 |
} |
| 38 |
|
| 39 |
/** |
| 40 |
* Return the selected mode identifier. |
| 41 |
* |
| 42 |
* @return {string} Selected mode or an empty string. |
| 43 |
*/ |
| 44 |
function get_selected_mode() { |
| 45 |
var selected = get_selected_mode_control(); |
| 46 |
|
| 47 |
return selected ? selected.value : ''; |
| 48 |
} |
| 49 |
|
| 50 |
/** |
| 51 |
* Return the selected server-authorized mode control. |
| 52 |
* |
| 53 |
* @return {HTMLElement|null} Selected radio control or null. |
| 54 |
*/ |
| 55 |
function get_selected_mode_control() { |
| 56 |
return editor ? editor.node.querySelector( '[data-wpbc-date-selection-mode]:checked' ) : null; |
| 57 |
} |
| 58 |
|
| 59 |
/** |
| 60 |
* Read one dependent-setting state from the selected mode policy. |
| 61 |
* |
| 62 |
* @param {string} setting_name Policy setting name in camel case. |
| 63 |
* @return {string} Policy state or `hidden_off`. |
| 64 |
*/ |
| 65 |
function get_mode_policy_state( setting_name ) { |
| 66 |
var selected_mode_control = get_selected_mode_control(); |
| 67 |
|
| 68 |
if ( ! selected_mode_control || ! selected_mode_control.dataset[ setting_name ] ) { |
| 69 |
return 'hidden_off'; |
| 70 |
} |
| 71 |
|
| 72 |
return String( selected_mode_control.dataset[ setting_name ] ); |
| 73 |
} |
| 74 |
|
| 75 |
/** |
| 76 |
* Apply defaults only when the customer changes the Date Selection mode. |
| 77 |
* |
| 78 |
* Time-based Customer Journeys default recurrent selected-time behavior to |
| 79 |
* on for Multiple days, while leaving the visible option editable afterward. |
| 80 |
* Tracking the previous mode prevents later synchronization from overwriting |
| 81 |
* an explicit customer choice. |
| 82 |
* |
| 83 |
* @param {HTMLElement|null} mode_control Changed mode radio control. |
| 84 |
* @return {void} |
| 85 |
*/ |
| 86 |
function apply_selected_mode_defaults( mode_control ) { |
| 87 |
var selected_mode; |
| 88 |
var recurrent_policy; |
| 89 |
var recurrent_toggle; |
| 90 |
|
| 91 |
if ( ! editor || ! mode_control || ! mode_control.checked ) { |
| 92 |
return; |
| 93 |
} |
| 94 |
|
| 95 |
selected_mode = String( mode_control.value || '' ); |
| 96 |
if ( selected_mode === last_selected_mode ) { |
| 97 |
return; |
| 98 |
} |
| 99 |
last_selected_mode = selected_mode; |
| 100 |
recurrent_policy = String( mode_control.dataset.wpbcDateSelectionPolicyRecurrentTime || '' ); |
| 101 |
if ( 'optional_default_on' !== recurrent_policy ) { |
| 102 |
return; |
| 103 |
} |
| 104 |
|
| 105 |
recurrent_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_recurrent_time"]' ); |
| 106 |
if ( recurrent_toggle ) { |
| 107 |
recurrent_toggle.checked = true; |
| 108 |
} |
| 109 |
} |
| 110 |
|
| 111 |
/** |
| 112 |
* Parse one bounded integer control. |
| 113 |
* |
| 114 |
* @param {string} selector Control selector below the editor. |
| 115 |
* @param {number} fallback Value used for an absent or invalid control. |
| 116 |
* @return {number} Parsed integer. |
| 117 |
*/ |
| 118 |
function get_integer( selector, fallback ) { |
| 119 |
var control = editor ? editor.node.querySelector( selector ) : null; |
| 120 |
var parsed = control ? parseInt( control.value, 10 ) : NaN; |
| 121 |
|
| 122 |
return isNaN( parsed ) ? fallback : parsed; |
| 123 |
} |
| 124 |
|
| 125 |
/** |
| 126 |
* Convert one weekday transport field to the canonical number array. |
| 127 |
* |
| 128 |
* @param {string} mode Range mode owning the transport field. |
| 129 |
* @return {number[]} Weekday numbers or the unrestricted marker. |
| 130 |
*/ |
| 131 |
function get_weekdays( mode ) { |
| 132 |
var transport = editor ? editor.node.querySelector( '[data-wpbc-date-selection-weekday-transport="' + mode + '"]' ) : null; |
| 133 |
var values = transport ? String( transport.value || '' ).split( ',' ) : []; |
| 134 |
var weekdays = values.map( function ( value ) { |
| 135 |
return parseInt( value, 10 ); |
| 136 |
} ).filter( function ( value ) { |
| 137 |
return -1 === value || ( value >= 0 && value <= 6 ); |
| 138 |
} ); |
| 139 |
|
| 140 |
return weekdays.length ? weekdays : [ -1 ]; |
| 141 |
} |
| 142 |
|
| 143 |
/** |
| 144 |
* Parse the released comma-and-range syntax into canonical day lengths. |
| 145 |
* |
| 146 |
* This intentionally mirrors the server validator instead of evaluating |
| 147 |
* input text. An empty expression means that every length within the |
| 148 |
* configured minimum and maximum is allowed. |
| 149 |
* |
| 150 |
* @param {string} raw_specific_days Untrusted text field value. |
| 151 |
* @return {number[]|null} Sorted unique lengths, an empty array, or null when invalid. |
| 152 |
*/ |
| 153 |
function parse_specific_days( raw_specific_days ) { |
| 154 |
var maximum_days = parseInt( config.max_dynamic_days || 1095, 10 ); |
| 155 |
var specific_expression = String( raw_specific_days || '' ).trim(); |
| 156 |
var specific_days = []; |
| 157 |
|
| 158 |
if ( '' === specific_expression ) { |
| 159 |
return specific_days; |
| 160 |
} |
| 161 |
if ( |
| 162 |
specific_expression.length > 500 |
| 163 |
|| ! /^\d+(?:\s*-\s*\d+)?(?:\s*,\s*\d+(?:\s*-\s*\d+)?)*$/.test( specific_expression ) |
| 164 |
) { |
| 165 |
return null; |
| 166 |
} |
| 167 |
|
| 168 |
specific_expression.split( ',' ).forEach( function ( specific_part ) { |
| 169 |
var range_bounds = specific_part.trim().split( /\s*-\s*/ ).map( function ( range_bound ) { |
| 170 |
return parseInt( range_bound, 10 ); |
| 171 |
} ); |
| 172 |
var range_start = Math.min.apply( null, range_bounds ); |
| 173 |
var range_end = Math.max.apply( null, range_bounds ); |
| 174 |
var specific_day; |
| 175 |
|
| 176 |
if ( null === specific_days ) { |
| 177 |
return; |
| 178 |
} |
| 179 |
|
| 180 |
if ( range_start < 1 || range_end > maximum_days ) { |
| 181 |
specific_days = null; |
| 182 |
return; |
| 183 |
} |
| 184 |
|
| 185 |
for ( specific_day = range_start; specific_day <= range_end; specific_day++ ) { |
| 186 |
if ( -1 === specific_days.indexOf( specific_day ) ) { |
| 187 |
specific_days.push( specific_day ); |
| 188 |
} |
| 189 |
} |
| 190 |
} ); |
| 191 |
|
| 192 |
return null === specific_days ? null : specific_days.sort( function ( first_day, second_day ) { |
| 193 |
return first_day - second_day; |
| 194 |
} ); |
| 195 |
} |
| 196 |
|
| 197 |
/** |
| 198 |
* Return the specific flexible-range lengths from the current editor. |
| 199 |
* |
| 200 |
* @return {number[]|null} Canonical lengths, an empty array, or null when invalid. |
| 201 |
*/ |
| 202 |
function get_specific_days() { |
| 203 |
var control = editor ? editor.node.querySelector( '[data-wpbc-date-selection-specific]' ) : null; |
| 204 |
|
| 205 |
return parse_specific_days( control ? control.value : '' ); |
| 206 |
} |
| 207 |
|
| 208 |
/** |
| 209 |
* Write visible weekday button state into its scalar transport field. |
| 210 |
* |
| 211 |
* @param {string} mode Range mode owning the controls. |
| 212 |
* @return {void} |
| 213 |
*/ |
| 214 |
function sync_weekday_transport( mode ) { |
| 215 |
var group = editor ? editor.node.querySelector( '[data-wpbc-date-selection-weekdays="' + mode + '"]' ) : null; |
| 216 |
var transport = editor ? editor.node.querySelector( '[data-wpbc-date-selection-weekday-transport="' + mode + '"]' ) : null; |
| 217 |
var selected = []; |
| 218 |
|
| 219 |
if ( ! group || ! transport ) { |
| 220 |
return; |
| 221 |
} |
| 222 |
|
| 223 |
group.querySelectorAll( '[data-weekday][aria-pressed="true"]' ).forEach( function ( button ) { |
| 224 |
selected.push( parseInt( button.dataset.weekday, 10 ) ); |
| 225 |
} ); |
| 226 |
selected = selected.filter( function ( value ) { |
| 227 |
return ! isNaN( value ) && value >= 0 && value <= 6; |
| 228 |
} ).sort( function ( first_value, second_value ) { |
| 229 |
return first_value - second_value; |
| 230 |
} ); |
| 231 |
|
| 232 |
transport.value = 7 === selected.length || 0 === selected.length ? '-1' : selected.join( ',' ); |
| 233 |
} |
| 234 |
|
| 235 |
/** |
| 236 |
* Apply a canonical weekday preset to the visible buttons and transport. |
| 237 |
* |
| 238 |
* The `-1` unrestricted marker is presented as all seven weekdays selected |
| 239 |
* so the allowed-start-days state remains explicit to sighted users. |
| 240 |
* |
| 241 |
* @param {string} mode Range mode owning the controls. |
| 242 |
* @param {string} raw_weekdays Canonical comma list or `-1` for any day. |
| 243 |
* @return {void} |
| 244 |
*/ |
| 245 |
function apply_weekday_preset( mode, raw_weekdays ) { |
| 246 |
var group = editor ? editor.node.querySelector( '[data-wpbc-date-selection-weekdays="' + mode + '"]' ) : null; |
| 247 |
var weekdays = '-1' === raw_weekdays ? [ 0, 1, 2, 3, 4, 5, 6 ] : String( raw_weekdays || '' ).split( ',' ).map( function ( raw_weekday ) { |
| 248 |
return parseInt( raw_weekday, 10 ); |
| 249 |
} ).filter( function ( weekday ) { |
| 250 |
return ! isNaN( weekday ) && weekday >= 0 && weekday <= 6; |
| 251 |
} ); |
| 252 |
|
| 253 |
if ( ! group ) { |
| 254 |
return; |
| 255 |
} |
| 256 |
|
| 257 |
group.querySelectorAll( '[data-weekday]' ).forEach( function ( button ) { |
| 258 |
var is_selected = -1 !== weekdays.indexOf( parseInt( button.dataset.weekday, 10 ) ); |
| 259 |
|
| 260 |
button.setAttribute( 'aria-pressed', is_selected ? 'true' : 'false' ); |
| 261 |
button.classList.toggle( 'is-selected', is_selected ); |
| 262 |
} ); |
| 263 |
sync_weekday_transport( mode ); |
| 264 |
} |
| 265 |
|
| 266 |
/** |
| 267 |
* Synchronize visible switches into their allow-listed hidden fields. |
| 268 |
* |
| 269 |
* @return {void} |
| 270 |
*/ |
| 271 |
function sync_toggle_transports() { |
| 272 |
if ( ! editor ) { |
| 273 |
return; |
| 274 |
} |
| 275 |
|
| 276 |
editor.node.querySelectorAll( '[data-wpbc-date-selection-toggle]' ).forEach( function ( toggle ) { |
| 277 |
var field_name = toggle.dataset.wpbcDateSelectionToggle; |
| 278 |
var transport = editor.node.querySelector( '[name="' + field_name + '"]' ); |
| 279 |
var policy_state = String( toggle.dataset.wpbcDateSelectionPolicyState || '' ); |
| 280 |
|
| 281 |
if ( transport ) { |
| 282 |
if ( 'required_on' === policy_state || 'hidden_on' === policy_state ) { |
| 283 |
transport.value = 'On'; |
| 284 |
} else if ( 'hidden_off' === policy_state ) { |
| 285 |
transport.value = 'Off'; |
| 286 |
} else { |
| 287 |
transport.value = toggle.checked && ! toggle.disabled ? 'On' : 'Off'; |
| 288 |
} |
| 289 |
} |
| 290 |
} ); |
| 291 |
} |
| 292 |
|
| 293 |
/** |
| 294 |
* Explain configuration-driven disabled toggle states. |
| 295 |
* |
| 296 |
* Native disabled inputs do not receive pointer or keyboard focus. The |
| 297 |
* surrounding toggle remains hoverable and becomes keyboard focusable only |
| 298 |
* while a documented dependency or edition rule disables its input. |
| 299 |
* |
| 300 |
* @return {void} |
| 301 |
*/ |
| 302 |
function sync_disabled_toggle_tooltips() { |
| 303 |
if ( ! editor ) { |
| 304 |
return; |
| 305 |
} |
| 306 |
|
| 307 |
editor.node.querySelectorAll( '[data-wpbc-date-selection-disabled-tooltip]' ).forEach( function ( tooltip_wrapper ) { |
| 308 |
var toggle = tooltip_wrapper.querySelector( '[data-wpbc-date-selection-toggle]' ); |
| 309 |
var description_id = String( tooltip_wrapper.dataset.wpbcDateSelectionDisabledTooltip || '' ); |
| 310 |
var description = description_id ? document.getElementById( description_id ) : null; |
| 311 |
var disabled_reason = toggle && toggle.disabled ? String( toggle.dataset.wpbcDateSelectionDisabledReason || '' ) : ''; |
| 312 |
|
| 313 |
if ( disabled_reason ) { |
| 314 |
tooltip_wrapper.setAttribute( 'title', disabled_reason ); |
| 315 |
tooltip_wrapper.setAttribute( 'tabindex', '0' ); |
| 316 |
tooltip_wrapper.setAttribute( 'aria-describedby', description_id ); |
| 317 |
tooltip_wrapper.dataset.wpbcDateSelectionDisabledTooltipActive = '1'; |
| 318 |
} else { |
| 319 |
tooltip_wrapper.removeAttribute( 'title' ); |
| 320 |
tooltip_wrapper.removeAttribute( 'tabindex' ); |
| 321 |
tooltip_wrapper.removeAttribute( 'aria-describedby' ); |
| 322 |
delete tooltip_wrapper.dataset.wpbcDateSelectionDisabledTooltipActive; |
| 323 |
} |
| 324 |
|
| 325 |
if ( description ) { |
| 326 |
description.textContent = disabled_reason; |
| 327 |
} |
| 328 |
} ); |
| 329 |
} |
| 330 |
|
| 331 |
/** |
| 332 |
* Enforce canonical changeover, recurrent-time, and check-out dependencies. |
| 333 |
* |
| 334 |
* Booking Calendar treats changeover processing as mutually exclusive with |
| 335 |
* recurrent time and an independently available check-out date. A check-out |
| 336 |
* date also has no meaning for single or independent-date selections. Hide |
| 337 |
* incompatible controls and normalize their request-local draft state. |
| 338 |
* |
| 339 |
* @return {void} |
| 340 |
*/ |
| 341 |
function sync_changeover_dependencies() { |
| 342 |
var changeover_toggle; |
| 343 |
var changeover_controls; |
| 344 |
var changeover_fields; |
| 345 |
var checkout_option; |
| 346 |
var checkout_toggle; |
| 347 |
var checkout_tooltip_wrapper; |
| 348 |
var checkout_disabled_reason; |
| 349 |
var recurrent_toggle; |
| 350 |
var recurrent_option; |
| 351 |
var behavior_section; |
| 352 |
var changeover_is_active; |
| 353 |
var checkout_is_supported; |
| 354 |
var changeover_policy; |
| 355 |
var recurrent_policy; |
| 356 |
var checkout_policy; |
| 357 |
|
| 358 |
if ( ! editor || ! editor.changeover ) { |
| 359 |
return; |
| 360 |
} |
| 361 |
|
| 362 |
changeover_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_changeover_enabled"]' ); |
| 363 |
changeover_controls = editor.changeover.querySelector( '[data-wpbc-date-selection-changeover-controls]' ); |
| 364 |
changeover_fields = editor.changeover.querySelector( '[data-wpbc-date-selection-changeover-fields]' ); |
| 365 |
checkout_option = editor.node.querySelector( '[data-wpbc-date-selection-checkout-option]' ); |
| 366 |
checkout_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_checkout_available"]' ); |
| 367 |
checkout_tooltip_wrapper = checkout_toggle ? checkout_toggle.closest( '[data-wpbc-date-selection-disabled-tooltip]' ) : null; |
| 368 |
recurrent_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_recurrent_time"]' ); |
| 369 |
recurrent_option = editor.node.querySelector( '[data-wpbc-date-selection-recurrent-option]' ); |
| 370 |
behavior_section = editor.node.querySelector( '[data-wpbc-date-selection-behavior]' ); |
| 371 |
changeover_policy = get_mode_policy_state( 'wpbcDateSelectionPolicyChangeover' ); |
| 372 |
recurrent_policy = get_mode_policy_state( 'wpbcDateSelectionPolicyRecurrentTime' ); |
| 373 |
checkout_policy = get_mode_policy_state( 'wpbcDateSelectionPolicyCheckoutAvailable' ); |
| 374 |
checkout_is_supported = 'optional' === checkout_policy; |
| 375 |
|
| 376 |
if ( changeover_toggle ) { |
| 377 |
changeover_toggle.dataset.wpbcDateSelectionPolicyState = changeover_policy; |
| 378 |
if ( 'required_on' === changeover_policy ) { |
| 379 |
changeover_toggle.checked = true; |
| 380 |
changeover_toggle.disabled = true; |
| 381 |
} else if ( 'hidden_off' === changeover_policy ) { |
| 382 |
changeover_toggle.checked = false; |
| 383 |
} else { |
| 384 |
changeover_toggle.disabled = '1' === changeover_toggle.dataset.wpbcDateSelectionEditionLocked; |
| 385 |
} |
| 386 |
} |
| 387 |
changeover_is_active = Boolean( changeover_toggle && changeover_toggle.checked && 'hidden_off' !== changeover_policy ); |
| 388 |
|
| 389 |
editor.changeover.hidden = -1 === [ 'optional', 'required_on' ].indexOf( changeover_policy ); |
| 390 |
|
| 391 |
if ( changeover_controls ) { |
| 392 |
changeover_controls.hidden = ! changeover_is_active; |
| 393 |
} |
| 394 |
if ( changeover_fields ) { |
| 395 |
changeover_fields.hidden = ! changeover_is_active; |
| 396 |
} |
| 397 |
if ( recurrent_option ) { |
| 398 |
recurrent_option.hidden = -1 === [ 'optional', 'optional_default_on', 'required_on' ].indexOf( recurrent_policy ); |
| 399 |
} |
| 400 |
if ( recurrent_toggle ) { |
| 401 |
recurrent_toggle.dataset.wpbcDateSelectionPolicyState = recurrent_policy; |
| 402 |
recurrent_toggle.disabled = 'required_on' === recurrent_policy; |
| 403 |
if ( 'hidden_on' === recurrent_policy || 'required_on' === recurrent_policy ) { |
| 404 |
recurrent_toggle.checked = true; |
| 405 |
} else if ( -1 === [ 'optional', 'optional_default_on' ].indexOf( recurrent_policy ) || changeover_is_active ) { |
| 406 |
recurrent_toggle.checked = false; |
| 407 |
} |
| 408 |
} |
| 409 |
if ( checkout_option ) { |
| 410 |
checkout_option.hidden = ! checkout_is_supported; |
| 411 |
checkout_option.dataset.wpbcDateSelectionPolicyState = checkout_policy; |
| 412 |
} |
| 413 |
if ( checkout_toggle ) { |
| 414 |
checkout_toggle.dataset.wpbcDateSelectionPolicyState = checkout_policy; |
| 415 |
checkout_toggle.disabled = '1' === checkout_toggle.dataset.wpbcDateSelectionEditionLocked || changeover_is_active || ! checkout_is_supported; |
| 416 |
checkout_disabled_reason = ''; |
| 417 |
if ( checkout_tooltip_wrapper && '1' === checkout_toggle.dataset.wpbcDateSelectionEditionLocked ) { |
| 418 |
checkout_disabled_reason = checkout_tooltip_wrapper.dataset.wpbcDateSelectionCheckoutReasonEdition || ''; |
| 419 |
} else if ( checkout_tooltip_wrapper && changeover_is_active ) { |
| 420 |
checkout_disabled_reason = checkout_tooltip_wrapper.dataset.wpbcDateSelectionCheckoutReasonChangeover || ''; |
| 421 |
} else if ( checkout_tooltip_wrapper && ! checkout_is_supported ) { |
| 422 |
checkout_disabled_reason = checkout_tooltip_wrapper.dataset.wpbcDateSelectionCheckoutReasonMode || ''; |
| 423 |
} |
| 424 |
checkout_toggle.dataset.wpbcDateSelectionDisabledReason = checkout_disabled_reason; |
| 425 |
if ( changeover_is_active || ! checkout_is_supported ) { |
| 426 |
checkout_toggle.checked = false; |
| 427 |
} |
| 428 |
} |
| 429 |
if ( behavior_section ) { |
| 430 |
behavior_section.hidden = -1 === [ 'optional', 'optional_default_on', 'required_on' ].indexOf( recurrent_policy ) && ! checkout_is_supported; |
| 431 |
} |
| 432 |
} |
| 433 |
|
| 434 |
/** |
| 435 |
* Let the most recently activated mutually exclusive option win. |
| 436 |
* |
| 437 |
* @param {HTMLElement|null} toggle Changed Date Selection toggle. |
| 438 |
* @return {void} |
| 439 |
*/ |
| 440 |
function apply_exclusive_toggle_action( toggle ) { |
| 441 |
var field_name; |
| 442 |
var conflicting_toggle; |
| 443 |
|
| 444 |
if ( ! editor || ! toggle || ! toggle.checked ) { |
| 445 |
return; |
| 446 |
} |
| 447 |
|
| 448 |
field_name = toggle.dataset.wpbcDateSelectionToggle || ''; |
| 449 |
if ( 'date_selection_changeover_enabled' === field_name ) { |
| 450 |
conflicting_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_recurrent_time"]' ); |
| 451 |
} else if ( 'date_selection_recurrent_time' === field_name ) { |
| 452 |
conflicting_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_changeover_enabled"]' ); |
| 453 |
} |
| 454 |
|
| 455 |
if ( conflicting_toggle ) { |
| 456 |
conflicting_toggle.checked = false; |
| 457 |
} |
| 458 |
} |
| 459 |
|
| 460 |
/** |
| 461 |
* Synchronize the non-mutating calendar-legend configuration preview. |
| 462 |
* |
| 463 |
* All preview nodes are rendered by PHP. This method changes only text, |
| 464 |
* visibility, and layout classes; it never constructs or evaluates markup. |
| 465 |
* |
| 466 |
* @return {void} |
| 467 |
*/ |
| 468 |
function sync_legend_preview() { |
| 469 |
var legend_toggle; |
| 470 |
var legend_settings; |
| 471 |
var legend_preview; |
| 472 |
var show_numbers_toggle; |
| 473 |
var vertical_toggle; |
| 474 |
var day_number; |
| 475 |
|
| 476 |
if ( ! editor || ! editor.legend ) { |
| 477 |
return; |
| 478 |
} |
| 479 |
|
| 480 |
legend_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_legend_enabled"]' ); |
| 481 |
legend_settings = editor.legend.querySelector( '[data-wpbc-date-selection-legend-settings]' ); |
| 482 |
legend_preview = editor.node.querySelector( '[data-wpbc-date-selection-legend-preview]' ); |
| 483 |
show_numbers_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_legend_show_numbers"]' ); |
| 484 |
vertical_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_legend_vertical"]' ); |
| 485 |
day_number = legend_preview ? String( legend_preview.dataset.wpbcDateSelectionLegendDayNumber || '' ) : ''; |
| 486 |
|
| 487 |
if ( legend_settings ) { |
| 488 |
legend_settings.hidden = ! legend_toggle || ! legend_toggle.checked; |
| 489 |
} |
| 490 |
if ( ! legend_preview ) { |
| 491 |
return; |
| 492 |
} |
| 493 |
|
| 494 |
legend_preview.hidden = ! legend_toggle || ! legend_toggle.checked; |
| 495 |
legend_preview.classList.toggle( 'is-vertical', Boolean( vertical_toggle && vertical_toggle.checked ) ); |
| 496 |
legend_preview.querySelectorAll( '[data-wpbc-date-selection-legend-preview-item]' ).forEach( function ( preview_item ) { |
| 497 |
var legend_id = preview_item.dataset.wpbcDateSelectionLegendPreviewItem; |
| 498 |
var item_toggle = editor.node.querySelector( '[data-wpbc-date-selection-toggle="date_selection_legend_item_' + legend_id + '"]' ); |
| 499 |
var title_control = editor.node.querySelector( '[data-wpbc-date-selection-legend-title="' + legend_id + '"]' ); |
| 500 |
var title_node = preview_item.querySelector( '.wpdev_hint_with_text .block_text:last-child' ); |
| 501 |
var title = title_control ? String( title_control.value || '' ).trim() : ''; |
| 502 |
|
| 503 |
preview_item.hidden = ! item_toggle || ! item_toggle.checked; |
| 504 |
if ( title_node && title_control ) { |
| 505 |
title_node.textContent = title || title_control.placeholder || ''; |
| 506 |
} |
| 507 |
preview_item.querySelectorAll( '.wpbc_calendar_legend_day_cell_height .date-cell-content > a, .wpbc_calendar_legend_day_cell_height .date-cell-content > span' ).forEach( function ( date_node ) { |
| 508 |
date_node.textContent = show_numbers_toggle && show_numbers_toggle.checked ? day_number : ''; |
| 509 |
} ); |
| 510 |
} ); |
| 511 |
} |
| 512 |
|
| 513 |
/** |
| 514 |
* Synchronize canonical changeover classes in the calendar and legend. |
| 515 |
* |
| 516 |
* Booking Calendar owns the underlying day status and SVG markup. This |
| 517 |
* adapter changes only the same ancestor and day-state classes used by the |
| 518 |
* released calendar renderer, so triangle and split-day presentation stay |
| 519 |
* consistent without constructing or evaluating HTML. |
| 520 |
* |
| 521 |
* @return {void} |
| 522 |
*/ |
| 523 |
function sync_changeover_preview_classes() { |
| 524 |
var changeover_transport; |
| 525 |
var triangles_transport; |
| 526 |
var changeover_is_active; |
| 527 |
var triangles_are_active; |
| 528 |
var partially_item; |
| 529 |
var partially_day; |
| 530 |
var legend_table; |
| 531 |
var legend_wrapper; |
| 532 |
var changeover_classes; |
| 533 |
|
| 534 |
if ( ! editor ) { |
| 535 |
return; |
| 536 |
} |
| 537 |
|
| 538 |
changeover_transport = editor.node.querySelector( '[name="date_selection_changeover_enabled"]' ); |
| 539 |
triangles_transport = editor.node.querySelector( '[name="date_selection_triangles"]' ); |
| 540 |
changeover_is_active = Boolean( changeover_transport && 'On' === changeover_transport.value ); |
| 541 |
triangles_are_active = Boolean( changeover_is_active && triangles_transport && 'On' === triangles_transport.value ); |
| 542 |
|
| 543 |
if ( editor.calendar ) { |
| 544 |
editor.calendar.classList.toggle( 'wpbc_change_over_triangle', triangles_are_active ); |
| 545 |
editor.calendar.querySelectorAll( '.wpbc_calendar_wraper' ).forEach( function ( calendar_wrapper ) { |
| 546 |
calendar_wrapper.classList.toggle( 'wpbc_change_over_triangle', triangles_are_active ); |
| 547 |
} ); |
| 548 |
} |
| 549 |
|
| 550 |
partially_item = editor.node.querySelector( '[data-wpbc-date-selection-legend-preview-item="partially"]' ); |
| 551 |
if ( ! partially_item ) { |
| 552 |
return; |
| 553 |
} |
| 554 |
|
| 555 |
partially_day = partially_item.querySelector( '.wpbc_calendar_legend_day_cell_height' ); |
| 556 |
if ( partially_day ) { |
| 557 |
changeover_classes = [ |
| 558 |
'date_approved', |
| 559 |
'check_in_time', |
| 560 |
'check_out_time', |
| 561 |
'check_in_time_date_approved', |
| 562 |
'check_out_time_date2approve' |
| 563 |
]; |
| 564 |
changeover_classes.forEach( function ( class_name ) { |
| 565 |
partially_day.classList.toggle( class_name, changeover_is_active ); |
| 566 |
} ); |
| 567 |
partially_day.classList.toggle( 'date2approve', ! changeover_is_active ); |
| 568 |
partially_day.classList.toggle( 'times_clock', ! changeover_is_active ); |
| 569 |
} |
| 570 |
|
| 571 |
legend_table = partially_item.querySelector( '.wpbc_calendar_legend_table_width_height' ); |
| 572 |
legend_wrapper = legend_table ? legend_table.parentElement : null; |
| 573 |
if ( legend_wrapper ) { |
| 574 |
legend_wrapper.classList.toggle( 'wpbc_change_over_triangle', triangles_are_active ); |
| 575 |
} |
| 576 |
} |
| 577 |
|
| 578 |
/** |
| 579 |
* Reload canonical day-status data with authorized draft overrides. |
| 580 |
* |
| 581 |
* The shared calendar endpoint calculates booking boundaries. A dedicated |
| 582 |
* Setup Wizard nonce and capability check authorize these request-local |
| 583 |
* values; no setting or booking is written by this request. |
| 584 |
* |
| 585 |
* @return {void} |
| 586 |
*/ |
| 587 |
function refresh_changeover_preview_data() { |
| 588 |
var changeover_transport; |
| 589 |
var triangles_transport; |
| 590 |
var recurrent_transport; |
| 591 |
var checkout_transport; |
| 592 |
|
| 593 |
if ( |
| 594 |
! editor |
| 595 |
|| ! editor.calendar |
| 596 |
|| ! config.preview_nonce |
| 597 |
|| 'function' !== typeof window.wpbc_calendar__load_data__ajx |
| 598 |
) { |
| 599 |
return; |
| 600 |
} |
| 601 |
|
| 602 |
changeover_transport = editor.node.querySelector( '[name="date_selection_changeover_enabled"]' ); |
| 603 |
triangles_transport = editor.node.querySelector( '[name="date_selection_triangles"]' ); |
| 604 |
recurrent_transport = editor.node.querySelector( '[name="date_selection_recurrent_time"]' ); |
| 605 |
checkout_transport = editor.node.querySelector( '[name="date_selection_checkout_available"]' ); |
| 606 |
|
| 607 |
window.wpbc_calendar__load_data__ajx( { |
| 608 |
resource_id: editor.resource_id, |
| 609 |
booking_hash: '', |
| 610 |
request_uri: '', |
| 611 |
custom_form: 'standard', |
| 612 |
aggregate_resource_id_str: '', |
| 613 |
aggregate_type: 'all', |
| 614 |
skip_general_availability: 0, |
| 615 |
classic_booking_context_token: '', |
| 616 |
wpbc_settings_calendar_preview: 1, |
| 617 |
wpbc_setup_wizard_date_selection_preview: 1, |
| 618 |
wpbc_setup_wizard_date_selection_preview_nonce: String( config.preview_nonce ), |
| 619 |
wpbc_settings_calendar_preview_changeover: changeover_transport && 'On' === changeover_transport.value ? 'On' : 'Off', |
| 620 |
wpbc_settings_calendar_preview_triangles: triangles_transport && 'On' === triangles_transport.value ? 'On' : 'Off', |
| 621 |
wpbc_settings_calendar_preview_recurrent_time: recurrent_transport && 'On' === recurrent_transport.value ? 'On' : 'Off', |
| 622 |
wpbc_settings_calendar_preview_last_checkout: checkout_transport && 'On' === checkout_transport.value ? 'On' : 'Off', |
| 623 |
wpbc_settings_calendar_preview_show_legend: 'Off' |
| 624 |
} ); |
| 625 |
} |
| 626 |
|
| 627 |
/** |
| 628 |
* Debounce request-local day-status refreshes after related toggles change. |
| 629 |
* |
| 630 |
* @return {void} |
| 631 |
*/ |
| 632 |
function schedule_changeover_data_refresh() { |
| 633 |
if ( preview_data_timer ) { |
| 634 |
window.clearTimeout( preview_data_timer ); |
| 635 |
} |
| 636 |
preview_data_timer = window.setTimeout( refresh_changeover_preview_data, 180 ); |
| 637 |
} |
| 638 |
|
| 639 |
/** |
| 640 |
* Return the authoritative number field paired with one presentation slider. |
| 641 |
* |
| 642 |
* @param {string} field_name Date Selection draft field name. |
| 643 |
* @return {HTMLInputElement|null} Matching number field, or null. |
| 644 |
*/ |
| 645 |
function get_number_control( field_name ) { |
| 646 |
return editor ? editor.node.querySelector( '[data-wpbc-date-selection-number="' + field_name + '"]' ) : null; |
| 647 |
} |
| 648 |
|
| 649 |
/** |
| 650 |
* Copy one valid authoritative number value into its presentation slider. |
| 651 |
* |
| 652 |
* Invalid exact values remain visible for native and wizard validation; the |
| 653 |
* slider must not silently coerce them into an apparently valid draft. |
| 654 |
* |
| 655 |
* @param {HTMLInputElement} number_control Authoritative number field. |
| 656 |
* @return {void} |
| 657 |
*/ |
| 658 |
function sync_range_from_number( number_control ) { |
| 659 |
var field_name = number_control ? number_control.dataset.wpbcDateSelectionNumber : ''; |
| 660 |
var range_control = field_name && editor ? editor.node.querySelector( '[data-wpbc-date-selection-range="' + field_name + '"]' ) : null; |
| 661 |
|
| 662 |
if ( ! range_control || '' === number_control.value || ! number_control.checkValidity() ) { |
| 663 |
return; |
| 664 |
} |
| 665 |
|
| 666 |
range_control.value = number_control.value; |
| 667 |
} |
| 668 |
|
| 669 |
/** |
| 670 |
* Copy one presentation slider value into its authoritative number field. |
| 671 |
* |
| 672 |
* @param {HTMLInputElement} range_control Presentation range control. |
| 673 |
* @return {void} |
| 674 |
*/ |
| 675 |
function sync_number_from_range( range_control ) { |
| 676 |
var field_name = range_control ? range_control.dataset.wpbcDateSelectionRange : ''; |
| 677 |
var number_control = get_number_control( field_name ); |
| 678 |
|
| 679 |
if ( ! number_control || number_control.disabled || range_control.disabled ) { |
| 680 |
return; |
| 681 |
} |
| 682 |
|
| 683 |
number_control.value = range_control.value; |
| 684 |
} |
| 685 |
|
| 686 |
/** |
| 687 |
* Align every presentation slider with its authoritative number field. |
| 688 |
* |
| 689 |
* @return {void} |
| 690 |
*/ |
| 691 |
function sync_number_ranges() { |
| 692 |
if ( ! editor ) { |
| 693 |
return; |
| 694 |
} |
| 695 |
|
| 696 |
editor.node.querySelectorAll( '[data-wpbc-date-selection-number]' ).forEach( sync_range_from_number ); |
| 697 |
} |
| 698 |
|
| 699 |
/** |
| 700 |
* Apply one trusted preset value through the authoritative number control. |
| 701 |
* |
| 702 |
* @param {string} field_name Draft field name. |
| 703 |
* @param {string} raw_value Server-defined integer value. |
| 704 |
* @return {void} |
| 705 |
*/ |
| 706 |
function apply_number_preset( field_name, raw_value ) { |
| 707 |
var number_control = get_number_control( field_name ); |
| 708 |
|
| 709 |
if ( ! number_control || number_control.disabled || ! /^\d+$/.test( String( raw_value ) ) ) { |
| 710 |
return; |
| 711 |
} |
| 712 |
|
| 713 |
number_control.value = String( raw_value ); |
| 714 |
sync_range_from_number( number_control ); |
| 715 |
} |
| 716 |
|
| 717 |
/** |
| 718 |
* Apply a server-defined specific-length preset as one coherent draft. |
| 719 |
* |
| 720 |
* Clear intentionally omits the range and weekday data attributes, so it |
| 721 |
* removes only the specific-length restriction and preserves manual rules. |
| 722 |
* |
| 723 |
* @param {HTMLElement} preset_button Activated preset button. |
| 724 |
* @param {HTMLInputElement} text_control Specific-length text field. |
| 725 |
* @return {void} |
| 726 |
*/ |
| 727 |
function apply_specific_day_preset( preset_button, text_control ) { |
| 728 |
text_control.value = preset_button.dataset.wpbcDateSelectionSpecificPreset || ''; |
| 729 |
|
| 730 |
if ( preset_button.hasAttribute( 'data-wpbc-date-selection-preset-minimum' ) ) { |
| 731 |
apply_number_preset( 'date_selection_dynamic_min', preset_button.dataset.wpbcDateSelectionPresetMinimum ); |
| 732 |
} |
| 733 |
if ( preset_button.hasAttribute( 'data-wpbc-date-selection-preset-maximum' ) ) { |
| 734 |
apply_number_preset( 'date_selection_dynamic_max', preset_button.dataset.wpbcDateSelectionPresetMaximum ); |
| 735 |
} |
| 736 |
if ( preset_button.hasAttribute( 'data-wpbc-date-selection-preset-weekdays' ) ) { |
| 737 |
apply_weekday_preset( 'dynamic', preset_button.dataset.wpbcDateSelectionPresetWeekdays ); |
| 738 |
} |
| 739 |
|
| 740 |
shell_api.clear_field_error( 'date_selection_dynamic_min' ); |
| 741 |
shell_api.clear_field_error( 'date_selection_dynamic_max' ); |
| 742 |
shell_api.clear_field_error( 'date_selection_dynamic_specific' ); |
| 743 |
shell_api.clear_field_error( 'date_selection_dynamic_weekdays' ); |
| 744 |
} |
| 745 |
|
| 746 |
/** |
| 747 |
* Update selected cards, settings panels, and changeover visibility. |
| 748 |
* |
| 749 |
* @return {void} |
| 750 |
*/ |
| 751 |
function render_editor_state() { |
| 752 |
var mode = get_selected_mode(); |
| 753 |
|
| 754 |
if ( ! editor ) { |
| 755 |
return; |
| 756 |
} |
| 757 |
|
| 758 |
editor.node.querySelectorAll( '.wpbc_setup_wizard__date-selection-mode-card' ).forEach( function ( card ) { |
| 759 |
var radio = card.querySelector( '[data-wpbc-date-selection-mode]' ); |
| 760 |
card.classList.toggle( 'is-selected', Boolean( radio && radio.checked ) ); |
| 761 |
} ); |
| 762 |
editor.node.querySelectorAll( '[data-wpbc-date-selection-panel]' ).forEach( function ( panel ) { |
| 763 |
panel.hidden = panel.dataset.wpbcDateSelectionPanel !== mode; |
| 764 |
} ); |
| 765 |
|
| 766 |
sync_changeover_dependencies(); |
| 767 |
} |
| 768 |
|
| 769 |
/** |
| 770 |
* Set one canonical request-local calendar parameter. |
| 771 |
* |
| 772 |
* @param {string} key Calendar parameter name. |
| 773 |
* @param {*} value Parameter value. |
| 774 |
* @return {void} |
| 775 |
*/ |
| 776 |
function set_calendar_parameter( key, value ) { |
| 777 |
if ( editor && window._wpbc && 'function' === typeof window._wpbc.calendar__set_param_value ) { |
| 778 |
window._wpbc.calendar__set_param_value( editor.resource_id, key, value ); |
| 779 |
} |
| 780 |
} |
| 781 |
|
| 782 |
/** |
| 783 |
* Normalize the legacy calendar scroll target before a released mode helper runs. |
| 784 |
* |
| 785 |
* The core calendar accepts either false or a two-value month/year array. The |
| 786 |
* request-local preview can initially expose null, which the legacy helper |
| 787 |
* otherwise attempts to index while reinitializing the calendar. |
| 788 |
* |
| 789 |
* @return {void} |
| 790 |
*/ |
| 791 |
function normalize_calendar_scroll_target() { |
| 792 |
var scroll_target; |
| 793 |
|
| 794 |
if ( |
| 795 |
! editor |
| 796 |
|| ! window._wpbc |
| 797 |
|| 'function' !== typeof window._wpbc.calendar__get_param_value |
| 798 |
|| 'function' !== typeof window._wpbc.calendar__set_param_value |
| 799 |
) { |
| 800 |
return; |
| 801 |
} |
| 802 |
|
| 803 |
scroll_target = window._wpbc.calendar__get_param_value( editor.resource_id, 'calendar_scroll_to' ); |
| 804 |
if ( |
| 805 |
false !== scroll_target |
| 806 |
&& ( |
| 807 |
! Array.isArray( scroll_target ) |
| 808 |
|| scroll_target.length < 2 |
| 809 |
|| null === scroll_target[ 0 ] |
| 810 |
|| null === scroll_target[ 1 ] |
| 811 |
) |
| 812 |
) { |
| 813 |
set_calendar_parameter( 'calendar_scroll_to', false ); |
| 814 |
} |
| 815 |
} |
| 816 |
|
| 817 |
/** |
| 818 |
* Apply the current draft through the real Booking Calendar mode helpers. |
| 819 |
* |
| 820 |
* @return {boolean} True when the calendar runtime was ready. |
| 821 |
*/ |
| 822 |
function apply_preview_policy() { |
| 823 |
var mode; |
| 824 |
var changeover; |
| 825 |
var recurrent_time; |
| 826 |
var triangles; |
| 827 |
var calendar_node; |
| 828 |
var fixed_days; |
| 829 |
var fixed_weekdays; |
| 830 |
var dynamic_min; |
| 831 |
var dynamic_max; |
| 832 |
var dynamic_specific; |
| 833 |
var dynamic_weekdays; |
| 834 |
|
| 835 |
if ( ! editor ) { |
| 836 |
return false; |
| 837 |
} |
| 838 |
|
| 839 |
sync_changeover_preview_classes(); |
| 840 |
|
| 841 |
if ( ! window._wpbc || 'function' !== typeof window._wpbc.calendar__set_param_value ) { |
| 842 |
return false; |
| 843 |
} |
| 844 |
|
| 845 |
calendar_node = document.getElementById( 'calendar_booking' + editor.resource_id ); |
| 846 |
if ( ! calendar_node ) { |
| 847 |
return false; |
| 848 |
} |
| 849 |
|
| 850 |
mode = get_selected_mode(); |
| 851 |
changeover = editor.node.querySelector( '[name="date_selection_changeover_enabled"]' ); |
| 852 |
recurrent_time = editor.node.querySelector( '[name="date_selection_recurrent_time"]' ); |
| 853 |
triangles = editor.node.querySelector( '[name="date_selection_triangles"]' ); |
| 854 |
fixed_days = get_integer( '[name="date_selection_fixed_days"]', 3 ); |
| 855 |
fixed_weekdays = get_weekdays( 'fixed' ); |
| 856 |
dynamic_min = get_integer( '[name="date_selection_dynamic_min"]', 1 ); |
| 857 |
dynamic_max = get_integer( '[name="date_selection_dynamic_max"]', 1 ); |
| 858 |
dynamic_specific = get_specific_days(); |
| 859 |
dynamic_weekdays = get_weekdays( 'dynamic' ); |
| 860 |
dynamic_specific = null === dynamic_specific ? [] : dynamic_specific; |
| 861 |
|
| 862 |
set_calendar_parameter( 'is_enabled_change_over', Boolean( changeover && 'On' === changeover.value ) ); |
| 863 |
set_calendar_parameter( 'booking_recurrent_time', recurrent_time && 'On' === recurrent_time.value ? 'On' : 'Off' ); |
| 864 |
set_calendar_parameter( 'days_select_mode', mode ); |
| 865 |
set_calendar_parameter( 'fixed__days_num', fixed_days ); |
| 866 |
set_calendar_parameter( 'fixed__week_days__start', fixed_weekdays ); |
| 867 |
set_calendar_parameter( 'dynamic__days_min', dynamic_min ); |
| 868 |
set_calendar_parameter( 'dynamic__days_max', dynamic_max ); |
| 869 |
set_calendar_parameter( 'dynamic__days_specific', dynamic_specific ); |
| 870 |
set_calendar_parameter( 'dynamic__week_days__start', dynamic_weekdays ); |
| 871 |
normalize_calendar_scroll_target(); |
| 872 |
if ( 'function' === typeof window._wpbc.set_other_param ) { |
| 873 |
window._wpbc.set_other_param( 'is_enabled_change_over', Boolean( changeover && 'On' === changeover.value ) ); |
| 874 |
} |
| 875 |
if ( 'function' === typeof window.wpbc__conditions__SAVE_INITIAL__days_selection_params__bm ) { |
| 876 |
window.wpbc__conditions__SAVE_INITIAL__days_selection_params__bm( editor.resource_id ); |
| 877 |
} |
| 878 |
|
| 879 |
if ( 'single' === mode && 'function' === typeof window.wpbc_cal_days_select__single ) { |
| 880 |
window.wpbc_cal_days_select__single( editor.resource_id ); |
| 881 |
return true; |
| 882 |
} |
| 883 |
if ( 'multiple' === mode && 'function' === typeof window.wpbc_cal_days_select__multiple ) { |
| 884 |
window.wpbc_cal_days_select__multiple( editor.resource_id ); |
| 885 |
return true; |
| 886 |
} |
| 887 |
if ( 'fixed' === mode && 'function' === typeof window.wpbc_cal_days_select__fixed ) { |
| 888 |
window.wpbc_cal_days_select__fixed( |
| 889 |
editor.resource_id, |
| 890 |
fixed_days, |
| 891 |
fixed_weekdays |
| 892 |
); |
| 893 |
return true; |
| 894 |
} |
| 895 |
if ( 'dynamic' === mode && 'function' === typeof window.wpbc_cal_days_select__range ) { |
| 896 |
window.wpbc_cal_days_select__range( |
| 897 |
editor.resource_id, |
| 898 |
dynamic_min, |
| 899 |
dynamic_max, |
| 900 |
dynamic_specific, |
| 901 |
dynamic_weekdays |
| 902 |
); |
| 903 |
return true; |
| 904 |
} |
| 905 |
|
| 906 |
return false; |
| 907 |
} |
| 908 |
|
| 909 |
/** |
| 910 |
* Reapply policy after immediate interaction and delayed calendar bootstrap. |
| 911 |
* |
| 912 |
* @return {void} |
| 913 |
*/ |
| 914 |
function schedule_preview_update() { |
| 915 |
apply_timers.forEach( function ( timer_id ) { |
| 916 |
window.clearTimeout( timer_id ); |
| 917 |
} ); |
| 918 |
apply_timers = [ 0, 120, 420, 900, 1400 ].map( function ( delay ) { |
| 919 |
return window.setTimeout( apply_preview_policy, delay ); |
| 920 |
} ); |
| 921 |
} |
| 922 |
|
| 923 |
/** |
| 924 |
* Synchronize all presentation controls before draft collection. |
| 925 |
* |
| 926 |
* @return {void} |
| 927 |
*/ |
| 928 |
function sync() { |
| 929 |
sync_weekday_transport( 'fixed' ); |
| 930 |
sync_weekday_transport( 'dynamic' ); |
| 931 |
sync_changeover_dependencies(); |
| 932 |
sync_disabled_toggle_tooltips(); |
| 933 |
sync_toggle_transports(); |
| 934 |
sync_legend_preview(); |
| 935 |
sync_changeover_preview_classes(); |
| 936 |
sync_number_ranges(); |
| 937 |
} |
| 938 |
|
| 939 |
/** |
| 940 |
* Validate coupled date-selection values before navigation. |
| 941 |
* |
| 942 |
* Server-side field allow-lists remain authoritative. This prevents an |
| 943 |
* internally inconsistent flexible range from being sent as a draft. |
| 944 |
* |
| 945 |
* @return {HTMLElement|null} First invalid control, or null. |
| 946 |
*/ |
| 947 |
function validate() { |
| 948 |
var mode = get_selected_mode(); |
| 949 |
var fixed_days = get_integer( '[name="date_selection_fixed_days"]', 0 ); |
| 950 |
var dynamic_min = get_integer( '[name="date_selection_dynamic_min"]', 0 ); |
| 951 |
var dynamic_max = get_integer( '[name="date_selection_dynamic_max"]', 0 ); |
| 952 |
var dynamic_specific = get_specific_days(); |
| 953 |
var fixed_control = editor.node.querySelector( '[name="date_selection_fixed_days"]' ); |
| 954 |
var dynamic_min_control = editor.node.querySelector( '[name="date_selection_dynamic_min"]' ); |
| 955 |
var dynamic_max_control = editor.node.querySelector( '[name="date_selection_dynamic_max"]' ); |
| 956 |
var dynamic_specific_control = editor.node.querySelector( '[data-wpbc-date-selection-specific]' ); |
| 957 |
|
| 958 |
if ( ! mode ) { |
| 959 |
shell_api.set_field_error( 'date_selection_mode', get_message( 'mode_required', 'Choose how customers select dates.' ) ); |
| 960 |
return editor.node.querySelector( '[data-wpbc-date-selection-mode]' ); |
| 961 |
} |
| 962 |
|
| 963 |
if ( fixed_days < 1 || fixed_days > parseInt( config.max_fixed_days || 180, 10 ) ) { |
| 964 |
shell_api.set_field_error( 'date_selection_fixed_days', get_message( 'fixed_days_invalid', 'Enter a valid fixed range.' ) ); |
| 965 |
return fixed_control; |
| 966 |
} |
| 967 |
shell_api.clear_field_error( 'date_selection_fixed_days' ); |
| 968 |
|
| 969 |
if ( dynamic_min < 1 || dynamic_max < dynamic_min || dynamic_max > parseInt( config.max_dynamic_days || 1095, 10 ) ) { |
| 970 |
shell_api.set_field_error( 'date_selection_dynamic_min', get_message( 'dynamic_days_invalid', 'Enter a valid flexible range.' ) ); |
| 971 |
shell_api.set_field_error( 'date_selection_dynamic_max', get_message( 'dynamic_days_invalid', 'Enter a valid flexible range.' ) ); |
| 972 |
return dynamic_min < 1 ? dynamic_min_control : dynamic_max_control; |
| 973 |
} |
| 974 |
shell_api.clear_field_error( 'date_selection_dynamic_min' ); |
| 975 |
shell_api.clear_field_error( 'date_selection_dynamic_max' ); |
| 976 |
|
| 977 |
if ( |
| 978 |
null === dynamic_specific |
| 979 |
|| dynamic_specific.some( function ( specific_day ) { |
| 980 |
return specific_day < dynamic_min || specific_day > dynamic_max; |
| 981 |
} ) |
| 982 |
) { |
| 983 |
shell_api.set_field_error( 'date_selection_dynamic_specific', get_message( 'specific_days_invalid', 'Enter comma-separated day lengths within the flexible range, for example 7,14,21,28.' ) ); |
| 984 |
return dynamic_specific_control; |
| 985 |
} |
| 986 |
shell_api.clear_field_error( 'date_selection_dynamic_specific' ); |
| 987 |
|
| 988 |
return null; |
| 989 |
} |
| 990 |
|
| 991 |
/** |
| 992 |
* Handle editor controls without constructing dynamic HTML. |
| 993 |
* |
| 994 |
* @param {Event} event Browser input or click event. |
| 995 |
* @return {void} |
| 996 |
*/ |
| 997 |
function handle_interaction( event ) { |
| 998 |
var weekday_button = event.target.closest( '[data-wpbc-date-selection-weekdays] [data-weekday]' ); |
| 999 |
var number_control = event.target.closest( '[data-wpbc-date-selection-number]' ); |
| 1000 |
var range_control = event.target.closest( '[data-wpbc-date-selection-range]' ); |
| 1001 |
var specific_preset = event.target.closest( '[data-wpbc-date-selection-specific-preset]' ); |
| 1002 |
var specific_control = editor ? editor.node.querySelector( '[data-wpbc-date-selection-specific]' ) : null; |
| 1003 |
var changed_mode = event.target.closest( '[data-wpbc-date-selection-mode]' ); |
| 1004 |
var changed_toggle = event.target.closest( '[data-wpbc-date-selection-toggle]' ); |
| 1005 |
var changed_toggle_name = changed_toggle ? String( changed_toggle.dataset.wpbcDateSelectionToggle || '' ) : ''; |
| 1006 |
var editor_control = event.target.closest( '[data-wpbc-date-selection-mode], [data-wpbc-date-selection-number], [data-wpbc-date-selection-range], [data-wpbc-date-selection-specific], [data-wpbc-date-selection-specific-preset], [data-wpbc-date-selection-toggle], [data-wpbc-date-selection-legend-title], [data-wpbc-date-selection-weekdays] [data-weekday], [name="date_selection_check_in_time"], [name="date_selection_check_out_time"]' ); |
| 1007 |
|
| 1008 |
if ( ! editor_control || event.target.closest( '[data-wpbc-date-selection-calendar]' ) ) { |
| 1009 |
return; |
| 1010 |
} |
| 1011 |
|
| 1012 |
if ( weekday_button && ! weekday_button.disabled ) { |
| 1013 |
event.preventDefault(); |
| 1014 |
weekday_button.setAttribute( 'aria-pressed', 'true' === weekday_button.getAttribute( 'aria-pressed' ) ? 'false' : 'true' ); |
| 1015 |
weekday_button.classList.toggle( 'is-selected', 'true' === weekday_button.getAttribute( 'aria-pressed' ) ); |
| 1016 |
sync_weekday_transport( weekday_button.closest( '[data-wpbc-date-selection-weekdays]' ).dataset.wpbcDateSelectionWeekdays ); |
| 1017 |
} |
| 1018 |
if ( specific_preset && specific_control && ! specific_preset.disabled && ! specific_control.disabled ) { |
| 1019 |
event.preventDefault(); |
| 1020 |
apply_specific_day_preset( specific_preset, specific_control ); |
| 1021 |
} |
| 1022 |
if ( range_control ) { |
| 1023 |
sync_number_from_range( range_control ); |
| 1024 |
} else if ( number_control ) { |
| 1025 |
sync_range_from_number( number_control ); |
| 1026 |
} |
| 1027 |
apply_exclusive_toggle_action( changed_toggle ); |
| 1028 |
apply_selected_mode_defaults( changed_mode ); |
| 1029 |
|
| 1030 |
sync(); |
| 1031 |
render_editor_state(); |
| 1032 |
schedule_preview_update(); |
| 1033 |
if ( |
| 1034 |
changed_mode |
| 1035 |
|| 'date_selection_changeover_enabled' === changed_toggle_name |
| 1036 |
|| 'date_selection_recurrent_time' === changed_toggle_name |
| 1037 |
|| 'date_selection_checkout_available' === changed_toggle_name |
| 1038 |
) { |
| 1039 |
schedule_changeover_data_refresh(); |
| 1040 |
} |
| 1041 |
} |
| 1042 |
|
| 1043 |
return { |
| 1044 |
/** |
| 1045 |
* Initialize the editor below the authorized wizard shell. |
| 1046 |
* |
| 1047 |
* @param {Object} api Shared shell services. |
| 1048 |
* @return {void} |
| 1049 |
*/ |
| 1050 |
initialize: function ( api ) { |
| 1051 |
var node = api.root.querySelector( '[data-wpbc-date-selection-editor]' ); |
| 1052 |
|
| 1053 |
if ( ! node ) { |
| 1054 |
return; |
| 1055 |
} |
| 1056 |
|
| 1057 |
shell_api = api; |
| 1058 |
editor = { |
| 1059 |
node: node, |
| 1060 |
resource_id: parseInt( node.dataset.resourceId, 10 ) || 1, |
| 1061 |
calendar: node.querySelector( '[data-wpbc-date-selection-calendar]' ), |
| 1062 |
changeover: node.querySelector( '[data-wpbc-date-selection-changeover]' ), |
| 1063 |
legend: node.querySelector( '[data-wpbc-date-selection-legend]' ) |
| 1064 |
}; |
| 1065 |
last_selected_mode = get_selected_mode(); |
| 1066 |
node.addEventListener( 'change', handle_interaction ); |
| 1067 |
node.addEventListener( 'input', handle_interaction ); |
| 1068 |
node.addEventListener( 'click', handle_interaction ); |
| 1069 |
sync(); |
| 1070 |
render_editor_state(); |
| 1071 |
schedule_preview_update(); |
| 1072 |
}, |
| 1073 |
sync: sync, |
| 1074 |
validate: validate |
| 1075 |
}; |
| 1076 |
} |
| 1077 |
|
| 1078 |
/** |
| 1079 |
* Register after the shared shell exposes its module adapter API. |
| 1080 |
* |
| 1081 |
* @return {void} |
| 1082 |
*/ |
| 1083 |
function register_adapter() { |
| 1084 |
if ( window.wpbc_setup_wizard_api && 'function' === typeof window.wpbc_setup_wizard_api.register_step_adapter ) { |
| 1085 |
window.wpbc_setup_wizard_api.register_step_adapter( create_date_selection_adapter( module_config ) ); |
| 1086 |
} |
| 1087 |
} |
| 1088 |
|
| 1089 |
window.wpbc_setup_editor_modules = window.wpbc_setup_editor_modules || {}; |
| 1090 |
window.wpbc_setup_editor_modules.date_selection = { |
| 1091 |
create: create_date_selection_adapter |
| 1092 |
}; |
| 1093 |
|
| 1094 |
if ( 'loading' === document.readyState ) { |
| 1095 |
document.addEventListener( 'DOMContentLoaded', register_adapter ); |
| 1096 |
} else { |
| 1097 |
register_adapter(); |
| 1098 |
} |
| 1099 |
}( window, document ) ); |
| 1100 |
|