Created
March 9, 2016 09:45
-
-
Save woraperth/78d05d815e651c25adb6 to your computer and use it in GitHub Desktop.
Foundation 6 ES5 version for Essential Download (Fixing "class is a reserved identifier" problem)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
'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 ($) { | |
'use strict'; | |
var FOUNDATION_VERSION = '6.2.0'; | |
// Global Foundation object | |
// This is attached to the window, or used as a module for AMD/Browserify | |
var Foundation = { | |
version: FOUNDATION_VERSION, | |
/** | |
* Stores initialized plugins. | |
*/ | |
_plugins: {}, | |
/** | |
* Stores generated unique ids for plugin instances | |
*/ | |
_uuids: [], | |
/** | |
* Returns a boolean for RTL support | |
*/ | |
rtl: function rtl() { | |
return $('html').attr('dir') === 'rtl'; | |
}, | |
/** | |
* Defines a Foundation plugin, adding it to the `Foundation` namespace and the list of plugins to initialize when reflowing. | |
* @param {Object} plugin - The constructor of the plugin. | |
*/ | |
plugin: function plugin(_plugin, name) { | |
// Object key to use when adding to global Foundation object | |
// Examples: Foundation.Reveal, Foundation.OffCanvas | |
var className = name || functionName(_plugin); | |
// Object key to use when storing the plugin, also used to create the identifying data attribute for the plugin | |
// Examples: data-reveal, data-off-canvas | |
var attrName = hyphenate(className); | |
// Add to the Foundation object and the plugins list (for reflowing) | |
this._plugins[attrName] = this[className] = _plugin; | |
}, | |
/** | |
* @function | |
* Populates the _uuids array with pointers to each individual plugin instance. | |
* Adds the `zfPlugin` data-attribute to programmatically created plugins to allow use of $(selector).foundation(method) calls. | |
* Also fires the initialization event for each plugin, consolidating repeditive code. | |
* @param {Object} plugin - an instance of a plugin, usually `this` in context. | |
* @param {String} name - the name of the plugin, passed as a camelCased string. | |
* @fires Plugin#init | |
*/ | |
registerPlugin: function registerPlugin(plugin, name) { | |
var pluginName = name ? hyphenate(name) : functionName(plugin.constructor).toLowerCase(); | |
plugin.uuid = this.GetYoDigits(6, pluginName); | |
if (!plugin.$element.attr('data-' + pluginName)) { | |
plugin.$element.attr('data-' + pluginName, plugin.uuid); | |
} | |
if (!plugin.$element.data('zfPlugin')) { | |
plugin.$element.data('zfPlugin', plugin); | |
} | |
/** | |
* Fires when the plugin has initialized. | |
* @event Plugin#init | |
*/ | |
plugin.$element.trigger('init.zf.' + pluginName); | |
this._uuids.push(plugin.uuid); | |
return; | |
}, | |
/** | |
* @function | |
* Removes the plugins uuid from the _uuids array. | |
* Removes the zfPlugin data attribute, as well as the data-plugin-name attribute. | |
* Also fires the destroyed event for the plugin, consolidating repeditive code. | |
* @param {Object} plugin - an instance of a plugin, usually `this` in context. | |
* @fires Plugin#destroyed | |
*/ | |
unregisterPlugin: function unregisterPlugin(plugin) { | |
var pluginName = hyphenate(functionName(plugin.$element.data('zfPlugin').constructor)); | |
this._uuids.splice(this._uuids.indexOf(plugin.uuid), 1); | |
plugin.$element.removeAttr('data-' + pluginName).removeData('zfPlugin') | |
/** | |
* Fires when the plugin has been destroyed. | |
* @event Plugin#destroyed | |
*/ | |
.trigger('destroyed.zf.' + pluginName); | |
for (var prop in plugin) { | |
plugin[prop] = null; //clean up script to prep for garbage collection. | |
} | |
return; | |
}, | |
/** | |
* @function | |
* Causes one or more active plugins to re-initialize, resetting event listeners, recalculating positions, etc. | |
* @param {String} plugins - optional string of an individual plugin key, attained by calling `$(element).data('pluginName')`, or string of a plugin class i.e. `'dropdown'` | |
* @default If no argument is passed, reflow all currently active plugins. | |
*/ | |
reInit: function reInit(plugins) { | |
var isJQ = plugins instanceof $; | |
try { | |
if (isJQ) { | |
plugins.each(function () { | |
$(this).data('zfPlugin')._init(); | |
}); | |
} else { | |
var type = typeof plugins, | |
_this = this, | |
fns = { | |
'object': function object(plgs) { | |
plgs.forEach(function (p) { | |
p = hyphenate(p); | |
$('[data-' + p + ']').foundation('_init'); | |
}); | |
}, | |
'string': function string() { | |
plugins = hyphenate(plugins); | |
$('[data-' + plugins + ']').foundation('_init'); | |
}, | |
'undefined': function undefined() { | |
this['object'](Object.keys(_this._plugins)); | |
} | |
}; | |
fns[type](plugins); | |
} | |
} catch (err) { | |
console.error(err); | |
} finally { | |
return plugins; | |
} | |
}, | |
/** | |
* returns a random base-36 uid with namespacing | |
* @function | |
* @param {Number} length - number of random base-36 digits desired. Increase for more random strings. | |
* @param {String} namespace - name of plugin to be incorporated in uid, optional. | |
* @default {String} '' - if no plugin name is provided, nothing is appended to the uid. | |
* @returns {String} - unique id | |
*/ | |
GetYoDigits: function GetYoDigits(length, namespace) { | |
length = length || 6; | |
return Math.round(Math.pow(36, length + 1) - Math.random() * Math.pow(36, length)).toString(36).slice(1) + (namespace ? '-' + namespace : ''); | |
}, | |
/** | |
* Initialize plugins on any elements within `elem` (and `elem` itself) that aren't already initialized. | |
* @param {Object} elem - jQuery object containing the element to check inside. Also checks the element itself, unless it's the `document` object. | |
* @param {String|Array} plugins - A list of plugins to initialize. Leave this out to initialize everything. | |
*/ | |
reflow: function reflow(elem, plugins) { | |
// If plugins is undefined, just grab everything | |
if (typeof plugins === 'undefined') { | |
plugins = Object.keys(this._plugins); | |
} | |
// If plugins is a string, convert it to an array with one item | |
else if (typeof plugins === 'string') { | |
plugins = [plugins]; | |
} | |
var _this = this; | |
// Iterate through each plugin | |
$.each(plugins, function (i, name) { | |
// Get the current plugin | |
var plugin = _this._plugins[name]; | |
// Localize the search to all elements inside elem, as well as elem itself, unless elem === document | |
var $elem = $(elem).find('[data-' + name + ']').addBack('[data-' + name + ']'); | |
// For each plugin found, initialize it | |
$elem.each(function () { | |
var $el = $(this), | |
opts = {}; | |
// Don't double-dip on plugins | |
if ($el.data('zfPlugin')) { | |
console.warn('Tried to initialize ' + name + ' on an element that already has a Foundation plugin.'); | |
return; | |
} | |
if ($el.attr('data-options')) { | |
var thing = $el.attr('data-options').split(';').forEach(function (e, i) { | |
var opt = e.split(':').map(function (el) { | |
return el.trim(); | |
}); | |
if (opt[0]) opts[opt[0]] = parseValue(opt[1]); | |
}); | |
} | |
try { | |
$el.data('zfPlugin', new plugin($(this), opts)); | |
} catch (er) { | |
console.error(er); | |
} finally { | |
return; | |
} | |
}); | |
}); | |
}, | |
getFnName: functionName, | |
transitionend: function transitionend($elem) { | |
var transitions = { | |
'transition': 'transitionend', | |
'WebkitTransition': 'webkitTransitionEnd', | |
'MozTransition': 'transitionend', | |
'OTransition': 'otransitionend' | |
}; | |
var elem = document.createElement('div'), | |
end; | |
for (var t in transitions) { | |
if (typeof elem.style[t] !== 'undefined') { | |
end = transitions[t]; | |
} | |
} | |
if (end) { | |
return end; | |
} else { | |
end = setTimeout(function () { | |
$elem.triggerHandler('transitionend', [$elem]); | |
}, 1); | |
return 'transitionend'; | |
} | |
} | |
}; | |
Foundation.util = { | |
/** | |
* Function for applying a debounce effect to a function call. | |
* @function | |
* @param {Function} func - Function to be called at end of timeout. | |
* @param {Number} delay - Time in ms to delay the call of `func`. | |
* @returns function | |
*/ | |
throttle: function throttle(func, delay) { | |
var timer = null; | |
return function () { | |
var context = this, | |
args = arguments; | |
if (timer === null) { | |
timer = setTimeout(function () { | |
func.apply(context, args); | |
timer = null; | |
}, delay); | |
} | |
}; | |
} | |
}; | |
// TODO: consider not making this a jQuery function | |
// TODO: need way to reflow vs. re-initialize | |
/** | |
* The Foundation jQuery method. | |
* @param {String|Array} method - An action to perform on the current jQuery object. | |
*/ | |
var foundation = function foundation(method) { | |
var type = typeof method, | |
$meta = $('meta.foundation-mq'), | |
$noJS = $('.no-js'); | |
if (!$meta.length) { | |
$('<meta class="foundation-mq">').appendTo(document.head); | |
} | |
if ($noJS.length) { | |
$noJS.removeClass('no-js'); | |
} | |
if (type === 'undefined') { | |
//needs to initialize the Foundation object, or an individual plugin. | |
Foundation.MediaQuery._init(); | |
Foundation.reflow(this); | |
} else if (type === 'string') { | |
//an individual method to invoke on a plugin or group of plugins | |
var args = Array.prototype.slice.call(arguments, 1); //collect all the arguments, if necessary | |
var plugClass = this.data('zfPlugin'); //determine the class of plugin | |
if (plugClass !== undefined && plugClass[method] !== undefined) { | |
//make sure both the class and method exist | |
if (this.length === 1) { | |
//if there's only one, call it directly. | |
plugClass[method].apply(plugClass, args); | |
} else { | |
this.each(function (i, el) { | |
//otherwise loop through the jQuery collection and invoke the method on each | |
plugClass[method].apply($(el).data('zfPlugin'), args); | |
}); | |
} | |
} else { | |
//error for no class or no method | |
throw new ReferenceError('We\'re sorry, \'' + method + '\' is not an available method for ' + (plugClass ? functionName(plugClass) : 'this element') + '.'); | |
} | |
} else { | |
//error for invalid argument type | |
throw new TypeError('We\'re sorry, ' + type + ' is not a valid parameter. You must use a string representing the method you wish to invoke.'); | |
} | |
return this; | |
}; | |
window.Foundation = Foundation; | |
$.fn.foundation = foundation; | |
// Polyfill for requestAnimationFrame | |
(function () { | |
if (!Date.now || !window.Date.now) window.Date.now = Date.now = function () { | |
return new Date().getTime(); | |
}; | |
var vendors = ['webkit', 'moz']; | |
for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { | |
var vp = vendors[i]; | |
window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; | |
window.cancelAnimationFrame = window[vp + 'CancelAnimationFrame'] || window[vp + 'CancelRequestAnimationFrame']; | |
} | |
if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) || !window.requestAnimationFrame || !window.cancelAnimationFrame) { | |
var lastTime = 0; | |
window.requestAnimationFrame = function (callback) { | |
var now = Date.now(); | |
var nextTime = Math.max(lastTime + 16, now); | |
return setTimeout(function () { | |
callback(lastTime = nextTime); | |
}, nextTime - now); | |
}; | |
window.cancelAnimationFrame = clearTimeout; | |
} | |
/** | |
* Polyfill for performance.now, required by rAF | |
*/ | |
if (!window.performance || !window.performance.now) { | |
window.performance = { | |
start: Date.now(), | |
now: function now() { | |
return Date.now() - this.start; | |
} | |
}; | |
} | |
})(); | |
if (!Function.prototype.bind) { | |
Function.prototype.bind = function (oThis) { | |
if (typeof this !== 'function') { | |
// closest thing possible to the ECMAScript 5 | |
// internal IsCallable function | |
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); | |
} | |
var aArgs = Array.prototype.slice.call(arguments, 1), | |
fToBind = this, | |
fNOP = function fNOP() {}, | |
fBound = function fBound() { | |
return fToBind.apply(this instanceof fNOP ? this : oThis, aArgs.concat(Array.prototype.slice.call(arguments))); | |
}; | |
if (this.prototype) { | |
// native functions don't have a prototype | |
fNOP.prototype = this.prototype; | |
} | |
fBound.prototype = new fNOP(); | |
return fBound; | |
}; | |
} | |
// Polyfill to get the name of a function in IE9 | |
function functionName(fn) { | |
if (Function.prototype.name === undefined) { | |
var funcNameRegex = /function\s([^(]{1,})\(/; | |
var results = funcNameRegex.exec(fn.toString()); | |
return results && results.length > 1 ? results[1].trim() : ''; | |
} else if (fn.prototype === undefined) { | |
return fn.constructor.name; | |
} else { | |
return fn.prototype.constructor.name; | |
} | |
} | |
function parseValue(str) { | |
if (/true/.test(str)) return true;else if (/false/.test(str)) return false;else if (!isNaN(str * 1)) return parseFloat(str); | |
return str; | |
} | |
// Convert PascalCase to kebab-case | |
// Thank you: http://stackoverflow.com/a/8955580 | |
function hyphenate(str) { | |
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); | |
} | |
})(jQuery); | |
'use strict'; | |
!(function ($) { | |
Foundation.Box = { | |
ImNotTouchingYou: ImNotTouchingYou, | |
GetDimensions: GetDimensions, | |
GetOffsets: GetOffsets | |
}; | |
/** | |
* Compares the dimensions of an element to a container and determines collision events with container. | |
* @function | |
* @param {jQuery} element - jQuery object to test for collisions. | |
* @param {jQuery} parent - jQuery object to use as bounding container. | |
* @param {Boolean} lrOnly - set to true to check left and right values only. | |
* @param {Boolean} tbOnly - set to true to check top and bottom values only. | |
* @default if no parent object passed, detects collisions with `window`. | |
* @returns {Boolean} - true if collision free, false if a collision in any direction. | |
*/ | |
function ImNotTouchingYou(element, parent, lrOnly, tbOnly) { | |
var eleDims = GetDimensions(element), | |
top, | |
bottom, | |
left, | |
right; | |
if (parent) { | |
var parDims = GetDimensions(parent); | |
bottom = eleDims.offset.top + eleDims.height <= parDims.height + parDims.offset.top; | |
top = eleDims.offset.top >= parDims.offset.top; | |
left = eleDims.offset.left >= parDims.offset.left; | |
right = eleDims.offset.left + eleDims.width <= parDims.width; | |
} else { | |
bottom = eleDims.offset.top + eleDims.height <= eleDims.windowDims.height + eleDims.windowDims.offset.top; | |
top = eleDims.offset.top >= eleDims.windowDims.offset.top; | |
left = eleDims.offset.left >= eleDims.windowDims.offset.left; | |
right = eleDims.offset.left + eleDims.width <= eleDims.windowDims.width; | |
} | |
var allDirs = [bottom, top, left, right]; | |
if (lrOnly) { | |
return left === right === true; | |
} | |
if (tbOnly) { | |
return top === bottom === true; | |
} | |
return allDirs.indexOf(false) === -1; | |
}; | |
/** | |
* Uses native methods to return an object of dimension values. | |
* @function | |
* @param {jQuery || HTML} element - jQuery object or DOM element for which to get the dimensions. Can be any element other that document or window. | |
* @returns {Object} - nested object of integer pixel values | |
* TODO - if element is window, return only those values. | |
*/ | |
function GetDimensions(elem, test) { | |
elem = elem.length ? elem[0] : elem; | |
if (elem === window || elem === document) { | |
throw new Error('I\'m sorry, Dave. I\'m afraid I can\'t do that.'); | |
} | |
var rect = elem.getBoundingClientRect(), | |
parRect = elem.parentNode.getBoundingClientRect(), | |
winRect = document.body.getBoundingClientRect(), | |
winY = window.pageYOffset, | |
winX = window.pageXOffset; | |
return { | |
width: rect.width, | |
height: rect.height, | |
offset: { | |
top: rect.top + winY, | |
left: rect.left + winX | |
}, | |
parentDims: { | |
width: parRect.width, | |
height: parRect.height, | |
offset: { | |
top: parRect.top + winY, | |
left: parRect.left + winX | |
} | |
}, | |
windowDims: { | |
width: winRect.width, | |
height: winRect.height, | |
offset: { | |
top: winY, | |
left: winX | |
} | |
} | |
}; | |
} | |
/** | |
* Returns an object of top and left integer pixel values for dynamically rendered elements, | |
* such as: Tooltip, Reveal, and Dropdown | |
* @function | |
* @param {jQuery} element - jQuery object for the element being positioned. | |
* @param {jQuery} anchor - jQuery object for the element's anchor point. | |
* @param {String} position - a string relating to the desired position of the element, relative to it's anchor | |
* @param {Number} vOffset - integer pixel value of desired vertical separation between anchor and element. | |
* @param {Number} hOffset - integer pixel value of desired horizontal separation between anchor and element. | |
* @param {Boolean} isOverflow - if a collision event is detected, sets to true to default the element to full width - any desired offset. | |
* TODO alter/rewrite to work with `em` values as well/instead of pixels | |
*/ | |
function GetOffsets(element, anchor, position, vOffset, hOffset, isOverflow) { | |
var $eleDims = GetDimensions(element), | |
$anchorDims = anchor ? GetDimensions(anchor) : null; | |
switch (position) { | |
case 'top': | |
return { | |
left: Foundation.rtl() ? $anchorDims.offset.left - $eleDims.width + $anchorDims.width : $anchorDims.offset.left, | |
top: $anchorDims.offset.top - ($eleDims.height + vOffset) | |
}; | |
break; | |
case 'left': | |
return { | |
left: $anchorDims.offset.left - ($eleDims.width + hOffset), | |
top: $anchorDims.offset.top | |
}; | |
break; | |
case 'right': | |
return { | |
left: $anchorDims.offset.left + $anchorDims.width + hOffset, | |
top: $anchorDims.offset.top | |
}; | |
break; | |
case 'center top': | |
return { | |
left: $anchorDims.offset.left + $anchorDims.width / 2 - $eleDims.width / 2, | |
top: $anchorDims.offset.top - ($eleDims.height + vOffset) | |
}; | |
break; | |
case 'center bottom': | |
return { | |
left: isOverflow ? hOffset : $anchorDims.offset.left + $anchorDims.width / 2 - $eleDims.width / 2, | |
top: $anchorDims.offset.top + $anchorDims.height + vOffset | |
}; | |
break; | |
case 'center left': | |
return { | |
left: $anchorDims.offset.left - ($eleDims.width + hOffset), | |
top: $anchorDims.offset.top + $anchorDims.height / 2 - $eleDims.height / 2 | |
}; | |
break; | |
case 'center right': | |
return { | |
left: $anchorDims.offset.left + $anchorDims.width + hOffset + 1, | |
top: $anchorDims.offset.top + $anchorDims.height / 2 - $eleDims.height / 2 | |
}; | |
break; | |
case 'center': | |
return { | |
left: $eleDims.windowDims.offset.left + $eleDims.windowDims.width / 2 - $eleDims.width / 2, | |
top: $eleDims.windowDims.offset.top + $eleDims.windowDims.height / 2 - $eleDims.height / 2 | |
}; | |
break; | |
case 'reveal': | |
return { | |
left: ($eleDims.windowDims.width - $eleDims.width) / 2, | |
top: $eleDims.windowDims.offset.top + vOffset | |
}; | |
case 'reveal full': | |
return { | |
left: $eleDims.windowDims.offset.left, | |
top: $eleDims.windowDims.offset.top | |
}; | |
break; | |
default: | |
return { | |
left: Foundation.rtl() ? $anchorDims.offset.left - $eleDims.width + $anchorDims.width : $anchorDims.offset.left, | |
top: $anchorDims.offset.top + $anchorDims.height + vOffset | |
}; | |
} | |
} | |
})(jQuery); | |
/******************************************* | |
* * | |
* This util was created by Marius Olbertz * | |
* Please thank Marius on GitHub /owlbertz * | |
* or the web http://www.mariusolbertz.de/ * | |
* * | |
******************************************/ | |
'use strict'; | |
!(function ($) { | |
var keyCodes = { | |
9: 'TAB', | |
13: 'ENTER', | |
27: 'ESCAPE', | |
32: 'SPACE', | |
37: 'ARROW_LEFT', | |
38: 'ARROW_UP', | |
39: 'ARROW_RIGHT', | |
40: 'ARROW_DOWN' | |
}; | |
var commands = {}; | |
var Keyboard = { | |
keys: getKeyCodes(keyCodes), | |
/** | |
* Parses the (keyboard) event and returns a String that represents its key | |
* Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE | |
* @param {Event} event - the event generated by the event handler | |
* @return String key - String that represents the key pressed | |
*/ | |
parseKey: function parseKey(event) { | |
var key = keyCodes[event.which || event.keyCode] || String.fromCharCode(event.which).toUpperCase(); | |
if (event.shiftKey) key = 'SHIFT_' + key; | |
if (event.ctrlKey) key = 'CTRL_' + key; | |
if (event.altKey) key = 'ALT_' + key; | |
return key; | |
}, | |
/** | |
* Handles the given (keyboard) event | |
* @param {Event} event - the event generated by the event handler | |
* @param {String} component - Foundation component's name, e.g. Slider or Reveal | |
* @param {Objects} functions - collection of functions that are to be executed | |
*/ | |
handleKey: function handleKey(event, component, functions) { | |
var commandList = commands[component], | |
keyCode = this.parseKey(event), | |
cmds, | |
command, | |
fn; | |
if (!commandList) return console.warn('Component not defined!'); | |
if (typeof commandList.ltr === 'undefined') { | |
// this component does not differentiate between ltr and rtl | |
cmds = commandList; // use plain list | |
} else { | |
// merge ltr and rtl: if document is rtl, rtl overwrites ltr and vice versa | |
if (Foundation.rtl()) cmds = $.extend({}, commandList.ltr, commandList.rtl);else cmds = $.extend({}, commandList.rtl, commandList.ltr); | |
} | |
command = cmds[keyCode]; | |
fn = functions[command]; | |
if (fn && typeof fn === 'function') { | |
// execute function if exists | |
fn.apply(); | |
if (functions.handled || typeof functions.handled === 'function') { | |
// execute function when event was handled | |
functions.handled.apply(); | |
} | |
} else { | |
if (functions.unhandled || typeof functions.unhandled === 'function') { | |
// execute function when event was not handled | |
functions.unhandled.apply(); | |
} | |
} | |
}, | |
/** | |
* Finds all focusable elements within the given `$element` | |
* @param {jQuery} $element - jQuery object to search within | |
* @return {jQuery} $focusable - all focusable elements within `$element` | |
*/ | |
findFocusable: function findFocusable($element) { | |
return $element.find('a[href], area[href], input:not([disabled]), select:not([disabled]), textarea:not([disabled]), button:not([disabled]), iframe, object, embed, *[tabindex], *[contenteditable]').filter(function () { | |
if (!$(this).is(':visible') || $(this).attr('tabindex') < 0) { | |
return false; | |
} //only have visible elements and those that have a tabindex greater or equal 0 | |
return true; | |
}); | |
}, | |
/** | |
* Returns the component name name | |
* @param {Object} component - Foundation component, e.g. Slider or Reveal | |
* @return String componentName | |
*/ | |
register: function register(componentName, cmds) { | |
commands[componentName] = cmds; | |
} | |
}; | |
/* | |
* Constants for easier comparing. | |
* Can be used like Foundation.parseKey(event) === Foundation.keys.SPACE | |
*/ | |
function getKeyCodes(kcs) { | |
var k = {}; | |
for (var kc in kcs) k[kcs[kc]] = kcs[kc]; | |
return k; | |
} | |
Foundation.Keyboard = Keyboard; | |
})(jQuery); | |
'use strict'; | |
!(function ($) { | |
// Default set of media queries | |
var defaultQueries = { | |
'default': 'only screen', | |
landscape: 'only screen and (orientation: landscape)', | |
portrait: 'only screen and (orientation: portrait)', | |
retina: 'only screen and (-webkit-min-device-pixel-ratio: 2),' + 'only screen and (min--moz-device-pixel-ratio: 2),' + 'only screen and (-o-min-device-pixel-ratio: 2/1),' + 'only screen and (min-device-pixel-ratio: 2),' + 'only screen and (min-resolution: 192dpi),' + 'only screen and (min-resolution: 2dppx)' | |
}; | |
var MediaQuery = { | |
queries: [], | |
current: '', | |
/** | |
* Initializes the media query helper, by extracting the breakpoint list from the CSS and activating the breakpoint watcher. | |
* @function | |
* @private | |
*/ | |
_init: function _init() { | |
var self = this; | |
var extractedStyles = $('.foundation-mq').css('font-family'); | |
var namedQueries; | |
namedQueries = parseStyleToObject(extractedStyles); | |
for (var key in namedQueries) { | |
self.queries.push({ | |
name: key, | |
value: 'only screen and (min-width: ' + namedQueries[key] + ')' | |
}); | |
} | |
this.current = this._getCurrentSize(); | |
this._watcher(); | |
}, | |
/** | |
* Checks if the screen is at least as wide as a breakpoint. | |
* @function | |
* @param {String} size - Name of the breakpoint to check. | |
* @returns {Boolean} `true` if the breakpoint matches, `false` if it's smaller. | |
*/ | |
atLeast: function atLeast(size) { | |
var query = this.get(size); | |
if (query) { | |
return window.matchMedia(query).matches; | |
} | |
return false; | |
}, | |
/** | |
* Gets the media query of a breakpoint. | |
* @function | |
* @param {String} size - Name of the breakpoint to get. | |
* @returns {String|null} - The media query of the breakpoint, or `null` if the breakpoint doesn't exist. | |
*/ | |
get: function get(size) { | |
for (var i in this.queries) { | |
var query = this.queries[i]; | |
if (size === query.name) return query.value; | |
} | |
return null; | |
}, | |
/** | |
* Gets the current breakpoint name by testing every breakpoint and returning the last one to match (the biggest one). | |
* @function | |
* @private | |
* @returns {String} Name of the current breakpoint. | |
*/ | |
_getCurrentSize: function _getCurrentSize() { | |
var matched; | |
for (var i in this.queries) { | |
var query = this.queries[i]; | |
if (window.matchMedia(query.value).matches) { | |
matched = query; | |
} | |
} | |
if (typeof matched === 'object') { | |
return matched.name; | |
} else { | |
return matched; | |
} | |
}, | |
/** | |
* Activates the breakpoint watcher, which fires an event on the window whenever the breakpoint changes. | |
* @function | |
* @private | |
*/ | |
_watcher: function _watcher() { | |
var _this2 = this; | |
$(window).on('resize.zf.mediaquery', function () { | |
var newSize = _this2._getCurrentSize(); | |
if (newSize !== _this2.current) { | |
// Broadcast the media query change on the window | |
$(window).trigger('changed.zf.mediaquery', [newSize, _this2.current]); | |
// Change the current media query | |
_this2.current = newSize; | |
} | |
}); | |
} | |
}; | |
Foundation.MediaQuery = MediaQuery; | |
// matchMedia() polyfill - Test a CSS media type/query in JS. | |
// Authors & copyright (c) 2012: Scott Jehl, Paul Irish, Nicholas Zakas, David Knight. Dual MIT/BSD license | |
window.matchMedia || (window.matchMedia = (function () { | |
'use strict'; | |
// For browsers that support matchMedium api such as IE 9 and webkit | |
var styleMedia = window.styleMedia || window.media; | |
// For those that don't support matchMedium | |
if (!styleMedia) { | |
var style = document.createElement('style'), | |
script = document.getElementsByTagName('script')[0], | |
info = null; | |
style.type = 'text/css'; | |
style.id = 'matchmediajs-test'; | |
script.parentNode.insertBefore(style, script); | |
// 'style.currentStyle' is used by IE <= 8 and 'window.getComputedStyle' for all other browsers | |
info = 'getComputedStyle' in window && window.getComputedStyle(style, null) || style.currentStyle; | |
styleMedia = { | |
matchMedium: function matchMedium(media) { | |
var text = '@media ' + media + '{ #matchmediajs-test { width: 1px; } }'; | |
// 'style.styleSheet' is used by IE <= 8 and 'style.textContent' for all other browsers | |
if (style.styleSheet) { | |
style.styleSheet.cssText = text; | |
} else { | |
style.textContent = text; | |
} | |
// Test if media query is true or false | |
return info.width === '1px'; | |
} | |
}; | |
} | |
return function (media) { | |
return { | |
matches: styleMedia.matchMedium(media || 'all'), | |
media: media || 'all' | |
}; | |
}; | |
})()); | |
// Thank you: https://github.com/sindresorhus/query-string | |
function parseStyleToObject(str) { | |
var styleObject = {}; | |
if (typeof str !== 'string') { | |
return styleObject; | |
} | |
str = str.trim().slice(1, -1); // browsers re-quote string style values | |
if (!str) { | |
return styleObject; | |
} | |
styleObject = str.split('&').reduce(function (ret, param) { | |
var parts = param.replace(/\+/g, ' ').split('='); | |
var key = parts[0]; | |
var val = parts[1]; | |
key = decodeURIComponent(key); | |
// missing `=` should be `null`: | |
// http://w3.org/TR/2012/WD-url-20120524/#collect-url-parameters | |
val = val === undefined ? null : decodeURIComponent(val); | |
if (!ret.hasOwnProperty(key)) { | |
ret[key] = val; | |
} else if (Array.isArray(ret[key])) { | |
ret[key].push(val); | |
} else { | |
ret[key] = [ret[key], val]; | |
} | |
return ret; | |
}, {}); | |
return styleObject; | |
} | |
Foundation.MediaQuery = MediaQuery; | |
})(jQuery); | |
'use strict'; | |
!(function ($) { | |
/** | |
* Motion module. | |
* @module foundation.motion | |
*/ | |
var initClasses = ['mui-enter', 'mui-leave']; | |
var activeClasses = ['mui-enter-active', 'mui-leave-active']; | |
var Motion = { | |
animateIn: function animateIn(element, animation, cb) { | |
animate(true, element, animation, cb); | |
}, | |
animateOut: function animateOut(element, animation, cb) { | |
animate(false, element, animation, cb); | |
} | |
}; | |
function Move(duration, elem, fn) { | |
var anim, | |
prog, | |
start = null; | |
// console.log('called'); | |
function move(ts) { | |
if (!start) start = window.performance.now(); | |
// console.log(start, ts); | |
prog = ts - start; | |
fn.apply(elem); | |
if (prog < duration) { | |
anim = window.requestAnimationFrame(move, elem); | |
} else { | |
window.cancelAnimationFrame(anim); | |
elem.trigger('finished.zf.animate', [elem]).triggerHandler('finished.zf.animate', [elem]); | |
} | |
} | |
anim = window.requestAnimationFrame(move); | |
} | |
/** | |
* Animates an element in or out using a CSS transition class. | |
* @function | |
* @private | |
* @param {Boolean} isIn - Defines if the animation is in or out. | |
* @param {Object} element - jQuery or HTML object to animate. | |
* @param {String} animation - CSS class to use. | |
* @param {Function} cb - Callback to run when animation is finished. | |
*/ | |
function animate(isIn, element, animation, cb) { | |
element = $(element).eq(0); | |
if (!element.length) return; | |
var initClass = isIn ? initClasses[0] : initClasses[1]; | |
var activeClass = isIn ? activeClasses[0] : activeClasses[1]; | |
// Set up the animation | |
reset(); | |
element.addClass(animation).css('transition', 'none'); | |
requestAnimationFrame(function () { | |
element.addClass(initClass); | |
if (isIn) element.show(); | |
}); | |
// Start the animation | |
requestAnimationFrame(function () { | |
element[0].offsetWidth; | |
element.css('transition', '').addClass(activeClass); | |
}); | |
// Clean up the animation when it finishes | |
element.one(Foundation.transitionend(element), finish); | |
// Hides the element (for out animations), resets the element, and runs a callback | |
function finish() { | |
if (!isIn) element.hide(); | |
reset(); | |
if (cb) cb.apply(element); | |
} | |
// Resets transitions and removes motion-specific classes | |
function reset() { | |
element[0].style.transitionDuration = 0; | |
element.removeClass(initClass + ' ' + activeClass + ' ' + animation); | |
} | |
} | |
Foundation.Move = Move; | |
Foundation.Motion = Motion; | |
})(jQuery); | |
'use strict'; | |
!(function ($) { | |
var Nest = { | |
Feather: function Feather(menu) { | |
var type = arguments.length <= 1 || arguments[1] === undefined ? 'zf' : arguments[1]; | |
menu.attr('role', 'menubar'); | |
var items = menu.find('li').attr({ 'role': 'menuitem' }), | |
subMenuClass = 'is-' + type + '-submenu', | |
subItemClass = subMenuClass + '-item', | |
hasSubClass = 'is-' + type + '-submenu-parent'; | |
menu.find('a:first').attr('tabindex', 0); | |
items.each(function () { | |
var $item = $(this), | |
$sub = $item.children('ul'); | |
if ($sub.length) { | |
$item.addClass(hasSubClass).attr({ | |
'aria-haspopup': true, | |
'aria-expanded': false, | |
'aria-label': $item.children('a:first').text() | |
}); | |
$sub.addClass('submenu ' + subMenuClass).attr({ | |
'data-submenu': '', | |
'aria-hidden': true, | |
'role': 'menu' | |
}); | |
} | |
if ($item.parent('[data-submenu]').length) { | |
$item.addClass('is-submenu-item ' + subItemClass); | |
} | |
}); | |
return; | |
}, | |
Burn: function Burn(menu, type) { | |
var items = menu.find('li').removeAttr('tabindex'), | |
subMenuClass = 'is-' + type + '-submenu', | |
subItemClass = subMenuClass + '-item', | |
hasSubClass = 'is-' + type + '-submenu-parent'; | |
menu.find('*').removeClass(subMenuClass + ' ' + subItemClass + ' ' + hasSubClass + ' is-submenu-item submenu is-active').removeAttr('data-submenu').css('display', ''); | |
// console.log( menu.find('.' + subMenuClass + ', .' + subItemClass + ', .has-submenu, .is-submenu-item, .submenu, [data-submenu]') | |
// .removeClass(subMenuClass + ' ' + subItemClass + ' has-submenu is-submenu-item submenu') | |
// .removeAttr('data-submenu')); | |
// items.each(function(){ | |
// var $item = $(this), | |
// $sub = $item.children('ul'); | |
// if($item.parent('[data-submenu]').length){ | |
// $item.removeClass('is-submenu-item ' + subItemClass); | |
// } | |
// if($sub.length){ | |
// $item.removeClass('has-submenu'); | |
// $sub.removeClass('submenu ' + subMenuClass).removeAttr('data-submenu'); | |
// } | |
// }); | |
} | |
}; | |
Foundation.Nest = Nest; | |
})(jQuery); | |
'use strict'; | |
!(function ($) { | |
function Timer(elem, options, cb) { | |
var _this = this, | |
duration = options.duration, | |
//options is an object for easily adding features later. | |
nameSpace = Object.keys(elem.data())[0] || 'timer', | |
remain = -1, | |
start, | |
timer; | |
this.isPaused = false; | |
this.restart = function () { | |
remain = -1; | |
clearTimeout(timer); | |
this.start(); | |
}; | |
this.start = function () { | |
this.isPaused = false; | |
// if(!elem.data('paused')){ return false; }//maybe implement this sanity check if used for other things. | |
clearTimeout(timer); | |
remain = remain <= 0 ? duration : remain; | |
elem.data('paused', false); | |
start = Date.now(); | |
timer = setTimeout(function () { | |
if (options.infinite) { | |
_this.restart(); //rerun the timer. | |
} | |
cb(); | |
}, remain); | |
elem.trigger('timerstart.zf.' + nameSpace); | |
}; | |
this.pause = function () { | |
this.isPaused = true; | |
//if(elem.data('paused')){ return false; }//maybe implement this sanity check if used for other things. | |
clearTimeout(timer); | |
elem.data('paused', true); | |
var end = Date.now(); | |
remain = remain - (end - start); | |
elem.trigger('timerpaused.zf.' + nameSpace); | |
}; | |
} | |
/** | |
* Runs a callback function when images are fully loaded. | |
* @param {Object} images - Image(s) to check if loaded. | |
* @param {Func} callback - Function to execute when image is fully loaded. | |
*/ | |
function onImagesLoaded(images, callback) { | |
var self = this, | |
unloaded = images.length; | |
if (unloaded === 0) { | |
callback(); | |
} | |
images.each(function () { | |
if (this.complete) { | |
singleImageLoaded(); | |
} else if (typeof this.naturalWidth !== 'undefined' && this.naturalWidth > 0) { | |
singleImageLoaded(); | |
} else { | |
$(this).one('load', function () { | |
singleImageLoaded(); | |
}); | |
} | |
}); | |
function singleImageLoaded() { | |
unloaded--; | |
if (unloaded === 0) { | |
callback(); | |
} | |
} | |
} | |
Foundation.Timer = Timer; | |
Foundation.onImagesLoaded = onImagesLoaded; | |
})(jQuery); | |
//************************************************** | |
//**Work inspired by multiple jquery swipe plugins** | |
//**Done by Yohai Ararat *************************** | |
//************************************************** | |
(function ($) { | |
$.spotSwipe = { | |
version: '1.0.0', | |
enabled: 'ontouchstart' in document.documentElement, | |
preventDefault: false, | |
moveThreshold: 75, | |
timeThreshold: 200 | |
}; | |
var startPosX, | |
startPosY, | |
startTime, | |
elapsedTime, | |
isMoving = false; | |
function onTouchEnd() { | |
// alert(this); | |
this.removeEventListener('touchmove', onTouchMove); | |
this.removeEventListener('touchend', onTouchEnd); | |
isMoving = false; | |
} | |
function onTouchMove(e) { | |
if ($.spotSwipe.preventDefault) { | |
e.preventDefault(); | |
} | |
if (isMoving) { | |
var x = e.touches[0].pageX; | |
var y = e.touches[0].pageY; | |
var dx = startPosX - x; | |
var dy = startPosY - y; | |
var dir; | |
elapsedTime = new Date().getTime() - startTime; | |
if (Math.abs(dx) >= $.spotSwipe.moveThreshold && elapsedTime <= $.spotSwipe.timeThreshold) { | |
dir = dx > 0 ? 'left' : 'right'; | |
} | |
// else if(Math.abs(dy) >= $.spotSwipe.moveThreshold && elapsedTime <= $.spotSwipe.timeThreshold) { | |
// dir = dy > 0 ? 'down' : 'up'; | |
// } | |
if (dir) { | |
e.preventDefault(); | |
onTouchEnd.call(this); | |
$(this).trigger('swipe', dir).trigger('swipe' + dir); | |
} | |
} | |
} | |
function onTouchStart(e) { | |
if (e.touches.length == 1) { | |
startPosX = e.touches[0].pageX; | |
startPosY = e.touches[0].pageY; | |
isMoving = true; | |
startTime = new Date().getTime(); | |
this.addEventListener('touchmove', onTouchMove, false); | |
this.addEventListener('touchend', onTouchEnd, false); | |
} | |
} | |
function init() { | |
this.addEventListener && this.addEventListener('touchstart', onTouchStart, false); | |
} | |
function teardown() { | |
this.removeEventListener('touchstart', onTouchStart); | |
} | |
$.event.special.swipe = { setup: init }; | |
$.each(['left', 'up', 'down', 'right'], function () { | |
$.event.special['swipe' + this] = { setup: function setup() { | |
$(this).on('swipe', $.noop); | |
} }; | |
}); | |
})(jQuery); | |
/**************************************************** | |
* Method for adding psuedo drag events to elements * | |
***************************************************/ | |
!(function ($) { | |
$.fn.addTouch = function () { | |
this.each(function (i, el) { | |
$(el).bind('touchstart touchmove touchend touchcancel', function () { | |
//we pass the original event object because the jQuery event | |
//object is normalized to w3c specs and does not provide the TouchList | |
handleTouch(event); | |
}); | |
}); | |
var handleTouch = function handleTouch(event) { | |
var touches = event.changedTouches, | |
first = touches[0], | |
eventTypes = { | |
touchstart: 'mousedown', | |
touchmove: 'mousemove', | |
touchend: 'mouseup' | |
}, | |
type = eventTypes[event.type], | |
simulatedEvent; | |
if ('MouseEvent' in window && typeof window.MouseEvent === 'function') { | |
simulatedEvent = window.MouseEvent(type, { | |
'bubbles': true, | |
'cancelable': true, | |
'screenX': first.screenX, | |
'screenY': first.screenY, | |
'clientX': first.clientX, | |
'clientY': first.clientY | |
}); | |
} else { | |
simulatedEvent = document.createEvent('MouseEvent'); | |
simulatedEvent.initMouseEvent(type, true, true, window, 1, first.screenX, first.screenY, first.clientX, first.clientY, false, false, false, false, 0, /*left*/null); | |
} | |
first.target.dispatchEvent(simulatedEvent); | |
}; | |
}; | |
})(jQuery); | |
//********************************** | |
//**From the jQuery Mobile Library** | |
//**need to recreate functionality** | |
//**and try to improve if possible** | |
//********************************** | |
/* Removing the jQuery function **** | |
************************************ | |
(function( $, window, undefined ) { | |
var $document = $( document ), | |
// supportTouch = $.mobile.support.touch, | |
touchStartEvent = 'touchstart'//supportTouch ? "touchstart" : "mousedown", | |
touchStopEvent = 'touchend'//supportTouch ? "touchend" : "mouseup", | |
touchMoveEvent = 'touchmove'//supportTouch ? "touchmove" : "mousemove"; | |
// setup new event shortcuts | |
$.each( ( "touchstart touchmove touchend " + | |
"swipe swipeleft swiperight" ).split( " " ), function( i, name ) { | |
$.fn[ name ] = function( fn ) { | |
return fn ? this.bind( name, fn ) : this.trigger( name ); | |
}; | |
// jQuery < 1.8 | |
if ( $.attrFn ) { | |
$.attrFn[ name ] = true; | |
} | |
}); | |
function triggerCustomEvent( obj, eventType, event, bubble ) { | |
var originalType = event.type; | |
event.type = eventType; | |
if ( bubble ) { | |
$.event.trigger( event, undefined, obj ); | |
} else { | |
$.event.dispatch.call( obj, event ); | |
} | |
event.type = originalType; | |
} | |
// also handles taphold | |
// Also handles swipeleft, swiperight | |
$.event.special.swipe = { | |
// More than this horizontal displacement, and we will suppress scrolling. | |
scrollSupressionThreshold: 30, | |
// More time than this, and it isn't a swipe. | |
durationThreshold: 1000, | |
// Swipe horizontal displacement must be more than this. | |
horizontalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30, | |
// Swipe vertical displacement must be less than this. | |
verticalDistanceThreshold: window.devicePixelRatio >= 2 ? 15 : 30, | |
getLocation: function ( event ) { | |
var winPageX = window.pageXOffset, | |
winPageY = window.pageYOffset, | |
x = event.clientX, | |
y = event.clientY; | |
if ( event.pageY === 0 && Math.floor( y ) > Math.floor( event.pageY ) || | |
event.pageX === 0 && Math.floor( x ) > Math.floor( event.pageX ) ) { | |
// iOS4 clientX/clientY have the value that should have been | |
// in pageX/pageY. While pageX/page/ have the value 0 | |
x = x - winPageX; | |
y = y - winPageY; | |
} else if ( y < ( event.pageY - winPageY) || x < ( event.pageX - winPageX ) ) { | |
// Some Android browsers have totally bogus values for clientX/Y | |
// when scrolling/zooming a page. Detectable since clientX/clientY | |
// should never be smaller than pageX/pageY minus page scroll | |
x = event.pageX - winPageX; | |
y = event.pageY - winPageY; | |
} | |
return { | |
x: x, | |
y: y | |
}; | |
}, | |
start: function( event ) { | |
var data = event.originalEvent.touches ? | |
event.originalEvent.touches[ 0 ] : event, | |
location = $.event.special.swipe.getLocation( data ); | |
return { | |
time: ( new Date() ).getTime(), | |
coords: [ location.x, location.y ], | |
origin: $( event.target ) | |
}; | |
}, | |
stop: function( event ) { | |
var data = event.originalEvent.touches ? | |
event.originalEvent.touches[ 0 ] : event, | |
location = $.event.special.swipe.getLocation( data ); | |
return { | |
time: ( new Date() ).getTime(), | |
coords: [ location.x, location.y ] | |
}; | |
}, | |
handleSwipe: function( start, stop, thisObject, origTarget ) { | |
if ( stop.time - start.time < $.event.special.swipe.durationThreshold && | |
Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.horizontalDistanceThreshold && | |
Math.abs( start.coords[ 1 ] - stop.coords[ 1 ] ) < $.event.special.swipe.verticalDistanceThreshold ) { | |
var direction = start.coords[0] > stop.coords[ 0 ] ? "swipeleft" : "swiperight"; | |
triggerCustomEvent( thisObject, "swipe", $.Event( "swipe", { target: origTarget, swipestart: start, swipestop: stop }), true ); | |
triggerCustomEvent( thisObject, direction,$.Event( direction, { target: origTarget, swipestart: start, swipestop: stop } ), true ); | |
return true; | |
} | |
return false; | |
}, | |
// This serves as a flag to ensure that at most one swipe event event is | |
// in work at any given time | |
eventInProgress: false, | |
setup: function() { | |
var events, | |
thisObject = this, | |
$this = $( thisObject ), | |
context = {}; | |
// Retrieve the events data for this element and add the swipe context | |
events = $.data( this, "mobile-events" ); | |
if ( !events ) { | |
events = { length: 0 }; | |
$.data( this, "mobile-events", events ); | |
} | |
events.length++; | |
events.swipe = context; | |
context.start = function( event ) { | |
// Bail if we're already working on a swipe event | |
if ( $.event.special.swipe.eventInProgress ) { | |
return; | |
} | |
$.event.special.swipe.eventInProgress = true; | |
var stop, | |
start = $.event.special.swipe.start( event ), | |
origTarget = event.target, | |
emitted = false; | |
context.move = function( event ) { | |
if ( !start || event.isDefaultPrevented() ) { | |
return; | |
} | |
stop = $.event.special.swipe.stop( event ); | |
if ( !emitted ) { | |
emitted = $.event.special.swipe.handleSwipe( start, stop, thisObject, origTarget ); | |
if ( emitted ) { | |
// Reset the context to make way for the next swipe event | |
$.event.special.swipe.eventInProgress = false; | |
} | |
} | |
// prevent scrolling | |
if ( Math.abs( start.coords[ 0 ] - stop.coords[ 0 ] ) > $.event.special.swipe.scrollSupressionThreshold ) { | |
event.preventDefault(); | |
} | |
}; | |
context.stop = function() { | |
emitted = true; | |
// Reset the context to make way for the next swipe event | |
$.event.special.swipe.eventInProgress = false; | |
$document.off( touchMoveEvent, context.move ); | |
context.move = null; | |
}; | |
$document.on( touchMoveEvent, context.move ) | |
.one( touchStopEvent, context.stop ); | |
}; | |
$this.on( touchStartEvent, context.start ); | |
}, | |
teardown: function() { | |
var events, context; | |
events = $.data( this, "mobile-events" ); | |
if ( events ) { | |
context = events.swipe; | |
delete events.swipe; | |
events.length--; | |
if ( events.length === 0 ) { | |
$.removeData( this, "mobile-events" ); | |
} | |
} | |
if ( context ) { | |
if ( context.start ) { | |
$( this ).off( touchStartEvent, context.start ); | |
} | |
if ( context.move ) { | |
$document.off( touchMoveEvent, context.move ); | |
} | |
if ( context.stop ) { | |
$document.off( touchStopEvent, context.stop ); | |
} | |
} | |
} | |
}; | |
$.each({ | |
swipeleft: "swipe.left", | |
swiperight: "swipe.right" | |
}, function( event, sourceEvent ) { | |
$.event.special[ event ] = { | |
setup: function() { | |
$( this ).bind( sourceEvent, $.noop ); | |
}, | |
teardown: function() { | |
$( this ).unbind( sourceEvent ); | |
} | |
}; | |
}); | |
})( jQuery, this ); | |
*/ | |
'use strict'; | |
!(function ($) { | |
var MutationObserver = (function () { | |
var prefixes = ['WebKit', 'Moz', 'O', 'Ms', '']; | |
for (var i = 0; i < prefixes.length; i++) { | |
if (prefixes[i] + 'MutationObserver' in window) { | |
return window[prefixes[i] + 'MutationObserver']; | |
} | |
} | |
return false; | |
})(); | |
var triggers = function triggers(el, type) { | |
el.data(type).split(' ').forEach(function (id) { | |
$('#' + id)[type === 'close' ? 'trigger' : 'triggerHandler'](type + '.zf.trigger', [el]); | |
}); | |
}; | |
// Elements with [data-open] will reveal a plugin that supports it when clicked. | |
$(document).on('click.zf.trigger', '[data-open]', function () { | |
triggers($(this), 'open'); | |
}); | |
// Elements with [data-close] will close a plugin that supports it when clicked. | |
// If used without a value on [data-close], the event will bubble, allowing it to close a parent component. | |
$(document).on('click.zf.trigger', '[data-close]', function () { | |
var id = $(this).data('close'); | |
if (id) { | |
triggers($(this), 'close'); | |
} else { | |
$(this).trigger('close.zf.trigger'); | |
} | |
}); | |
// Elements with [data-toggle] will toggle a plugin that supports it when clicked. | |
$(document).on('click.zf.trigger', '[data-toggle]', function () { | |
triggers($(this), 'toggle'); | |
}); | |
// Elements with [data-closable] will respond to close.zf.trigger events. | |
$(document).on('close.zf.trigger', '[data-closable]', function (e) { | |
e.stopPropagation(); | |
var animation = $(this).data('closable'); | |
if (animation !== '') { | |
Foundation.Motion.animateOut($(this), animation, function () { | |
$(this).trigger('closed.zf'); | |
}); | |
} else { | |
$(this).fadeOut().trigger('closed.zf'); | |
} | |
}); | |
$(document).on('focus.zf.trigger blur.zf.trigger', '[data-toggle-focus]', function () { | |
var id = $(this).data('toggle-focus'); | |
$('#' + id).triggerHandler('toggle.zf.trigger', [$(this)]); | |
}); | |
/** | |
* Fires once after all other scripts have loaded | |
* @function | |
* @private | |
*/ | |
$(window).load(function () { | |
checkListeners(); | |
}); | |
function checkListeners() { | |
eventsListener(); | |
resizeListener(); | |
scrollListener(); | |
closemeListener(); | |
} | |
//******** only fires this function once on load, if there's something to watch ******** | |
function closemeListener(pluginName) { | |
var yetiBoxes = $('[data-yeti-box]'), | |
plugNames = ['dropdown', 'tooltip', 'reveal']; | |
if (pluginName) { | |
if (typeof pluginName === 'string') { | |
plugNames.push(pluginName); | |
} else if (typeof pluginName === 'object' && typeof pluginName[0] === 'string') { | |
plugNames.concat(pluginName); | |
} else { | |
console.error('Plugin names must be strings'); | |
} | |
} | |
if (yetiBoxes.length) { | |
var listeners = plugNames.map(function (name) { | |
return 'closeme.zf.' + name; | |
}).join(' '); | |
$(window).off(listeners).on(listeners, function (e, pluginId) { | |
var plugin = e.namespace.split('.')[0]; | |
var plugins = $('[data-' + plugin + ']').not('[data-yeti-box="' + pluginId + '"]'); | |
plugins.each(function () { | |
var _this = $(this); | |
_this.triggerHandler('close.zf.trigger', [_this]); | |
}); | |
}); | |
} | |
} | |
function resizeListener(debounce) { | |
var timer = undefined, | |
$nodes = $('[data-resize]'); | |
if ($nodes.length) { | |
$(window).off('resize.zf.trigger').on('resize.zf.trigger', function (e) { | |
if (timer) { | |
clearTimeout(timer); | |
} | |
timer = setTimeout(function () { | |
if (!MutationObserver) { | |
//fallback for IE 9 | |
$nodes.each(function () { | |
$(this).triggerHandler('resizeme.zf.trigger'); | |
}); | |
} | |
//trigger all listening elements and signal a resize event | |
$nodes.attr('data-events', 'resize'); | |
}, debounce || 10); //default time to emit resize event | |
}); | |
} | |
} | |
function scrollListener(debounce) { | |
var timer = undefined, | |
$nodes = $('[data-scroll]'); | |
if ($nodes.length) { | |
$(window).off('scroll.zf.trigger').on('scroll.zf.trigger', function (e) { | |
if (timer) { | |
clearTimeout(timer); | |
} | |
timer = setTimeout(function () { | |
if (!MutationObserver) { | |
//fallback for IE 9 | |
$nodes.each(function () { | |
$(this).triggerHandler('scrollme.zf.trigger'); | |
}); | |
} | |
//trigger all listening elements and signal a scroll event | |
$nodes.attr('data-events', 'scroll'); | |
}, debounce || 10); //default time to emit scroll event | |
}); | |
} | |
} | |
function eventsListener() { | |
if (!MutationObserver) { | |
return false; | |
} | |
var nodes = document.querySelectorAll('[data-resize], [data-scroll], [data-mutate]'); | |
//element callback | |
var listeningElementsMutation = function listeningElementsMutation(mutationRecordsList) { | |
var $target = $(mutationRecordsList[0].target); | |
//trigger the event handler for the element depending on type | |
switch ($target.attr('data-events')) { | |
case 'resize': | |
$target.triggerHandler('resizeme.zf.trigger', [$target]); | |
break; | |
case 'scroll': | |
$target.triggerHandler('scrollme.zf.trigger', [$target, window.pageYOffset]); | |
break; | |
// case "mutate" : | |
// console.log('mutate', $target); | |
// $target.triggerHandler('mutate.zf.trigger'); | |
// | |
// //make sure we don't get stuck in an infinite loop from sloppy codeing | |
// if ($target.index('[data-mutate]') == $("[data-mutate]").length-1) { | |
// domMutationObserver(); | |
// } | |
// break; | |
default: | |
return false; | |
//nothing | |
} | |
}; | |
if (nodes.length) { | |
//for each element that needs to listen for resizing, scrolling, (or coming soon mutation) add a single observer | |
for (var i = 0; i <= nodes.length - 1; i++) { | |
var elementObserver = new MutationObserver(listeningElementsMutation); | |
elementObserver.observe(nodes[i], { attributes: true, childList: false, characterData: false, subtree: false, attributeFilter: ['data-events'] }); | |
} | |
} | |
} | |
// ------------------------------------ | |
// [PH] | |
// Foundation.CheckWatchers = checkWatchers; | |
Foundation.IHearYou = checkListeners; | |
// Foundation.ISeeYou = scrollListener; | |
// Foundation.IFeelYou = closemeListener; | |
})(jQuery); | |
// function domMutationObserver(debounce) { | |
// // !!! This is coming soon and needs more work; not active !!! // | |
// var timer, | |
// nodes = document.querySelectorAll('[data-mutate]'); | |
// // | |
// if (nodes.length) { | |
// // var MutationObserver = (function () { | |
// // var prefixes = ['WebKit', 'Moz', 'O', 'Ms', '']; | |
// // for (var i=0; i < prefixes.length; i++) { | |
// // if (prefixes[i] + 'MutationObserver' in window) { | |
// // return window[prefixes[i] + 'MutationObserver']; | |
// // } | |
// // } | |
// // return false; | |
// // }()); | |
// | |
// | |
// //for the body, we need to listen for all changes effecting the style and class attributes | |
// var bodyObserver = new MutationObserver(bodyMutation); | |
// bodyObserver.observe(document.body, { attributes: true, childList: true, characterData: false, subtree:true, attributeFilter:["style", "class"]}); | |
// | |
// | |
// //body callback | |
// function bodyMutation(mutate) { | |
// //trigger all listening elements and signal a mutation event | |
// if (timer) { clearTimeout(timer); } | |
// | |
// timer = setTimeout(function() { | |
// bodyObserver.disconnect(); | |
// $('[data-mutate]').attr('data-events',"mutate"); | |
// }, debounce || 150); | |
// } | |
// } | |
// } | |
'use strict'; | |
!(function ($) { | |
/** | |
* AccordionMenu module. | |
* @module foundation.accordionMenu | |
* @requires foundation.util.keyboard | |
* @requires foundation.util.motion | |
* @requires foundation.util.nest | |
*/ | |
var AccordionMenu = (function () { | |
/** | |
* 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. | |
*/ | |
function AccordionMenu(element, options) { | |
_classCallCheck(this, AccordionMenu); | |
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' | |
}); | |
} | |
_createClass(AccordionMenu, [{ | |
key: '_init', | |
/** | |
* Initializes the accordion menu by hiding all nested menus. | |
* @private | |
*/ | |
value: function _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(); | |
} | |
}, { | |
key: '_events', | |
/** | |
* Adds event handlers for items within the menu. | |
* @private | |
*/ | |
value: function _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)); | |
$nextElement = $elements.eq(Math.min(i + 1, $elements.length - 1)); | |
if ($(this).children('[data-submenu]:visible').length) { | |
// has open sub menu | |
$nextElement = $element.find('li:first-child'); | |
} | |
if ($(this).is(':first-child')) { | |
// is first element of sub menu | |
$prevElement = $element.parents('li').first(); | |
} else if ($prevElement.children('[data-submenu]:visible').length) { | |
// if previous element has open sub menu | |
$prevElement = $prevElement.find('li:last-child'); | |
} | |
if ($(this).is(':last-child')) { | |
// is last element of sub menu | |
$nextElement = $element.parents('li').first().next('li'); | |
} | |
return; | |
} | |
}); | |
Foundation.Keyboard.handleKey(e, 'AccordionMenu', { | |
open: function open() { | |
if ($target.is(':hidden')) { | |
_this.down($target); | |
$target.find('li').first().focus(); | |
} | |
}, | |
close: function close() { | |
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().focus(); | |
} | |
}, | |
up: function up() { | |
$prevElement.focus(); | |
}, | |
down: function down() { | |
$nextElement.focus(); | |
}, | |
toggle: function toggle() { | |
if ($element.children('[data-submenu]').length) { | |
_this.toggle($element.children('[data-submenu]')); | |
} | |
}, | |
closeAll: function closeAll() { | |
_this.hideAll(); | |
}, | |
handled: function handled() { | |
e.preventDefault(); | |
e.stopImmediatePropagation(); | |
} | |
}); | |
}); //.attr('tabindex', 0); | |
} | |
}, { | |
key: 'hideAll', | |
/** | |
* Closes all panes of the menu. | |
* @function | |
*/ | |
value: function hideAll() { | |
this.$element.find('[data-submenu]').slideUp(this.options.slideSpeed); | |
} | |
}, { | |
key: 'toggle', | |
/** | |
* Toggles the open/close state of a submenu. | |
* @function | |
* @param {jQuery} $target - the submenu to toggle | |
*/ | |
value: function toggle($target) { | |
if (!$target.is(':animated')) { | |
if (!$target.is(':hidden')) { | |
this.up($target); | |
} else { | |
this.down($target); | |
} | |
} | |
} | |
}, { | |
key: 'down', | |
/** | |
* Opens the sub-menu defined by `$target`. | |
* @param {jQuery} $target - Sub-menu to open. | |
* @fires AccordionMenu#down | |
*/ | |
value: function 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]); | |
}); | |
}); | |
} | |
}, { | |
key: 'up', | |
/** | |
* 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 | |
*/ | |
value: function 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); | |
} | |
}, { | |
key: 'destroy', | |
/** | |
* Destroys an instance of accordion menu. | |
* @fires AccordionMenu#destroyed | |
*/ | |
value: function 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); | |
} | |
}]); | |
return AccordionMenu; | |
})(); | |
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); | |
'use strict'; | |
!(function ($) { | |
/** | |
* Drilldown module. | |
* @module foundation.drilldown | |
* @requires foundation.util.keyboard | |
* @requires foundation.util.motion | |
* @requires foundation.util.nest | |
*/ | |
var Drilldown = (function () { | |
/** | |
* Creates a new instance of a drilldown menu. | |
* @class | |
* @param {jQuery} element - jQuery object to make into an accordion menu. | |
* @param {Object} options - Overrides to the default plugin settings. | |
*/ | |
function Drilldown(element, options) { | |
_classCallCheck(this, Drilldown); | |
this.$element = element; | |
this.options = $.extend({}, Drilldown.defaults, this.$element.data(), options); | |
Foundation.Nest.Feather(this.$element, 'drilldown'); | |
this._init(); | |
Foundation.registerPlugin(this, 'Drilldown'); | |
Foundation.Keyboard.register('Drilldown', { | |
'ENTER': 'open', | |
'SPACE': 'open', | |
'ARROW_RIGHT': 'next', | |
'ARROW_UP': 'up', | |
'ARROW_DOWN': 'down', | |
'ARROW_LEFT': 'previous', | |
'ESCAPE': 'close', | |
'TAB': 'down', | |
'SHIFT_TAB': 'up' | |
}); | |
} | |
_createClass(Drilldown, [{ | |
key: '_init', | |
/** | |
* Initializes the drilldown by creating jQuery collections of elements | |
* @private | |
*/ | |
value: function _init() { | |
this.$submenuAnchors = this.$element.find('li.is-drilldown-submenu-parent'); | |
this.$submenus = this.$submenuAnchors.children('[data-submenu]'); | |
this.$menuItems = this.$element.find('li').not('.js-drilldown-back').attr('role', 'menuitem'); | |
this._prepareMenu(); | |
this._keyboardEvents(); | |
} | |
}, { | |
key: '_prepareMenu', | |
/** | |
* prepares drilldown menu by setting attributes to links and elements | |
* sets a min height to prevent content jumping | |
* wraps the element if not already wrapped | |
* @private | |
* @function | |
*/ | |
value: function _prepareMenu() { | |
var _this = this; | |
// if(!this.options.holdOpen){ | |
// this._menuLinkEvents(); | |
// } | |
this.$submenuAnchors.each(function () { | |
var $sub = $(this); | |
var $link = $sub.find('a:first'); | |
if (_this.options.parentLink) { | |
$link.clone().prependTo($sub.children('[data-submenu]')).wrap('<li class="is-submenu-parent-item is-submenu-item is-drilldown-submenu-item" role="menu-item"></li>'); | |
} | |
$link.data('savedHref', $link.attr('href')).removeAttr('href'); | |
$sub.children('[data-submenu]').attr({ | |
'aria-hidden': true, | |
'tabindex': 0, | |
'role': 'menu' | |
}); | |
_this._events($sub); | |
}); | |
this.$submenus.each(function () { | |
var $menu = $(this), | |
$back = $menu.find('.js-drilldown-back'); | |
if (!$back.length) { | |
$menu.prepend(_this.options.backButton); | |
} | |
_this._back($menu); | |
}); | |
if (!this.$element.parent().hasClass('is-drilldown')) { | |
this.$wrapper = $(this.options.wrapper).addClass('is-drilldown').css(this._getMaxDims()); | |
this.$element.wrap(this.$wrapper); | |
} | |
} | |
}, { | |
key: '_events', | |
/** | |
* Adds event handlers to elements in the menu. | |
* @function | |
* @private | |
* @param {jQuery} $elem - the current menu item to add handlers to. | |
*/ | |
value: function _events($elem) { | |
var _this = this; | |
$elem.off('click.zf.drilldown').on('click.zf.drilldown', function (e) { | |
if ($(e.target).parentsUntil('ul', 'li').hasClass('is-drilldown-submenu-parent')) { | |
e.stopImmediatePropagation(); | |
e.preventDefault(); | |
} | |
// if(e.target !== e.currentTarget.firstElementChild){ | |
// return false; | |
// } | |
_this._show($elem); | |
if (_this.options.closeOnClick) { | |
var $body = $('body').not(_this.$wrapper); | |
$body.off('.zf.drilldown').on('click.zf.drilldown', function (e) { | |
e.preventDefault(); | |
_this._hideAll(); | |
$body.off('.zf.drilldown'); | |
}); | |
} | |
}); | |
} | |
}, { | |
key: '_keyboardEvents', | |
/** | |
* Adds keydown event listener to `li`'s in the menu. | |
* @private | |
*/ | |
value: function _keyboardEvents() { | |
var _this = this; | |
this.$menuItems.add(this.$element.find('.js-drilldown-back')).on('keydown.zf.drilldown', function (e) { | |
var $element = $(this), | |
$elements = $element.parent('ul').children('li'), | |
$prevElement, | |
$nextElement; | |
$elements.each(function (i) { | |
if ($(this).is($element)) { | |
$prevElement = $elements.eq(Math.max(0, i - 1)); | |
$nextElement = $elements.eq(Math.min(i + 1, $elements.length - 1)); | |
return; | |
} | |
}); | |
Foundation.Keyboard.handleKey(e, 'Drilldown', { | |
next: function next() { | |
if ($element.is(_this.$submenuAnchors)) { | |
_this._show($element); | |
$element.on(Foundation.transitionend($element), function () { | |
$element.find('ul li').filter(_this.$menuItems).first().focus(); | |
}); | |
} | |
}, | |
previous: function previous() { | |
_this._hide($element.parent('ul')); | |
$element.parent('ul').on(Foundation.transitionend($element), function () { | |
setTimeout(function () { | |
$element.parent('ul').parent('li').focus(); | |
}, 1); | |
}); | |
}, | |
up: function up() { | |
$prevElement.focus(); | |
}, | |
down: function down() { | |
$nextElement.focus(); | |
}, | |
close: function close() { | |
_this._back(); | |
//_this.$menuItems.first().focus(); // focus to first element | |
}, | |
open: function open() { | |
if (!$element.is(_this.$menuItems)) { | |
// not menu item means back button | |
_this._hide($element.parent('ul')); | |
setTimeout(function () { | |
$element.parent('ul').parent('li').focus(); | |
}, 1); | |
} else if ($element.is(_this.$submenuAnchors)) { | |
_this._show($element); | |
setTimeout(function () { | |
$element.find('ul li').filter(_this.$menuItems).first().focus(); | |
}, 1); | |
} | |
}, | |
handled: function handled() { | |
e.preventDefault(); | |
e.stopImmediatePropagation(); | |
} | |
}); | |
}); // end keyboardAccess | |
} | |
}, { | |
key: '_hideAll', | |
/** | |
* Closes all open elements, and returns to root menu. | |
* @function | |
* @fires Drilldown#closed | |
*/ | |
value: function _hideAll() { | |
var $elem = this.$element.find('.is-drilldown-submenu.is-active').addClass('is-closing'); | |
$elem.one(Foundation.transitionend($elem), function (e) { | |
$elem.removeClass('is-active is-closing'); | |
}); | |
/** | |
* Fires when the menu is fully closed. | |
* @event Drilldown#closed | |
*/ | |
this.$element.trigger('closed.zf.drilldown'); | |
} | |
}, { | |
key: '_back', | |
/** | |
* Adds event listener for each `back` button, and closes open menus. | |
* @function | |
* @fires Drilldown#back | |
* @param {jQuery} $elem - the current sub-menu to add `back` event. | |
*/ | |
value: function _back($elem) { | |
var _this = this; | |
$elem.off('click.zf.drilldown'); | |
$elem.children('.js-drilldown-back').on('click.zf.drilldown', function (e) { | |
e.stopImmediatePropagation(); | |
// console.log('mouseup on back'); | |
_this._hide($elem); | |
}); | |
} | |
}, { | |
key: '_menuLinkEvents', | |
/** | |
* Adds event listener to menu items w/o submenus to close open menus on click. | |
* @function | |
* @private | |
*/ | |
value: function _menuLinkEvents() { | |
var _this = this; | |
this.$menuItems.not('.is-drilldown-submenu-parent').off('click.zf.drilldown').on('click.zf.drilldown', function (e) { | |
// e.stopImmediatePropagation(); | |
setTimeout(function () { | |
_this._hideAll(); | |
}, 0); | |
}); | |
} | |
}, { | |
key: '_show', | |
/** | |
* Opens a submenu. | |
* @function | |
* @fires Drilldown#open | |
* @param {jQuery} $elem - the current element with a submenu to open. | |
*/ | |
value: function _show($elem) { | |
$elem.children('[data-submenu]').addClass('is-active'); | |
this.$element.trigger('open.zf.drilldown', [$elem]); | |
} | |
}, { | |
key: '_hide', | |
/** | |
* Hides a submenu | |
* @function | |
* @fires Drilldown#hide | |
* @param {jQuery} $elem - the current sub-menu to hide. | |
*/ | |
value: function _hide($elem) { | |
var _this = this; | |
$elem.addClass('is-closing').one(Foundation.transitionend($elem), function () { | |
$elem.removeClass('is-active is-closing'); | |
$elem.blur(); | |
}); | |
/** | |
* Fires when the submenu is has closed. | |
* @event Drilldown#hide | |
*/ | |
$elem.trigger('hide.zf.drilldown', [$elem]); | |
} | |
}, { | |
key: '_getMaxDims', | |
/** | |
* Iterates through the nested menus to calculate the min-height, and max-width for the menu. | |
* Prevents content jumping. | |
* @function | |
* @private | |
*/ | |
value: function _getMaxDims() { | |
var max = 0, | |
result = {}; | |
this.$submenus.add(this.$element).each(function () { | |
var numOfElems = $(this).children('li').length; | |
max = numOfElems > max ? numOfElems : max; | |
}); | |
result['min-height'] = max * this.$menuItems[0].getBoundingClientRect().height + 'px'; | |
result['max-width'] = this.$element[0].getBoundingClientRect().width + 'px'; | |
return result; | |
} | |
}, { | |
key: 'destroy', | |
/** | |
* Destroys the Drilldown Menu | |
* @function | |
*/ | |
value: function destroy() { | |
this._hideAll(); | |
Foundation.Nest.Burn(this.$element, 'drilldown'); | |
this.$element.unwrap().find('.js-drilldown-back, .is-submenu-parent-item').remove().end().find('.is-active, .is-closing, .is-drilldown-submenu').removeClass('is-active is-closing is-drilldown-submenu').end().find('[data-submenu]').removeAttr('aria-hidden tabindex role').off('.zf.drilldown').end().off('zf.drilldown'); | |
this.$element.find('a').each(function () { | |
var $link = $(this); | |
if ($link.data('savedHref')) { | |
$link.attr('href', $link.data('savedHref')).removeData('savedHref'); | |
} else { | |
return; | |
} | |
}); | |
Foundation.unregisterPlugin(this); | |
} | |
}]); | |
return Drilldown; | |
})(); | |
Drilldown.defaults = { | |
/** | |
* Markup used for JS generated back button. Prepended to submenu lists and deleted on `destroy` method, 'js-drilldown-back' class required. Remove the backslash (`\`) if copy and pasting. | |
* @option | |
* @example '<\li><\a>Back<\/a><\/li>' | |
*/ | |
backButton: '<li class="js-drilldown-back"><a>Back</a></li>', | |
/** | |
* Markup used to wrap drilldown menu. Use a class name for independent styling; the JS applied class: `is-drilldown` is required. Remove the backslash (`\`) if copy and pasting. | |
* @option | |
* @example '<\div class="is-drilldown"><\/div>' | |
*/ | |
wrapper: '<div></div>', | |
/** | |
* Adds the parent link to the submenu. | |
* @option | |
* @example false | |
*/ | |
parentLink: false, | |
/** | |
* Allow the menu to return to root list on body click. | |
* @option | |
* @example false | |
*/ | |
closeOnClick: false | |
// holdOpen: false | |
}; | |
// Window exports | |
Foundation.plugin(Drilldown, 'Drilldown'); | |
})(jQuery); | |
'use strict'; | |
!(function ($) { | |
/** | |
* Reveal module. | |
* @module foundation.reveal | |
* @requires foundation.util.keyboard | |
* @requires foundation.util.box | |
* @requires foundation.util.triggers | |
* @requires foundation.util.mediaQuery | |
* @requires foundation.util.motion if using animations | |
*/ | |
var Reveal = (function () { | |
/** | |
* Creates a new instance of Reveal. | |
* @class | |
* @param {jQuery} element - jQuery object to use for the modal. | |
* @param {Object} options - optional parameters. | |
*/ | |
function Reveal(element, options) { | |
_classCallCheck(this, Reveal); | |
this.$element = element; | |
this.options = $.extend({}, Reveal.defaults, this.$element.data(), options); | |
this._init(); | |
Foundation.registerPlugin(this, 'Reveal'); | |
Foundation.Keyboard.register('Reveal', { | |
'ENTER': 'open', | |
'SPACE': 'open', | |
'ESCAPE': 'close', | |
'TAB': 'tab_forward', | |
'SHIFT_TAB': 'tab_backward' | |
}); | |
} | |
_createClass(Reveal, [{ | |
key: '_init', | |
/** | |
* Initializes the modal by adding the overlay and close buttons, (if selected). | |
* @private | |
*/ | |
value: function _init() { | |
this.id = this.$element.attr('id'); | |
this.isActive = false; | |
this.cached = { mq: Foundation.MediaQuery.current }; | |
this.isiOS = iPhoneSniff(); | |
if (this.isiOS) { | |
this.$element.addClass('is-ios'); | |
} | |
this.$anchor = $('[data-open="' + this.id + '"]').length ? $('[data-open="' + this.id + '"]') : $('[data-toggle="' + this.id + '"]'); | |
if (this.$anchor.length) { | |
var anchorId = this.$anchor[0].id || Foundation.GetYoDigits(6, 'reveal'); | |
this.$anchor.attr({ | |
'aria-controls': this.id, | |
'id': anchorId, | |
'aria-haspopup': true, | |
'tabindex': 0 | |
}); | |
this.$element.attr({ 'aria-labelledby': anchorId }); | |
} | |
if (this.options.fullScreen || this.$element.hasClass('full')) { | |
this.options.fullScreen = true; | |
this.options.overlay = false; | |
} | |
if (this.options.overlay && !this.$overlay) { | |
this.$overlay = this._makeOverlay(this.id); | |
} | |
this.$element.attr({ | |
'role': 'dialog', | |
'aria-hidden': true, | |
'data-yeti-box': this.id, | |
'data-resize': this.id | |
}); | |
if (this.$overlay) { | |
this.$element.detach().appendTo(this.$overlay); | |
} else { | |
this.$element.detach().appendTo($('body')); | |
this.$element.addClass('without-overlay'); | |
} | |
this._events(); | |
if (this.options.deepLink && window.location.hash === '#' + this.id) { | |
$(window).one('load.zf.reveal', this.open.bind(this)); | |
} | |
} | |
}, { | |
key: '_makeOverlay', | |
/** | |
* Creates an overlay div to display behind the modal. | |
* @private | |
*/ | |
value: function _makeOverlay(id) { | |
var $overlay = $('<div></div>').addClass('reveal-overlay').attr({ 'tabindex': -1, 'aria-hidden': true }).appendTo('body'); | |
return $overlay; | |
} | |
}, { | |
key: '_updatePosition', | |
/** | |
* Updates position of modal | |
* TODO: Figure out if we actually need to cache these values or if it doesn't matter | |
* @private | |
*/ | |
value: function _updatePosition() { | |
var width = this.$element.outerWidth(); | |
var outerWidth = $(window).width(); | |
var height = this.$element.outerHeight(); | |
var outerHeight = $(window).height(); | |
var left = parseInt((outerWidth - width) / 2, 10); | |
var top; | |
if (height > outerHeight) { | |
top = parseInt(Math.min(100, outerHeight / 10), 10); | |
} else { | |
top = parseInt((outerHeight - height) / 4, 10); | |
} | |
this.$element.css({ top: top + 'px' }); | |
// only worry about left if we don't have an overlay, otherwise we're perfectly in the middle | |
if (!this.$overlay) { | |
this.$element.css({ left: left + 'px' }); | |
} | |
} | |
}, { | |
key: '_events', | |
/** | |
* Adds event handlers for the modal. | |
* @private | |
*/ | |
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': function resizemeZfTrigger() { | |
_this._updatePosition(); | |
} | |
}); | |
if (this.$anchor.length) { | |
this.$anchor.on('keydown.zf.reveal', function (e) { | |
if (e.which === 13 || e.which === 32) { | |
e.stopPropagation(); | |
e.preventDefault(); | |
_this.open(); | |
} | |
}); | |
} | |
if (this.options.closeOnClick && this.options.overlay) { | |
this.$overlay.off('.zf.reveal').on('click.zf.reveal', function (e) { | |
if (e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)) { | |
return; | |
} | |
_this.close(); | |
}); | |
} | |
if (this.options.deepLink) { | |
$(window).on('popstate.zf.reveal:' + this.id, this._handleState.bind(this)); | |
} | |
} | |
}, { | |
key: '_handleState', | |
/** | |
* Handles modal methods on back/forward button clicks or any other event that triggers popstate. | |
* @private | |
*/ | |
value: function _handleState(e) { | |
if (window.location.hash === '#' + this.id && !this.isActive) { | |
this.open(); | |
} else { | |
this.close(); | |
} | |
} | |
}, { | |
key: 'open', | |
/** | |
* Opens the modal controlled by `this.$anchor`, and closes all others by default. | |
* @function | |
* @fires Reveal#closeme | |
* @fires Reveal#open | |
*/ | |
value: function open() { | |
var _this3 = this; | |
if (this.options.deepLink) { | |
var hash = '#' + this.id; | |
if (window.history.pushState) { | |
window.history.pushState(null, null, hash); | |
} else { | |
window.location.hash = hash; | |
} | |
} | |
this.isActive = true; | |
// Make elements invisible, but remove display: none so we can get size and positioning | |
this.$element.css({ 'visibility': 'hidden' }).show().scrollTop(0); | |
if (this.options.overlay) { | |
this.$overlay.css({ 'visibility': 'hidden' }).show(); | |
} | |
this._updatePosition(); | |
this.$element.hide().css({ 'visibility': '' }); | |
if (this.$overlay) { | |
this.$overlay.css({ 'visibility': '' }).hide(); | |
} | |
if (!this.options.multipleOpened) { | |
/** | |
* Fires immediately before the modal opens. | |
* Closes any other modals that are currently open | |
* @event Reveal#closeme | |
*/ | |
this.$element.trigger('closeme.zf.reveal', this.id); | |
} | |
// Motion UI method of reveal | |
if (this.options.animationIn) { | |
if (this.options.overlay) { | |
Foundation.Motion.animateIn(this.$overlay, 'fade-in'); | |
} | |
Foundation.Motion.animateIn(this.$element, this.options.animationIn, function () { | |
this.focusableElements = Foundation.Keyboard.findFocusable(this.$element); | |
}); | |
} | |
// jQuery method of reveal | |
else { | |
if (this.options.overlay) { | |
this.$overlay.show(0); | |
} | |
this.$element.show(this.options.showDelay); | |
} | |
// handle accessibility | |
this.$element.attr({ | |
'aria-hidden': false, | |
'tabindex': -1 | |
}).focus(); | |
/** | |
* Fires when the modal has successfully opened. | |
* @event Reveal#open | |
*/ | |
this.$element.trigger('open.zf.reveal'); | |
if (this.isiOS) { | |
var scrollPos = window.pageYOffset; | |
$('html, body').addClass('is-reveal-open').scrollTop(scrollPos); | |
} else { | |
$('body').addClass('is-reveal-open'); | |
} | |
$('body').addClass('is-reveal-open').attr('aria-hidden', this.options.overlay || this.options.fullScreen ? true : false); | |
setTimeout(function () { | |
_this3._extraHandlers(); | |
}, 0); | |
} | |
}, { | |
key: '_extraHandlers', | |
/** | |
* Adds extra event handlers for the body and window if necessary. | |
* @private | |
*/ | |
value: function _extraHandlers() { | |
var _this = this; | |
this.focusableElements = Foundation.Keyboard.findFocusable(this.$element); | |
if (!this.options.overlay && this.options.closeOnClick && !this.options.fullScreen) { | |
$('body').on('click.zf.reveal', function (e) { | |
if (e.target === _this.$element[0] || $.contains(_this.$element[0], e.target)) { | |
return; | |
} | |
_this.close(); | |
}); | |
} | |
if (this.options.closeOnEsc) { | |
$(window).on('keydown.zf.reveal', function (e) { | |
Foundation.Keyboard.handleKey(e, 'Reveal', { | |
close: function close() { | |
if (_this.options.closeOnEsc) { | |
_this.close(); | |
_this.$anchor.focus(); | |
} | |
} | |
}); | |
if (_this.focusableElements.length === 0) { | |
// no focusable elements inside the modal at all, prevent tabbing in general | |
e.preventDefault(); | |
} | |
}); | |
} | |
// lock focus within modal while tabbing | |
this.$element.on('keydown.zf.reveal', function (e) { | |
var $target = $(this); | |
// handle keyboard event with keyboard util | |
Foundation.Keyboard.handleKey(e, 'Reveal', { | |
tab_forward: function tab_forward() { | |
if (_this.$element.find(':focus').is(_this.focusableElements.eq(-1))) { | |
// left modal downwards, setting focus to first element | |
_this.focusableElements.eq(0).focus(); | |
e.preventDefault(); | |
} | |
}, | |
tab_backward: function tab_backward() { | |
if (_this.$element.find(':focus').is(_this.focusableElements.eq(0)) || _this.$element.is(':focus')) { | |
// left modal upwards, setting focus to last element | |
_this.focusableElements.eq(-1).focus(); | |
e.preventDefault(); | |
} | |
}, | |
open: function open() { | |
if (_this.$element.find(':focus').is(_this.$element.find('[data-close]'))) { | |
setTimeout(function () { | |
// set focus back to anchor if close button has been activated | |
_this.$anchor.focus(); | |
}, 1); | |
} else if ($target.is(_this.focusableElements)) { | |
// dont't trigger if acual element has focus (i.e. inputs, links, ...) | |
_this.open(); | |
} | |
}, | |
close: function close() { | |
if (_this.options.closeOnEsc) { | |
_this.close(); | |
_this.$anchor.focus(); | |
} | |
} | |
}); | |
}); | |
} | |
}, { | |
key: 'close', | |
/** | |
* Closes the modal. | |
* @function | |
* @fires Reveal#closed | |
*/ | |
value: function close() { | |
if (!this.isActive || !this.$element.is(':visible')) { | |
return false; | |
} | |
var _this = this; | |
// Motion UI method of hiding | |
if (this.options.animationOut) { | |
if (this.options.overlay) { | |
Foundation.Motion.animateOut(this.$overlay, 'fade-out', finishUp); | |
} else { | |
finishUp(); | |
} | |
Foundation.Motion.animateOut(this.$element, this.options.animationOut); | |
} | |
// jQuery method of hiding | |
else { | |
if (this.options.overlay) { | |
this.$overlay.hide(0, finishUp); | |
} else { | |
finishUp(); | |
} | |
this.$element.hide(this.options.hideDelay); | |
} | |
// Conditionals to remove extra event listeners added on open | |
if (this.options.closeOnEsc) { | |
$(window).off('keydown.zf.reveal'); | |
} | |
if (!this.options.overlay && this.options.closeOnClick) { | |
$('body').off('click.zf.reveal'); | |
} | |
this.$element.off('keydown.zf.reveal'); | |
function finishUp() { | |
if (_this.isiOS) { | |
$('html, body').removeClass('is-reveal-open'); | |
} else { | |
$('body').removeClass('is-reveal-open'); | |
} | |
$('body').attr({ | |
'aria-hidden': false, | |
'tabindex': '' | |
}); | |
_this.$element.attr('aria-hidden', true); | |
/** | |
* Fires when the modal is done closing. | |
* @event Reveal#closed | |
*/ | |
_this.$element.trigger('closed.zf.reveal'); | |
} | |
/** | |
* Resets the modal content | |
* This prevents a running video to keep going in the background | |
*/ | |
if (this.options.resetOnClose) { | |
this.$element.html(this.$element.html()); | |
} | |
this.isActive = false; | |
if (_this.options.deepLink) { | |
if (window.history.replaceState) { | |
window.history.replaceState('', document.title, window.location.pathname); | |
} else { | |
window.location.hash = ''; | |
} | |
} | |
} | |
}, { | |
key: 'toggle', | |
/** | |
* Toggles the open/closed state of a modal. | |
* @function | |
*/ | |
value: function toggle() { | |
if (this.isActive) { | |
this.close(); | |
} else { | |
this.open(); | |
} | |
} | |
}, { | |
key: 'destroy', | |
/** | |
* Destroys an instance of a modal. | |
* @function | |
*/ | |
value: function destroy() { | |
if (this.options.overlay) { | |
this.$overlay.hide().off().remove(); | |
} | |
this.$element.hide().off(); | |
this.$anchor.off('.zf'); | |
$(window).off('.zf.reveal:' + this.id); | |
Foundation.unregisterPlugin(this); | |
} | |
}]); | |
return Reveal; | |
})(); | |
Reveal.defaults = { | |
/** | |
* Motion-UI class to use for animated elements. If none used, defaults to simple show/hide. | |
* @option | |
* @example 'slide-in-left' | |
*/ | |
animationIn: '', | |
/** | |
* Motion-UI class to use for animated elements. If none used, defaults to simple show/hide. | |
* @option | |
* @example 'slide-out-right' | |
*/ | |
animationOut: '', | |
/** | |
* Time, in ms, to delay the opening of a modal after a click if no animation used. | |
* @option | |
* @example 10 | |
*/ | |
showDelay: 0, | |
/** | |
* Time, in ms, to delay the closing of a modal after a click if no animation used. | |
* @option | |
* @example 10 | |
*/ | |
hideDelay: 0, | |
/** | |
* Allows a click on the body/overlay to close the modal. | |
* @option | |
* @example true | |
*/ | |
closeOnClick: true, | |
/** | |
* Allows the modal to close if the user presses the `ESCAPE` key. | |
* @option | |
* @example true | |
*/ | |
closeOnEsc: true, | |
/** | |
* If true, allows multiple modals to be displayed at once. | |
* @option | |
* @example false | |
*/ | |
multipleOpened: false, | |
/** | |
* Distance, in pixels, the modal should push down from the top of the screen. | |
* @option | |
* @example 100 | |
*/ | |
vOffset: 100, | |
/** | |
* Distance, in pixels, the modal should push in from the side of the screen. | |
* @option | |
* @example 0 | |
*/ | |
hOffset: 0, | |
/** | |
* Allows the modal to be fullscreen, completely blocking out the rest of the view. JS checks for this as well. | |
* @option | |
* @example false | |
*/ | |
fullScreen: false, | |
/** | |
* Percentage of screen height the modal should push up from the bottom of the view. | |
* @option | |
* @example 10 | |
*/ | |
btmOffsetPct: 10, | |
/** | |
* Allows the modal to generate an overlay div, which will cover the view when modal opens. | |
* @option | |
* @example true | |
*/ | |
overlay: true, | |
/** | |
* Allows the modal to remove and reinject markup on close. Should be true if using video elements w/o using provider's api, otherwise, videos will continue to play in the background. | |
* @option | |
* @example false | |
*/ | |
resetOnClose: false, | |
/** | |
* Allows the modal to alter the url on open/close, and allows the use of the `back` button to close modals. ALSO, allows a modal to auto-maniacally open on page load IF the hash === the modal's user-set id. | |
* @option | |
* @example false | |
*/ | |
deepLink: false | |
}; | |
// Window exports | |
Foundation.plugin(Reveal, 'Reveal'); | |
function iPhoneSniff() { | |
return /iP(ad|hone|od).*OS/.test(window.navigator.userAgent); | |
} | |
})(jQuery); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment