123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313 |
- /**
- * @file
- * Define vertical tabs functionality.
- */
- /**
- * Triggers when form values inside a vertical tab changes.
- *
- * This is used to update the summary in vertical tabs in order to know what
- * are the important fields' values.
- *
- * @event summaryUpdated
- */
- (function($, Drupal, drupalSettings) {
- /**
- * Show the parent vertical tab pane of a targeted page fragment.
- *
- * In order to make sure a targeted element inside a vertical tab pane is
- * visible on a hash change or fragment link click, show all parent panes.
- *
- * @param {jQuery.Event} e
- * The event triggered.
- * @param {jQuery} $target
- * The targeted node as a jQuery object.
- */
- const handleFragmentLinkClickOrHashChange = (e, $target) => {
- $target.parents('.vertical-tabs__pane').each((index, pane) => {
- $(pane)
- .data('verticalTab')
- .focus();
- });
- };
- /**
- * This script transforms a set of details into a stack of vertical tabs.
- *
- * Each tab may have a summary which can be updated by another
- * script. For that to work, each details element has an associated
- * 'verticalTabCallback' (with jQuery.data() attached to the details),
- * which is called every time the user performs an update to a form
- * element inside the tab pane.
- *
- * @type {Drupal~behavior}
- *
- * @prop {Drupal~behaviorAttach} attach
- * Attaches behaviors for vertical tabs.
- */
- Drupal.behaviors.verticalTabs = {
- attach(context) {
- const width = drupalSettings.widthBreakpoint || 640;
- const mq = `(max-width: ${width}px)`;
- if (window.matchMedia(mq).matches) {
- return;
- }
- /**
- * Binds a listener to handle fragment link clicks and URL hash changes.
- */
- $('body')
- .once('vertical-tabs-fragments')
- .on(
- 'formFragmentLinkClickOrHashChange.verticalTabs',
- handleFragmentLinkClickOrHashChange,
- );
- $(context)
- .find('[data-vertical-tabs-panes]')
- .once('vertical-tabs')
- .each(function() {
- const $this = $(this).addClass('vertical-tabs__panes');
- const focusID = $this.find(':hidden.vertical-tabs__active-tab').val();
- let tabFocus;
- // Check if there are some details that can be converted to
- // vertical-tabs.
- const $details = $this.find('> details');
- if ($details.length === 0) {
- return;
- }
- // Create the tab column.
- const tabList = $('<ul class="vertical-tabs__menu"></ul>');
- $this
- .wrap('<div class="vertical-tabs clearfix"></div>')
- .before(tabList);
- // Transform each details into a tab.
- $details.each(function() {
- const $that = $(this);
- const verticalTab = new Drupal.verticalTab({
- title: $that.find('> summary').text(),
- details: $that,
- });
- tabList.append(verticalTab.item);
- $that
- .removeClass('collapsed')
- // prop() can't be used on browsers not supporting details element,
- // the style won't apply to them if prop() is used.
- .attr('open', true)
- .addClass('vertical-tabs__pane')
- .data('verticalTab', verticalTab);
- if (this.id === focusID) {
- tabFocus = $that;
- }
- });
- $(tabList)
- .find('> li')
- .eq(0)
- .addClass('first');
- $(tabList)
- .find('> li')
- .eq(-1)
- .addClass('last');
- if (!tabFocus) {
- // If the current URL has a fragment and one of the tabs contains an
- // element that matches the URL fragment, activate that tab.
- const $locationHash = $this.find(window.location.hash);
- if (window.location.hash && $locationHash.length) {
- tabFocus = $locationHash.closest('.vertical-tabs__pane');
- } else {
- tabFocus = $this.find('> .vertical-tabs__pane').eq(0);
- }
- }
- if (tabFocus.length) {
- tabFocus.data('verticalTab').focus();
- }
- });
- },
- };
- /**
- * The vertical tab object represents a single tab within a tab group.
- *
- * @constructor
- *
- * @param {object} settings
- * Settings object.
- * @param {string} settings.title
- * The name of the tab.
- * @param {jQuery} settings.details
- * The jQuery object of the details element that is the tab pane.
- *
- * @fires event:summaryUpdated
- *
- * @listens event:summaryUpdated
- */
- Drupal.verticalTab = function(settings) {
- const self = this;
- $.extend(this, settings, Drupal.theme('verticalTab', settings));
- this.link.attr('href', `#${settings.details.attr('id')}`);
- this.link.on('click', e => {
- e.preventDefault();
- self.focus();
- });
- // Keyboard events added:
- // Pressing the Enter key will open the tab pane.
- this.link.on('keydown', event => {
- if (event.keyCode === 13) {
- event.preventDefault();
- self.focus();
- // Set focus on the first input field of the visible details/tab pane.
- $('.vertical-tabs__pane :input:visible:enabled')
- .eq(0)
- .trigger('focus');
- }
- });
- this.details
- .on('summaryUpdated', () => {
- self.updateSummary();
- })
- .trigger('summaryUpdated');
- };
- Drupal.verticalTab.prototype = {
- /**
- * Displays the tab's content pane.
- */
- focus() {
- this.details
- .siblings('.vertical-tabs__pane')
- .each(function() {
- const tab = $(this).data('verticalTab');
- tab.details.hide();
- tab.item.removeClass('is-selected');
- })
- .end()
- .show()
- .siblings(':hidden.vertical-tabs__active-tab')
- .val(this.details.attr('id'));
- this.item.addClass('is-selected');
- // Mark the active tab for screen readers.
- $('#active-vertical-tab').remove();
- this.link.append(
- `<span id="active-vertical-tab" class="visually-hidden">${Drupal.t(
- '(active tab)',
- )}</span>`,
- );
- },
- /**
- * Updates the tab's summary.
- */
- updateSummary() {
- this.summary.html(this.details.drupalGetSummary());
- },
- /**
- * Shows a vertical tab pane.
- *
- * @return {Drupal.verticalTab}
- * The verticalTab instance.
- */
- tabShow() {
- // Display the tab.
- this.item.show();
- // Show the vertical tabs.
- this.item.closest('.js-form-type-vertical-tabs').show();
- // Update .first marker for items. We need recurse from parent to retain
- // the actual DOM element order as jQuery implements sortOrder, but not
- // as public method.
- this.item
- .parent()
- .children('.vertical-tabs__menu-item')
- .removeClass('first')
- .filter(':visible')
- .eq(0)
- .addClass('first');
- // Display the details element.
- this.details.removeClass('vertical-tab--hidden').show();
- // Focus this tab.
- this.focus();
- return this;
- },
- /**
- * Hides a vertical tab pane.
- *
- * @return {Drupal.verticalTab}
- * The verticalTab instance.
- */
- tabHide() {
- // Hide this tab.
- this.item.hide();
- // Update .first marker for items. We need recurse from parent to retain
- // the actual DOM element order as jQuery implements sortOrder, but not
- // as public method.
- this.item
- .parent()
- .children('.vertical-tabs__menu-item')
- .removeClass('first')
- .filter(':visible')
- .eq(0)
- .addClass('first');
- // Hide the details element.
- this.details.addClass('vertical-tab--hidden').hide();
- // Focus the first visible tab (if there is one).
- const $firstTab = this.details
- .siblings('.vertical-tabs__pane:not(.vertical-tab--hidden)')
- .eq(0);
- if ($firstTab.length) {
- $firstTab.data('verticalTab').focus();
- }
- // Hide the vertical tabs (if no tabs remain).
- else {
- this.item.closest('.js-form-type-vertical-tabs').hide();
- }
- return this;
- },
- };
- /**
- * Theme function for a vertical tab.
- *
- * @param {object} settings
- * An object with the following keys:
- * @param {string} settings.title
- * The name of the tab.
- *
- * @return {object}
- * This function has to return an object with at least these keys:
- * - item: The root tab jQuery element
- * - link: The anchor tag that acts as the clickable area of the tab
- * (jQuery version)
- * - summary: The jQuery element that contains the tab summary
- */
- Drupal.theme.verticalTab = function(settings) {
- const tab = {};
- tab.item = $(
- '<li class="vertical-tabs__menu-item" tabindex="-1"></li>',
- ).append(
- (tab.link = $('<a href="#"></a>')
- .append(
- (tab.title = $(
- '<strong class="vertical-tabs__menu-item-title"></strong>',
- ).text(settings.title)),
- )
- .append(
- (tab.summary = $(
- '<span class="vertical-tabs__menu-item-summary"></span>',
- )),
- )),
- );
- return tab;
- };
- })(jQuery, Drupal, drupalSettings);
|