# contact-forms/trunk/assets/js/admin/tabs.js

Contact Forms by Cimatti, version trunk. 162 lines.

- Page: https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/admin/tabs.js
- Raw: https://pluginprobe.com/plugins/contact-forms/trunk/raw/assets/js/admin/tabs.js
- Modified: 2026-08-21T08:39:40+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/contact-forms/trunk/code/assets/js/admin/tabs.js#L10-L20`.

```javascript
/**
 * Contact Forms - Tab component for WordPress admin.
 *
 * Implements the W3C ARIA Tabs pattern with keyboard navigation.
 * Supports nesting (each instance scopes to its own direct tabs/panels).
 *
 * HTML structure:
 *   <div class="accua-tabs" data-default-tab="first-tab-id">
 *     <div class="accua-tabs__tablist" role="tablist">
 *       <button role="tab" data-tab="tab-id">Label</button>
 *       ...
 *     </div>
 *     <div role="tabpanel" data-tab="tab-id">
 *       ...content...
 *     </div>
 *     ...
 *   </div>
 *
 * Usage:
 *   $(selector).cfTabs();
 *   $(selector).cfTabs({ onSelect: function(tabId, $panel) { ... } });
 */
(function($) {
	'use strict';

	$.fn.cfTabs = function(options) {
		var defaults = {
			onSelect: null
		};
		var settings = $.extend({}, defaults, options);

		return this.each(function() {
			var $container = $(this);

			// Prevent double-init
			if ($container.hasClass('is-initialized')) {
				return;
			}

			// Scope: only direct tablist and panels (not nested ones)
			// Tablist may be a direct child or inside a header wrapper
			var $tablist = $container.children('[role="tablist"]');
			if (!$tablist.length) {
				$tablist = $container.children('.accua-tabs__header').children('[role="tablist"]');
			}
			var $tabs = $tablist.find('> [role="tab"]');
			var $panels = $container.children('[role="tabpanel"]');

			if (!$tabs.length || !$panels.length) {
				return;
			}

			// Wire up ARIA ids
			$tabs.each(function() {
				var $tab = $(this);
				var tabId = $tab.attr('data-tab');
				var tabElId = $container.attr('id') + '-tab-' + tabId;
				var panelElId = $container.attr('id') + '-panel-' + tabId;
				$tab.attr('id', tabElId);
				$tab.attr('aria-controls', panelElId);
				$panels.filter('[data-tab="' + tabId + '"]')
					.attr('id', panelElId)
					.attr('aria-labelledby', tabElId);
			});

			// Determine default tab
			var defaultTab = $container.attr('data-default-tab') || $tabs.first().attr('data-tab');

			function selectTab(tabId, focus) {
				// Deselect all
				$tabs.attr('aria-selected', 'false').attr('tabindex', '-1');
				$panels.attr('hidden', '');

				// Select target
				var $activeTab = $tabs.filter('[data-tab="' + tabId + '"]');
				var $activePanel = $panels.filter('[data-tab="' + tabId + '"]');
				$activeTab.attr('aria-selected', 'true').attr('tabindex', '0');
				$activePanel.removeAttr('hidden');

				if (focus) {
					$activeTab.trigger('focus');
				}

				if (typeof settings.onSelect === 'function') {
					settings.onSelect(tabId, $activePanel);
				}
			}

			// Click handler
			$tabs.on('click', function(e) {
				e.preventDefault();
				selectTab($(this).attr('data-tab'), false);
			});

			// Keyboard navigation (roving tabindex)
			$tabs.on('keydown', function(e) {
				var index = $tabs.index(this);
				var newIndex = null;

				switch (e.key) {
					case 'ArrowLeft':
					case 'ArrowUp':
						newIndex = index > 0 ? index - 1 : $tabs.length - 1;
						break;
					case 'ArrowRight':
					case 'ArrowDown':
						newIndex = index < $tabs.length - 1 ? index + 1 : 0;
						break;
					case 'Home':
						newIndex = 0;
						break;
					case 'End':
						newIndex = $tabs.length - 1;
						break;
				}

				if (newIndex !== null) {
					e.preventDefault();
					selectTab($tabs.eq(newIndex).attr('data-tab'), true);
				}
			});

			// Initialize: select default tab, mark container
			selectTab(defaultTab, false);
			$container.addClass('is-initialized');
		});
	};

	// Form editor initialization
	$(document).ready(function() {
		var $tabs = $('#accua_tabs');
		if (!$tabs.length) {
			return;
		}

		var $preview = $('#accua_form_preview_pane');
		var $saveBtn = $('#accua_form_save_settings_top');

		// Tabs that show the preview pane alongside
		var previewTabs = { fields: true, customise: true };

		$tabs.find('.hidden').removeClass('hidden');
		$tabs.cfTabs({
			onSelect: function(tabId) {
				// Show/hide preview (no DOM movement - avoids iframe reload)
				if (previewTabs[tabId]) {
					$preview.removeAttr('hidden');
				} else {
					$preview.attr('hidden', '');
				}
				// Hide save button on tokens tab (read-only reference)
				if (tabId === 'tokens') {
					$saveBtn.hide();
				} else {
					$saveBtn.show();
				}
			}
		});
	});

})(jQuery);

```
