447 lines
15 KiB
JavaScript
447 lines
15 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||
|
|
||
|
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||
|
|
||
|
!function ($) {
|
||
|
|
||
|
/**
|
||
|
* Dropdown module.
|
||
|
* @module foundation.dropdown
|
||
|
* @requires foundation.util.keyboard
|
||
|
* @requires foundation.util.box
|
||
|
* @requires foundation.util.triggers
|
||
|
*/
|
||
|
|
||
|
var Dropdown = function () {
|
||
|
/**
|
||
|
* Creates a new instance of a dropdown.
|
||
|
* @class
|
||
|
* @param {jQuery} element - jQuery object to make into a dropdown.
|
||
|
* Object should be of the dropdown panel, rather than its anchor.
|
||
|
* @param {Object} options - Overrides to the default plugin settings.
|
||
|
*/
|
||
|
|
||
|
function Dropdown(element, options) {
|
||
|
_classCallCheck(this, Dropdown);
|
||
|
|
||
|
this.$element = element;
|
||
|
this.options = $.extend({}, Dropdown.defaults, this.$element.data(), options);
|
||
|
this._init();
|
||
|
|
||
|
Foundation.registerPlugin(this, 'Dropdown');
|
||
|
Foundation.Keyboard.register('Dropdown', {
|
||
|
'ENTER': 'open',
|
||
|
'SPACE': 'open',
|
||
|
'ESCAPE': 'close',
|
||
|
'TAB': 'tab_forward',
|
||
|
'SHIFT_TAB': 'tab_backward'
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes the plugin by setting/checking options and attributes, adding helper variables, and saving the anchor.
|
||
|
* @function
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
|
||
|
_createClass(Dropdown, [{
|
||
|
key: '_init',
|
||
|
value: function _init() {
|
||
|
var $id = this.$element.attr('id');
|
||
|
|
||
|
this.$anchor = $('[data-toggle="' + $id + '"]') || $('[data-open="' + $id + '"]');
|
||
|
this.$anchor.attr({
|
||
|
'aria-controls': $id,
|
||
|
'data-is-focus': false,
|
||
|
'data-yeti-box': $id,
|
||
|
'aria-haspopup': true,
|
||
|
'aria-expanded': false
|
||
|
|
||
|
});
|
||
|
|
||
|
this.options.positionClass = this.getPositionClass();
|
||
|
this.counter = 4;
|
||
|
this.usedPositions = [];
|
||
|
this.$element.attr({
|
||
|
'aria-hidden': 'true',
|
||
|
'data-yeti-box': $id,
|
||
|
'data-resize': $id,
|
||
|
'aria-labelledby': this.$anchor[0].id || Foundation.GetYoDigits(6, 'dd-anchor')
|
||
|
});
|
||
|
this._events();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Helper function to determine current orientation of dropdown pane.
|
||
|
* @function
|
||
|
* @returns {String} position - string value of a position class.
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: 'getPositionClass',
|
||
|
value: function getPositionClass() {
|
||
|
var verticalPosition = this.$element[0].className.match(/(top|left|right|bottom)/g);
|
||
|
verticalPosition = verticalPosition ? verticalPosition[0] : '';
|
||
|
var horizontalPosition = /float-(\S+)\s/.exec(this.$anchor[0].className);
|
||
|
horizontalPosition = horizontalPosition ? horizontalPosition[1] : '';
|
||
|
var position = horizontalPosition ? horizontalPosition + ' ' + verticalPosition : verticalPosition;
|
||
|
return position;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adjusts the dropdown panes orientation by adding/removing positioning classes.
|
||
|
* @function
|
||
|
* @private
|
||
|
* @param {String} position - position class to remove.
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: '_reposition',
|
||
|
value: function _reposition(position) {
|
||
|
this.usedPositions.push(position ? position : 'bottom');
|
||
|
//default, try switching to opposite side
|
||
|
if (!position && this.usedPositions.indexOf('top') < 0) {
|
||
|
this.$element.addClass('top');
|
||
|
} else if (position === 'top' && this.usedPositions.indexOf('bottom') < 0) {
|
||
|
this.$element.removeClass(position);
|
||
|
} else if (position === 'left' && this.usedPositions.indexOf('right') < 0) {
|
||
|
this.$element.removeClass(position).addClass('right');
|
||
|
} else if (position === 'right' && this.usedPositions.indexOf('left') < 0) {
|
||
|
this.$element.removeClass(position).addClass('left');
|
||
|
}
|
||
|
|
||
|
//if default change didn't work, try bottom or left first
|
||
|
else if (!position && this.usedPositions.indexOf('top') > -1 && this.usedPositions.indexOf('left') < 0) {
|
||
|
this.$element.addClass('left');
|
||
|
} else if (position === 'top' && this.usedPositions.indexOf('bottom') > -1 && this.usedPositions.indexOf('left') < 0) {
|
||
|
this.$element.removeClass(position).addClass('left');
|
||
|
} else if (position === 'left' && this.usedPositions.indexOf('right') > -1 && this.usedPositions.indexOf('bottom') < 0) {
|
||
|
this.$element.removeClass(position);
|
||
|
} else if (position === 'right' && this.usedPositions.indexOf('left') > -1 && this.usedPositions.indexOf('bottom') < 0) {
|
||
|
this.$element.removeClass(position);
|
||
|
}
|
||
|
//if nothing cleared, set to bottom
|
||
|
else {
|
||
|
this.$element.removeClass(position);
|
||
|
}
|
||
|
this.classChanged = true;
|
||
|
this.counter--;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the position and orientation of the dropdown pane, checks for collisions.
|
||
|
* Recursively calls itself if a collision is detected, with a new position class.
|
||
|
* @function
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: '_setPosition',
|
||
|
value: function _setPosition() {
|
||
|
if (this.$anchor.attr('aria-expanded') === 'false') {
|
||
|
return false;
|
||
|
}
|
||
|
var position = this.getPositionClass(),
|
||
|
$eleDims = Foundation.Box.GetDimensions(this.$element),
|
||
|
$anchorDims = Foundation.Box.GetDimensions(this.$anchor),
|
||
|
_this = this,
|
||
|
direction = position === 'left' ? 'left' : position === 'right' ? 'left' : 'top',
|
||
|
param = direction === 'top' ? 'height' : 'width',
|
||
|
offset = param === 'height' ? this.options.vOffset : this.options.hOffset;
|
||
|
|
||
|
if ($eleDims.width >= $eleDims.windowDims.width || !this.counter && !Foundation.Box.ImNotTouchingYou(this.$element)) {
|
||
|
this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, 'center bottom', this.options.vOffset, this.options.hOffset, true)).css({
|
||
|
'width': $eleDims.windowDims.width - this.options.hOffset * 2,
|
||
|
'height': 'auto'
|
||
|
});
|
||
|
this.classChanged = true;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
this.$element.offset(Foundation.Box.GetOffsets(this.$element, this.$anchor, position, this.options.vOffset, this.options.hOffset));
|
||
|
|
||
|
while (!Foundation.Box.ImNotTouchingYou(this.$element, false, true) && this.counter) {
|
||
|
this._reposition(position);
|
||
|
this._setPosition();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds event listeners to the element utilizing the triggers utility library.
|
||
|
* @function
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: '_events',
|
||
|
value: function _events() {
|
||
|
var _this = this;
|
||
|
this.$element.on({
|
||
|
'open.zf.trigger': this.open.bind(this),
|
||
|
'close.zf.trigger': this.close.bind(this),
|
||
|
'toggle.zf.trigger': this.toggle.bind(this),
|
||
|
'resizeme.zf.trigger': this._setPosition.bind(this)
|
||
|
});
|
||
|
|
||
|
if (this.options.hover) {
|
||
|
this.$anchor.off('mouseenter.zf.dropdown mouseleave.zf.dropdown').on('mouseenter.zf.dropdown', function () {
|
||
|
clearTimeout(_this.timeout);
|
||
|
_this.timeout = setTimeout(function () {
|
||
|
_this.open();
|
||
|
_this.$anchor.data('hover', true);
|
||
|
}, _this.options.hoverDelay);
|
||
|
}).on('mouseleave.zf.dropdown', function () {
|
||
|
clearTimeout(_this.timeout);
|
||
|
_this.timeout = setTimeout(function () {
|
||
|
_this.close();
|
||
|
_this.$anchor.data('hover', false);
|
||
|
}, _this.options.hoverDelay);
|
||
|
});
|
||
|
if (this.options.hoverPane) {
|
||
|
this.$element.off('mouseenter.zf.dropdown mouseleave.zf.dropdown').on('mouseenter.zf.dropdown', function () {
|
||
|
clearTimeout(_this.timeout);
|
||
|
}).on('mouseleave.zf.dropdown', function () {
|
||
|
clearTimeout(_this.timeout);
|
||
|
_this.timeout = setTimeout(function () {
|
||
|
_this.close();
|
||
|
_this.$anchor.data('hover', false);
|
||
|
}, _this.options.hoverDelay);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
this.$anchor.add(this.$element).on('keydown.zf.dropdown', function (e) {
|
||
|
|
||
|
var $target = $(this),
|
||
|
visibleFocusableElements = Foundation.Keyboard.findFocusable(_this.$element);
|
||
|
|
||
|
Foundation.Keyboard.handleKey(e, 'Dropdown', {
|
||
|
tab_forward: function () {
|
||
|
if (_this.$element.find(':focus').is(visibleFocusableElements.eq(-1))) {
|
||
|
// left modal downwards, setting focus to first element
|
||
|
if (_this.options.trapFocus) {
|
||
|
// if focus shall be trapped
|
||
|
visibleFocusableElements.eq(0).focus();
|
||
|
e.preventDefault();
|
||
|
} else {
|
||
|
// if focus is not trapped, close dropdown on focus out
|
||
|
_this.close();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
tab_backward: function () {
|
||
|
if (_this.$element.find(':focus').is(visibleFocusableElements.eq(0)) || _this.$element.is(':focus')) {
|
||
|
// left modal upwards, setting focus to last element
|
||
|
if (_this.options.trapFocus) {
|
||
|
// if focus shall be trapped
|
||
|
visibleFocusableElements.eq(-1).focus();
|
||
|
e.preventDefault();
|
||
|
} else {
|
||
|
// if focus is not trapped, close dropdown on focus out
|
||
|
_this.close();
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
open: function () {
|
||
|
if ($target.is(_this.$anchor)) {
|
||
|
_this.open();
|
||
|
_this.$element.attr('tabindex', -1).focus();
|
||
|
e.preventDefault();
|
||
|
}
|
||
|
},
|
||
|
close: function () {
|
||
|
_this.close();
|
||
|
_this.$anchor.focus();
|
||
|
}
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Adds an event handler to the body to close any dropdowns on a click.
|
||
|
* @function
|
||
|
* @private
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: '_addBodyHandler',
|
||
|
value: function _addBodyHandler() {
|
||
|
var $body = $(document.body).not(this.$element),
|
||
|
_this = this;
|
||
|
$body.off('click.zf.dropdown').on('click.zf.dropdown', function (e) {
|
||
|
if (_this.$anchor.is(e.target) || _this.$anchor.find(e.target).length) {
|
||
|
return;
|
||
|
}
|
||
|
if (_this.$element.find(e.target).length) {
|
||
|
return;
|
||
|
}
|
||
|
_this.close();
|
||
|
$body.off('click.zf.dropdown');
|
||
|
});
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Opens the dropdown pane, and fires a bubbling event to close other dropdowns.
|
||
|
* @function
|
||
|
* @fires Dropdown#closeme
|
||
|
* @fires Dropdown#show
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: 'open',
|
||
|
value: function open() {
|
||
|
// var _this = this;
|
||
|
/**
|
||
|
* Fires to close other open dropdowns
|
||
|
* @event Dropdown#closeme
|
||
|
*/
|
||
|
this.$element.trigger('closeme.zf.dropdown', this.$element.attr('id'));
|
||
|
this.$anchor.addClass('hover').attr({ 'aria-expanded': true });
|
||
|
// this.$element/*.show()*/;
|
||
|
this._setPosition();
|
||
|
this.$element.addClass('is-open').attr({ 'aria-hidden': false });
|
||
|
|
||
|
if (this.options.autoFocus) {
|
||
|
var $focusable = Foundation.Keyboard.findFocusable(this.$element);
|
||
|
if ($focusable.length) {
|
||
|
$focusable.eq(0).focus();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (this.options.closeOnClick) {
|
||
|
this._addBodyHandler();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Fires once the dropdown is visible.
|
||
|
* @event Dropdown#show
|
||
|
*/
|
||
|
this.$element.trigger('show.zf.dropdown', [this.$element]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Closes the open dropdown pane.
|
||
|
* @function
|
||
|
* @fires Dropdown#hide
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: 'close',
|
||
|
value: function close() {
|
||
|
if (!this.$element.hasClass('is-open')) {
|
||
|
return false;
|
||
|
}
|
||
|
this.$element.removeClass('is-open').attr({ 'aria-hidden': true });
|
||
|
|
||
|
this.$anchor.removeClass('hover').attr('aria-expanded', false);
|
||
|
|
||
|
if (this.classChanged) {
|
||
|
var curPositionClass = this.getPositionClass();
|
||
|
if (curPositionClass) {
|
||
|
this.$element.removeClass(curPositionClass);
|
||
|
}
|
||
|
this.$element.addClass(this.options.positionClass)
|
||
|
/*.hide()*/.css({ height: '', width: '' });
|
||
|
this.classChanged = false;
|
||
|
this.counter = 4;
|
||
|
this.usedPositions.length = 0;
|
||
|
}
|
||
|
this.$element.trigger('hide.zf.dropdown', [this.$element]);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Toggles the dropdown pane's visibility.
|
||
|
* @function
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: 'toggle',
|
||
|
value: function toggle() {
|
||
|
if (this.$element.hasClass('is-open')) {
|
||
|
if (this.$anchor.data('hover')) return;
|
||
|
this.close();
|
||
|
} else {
|
||
|
this.open();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Destroys the dropdown.
|
||
|
* @function
|
||
|
*/
|
||
|
|
||
|
}, {
|
||
|
key: 'destroy',
|
||
|
value: function destroy() {
|
||
|
this.$element.off('.zf.trigger').hide();
|
||
|
this.$anchor.off('.zf.dropdown');
|
||
|
|
||
|
Foundation.unregisterPlugin(this);
|
||
|
}
|
||
|
}]);
|
||
|
|
||
|
return Dropdown;
|
||
|
}();
|
||
|
|
||
|
Dropdown.defaults = {
|
||
|
/**
|
||
|
* Amount of time to delay opening a submenu on hover event.
|
||
|
* @option
|
||
|
* @example 250
|
||
|
*/
|
||
|
hoverDelay: 250,
|
||
|
/**
|
||
|
* Allow submenus to open on hover events
|
||
|
* @option
|
||
|
* @example false
|
||
|
*/
|
||
|
hover: false,
|
||
|
/**
|
||
|
* Don't close dropdown when hovering over dropdown pane
|
||
|
* @option
|
||
|
* @example true
|
||
|
*/
|
||
|
hoverPane: false,
|
||
|
/**
|
||
|
* Number of pixels between the dropdown pane and the triggering element on open.
|
||
|
* @option
|
||
|
* @example 1
|
||
|
*/
|
||
|
vOffset: 1,
|
||
|
/**
|
||
|
* Number of pixels between the dropdown pane and the triggering element on open.
|
||
|
* @option
|
||
|
* @example 1
|
||
|
*/
|
||
|
hOffset: 1,
|
||
|
/**
|
||
|
* Class applied to adjust open position. JS will test and fill this in.
|
||
|
* @option
|
||
|
* @example 'top'
|
||
|
*/
|
||
|
positionClass: '',
|
||
|
/**
|
||
|
* Allow the plugin to trap focus to the dropdown pane if opened with keyboard commands.
|
||
|
* @option
|
||
|
* @example false
|
||
|
*/
|
||
|
trapFocus: false,
|
||
|
/**
|
||
|
* Allow the plugin to set focus to the first focusable element within the pane, regardless of method of opening.
|
||
|
* @option
|
||
|
* @example true
|
||
|
*/
|
||
|
autoFocus: false,
|
||
|
/**
|
||
|
* Allows a click on the body to close the dropdown.
|
||
|
* @option
|
||
|
* @example false
|
||
|
*/
|
||
|
closeOnClick: false
|
||
|
};
|
||
|
|
||
|
// Window exports
|
||
|
Foundation.plugin(Dropdown, 'Dropdown');
|
||
|
}(jQuery);
|