| 1 |
/** |
| 2 |
* Provide the reusable weekly schedule editor used by Setup Wizard Step 7. |
| 3 |
* |
| 4 |
* The module edits a bounded proposal DTO. It supports multiple disjoint |
| 5 |
* intervals per day and a general source-to-target schedule copy workflow. |
| 6 |
* Canonical persistence remains server-owned at the explicit save boundary. |
| 7 |
* |
| 8 |
* @package Booking Calendar |
| 9 |
*/ |
| 10 |
( function ( window, document ) { |
| 11 |
'use strict'; |
| 12 |
|
| 13 |
var module_config = window.wpbc_setup_wizard_working_hours || { i18n: {} }; |
| 14 |
|
| 15 |
/** |
| 16 |
* Create one isolated Working Hours editor adapter. |
| 17 |
* |
| 18 |
* @param {Object} config Module translations and presentation settings. |
| 19 |
* @return {Object} Step adapter accepted by the shared wizard shell. |
| 20 |
*/ |
| 21 |
function create_working_hours_adapter( config ) { |
| 22 |
var shell_api = null; |
| 23 |
var root = null; |
| 24 |
var editor = null; |
| 25 |
|
| 26 |
/** |
| 27 |
* Return one translated string with a safe fallback. |
| 28 |
* |
| 29 |
* @param {string} key Translation key. |
| 30 |
* @param {string} fallback Fallback copy. |
| 31 |
* @return {string} Translation or fallback. |
| 32 |
*/ |
| 33 |
function get_message( key, fallback ) { |
| 34 |
return config.i18n && config.i18n[ key ] ? config.i18n[ key ] : fallback; |
| 35 |
} |
| 36 |
|
| 37 |
/** |
| 38 |
* Clone one weekday interval list without sharing object references. |
| 39 |
* |
| 40 |
* @param {Object[]} intervals Source intervals. |
| 41 |
* @return {Object[]} Cloned intervals. |
| 42 |
*/ |
| 43 |
function clone_intervals( intervals ) { |
| 44 |
return intervals.map( function ( interval ) { |
| 45 |
return { |
| 46 |
start_second: interval.start_second, |
| 47 |
end_second: interval.end_second |
| 48 |
}; |
| 49 |
} ); |
| 50 |
} |
| 51 |
|
| 52 |
/** |
| 53 |
* Write the in-memory weekly schedule to the allow-listed draft field. |
| 54 |
* |
| 55 |
* @return {void} |
| 56 |
*/ |
| 57 |
function sync_working_hours_draft() { |
| 58 |
if ( ! editor ) { |
| 59 |
return; |
| 60 |
} |
| 61 |
|
| 62 |
editor.transport.value = JSON.stringify( { |
| 63 |
enabled: editor.enabled ? 'On' : 'Off', |
| 64 |
weekdays: editor.weekdays |
| 65 |
} ); |
| 66 |
} |
| 67 |
|
| 68 |
/** |
| 69 |
* Find a non-overlapping one-hour proposal for a new interval. |
| 70 |
* |
| 71 |
* @param {Object[]} intervals Existing intervals for one weekday. |
| 72 |
* @return {Object|null} Proposed interval, or null when no gap exists. |
| 73 |
*/ |
| 74 |
function find_interval_proposal( intervals ) { |
| 75 |
var sorted_intervals = intervals.slice().sort( function ( first_interval, second_interval ) { |
| 76 |
return first_interval.start_second - second_interval.start_second; |
| 77 |
} ); |
| 78 |
var preferred_start = sorted_intervals.length ? sorted_intervals[ sorted_intervals.length - 1 ].end_second : 9 * 3600; |
| 79 |
var candidates = []; |
| 80 |
var candidate_start; |
| 81 |
|
| 82 |
for ( candidate_start = 0; candidate_start <= 23 * 3600; candidate_start += 1800 ) { |
| 83 |
if ( candidate_start >= preferred_start ) { |
| 84 |
candidates.push( candidate_start ); |
| 85 |
} |
| 86 |
} |
| 87 |
for ( candidate_start = 0; candidate_start < preferred_start && candidate_start <= 23 * 3600; candidate_start += 1800 ) { |
| 88 |
candidates.push( candidate_start ); |
| 89 |
} |
| 90 |
|
| 91 |
candidate_start = null; |
| 92 |
candidates.some( function ( proposed_start ) { |
| 93 |
var proposed_end = proposed_start + 3600; |
| 94 |
var overlaps = sorted_intervals.some( function ( interval ) { |
| 95 |
return proposed_start < interval.end_second && proposed_end > interval.start_second; |
| 96 |
} ); |
| 97 |
|
| 98 |
if ( proposed_end <= 86400 && ! overlaps ) { |
| 99 |
candidate_start = proposed_start; |
| 100 |
return true; |
| 101 |
} |
| 102 |
return false; |
| 103 |
} ); |
| 104 |
|
| 105 |
return null === candidate_start ? null : { |
| 106 |
start_second: candidate_start, |
| 107 |
end_second: candidate_start + 3600 |
| 108 |
}; |
| 109 |
} |
| 110 |
|
| 111 |
/** |
| 112 |
* Render all weekday rows from the data-only Working Hours draft. |
| 113 |
* |
| 114 |
* @return {void} |
| 115 |
*/ |
| 116 |
function render_working_hours_editor() { |
| 117 |
if ( ! editor || ! editor.template ) { |
| 118 |
return; |
| 119 |
} |
| 120 |
|
| 121 |
editor.node.classList.toggle( 'is-disabled', ! editor.enabled ); |
| 122 |
if ( editor.enabled_toggle ) { |
| 123 |
editor.enabled_toggle.checked = editor.enabled; |
| 124 |
} |
| 125 |
if ( editor.schedule_controls ) { |
| 126 |
editor.schedule_controls.disabled = ! editor.enabled; |
| 127 |
} |
| 128 |
|
| 129 |
editor.node.querySelectorAll( '[data-wpbc-working-day]' ).forEach( function ( day_node ) { |
| 130 |
var day_number = parseInt( day_node.dataset.wpbcWorkingDay, 10 ); |
| 131 |
var day_label = day_node.dataset.dayLabel || ''; |
| 132 |
var intervals = editor.weekdays[ day_number ] || []; |
| 133 |
var is_enabled = 0 < intervals.length; |
| 134 |
var toggle = day_node.querySelector( '[data-wpbc-working-day-toggle]' ); |
| 135 |
var interval_container = day_node.querySelector( '[data-wpbc-working-intervals]' ); |
| 136 |
var unavailable = day_node.querySelector( '[data-wpbc-working-unavailable]' ); |
| 137 |
var add_button = day_node.querySelector( '[data-wpbc-add-working-interval]' ); |
| 138 |
|
| 139 |
day_node.classList.toggle( 'is-enabled', is_enabled ); |
| 140 |
if ( toggle ) { |
| 141 |
toggle.checked = is_enabled; |
| 142 |
} |
| 143 |
if ( interval_container ) { |
| 144 |
interval_container.hidden = ! is_enabled; |
| 145 |
interval_container.textContent = ''; |
| 146 |
intervals.forEach( function ( interval, interval_index ) { |
| 147 |
var interval_node = editor.template.content.firstElementChild.cloneNode( true ); |
| 148 |
var start_select = interval_node.querySelector( '[data-wpbc-working-start]' ); |
| 149 |
var end_select = interval_node.querySelector( '[data-wpbc-working-end]' ); |
| 150 |
var start_label = interval_node.querySelector( '[data-wpbc-working-start-label]' ); |
| 151 |
var end_label = interval_node.querySelector( '[data-wpbc-working-end-label]' ); |
| 152 |
var remove_button = interval_node.querySelector( '[data-wpbc-remove-working-interval]' ); |
| 153 |
var start_id = 'wpbc-working-' + day_number + '-' + interval_index + '-start'; |
| 154 |
var end_id = 'wpbc-working-' + day_number + '-' + interval_index + '-end'; |
| 155 |
|
| 156 |
interval_node.dataset.intervalIndex = String( interval_index ); |
| 157 |
start_select.id = start_id; |
| 158 |
start_select.value = String( interval.start_second ); |
| 159 |
end_select.id = end_id; |
| 160 |
end_select.value = String( interval.end_second ); |
| 161 |
start_label.htmlFor = start_id; |
| 162 |
end_label.htmlFor = end_id; |
| 163 |
remove_button.setAttribute( 'aria-label', get_message( 'remove_working_interval', 'Remove %s working interval' ).replace( '%s', day_label ) ); |
| 164 |
interval_container.appendChild( interval_node ); |
| 165 |
} ); |
| 166 |
} |
| 167 |
if ( unavailable ) { |
| 168 |
unavailable.hidden = is_enabled; |
| 169 |
} |
| 170 |
if ( add_button ) { |
| 171 |
add_button.hidden = ! is_enabled; |
| 172 |
add_button.disabled = intervals.length >= editor.max_intervals; |
| 173 |
} |
| 174 |
} ); |
| 175 |
|
| 176 |
sync_working_hours_draft(); |
| 177 |
} |
| 178 |
|
| 179 |
/** |
| 180 |
* Restore focus after an interval render replaces its controls. |
| 181 |
* |
| 182 |
* @param {number} day_number Weekday number using the canonical 0-6 shape. |
| 183 |
* @param {number} interval_index Preferred interval index after render. |
| 184 |
* @return {void} |
| 185 |
*/ |
| 186 |
function focus_working_hours_control( day_number, interval_index ) { |
| 187 |
var day_node; |
| 188 |
var interval_node; |
| 189 |
var focus_target; |
| 190 |
|
| 191 |
if ( ! editor ) { |
| 192 |
return; |
| 193 |
} |
| 194 |
|
| 195 |
day_node = editor.node.querySelector( '[data-wpbc-working-day="' + day_number + '"]' ); |
| 196 |
if ( ! day_node ) { |
| 197 |
return; |
| 198 |
} |
| 199 |
|
| 200 |
interval_node = day_node.querySelector( '[data-interval-index="' + interval_index + '"]' ); |
| 201 |
focus_target = interval_node |
| 202 |
? interval_node.querySelector( '[data-wpbc-working-start]' ) |
| 203 |
: day_node.querySelector( '[data-wpbc-working-day-toggle]' ); |
| 204 |
if ( focus_target ) { |
| 205 |
focus_target.focus(); |
| 206 |
} |
| 207 |
} |
| 208 |
|
| 209 |
/** |
| 210 |
* Keep copy targets valid when the source day changes. |
| 211 |
* |
| 212 |
* @return {void} |
| 213 |
*/ |
| 214 |
function refresh_copy_targets() { |
| 215 |
var source_day; |
| 216 |
|
| 217 |
if ( ! editor || ! editor.copy_source ) { |
| 218 |
return; |
| 219 |
} |
| 220 |
|
| 221 |
source_day = editor.copy_source.value; |
| 222 |
editor.node.querySelectorAll( '[data-wpbc-copy-target]' ).forEach( function ( target ) { |
| 223 |
var is_source = target.value === source_day; |
| 224 |
target.disabled = is_source; |
| 225 |
if ( is_source ) { |
| 226 |
target.checked = false; |
| 227 |
} |
| 228 |
} ); |
| 229 |
} |
| 230 |
|
| 231 |
/** |
| 232 |
* Open the accessible schedule-copy controls. |
| 233 |
* |
| 234 |
* @return {void} |
| 235 |
*/ |
| 236 |
function open_copy_panel() { |
| 237 |
if ( ! editor || ! editor.copy_panel || ! editor.copy_toggle ) { |
| 238 |
return; |
| 239 |
} |
| 240 |
|
| 241 |
editor.copy_panel.hidden = false; |
| 242 |
editor.copy_toggle.setAttribute( 'aria-expanded', 'true' ); |
| 243 |
refresh_copy_targets(); |
| 244 |
editor.copy_source.focus(); |
| 245 |
} |
| 246 |
|
| 247 |
/** |
| 248 |
* Close schedule-copy controls and optionally restore trigger focus. |
| 249 |
* |
| 250 |
* @param {boolean} restore_focus Whether trigger focus should be restored. |
| 251 |
* @return {void} |
| 252 |
*/ |
| 253 |
function close_copy_panel( restore_focus ) { |
| 254 |
if ( ! editor || ! editor.copy_panel || ! editor.copy_toggle ) { |
| 255 |
return; |
| 256 |
} |
| 257 |
|
| 258 |
editor.copy_panel.hidden = true; |
| 259 |
editor.copy_toggle.setAttribute( 'aria-expanded', 'false' ); |
| 260 |
if ( restore_focus ) { |
| 261 |
editor.copy_toggle.focus(); |
| 262 |
} |
| 263 |
} |
| 264 |
|
| 265 |
/** |
| 266 |
* Copy one source schedule to all selected target weekdays. |
| 267 |
* |
| 268 |
* Empty source schedules are valid and make the selected targets |
| 269 |
* unavailable, matching the visible source state. |
| 270 |
* |
| 271 |
* @return {void} |
| 272 |
*/ |
| 273 |
function apply_schedule_copy() { |
| 274 |
var source_day = parseInt( editor.copy_source.value, 10 ); |
| 275 |
var targets = Array.prototype.slice.call( editor.node.querySelectorAll( '[data-wpbc-copy-target]:checked:not(:disabled)' ) ); |
| 276 |
var source_intervals = editor.weekdays[ source_day ] || []; |
| 277 |
var first_available_target; |
| 278 |
|
| 279 |
if ( ! targets.length ) { |
| 280 |
shell_api.set_status( get_message( 'copy_target_required', 'Select at least one target day.' ) ); |
| 281 |
first_available_target = editor.node.querySelector( '[data-wpbc-copy-target]:not(:disabled)' ); |
| 282 |
if ( first_available_target ) { |
| 283 |
first_available_target.focus(); |
| 284 |
} |
| 285 |
return; |
| 286 |
} |
| 287 |
|
| 288 |
targets.forEach( function ( target ) { |
| 289 |
editor.weekdays[ parseInt( target.value, 10 ) ] = clone_intervals( source_intervals ); |
| 290 |
} ); |
| 291 |
render_working_hours_editor(); |
| 292 |
shell_api.clear_field_error( 'working_hours' ); |
| 293 |
shell_api.set_status( get_message( 'schedule_copied', 'The working-hours schedule was copied to the selected days.' ) ); |
| 294 |
close_copy_panel( true ); |
| 295 |
} |
| 296 |
|
| 297 |
/** |
| 298 |
* Handle Working Hours button actions. |
| 299 |
* |
| 300 |
* @param {MouseEvent} event Delegated click event. |
| 301 |
* @return {void} |
| 302 |
*/ |
| 303 |
function handle_click( event ) { |
| 304 |
var add_button = event.target.closest( '[data-wpbc-add-working-interval]' ); |
| 305 |
var remove_button = event.target.closest( '[data-wpbc-remove-working-interval]' ); |
| 306 |
|
| 307 |
if ( event.target.closest( '[data-wpbc-copy-schedule-toggle]' ) ) { |
| 308 |
event.preventDefault(); |
| 309 |
if ( editor.copy_panel.hidden ) { |
| 310 |
open_copy_panel(); |
| 311 |
} else { |
| 312 |
close_copy_panel( true ); |
| 313 |
} |
| 314 |
return; |
| 315 |
} |
| 316 |
if ( event.target.closest( '[data-wpbc-copy-schedule-cancel]' ) ) { |
| 317 |
event.preventDefault(); |
| 318 |
close_copy_panel( true ); |
| 319 |
return; |
| 320 |
} |
| 321 |
if ( event.target.closest( '[data-wpbc-copy-schedule-apply]' ) ) { |
| 322 |
event.preventDefault(); |
| 323 |
apply_schedule_copy(); |
| 324 |
return; |
| 325 |
} |
| 326 |
|
| 327 |
if ( add_button ) { |
| 328 |
var add_day_node = add_button.closest( '[data-wpbc-working-day]' ); |
| 329 |
var add_day_number = parseInt( add_day_node.dataset.wpbcWorkingDay, 10 ); |
| 330 |
var proposal = find_interval_proposal( editor.weekdays[ add_day_number ] || [] ); |
| 331 |
|
| 332 |
event.preventDefault(); |
| 333 |
if ( ! proposal || editor.weekdays[ add_day_number ].length >= editor.max_intervals ) { |
| 334 |
shell_api.set_status( get_message( 'working_hours_limit', 'No additional one-hour interval is available for this day.' ) ); |
| 335 |
return; |
| 336 |
} |
| 337 |
editor.weekdays[ add_day_number ].push( proposal ); |
| 338 |
render_working_hours_editor(); |
| 339 |
focus_working_hours_control( add_day_number, editor.weekdays[ add_day_number ].length - 1 ); |
| 340 |
shell_api.clear_field_error( 'working_hours' ); |
| 341 |
return; |
| 342 |
} |
| 343 |
|
| 344 |
if ( remove_button ) { |
| 345 |
var remove_day_node = remove_button.closest( '[data-wpbc-working-day]' ); |
| 346 |
var remove_interval_node = remove_button.closest( '[data-interval-index]' ); |
| 347 |
var remove_day_number = parseInt( remove_day_node.dataset.wpbcWorkingDay, 10 ); |
| 348 |
var remove_interval_index = parseInt( remove_interval_node.dataset.intervalIndex, 10 ); |
| 349 |
|
| 350 |
event.preventDefault(); |
| 351 |
editor.weekdays[ remove_day_number ].splice( remove_interval_index, 1 ); |
| 352 |
render_working_hours_editor(); |
| 353 |
focus_working_hours_control( remove_day_number, Math.min( remove_interval_index, editor.weekdays[ remove_day_number ].length - 1 ) ); |
| 354 |
shell_api.clear_field_error( 'working_hours' ); |
| 355 |
} |
| 356 |
} |
| 357 |
|
| 358 |
/** |
| 359 |
* Handle weekday, interval, and copy-source changes. |
| 360 |
* |
| 361 |
* @param {Event} event Delegated change event. |
| 362 |
* @return {void} |
| 363 |
*/ |
| 364 |
function handle_change( event ) { |
| 365 |
if ( event.target.matches( '[data-wpbc-working-hours-enabled]' ) ) { |
| 366 |
editor.enabled = event.target.checked; |
| 367 |
if ( ! editor.enabled ) { |
| 368 |
close_copy_panel( false ); |
| 369 |
} |
| 370 |
render_working_hours_editor(); |
| 371 |
shell_api.clear_field_error( 'working_hours' ); |
| 372 |
return; |
| 373 |
} |
| 374 |
|
| 375 |
if ( event.target.matches( '[data-wpbc-copy-source]' ) ) { |
| 376 |
refresh_copy_targets(); |
| 377 |
return; |
| 378 |
} |
| 379 |
|
| 380 |
if ( event.target.matches( '[data-wpbc-working-day-toggle]' ) ) { |
| 381 |
var toggle_day_node = event.target.closest( '[data-wpbc-working-day]' ); |
| 382 |
var toggle_day_number = parseInt( toggle_day_node.dataset.wpbcWorkingDay, 10 ); |
| 383 |
|
| 384 |
editor.weekdays[ toggle_day_number ] = event.target.checked ? [ find_interval_proposal( [] ) ] : []; |
| 385 |
render_working_hours_editor(); |
| 386 |
shell_api.clear_field_error( 'working_hours' ); |
| 387 |
return; |
| 388 |
} |
| 389 |
|
| 390 |
if ( event.target.matches( '[data-wpbc-working-start], [data-wpbc-working-end]' ) ) { |
| 391 |
var changed_day_node = event.target.closest( '[data-wpbc-working-day]' ); |
| 392 |
var changed_interval_node = event.target.closest( '[data-interval-index]' ); |
| 393 |
var changed_day_number = parseInt( changed_day_node.dataset.wpbcWorkingDay, 10 ); |
| 394 |
var changed_interval_index = parseInt( changed_interval_node.dataset.intervalIndex, 10 ); |
| 395 |
var changed_interval = editor.weekdays[ changed_day_number ][ changed_interval_index ]; |
| 396 |
|
| 397 |
if ( event.target.matches( '[data-wpbc-working-start]' ) ) { |
| 398 |
changed_interval.start_second = parseInt( event.target.value, 10 ); |
| 399 |
} else { |
| 400 |
changed_interval.end_second = parseInt( event.target.value, 10 ); |
| 401 |
} |
| 402 |
sync_working_hours_draft(); |
| 403 |
shell_api.clear_field_error( 'working_hours' ); |
| 404 |
} |
| 405 |
} |
| 406 |
|
| 407 |
/** |
| 408 |
* Close the schedule-copy panel with Escape while focus is inside it. |
| 409 |
* |
| 410 |
* @param {KeyboardEvent} event Keydown event. |
| 411 |
* @return {void} |
| 412 |
*/ |
| 413 |
function handle_keydown( event ) { |
| 414 |
if ( 'Escape' === event.key && editor.copy_panel && ! editor.copy_panel.hidden && editor.copy_panel.contains( event.target ) ) { |
| 415 |
event.preventDefault(); |
| 416 |
close_copy_panel( true ); |
| 417 |
} |
| 418 |
} |
| 419 |
|
| 420 |
/** |
| 421 |
* Initialize this module against shell-provided services. |
| 422 |
* |
| 423 |
* @param {Object} registered_shell_api Shared wizard adapter services. |
| 424 |
* @return {void} |
| 425 |
*/ |
| 426 |
function initialize( registered_shell_api ) { |
| 427 |
var parsed_working_hours; |
| 428 |
var weekdays = {}; |
| 429 |
var editor_node; |
| 430 |
var transport; |
| 431 |
|
| 432 |
shell_api = registered_shell_api; |
| 433 |
root = shell_api.root; |
| 434 |
editor_node = root.querySelector( '[data-wpbc-working-hours-editor]' ); |
| 435 |
transport = root.querySelector( '[data-wpbc-working-hours-draft]' ); |
| 436 |
if ( ! editor_node || ! transport ) { |
| 437 |
return; |
| 438 |
} |
| 439 |
|
| 440 |
try { |
| 441 |
parsed_working_hours = JSON.parse( transport.value || '{}' ); |
| 442 |
} catch ( parse_error ) { |
| 443 |
parsed_working_hours = {}; |
| 444 |
} |
| 445 |
|
| 446 |
[ 0, 1, 2, 3, 4, 5, 6 ].forEach( function ( day_number ) { |
| 447 |
var raw_intervals = parsed_working_hours.weekdays && Array.isArray( parsed_working_hours.weekdays[ day_number ] ) ? parsed_working_hours.weekdays[ day_number ] : []; |
| 448 |
|
| 449 |
weekdays[ day_number ] = raw_intervals.map( function ( interval ) { |
| 450 |
return { |
| 451 |
start_second: parseInt( interval.start_second, 10 ), |
| 452 |
end_second: parseInt( interval.end_second, 10 ) |
| 453 |
}; |
| 454 |
} ).filter( function ( interval ) { |
| 455 |
return isFinite( interval.start_second ) && Math.floor( interval.start_second ) === interval.start_second && isFinite( interval.end_second ) && Math.floor( interval.end_second ) === interval.end_second; |
| 456 |
} ); |
| 457 |
} ); |
| 458 |
|
| 459 |
editor = { |
| 460 |
node: editor_node, |
| 461 |
transport: transport, |
| 462 |
template: root.querySelector( '[data-wpbc-working-interval-template]' ), |
| 463 |
enabled: 'Off' !== parsed_working_hours.enabled, |
| 464 |
enabled_toggle: editor_node.querySelector( '[data-wpbc-working-hours-enabled]' ), |
| 465 |
schedule_controls: editor_node.querySelector( '[data-wpbc-working-hours-controls]' ), |
| 466 |
weekdays: weekdays, |
| 467 |
max_intervals: parseInt( transport.dataset.maxIntervals || '8', 10 ), |
| 468 |
copy_toggle: editor_node.querySelector( '[data-wpbc-copy-schedule-toggle]' ), |
| 469 |
copy_panel: editor_node.querySelector( '[data-wpbc-copy-schedule-panel]' ), |
| 470 |
copy_source: editor_node.querySelector( '[data-wpbc-copy-source]' ) |
| 471 |
}; |
| 472 |
|
| 473 |
editor.node.addEventListener( 'click', handle_click ); |
| 474 |
editor.node.addEventListener( 'change', handle_change ); |
| 475 |
editor.node.addEventListener( 'keydown', handle_keydown ); |
| 476 |
refresh_copy_targets(); |
| 477 |
render_working_hours_editor(); |
| 478 |
} |
| 479 |
|
| 480 |
/** |
| 481 |
* Validate interval order and overlap before forward navigation. |
| 482 |
* |
| 483 |
* @return {HTMLElement|null} First invalid control, or null. |
| 484 |
*/ |
| 485 |
function validate() { |
| 486 |
var first_invalid = null; |
| 487 |
var enabled_interval_count = 0; |
| 488 |
|
| 489 |
if ( ! editor ) { |
| 490 |
return null; |
| 491 |
} |
| 492 |
if ( ! editor.enabled ) { |
| 493 |
shell_api.clear_field_error( 'working_hours' ); |
| 494 |
sync_working_hours_draft(); |
| 495 |
return null; |
| 496 |
} |
| 497 |
|
| 498 |
editor.node.querySelectorAll( '[data-wpbc-working-start], [data-wpbc-working-end]' ).forEach( function ( control ) { |
| 499 |
control.removeAttribute( 'aria-invalid' ); |
| 500 |
} ); |
| 501 |
|
| 502 |
[ 0, 1, 2, 3, 4, 5, 6 ].some( function ( day_number ) { |
| 503 |
var intervals = editor.weekdays[ day_number ] || []; |
| 504 |
var sorted_intervals = intervals.slice().sort( function ( first_interval, second_interval ) { |
| 505 |
return first_interval.start_second - second_interval.start_second; |
| 506 |
} ); |
| 507 |
var previous_end = -1; |
| 508 |
var day_node = editor.node.querySelector( '[data-wpbc-working-day="' + day_number + '"]' ); |
| 509 |
|
| 510 |
enabled_interval_count += intervals.length; |
| 511 |
return sorted_intervals.some( function ( interval ) { |
| 512 |
var interval_index = intervals.indexOf( interval ); |
| 513 |
var interval_node = day_node && day_node.querySelector( '[data-interval-index="' + interval_index + '"]' ); |
| 514 |
var invalid_range = ! isFinite( interval.start_second ) || Math.floor( interval.start_second ) !== interval.start_second || ! isFinite( interval.end_second ) || Math.floor( interval.end_second ) !== interval.end_second || interval.start_second < 0 || interval.end_second > 86400 || interval.start_second >= interval.end_second; |
| 515 |
var overlaps = interval.start_second < previous_end; |
| 516 |
|
| 517 |
previous_end = Math.max( previous_end, interval.end_second ); |
| 518 |
if ( invalid_range || overlaps ) { |
| 519 |
first_invalid = interval_node ? interval_node.querySelector( invalid_range ? '[data-wpbc-working-end]' : '[data-wpbc-working-start]' ) : day_node.querySelector( '[data-wpbc-working-day-toggle]' ); |
| 520 |
if ( first_invalid ) { |
| 521 |
first_invalid.setAttribute( 'aria-invalid', 'true' ); |
| 522 |
} |
| 523 |
shell_api.set_field_error( 'working_hours', invalid_range ? get_message( 'working_hours_invalid', 'Every working interval must end after it starts.' ) : get_message( 'working_hours_overlap', 'Working intervals on the same day cannot overlap or repeat.' ) ); |
| 524 |
return true; |
| 525 |
} |
| 526 |
return false; |
| 527 |
} ); |
| 528 |
} ); |
| 529 |
|
| 530 |
if ( ! first_invalid && 0 === enabled_interval_count ) { |
| 531 |
first_invalid = shell_api.set_field_error( 'working_hours', get_message( 'working_hours_required', 'Enable at least one weekday and add its working hours.' ) ); |
| 532 |
} |
| 533 |
|
| 534 |
sync_working_hours_draft(); |
| 535 |
return first_invalid; |
| 536 |
} |
| 537 |
|
| 538 |
return { |
| 539 |
initialize: initialize, |
| 540 |
sync: sync_working_hours_draft, |
| 541 |
validate: validate |
| 542 |
}; |
| 543 |
} |
| 544 |
|
| 545 |
window.wpbc_setup_editor_modules = window.wpbc_setup_editor_modules || {}; |
| 546 |
window.wpbc_setup_editor_modules.working_hours = { |
| 547 |
create: create_working_hours_adapter |
| 548 |
}; |
| 549 |
|
| 550 |
if ( window.wpbc_setup_wizard_api && 'function' === typeof window.wpbc_setup_wizard_api.register_step_adapter ) { |
| 551 |
window.wpbc_setup_wizard_api.register_step_adapter( create_working_hours_adapter( module_config ) ); |
| 552 |
} |
| 553 |
}( window, document ) ); |
| 554 |
|