| 'use strict'; |
| |
| var _toConsumableArray2 = require('babel-runtime/helpers/toConsumableArray'); |
| |
| var _toConsumableArray3 = _interopRequireDefault(_toConsumableArray2); |
| |
| var _constants = require('../utils/constants'); |
| |
| function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } |
| |
| (function () { |
| 'use strict'; |
| |
| var ACCORDION = 'mdlext-accordion'; |
| var ACCORDION_VERTICAL = 'mdlext-accordion--vertical'; |
| var ACCORDION_HORIZONTAL = 'mdlext-accordion--horizontal'; |
| var PANEL = 'mdlext-accordion__panel'; |
| var PANEL_ROLE = 'presentation'; |
| var TAB = 'mdlext-accordion__tab'; |
| var TAB_CAPTION = 'mdlext-accordion__tab__caption'; |
| var TAB_ROLE = 'tab'; |
| var TABPANEL = 'mdlext-accordion__tabpanel'; |
| var TABPANEL_ROLE = 'tabpanel'; |
| var RIPPLE_EFFECT = 'mdlext-js-ripple-effect'; |
| var RIPPLE = 'mdlext-accordion__tab--ripple'; |
| var ANIMATION_EFFECT = 'mdlext-js-animation-effect'; |
| var ANIMATION = 'mdlext-accordion__tabpanel--animation'; |
| |
| /** |
| * @constructor |
| * @param {Element} element The element that will be upgraded. |
| */ |
| var MaterialExtAccordion = function MaterialExtAccordion(element) { |
| |
| // Stores the Accordion HTML element. |
| this.element_ = element; |
| |
| // Initialize instance. |
| this.init(); |
| }; |
| window['MaterialExtAccordion'] = MaterialExtAccordion; |
| |
| // Helpers |
| var accordionPanelElements = function accordionPanelElements(element) { |
| if (!element) { |
| return { |
| panel: null, |
| tab: null, |
| tabpanel: null |
| }; |
| } else if (element.classList.contains(PANEL)) { |
| return { |
| panel: element, |
| tab: element.querySelector('.' + TAB), |
| tabpanel: element.querySelector('.' + TABPANEL) |
| }; |
| } else { |
| return { |
| panel: element.parentNode, |
| tab: element.parentNode.querySelector('.' + TAB), |
| tabpanel: element.parentNode.querySelector('.' + TABPANEL) |
| }; |
| } |
| }; |
| |
| // Private methods. |
| |
| /** |
| * Handles custom command event, 'open', 'close', 'toggle' or upgrade |
| * @param event. A custom event |
| * @private |
| */ |
| MaterialExtAccordion.prototype.commandHandler_ = function (event) { |
| event.preventDefault(); |
| event.stopPropagation(); |
| |
| if (event && event.detail) { |
| this.command(event.detail); |
| } |
| }; |
| |
| /** |
| * Dispatch toggle event |
| * @param {string} state |
| * @param {Element} tab |
| * @param {Element} tabpanel |
| * @private |
| */ |
| MaterialExtAccordion.prototype.dispatchToggleEvent_ = function (state, tab, tabpanel) { |
| var ce = new CustomEvent('toggle', { |
| bubbles: true, |
| cancelable: true, |
| detail: { state: state, tab: tab, tabpanel: tabpanel } |
| }); |
| this.element_.dispatchEvent(ce); |
| }; |
| |
| /** |
| * Open tab |
| * @param {Element} panel |
| * @param {Element} tab |
| * @param {Element} tabpanel |
| * @private |
| */ |
| MaterialExtAccordion.prototype.openTab_ = function (panel, tab, tabpanel) { |
| panel.classList.add(_constants.IS_EXPANDED); |
| tab.setAttribute(_constants.ARIA_EXPANDED, 'true'); |
| tabpanel.removeAttribute('hidden'); |
| tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'false'); |
| this.dispatchToggleEvent_('open', tab, tabpanel); |
| }; |
| |
| /** |
| * Close tab |
| * @param {Element} panel |
| * @param {Element} tab |
| * @param {Element} tabpanel |
| * @private |
| */ |
| MaterialExtAccordion.prototype.closeTab_ = function (panel, tab, tabpanel) { |
| panel.classList.remove(_constants.IS_EXPANDED); |
| tab.setAttribute(_constants.ARIA_EXPANDED, 'false'); |
| tabpanel.setAttribute('hidden', ''); |
| tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'true'); |
| this.dispatchToggleEvent_('close', tab, tabpanel); |
| }; |
| |
| /** |
| * Toggle tab |
| * @param {Element} panel |
| * @param {Element} tab |
| * @param {Element} tabpanel |
| * @private |
| */ |
| MaterialExtAccordion.prototype.toggleTab_ = function (panel, tab, tabpanel) { |
| if (!(this.element_.hasAttribute('disabled') || tab.hasAttribute('disabled'))) { |
| if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() === 'true') { |
| this.closeTab_(panel, tab, tabpanel); |
| } else { |
| if (this.element_.getAttribute(_constants.ARIA_MULTISELECTABLE).toLowerCase() !== 'true') { |
| this.closeTabs_(); |
| } |
| this.openTab_(panel, tab, tabpanel); |
| } |
| } |
| }; |
| |
| /** |
| * Open tabs |
| * @private |
| */ |
| MaterialExtAccordion.prototype.openTabs_ = function () { |
| var _this = this; |
| |
| if (this.element_.getAttribute(_constants.ARIA_MULTISELECTABLE).toLowerCase() === 'true') { |
| [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + ACCORDION + ' > .' + PANEL))).filter(function (panel) { |
| return !panel.classList.contains(_constants.IS_EXPANDED); |
| }).forEach(function (closedItem) { |
| var tab = closedItem.querySelector('.' + TAB); |
| if (!tab.hasAttribute('disabled')) { |
| _this.openTab_(closedItem, tab, closedItem.querySelector('.' + TABPANEL)); |
| } |
| }); |
| } |
| }; |
| |
| /** |
| * Close tabs |
| * @private |
| */ |
| MaterialExtAccordion.prototype.closeTabs_ = function () { |
| var _this2 = this; |
| |
| [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + ACCORDION + ' > .' + PANEL + '.' + _constants.IS_EXPANDED))).forEach(function (panel) { |
| var tab = panel.querySelector('.' + TAB); |
| if (!tab.hasAttribute('disabled')) { |
| _this2.closeTab_(panel, tab, panel.querySelector('.' + TABPANEL)); |
| } |
| }); |
| }; |
| |
| // Public methods. |
| |
| /** |
| * Upgrade an individual accordion tab |
| * @public |
| * @param {Element} tabElement The HTML element for the accordion panel. |
| */ |
| MaterialExtAccordion.prototype.upgradeTab = function (tabElement) { |
| var _this3 = this; |
| |
| var _accordionPanelElemen = accordionPanelElements(tabElement), |
| panel = _accordionPanelElemen.panel, |
| tab = _accordionPanelElemen.tab, |
| tabpanel = _accordionPanelElemen.tabpanel; |
| |
| var disableTab = function disableTab() { |
| panel.classList.remove(_constants.IS_EXPANDED); |
| tab.setAttribute('tabindex', '-1'); |
| tab.setAttribute(_constants.ARIA_EXPANDED, 'false'); |
| tabpanel.setAttribute('hidden', ''); |
| tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'true'); |
| }; |
| |
| var enableTab = function enableTab() { |
| if (!tab.hasAttribute(_constants.ARIA_EXPANDED)) { |
| tab.setAttribute(_constants.ARIA_EXPANDED, 'false'); |
| } |
| |
| tab.setAttribute('tabindex', '0'); |
| |
| if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() === 'true') { |
| panel.classList.add(_constants.IS_EXPANDED); |
| tabpanel.removeAttribute('hidden'); |
| tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'false'); |
| } else { |
| panel.classList.remove(_constants.IS_EXPANDED); |
| tabpanel.setAttribute('hidden', ''); |
| tabpanel.setAttribute(_constants.ARIA_HIDDEN, 'true'); |
| } |
| }; |
| |
| // In horizontal layout, caption must have a max-width defined to prevent pushing elements to the right of the caption out of view. |
| // In JsDom, offsetWidth and offsetHeight properties do not work, so this function is not testable. |
| /* istanbul ignore next */ |
| var calcMaxTabCaptionWidth = function calcMaxTabCaptionWidth() { |
| |
| var tabCaption = tab.querySelector('.' + TAB_CAPTION); |
| if (tabCaption !== null) { |
| var w = [].concat((0, _toConsumableArray3.default)(tab.children)).filter(function (el) { |
| return el.classList && !el.classList.contains(TAB_CAPTION); |
| }).reduce(function (v, el) { |
| return v + el.offsetWidth; |
| }, 0); |
| |
| var maxWidth = tab.clientHeight - w; |
| if (maxWidth > 0) { |
| tabCaption.style['max-width'] = maxWidth + 'px'; |
| } |
| } |
| }; |
| |
| var selectTab = function selectTab() { |
| if (!tab.hasAttribute(_constants.ARIA_SELECTED)) { |
| [].concat((0, _toConsumableArray3.default)(_this3.element_.querySelectorAll('.' + TAB + '[aria-selected="true"]'))).forEach(function (selectedTab) { |
| return selectedTab.removeAttribute(_constants.ARIA_SELECTED); |
| }); |
| tab.setAttribute(_constants.ARIA_SELECTED, 'true'); |
| } |
| }; |
| |
| var tabClickHandler = function tabClickHandler() { |
| _this3.toggleTab_(panel, tab, tabpanel); |
| selectTab(); |
| }; |
| |
| var tabFocusHandler = function tabFocusHandler() { |
| selectTab(); |
| }; |
| |
| var tabpanelClickHandler = function tabpanelClickHandler() { |
| selectTab(); |
| }; |
| |
| var tabpanelFocusHandler = function tabpanelFocusHandler() { |
| selectTab(); |
| }; |
| |
| var tabKeydownHandler = function tabKeydownHandler(e) { |
| |
| if (_this3.element_.hasAttribute('disabled')) { |
| return; |
| } |
| |
| if (e.keyCode === _constants.VK_END || e.keyCode === _constants.VK_HOME || e.keyCode === _constants.VK_ARROW_UP || e.keyCode === _constants.VK_ARROW_LEFT || e.keyCode === _constants.VK_ARROW_DOWN || e.keyCode === _constants.VK_ARROW_RIGHT) { |
| |
| var nextTab = null; |
| var keyCode = e.keyCode; |
| |
| if (keyCode === _constants.VK_HOME) { |
| nextTab = _this3.element_.querySelector('.' + PANEL + ':first-child > .' + TAB); |
| if (nextTab && nextTab.hasAttribute('disabled')) { |
| nextTab = null; |
| keyCode = _constants.VK_ARROW_DOWN; |
| } |
| } else if (keyCode === _constants.VK_END) { |
| nextTab = _this3.element_.querySelector('.' + PANEL + ':last-child > .' + TAB); |
| if (nextTab && nextTab.hasAttribute('disabled')) { |
| nextTab = null; |
| keyCode = _constants.VK_ARROW_UP; |
| } |
| } |
| |
| if (!nextTab) { |
| var nextPanel = panel; |
| |
| do { |
| if (keyCode === _constants.VK_ARROW_UP || keyCode === _constants.VK_ARROW_LEFT) { |
| nextPanel = nextPanel.previousElementSibling; |
| if (!nextPanel) { |
| nextPanel = _this3.element_.querySelector('.' + PANEL + ':last-child'); |
| } |
| if (nextPanel) { |
| nextTab = nextPanel.querySelector('.' + PANEL + ' > .' + TAB); |
| } |
| } else if (keyCode === _constants.VK_ARROW_DOWN || keyCode === _constants.VK_ARROW_RIGHT) { |
| nextPanel = nextPanel.nextElementSibling; |
| if (!nextPanel) { |
| nextPanel = _this3.element_.querySelector('.' + PANEL + ':first-child'); |
| } |
| if (nextPanel) { |
| nextTab = nextPanel.querySelector('.' + PANEL + ' > .' + TAB); |
| } |
| } |
| |
| if (nextTab && nextTab.hasAttribute('disabled')) { |
| nextTab = null; |
| } else { |
| break; |
| } |
| } while (nextPanel !== panel); |
| } |
| |
| if (nextTab) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| nextTab.focus(); |
| |
| // Workaround for JSDom testing: |
| // In JsDom 'element.focus()' does not trigger any focus event |
| if (!nextTab.hasAttribute(_constants.ARIA_SELECTED)) { |
| |
| [].concat((0, _toConsumableArray3.default)(_this3.element_.querySelectorAll('.' + TAB + '[aria-selected="true"]'))).forEach(function (selectedTab) { |
| return selectedTab.removeAttribute(_constants.ARIA_SELECTED); |
| }); |
| |
| nextTab.setAttribute(_constants.ARIA_SELECTED, 'true'); |
| } |
| } |
| } else if (e.keyCode === _constants.VK_ENTER || e.keyCode === _constants.VK_SPACE) { |
| e.preventDefault(); |
| e.stopPropagation(); |
| _this3.toggleTab_(panel, tab, tabpanel); |
| } |
| }; |
| |
| if (tab === null) { |
| throw new Error('There must be a tab element for each accordion panel.'); |
| } |
| |
| if (tabpanel === null) { |
| throw new Error('There must be a tabpanel element for each accordion panel.'); |
| } |
| |
| panel.setAttribute('role', PANEL_ROLE); |
| tab.setAttribute('role', TAB_ROLE); |
| tabpanel.setAttribute('role', TABPANEL_ROLE); |
| |
| if (tab.hasAttribute('disabled')) { |
| disableTab(); |
| } else { |
| enableTab(); |
| } |
| |
| if (this.element_.classList.contains(ACCORDION_HORIZONTAL)) { |
| calcMaxTabCaptionWidth(); |
| } |
| |
| if (this.element_.classList.contains(RIPPLE_EFFECT)) { |
| tab.classList.add(RIPPLE); |
| } |
| |
| if (this.element_.classList.contains(ANIMATION_EFFECT)) { |
| tabpanel.classList.add(ANIMATION); |
| } |
| |
| // Remove listeners, just in case ... |
| tab.removeEventListener('click', tabClickHandler); |
| tab.removeEventListener('focus', tabFocusHandler); |
| tab.removeEventListener('keydown', tabKeydownHandler); |
| tabpanel.removeEventListener('click', tabpanelClickHandler); |
| tabpanel.removeEventListener('focus', tabpanelFocusHandler); |
| |
| tab.addEventListener('click', tabClickHandler); |
| tab.addEventListener('focus', tabFocusHandler); |
| tab.addEventListener('keydown', tabKeydownHandler); |
| tabpanel.addEventListener('click', tabpanelClickHandler, true); |
| tabpanel.addEventListener('focus', tabpanelFocusHandler, true); |
| }; |
| MaterialExtAccordion.prototype['upgradeTab'] = MaterialExtAccordion.prototype.upgradeTab; |
| |
| /** |
| * Execute command |
| * @param detail |
| */ |
| MaterialExtAccordion.prototype.command = function (detail) { |
| var _this4 = this; |
| |
| var openTab = function openTab(tabElement) { |
| |
| if (tabElement === undefined) { |
| _this4.openTabs_(); |
| } else if (tabElement !== null) { |
| var _accordionPanelElemen2 = accordionPanelElements(tabElement), |
| panel = _accordionPanelElemen2.panel, |
| tab = _accordionPanelElemen2.tab, |
| tabpanel = _accordionPanelElemen2.tabpanel; |
| |
| if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() !== 'true') { |
| _this4.toggleTab_(panel, tab, tabpanel); |
| } |
| } |
| }; |
| |
| var closeTab = function closeTab(tabElement) { |
| if (tabElement === undefined) { |
| _this4.closeTabs_(); |
| } else if (tabElement !== null) { |
| var _accordionPanelElemen3 = accordionPanelElements(tabElement), |
| panel = _accordionPanelElemen3.panel, |
| tab = _accordionPanelElemen3.tab, |
| tabpanel = _accordionPanelElemen3.tabpanel; |
| |
| if (tab.getAttribute(_constants.ARIA_EXPANDED).toLowerCase() === 'true') { |
| _this4.toggleTab_(panel, tab, tabpanel); |
| } |
| } |
| }; |
| |
| var toggleTab = function toggleTab(tabElement) { |
| if (tabElement) { |
| var _accordionPanelElemen4 = accordionPanelElements(tabElement), |
| panel = _accordionPanelElemen4.panel, |
| tab = _accordionPanelElemen4.tab, |
| tabpanel = _accordionPanelElemen4.tabpanel; |
| |
| _this4.toggleTab_(panel, tab, tabpanel); |
| } |
| }; |
| |
| if (detail && detail.action) { |
| var action = detail.action, |
| target = detail.target; |
| |
| |
| switch (action.toLowerCase()) { |
| case 'open': |
| openTab(target); |
| break; |
| case 'close': |
| closeTab(target); |
| break; |
| case 'toggle': |
| toggleTab(target); |
| break; |
| case 'upgrade': |
| if (target) { |
| this.upgradeTab(target); |
| } |
| break; |
| default: |
| throw new Error('Unknown action "' + action + '". Action must be one of "open", "close", "toggle" or "upgrade"'); |
| } |
| } |
| }; |
| MaterialExtAccordion.prototype['command'] = MaterialExtAccordion.prototype.command; |
| |
| /** |
| * Initialize component |
| */ |
| MaterialExtAccordion.prototype.init = function () { |
| var _this5 = this; |
| |
| if (this.element_) { |
| // Do the init required for this component to work |
| if (!(this.element_.classList.contains(ACCORDION_HORIZONTAL) || this.element_.classList.contains(ACCORDION_VERTICAL))) { |
| throw new Error('Accordion must have one of the classes "' + ACCORDION_HORIZONTAL + '" or "' + ACCORDION_VERTICAL + '"'); |
| } |
| |
| this.element_.setAttribute('role', 'tablist'); |
| |
| if (!this.element_.hasAttribute(_constants.ARIA_MULTISELECTABLE)) { |
| this.element_.setAttribute(_constants.ARIA_MULTISELECTABLE, 'false'); |
| } |
| |
| this.element_.removeEventListener('command', this.commandHandler_); |
| this.element_.addEventListener('command', this.commandHandler_.bind(this), false); |
| |
| [].concat((0, _toConsumableArray3.default)(this.element_.querySelectorAll('.' + ACCORDION + ' > .' + PANEL))).forEach(function (panel) { |
| return _this5.upgradeTab(panel); |
| }); |
| |
| // Set upgraded flag |
| this.element_.classList.add(_constants.IS_UPGRADED); |
| } |
| }; |
| |
| /* |
| * Downgrade component |
| * E.g remove listeners and clean up resources |
| * |
| * Nothing to downgrade |
| * |
| MaterialExtAccordion.prototype.mdlDowngrade_ = function() { |
| 'use strict'; |
| console.log('***** MaterialExtAccordion.mdlDowngrade'); |
| }; |
| */ |
| |
| // The component registers itself. It can assume componentHandler is available |
| // in the global scope. |
| /* eslint no-undef: 0 */ |
| componentHandler.register({ |
| constructor: MaterialExtAccordion, |
| classAsString: 'MaterialExtAccordion', |
| cssClass: 'mdlext-js-accordion', |
| widget: true |
| }); |
| })(); /** |
| * @license |
| * Copyright 2016 Leif Olsen. All Rights Reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| * |
| * This code is built with Google Material Design Lite, |
| * which is Licensed under the Apache License, Version 2.0 |
| */ |
| |
| /** |
| * A WAI-ARIA friendly accordion component. |
| * An accordion is a collection of expandable panels associated with a common outer container. Panels consist |
| * of a header and an associated content region or tabpanel. The primary use of an Accordion is to present multiple sections |
| * of content on a single page without scrolling, where all of the sections are peers in the application or object hierarchy. |
| * The general look is similar to a tree where each root tree node is an expandable accordion header. The user navigates |
| * and makes the contents of each panel visible (or not) by interacting with the Accordion Header |
| */ |