UNPKG

carbon-components

Version:

The Carbon Design System is IBM’s open-source design system for products and experiences.

251 lines (233 loc) • 9.21 kB
/** * Copyright IBM Corp. 2016, 2018 * * This source code is licensed under the Apache-2.0 license found in the * LICENSE file in the root directory of this source tree. */ import settings from '../../globals/js/settings'; import eventMatches from '../../globals/js/misc/event-matches'; import ContentSwitcher from '../content-switcher/content-switcher'; import on from '../../globals/js/misc/on'; const toArray = (arrayLike) => Array.prototype.slice.call(arrayLike); class Tab extends ContentSwitcher { /** * Container of tabs. * @extends ContentSwitcher * @param {HTMLElement} element The element working as a container of tabs. * @param {object} [options] The component options. * @param {string} [options.selectorMenu] The CSS selector to find the drop down menu used in narrow mode. * @param {string} [options.selectorTrigger] The CSS selector to find the button to open the drop down menu used in narrow mode. * @param {string} [options.selectorTriggerText] * The CSS selector to find the element used in narrow mode showing the selected tab item. * @param {string} [options.selectorButton] The CSS selector to find tab containers. * @param {string} [options.selectorButtonSelected] The CSS selector to find the selected tab. * @param {string} [options.selectorLink] The CSS selector to find the links in tabs. * @param {string} [options.classActive] The CSS class for tab's selected state. * @param {string} [options.classHidden] The CSS class for the drop down menu's hidden state used in narrow mode. * @param {string} [options.eventBeforeSelected] * The name of the custom event fired before a tab is selected. * Cancellation of this event stops selection of tab. * @param {string} [options.eventAfterSelected] The name of the custom event fired after a tab is selected. */ constructor(element, options) { super(element, options); this.manage( on(this.element, 'keydown', (event) => { this._handleKeyDown(event); }) ); this.manage( on(this.element.ownerDocument, 'click', (event) => { this._handleDocumentClick(event); }) ); const selected = this.element.querySelector( this.options.selectorButtonSelected ); if (selected) { this._updateTriggerText(selected); } } /** * Internal method of {@linkcode Tab#setActive .setActive()}, to select a tab item. * @private * @param {object} detail The detail of the event trigging this action. * @param {HTMLElement} detail.item The tab item to be selected. * @param {Function} callback Callback called when change in state completes. */ _changeState(detail, callback) { super._changeState(detail, (error, ...data) => { if (!error) { this._updateTriggerText(detail.item); } callback(error, ...data); }); } /** * Handles click on tab container. * * If the click is on a tab, activates it. * * If the click is on the button to open the drop down menu, does so. * @param {Event} event The event triggering this method. */ _handleClick(event) { const button = eventMatches(event, this.options.selectorButton); const trigger = eventMatches(event, this.options.selectorTrigger); if ( button && !button.classList.contains(this.options.classButtonDisabled) ) { super._handleClick(event); this._updateMenuState(false); } if (trigger) { this._updateMenuState(); } } /** * Handles click on document. * @param {Event} event The triggering event. * @private */ _handleDocumentClick(event) { const { element } = this; const isOfSelf = element.contains(event.target); if (isOfSelf) { return; } this._updateMenuState(false); } /** * Handles arrow keys on tab container. * * Left keys are used to go to previous tab. * * Right keys are used to go to next tab. * @param {Event} event The event triggering this method. */ _handleKeyDown(event) { const triggerNode = eventMatches(event, this.options.selectorTrigger); if (triggerNode) { if (event.which === 13) { this._updateMenuState(); } return; } const direction = { 37: this.constructor.NAVIGATE.BACKWARD, 39: this.constructor.NAVIGATE.FORWARD, }[event.which]; if (direction) { const buttons = toArray( this.element.querySelectorAll(this.options.selectorButtonEnabled) ); const button = this.element.querySelector( this.options.selectorButtonSelected ); const nextIndex = Math.max( buttons.indexOf(button) + direction, -1 /* For `button` not found in `buttons` */ ); const nextIndexLooped = nextIndex >= 0 && nextIndex < buttons.length ? nextIndex : nextIndex - Math.sign(nextIndex) * buttons.length; this.setActive(buttons[nextIndexLooped], (error, item) => { if (item) { const link = item.querySelector(this.options.selectorLink); if (link) { link.focus(); } } }); event.preventDefault(); } } /** * Shows/hides the drop down menu used in narrow mode. * @param {boolean} [force] `true` to show the menu, `false` to hide the menu, otherwise toggles the menu. */ _updateMenuState(force) { const menu = this.element.querySelector(this.options.selectorMenu); const trigger = this.element.querySelector(this.options.selectorTrigger); if (menu) { menu.classList.toggle( this.options.classHidden, typeof force === 'undefined' ? force : !force ); if (menu.classList.contains(this.options.classHidden)) { trigger.classList.remove(this.options.classOpen); } else { trigger.classList.add(this.options.classOpen); } } } /** * Updates the text indicating the currently selected tab item. * @param {HTMLElement} target The newly selected tab item. */ _updateTriggerText(target) { const triggerText = this.element.querySelector( this.options.selectorTriggerText ); if (triggerText) { triggerText.textContent = target.textContent; } } /** * The map associating DOM element and tab container instance. * @member Tab.components * @type {WeakMap} */ static components /* #__PURE_CLASS_PROPERTY__ */ = new WeakMap(); /** * The component options. * If `options` is specified in the constructor, {@linkcode ContentSwitcher.create .create()}, or {@linkcode Tab.init .init()}, * properties in this object are overridden for the instance being create and how {@linkcode Tab.init .init()} works. * @member Tab.options * @type {object} * @property {string} selectorInit The CSS selector to find tab containers. * @property {string} [selectorMenu] The CSS selector to find the drop down menu used in narrow mode. * @property {string} [selectorTrigger] The CSS selector to find the button to open the drop down menu used in narrow mode. * @property {string} [selectorTriggerText] * The CSS selector to find the element used in narrow mode showing the selected tab item. * @property {string} [selectorButton] The CSS selector to find tab containers. * @property {string} [selectorButtonSelected] The CSS selector to find the selected tab. * @property {string} [selectorLink] The CSS selector to find the links in tabs. * @property {string} [classActive] The CSS class for tab's selected state. * @property {string} [classHidden] The CSS class for the drop down menu's hidden state used in narrow mode. * @property {string} [eventBeforeSelected] * The name of the custom event fired before a tab is selected. * Cancellation of this event stops selection of tab. * @property {string} [eventAfterSelected] The name of the custom event fired after a tab is selected. */ static get options() { const { prefix } = settings; return Object.assign(Object.create(ContentSwitcher.options), { selectorInit: '[data-tabs]', selectorMenu: `.${prefix}--tabs__nav`, selectorTrigger: `.${prefix}--tabs-trigger`, selectorTriggerText: `.${prefix}--tabs-trigger-text`, selectorButton: `.${prefix}--tabs__nav-item`, selectorButtonEnabled: `.${prefix}--tabs__nav-item:not(.${prefix}--tabs__nav-item--disabled)`, selectorButtonSelected: `.${prefix}--tabs__nav-item--selected`, selectorLink: `.${prefix}--tabs__nav-link`, classActive: `${prefix}--tabs__nav-item--selected`, classHidden: `${prefix}--tabs__nav--hidden`, classOpen: `${prefix}--tabs-trigger--open`, classButtonDisabled: `${prefix}--tabs__nav-item--disabled`, eventBeforeSelected: 'tab-beingselected', eventAfterSelected: 'tab-selected', }); } /** * Enum for navigating backward/forward. * @readonly * @member Tab.NAVIGATE * @type {object} * @property {number} BACKWARD Navigating backward. * @property {number} FORWARD Navigating forward. */ static NAVIGATE /* #__PURE_CLASS_PROPERTY__ */ = { BACKWARD: -1, FORWARD: 1, }; } export default Tab;