/**
* Booking Calendar — Generic UI Tabs Utility (JS)
*
* Purpose: Lightweight, dependency-free tabs controller for any small tab group in admin UIs.
* - Auto-initializes groups marked with data-wpbc-tabs.
* - Assigns ARIA roles and toggles aria-selected/aria-hidden/tabindex.
* - Supports keyboard navigation (Left/Right/Home/End).
* - Public API: window.wpbc_ui_tabs.{init_on, init_group, set_active}
* - Emits 'wpbc:tabs:change' on the group root when the active tab changes.
*
* Markup contract:
* - Root: [data-wpbc-tabs]
* - Tabs: [data-wpbc-tab-key="K"]
* - Panels: [data-wpbc-tab-panel="K"]
*
* @package Booking Calendar
* @subpackage Admin\UI
* @since 11.0.0
* @version 1.0.0
* @see /includes/__js/admin/ui_tabs/ui_tabs.js
*
*
* How it works:
* - Root node must have [data-wpbc-tabs] attribute (any value).
* - Tab buttons must carry [data-wpbc-tab-key="..."] (unique per group).
* - Panels must carry [data-wpbc-tab-panel="..."] with matching keys.
* - Adds WAI-ARIA roles and aria-selected/hidden wiring.
*
*
*
* Public API:
* - wpbc_ui_tabs.init_on(root_or_selector) // find and init groups within a container
* - wpbc_ui_tabs.init_group(root_el) // init a single group root
* - wpbc_ui_tabs.set_active(root_el, key) // programmatically change active tab
*
* Events:
* - Dispatches CustomEvent 'wpbc:tabs:change' on root when tab changes:
* detail: { active_key: '2', prev_key: '1' }
*
* Switch a local (generic) tabs group to tab 3: var group = document.querySelector('[data-wpbc-tabs="column-styles"]'); if ( group ) { wpbc_ui_tabs.set_active(group, '3'); }
*/
(function ( w ) {
'use strict';
if ( w.wpbc_ui_tabs ) {
return;
}
/**
* Internal: toggle active state.
*
* @param {HTMLElement} root_el
* @param {string} key
* @param {boolean} should_emit
*/
function set_active_internal( root_el, key, should_emit ) {
var tab_btns = root_el.querySelectorAll( '[data-wpbc-tab-key]' );
var panels = root_el.querySelectorAll( '[data-wpbc-tab-panel]' );
var prev_key = root_el.getAttribute( 'data-wpbc-tab-active' ) || null;
if ( String( prev_key ) === String( key ) ) {
return;
}
// Buttons: aria + class
for ( var i = 0; i < tab_btns.length; i++ ) {
var btn = tab_btns[i];
var b_key = btn.getAttribute( 'data-wpbc-tab-key' );
var is_on = String( b_key ) === String( key );
btn.setAttribute( 'role', 'tab' );
btn.setAttribute( 'aria-selected', is_on ? 'true' : 'false' );
btn.setAttribute( 'tabindex', is_on ? '0' : '-1' );
if ( is_on ) {
btn.classList.add( 'active' );
} else {
btn.classList.remove( 'active' );
}
}
// Panels: aria + visibility
for ( var j = 0; j < panels.length; j++ ) {
var pn = panels[j];
var pkey = pn.getAttribute( 'data-wpbc-tab-panel' );
var show = String( pkey ) === String( key );
pn.setAttribute( 'role', 'tabpanel' );
pn.setAttribute( 'aria-hidden', show ? 'false' : 'true' );
if ( show ) {
pn.removeAttribute( 'hidden' );
} else {
pn.setAttribute( 'hidden', '' );
}
}
root_el.setAttribute( 'data-wpbc-tab-active', String( key ) );
if ( should_emit ) {
try {
var ev = new w.CustomEvent( 'wpbc:tabs:change', {
bubbles : true,
detail : { active_key : String( key ), prev_key : prev_key }
} );
root_el.dispatchEvent( ev );
} catch ( _e ) {}
}
}
/**
* Internal: get ordered keys from buttons.
*
* @param {HTMLElement} root_el
* @returns {string[]}
*/
function get_keys( root_el ) {
var list = [];
var btns = root_el.querySelectorAll( '[data-wpbc-tab-key]' );
for ( var i = 0; i < btns.length; i++ ) {
var k = btns[i].getAttribute( 'data-wpbc-tab-key' );
if ( k != null && k !== '' ) {
list.push( String( k ) );
}
}
return list;
}
/**
* Internal: move focus between tabs using keyboard.
*
* @param {HTMLElement} root_el
* @param {number} dir +1 (next) / -1 (prev)
*/
function focus_relative( root_el, dir ) {
var keys = get_keys( root_el );
var current = root_el.getAttribute( 'data-wpbc-tab-active' ) || keys[0] || null;
var idx = Math.max( 0, keys.indexOf( String( current ) ) );
var next = keys[ ( idx + ( dir > 0 ? 1 : keys.length - 1 ) ) % keys.length ];
var next_btn = root_el.querySelector( '[data-wpbc-tab-key="' + next + '"]' );
if ( next_btn ) {
next_btn.focus();
set_active_internal( root_el, next, true );
}
}
/**
* Initialize a single tabs group root.
*
* @param {HTMLElement} root_el
*/
function init_group( root_el ) {
if ( ! root_el || root_el.__wpbc_tabs_inited ) {
return;
}
root_el.__wpbc_tabs_inited = true;
// Roles
var tablist = root_el.querySelector( '[data-wpbc-tablist]' ) || root_el;
tablist.setAttribute( 'role', 'tablist' );
// Default active: from attribute or first button
var keys = get_keys( root_el );
var def = root_el.getAttribute( 'data-wpbc-tab-active' ) || ( keys[0] || '1' );
set_active_internal( root_el, def, false );
// Clicks
root_el.addEventListener( 'click', function ( e ) {
var btn = e.target.closest ? e.target.closest( '[data-wpbc-tab-key]' ) : null;
if ( ! btn || ! root_el.contains( btn ) ) {
return;
}
e.preventDefault();
var key = btn.getAttribute( 'data-wpbc-tab-key' );
if ( key != null ) {
set_active_internal( root_el, key, true );
}
}, true );
// Keyboard (Left/Right/Home/End)
root_el.addEventListener( 'keydown', function ( e ) {
var tgt = e.target;
if ( ! tgt || ! tgt.hasAttribute || ! tgt.hasAttribute( 'data-wpbc-tab-key' ) ) {
return;
}
switch ( e.key ) {
case 'ArrowLeft':
e.preventDefault(); focus_relative( root_el, -1 ); break;
case 'ArrowRight':
e.preventDefault(); focus_relative( root_el, +1 ); break;
case 'Home':
e.preventDefault(); set_active_internal( root_el, ( get_keys( root_el )[0] || '1' ), true ); break;
case 'End':
e.preventDefault(); var ks = get_keys( root_el ); set_active_internal( root_el, ( ks[ ks.length - 1 ] || '1' ), true ); break;
}
}, true );
}
/**
* Initialize all groups within a container (or document).
*
* @param {HTMLElement|string|null} container
*/
function init_on( container ) {
var ctx = container ? ( typeof container === 'string' ? document.querySelector( container ) : container ) : document;
if ( ! ctx ) {
return;
}
var groups = ctx.querySelectorAll( '[data-wpbc-tabs]' );
for ( var i = 0; i < groups.length; i++ ) {
init_group( groups[i] );
}
}
/**
* Programmatically set active tab by key.
*
* @param {HTMLElement} root_el
* @param {string|number} key
*/
function set_active( root_el, key ) {
if ( root_el && root_el.hasAttribute && root_el.hasAttribute( 'data-wpbc-tabs' ) ) {
set_active_internal( root_el, String( key ), true );
}
}
// Public API (snake_case)
w.wpbc_ui_tabs = {
init_on : init_on,
init_group : init_group,
set_active : set_active
};
// Auto-init on DOM ready
if ( document.readyState === 'loading' ) {
document.addEventListener( 'DOMContentLoaded', function () { init_on( document ); } );
} else {
init_on( document );
}
})( window );