UNPKG

@atlassian/aui

Version:

Atlassian User Interface library

295 lines (245 loc) 10.9 kB
import $ from '../../jquery'; import CustomEvent from '../../polyfills/custom-event'; import debounce from '../../debounce'; import { I18n } from '../../i18n'; import skate from '../skate'; import template from 'skatejs-template-html'; const $window = $(window); function Header(element) { var that = this; this.element = element; this.$element = $(element); this.index = $('aui-header, .aui-header').index(element); this.$secondaryNav = this.$element.find('.aui-header-secondary .aui-nav').first(); this.menuItems = []; this.totalWidth = 0; this.$moreMenu = undefined; this.rightMostNavItemIndex = undefined; this.$applicationLogo = this.$element.find('#logo'); this.moreMenuWidth = 0; this.primaryButtonsWidth = 0; // to cache the selector and give .find convenience this.$headerFind = (function () { var $header = $(that.$element[0].querySelector('.aui-header-primary')); return function (selector) { return $header.find(selector); }; })(); } Header.prototype = { init: function () { var that = this; this.element.setAttribute('data-aui-responsive', 'true'); this.$headerFind('.aui-button') .parent() .each(function () { that.primaryButtonsWidth += $(this).outerWidth(true); }); // remember the widths of all the menu items this.$headerFind('.aui-nav > li > a:not(.aui-button)').each(function () { var $this = $(this).parent(); var outerWidth = $this.outerWidth(true); that.totalWidth += outerWidth; that.menuItems.push({ $element: $this, outerWidth: outerWidth, }); }); /** The zero based index of the right-most visible nav menu item. */ this.rightMostNavItemIndex = this.menuItems.length - 1; $window.on( 'resize', (this._resizeHandler = debounce(function () { that.constructResponsiveDropdown(); }, 100)) ); // So that the header logo doesn't mess things up. (size is unknown before the image loads) var $logoImg = this.$applicationLogo.find('img'); if ($logoImg.length !== 0) { $logoImg.attr('data-aui-responsive-header-index', this.index); $logoImg.on('load', function () { that.constructResponsiveDropdown(); }); } this.constructResponsiveDropdown(); // show the aui nav (hidden via css on load) this.$headerFind('.aui-nav').css('width', 'auto'); }, destroy: function () { $window.off('resize', this._resizeHandler); }, // calculate widths based on the current state of the page calculateAvailableWidth: function () { // if there is no secondary nav, use the right of the screen as the boundary instead var rightMostBoundary = this.$secondaryNav.is(':visible') ? this.$secondaryNav.offset().left : this.$element.outerWidth(); // the right most side of the primary nav, this is assumed to exists if this code is running var primaryNavRight = this.$applicationLogo.offset().left + this.$applicationLogo.outerWidth(true) + this.primaryButtonsWidth; return rightMostBoundary - primaryNavRight; }, showResponsiveDropdown: function () { if (this.$moreMenu === undefined) { this.$moreMenu = this.createResponsiveDropdownTrigger(); } this.$moreMenu.css('display', ''); }, hideResponsiveDropdown: function () { if (this.$moreMenu !== undefined) { this.$moreMenu.css('display', 'none'); } }, constructResponsiveDropdown: function () { if (!this.menuItems.length) { return; } var remainingWidth; var availableWidth = this.calculateAvailableWidth(this.$element, this.primaryButtonsWidth); if (availableWidth > this.totalWidth) { this.showAll(); } else { this.showResponsiveDropdown(); remainingWidth = availableWidth - this.moreMenuWidth; // Figure out how many nav menu items fit into the available space. var newRightMostNavItemIndex = -1; while (remainingWidth - this.menuItems[newRightMostNavItemIndex + 1].outerWidth >= 0) { remainingWidth -= this.menuItems[newRightMostNavItemIndex + 1].outerWidth; newRightMostNavItemIndex++; } if (newRightMostNavItemIndex < this.rightMostNavItemIndex) { this.moveToResponsiveDropdown( this.rightMostNavItemIndex - newRightMostNavItemIndex ); } else if (this.rightMostNavItemIndex < newRightMostNavItemIndex) { this.moveOutOfResponsiveDropdown( newRightMostNavItemIndex - this.rightMostNavItemIndex ); } } }, // creates the trigger and content elements for the show more dropdown createResponsiveDropdownTrigger: function () { var moreNavItemEl = document.createElement('li'); var dropdownEl = document.createElement('aui-dropdown-menu'); dropdownEl.id = `aui-responsive-header-dropdown-${this.index}`; skate.init(dropdownEl); var dropdownSectionEl = document.createElement('aui-section'); dropdownSectionEl.id = `aui-responsive-header-dropdown-list-${this.index}`; skate.init(dropdownSectionEl); dropdownEl.appendChild(dropdownSectionEl); var triggerEl = createTriggerAndAssociate(dropdownEl); moreNavItemEl.appendChild(triggerEl); moreNavItemEl.appendChild(dropdownEl); // Append the More menu before any primary buttons. if (this.primaryButtonsWidth === 0) { this.$headerFind('.aui-nav').append(moreNavItemEl); } else { this.$headerFind('.aui-nav > li > .aui-button:first').parent().before(moreNavItemEl); } this.moreMenuWidth = $(moreNavItemEl).outerWidth(true); return $(moreNavItemEl); }, // function that handles moving items out of the show more menu into the app header moveOutOfResponsiveDropdown: function (numItems) { if (numItems <= 0) { return; } var $moreDropdown = $(`#aui-responsive-header-dropdown-${this.index}`); // Move items (working top-to-bottom) from the more menu into the nav bar. var leftMostIndexToMove = this.rightMostNavItemIndex + 1; var rightMostIndexToMove = this.rightMostNavItemIndex + numItems; for (var i = leftMostIndexToMove; i <= rightMostIndexToMove; i++) { var $navItem = this.menuItems[i].$element; var $navItemTrigger = $navItem.children('a'); if ($navItemTrigger.attr('aria-controls')) { var $navItemDropdown = $( document.getElementById($navItemTrigger.attr('aria-controls')) ); $navItemDropdown.removeClass('aui-dropdown2-sub-menu'); $navItem.append($navItemDropdown); } $moreDropdown.find('aui-item-link:first').remove(); $navItem.insertBefore(this.$moreMenu); } this.rightMostNavItemIndex += numItems; }, // function that handles moving items into the show more menu moveToResponsiveDropdown: function (numItems) { if (numItems <= 0) { return; } var moreDropdownSectionEl = template.wrap(this.$moreMenu[0].querySelector('aui-section')); // Move items (working right-to-left) from the nav bar to the more menu. var rightMostIndexToMove = this.rightMostNavItemIndex; var leftMostIndexToMove = this.rightMostNavItemIndex - numItems + 1; for (var i = rightMostIndexToMove; i >= leftMostIndexToMove; i--) { var $navItem = this.menuItems[i].$element; var $navItemTrigger = $navItem.children('a'); skate.init($navItemTrigger); // ensure all triggers have gone through their lifecycle before we check for submenus var moreDropdownItemEl = document.createElement('aui-item-link'); moreDropdownItemEl.setAttribute('href', $navItemTrigger.attr('href')); if ($navItemTrigger.attr('aria-controls')) { var $navItemDropdown = $( document.getElementById($navItemTrigger.attr('aria-controls')) ); moreDropdownItemEl.setAttribute('for', $navItemTrigger.attr('aria-controls')); $navItemDropdown.addClass('aui-dropdown2-sub-menu'); $navItemDropdown.appendTo('body'); } if ($navItemTrigger.get(0).hasAttribute('data-aui-extra-classes')) { // an opt-in behaviour const extraClasses = Array.from($navItemTrigger.get(0).classList).filter( (cls) => !cls.startsWith('aui-') ); moreDropdownItemEl.setAttribute('extra-classes', extraClasses.join(' ')); } skate.init(moreDropdownItemEl); template.wrap(moreDropdownItemEl).textContent = $navItemTrigger.text(); $navItem.detach(); moreDropdownSectionEl.insertBefore( moreDropdownItemEl, moreDropdownSectionEl.firstChild ); this.element.dispatchEvent( new CustomEvent('aui-responsive-menu-item-created', { bubbles: true, detail: { originalItem: $navItem[0], newItem: moreDropdownItemEl, }, }) ); } this.rightMostNavItemIndex -= numItems; }, // function that handles show everything showAll: function () { this.moveOutOfResponsiveDropdown(this.menuItems.length - 1 - this.rightMostNavItemIndex); this.hideResponsiveDropdown(); }, }; function createTriggerAndAssociate(dropdown) { var trigger = document.createElement('a'); trigger.setAttribute('class', 'aui-dropdown2-trigger'); trigger.setAttribute('href', '#'); trigger.id = dropdown.id + '-trigger'; trigger.setAttribute('aria-controls', dropdown.id); trigger.innerHTML = I18n.getText('aui.words.more'); return trigger; } /** * Initialise a Header, or return an existing Header for an element. */ function createHeader(element) { let header = element._header; if (!(header instanceof Header)) { header = new Header(element); header.init(); element._header = header; } return header; } export default createHeader;