blob: 2d518caaefa62147f041eccb1ca50ca3b53e1cec [file] [log] [blame]
'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
*/