UNPKG

@atlassian/aui

Version:

Atlassian User Interface Framework

538 lines (418 loc) 20.9 kB
(function (global, factory) { if (typeof define === "function" && define.amd) { define(['module', 'exports', './internal/log', './debounce', './jquery', './internal/add-id', './internal/globalize', './is-clipped', './internal/skate', 'skatejs-template-html'], factory); } else if (typeof exports !== "undefined") { factory(module, exports, require('./internal/log'), require('./debounce'), require('./jquery'), require('./internal/add-id'), require('./internal/globalize'), require('./is-clipped'), require('./internal/skate'), require('skatejs-template-html')); } else { var mod = { exports: {} }; factory(mod, mod.exports, global.log, global.debounce, global.jquery, global.addId, global.globalize, global.isClipped, global.skate, global.skatejsTemplateHtml); global.tabs = mod.exports; } })(this, function (module, exports, _log, _debounce, _jquery, _addId, _globalize, _isClipped, _skate) { 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); var logger = _interopRequireWildcard(_log); var _debounce2 = _interopRequireDefault(_debounce); var _jquery2 = _interopRequireDefault(_jquery); var _addId2 = _interopRequireDefault(_addId); var _globalize2 = _interopRequireDefault(_globalize); var _isClipped2 = _interopRequireDefault(_isClipped); var _skate2 = _interopRequireDefault(_skate); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } var template = window.skateTemplateHtml; var REGEX = /#.*/; var STORAGE_PREFIX = '_internal-aui-tabs-'; var RESPONSIVE_OPT_IN_SELECTOR = '.aui-tabs.horizontal-tabs[data-aui-responsive]:not([data-aui-responsive="false"]), aui-tabs[responsive]:not([responsive="false"])'; function enhanceTabLink(link) { var $thisLink = (0, _jquery2.default)(link); var targetPane = $thisLink.attr('href'); (0, _addId2.default)($thisLink); $thisLink.attr('role', 'tab'); (0, _jquery2.default)(targetPane).attr('aria-labelledby', $thisLink.attr('id')); if ($thisLink.parent().hasClass('active-tab')) { $thisLink.attr('aria-selected', 'true'); } else { $thisLink.attr('aria-selected', 'false'); } } var ResponsiveAdapter = { totalTabsWidth: function totalTabsWidth($visibleTabs, $dropdown) { var totalVisibleTabsWidth = this.totalVisibleTabWidth($visibleTabs); var totalDropdownTabsWidth = 0; $dropdown.find('li').each(function (index, tab) { totalDropdownTabsWidth += parseInt(tab.getAttribute('data-aui-tab-width')); }); return totalVisibleTabsWidth + totalDropdownTabsWidth; }, totalVisibleTabWidth: function totalVisibleTabWidth($tabs) { var totalWidth = 0; $tabs.each(function (index, tab) { totalWidth += (0, _jquery2.default)(tab).outerWidth(); }); return totalWidth; }, removeResponsiveDropdown: function removeResponsiveDropdown($dropdown, $dropdownTriggerTab) { $dropdown.remove(); $dropdownTriggerTab.remove(); }, createResponsiveDropdownTrigger: function createResponsiveDropdownTrigger($tabsMenu, id) { var triggerMarkup = '<li class="menu-item aui-tabs-responsive-trigger-item">' + '<a class="aui-dropdown2-trigger aui-tabs-responsive-trigger aui-dropdown2-trigger-arrowless" id="aui-tabs-responsive-trigger-' + id + '" aria-haspopup="true" aria-controls="aui-tabs-responsive-dropdown-' + id + '" href="aui-tabs-responsive-dropdown-' + id + '">...</a>' + '</li>'; $tabsMenu.append(triggerMarkup); var $trigger = $tabsMenu.find('.aui-tabs-responsive-trigger-item'); return $trigger; }, createResponsiveDropdown: function createResponsiveDropdown($tabsContainer, id) { var dropdownMarkup = '<div class="aui-dropdown2 aui-style-default aui-tabs-responsive-dropdown" id="aui-tabs-responsive-dropdown-' + id + '">' + '<ul>' + '</ul>' + '</div>'; $tabsContainer.append(dropdownMarkup); var $dropdown = $tabsContainer.find('#aui-tabs-responsive-dropdown-' + id); return $dropdown; }, findNewVisibleTabs: function findNewVisibleTabs(tabs, parentWidth, dropdownTriggerTabWidth) { function hasMoreSpace(currentTotalTabWidth, dropdownTriggerTabWidth, parentWidth) { return currentTotalTabWidth + dropdownTriggerTabWidth <= parentWidth; } var currentTotalTabWidth = 0; for (var i = 0; hasMoreSpace(currentTotalTabWidth, dropdownTriggerTabWidth, parentWidth) && i < tabs.length; i++) { var $tab = (0, _jquery2.default)(tabs[i]); var tabWidth = $tab.outerWidth(true); currentTotalTabWidth += tabWidth; } // i should now be at the tab index after the last visible tab because of the loop so we minus 1 to get the new visible tabs return tabs.slice(0, i - 1); }, moveVisibleTabs: function moveVisibleTabs(oldVisibleTabs, $tabsParent, $dropdownTriggerTab) { var dropdownId = $dropdownTriggerTab.find('a').attr('aria-controls'); var $dropdown = (0, _jquery2.default)('#' + dropdownId); var newVisibleTabs = this.findNewVisibleTabs(oldVisibleTabs, $tabsParent.outerWidth(), $dropdownTriggerTab.parent().outerWidth(true)); var lastTabIndex = newVisibleTabs.length - 1; for (var j = oldVisibleTabs.length - 1; j >= lastTabIndex; j--) { var $tab = (0, _jquery2.default)(oldVisibleTabs[j]); this.moveTabToResponsiveDropdown($tab, $dropdown, $dropdownTriggerTab); } return (0, _jquery2.default)(newVisibleTabs); }, moveTabToResponsiveDropdown: function moveTabToResponsiveDropdown($tab, $dropdown, $dropdownTriggerTab) { var $tabLink = $tab.find('a'); $tab.attr('data-aui-tab-width', $tab.outerWidth(true)); $tabLink.addClass('aui-dropdown2-radio aui-tabs-responsive-item'); if ($tab.hasClass('active-tab')) { $tabLink.addClass('aui-dropdown2-checked'); $dropdownTriggerTab.addClass('active-tab'); } $dropdown.find('ul').prepend($tab); }, moveInvisibleTabs: function moveInvisibleTabs(tabsInDropdown, remainingSpace, $dropdownTriggerTab) { function hasMoreSpace(remainingSpace) { return remainingSpace > 0; } for (var i = 0; hasMoreSpace(remainingSpace) && i < tabsInDropdown.length; i++) { var $tab = (0, _jquery2.default)(tabsInDropdown[i]); var tabInDropdownWidth = parseInt($tab.attr('data-aui-tab-width'), 10); var shouldMoveTabOut = tabInDropdownWidth < remainingSpace; if (shouldMoveTabOut) { this.moveTabOutOfDropdown($tab, $dropdownTriggerTab); } remainingSpace -= tabInDropdownWidth; } }, moveTabOutOfDropdown: function moveTabOutOfDropdown($tab, $dropdownTriggerTab) { var isTabInDropdownActive = $tab.find('a').hasClass('aui-dropdown2-checked'); if (isTabInDropdownActive) { $tab.addClass('active-tab'); $dropdownTriggerTab.removeClass('active-tab'); } $tab.children('a').removeClass('aui-dropdown2-radio aui-tabs-responsive-item aui-dropdown2-checked'); $dropdownTriggerTab.before($tab); } }; // this function is run by jquery .each() where 'this' is the current tabs container function calculateResponsiveTabs(tabsContainer, index) { var $tabsContainer = (0, _jquery2.default)(tabsContainer); var $tabsMenu = $tabsContainer.find('.tabs-menu').first(); var $visibleTabs = $tabsMenu.find('li:not(.aui-tabs-responsive-trigger-item)'); var $dropdownTriggerTab = $tabsMenu.find('.aui-tabs-responsive-trigger').parent(); var $dropdownTrigger = $dropdownTriggerTab.find('a'); var dropdownId = $dropdownTrigger.attr('aria-controls'); var $dropdown = (0, _jquery2.default)(document).find('#' + dropdownId).attr('aria-checked', false); var isResponsive = $dropdown.length > 0; var totalTabsWidth = ResponsiveAdapter.totalTabsWidth($visibleTabs, $dropdown); var needsResponsive = totalTabsWidth > $tabsContainer.outerWidth(); if (!isResponsive && needsResponsive) { $dropdownTriggerTab = ResponsiveAdapter.createResponsiveDropdownTrigger($tabsMenu, index); $dropdown = ResponsiveAdapter.createResponsiveDropdown($tabsContainer, index); } // reset id's in case tabs have changed DOM order $dropdownTrigger.attr('aria-controls', 'aui-tabs-responsive-dropdown-' + index); $dropdownTrigger.attr('id', 'aui-tabs-responsive-trigger-' + index); $dropdownTrigger.attr('href', 'aui-tabs-responsive-trigger-' + index); $dropdown.attr('id', 'aui-tabs-responsive-dropdown-' + index); if (needsResponsive) { var $newVisibleTabs = ResponsiveAdapter.moveVisibleTabs($visibleTabs.toArray(), $tabsContainer, $dropdownTriggerTab); var visibleTabWidth = ResponsiveAdapter.totalVisibleTabWidth($newVisibleTabs); var remainingSpace = $tabsContainer.outerWidth() - visibleTabWidth - $dropdownTriggerTab.outerWidth(true); var hasSpace = remainingSpace > 0; if (hasSpace) { var $tabsInDropdown = $dropdown.find('li'); ResponsiveAdapter.moveInvisibleTabs($tabsInDropdown.toArray(), remainingSpace, $dropdownTriggerTab); } $dropdown.on('click', 'a', handleTabClick); /* Workaround for bug in Edge where the dom is just not being re-rendered properly It is only triggered for certain widths. Simply taking the element out of the DOM and placing it back in causes the browser to re-render, hiding the issue. added from AUI-4098 and to be revisited in AUI-4117*/ if ($tabsMenu.is(':visible')) { $tabsMenu.hide().show(); } } if (isResponsive && !needsResponsive) { $dropdown.find('li').each(function () { ResponsiveAdapter.moveTabOutOfDropdown((0, _jquery2.default)(this), $dropdownTriggerTab); }); ResponsiveAdapter.removeResponsiveDropdown($dropdown, $dropdownTriggerTab); } } function switchToTab(tab) { var $tab = (0, _jquery2.default)(tab); // This probably isn't needed anymore. Remove once confirmed. if ($tab.hasClass('aui-tabs-responsive-trigger')) { return; } var $pane = (0, _jquery2.default)($tab.attr('href').match(REGEX)[0]); $pane.addClass('active-pane').attr('aria-hidden', 'false').siblings('.tabs-pane').removeClass('active-pane').attr('aria-hidden', 'true'); var $dropdownTriggerTab = $tab.parents('.aui-tabs').find('.aui-tabs-responsive-trigger-item a'); var dropdownId = $dropdownTriggerTab.attr('aria-controls'); var $dropdown = (0, _jquery2.default)(document).find('#' + dropdownId); $dropdown.find('li a').attr('aria-checked', false).removeClass('checked aui-dropdown2-checked'); $dropdown.find('li').removeClass('active-tab'); $tab.parent('li.menu-item').addClass('active-tab').siblings('.menu-item').removeClass('active-tab'); if ($tab.hasClass('aui-tabs-responsive-item')) { var $visibleTabs = $pane.parent('.aui-tabs').find('li.menu-item:not(.aui-tabs-responsive-trigger-item)'); $visibleTabs.removeClass('active-tab'); $visibleTabs.find('a').removeClass('checked').removeAttr('aria-checked'); } if ($tab.hasClass('aui-tabs-responsive-item')) { $pane.parent('.aui-tabs').find('li.menu-item.aui-tabs-responsive-trigger-item').addClass('active-tab'); } $tab.closest('.tabs-menu').find('a').attr('aria-selected', 'false'); $tab.attr('aria-selected', 'true'); $tab.trigger('tabSelect', { tab: $tab, pane: $pane }); } function isPersistentTabGroup($tabGroup) { // Tab group persistent attribute exists and is not false return $tabGroup.attr('data-aui-persist') !== undefined && $tabGroup.attr('data-aui-persist') !== 'false'; } function createPersistentKey($tabGroup) { var tabGroupId = $tabGroup.attr('id'); var value = $tabGroup.attr('data-aui-persist'); return STORAGE_PREFIX + (tabGroupId ? tabGroupId : '') + (value && value !== 'true' ? '-' + value : ''); } /* eslint max-depth: 1 */ function updateTabsFromLocalStorage($tabGroups) { for (var i = 0, ii = $tabGroups.length; i < ii; i++) { var $tabGroup = $tabGroups.eq(i); var tabs = $tabGroups.get(i); if (isPersistentTabGroup($tabGroup) && window.localStorage) { var tabGroupId = $tabGroup.attr('id'); if (tabGroupId) { var persistentTabId = window.localStorage.getItem(createPersistentKey($tabGroup)); if (persistentTabId) { var anchor = tabs.querySelector('a[href$="' + persistentTabId + '"]'); if (anchor) { switchToTab(anchor); } } } else { logger.warn('A tab group must specify an id attribute if it specifies data-aui-persist.'); } } } } function updateLocalStorageEntry($tab) { var $tabGroup = $tab.closest('.aui-tabs'); var tabGroupId = $tabGroup.attr('id'); if (tabGroupId) { var tabId = $tab.attr('href'); if (tabId) { window.localStorage.setItem(createPersistentKey($tabGroup), tabId); } } else { logger.warn('A tab group must specify an id attribute if it specifies data-aui-persist.'); } } function handleTabClick(e) { tabs.change((0, _jquery2.default)(e.target).closest('a')); if (e) { e.preventDefault(); } } function responsiveResizeHandler(tabs) { tabs.forEach(function (tab, index) { calculateResponsiveTabs(tab, index); }); } // Initialisation // -------------- function getTabs() { return (0, _jquery2.default)('.aui-tabs:not(.aui-tabs-disabled)'); } function getResponsiveTabs() { return (0, _jquery2.default)(RESPONSIVE_OPT_IN_SELECTOR).toArray(); } function initWindow() { var debounced = (0, _debounce2.default)(responsiveResizeHandler, 200); var responsive = getResponsiveTabs(); responsiveResizeHandler(responsive); (0, _jquery2.default)(window).resize(function () { debounced(responsive); }); } function initTab(tab) { var $tab = (0, _jquery2.default)(tab); tab.setAttribute('role', 'application'); if (!$tab.data('aui-tab-events-bound')) { var $tabMenu = $tab.children('ul.tabs-menu'); // ARIA setup $tabMenu.attr('role', 'tablist'); // ignore the LIs so tab count is announced correctly $tabMenu.children('li').attr('role', 'presentation'); $tabMenu.find('> .menu-item a').each(function () { enhanceTabLink(this); }); // Set up click event for tabs $tabMenu.on('click', 'a', handleTabClick); $tab.data('aui-tab-events-bound', true); initPanes(tab); } } function initTabs() { var tabs = getTabs(); tabs.each(function () { initTab(this); }); updateTabsFromLocalStorage(tabs); } function initPane(pane) { pane.setAttribute('role', 'tabpanel'); pane.setAttribute('aria-hidden', (0, _jquery2.default)(pane).hasClass('active-pane') ? 'false' : 'true'); } function initPanes(tab) { [].slice.call(tab.querySelectorAll('.tabs-pane')).forEach(initPane); } function initVerticalTabs() { // Vertical tab truncation setup (adds title if clipped) (0, _jquery2.default)('.aui-tabs.vertical-tabs').find('a').each(function () { var thisTab = (0, _jquery2.default)(this); // don't override existing titles if (!thisTab.attr('title')) { // if text has been truncated, add title if ((0, _isClipped2.default)(thisTab)) { thisTab.attr('title', thisTab.text()); } } }); } var tabs = { setup: function setup() { initWindow(); initTabs(); initVerticalTabs(); }, change: function change(a) { var $a = (0, _jquery2.default)(a); var $tabGroup = $a.closest('.aui-tabs'); switchToTab($a); if (isPersistentTabGroup($tabGroup) && window.localStorage) { updateLocalStorageEntry($a); } } }; (0, _jquery2.default)(tabs.setup); // Web Components // -------------- function findComponent(element) { return (0, _jquery2.default)(element).closest('aui-tabs').get(0); } function findPanes(tabs) { return tabs.querySelectorAll('aui-tabs-pane'); } function findTabs(tabs) { return tabs.querySelectorAll('li[is=aui-tabs-tab]'); } (0, _skate2.default)('aui-tabs', { created: function created(element) { (0, _jquery2.default)(element).addClass('aui-tabs horizontal-tabs'); // We must initialise here so that the old code still works since // the lifecycle of the sub-components setup the markup so that it // can be processed by the old logic. _skate2.default.init(element); // Use the old logic to initialise the tabs. initTab(element); }, template: template('<ul class="tabs-menu">', '<content select="li[is=aui-tabs-tab]"></content>', '</ul>', '<content select="aui-tabs-pane"></content>'), prototype: { select: function select(element) { var index = (0, _jquery2.default)(findPanes(this)).index(element); if (index > -1) { tabs.change(findTabs(this)[index].children[0]); } return this; } } }); var Tab = (0, _skate2.default)('aui-tabs-tab', { extends: 'li', created: function created(element) { (0, _jquery2.default)(element).addClass('menu-item'); }, template: template('<a href="#">', '<strong>', '<content></content>', '</strong>', '</a>') }); (0, _skate2.default)('aui-tabs-pane', { attached: function attached(element) { var $component = (0, _jquery2.default)(findComponent(element)); var $element = (0, _jquery2.default)(element); var index = $component.find('aui-tabs-pane').index($element); var tab = new Tab(); var $tab = (0, _jquery2.default)(tab); $element.addClass('tabs-pane'); tab.firstChild.setAttribute('href', '#' + element.id); template.wrap(tab).textContent = $element.attr('title'); if (index === 0) { $element.addClass('active-pane'); } if ($element.hasClass('active-pane')) { $tab.addClass('active-tab'); } $element.siblings('ul').append(tab); }, template: template('<content></content>') }); (0, _globalize2.default)('tabs', tabs); exports.default = tabs; module.exports = exports['default']; }); //# sourceMappingURL=tabs.js.map