PluginProbe
Contact Forms by Cimatti / trunk
Contact Forms by Cimatti vtrunk
2.3.6 2.3.5 2.3.0 2.2.32 2.2.4 2.2.0 2.1.2 2.1.1 trunk 1.0 1.1 1.2 1.2.1 1.3 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 All 62 releases
contact-forms / assets / js / admin / tabs.js

tabs.js in Contact Forms by Cimatti trunk, at assets/js/admin/tabs.js

162 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Contact Forms - Tab component for WordPress admin.
3 *
4 * Implements the W3C ARIA Tabs pattern with keyboard navigation.
5 * Supports nesting (each instance scopes to its own direct tabs/panels).
6 *
7 * HTML structure:
8 * <div class="accua-tabs" data-default-tab="first-tab-id">
9 * <div class="accua-tabs__tablist" role="tablist">
10 * <button role="tab" data-tab="tab-id">Label</button>
11 * ...
12 * </div>
13 * <div role="tabpanel" data-tab="tab-id">
14 * ...content...
15 * </div>
16 * ...
17 * </div>
18 *
19 * Usage:
20 * $(selector).cfTabs();
21 * $(selector).cfTabs({ onSelect: function(tabId, $panel) { ... } });
22 */
23 (function($) {
24 'use strict';
25
26 $.fn.cfTabs = function(options) {
27 var defaults = {
28 onSelect: null
29 };
30 var settings = $.extend({}, defaults, options);
31
32 return this.each(function() {
33 var $container = $(this);
34
35 // Prevent double-init
36 if ($container.hasClass('is-initialized')) {
37 return;
38 }
39
40 // Scope: only direct tablist and panels (not nested ones)
41 // Tablist may be a direct child or inside a header wrapper
42 var $tablist = $container.children('[role="tablist"]');
43 if (!$tablist.length) {
44 $tablist = $container.children('.accua-tabs__header').children('[role="tablist"]');
45 }
46 var $tabs = $tablist.find('> [role="tab"]');
47 var $panels = $container.children('[role="tabpanel"]');
48
49 if (!$tabs.length || !$panels.length) {
50 return;
51 }
52
53 // Wire up ARIA ids
54 $tabs.each(function() {
55 var $tab = $(this);
56 var tabId = $tab.attr('data-tab');
57 var tabElId = $container.attr('id') + '-tab-' + tabId;
58 var panelElId = $container.attr('id') + '-panel-' + tabId;
59 $tab.attr('id', tabElId);
60 $tab.attr('aria-controls', panelElId);
61 $panels.filter('[data-tab="' + tabId + '"]')
62 .attr('id', panelElId)
63 .attr('aria-labelledby', tabElId);
64 });
65
66 // Determine default tab
67 var defaultTab = $container.attr('data-default-tab') || $tabs.first().attr('data-tab');
68
69 function selectTab(tabId, focus) {
70 // Deselect all
71 $tabs.attr('aria-selected', 'false').attr('tabindex', '-1');
72 $panels.attr('hidden', '');
73
74 // Select target
75 var $activeTab = $tabs.filter('[data-tab="' + tabId + '"]');
76 var $activePanel = $panels.filter('[data-tab="' + tabId + '"]');
77 $activeTab.attr('aria-selected', 'true').attr('tabindex', '0');
78 $activePanel.removeAttr('hidden');
79
80 if (focus) {
81 $activeTab.trigger('focus');
82 }
83
84 if (typeof settings.onSelect === 'function') {
85 settings.onSelect(tabId, $activePanel);
86 }
87 }
88
89 // Click handler
90 $tabs.on('click', function(e) {
91 e.preventDefault();
92 selectTab($(this).attr('data-tab'), false);
93 });
94
95 // Keyboard navigation (roving tabindex)
96 $tabs.on('keydown', function(e) {
97 var index = $tabs.index(this);
98 var newIndex = null;
99
100 switch (e.key) {
101 case 'ArrowLeft':
102 case 'ArrowUp':
103 newIndex = index > 0 ? index - 1 : $tabs.length - 1;
104 break;
105 case 'ArrowRight':
106 case 'ArrowDown':
107 newIndex = index < $tabs.length - 1 ? index + 1 : 0;
108 break;
109 case 'Home':
110 newIndex = 0;
111 break;
112 case 'End':
113 newIndex = $tabs.length - 1;
114 break;
115 }
116
117 if (newIndex !== null) {
118 e.preventDefault();
119 selectTab($tabs.eq(newIndex).attr('data-tab'), true);
120 }
121 });
122
123 // Initialize: select default tab, mark container
124 selectTab(defaultTab, false);
125 $container.addClass('is-initialized');
126 });
127 };
128
129 // Form editor initialization
130 $(document).ready(function() {
131 var $tabs = $('#accua_tabs');
132 if (!$tabs.length) {
133 return;
134 }
135
136 var $preview = $('#accua_form_preview_pane');
137 var $saveBtn = $('#accua_form_save_settings_top');
138
139 // Tabs that show the preview pane alongside
140 var previewTabs = { fields: true, customise: true };
141
142 $tabs.find('.hidden').removeClass('hidden');
143 $tabs.cfTabs({
144 onSelect: function(tabId) {
145 // Show/hide preview (no DOM movement - avoids iframe reload)
146 if (previewTabs[tabId]) {
147 $preview.removeAttr('hidden');
148 } else {
149 $preview.attr('hidden', '');
150 }
151 // Hide save button on tokens tab (read-only reference)
152 if (tabId === 'tokens') {
153 $saveBtn.hide();
154 } else {
155 $saveBtn.show();
156 }
157 }
158 });
159 });
160
161 })(jQuery);
162