← All changes
|
includes/page-form-builder/_src/bfb-rightbar-tabs.js
+258
-9
11.2.1
→
11.9
View file →
| @@ -1,4 +1,4 @@ | ||
| 1 | 1 | /** |
| 2 | 2 | * Booking Calendar — Rightbar Tabs Controller (JS) |
| 3 | 3 | * |
| 4 | 4 | * Purpose: Handles the main right sidebar tabs (Library / Inspector / Settings) in the Booking Form Builder. |
| @@ -303,17 +303,266 @@ | ||
| 303 | 303 | if ( panel_id ) { |
| 304 | 304 | this.show_panel( panel_id, tab_el || undefined ); |
| 305 | 305 | } |
| 306 | 306 | } |
| 307 | - } | |
| 308 | - | |
| 309 | - // Boot once DOM is ready. | |
| 310 | - const instance = new WPBC_BFB_Rightbar_Tabs(); | |
| 311 | - if ( d.readyState === 'loading' ) { | |
| 312 | - d.addEventListener( 'DOMContentLoaded', () => instance.init() ); | |
| 313 | - } else { | |
| 314 | - instance.init(); | |
| 315 | - } | |
| 307 | + } | |
| 308 | + | |
| 309 | + function esc_attr_selector_value(value) { | |
| 310 | + if ( Sanit && typeof Sanit.esc_attr_value_for_selector === 'function' ) { | |
| 311 | + return Sanit.esc_attr_value_for_selector( value ); | |
| 312 | + } | |
| 313 | + return String( value == null ? '' : value ) | |
| 314 | + .replace( /\\/g, '\\\\' ) | |
| 315 | + .replace( /"/g, '\\"' ) | |
| 316 | + .replace( /\n/g, '\\A ' ) | |
| 317 | + .replace( /\]/g, '\\]' ); | |
| 318 | + } | |
| 319 | + | |
| 320 | + function get_url_params() { | |
| 321 | + try { | |
| 322 | + return new URLSearchParams( w.location.search || '' ); | |
| 323 | + } catch ( _e ) { | |
| 324 | + return null; | |
| 325 | + } | |
| 326 | + } | |
| 327 | + | |
| 328 | + function open_settings_group(group_key) { | |
| 329 | + const panel = d.getElementById( 'wpbc_bfb__inspector_form_settings' ) || d; | |
| 330 | + const group = panel.querySelector( '.wpbc_bfb__inspector__group[data-group="' + esc_attr_selector_value( group_key ) + '"]' ); | |
| 331 | + if ( ! group ) { | |
| 332 | + return false; | |
| 333 | + } | |
| 334 | + | |
| 335 | + const header = group.querySelector( '.group__header' ); | |
| 336 | + const fields = group.querySelector( '.group__fields' ); | |
| 337 | + | |
| 338 | + group.classList.add( 'is-open' ); | |
| 339 | + if ( header ) { | |
| 340 | + header.setAttribute( 'aria-expanded', 'true' ); | |
| 341 | + } | |
| 342 | + if ( fields ) { | |
| 343 | + fields.removeAttribute( 'hidden' ); | |
| 344 | + fields.setAttribute( 'aria-hidden', 'false' ); | |
| 345 | + } | |
| 346 | + | |
| 347 | + return true; | |
| 348 | + } | |
| 349 | + | |
| 350 | + function focus_settings_row(row_key) { | |
| 351 | + const panel = d.getElementById( 'wpbc_bfb__inspector_form_settings' ) || d; | |
| 352 | + const row = panel.querySelector( '.wpbc-setting[data-key="' + esc_attr_selector_value( row_key ) + '"]' ); | |
| 353 | + if ( ! row ) { | |
| 354 | + return false; | |
| 355 | + } | |
| 356 | + | |
| 357 | + try { | |
| 358 | + row.scrollIntoView( { behavior: 'smooth', block: 'center', inline: 'nearest' } ); | |
| 359 | + } catch ( _e ) { | |
| 360 | + row.scrollIntoView( true ); | |
| 361 | + } | |
| 362 | + | |
| 363 | + row.classList.remove( 'wpbc_bfb__scroll-pulse', 'wpbc_bfb__highlight-pulse' ); | |
| 364 | + void row.offsetWidth; | |
| 365 | + row.classList.add( 'wpbc_bfb__scroll-pulse', 'wpbc_bfb__highlight-pulse' ); | |
| 366 | + | |
| 367 | + setTimeout( () => { | |
| 368 | + row.classList.remove( 'wpbc_bfb__scroll-pulse', 'wpbc_bfb__highlight-pulse' ); | |
| 369 | + }, 2200 ); | |
| 370 | + | |
| 371 | + const control = row.querySelector( '[data-wpbc-bfb-fs-key="' + esc_attr_selector_value( row_key ) + '"]' ) | |
| 372 | + || row.querySelector( 'select,input,textarea,button' ); | |
| 373 | + | |
| 374 | + if ( control && typeof control.focus === 'function' ) { | |
| 375 | + setTimeout( () => { | |
| 376 | + try { | |
| 377 | + control.focus( { preventScroll: true } ); | |
| 378 | + } catch ( _e ) { | |
| 379 | + control.focus(); | |
| 380 | + } | |
| 381 | + }, 250 ); | |
| 382 | + } | |
| 383 | + | |
| 384 | + return true; | |
| 385 | + } | |
| 386 | + | |
| 387 | + /** | |
| 388 | + * Open one collapsible Add Fields palette group. | |
| 389 | + * | |
| 390 | + * The setup-wizard deep link uses this helper to reveal a field pack without | |
| 391 | + * inserting or otherwise changing the current booking form. | |
| 392 | + * | |
| 393 | + * @param {string} group_key Palette group key from its data-group attribute. | |
| 394 | + * @returns {boolean} Whether the requested palette group was found and opened. | |
| 395 | + */ | |
| 396 | + function open_palette_group(group_key) { | |
| 397 | + const panel = d.getElementById( 'wpbc_bfb__palette_add_new' ) || d; | |
| 398 | + const group = panel.querySelector( '.wpbc_bfb__inspector__group[data-group="' + esc_attr_selector_value( group_key ) + '"]' ); | |
| 399 | + if ( ! group ) { | |
| 400 | + return false; | |
| 401 | + } | |
| 402 | + | |
| 403 | + const header = group.querySelector( '.group__header' ); | |
| 404 | + const fields = group.querySelector( '.group__fields' ); | |
| 405 | + | |
| 406 | + group.classList.add( 'is-open' ); | |
| 407 | + if ( header ) { | |
| 408 | + header.setAttribute( 'aria-expanded', 'true' ); | |
| 409 | + } | |
| 410 | + if ( fields ) { | |
| 411 | + fields.removeAttribute( 'hidden' ); | |
| 412 | + fields.setAttribute( 'aria-hidden', 'false' ); | |
| 413 | + } | |
| 414 | + | |
| 415 | + return true; | |
| 416 | + } | |
| 417 | + | |
| 418 | + /** | |
| 419 | + * Focus and visually highlight one Add Fields palette item. | |
| 420 | + * | |
| 421 | + * @param {string} field_type Registered Form Builder field type. | |
| 422 | + * @returns {boolean} Whether the requested field pack palette item was found. | |
| 423 | + */ | |
| 424 | + function focus_palette_field(field_type) { | |
| 425 | + const panel = d.getElementById( 'wpbc_bfb__palette_add_new' ) || d; | |
| 426 | + const field = panel.querySelector( '.wpbc_bfb__field[data-type="' + esc_attr_selector_value( field_type ) + '"]' ); | |
| 427 | + if ( ! field ) { | |
| 428 | + return false; | |
| 429 | + } | |
| 430 | + | |
| 431 | + try { | |
| 432 | + field.scrollIntoView( { behavior: 'smooth', block: 'center', inline: 'nearest' } ); | |
| 433 | + } catch ( _e ) { | |
| 434 | + field.scrollIntoView( true ); | |
| 435 | + } | |
| 436 | + | |
| 437 | + field.classList.remove( 'wpbc_bfb__scroll-pulse', 'wpbc_bfb__highlight-pulse' ); | |
| 438 | + void field.offsetWidth; | |
| 439 | + field.classList.add( 'wpbc_bfb__scroll-pulse', 'wpbc_bfb__highlight-pulse' ); | |
| 440 | + field.setAttribute( 'tabindex', '-1' ); | |
| 441 | + | |
| 442 | + setTimeout( () => { | |
| 443 | + field.classList.remove( 'wpbc_bfb__scroll-pulse', 'wpbc_bfb__highlight-pulse' ); | |
| 444 | + }, 2200 ); | |
| 445 | + setTimeout( () => { | |
| 446 | + try { | |
| 447 | + field.focus( { preventScroll: true } ); | |
| 448 | + } catch ( _e ) { | |
| 449 | + field.focus(); | |
| 450 | + } | |
| 451 | + }, 250 ); | |
| 452 | + | |
| 453 | + return true; | |
| 454 | + } | |
| 455 | + | |
| 456 | + let deep_link_done = false; | |
| 457 | + let deep_link_ajax_listener_bound = false; | |
| 458 | + | |
| 459 | + function has_initial_deep_link() { | |
| 460 | + const params = get_url_params(); | |
| 461 | + return !! ( | |
| 462 | + params | |
| 463 | + && -1 !== [ 'form_settings', 'add_fields' ].indexOf( params.get( 'wpbc_bfb_panel' ) ) | |
| 464 | + ); | |
| 465 | + } | |
| 466 | + | |
| 467 | + function handle_initial_deep_link(tabs, attempt = 0) { | |
| 468 | + if ( deep_link_done ) { | |
| 469 | + return; | |
| 470 | + } | |
| 471 | + | |
| 472 | + const params = get_url_params(); | |
| 473 | + if ( ! params || -1 === [ 'form_settings', 'add_fields' ].indexOf( params.get( 'wpbc_bfb_panel' ) ) ) { | |
| 474 | + return; | |
| 475 | + } | |
| 476 | + | |
| 477 | + const panel_mode = params.get( 'wpbc_bfb_panel' ); | |
| 478 | + const panel_id = 'add_fields' === panel_mode ? 'wpbc_bfb__palette_add_new' : 'wpbc_bfb__inspector_form_settings'; | |
| 479 | + const tab = d.getElementById( 'add_fields' === panel_mode ? 'wpbc_tab_library' : 'wpbc_tab_form' ); | |
| 480 | + const panel = d.getElementById( panel_id ); | |
| 481 | + if ( ! tab || ! panel ) { | |
| 482 | + if ( attempt < 25 ) { | |
| 483 | + setTimeout( () => handle_initial_deep_link( tabs, attempt + 1 ), 80 ); | |
| 484 | + } | |
| 485 | + return; | |
| 486 | + } | |
| 487 | + | |
| 488 | + tabs.show_panel( panel_id, tab ); | |
| 489 | + | |
| 490 | + const group_key = params.get( 'wpbc_bfb_group' ); | |
| 491 | + const row_key = params.get( 'wpbc_bfb_focus' ); | |
| 492 | + const group_ok = group_key | |
| 493 | + ? ( 'add_fields' === panel_mode ? open_palette_group( group_key ) : open_settings_group( group_key ) ) | |
| 494 | + : true; | |
| 495 | + const row_ok = row_key | |
| 496 | + ? ( 'add_fields' === panel_mode ? focus_palette_field( row_key ) : focus_settings_row( row_key ) ) | |
| 497 | + : true; | |
| 498 | + | |
| 499 | + if ( ( ! group_ok || ! row_ok ) && attempt < 25 ) { | |
| 500 | + setTimeout( () => handle_initial_deep_link( tabs, attempt + 1 ), 80 ); | |
| 501 | + return; | |
| 502 | + } | |
| 503 | + | |
| 504 | + deep_link_done = group_ok && row_ok; | |
| 505 | + } | |
| 506 | + | |
| 507 | + function schedule_initial_deep_link(tabs, delay = 0) { | |
| 508 | + if ( deep_link_done || ! has_initial_deep_link() ) { | |
| 509 | + return; | |
| 510 | + } | |
| 511 | + | |
| 512 | + setTimeout( () => handle_initial_deep_link( tabs ), delay ); | |
| 513 | + } | |
| 514 | + | |
| 515 | + function bind_initial_deep_link_after_form_load(tabs, attempt = 0) { | |
| 516 | + if ( ! has_initial_deep_link() ) { | |
| 517 | + return; | |
| 518 | + } | |
| 519 | + | |
| 520 | + if ( ! deep_link_ajax_listener_bound ) { | |
| 521 | + deep_link_ajax_listener_bound = true; | |
| 522 | + d.addEventListener( 'wpbc:bfb:form:ajax_loaded', () => { | |
| 523 | + // Legacy/blank forms do not always emit STRUCTURE_LOADED; wait until add_page() and UI defaults settle. | |
| 524 | + schedule_initial_deep_link( tabs, 450 ); | |
| 525 | + }, { once: true } ); | |
| 526 | + } | |
| 527 | + | |
| 528 | + if ( ! w.wpbc_bfb_api || ! w.wpbc_bfb_api.ready || typeof w.wpbc_bfb_api.ready.then !== 'function' ) { | |
| 529 | + if ( attempt < 25 ) { | |
| 530 | + setTimeout( () => bind_initial_deep_link_after_form_load( tabs, attempt + 1 ), 80 ); | |
| 531 | + } | |
| 532 | + return; | |
| 533 | + } | |
| 534 | + | |
| 535 | + w.wpbc_bfb_api.ready.then( (builder) => { | |
| 536 | + const events = ( w.WPBC_BFB_Core && w.WPBC_BFB_Core.WPBC_BFB_Events ) || {}; | |
| 537 | + const event_name = events.STRUCTURE_LOADED || 'wpbc:bfb:structure:loaded'; | |
| 538 | + if ( ! builder || ! builder.bus || typeof builder.bus.on !== 'function' ) { | |
| 539 | + return; | |
| 540 | + } | |
| 541 | + | |
| 542 | + const on_structure_loaded = () => { | |
| 543 | + if ( builder.bus && typeof builder.bus.off === 'function' ) { | |
| 544 | + builder.bus.off( event_name, on_structure_loaded ); | |
| 545 | + } | |
| 546 | + // Run after selection clearing/inspector defaults attached to the same load event. | |
| 547 | + schedule_initial_deep_link( tabs, 0 ); | |
| 548 | + }; | |
| 549 | + | |
| 550 | + builder.bus.on( event_name, on_structure_loaded ); | |
| 551 | + } ); | |
| 552 | + } | |
| 553 | + | |
| 554 | + // Boot once DOM is ready. | |
| 555 | + const instance = new WPBC_BFB_Rightbar_Tabs(); | |
| 556 | + const boot = () => { | |
| 557 | + instance.init(); | |
| 558 | + bind_initial_deep_link_after_form_load( instance ); | |
| 559 | + }; | |
| 560 | + if ( d.readyState === 'loading' ) { | |
| 561 | + d.addEventListener( 'DOMContentLoaded', boot ); | |
| 562 | + } else { | |
| 563 | + boot(); | |
| 564 | + } | |
| 316 | 565 | |
| 317 | 566 | // (Optional) expose for debugging: |
| 318 | 567 | // w.WPBC_BFB_Rightbar_Tabs = instance; |
| 319 | 568 | |