comparison core/misc/vertical-tabs.es6.js @ 4:a9cd425dd02b

Update, including to Drupal core 8.6.10
author Chris Cannam
date Thu, 28 Feb 2019 13:11:55 +0000
parents c75dbcec494b
children
comparison
equal deleted inserted replaced
3:307d7a7fd348 4:a9cd425dd02b
10 * are the important fields' values. 10 * are the important fields' values.
11 * 11 *
12 * @event summaryUpdated 12 * @event summaryUpdated
13 */ 13 */
14 14
15 (function ($, Drupal, drupalSettings) { 15 (function($, Drupal, drupalSettings) {
16 /** 16 /**
17 * Show the parent vertical tab pane of a targeted page fragment. 17 * Show the parent vertical tab pane of a targeted page fragment.
18 * 18 *
19 * In order to make sure a targeted element inside a vertical tab pane is 19 * In order to make sure a targeted element inside a vertical tab pane is
20 * visible on a hash change or fragment link click, show all parent panes. 20 * visible on a hash change or fragment link click, show all parent panes.
24 * @param {jQuery} $target 24 * @param {jQuery} $target
25 * The targeted node as a jQuery object. 25 * The targeted node as a jQuery object.
26 */ 26 */
27 const handleFragmentLinkClickOrHashChange = (e, $target) => { 27 const handleFragmentLinkClickOrHashChange = (e, $target) => {
28 $target.parents('.vertical-tabs__pane').each((index, pane) => { 28 $target.parents('.vertical-tabs__pane').each((index, pane) => {
29 $(pane).data('verticalTab').focus(); 29 $(pane)
30 .data('verticalTab')
31 .focus();
30 }); 32 });
31 }; 33 };
32 34
33 /** 35 /**
34 * This script transforms a set of details into a stack of vertical tabs. 36 * This script transforms a set of details into a stack of vertical tabs.
54 } 56 }
55 57
56 /** 58 /**
57 * Binds a listener to handle fragment link clicks and URL hash changes. 59 * Binds a listener to handle fragment link clicks and URL hash changes.
58 */ 60 */
59 $('body').once('vertical-tabs-fragments').on('formFragmentLinkClickOrHashChange.verticalTabs', handleFragmentLinkClickOrHashChange); 61 $('body')
60 62 .once('vertical-tabs-fragments')
61 $(context).find('[data-vertical-tabs-panes]').once('vertical-tabs').each(function () { 63 .on(
62 const $this = $(this).addClass('vertical-tabs__panes'); 64 'formFragmentLinkClickOrHashChange.verticalTabs',
63 const focusID = $this.find(':hidden.vertical-tabs__active-tab').val(); 65 handleFragmentLinkClickOrHashChange,
64 let tabFocus; 66 );
65 67
66 // Check if there are some details that can be converted to 68 $(context)
67 // vertical-tabs. 69 .find('[data-vertical-tabs-panes]')
68 const $details = $this.find('> details'); 70 .once('vertical-tabs')
69 if ($details.length === 0) { 71 .each(function() {
70 return; 72 const $this = $(this).addClass('vertical-tabs__panes');
71 } 73 const focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
72 74 let tabFocus;
73 // Create the tab column. 75
74 const tabList = $('<ul class="vertical-tabs__menu"></ul>'); 76 // Check if there are some details that can be converted to
75 $this.wrap('<div class="vertical-tabs clearfix"></div>').before(tabList); 77 // vertical-tabs.
76 78 const $details = $this.find('> details');
77 // Transform each details into a tab. 79 if ($details.length === 0) {
78 $details.each(function () { 80 return;
79 const $that = $(this); 81 }
80 const verticalTab = new Drupal.verticalTab({ 82
81 title: $that.find('> summary').text(), 83 // Create the tab column.
82 details: $that, 84 const tabList = $('<ul class="vertical-tabs__menu"></ul>');
85 $this
86 .wrap('<div class="vertical-tabs clearfix"></div>')
87 .before(tabList);
88
89 // Transform each details into a tab.
90 $details.each(function() {
91 const $that = $(this);
92 const verticalTab = new Drupal.verticalTab({
93 title: $that.find('> summary').text(),
94 details: $that,
95 });
96 tabList.append(verticalTab.item);
97 $that
98 .removeClass('collapsed')
99 // prop() can't be used on browsers not supporting details element,
100 // the style won't apply to them if prop() is used.
101 .attr('open', true)
102 .addClass('vertical-tabs__pane')
103 .data('verticalTab', verticalTab);
104 if (this.id === focusID) {
105 tabFocus = $that;
106 }
83 }); 107 });
84 tabList.append(verticalTab.item); 108
85 $that 109 $(tabList)
86 .removeClass('collapsed') 110 .find('> li')
87 // prop() can't be used on browsers not supporting details element, 111 .eq(0)
88 // the style won't apply to them if prop() is used. 112 .addClass('first');
89 .attr('open', true) 113 $(tabList)
90 .addClass('vertical-tabs__pane') 114 .find('> li')
91 .data('verticalTab', verticalTab); 115 .eq(-1)
92 if (this.id === focusID) { 116 .addClass('last');
93 tabFocus = $that; 117
118 if (!tabFocus) {
119 // If the current URL has a fragment and one of the tabs contains an
120 // element that matches the URL fragment, activate that tab.
121 const $locationHash = $this.find(window.location.hash);
122 if (window.location.hash && $locationHash.length) {
123 tabFocus = $locationHash.closest('.vertical-tabs__pane');
124 } else {
125 tabFocus = $this.find('> .vertical-tabs__pane').eq(0);
126 }
127 }
128 if (tabFocus.length) {
129 tabFocus.data('verticalTab').focus();
94 } 130 }
95 }); 131 });
96
97 $(tabList).find('> li').eq(0).addClass('first');
98 $(tabList).find('> li').eq(-1).addClass('last');
99
100 if (!tabFocus) {
101 // If the current URL has a fragment and one of the tabs contains an
102 // element that matches the URL fragment, activate that tab.
103 const $locationHash = $this.find(window.location.hash);
104 if (window.location.hash && $locationHash.length) {
105 tabFocus = $locationHash.closest('.vertical-tabs__pane');
106 }
107 else {
108 tabFocus = $this.find('> .vertical-tabs__pane').eq(0);
109 }
110 }
111 if (tabFocus.length) {
112 tabFocus.data('verticalTab').focus();
113 }
114 });
115 }, 132 },
116 }; 133 };
117 134
118 /** 135 /**
119 * The vertical tab object represents a single tab within a tab group. 136 * The vertical tab object represents a single tab within a tab group.
129 * 146 *
130 * @fires event:summaryUpdated 147 * @fires event:summaryUpdated
131 * 148 *
132 * @listens event:summaryUpdated 149 * @listens event:summaryUpdated
133 */ 150 */
134 Drupal.verticalTab = function (settings) { 151 Drupal.verticalTab = function(settings) {
135 const self = this; 152 const self = this;
136 $.extend(this, settings, Drupal.theme('verticalTab', settings)); 153 $.extend(this, settings, Drupal.theme('verticalTab', settings));
137 154
138 this.link.attr('href', `#${settings.details.attr('id')}`); 155 this.link.attr('href', `#${settings.details.attr('id')}`);
139 156
140 this.link.on('click', (e) => { 157 this.link.on('click', e => {
141 e.preventDefault(); 158 e.preventDefault();
142 self.focus(); 159 self.focus();
143 }); 160 });
144 161
145 // Keyboard events added: 162 // Keyboard events added:
146 // Pressing the Enter key will open the tab pane. 163 // Pressing the Enter key will open the tab pane.
147 this.link.on('keydown', (event) => { 164 this.link.on('keydown', event => {
148 if (event.keyCode === 13) { 165 if (event.keyCode === 13) {
149 event.preventDefault(); 166 event.preventDefault();
150 self.focus(); 167 self.focus();
151 // Set focus on the first input field of the visible details/tab pane. 168 // Set focus on the first input field of the visible details/tab pane.
152 $('.vertical-tabs__pane :input:visible:enabled').eq(0).trigger('focus'); 169 $('.vertical-tabs__pane :input:visible:enabled')
170 .eq(0)
171 .trigger('focus');
153 } 172 }
154 }); 173 });
155 174
156 this.details 175 this.details
157 .on('summaryUpdated', () => { 176 .on('summaryUpdated', () => {
159 }) 178 })
160 .trigger('summaryUpdated'); 179 .trigger('summaryUpdated');
161 }; 180 };
162 181
163 Drupal.verticalTab.prototype = { 182 Drupal.verticalTab.prototype = {
164
165 /** 183 /**
166 * Displays the tab's content pane. 184 * Displays the tab's content pane.
167 */ 185 */
168 focus() { 186 focus() {
169 this.details 187 this.details
170 .siblings('.vertical-tabs__pane') 188 .siblings('.vertical-tabs__pane')
171 .each(function () { 189 .each(function() {
172 const tab = $(this).data('verticalTab'); 190 const tab = $(this).data('verticalTab');
173 tab.details.hide(); 191 tab.details.hide();
174 tab.item.removeClass('is-selected'); 192 tab.item.removeClass('is-selected');
175 }) 193 })
176 .end() 194 .end()
178 .siblings(':hidden.vertical-tabs__active-tab') 196 .siblings(':hidden.vertical-tabs__active-tab')
179 .val(this.details.attr('id')); 197 .val(this.details.attr('id'));
180 this.item.addClass('is-selected'); 198 this.item.addClass('is-selected');
181 // Mark the active tab for screen readers. 199 // Mark the active tab for screen readers.
182 $('#active-vertical-tab').remove(); 200 $('#active-vertical-tab').remove();
183 this.link.append(`<span id="active-vertical-tab" class="visually-hidden">${Drupal.t('(active tab)')}</span>`); 201 this.link.append(
202 `<span id="active-vertical-tab" class="visually-hidden">${Drupal.t(
203 '(active tab)',
204 )}</span>`,
205 );
184 }, 206 },
185 207
186 /** 208 /**
187 * Updates the tab's summary. 209 * Updates the tab's summary.
188 */ 210 */
238 .eq(0) 260 .eq(0)
239 .addClass('first'); 261 .addClass('first');
240 // Hide the details element. 262 // Hide the details element.
241 this.details.addClass('vertical-tab--hidden').hide(); 263 this.details.addClass('vertical-tab--hidden').hide();
242 // Focus the first visible tab (if there is one). 264 // Focus the first visible tab (if there is one).
243 const $firstTab = this.details.siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)').eq(0); 265 const $firstTab = this.details
266 .siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)')
267 .eq(0);
244 if ($firstTab.length) { 268 if ($firstTab.length) {
245 $firstTab.data('verticalTab').focus(); 269 $firstTab.data('verticalTab').focus();
246 } 270 }
247 // Hide the vertical tabs (if no tabs remain). 271 // Hide the vertical tabs (if no tabs remain).
248 else { 272 else {
265 * - item: The root tab jQuery element 289 * - item: The root tab jQuery element
266 * - link: The anchor tag that acts as the clickable area of the tab 290 * - link: The anchor tag that acts as the clickable area of the tab
267 * (jQuery version) 291 * (jQuery version)
268 * - summary: The jQuery element that contains the tab summary 292 * - summary: The jQuery element that contains the tab summary
269 */ 293 */
270 Drupal.theme.verticalTab = function (settings) { 294 Drupal.theme.verticalTab = function(settings) {
271 const tab = {}; 295 const tab = {};
272 tab.item = $('<li class="vertical-tabs__menu-item" tabindex="-1"></li>') 296 tab.item = $(
273 .append(tab.link = $('<a href="#"></a>') 297 '<li class="vertical-tabs__menu-item" tabindex="-1"></li>',
274 .append(tab.title = $('<strong class="vertical-tabs__menu-item-title"></strong>').text(settings.title)) 298 ).append(
275 .append(tab.summary = $('<span class="vertical-tabs__menu-item-summary"></span>'), 299 (tab.link = $('<a href="#"></a>')
276 ), 300 .append(
277 ); 301 (tab.title = $(
302 '<strong class="vertical-tabs__menu-item-title"></strong>',
303 ).text(settings.title)),
304 )
305 .append(
306 (tab.summary = $(
307 '<span class="vertical-tabs__menu-item-summary"></span>',
308 )),
309 )),
310 );
278 return tab; 311 return tab;
279 }; 312 };
280 }(jQuery, Drupal, drupalSettings)); 313 })(jQuery, Drupal, drupalSettings);