"use strict";
/**
* Blink specific HTML element to set attention to this element.
*
* @param {string} element_to_blink - class or id of element: '.wpbc_widget_available_unavailable'
* @param {int} how_many_times - 4
* @param {int} how_long_to_blink - 350
*/
function wpbc_blink_element(element_to_blink, how_many_times = 4, how_long_to_blink = 350) {
for (let i = 0; i < how_many_times; i++) {
jQuery(element_to_blink).fadeOut(how_long_to_blink).fadeIn(how_long_to_blink);
}
jQuery(element_to_blink).animate({
opacity: 1
}, 500);
}
/**
* Support Functions - Spin Icon in Buttons ------------------------------------------------------------------ */
/**
* Remove spin icon from button and Enable this button.
*
* @param button_clicked_element_id - HTML ID attribute of this button
* @return string - CSS classes that was previously in button icon
*/
function wpbc_button__remove_spin(button_clicked_element_id) {
var previos_classes = '';
if (undefined != button_clicked_element_id && '' != button_clicked_element_id) {
var jElement = jQuery('#' + button_clicked_element_id);
if (jElement.length) {
previos_classes = wpbc_button_disable_loading_icon(jElement.get(0));
}
}
return previos_classes;
}
/**
* Show Loading (rotating arrow) icon for button that has been clicked
*
* @param this_button - this object of specific button
* @return string - CSS classes that was previously in button icon
*/
function wpbc_button_enable_loading_icon(this_button) {
var jButton = jQuery(this_button);
var jIcon = jButton.find('i');
var previos_classes = jIcon.attr('class');
jIcon.removeClass().addClass('menu_icon icon-1x wpbc_icn_rotate_right wpbc_spin'); // Set Rotate icon.
// jIcon.addClass( 'wpbc_animation_pause' ); // Pause animation.
// jIcon.addClass( 'wpbc_ui_red' ); // Set icon color red.
jIcon.attr('wpbc_previous_class', previos_classes);
jButton.addClass('disabled'); // Disable button
// We need to set here attr instead of prop, because for A elements, attribute 'disabled' do not added with jButton.prop( "disabled", true );.
jButton.attr('wpbc_previous_onclick', jButton.attr('onclick')); // Save this value.
jButton.attr('onclick', ''); // Disable actions "on click".
return previos_classes;
}
/**
* Hide Loading (rotating arrow) icon for button that was clicked and show previous icon and enable button
*
* @param this_button - this object of specific button
* @return string - CSS classes that was previously in button icon
*/
function wpbc_button_disable_loading_icon(this_button) {
var jButton = jQuery(this_button);
var jIcon = jButton.find('i');
var previos_classes = jIcon.attr('wpbc_previous_class');
if (undefined != previos_classes && '' != previos_classes) {
jIcon.removeClass().addClass(previos_classes);
}
jButton.removeClass('disabled'); // Remove Disable button.
var previous_onclick = jButton.attr('wpbc_previous_onclick');
if (undefined != previous_onclick && '' != previous_onclick) {
jButton.attr('onclick', previous_onclick);
}
return previos_classes;
}
/**
* On selection of radio button, adjust attributes of radio container
*
* @param _this
*/
function wpbc_ui_el__radio_container_selection(_this) {
if (jQuery(_this).is(':checked')) {
jQuery(_this).parents('.wpbc_ui_radio_section').find('.wpbc_ui_radio_container').removeAttr('data-selected');
jQuery(_this).parents('.wpbc_ui_radio_container:not(.disabled)').attr('data-selected', true);
}
if (jQuery(_this).is(':disabled')) {
jQuery(_this).parents('.wpbc_ui_radio_container').addClass('disabled');
}
}
/**
* On click on Radio Container, we will select the radio button and then adjust attributes of radio container
*
* @param _this
*/
function wpbc_ui_el__radio_container_click(_this) {
if (jQuery(_this).hasClass('disabled')) {
return false;
}
var j_radio = jQuery(_this).find('input[type=radio]:not(.wpbc-form-radio-internal)');
if (j_radio.length) {
j_radio.prop('checked', true).trigger('change');
}
}
"use strict";
// =====================================================================================================================
// == Full Screen - support functions ==
// =====================================================================================================================
/**
* Save Full Screen preference in a short browser cookie.
*
* This makes the next admin page load deterministic even if the async user-meta
* AJAX request is interrupted by immediate navigation.
*
* @param string value 'On' or 'Off'.
*/
function wpbc_admin_ui__full_screen__set_cookie(value) {
var max_age = 60 * 60 * 24 * 365;
document.cookie = 'wpbc_admin_full_screen=' + encodeURIComponent(value) + '; path=/; max-age=' + max_age + '; SameSite=Lax';
}
/**
* Apply Full Screen mode from a user click.
*
* @param HTMLElement el Clicked control.
* @param bool is_save_user_state Whether to save user preference.
*/
function wpbc_admin_ui__full_screen__do_on(el, is_save_user_state) {
jQuery('body').addClass('wpbc_admin_full_screen');
wpbc_check_full_screen_mode();
if (is_save_user_state) {
wpbc_admin_ui__full_screen__set_cookie('On');
if ('function' === typeof wpbc_save_custom_user_data_from_element) {
wpbc_save_custom_user_data_from_element(el);
}
}
}
/**
* Exit Full Screen mode from a user click.
*
* @param HTMLElement el Clicked control.
* @param bool is_save_user_state Whether to save user preference.
*/
function wpbc_admin_ui__full_screen__do_off(el, is_save_user_state) {
jQuery('body').removeClass('wpbc_admin_full_screen');
wpbc_check_full_screen_mode();
if (is_save_user_state) {
wpbc_admin_ui__full_screen__set_cookie('Off');
if ('function' === typeof wpbc_save_custom_user_data_from_element) {
wpbc_save_custom_user_data_from_element(el);
}
}
}
/**
* Check Full screen mode, by removing top tab
*/
function wpbc_check_full_screen_mode() {
if (jQuery('body').hasClass('wpbc_admin_full_screen')) {
jQuery('html').removeClass('wp-toolbar');
} else {
jQuery('html').addClass('wp-toolbar');
}
wpbc_check_buttons_max_min_in_full_screen_mode();
}
function wpbc_check_buttons_max_min_in_full_screen_mode() {
if (jQuery('body').hasClass('wpbc_admin_full_screen')) {
jQuery('.wpbc_ui__top_nav__btn_full_screen').addClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_normal_screen').removeClass('wpbc_ui__hide');
} else {
jQuery('.wpbc_ui__top_nav__btn_full_screen').removeClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_normal_screen').addClass('wpbc_ui__hide');
}
}
jQuery(document).ready(function () {
wpbc_check_full_screen_mode();
});
/**
* Checkbox Selection functions for Listing.
*/
/**
* Selections of several checkboxes like in gMail with shift :)
* Need to have this structure:
* .wpbc_selectable_table
* .wpbc_selectable_head
* .check-column
* :checkbox
* .wpbc_selectable_body
* .wpbc_row
* .check-column
* :checkbox
* .wpbc_selectable_foot
* .check-column
* :checkbox
*/
function wpbc_define_gmail_checkbox_selection($) {
var checks,
first,
last,
checked,
sliced,
lastClicked = false;
// Check all checkboxes.
$('.wpbc_selectable_body').find('.check-column').find(':checkbox').on('click', function (e) {
if ('undefined' == e.shiftKey) {
return true;
}
if (e.shiftKey) {
if (!lastClicked) {
return true;
}
checks = $(lastClicked).closest('.wpbc_selectable_body').find(':checkbox').filter(':visible:enabled');
first = checks.index(lastClicked);
last = checks.index(this);
checked = $(this).prop('checked');
if (0 < first && 0 < last && first != last) {
sliced = last > first ? checks.slice(first, last) : checks.slice(last, first);
sliced.prop('checked', function () {
if ($(this).closest('.wpbc_row').is(':visible')) {
return checked;
}
return false;
}).trigger('change');
}
}
lastClicked = this;
// toggle "check all" checkboxes.
var unchecked = $(this).closest('.wpbc_selectable_body').find(':checkbox').filter(':visible:enabled').not(':checked');
$(this).closest('.wpbc_selectable_table').children('.wpbc_selectable_head, .wpbc_selectable_foot').find(':checkbox').prop('checked', function () {
return 0 === unchecked.length;
}).trigger('change');
return true;
});
// Head || Foot clicking to select / deselect ALL.
$('.wpbc_selectable_head, .wpbc_selectable_foot').find('.check-column :checkbox').on('click', function (event) {
var $this = $(this),
$table = $this.closest('.wpbc_selectable_table'),
controlChecked = $this.prop('checked'),
toggle = event.shiftKey || $this.data('wp-toggle');
$table.children('.wpbc_selectable_body').filter(':visible').find('.check-column').find(':checkbox').prop('checked', function () {
if ($(this).is(':hidden,:disabled')) {
return false;
}
if (toggle) {
return !$(this).prop('checked');
} else if (controlChecked) {
return true;
}
return false;
}).trigger('change');
$table.children('.wpbc_selectable_head, .wpbc_selectable_foot').filter(':visible').find('.check-column').find(':checkbox').prop('checked', function () {
if (toggle) {
return false;
} else if (controlChecked) {
return true;
}
return false;
});
});
// Visually show selected border.
$('.wpbc_selectable_body').find('.check-column :checkbox').on('change', function (event) {
if (jQuery(this).is(':checked')) {
jQuery(this).closest('.wpbc_list_row').addClass('row_selected_color');
} else {
jQuery(this).closest('.wpbc_list_row').removeClass('row_selected_color');
}
// Disable text selection while pressing 'shift'.
document.getSelection().removeAllRanges();
// Show or hide buttons on Actions toolbar at Booking Listing page, if we have some selected bookings.
wpbc_show_hide_action_buttons_for_selected_bookings();
});
wpbc_show_hide_action_buttons_for_selected_bookings();
}
/**
* Get ID array of selected elements
*/
function wpbc_get_selected_row_id() {
var $table = jQuery('.wpbc__wrap__booking_listing .wpbc_selectable_table');
var checkboxes = $table.children('.wpbc_selectable_body').filter(':visible').find('.check-column').find(':checkbox');
var selected_id = [];
jQuery.each(checkboxes, function (key, checkbox) {
if (jQuery(checkbox).is(':checked')) {
var element_id = wpbc_get_row_id_from_element(checkbox);
selected_id.push(element_id);
}
});
return selected_id;
}
/**
* Get ID of row, based on clciked element
*
* @param this_inbound_element - ususlly this
* @returns {number}
*/
function wpbc_get_row_id_from_element(this_inbound_element) {
var element_id = jQuery(this_inbound_element).closest('.wpbc_listing_usual_row').attr('id');
element_id = parseInt(element_id.replace('row_id_', ''));
return element_id;
}
/**
* == Booking Listing == Show or hide buttons on Actions toolbar at page, if we have some selected bookings.
*/
function wpbc_show_hide_action_buttons_for_selected_bookings() {
var selected_rows_arr = wpbc_get_selected_row_id();
if (selected_rows_arr.length > 0) {
jQuery('.hide_button_if_no_selection').show();
} else {
jQuery('.hide_button_if_no_selection').hide();
}
}
"use strict";
// =====================================================================================================================
// == Left Bar - expand / colapse functions ==
// =====================================================================================================================
/**
* Save user's preferred left sidebar mode.
*
* @param string mode
*/
function wpbc_admin_ui__sidebar_left__save_mode(mode) {
var allowed_modes = ['min', 'compact', 'max'];
if (allowed_modes.indexOf(mode) === -1) {
return;
}
var $saver = jQuery('#wpbc_left_sidebar_view_mode_saver');
if (!$saver.length) {
return;
}
if ('function' !== typeof wpbc_save_custom_user_data_from_element) {
return;
}
$saver.data('wpbc-u-save-value', mode);
$saver.attr('data-wpbc-u-save-value', mode);
wpbc_save_custom_user_data_from_element($saver.get(0));
}
/**
* Expand Vertical Left Bar.
*
* @param bool is_save_user_state Save this mode as user's preference.
*/
function wpbc_admin_ui__sidebar_left__do_max(is_save_user_state) {
jQuery('.wpbc_settings_page_wrapper').removeClass('min max compact none');
jQuery('.wpbc_settings_page_wrapper').addClass('max');
jQuery('.wpbc_ui__top_nav__btn_open_left_vertical_nav').addClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_left_vertical_nav').removeClass('wpbc_ui__hide');
jQuery('.wp-admin').removeClass('wpbc_page_wrapper_left_min wpbc_page_wrapper_left_max wpbc_page_wrapper_left_compact wpbc_page_wrapper_left_none');
jQuery('.wp-admin').addClass('wpbc_page_wrapper_left_max');
if (is_save_user_state) {
wpbc_admin_ui__sidebar_left__save_mode('max');
}
}
/**
* Hide Vertical Left Bar.
*
* @param bool is_save_user_state Save this mode as user's preference.
*/
function wpbc_admin_ui__sidebar_left__do_min(is_save_user_state) {
jQuery('.wpbc_settings_page_wrapper').removeClass('min max compact none');
jQuery('.wpbc_settings_page_wrapper').addClass('min');
jQuery('.wpbc_ui__top_nav__btn_open_left_vertical_nav').removeClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_left_vertical_nav').addClass('wpbc_ui__hide');
jQuery('.wp-admin').removeClass('wpbc_page_wrapper_left_min wpbc_page_wrapper_left_max wpbc_page_wrapper_left_compact wpbc_page_wrapper_left_none');
jQuery('.wp-admin').addClass('wpbc_page_wrapper_left_min');
if (is_save_user_state) {
wpbc_admin_ui__sidebar_left__save_mode('min');
}
}
/**
* Colapse Vertical Left Bar.
*
* @param bool is_save_user_state Save this mode as user's preference.
*/
function wpbc_admin_ui__sidebar_left__do_compact(is_save_user_state) {
jQuery('.wpbc_settings_page_wrapper').removeClass('min max compact none');
jQuery('.wpbc_settings_page_wrapper').addClass('compact');
jQuery('.wpbc_ui__top_nav__btn_open_left_vertical_nav').removeClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_left_vertical_nav').addClass('wpbc_ui__hide');
jQuery('.wp-admin').removeClass('wpbc_page_wrapper_left_min wpbc_page_wrapper_left_max wpbc_page_wrapper_left_compact wpbc_page_wrapper_left_none');
jQuery('.wp-admin').addClass('wpbc_page_wrapper_left_compact');
if (is_save_user_state) {
wpbc_admin_ui__sidebar_left__save_mode('compact');
}
}
/**
* Completely Hide Vertical Left Bar.
*/
function wpbc_admin_ui__sidebar_left__do_hide() {
jQuery('.wpbc_settings_page_wrapper').removeClass('min max compact none');
jQuery('.wpbc_settings_page_wrapper').addClass('none');
jQuery('.wpbc_ui__top_nav__btn_open_left_vertical_nav').removeClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_left_vertical_nav').addClass('wpbc_ui__hide');
// Hide top "Menu" button with divider.
jQuery('.wpbc_ui__top_nav__btn_show_left_vertical_nav,.wpbc_ui__top_nav__btn_show_left_vertical_nav_divider').addClass('wpbc_ui__hide');
jQuery('.wp-admin').removeClass('wpbc_page_wrapper_left_min wpbc_page_wrapper_left_max wpbc_page_wrapper_left_compact wpbc_page_wrapper_left_none');
jQuery('.wp-admin').addClass('wpbc_page_wrapper_left_none');
}
/**
* Action on click "Go Back" - show root menu
* or some other section in left sidebar.
*
* @param string menu_to_show - menu slug.
*/
function wpbc_admin_ui__sidebar_left__show_section(menu_to_show) {
jQuery('.wpbc_ui_el__vert_left_bar__section').addClass('wpbc_ui__hide');
jQuery('.wpbc_ui_el__vert_left_bar__section_' + menu_to_show).removeClass('wpbc_ui__hide');
}
// =====================================================================================================================
// == Right Side Bar - expand / colapse functions ==
// =====================================================================================================================
/**
* Expand Vertical Right Bar.
*/
function wpbc_admin_ui__sidebar_right__do_max() {
jQuery('.wpbc_settings_page_wrapper').removeClass('min_right max_right compact_right none_right');
jQuery('.wpbc_settings_page_wrapper').addClass('max_right');
jQuery('.wpbc_ui__top_nav__btn_open_right_vertical_nav').addClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_right_vertical_nav').removeClass('wpbc_ui__hide');
}
/**
* Hide Vertical Right Bar.
*/
function wpbc_admin_ui__sidebar_right__do_min() {
jQuery('.wpbc_settings_page_wrapper').removeClass('min_right max_right compact_right none_right');
jQuery('.wpbc_settings_page_wrapper').addClass('min_right');
jQuery('.wpbc_ui__top_nav__btn_open_right_vertical_nav').removeClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_right_vertical_nav').addClass('wpbc_ui__hide');
}
/**
* Colapse Vertical Right Bar.
*/
function wpbc_admin_ui__sidebar_right__do_compact() {
jQuery('.wpbc_settings_page_wrapper').removeClass('min_right max_right compact_right none_right');
jQuery('.wpbc_settings_page_wrapper').addClass('compact_right');
jQuery('.wpbc_ui__top_nav__btn_open_right_vertical_nav').removeClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_right_vertical_nav').addClass('wpbc_ui__hide');
}
/**
* Completely Hide Vertical Right Bar.
*/
function wpbc_admin_ui__sidebar_right__do_hide() {
jQuery('.wpbc_settings_page_wrapper').removeClass('min_right max_right compact_right none_right');
jQuery('.wpbc_settings_page_wrapper').addClass('none_right');
jQuery('.wpbc_ui__top_nav__btn_open_right_vertical_nav').removeClass('wpbc_ui__hide');
jQuery('.wpbc_ui__top_nav__btn_hide_right_vertical_nav').addClass('wpbc_ui__hide');
// Hide top "Menu" button with divider.
jQuery('.wpbc_ui__top_nav__btn_show_right_vertical_nav,.wpbc_ui__top_nav__btn_show_right_vertical_nav_divider').addClass('wpbc_ui__hide');
}
/**
* Action on click "Go Back" - show root menu
* or some other section in right sidebar.
*
* @param string menu_to_show - menu slug.
*/
function wpbc_admin_ui__sidebar_right__show_section(menu_to_show) {
jQuery('.wpbc_ui_el__vert_right_bar__section').addClass('wpbc_ui__hide');
jQuery('.wpbc_ui_el__vert_right_bar__section_' + menu_to_show).removeClass('wpbc_ui__hide');
}
// =====================================================================================================================
// == End Right Side Bar section ==
// =====================================================================================================================
/**
* Get anchor(s) array from URL.
* Doc: https://developer.mozilla.org/en-US/docs/Web/API/Location
*
* @returns {*[]}
*/
function wpbc_url_get_anchors_arr() {
var hashes = window.location.hash.replace('%23', '#');
var hashes_arr = hashes.split('#');
var result = [];
var hashes_arr_length = hashes_arr.length;
for (var i = 0; i < hashes_arr_length; i++) {
if (hashes_arr[i].length > 0) {
result.push(hashes_arr[i]);
}
}
return result;
}
/**
* Auto Expand Settings section based on URL anchor, after page loaded.
*/
jQuery(document).ready(function () {
wpbc_admin_ui__redirect_legacy_general_availability_url();
});
jQuery(document).ready(function () {
wpbc_admin_ui__do_expand_section();
setTimeout('wpbc_admin_ui__do_expand_section', 10);
});
jQuery(document).ready(function () {
wpbc_admin_ui__do_expand_section();
setTimeout('wpbc_admin_ui__do_expand_section', 150);
});
/**
* Redirect old Settings > Availability anchors to the dedicated General Availability page.
*/
function wpbc_admin_ui__redirect_legacy_general_availability_url() {
if (window.location.href.indexOf('page=wpbc-settings') > -1 && (window.location.hash.indexOf('wpbc_general_settings_availability_metabox') > -1 || window.location.hash.indexOf('wpbc_general_settings_availability_tab') > -1)) {
window.location.replace(window.location.href.split('?')[0] + '?page=wpbc-availability&tab=general_availability');
}
}
/**
* Expand section in General Settings page and select Menu item.
*/
function wpbc_admin_ui__do_expand_section() {
// window.location.hash = #section_id / doc: https://developer.mozilla.org/en-US/docs/Web/API/Location .
var anchors_arr = wpbc_url_get_anchors_arr();
var anchors_arr_length = anchors_arr.length;
if (anchors_arr_length > 0) {
var one_anchor_prop_value = anchors_arr[0].split('do_expand__');
if (one_anchor_prop_value.length > 1) {
// 'wpbc_general_settings_calendar_metabox'
var section_to_show = one_anchor_prop_value[1];
var section_id_to_show = '#' + section_to_show;
// -- Remove selected background in all left menu items ---------------------------------------------------
jQuery('.wpbc_ui_el__vert_nav_item ').removeClass('active');
// Set left menu selected.
jQuery('.do_expand__' + section_to_show + '_link').addClass('active');
var selected_title = jQuery('.do_expand__' + section_to_show + '_link a .wpbc_ui_el__vert_nav_title ').text();
// Expand section, if it colapsed.
if (!jQuery('.do_expand__' + section_to_show + '_link').parents('.wpbc_ui_el__level__folder').hasClass('expanded')) {
jQuery('.wpbc_ui_el__level__folder').removeClass('expanded');
jQuery('.do_expand__' + section_to_show + '_link').parents('.wpbc_ui_el__level__folder').addClass('expanded');
}
// -- Expand section ---------------------------------------------------------------------------------------
var container_to_hide_class = '.postbox';
// Hide sections '.postbox' in admin page and show specific one.
jQuery('.wpbc_admin_page ' + container_to_hide_class).hide();
jQuery('.wpbc_container_always_hide__on_left_nav_click').hide();
jQuery(section_id_to_show).show();
// Show all other sections, if provided in URL: ..?page=wpbc-settings#do_expand__wpbc_general_settings_capacity_metabox#wpbc_general_settings_capacity_upgrade_metabox .
for (let i = 1; i < anchors_arr_length; i++) {
jQuery('#' + anchors_arr[i]).show();
}
if (false) {
var targetOffset = wpbc_scroll_to(section_id_to_show);
}
// -- Set Value to Input about selected Nav element --------------------------------------------------------------- // FixIn: 9.8.6.1.
var section_id_tab = section_id_to_show.substring(0, section_id_to_show.length - 8) + '_tab';
if (container_to_hide_class == section_id_to_show) {
section_id_tab = '#wpbc_general_settings_all_tab';
}
if ('#wpbc_general_settings_capacity_metabox,#wpbc_general_settings_capacity_upgrade_metabox' == section_id_to_show) {
section_id_tab = '#wpbc_general_settings_capacity_tab';
}
jQuery('#form_visible_section').val(section_id_tab);
}
// Like blinking some elements.
wpbc_admin_ui__do__anchor__another_actions();
}
}
function wpbc_admin_ui__is_in_mobile_screen_size() {
return wpbc_admin_ui__is_in_this_screen_size(605);
}
function wpbc_admin_ui__is_in_this_screen_size(size) {
return window.screen.width <= size;
}
/**
* Open settings page | Expand section | Select Menu item.
*/
function wpbc_admin_ui__do__open_url__expand_section(url, section_id) {
// window.location.href = url + '&do_expand=' + section_id + '#do_expand__' + section_id; //.
window.location.href = url + '#do_expand__' + section_id;
if (wpbc_admin_ui__is_in_mobile_screen_size()) {
wpbc_admin_ui__sidebar_left__do_min();
}
wpbc_admin_ui__do_expand_section();
}
/**
* Check for Other actions: Like blinking some elements in settings page. E.g. Days selection or change-over days.
*/
function wpbc_admin_ui__do__anchor__another_actions() {
var anchors_arr = wpbc_url_get_anchors_arr();
var anchors_arr_length = anchors_arr.length;
// Other actions: Like blinking some elements.
for (var i = 0; i < anchors_arr_length; i++) {
var this_anchor = anchors_arr[i];
var this_anchor_prop_value = this_anchor.split('do_other_actions__');
if (this_anchor_prop_value.length > 1) {
var section_action = this_anchor_prop_value[1];
switch (section_action) {
case 'blink_day_selections':
// wpbc_ui_settings__panel__click( '#wpbc_general_settings_calendar_tab a', '#wpbc_general_settings_calendar_metabox', 'Days Selection' );.
wpbc_blink_element('.wpbc_tr_set_gen_booking_type_of_day_selections', 4, 350);
wpbc_scroll_to('.wpbc_tr_set_gen_booking_type_of_day_selections');
break;
case 'blink_change_over_days':
// wpbc_ui_settings__panel__click( '#wpbc_general_settings_calendar_tab a', '#wpbc_general_settings_calendar_metabox', 'Changeover Days' );.
wpbc_blink_element('.wpbc_tr_set_gen_booking_range_selection_time_is_active', 4, 350);
wpbc_scroll_to('.wpbc_tr_set_gen_booking_range_selection_time_is_active');
break;
case 'blink_captcha':
wpbc_blink_element('.wpbc_tr_set_gen_booking_is_use_captcha', 4, 350);
wpbc_scroll_to('.wpbc_tr_set_gen_booking_is_use_captcha');
break;
default:
}
}
}
}
/**
* Copy txt to clipbrd from Text fields.
*
* @param html_element_id - e.g. 'data_field'
* @returns {boolean}
*/
function wpbc_copy_text_to_clipbrd_from_element(html_element_id) {
// Get the text field.
var copyText = document.getElementById(html_element_id);
// Select the text field.
copyText.select();
copyText.setSelectionRange(0, 99999); // For mobile devices.
// Copy the text inside the text field.
var is_copied = wpbc_copy_text_to_clipbrd(copyText.value);
if (!is_copied) {
console.error('Oops, unable to copy', copyText.value);
}
return is_copied;
}
/**
* Copy txt to clipbrd.
*
* @param text
* @returns {boolean}
*/
function wpbc_copy_text_to_clipbrd(text) {
if (!navigator.clipboard) {
return wpbc_fallback_copy_text_to_clipbrd(text);
}
navigator.clipboard.writeText(text).then(function () {
// console.log( 'Async: Copying to clipboard was successful!' );.
return true;
}, function (err) {
// console.error( 'Async: Could not copy text: ', err );.
return false;
});
}
/**
* Copy txt to clipbrd - depricated method.
*
* @param text
* @returns {boolean}
*/
function wpbc_fallback_copy_text_to_clipbrd(text) {
// -----------------------------------------------------------------------------------------------------------------
// var textArea = document.createElement( "textarea" );
// textArea.value = text;
//
// // Avoid scrolling to bottom.
// textArea.style.top = "0";
// textArea.style.left = "0";
// textArea.style.position = "fixed";
// textArea.style.zIndex = "999999999";
// document.body.appendChild( textArea );
// textArea.focus();
// textArea.select();
// -----------------------------------------------------------------------------------------------------------------
// Now get it as HTML (original here https://stackoverflow.com/questions/34191780/javascript-copy-string-to-clipboard-as-text-html ).
// [1] - Create container for the HTML.
var container = document.createElement('div');
container.innerHTML = text;
// [2] - Hide element.
container.style.position = 'fixed';
container.style.pointerEvents = 'none';
container.style.opacity = 0;
// Detect all style sheets of the page.
var activeSheets = Array.prototype.slice.call(document.styleSheets).filter(function (sheet) {
return !sheet.disabled;
});
// [3] - Mount the container to the DOM to make `contentWindow` available.
document.body.appendChild(container);
// [4] - Copy to clipboard.
window.getSelection().removeAllRanges();
var range = document.createRange();
range.selectNode(container);
window.getSelection().addRange(range);
// -----------------------------------------------------------------------------------------------------------------
var result = false;
try {
result = document.execCommand('copy');
// console.log( 'Fallback: Copying text command was ' + msg ); //.
} catch (err) {
// console.error( 'Fallback: Oops, unable to copy', err ); //.
}
// document.body.removeChild( textArea ); //.
// [5.4] - Enable CSS.
var activeSheets_length = activeSheets.length;
for (var i = 0; i < activeSheets_length; i++) {
activeSheets[i].disabled = false;
}
// [6] - Remove the container
document.body.removeChild(container);
return result;
}
/**
* WPBC Collapsible Groups
*
* Universal, dependency-free controller for expanding/collapsing grouped sections in right-side panels (Inspector/Library/Form Settings, or any other WPBC page).
*
* === How to use it (quick) ? ===
*
* -- 1. Markup (independent mode: multiple open allowed) --
*
*
*
*
…
*
*
*
*
…
*
*
*
* -- 2. Exclusive/accordion mode (one open at a time) --
*
…
*
* -- 3. Auto-init --
* The script auto-initializes on DOMContentLoaded. No extra code needed.
*
* -- 4. Programmatic control (optional)
* const root = document.querySelector('#wpbc_bfb__inspector');
* const api = root.__wpbc_collapsible_instance; // set by auto-init
*
* api.open_by_heading('Validation'); // open by heading text
* api.open_by_index(0); // open the first group
*
* -- 5.Listen to events (e.g., to persist “open group” state) --
* root.addEventListener('wpbc:collapsible:open', (e) => { console.log( e.detail.group ); });
* root.addEventListener('wpbc:collapsible:close', (e) => { console.log( e.detail.group ); });
*
*
*
* Markup expectations (minimal):
*
*
*
*
...
*
* ... more ...
*
*
* Notes:
* - Add `is-open` to any section you want initially expanded.
* - Add `wpbc_collapsible--exclusive` to the container for "open one at a time" behavior.
* - Works with your existing BFB markup (classes used there are the defaults).
*
* Accessibility:
* - Sets aria-expanded on .group__header
* - Sets aria-hidden + [hidden] on .group__fields
* - ArrowUp/ArrowDown move focus between headers; Enter/Space toggles
*
* Events (bubbles from the ):
* - 'wpbc:collapsible:open' (detail: { group, root, instance })
* - 'wpbc:collapsible:close' (detail: { group, root, instance })
*
* Public API (instance methods):
* - init(), destroy(), refresh()
* - expand(group, [exclusive]), collapse(group), toggle(group)
* - open_by_index(index), open_by_heading(text)
* - is_exclusive(), is_open(group)
*
* @version 2025-08-26
* @since 2025-08-26
*/
// ---------------------------------------------------------------------------------------------------------------------
// == File /collapsible_groups.js == Time point: 2025-08-26 14:13
// ---------------------------------------------------------------------------------------------------------------------
(function (w, d) {
'use strict';
class WPBC_Collapsible_Groups {
/**
* Create a collapsible controller for a container.
*
* @param {HTMLElement|string} root_el
* The container element (or CSS selector) that wraps collapsible groups.
* The container usually has the class `.wpbc_collapsible`.
* @param {Object} [opts={}]
* @param {string} [opts.group_selector='.wpbc_ui__collapsible_group']
* Selector for each collapsible group inside the container.
* @param {string} [opts.header_selector='.group__header']
* Selector for the clickable header inside a group.
* @param {string} [opts.fields_selector='.group__fields']
* Selector for the content/panel element inside a group.
* @param {string} [opts.open_class='is-open']
* Class name that indicates the group is open.
* @param {boolean} [opts.exclusive=false]
* If true, only one group can be open at a time in this container.
*
* @constructor
* @since 2025-08-26
*/
constructor(root_el, opts = {}) {
this.root = typeof root_el === 'string' ? d.querySelector(root_el) : root_el;
this.opts = Object.assign({
group_selector: '.wpbc_ui__collapsible_group',
header_selector: '.group__header',
fields_selector: '.group__fields,.group__content',
open_class: 'is-open',
exclusive: false
}, opts);
// Bound handlers (for add/removeEventListener symmetry).
/** @private */
this._on_click = this._on_click.bind(this);
/** @private */
this._on_keydown = this._on_keydown.bind(this);
/** @type {HTMLElement[]} @private */
this._groups = [];
/** @type {MutationObserver|null} @private */
this._observer = null;
}
/**
* Initialize the controller: cache groups, attach listeners, set ARIA,
* and start observing DOM changes inside the container.
*
* @returns {WPBC_Collapsible_Groups} The instance (chainable).
* @listens click
* @listens keydown
* @since 2025-08-26
*/
init() {
if (!this.root) {
return this;
}
this._groups = Array.prototype.slice.call(this.root.querySelectorAll(this.opts.group_selector));
this.root.addEventListener('click', this._on_click, false);
this.root.addEventListener('keydown', this._on_keydown, false);
// Observe dynamic inserts/removals (Inspector re-renders).
this._observer = new MutationObserver(() => {
this.refresh();
});
this._observer.observe(this.root, {
childList: true,
subtree: true
});
this._sync_all_aria();
return this;
}
/**
* Tear down the controller: detach listeners, stop the observer,
* and drop internal references.
*
* @returns {void}
* @since 2025-08-26
*/
destroy() {
if (!this.root) {
return;
}
this.root.removeEventListener('click', this._on_click, false);
this.root.removeEventListener('keydown', this._on_keydown, false);
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
this._groups = [];
}
/**
* Re-scan the DOM for current groups and re-apply ARIA to all of them.
* Useful after dynamic (re)renders.
*
* @returns {void}
* @since 2025-08-26
*/
refresh() {
if (!this.root) {
return;
}
this._groups = Array.prototype.slice.call(this.root.querySelectorAll(this.opts.group_selector));
this._sync_all_aria();
}
/**
* Check whether the container is in exclusive (accordion) mode.
*
* Order of precedence:
* 1) Explicit option `opts.exclusive`
* 2) Container has class `.wpbc_collapsible--exclusive`
* 3) Container matches `[data-wpbc-accordion="exclusive"]`
*
* @returns {boolean} True if exclusive mode is active.
* @since 2025-08-26
*/
is_exclusive() {
return !!(this.opts.exclusive || this.root.classList.contains('wpbc_collapsible--exclusive') || this.root.matches('[data-wpbc-accordion="exclusive"]'));
}
/**
* Determine whether a specific group is open.
*
* @param {HTMLElement} group The group element to test.
* @returns {boolean} True if the group is currently open.
* @since 2025-08-26
*/
is_open(group) {
return group.classList.contains(this.opts.open_class);
}
/**
* Open a group. Honors exclusive mode by collapsing all sibling groups
* (queried from the live DOM at call-time).
*
* @param {HTMLElement} group The group element to open.
* @param {boolean} [exclusive]
* If provided, overrides container mode for this action only.
* @returns {void}
* @fires CustomEvent#wpbc:collapsible:open
* @since 2025-08-26
*/
expand(group, exclusive) {
if (!group) {
return;
}
const do_exclusive = typeof exclusive === 'boolean' ? exclusive : this.is_exclusive();
if (do_exclusive) {
// Always use the live DOM, not the cached list.
Array.prototype.forEach.call(this.root.querySelectorAll(this.opts.group_selector), g => {
if (g !== group) {
this._set_open(g, false);
}
});
}
this._set_open(group, true);
}
/**
* Close a group.
*
* @param {HTMLElement} group The group element to close.
* @returns {void}
* @fires CustomEvent#wpbc:collapsible:close
* @since 2025-08-26
*/
collapse(group) {
if (!group) {
return;
}
this._set_open(group, false);
}
/**
* Toggle a group's open/closed state.
*
* @param {HTMLElement} group The group element to toggle.
* @returns {void}
* @since 2025-08-26
*/
toggle(group) {
if (!group) {
return;
}
this[this.is_open(group) ? 'collapse' : 'expand'](group);
}
/**
* Open a group by its index within the container (0-based).
*
* @param {number} index Zero-based index of the group.
* @returns {void}
* @since 2025-08-26
*/
open_by_index(index) {
const group = this._groups[index];
if (group) {
this.expand(group);
}
}
/**
* Open a group by matching text contained within the
inside the header.
* The comparison is case-insensitive and substring-based.
*
* @param {string} text Text to match against the heading contents.
* @returns {void}
* @since 2025-08-26
*/
open_by_heading(text) {
if (!text) {
return;
}
const t = String(text).toLowerCase();
const match = this._groups.find(g => {
const h = g.querySelector(this.opts.header_selector + ' h3');
return h && h.textContent.toLowerCase().indexOf(t) !== -1;
});
if (match) {
this.expand(match);
}
}
// -------------------------------------------------------------------------------------------------------------
// Internal
// -------------------------------------------------------------------------------------------------------------
/**
* Delegated click handler for headers.
*
* @private
* @param {MouseEvent} ev The click event.
* @returns {void}
* @since 2025-08-26
*/
_on_click(ev) {
const btn = ev.target.closest(this.opts.header_selector);
if (!btn || !this.root.contains(btn)) {
return;
}
ev.preventDefault();
ev.stopPropagation();
const group = btn.closest(this.opts.group_selector);
if (group) {
this.toggle(group);
}
}
/**
* Keyboard handler for header interactions and roving focus:
* - Enter/Space toggles the active group.
* - ArrowUp/ArrowDown moves focus between group headers.
*
* @private
* @param {KeyboardEvent} ev The keyboard event.
* @returns {void}
* @since 2025-08-26
*/
_on_keydown(ev) {
const btn = ev.target.closest(this.opts.header_selector);
if (!btn) {
return;
}
const key = ev.key;
// Toggle on Enter / Space.
if (key === 'Enter' || key === ' ') {
ev.preventDefault();
const group = btn.closest(this.opts.group_selector);
if (group) {
this.toggle(group);
}
return;
}
// Move focus with ArrowUp/ArrowDown between headers in this container.
if (key === 'ArrowUp' || key === 'ArrowDown') {
ev.preventDefault();
const headers = Array.prototype.map.call(this.root.querySelectorAll(this.opts.group_selector), g => g.querySelector(this.opts.header_selector)).filter(Boolean);
const idx = headers.indexOf(btn);
if (idx !== -1) {
const next_idx = key === 'ArrowDown' ? Math.min(headers.length - 1, idx + 1) : Math.max(0, idx - 1);
headers[next_idx].focus();
}
}
}
/**
* Apply ARIA synchronization to all known groups based on their open state.
*
* @private
* @returns {void}
* @since 2025-08-26
*/
_sync_all_aria() {
this._groups.forEach(g => this._sync_group_aria(g));
}
/**
* Sync ARIA attributes and visibility on a single group.
*
* @private
* @param {HTMLElement} group The group element to sync.
* @returns {void}
* @since 2025-08-26
*/
_sync_group_aria(group) {
const is_open = this.is_open(group);
const header = group.querySelector(this.opts.header_selector);
// Only direct children that match.
const panels = Array.prototype.filter.call(group.children, el => el.matches(this.opts.fields_selector));
// Header ARIA.
if (header) {
header.setAttribute('role', 'button');
header.setAttribute('aria-expanded', is_open ? 'true' : 'false');
if (panels.length) {
// Ensure each panel has an id; then wire aria-controls with space-separated ids.
const ids = panels.map(p => {
if (!p.id) p.id = this._generate_id('wpbc_collapsible_panel');
return p.id;
});
header.setAttribute('aria-controls', ids.join(' '));
}
}
// (3) Panels ARIA + visibility.
panels.forEach(p => {
p.hidden = !is_open; // actual visibility.
p.setAttribute('aria-hidden', is_open ? 'false' : 'true'); // ARIA.
});
}
/**
* Internal state change: set a group's open/closed state, sync ARIA,
* manage focus on collapse, and emit a custom event.
*
* @private
* @param {HTMLElement} group The group element to mutate.
* @param {boolean} open Whether the group should be open.
* @returns {void}
* @fires CustomEvent#wpbc:collapsible:open
* @fires CustomEvent#wpbc:collapsible:close
* @since 2025-08-26
*/
_set_open(group, open) {
if (!open && group.contains(document.activeElement)) {
const header = group.querySelector(this.opts.header_selector);
header && header.focus();
}
group.classList.toggle(this.opts.open_class, open);
this._sync_group_aria(group);
const ev_name = open ? 'wpbc:collapsible:open' : 'wpbc:collapsible:close';
group.dispatchEvent(new CustomEvent(ev_name, {
bubbles: true,
detail: {
group,
root: this.root,
instance: this
}
}));
}
/**
* Generate a unique DOM id with the specified prefix.
*
* @private
* @param {string} prefix The id prefix to use.
* @returns {string} A unique element id not present in the document.
* @since 2025-08-26
*/
_generate_id(prefix) {
let i = 1;
let id;
do {
id = prefix + '_' + i++;
} while (d.getElementById(id));
return id;
}
}
/**
* Auto-initialize collapsible controllers on the page.
* Finds top-level `.wpbc_collapsible` containers (ignoring nested ones),
* and instantiates {@link WPBC_Collapsible_Groups} on each.
*
* @function WPBC_Collapsible_AutoInit
* @returns {void}
* @since 2025-08-26
* @example
* // Runs automatically on DOMContentLoaded; can also be called manually:
* WPBC_Collapsible_AutoInit();
*/
function wpbc_collapsible__auto_init() {
var ROOT = '.wpbc_collapsible';
var nodes = Array.prototype.slice.call(d.querySelectorAll(ROOT)).filter(function (n) {
return !n.parentElement || !n.parentElement.closest(ROOT);
});
nodes.forEach(function (node) {
if (node.__wpbc_collapsible_instance) {
return;
}
var exclusive = node.classList.contains('wpbc_collapsible--exclusive') || node.matches('[data-wpbc-accordion="exclusive"]');
node.__wpbc_collapsible_instance = new WPBC_Collapsible_Groups(node, {
exclusive
}).init();
});
}
// Export to global for manual control if needed.
w.WPBC_Collapsible_Groups = WPBC_Collapsible_Groups;
w.WPBC_Collapsible_AutoInit = wpbc_collapsible__auto_init;
// DOM-ready auto init.
if (d.readyState === 'loading') {
d.addEventListener('DOMContentLoaded', wpbc_collapsible__auto_init, {
once: true
});
} else {
wpbc_collapsible__auto_init();
}
})(window, document);
/* globals window, document */
/**
* WPBC Slider Length Groups
*
* Universal, dependency-free controller that keeps a "length" control in sync:
* - number input (data-wpbc_slider_len_value)
* - unit select (data-wpbc_slider_len_unit)
* - range slider (data-wpbc_slider_len_range)
* - writer input (data-wpbc_slider_len_writer) [optional but recommended]
*
* The "writer" stores the combined value like: "100%", "420px", "12.5rem".
* When number/unit/slider change -> writer updates and emits 'input' (bubbles).
* When writer is changed externally (apply-from-JSON, etc) -> UI updates.
*
* Markup expectations (minimal):
*
*
*
*
*
*
*
* Performance notes:
* - MutationObserver is DISABLED by default (prevents performance issues).
* - If your UI re-renders and inserts new groups dynamically, call:
* WPBC_Slider_Len_AutoInit(); OR instance.refresh();
* Or enable observer via: new WPBC_Slider_Len_Groups(root, { enable_observer:true }).init();
*
* Public API (instance methods):
* - init(), destroy(), refresh()
*
* @version 2026-01-25
* @since 2026-01-25
* @file ../includes/__js/admin/slider_groups/wpbc_len_groups.js
*/
(function (w, d) {
'use strict';
// -------------------------------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------------------------------
function clamp_num(v, min, max) {
if (typeof min === 'number' && !isNaN(min)) v = Math.max(min, v);
if (typeof max === 'number' && !isNaN(max)) v = Math.min(max, v);
return v;
}
function parse_float(v) {
var n = parseFloat(v);
return isNaN(n) ? null : n;
}
function safe_json_parse(str) {
try {
return JSON.parse(str);
} catch (e) {
return null;
}
}
function parse_len_combined(raw, default_unit) {
var s = raw == null ? '' : String(raw).trim();
if (!s) return {
num: '',
unit: default_unit || '%'
};
var m = s.match(/^\s*([\-]?\d+(?:\.\d+)?)\s*([a-z%]*)\s*$/i);
if (!m) {
// If it's not parseable, treat as number and keep default unit.
return {
num: s,
unit: default_unit || '%'
};
}
var num = m[1] ? String(m[1]) : '';
var unit = m[2] ? String(m[2]) : '';
if (!unit) unit = default_unit || '%';
return {
num: num,
unit: unit
};
}
function build_combined(num, unit) {
if (num == null || String(num).trim() === '') return '';
return String(num) + String(unit || '');
}
function emit_input(el) {
if (!el) return;
el.dispatchEvent(new Event('input', {
bubbles: true
}));
}
// -------------------------------------------------------------------------------------------------
// Controller
// -------------------------------------------------------------------------------------------------
class WPBC_Slider_Len_Groups {
/**
* @param {HTMLElement|string} root_el Container (or selector). If omitted, uses document.
* @param {Object} [opts={}]
*/
constructor(root_el, opts) {
this.root = root_el ? typeof root_el === 'string' ? d.querySelector(root_el) : root_el : d;
this.opts = Object.assign({
// Strict selectors (NO backward compatibility).
group_selector: '.wpbc_slider_len_group',
value_selector: '[data-wpbc_slider_len_value]',
unit_selector: '[data-wpbc_slider_len_unit]',
range_selector: '[data-wpbc_slider_len_range]',
writer_selector: '[data-wpbc_slider_len_writer]',
default_unit: '%',
fallback_bounds: {
'px': {
min: 0,
max: 512,
step: 1
},
'%': {
min: 0,
max: 100,
step: 1
},
'rem': {
min: 0,
max: 10,
step: 0.1
},
'em': {
min: 0,
max: 10,
step: 0.1
}
},
// Disabled by default for performance.
enable_observer: false,
observer_debounce_ms: 150
}, opts || {});
this._on_input = this._on_input.bind(this);
this._on_change = this._on_change.bind(this);
this._bounds_cache = new WeakMap(); // group -> bounds_map_object
this._observer = null;
this._refresh_tmr = null;
}
init() {
if (!this.root) return this;
this.root.addEventListener('input', this._on_input, true);
this.root.addEventListener('change', this._on_change, true);
if (this.opts.enable_observer && w.MutationObserver) {
this._observer = new MutationObserver(() => {
this._debounced_refresh();
});
this._observer.observe(this.root === d ? d.documentElement : this.root, {
childList: true,
subtree: true
});
}
this.refresh();
return this;
}
destroy() {
if (!this.root) return;
this.root.removeEventListener('input', this._on_input, true);
this.root.removeEventListener('change', this._on_change, true);
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
if (this._refresh_tmr) {
clearTimeout(this._refresh_tmr);
this._refresh_tmr = null;
}
}
refresh() {
if (!this.root) return;
var scope = this.root === d ? d : this.root;
var groups = Array.prototype.slice.call(scope.querySelectorAll(this.opts.group_selector));
for (var i = 0; i < groups.length; i++) {
this._sync_group_from_writer(groups[i]);
this._apply_bounds_for_current_unit(groups[i]);
}
}
// -------------------------------------------------------------------------------------------------
// Internal
// -------------------------------------------------------------------------------------------------
_debounced_refresh() {
if (this._refresh_tmr) clearTimeout(this._refresh_tmr);
this._refresh_tmr = setTimeout(() => {
this._refresh_tmr = null;
this.refresh();
}, Number(this.opts.observer_debounce_ms) || 0);
}
_find_group(el) {
return el && el.closest ? el.closest(this.opts.group_selector) : null;
}
_get_parts(group) {
if (!group) return null;
return {
group: group,
num: group.querySelector(this.opts.value_selector),
unit: group.querySelector(this.opts.unit_selector),
range: group.querySelector(this.opts.range_selector),
writer: group.querySelector(this.opts.writer_selector)
};
}
_get_default_unit(group) {
var du = group && group.getAttribute ? group.getAttribute('data-wpbc_slider_len_default_unit') : '';
return du ? String(du) : this.opts.default_unit;
}
_get_bounds_map(group) {
if (!group) return null;
if (this._bounds_cache.has(group)) {
return this._bounds_cache.get(group);
}
var raw = group.getAttribute('data-wpbc_slider_len_bounds_map');
var map = raw ? safe_json_parse(raw) : null;
if (!map || typeof map !== 'object') map = null;
this._bounds_cache.set(group, map);
return map;
}
_get_bounds_for_unit(group, unit) {
var map = this._get_bounds_map(group);
if (map && unit && map[unit]) {
return map[unit];
}
return this.opts.fallback_bounds[unit] || this.opts.fallback_bounds['px'];
}
_apply_bounds(parts, bounds) {
if (!parts || !bounds) return;
var min = bounds.min != null ? Number(bounds.min) : null;
var max = bounds.max != null ? Number(bounds.max) : null;
var step = bounds.step != null ? Number(bounds.step) : null;
if (parts.range) {
if (!isNaN(min)) parts.range.min = String(min);
if (!isNaN(max)) parts.range.max = String(max);
if (!isNaN(step)) parts.range.step = String(step);
}
if (parts.num) {
if (!isNaN(min)) parts.num.min = String(min);
if (!isNaN(max)) parts.num.max = String(max);
if (!isNaN(step)) parts.num.step = String(step);
}
}
_apply_bounds_for_current_unit(group) {
var parts = this._get_parts(group);
if (!parts || !parts.unit) return;
var unit = parts.unit.value || this._get_default_unit(group);
var b = this._get_bounds_for_unit(group, unit);
this._apply_bounds(parts, b);
// Clamp current value to new bounds.
var v = parse_float(parts.num && parts.num.value ? parts.num.value : parts.range ? parts.range.value : '');
if (v == null) return;
var min = b && b.min != null ? Number(b.min) : null;
var max = b && b.max != null ? Number(b.max) : null;
v = clamp_num(v, isNaN(min) ? null : min, isNaN(max) ? null : max);
if (parts.num) parts.num.value = String(v);
if (parts.range) parts.range.value = String(v);
this._write_combined(parts, String(v), unit, /*emit*/false);
}
_write_combined(parts, num, unit, emit) {
if (!parts) return;
var combined = build_combined(num, unit);
if (parts.writer) {
// Avoid recursion: mark as internal write.
parts.writer.__wpbc_slider_len_internal = true;
parts.writer.value = combined;
if (emit) emit_input(parts.writer);
parts.writer.__wpbc_slider_len_internal = false;
} else if (parts.num) {
// If writer is missing, at least notify via number input.
if (emit) emit_input(parts.num);
}
}
_sync_group_from_writer(group) {
var parts = this._get_parts(group);
if (!parts || !parts.writer) return;
var raw = String(parts.writer.value || '').trim();
if (!raw) return;
var du = this._get_default_unit(group);
var p = parse_len_combined(raw, du);
if (parts.unit) parts.unit.value = p.unit;
if (parts.num) parts.num.value = p.num;
if (parts.range) parts.range.value = p.num;
}
_on_input(ev) {
var t = ev.target;
if (!t) return;
var group = this._find_group(t);
if (!group) return;
var parts = this._get_parts(group);
if (!parts) return;
// Writer changed externally -> update UI.
if (parts.writer && t === parts.writer) {
if (t.__wpbc_slider_len_internal) return;
this._sync_group_from_writer(group);
this._apply_bounds_for_current_unit(group);
return;
}
// Slider moved -> update number + writer.
if (t.matches && t.matches(this.opts.range_selector)) {
if (parts.num) parts.num.value = t.value;
var unit = parts.unit && parts.unit.value ? parts.unit.value : this._get_default_unit(group);
this._write_combined(parts, t.value, unit, /*emit*/true);
return;
}
// Number typed -> update slider + writer (clamp if slider has bounds).
if (t.matches && t.matches(this.opts.value_selector)) {
var v = parse_float(t.value);
if (v != null && parts.range) {
var rmin = Number(parts.range.min);
var rmax = Number(parts.range.max);
v = clamp_num(v, isNaN(rmin) ? null : rmin, isNaN(rmax) ? null : rmax);
parts.range.value = String(v);
if (String(v) !== t.value) t.value = String(v);
}
var unit2 = parts.unit && parts.unit.value ? parts.unit.value : this._get_default_unit(group);
this._write_combined(parts, t.value, unit2, /*emit*/true);
}
}
_on_change(ev) {
var t = ev.target;
if (!t) return;
var group = this._find_group(t);
if (!group) return;
var parts = this._get_parts(group);
if (!parts) return;
// Unit changed -> update bounds + writer.
if (t.matches && t.matches(this.opts.unit_selector)) {
this._apply_bounds_for_current_unit(group);
var num = parts.num ? parts.num.value : parts.range ? parts.range.value : '';
var unit = t.value || this._get_default_unit(group);
this._write_combined(parts, num, unit, /*emit*/true);
}
}
}
// -------------------------------------------------------------------------------------------------
// Auto-init
// -------------------------------------------------------------------------------------------------
function wpbc_slider_len_groups__auto_init() {
var ROOT = '.wpbc_slider_len_groups';
var nodes = Array.prototype.slice.call(d.querySelectorAll(ROOT)).filter(function (n) {
return !n.parentElement || !n.parentElement.closest(ROOT);
});
// If no explicit containers, install a single document-root instance.
if (!nodes.length) {
if (!d.__wpbc_slider_len_groups_global_instance) {
d.__wpbc_slider_len_groups_global_instance = new WPBC_Slider_Len_Groups(d).init();
}
return;
}
nodes.forEach(function (node) {
if (node.__wpbc_slider_len_groups_instance) return;
node.__wpbc_slider_len_groups_instance = new WPBC_Slider_Len_Groups(node).init();
});
}
// Export globals (manual control if needed).
w.WPBC_Slider_Len_Groups = WPBC_Slider_Len_Groups;
w.WPBC_Slider_Len_AutoInit = wpbc_slider_len_groups__auto_init;
// DOM-ready auto init.
if (d.readyState === 'loading') {
d.addEventListener('DOMContentLoaded', wpbc_slider_len_groups__auto_init, {
once: true
});
} else {
wpbc_slider_len_groups__auto_init();
}
})(window, document);
/* globals window, document */
/**
* WPBC Slider Range Groups
*
* Universal, dependency-free controller that keeps a "range + number" pair in sync:
* - number input (data-wpbc_slider_range_value)
* - range slider (data-wpbc_slider_range_range)
* - writer input (data-wpbc_slider_range_writer) [optional]
*
* If writer exists: number/slider update writer and emit 'input' on writer (bubbles).
* If writer is missing: emits 'input' on the number input.
* If writer changes externally: updates number/slider.
*
* Markup expectations (minimal):
*
*
*
*
*
*
*
* Performance notes:
* - MutationObserver is DISABLED by default.
* - If your UI re-renders and inserts new groups dynamically, call:
* WPBC_Slider_Range_AutoInit(); OR instance.refresh();
* Or enable observer via: new WPBC_Slider_Range_Groups(root, { enable_observer:true }).init();
*
* Public API (instance methods):
* - init(), destroy(), refresh()
*
* @version 2026-01-25
* @since 2026-01-25
* @file ../includes/__js/admin/slider_groups/wpbc_range_groups.js
*/
(function (w, d) {
'use strict';
// -------------------------------------------------------------------------------------------------
// Helpers
// -------------------------------------------------------------------------------------------------
function clamp_num(v, min, max) {
if (typeof min === 'number' && !isNaN(min)) v = Math.max(min, v);
if (typeof max === 'number' && !isNaN(max)) v = Math.min(max, v);
return v;
}
function parse_float(v) {
var n = parseFloat(v);
return isNaN(n) ? null : n;
}
function emit_input(el) {
if (!el) return;
el.dispatchEvent(new Event('input', {
bubbles: true
}));
}
// -------------------------------------------------------------------------------------------------
// Controller
// -------------------------------------------------------------------------------------------------
class WPBC_Slider_Range_Groups {
/**
* @param {HTMLElement|string} root_el Container (or selector). If omitted, uses document.
* @param {Object} [opts={}]
*/
constructor(root_el, opts) {
this.root = root_el ? typeof root_el === 'string' ? d.querySelector(root_el) : root_el : d;
this.opts = Object.assign({
// Strict selectors (NO backward compatibility).
group_selector: '.wpbc_slider_range_group',
value_selector: '[data-wpbc_slider_range_value]',
range_selector: '[data-wpbc_slider_range_range]',
writer_selector: '[data-wpbc_slider_range_writer]',
// Disabled by default for performance.
enable_observer: false,
observer_debounce_ms: 150
}, opts || {});
this._on_input = this._on_input.bind(this);
this._on_change = this._on_change.bind(this);
this._observer = null;
this._refresh_tmr = null;
}
init() {
if (!this.root) return this;
this.root.addEventListener('input', this._on_input, true);
this.root.addEventListener('change', this._on_change, true);
if (this.opts.enable_observer && w.MutationObserver) {
this._observer = new MutationObserver(() => {
this._debounced_refresh();
});
this._observer.observe(this.root === d ? d.documentElement : this.root, {
childList: true,
subtree: true
});
}
this.refresh();
return this;
}
destroy() {
if (!this.root) return;
this.root.removeEventListener('input', this._on_input, true);
this.root.removeEventListener('change', this._on_change, true);
if (this._observer) {
this._observer.disconnect();
this._observer = null;
}
if (this._refresh_tmr) {
clearTimeout(this._refresh_tmr);
this._refresh_tmr = null;
}
}
refresh() {
if (!this.root) return;
var scope = this.root === d ? d : this.root;
var groups = Array.prototype.slice.call(scope.querySelectorAll(this.opts.group_selector));
for (var i = 0; i < groups.length; i++) {
this._sync_from_writer(groups[i]);
this._clamp_to_range(groups[i]);
}
}
// -------------------------------------------------------------------------------------------------
// Internal
// -------------------------------------------------------------------------------------------------
_debounced_refresh() {
if (this._refresh_tmr) clearTimeout(this._refresh_tmr);
this._refresh_tmr = setTimeout(() => {
this._refresh_tmr = null;
this.refresh();
}, Number(this.opts.observer_debounce_ms) || 0);
}
_find_group(el) {
return el && el.closest ? el.closest(this.opts.group_selector) : null;
}
_get_parts(group) {
if (!group) return null;
return {
group: group,
num: group.querySelector(this.opts.value_selector),
range: group.querySelector(this.opts.range_selector),
writer: group.querySelector(this.opts.writer_selector)
};
}
_write(parts, value, emit) {
if (!parts) return;
if (parts.writer) {
parts.writer.__wpbc_slider_range_internal = true;
parts.writer.value = String(value);
if (emit) emit_input(parts.writer);
parts.writer.__wpbc_slider_range_internal = false;
} else if (parts.num) {
// If writer is missing, at least notify via number input.
if (emit) emit_input(parts.num);
}
}
_sync_from_writer(group) {
var parts = this._get_parts(group);
if (!parts || !parts.writer) return;
var raw = String(parts.writer.value || '').trim();
if (!raw) return;
if (parts.num) parts.num.value = raw;
if (parts.range) parts.range.value = raw;
}
_clamp_to_range(group) {
var parts = this._get_parts(group);
if (!parts || !parts.range || !parts.num) return;
var v = parse_float(parts.num.value);
if (v == null) return;
var min = Number(parts.range.min);
var max = Number(parts.range.max);
var vv = clamp_num(v, isNaN(min) ? null : min, isNaN(max) ? null : max);
if (String(vv) !== parts.num.value) parts.num.value = String(vv);
parts.range.value = String(vv);
}
_on_input(ev) {
var t = ev.target;
if (!t) return;
var group = this._find_group(t);
if (!group) return;
var parts = this._get_parts(group);
if (!parts) return;
// Writer changed externally -> update UI.
if (parts.writer && t === parts.writer) {
if (t.__wpbc_slider_range_internal) return;
this._sync_from_writer(group);
this._clamp_to_range(group);
return;
}
// Range moved -> update number + writer.
if (t.matches && t.matches(this.opts.range_selector)) {
if (parts.num) parts.num.value = t.value;
this._write(parts, t.value, /*emit*/true);
return;
}
// Number typed -> update range + writer (clamp by slider bounds).
if (t.matches && t.matches(this.opts.value_selector)) {
if (parts.range) {
var v = parse_float(t.value);
if (v != null) {
var min = Number(parts.range.min);
var max = Number(parts.range.max);
v = clamp_num(v, isNaN(min) ? null : min, isNaN(max) ? null : max);
parts.range.value = String(v);
if (String(v) !== t.value) t.value = String(v);
}
}
this._write(parts, t.value, /*emit*/true);
}
}
_on_change(ev) {
// No special "change" handling needed currently; kept for symmetry/future.
}
}
// -------------------------------------------------------------------------------------------------
// Auto-init
// -------------------------------------------------------------------------------------------------
function wpbc_slider_range_groups__auto_init() {
var ROOT = '.wpbc_slider_range_groups';
var nodes = Array.prototype.slice.call(d.querySelectorAll(ROOT)).filter(function (n) {
return !n.parentElement || !n.parentElement.closest(ROOT);
});
if (!nodes.length) {
if (!d.__wpbc_slider_range_groups_global_instance) {
d.__wpbc_slider_range_groups_global_instance = new WPBC_Slider_Range_Groups(d).init();
}
return;
}
nodes.forEach(function (node) {
if (node.__wpbc_slider_range_groups_instance) return;
node.__wpbc_slider_range_groups_instance = new WPBC_Slider_Range_Groups(node).init();
});
}
// Export globals.
w.WPBC_Slider_Range_Groups = WPBC_Slider_Range_Groups;
w.WPBC_Slider_Range_AutoInit = wpbc_slider_range_groups__auto_init;
if (d.readyState === 'loading') {
d.addEventListener('DOMContentLoaded', wpbc_slider_range_groups__auto_init, {
once: true
});
} else {
wpbc_slider_range_groups__auto_init();
}
})(window, document);
/**
* 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.
*
*