biofriction-wp-theme/node_modules/foundation-sites/js/foundation.accordionMenu.js

346 lines
9.8 KiB
JavaScript

'use strict';
import $ from 'jquery';
import { Keyboard } from './foundation.util.keyboard';
import { Nest } from './foundation.util.nest';
import { GetYoDigits } from './foundation.core.utils';
import { Plugin } from './foundation.core.plugin';
/**
* AccordionMenu module.
* @module foundation.accordionMenu
* @requires foundation.util.keyboard
* @requires foundation.util.nest
*/
class AccordionMenu extends Plugin {
/**
* Creates a new instance of an accordion menu.
* @class
* @name AccordionMenu
* @fires AccordionMenu#init
* @param {jQuery} element - jQuery object to make into an accordion menu.
* @param {Object} options - Overrides to the default plugin settings.
*/
_setup(element, options) {
this.$element = element;
this.options = $.extend({}, AccordionMenu.defaults, this.$element.data(), options);
this.className = 'AccordionMenu'; // ie9 back compat
this._init();
Keyboard.register('AccordionMenu', {
'ENTER': 'toggle',
'SPACE': 'toggle',
'ARROW_RIGHT': 'open',
'ARROW_UP': 'up',
'ARROW_DOWN': 'down',
'ARROW_LEFT': 'close',
'ESCAPE': 'closeAll'
});
}
/**
* Initializes the accordion menu by hiding all nested menus.
* @private
*/
_init() {
Nest.Feather(this.$element, 'accordion');
var _this = this;
this.$element.find('[data-submenu]').not('.is-active').slideUp(0);//.find('a').css('padding-left', '1rem');
this.$element.attr({
'role': 'tree',
'aria-multiselectable': this.options.multiOpen
});
this.$menuLinks = this.$element.find('.is-accordion-submenu-parent');
this.$menuLinks.each(function(){
var linkId = this.id || GetYoDigits(6, 'acc-menu-link'),
$elem = $(this),
$sub = $elem.children('[data-submenu]'),
subId = $sub[0].id || GetYoDigits(6, 'acc-menu'),
isActive = $sub.hasClass('is-active');
if(_this.options.parentLink) {
let $anchor = $elem.children('a');
$anchor.clone().prependTo($sub).wrap('<li data-is-parent-link class="is-submenu-parent-item is-submenu-item is-accordion-submenu-item"></li>');
}
if(_this.options.submenuToggle) {
$elem.addClass('has-submenu-toggle');
$elem.children('a').after('<button id="' + linkId + '" class="submenu-toggle" aria-controls="' + subId + '" aria-expanded="' + isActive + '" title="' + _this.options.submenuToggleText + '"><span class="submenu-toggle-text">' + _this.options.submenuToggleText + '</span></button>');
} else {
$elem.attr({
'aria-controls': subId,
'aria-expanded': isActive,
'id': linkId
});
}
$sub.attr({
'aria-labelledby': linkId,
'aria-hidden': !isActive,
'role': 'group',
'id': subId
});
});
this.$element.find('li').attr({
'role': 'treeitem'
});
var initPanes = this.$element.find('.is-active');
if(initPanes.length){
var _this = this;
initPanes.each(function(){
_this.down($(this));
});
}
this._events();
}
/**
* Adds event handlers for items within the menu.
* @private
*/
_events() {
var _this = this;
this.$element.find('li').each(function() {
var $submenu = $(this).children('[data-submenu]');
if ($submenu.length) {
if(_this.options.submenuToggle) {
$(this).children('.submenu-toggle').off('click.zf.accordionMenu').on('click.zf.accordionMenu', function(e) {
_this.toggle($submenu);
});
} else {
$(this).children('a').off('click.zf.accordionMenu').on('click.zf.accordionMenu', function(e) {
e.preventDefault();
_this.toggle($submenu);
});
}
}
}).on('keydown.zf.accordionmenu', function(e){
var $element = $(this),
$elements = $element.parent('ul').children('li'),
$prevElement,
$nextElement,
$target = $element.children('[data-submenu]');
$elements.each(function(i) {
if ($(this).is($element)) {
$prevElement = $elements.eq(Math.max(0, i-1)).find('a').first();
$nextElement = $elements.eq(Math.min(i+1, $elements.length-1)).find('a').first();
if ($(this).children('[data-submenu]:visible').length) { // has open sub menu
$nextElement = $element.find('li:first-child').find('a').first();
}
if ($(this).is(':first-child')) { // is first element of sub menu
$prevElement = $element.parents('li').first().find('a').first();
} else if ($prevElement.parents('li').first().children('[data-submenu]:visible').length) { // if previous element has open sub menu
$prevElement = $prevElement.parents('li').find('li:last-child').find('a').first();
}
if ($(this).is(':last-child')) { // is last element of sub menu
$nextElement = $element.parents('li').first().next('li').find('a').first();
}
return;
}
});
Keyboard.handleKey(e, 'AccordionMenu', {
open: function() {
if ($target.is(':hidden')) {
_this.down($target);
$target.find('li').first().find('a').first().focus();
}
},
close: function() {
if ($target.length && !$target.is(':hidden')) { // close active sub of this item
_this.up($target);
} else if ($element.parent('[data-submenu]').length) { // close currently open sub
_this.up($element.parent('[data-submenu]'));
$element.parents('li').first().find('a').first().focus();
}
},
up: function() {
$prevElement.focus();
return true;
},
down: function() {
$nextElement.focus();
return true;
},
toggle: function() {
if (_this.options.submenuToggle) {
return false;
}
if ($element.children('[data-submenu]').length) {
_this.toggle($element.children('[data-submenu]'));
return true;
}
},
closeAll: function() {
_this.hideAll();
},
handled: function(preventDefault) {
if (preventDefault) {
e.preventDefault();
}
e.stopImmediatePropagation();
}
});
});//.attr('tabindex', 0);
}
/**
* Closes all panes of the menu.
* @function
*/
hideAll() {
this.up(this.$element.find('[data-submenu]'));
}
/**
* Opens all panes of the menu.
* @function
*/
showAll() {
this.down(this.$element.find('[data-submenu]'));
}
/**
* Toggles the open/close state of a submenu.
* @function
* @param {jQuery} $target - the submenu to toggle
*/
toggle($target){
if(!$target.is(':animated')) {
if (!$target.is(':hidden')) {
this.up($target);
}
else {
this.down($target);
}
}
}
/**
* Opens the sub-menu defined by `$target`.
* @param {jQuery} $target - Sub-menu to open.
* @fires AccordionMenu#down
*/
down($target) {
if(!this.options.multiOpen) {
this.up(this.$element.find('.is-active').not($target.parentsUntil(this.$element).add($target)));
}
$target
.addClass('is-active')
.attr({ 'aria-hidden': false });
if(this.options.submenuToggle) {
$target.prev('.submenu-toggle').attr({'aria-expanded': true});
}
else {
$target.parent('.is-accordion-submenu-parent').attr({'aria-expanded': true});
}
$target.slideDown(this.options.slideSpeed, () => {
/**
* Fires when the menu is done opening.
* @event AccordionMenu#down
*/
this.$element.trigger('down.zf.accordionMenu', [$target]);
});
}
/**
* Closes the sub-menu defined by `$target`. All sub-menus inside the target will be closed as well.
* @param {jQuery} $target - Sub-menu to close.
* @fires AccordionMenu#up
*/
up($target) {
const $submenus = $target.find('[data-submenu]');
const $allmenus = $target.add($submenus);
$submenus.slideUp(0);
$allmenus
.removeClass('is-active')
.attr('aria-hidden', true);
if(this.options.submenuToggle) {
$allmenus.prev('.submenu-toggle').attr('aria-expanded', false);
}
else {
$allmenus.parent('.is-accordion-submenu-parent').attr('aria-expanded', false);
}
$target.slideUp(this.options.slideSpeed, () => {
/**
* Fires when the menu is done collapsing up.
* @event AccordionMenu#up
*/
this.$element.trigger('up.zf.accordionMenu', [$target]);
});
}
/**
* Destroys an instance of accordion menu.
* @fires AccordionMenu#destroyed
*/
_destroy() {
this.$element.find('[data-submenu]').slideDown(0).css('display', '');
this.$element.find('a').off('click.zf.accordionMenu');
this.$element.find('[data-is-parent-link]').detach();
if(this.options.submenuToggle) {
this.$element.find('.has-submenu-toggle').removeClass('has-submenu-toggle');
this.$element.find('.submenu-toggle').remove();
}
Nest.Burn(this.$element, 'accordion');
}
}
AccordionMenu.defaults = {
/**
* Adds the parent link to the submenu.
* @option
* @type {boolean}
* @default false
*/
parentLink: false,
/**
* Amount of time to animate the opening of a submenu in ms.
* @option
* @type {number}
* @default 250
*/
slideSpeed: 250,
/**
* Adds a separate submenu toggle button. This allows the parent item to have a link.
* @option
* @example true
*/
submenuToggle: false,
/**
* The text used for the submenu toggle if enabled. This is used for screen readers only.
* @option
* @example true
*/
submenuToggleText: 'Toggle menu',
/**
* Allow the menu to have multiple open panes.
* @option
* @type {boolean}
* @default true
*/
multiOpen: true
};
export {AccordionMenu};