275 lines
7.8 KiB
JavaScript
275 lines
7.8 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
!function($) {
|
||
|
|
||
|
/**
|
||
|
* AccordionMenu module.
|
||
|
* @module foundation.accordionMenu
|
||
|
* @requires foundation.util.keyboard
|
||
|
* @requires foundation.util.motion
|
||
|
* @requires foundation.util.nest
|
||
|
*/
|
||
|
|
||
|
class AccordionMenu {
|
||
|
/**
|
||
|
* Creates a new instance of an accordion menu.
|
||
|
* @class
|
||
|
* @fires AccordionMenu#init
|
||
|
* @param {jQuery} element - jQuery object to make into an accordion menu.
|
||
|
* @param {Object} options - Overrides to the default plugin settings.
|
||
|
*/
|
||
|
constructor(element, options) {
|
||
|
this.$element = element;
|
||
|
this.options = $.extend({}, AccordionMenu.defaults, this.$element.data(), options);
|
||
|
|
||
|
Foundation.Nest.Feather(this.$element, 'accordion');
|
||
|
|
||
|
this._init();
|
||
|
|
||
|
Foundation.registerPlugin(this, 'AccordionMenu');
|
||
|
Foundation.Keyboard.register('AccordionMenu', {
|
||
|
'ENTER': 'toggle',
|
||
|
'SPACE': 'toggle',
|
||
|
'ARROW_RIGHT': 'open',
|
||
|
'ARROW_UP': 'up',
|
||
|
'ARROW_DOWN': 'down',
|
||
|
'ARROW_LEFT': 'close',
|
||
|
'ESCAPE': 'closeAll',
|
||
|
'TAB': 'down',
|
||
|
'SHIFT_TAB': 'up'
|
||
|
});
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Initializes the accordion menu by hiding all nested menus.
|
||
|
* @private
|
||
|
*/
|
||
|
_init() {
|
||
|
this.$element.find('[data-submenu]').not('.is-active').slideUp(0);//.find('a').css('padding-left', '1rem');
|
||
|
this.$element.attr({
|
||
|
'role': 'tablist',
|
||
|
'aria-multiselectable': this.options.multiOpen
|
||
|
});
|
||
|
|
||
|
this.$menuLinks = this.$element.find('.is-accordion-submenu-parent');
|
||
|
this.$menuLinks.each(function(){
|
||
|
var linkId = this.id || Foundation.GetYoDigits(6, 'acc-menu-link'),
|
||
|
$elem = $(this),
|
||
|
$sub = $elem.children('[data-submenu]'),
|
||
|
subId = $sub[0].id || Foundation.GetYoDigits(6, 'acc-menu'),
|
||
|
isActive = $sub.hasClass('is-active');
|
||
|
$elem.attr({
|
||
|
'aria-controls': subId,
|
||
|
'aria-expanded': isActive,
|
||
|
'role': 'tab',
|
||
|
'id': linkId
|
||
|
});
|
||
|
$sub.attr({
|
||
|
'aria-labelledby': linkId,
|
||
|
'aria-hidden': !isActive,
|
||
|
'role': 'tabpanel',
|
||
|
'id': subId
|
||
|
});
|
||
|
});
|
||
|
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) {
|
||
|
$(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.children('[data-submenu]:visible').length) { // if previous element has open sub menu
|
||
|
$prevElement = $prevElement.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;
|
||
|
}
|
||
|
});
|
||
|
Foundation.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.attr('tabindex', -1).focus();
|
||
|
return true;
|
||
|
},
|
||
|
down: function() {
|
||
|
$nextElement.attr('tabindex', -1).focus();
|
||
|
return true;
|
||
|
},
|
||
|
toggle: function() {
|
||
|
if ($element.children('[data-submenu]').length) {
|
||
|
_this.toggle($element.children('[data-submenu]'));
|
||
|
}
|
||
|
},
|
||
|
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.$element.find('[data-submenu]').slideUp(this.options.slideSpeed);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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) {
|
||
|
var _this = this;
|
||
|
|
||
|
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})
|
||
|
.parent('.is-accordion-submenu-parent').attr({'aria-expanded': true});
|
||
|
|
||
|
//Foundation.Move(this.options.slideSpeed, $target, function() {
|
||
|
$target.slideDown(_this.options.slideSpeed, function () {
|
||
|
/**
|
||
|
* 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) {
|
||
|
var _this = this;
|
||
|
//Foundation.Move(this.options.slideSpeed, $target, function(){
|
||
|
$target.slideUp(_this.options.slideSpeed, function () {
|
||
|
/**
|
||
|
* Fires when the menu is done collapsing up.
|
||
|
* @event AccordionMenu#up
|
||
|
*/
|
||
|
_this.$element.trigger('up.zf.accordionMenu', [$target]);
|
||
|
});
|
||
|
//});
|
||
|
|
||
|
var $menus = $target.find('[data-submenu]').slideUp(0).addBack().attr('aria-hidden', true);
|
||
|
|
||
|
$menus.parent('.is-accordion-submenu-parent').attr('aria-expanded', false);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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');
|
||
|
|
||
|
Foundation.Nest.Burn(this.$element, 'accordion');
|
||
|
Foundation.unregisterPlugin(this);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
AccordionMenu.defaults = {
|
||
|
/**
|
||
|
* Amount of time to animate the opening of a submenu in ms.
|
||
|
* @option
|
||
|
* @example 250
|
||
|
*/
|
||
|
slideSpeed: 250,
|
||
|
/**
|
||
|
* Allow the menu to have multiple open panes.
|
||
|
* @option
|
||
|
* @example true
|
||
|
*/
|
||
|
multiOpen: true
|
||
|
};
|
||
|
|
||
|
// Window exports
|
||
|
Foundation.plugin(AccordionMenu, 'AccordionMenu');
|
||
|
|
||
|
}(jQuery);
|