2118 lines
51 KiB
JavaScript
2118 lines
51 KiB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS main module
|
|
* @module main
|
|
*/
|
|
|
|
(function(win) {
|
|
'use strict';
|
|
|
|
// return if library has been loaded already
|
|
if (win._muiLoadedJS) return;
|
|
else win._muiLoadedJS = true;
|
|
|
|
// load dependencies
|
|
var jqLite = require('src/js/lib/jqLite'),
|
|
dropdown = require('src/js/dropdown'),
|
|
overlay = require('src/js/overlay'),
|
|
ripple = require('src/js/ripple'),
|
|
select = require('src/js/select'),
|
|
tabs = require('src/js/tabs'),
|
|
textfield = require('src/js/textfield');
|
|
|
|
// expose api
|
|
win.mui = {
|
|
overlay: overlay,
|
|
tabs: tabs.api
|
|
};
|
|
|
|
// init libraries
|
|
jqLite.ready(function() {
|
|
textfield.initListeners();
|
|
select.initListeners();
|
|
ripple.initListeners();
|
|
dropdown.initListeners();
|
|
tabs.initListeners();
|
|
});
|
|
})(window);
|
|
|
|
},{"src/js/dropdown":7,"src/js/lib/jqLite":8,"src/js/overlay":9,"src/js/ripple":10,"src/js/select":11,"src/js/tabs":12,"src/js/textfield":13}],2:[function(require,module,exports){
|
|
/**
|
|
* MUI config module
|
|
* @module config
|
|
*/
|
|
|
|
/** Define module API */
|
|
module.exports = {
|
|
/** Use debug mode */
|
|
debug: true
|
|
};
|
|
|
|
},{}],3:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS animation helper module
|
|
* @module lib/animationHelpers
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var jqLite = require('./jqLite'),
|
|
util = require('./util'),
|
|
animationEvents = 'animationstart mozAnimationStart webkitAnimationStart',
|
|
animationCallbacks = {};
|
|
|
|
|
|
/**
|
|
* Register callbacks
|
|
* @param {String} name - The animation name
|
|
* @param {Function} callbackFn = The callback function
|
|
*/
|
|
function onAnimationStartFn(name, callbackFn) {
|
|
// get/set callback function
|
|
var callbacks = animationCallbacks[name];
|
|
if (!callbacks) callbacks = animationCallbacks[name] = [];
|
|
callbacks.push(callbackFn);
|
|
|
|
// initialize listeners
|
|
if (!this.init) {
|
|
// add css classes
|
|
loadCss();
|
|
|
|
// add listener
|
|
jqLite.on(document, animationEvents, animationStartHandler, true);
|
|
|
|
// set flag
|
|
this.init = true;
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Animation start handler
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
function animationStartHandler(ev) {
|
|
var callbacks = animationCallbacks[ev.animationName] || [],
|
|
i = callbacks.length;
|
|
|
|
// exit if a callback hasn't been registered
|
|
if (!i) return;
|
|
|
|
// stop other callbacks from firing
|
|
ev.stopImmediatePropagation();
|
|
|
|
// iterate through callbacks
|
|
while (i--) callbacks[i](ev);
|
|
}
|
|
|
|
|
|
/**
|
|
* Load animation css
|
|
*/
|
|
function loadCss() {
|
|
// define rules
|
|
var rules = [
|
|
['.mui-btn', 'mui-btn-inserted'],
|
|
['[data-mui-toggle="dropdown"]', 'mui-dropdown-inserted'],
|
|
[
|
|
'.mui-btn[data-mui-toggle="dropdown"]',
|
|
'mui-btn-inserted,mui-dropdown-inserted'
|
|
],
|
|
['[data-mui-toggle="tab"]', 'mui-tab-inserted'],
|
|
['.mui-textfield > input', 'mui-textfield-inserted'],
|
|
['.mui-textfield > textarea', 'mui-textfield-inserted'],
|
|
['.mui-textfield > input:-webkit-autofill', 'mui-textfield-autofill'],
|
|
['.mui-textfield > textarea:-webkit-autofill', 'mui-textfield-autofill'],
|
|
['.mui-select > select', 'mui-select-inserted'],
|
|
['.mui-select > select ~ .mui-event-trigger', 'mui-node-inserted'],
|
|
['.mui-select > select:disabled ~ .mui-event-trigger', 'mui-node-disabled']
|
|
];
|
|
|
|
// build css
|
|
var css = '',
|
|
rule;
|
|
|
|
for (var i=0, m=rules.length; i < m; i++) {
|
|
rule = rules[i];
|
|
css += '@keyframes ' + rule[1];
|
|
css += '{from{transform:none;}to{transform:none;}}';
|
|
css += rule[0];
|
|
css += '{animation-duration:0.0001s;animation-name:' + rule[1] + ';}';
|
|
}
|
|
|
|
// add CSS to DOM
|
|
util.loadStyle(css);
|
|
}
|
|
|
|
|
|
/**
|
|
* Define module API
|
|
*/
|
|
module.exports = {
|
|
animationEvents: animationEvents,
|
|
onAnimationStart: onAnimationStartFn
|
|
}
|
|
|
|
},{"./jqLite":5,"./util":6}],4:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS form helpers module
|
|
* @module lib/forms.py
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
var wrapperPadding = 15, // from CSS
|
|
inputHeight = 32, // from CSS
|
|
rowHeight = 42, // from CSS
|
|
menuPadding = 8; // from CSS
|
|
|
|
|
|
/**
|
|
* Menu position/size/scroll helper
|
|
* @returns {Object} Object with keys 'height', 'top', 'scrollTop'
|
|
*/
|
|
function getMenuPositionalCSSFn(wrapperEl, numRows, selectedRow) {
|
|
var viewHeight = document.documentElement.clientHeight;
|
|
|
|
// determine 'height'
|
|
var h = numRows * rowHeight + 2 * menuPadding,
|
|
height = Math.min(h, viewHeight);
|
|
|
|
// determine 'top'
|
|
var top, initTop, minTop, maxTop;
|
|
|
|
initTop = (menuPadding + rowHeight) - (wrapperPadding + inputHeight);
|
|
initTop -= selectedRow * rowHeight;
|
|
|
|
minTop = -1 * wrapperEl.getBoundingClientRect().top;
|
|
maxTop = (viewHeight - height) + minTop;
|
|
|
|
top = Math.min(Math.max(initTop, minTop), maxTop);
|
|
|
|
// determine 'scrollTop'
|
|
var scrollTop = 0,
|
|
scrollIdeal,
|
|
scrollMax;
|
|
|
|
if (h > viewHeight) {
|
|
scrollIdeal = (menuPadding + (selectedRow + 1) * rowHeight) -
|
|
(-1 * top + wrapperPadding + inputHeight);
|
|
scrollMax = numRows * rowHeight + 2 * menuPadding - height;
|
|
scrollTop = Math.min(scrollIdeal, scrollMax);
|
|
}
|
|
|
|
return {
|
|
'height': height + 'px',
|
|
'top': top + 'px',
|
|
'scrollTop': scrollTop
|
|
};
|
|
}
|
|
|
|
|
|
/** Define module API */
|
|
module.exports = {
|
|
getMenuPositionalCSS: getMenuPositionalCSSFn
|
|
};
|
|
|
|
},{}],5:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS jqLite module
|
|
* @module lib/jqLite
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
/**
|
|
* Add a class to an element.
|
|
* @param {Element} element - The DOM element.
|
|
* @param {string} cssClasses - Space separated list of class names.
|
|
*/
|
|
function jqLiteAddClass(element, cssClasses) {
|
|
if (!cssClasses || !element.setAttribute) return;
|
|
|
|
var existingClasses = _getExistingClasses(element),
|
|
splitClasses = cssClasses.split(' '),
|
|
cssClass;
|
|
|
|
for (var i=0; i < splitClasses.length; i++) {
|
|
cssClass = splitClasses[i].trim();
|
|
if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
|
|
existingClasses += cssClass + ' ';
|
|
}
|
|
}
|
|
|
|
element.setAttribute('class', existingClasses.trim());
|
|
}
|
|
|
|
|
|
/**
|
|
* Get or set CSS properties.
|
|
* @param {Element} element - The DOM element.
|
|
* @param {string} [name] - The property name.
|
|
* @param {string} [value] - The property value.
|
|
*/
|
|
function jqLiteCss(element, name, value) {
|
|
// Return full style object
|
|
if (name === undefined) {
|
|
return getComputedStyle(element);
|
|
}
|
|
|
|
var nameType = jqLiteType(name);
|
|
|
|
// Set multiple values
|
|
if (nameType === 'object') {
|
|
for (var key in name) element.style[_camelCase(key)] = name[key];
|
|
return;
|
|
}
|
|
|
|
// Set a single value
|
|
if (nameType === 'string' && value !== undefined) {
|
|
element.style[_camelCase(name)] = value;
|
|
}
|
|
|
|
var styleObj = getComputedStyle(element),
|
|
isArray = (jqLiteType(name) === 'array');
|
|
|
|
// Read single value
|
|
if (!isArray) return _getCurrCssProp(element, name, styleObj);
|
|
|
|
// Read multiple values
|
|
var outObj = {},
|
|
key;
|
|
|
|
for (var i=0; i < name.length; i++) {
|
|
key = name[i];
|
|
outObj[key] = _getCurrCssProp(element, key, styleObj);
|
|
}
|
|
|
|
return outObj;
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if element has class.
|
|
* @param {Element} element - The DOM element.
|
|
* @param {string} cls - The class name string.
|
|
*/
|
|
function jqLiteHasClass(element, cls) {
|
|
if (!cls || !element.getAttribute) return false;
|
|
return (_getExistingClasses(element).indexOf(' ' + cls + ' ') > -1);
|
|
}
|
|
|
|
|
|
/**
|
|
* Return the type of a variable.
|
|
* @param {} somevar - The JavaScript variable.
|
|
*/
|
|
function jqLiteType(somevar) {
|
|
// handle undefined
|
|
if (somevar === undefined) return 'undefined';
|
|
|
|
// handle others (of type [object <Type>])
|
|
var typeStr = Object.prototype.toString.call(somevar);
|
|
if (typeStr.indexOf('[object ') === 0) {
|
|
return typeStr.slice(8, -1).toLowerCase();
|
|
} else {
|
|
throw new Error("MUI: Could not understand type: " + typeStr);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Attach an event handler to a DOM element
|
|
* @param {Element} element - The DOM element.
|
|
* @param {string} events - Space separated event names.
|
|
* @param {Function} callback - The callback function.
|
|
* @param {Boolean} useCapture - Use capture flag.
|
|
*/
|
|
function jqLiteOn(element, events, callback, useCapture) {
|
|
useCapture = (useCapture === undefined) ? false : useCapture;
|
|
|
|
var cache = element._muiEventCache = element._muiEventCache || {};
|
|
|
|
events.split(' ').map(function(event) {
|
|
// add to DOM
|
|
element.addEventListener(event, callback, useCapture);
|
|
|
|
// add to cache
|
|
cache[event] = cache[event] || [];
|
|
cache[event].push([callback, useCapture]);
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove an event handler from a DOM element
|
|
* @param {Element} element - The DOM element.
|
|
* @param {string} events - Space separated event names.
|
|
* @param {Function} callback - The callback function.
|
|
* @param {Boolean} useCapture - Use capture flag.
|
|
*/
|
|
function jqLiteOff(element, events, callback, useCapture) {
|
|
useCapture = (useCapture === undefined) ? false : useCapture;
|
|
|
|
// remove from cache
|
|
var cache = element._muiEventCache = element._muiEventCache || {},
|
|
argsList,
|
|
args,
|
|
i;
|
|
|
|
events.split(' ').map(function(event) {
|
|
argsList = cache[event] || [];
|
|
|
|
i = argsList.length;
|
|
while (i--) {
|
|
args = argsList[i];
|
|
|
|
// remove all events if callback is undefined
|
|
if (callback === undefined ||
|
|
(args[0] === callback && args[1] === useCapture)) {
|
|
|
|
// remove from cache
|
|
argsList.splice(i, 1);
|
|
|
|
// remove from DOM
|
|
element.removeEventListener(event, args[0], args[1]);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Attach an event hander which will only execute once per element per event
|
|
* @param {Element} element - The DOM element.
|
|
* @param {string} events - Space separated event names.
|
|
* @param {Function} callback - The callback function.
|
|
* @param {Boolean} useCapture - Use capture flag.
|
|
*/
|
|
function jqLiteOne(element, events, callback, useCapture) {
|
|
events.split(' ').map(function(event) {
|
|
jqLiteOn(element, event, function onFn(ev) {
|
|
// execute callback
|
|
if (callback) callback.apply(this, arguments);
|
|
|
|
// remove wrapper
|
|
jqLiteOff(element, event, onFn, useCapture);
|
|
}, useCapture);
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Get or set horizontal scroll position
|
|
* @param {Element} element - The DOM element
|
|
* @param {number} [value] - The scroll position
|
|
*/
|
|
function jqLiteScrollLeft(element, value) {
|
|
var win = window;
|
|
|
|
// get
|
|
if (value === undefined) {
|
|
if (element === win) {
|
|
var docEl = document.documentElement;
|
|
return (win.pageXOffset || docEl.scrollLeft) - (docEl.clientLeft || 0);
|
|
} else {
|
|
return element.scrollLeft;
|
|
}
|
|
}
|
|
|
|
// set
|
|
if (element === win) win.scrollTo(value, jqLiteScrollTop(win));
|
|
else element.scrollLeft = value;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get or set vertical scroll position
|
|
* @param {Element} element - The DOM element
|
|
* @param {number} value - The scroll position
|
|
*/
|
|
function jqLiteScrollTop(element, value) {
|
|
var win = window;
|
|
|
|
// get
|
|
if (value === undefined) {
|
|
if (element === win) {
|
|
var docEl = document.documentElement;
|
|
return (win.pageYOffset || docEl.scrollTop) - (docEl.clientTop || 0);
|
|
} else {
|
|
return element.scrollTop;
|
|
}
|
|
}
|
|
|
|
// set
|
|
if (element === win) win.scrollTo(jqLiteScrollLeft(win), value);
|
|
else element.scrollTop = value;
|
|
}
|
|
|
|
|
|
/**
|
|
* Return object representing top/left offset and element height/width.
|
|
* @param {Element} element - The DOM element.
|
|
*/
|
|
function jqLiteOffset(element) {
|
|
var win = window,
|
|
rect = element.getBoundingClientRect(),
|
|
scrollTop = jqLiteScrollTop(win),
|
|
scrollLeft = jqLiteScrollLeft(win);
|
|
|
|
return {
|
|
top: rect.top + scrollTop,
|
|
left: rect.left + scrollLeft,
|
|
height: rect.height,
|
|
width: rect.width
|
|
};
|
|
}
|
|
|
|
|
|
/**
|
|
* Attach a callback to the DOM ready event listener
|
|
* @param {Function} fn - The callback function.
|
|
*/
|
|
function jqLiteReady(fn) {
|
|
var done = false,
|
|
top = true,
|
|
doc = document,
|
|
win = doc.defaultView,
|
|
root = doc.documentElement,
|
|
add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
|
|
rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
|
|
pre = doc.addEventListener ? '' : 'on';
|
|
|
|
var init = function(e) {
|
|
if (e.type == 'readystatechange' && doc.readyState != 'complete') {
|
|
return;
|
|
}
|
|
|
|
(e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
|
|
if (!done && (done = true)) fn.call(win, e.type || e);
|
|
};
|
|
|
|
var poll = function() {
|
|
try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
|
|
init('poll');
|
|
};
|
|
|
|
if (doc.readyState == 'complete') {
|
|
fn.call(win, 'lazy');
|
|
} else {
|
|
if (doc.createEventObject && root.doScroll) {
|
|
try { top = !win.frameElement; } catch(e) { }
|
|
if (top) poll();
|
|
}
|
|
doc[add](pre + 'DOMContentLoaded', init, false);
|
|
doc[add](pre + 'readystatechange', init, false);
|
|
win[add](pre + 'load', init, false);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove classes from a DOM element
|
|
* @param {Element} element - The DOM element.
|
|
* @param {string} cssClasses - Space separated list of class names.
|
|
*/
|
|
function jqLiteRemoveClass(element, cssClasses) {
|
|
if (!cssClasses || !element.setAttribute) return;
|
|
|
|
var existingClasses = _getExistingClasses(element),
|
|
splitClasses = cssClasses.split(' '),
|
|
cssClass;
|
|
|
|
for (var i=0; i < splitClasses.length; i++) {
|
|
cssClass = splitClasses[i].trim();
|
|
while (existingClasses.indexOf(' ' + cssClass + ' ') >= 0) {
|
|
existingClasses = existingClasses.replace(' ' + cssClass + ' ', ' ');
|
|
}
|
|
}
|
|
|
|
element.setAttribute('class', existingClasses.trim());
|
|
}
|
|
|
|
|
|
// ------------------------------
|
|
// Utilities
|
|
// ------------------------------
|
|
var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g,
|
|
MOZ_HACK_REGEXP = /^moz([A-Z])/,
|
|
ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g;
|
|
|
|
|
|
function _getExistingClasses(element) {
|
|
var classes = (element.getAttribute('class') || '').replace(/[\n\t]/g, '');
|
|
return ' ' + classes + ' ';
|
|
}
|
|
|
|
|
|
function _camelCase(name) {
|
|
return name.
|
|
replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
|
|
return offset ? letter.toUpperCase() : letter;
|
|
}).
|
|
replace(MOZ_HACK_REGEXP, 'Moz$1');
|
|
}
|
|
|
|
|
|
function _escapeRegExp(string) {
|
|
return string.replace(ESCAPE_REGEXP, "\\$1");
|
|
}
|
|
|
|
|
|
function _getCurrCssProp(elem, name, computed) {
|
|
var ret;
|
|
|
|
// try computed style
|
|
ret = computed.getPropertyValue(name);
|
|
|
|
// try style attribute (if element is not attached to document)
|
|
if (ret === '' && !elem.ownerDocument) ret = elem.style[_camelCase(name)];
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/**
|
|
* Module API
|
|
*/
|
|
module.exports = {
|
|
/** Add classes */
|
|
addClass: jqLiteAddClass,
|
|
|
|
/** Get or set CSS properties */
|
|
css: jqLiteCss,
|
|
|
|
/** Check for class */
|
|
hasClass: jqLiteHasClass,
|
|
|
|
/** Remove event handlers */
|
|
off: jqLiteOff,
|
|
|
|
/** Return offset values */
|
|
offset: jqLiteOffset,
|
|
|
|
/** Add event handlers */
|
|
on: jqLiteOn,
|
|
|
|
/** Add an execute-once event handler */
|
|
one: jqLiteOne,
|
|
|
|
/** DOM ready event handler */
|
|
ready: jqLiteReady,
|
|
|
|
/** Remove classes */
|
|
removeClass: jqLiteRemoveClass,
|
|
|
|
/** Check JavaScript variable instance type */
|
|
type: jqLiteType,
|
|
|
|
/** Get or set horizontal scroll position */
|
|
scrollLeft: jqLiteScrollLeft,
|
|
|
|
/** Get or set vertical scroll position */
|
|
scrollTop: jqLiteScrollTop
|
|
};
|
|
|
|
},{}],6:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS utilities module
|
|
* @module lib/util
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
var config = require('../config'),
|
|
jqLite = require('./jqLite'),
|
|
scrollLock = 0,
|
|
scrollLockCls = 'mui-scroll-lock',
|
|
scrollLockPos,
|
|
scrollStyleEl,
|
|
scrollEventHandler,
|
|
_scrollBarWidth,
|
|
_supportsPointerEvents;
|
|
|
|
|
|
scrollEventHandler = function(ev) {
|
|
// stop propagation on window scroll events
|
|
if (!ev.target.tagName) ev.stopImmediatePropagation();
|
|
}
|
|
|
|
|
|
/**
|
|
* Logging function
|
|
*/
|
|
function logFn() {
|
|
var win = window;
|
|
|
|
if (config.debug && typeof win.console !== "undefined") {
|
|
try {
|
|
win.console.log.apply(win.console, arguments);
|
|
} catch (a) {
|
|
var e = Array.prototype.slice.call(arguments);
|
|
win.console.log(e.join("\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Load CSS text in new stylesheet
|
|
* @param {string} cssText - The css text.
|
|
*/
|
|
function loadStyleFn(cssText) {
|
|
var doc = document,
|
|
head;
|
|
|
|
// copied from jQuery
|
|
head = doc.head ||
|
|
doc.getElementsByTagName('head')[0] ||
|
|
doc.documentElement;
|
|
|
|
var e = doc.createElement('style');
|
|
e.type = 'text/css';
|
|
|
|
if (e.styleSheet) e.styleSheet.cssText = cssText;
|
|
else e.appendChild(doc.createTextNode(cssText));
|
|
|
|
// add to document
|
|
head.insertBefore(e, head.firstChild);
|
|
|
|
return e;
|
|
}
|
|
|
|
|
|
/**
|
|
* Raise an error
|
|
* @param {string} msg - The error message.
|
|
*/
|
|
function raiseErrorFn(msg, useConsole) {
|
|
if (useConsole) {
|
|
if (typeof console !== 'undefined') console.error('MUI Warning: ' + msg);
|
|
} else {
|
|
throw new Error('MUI: ' + msg);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Convert Classname object, with class as key and true/false as value, to an
|
|
* class string.
|
|
* @param {Object} classes The classes
|
|
* @return {String} class string
|
|
*/
|
|
function classNamesFn(classes) {
|
|
var cs = '';
|
|
for (var i in classes) {
|
|
cs += (classes[i]) ? i + ' ' : '';
|
|
}
|
|
return cs.trim();
|
|
}
|
|
|
|
|
|
/**
|
|
* Check if client supports pointer events.
|
|
*/
|
|
function supportsPointerEventsFn() {
|
|
// check cache
|
|
if (_supportsPointerEvents !== undefined) return _supportsPointerEvents;
|
|
|
|
var element = document.createElement('x');
|
|
element.style.cssText = 'pointer-events:auto';
|
|
_supportsPointerEvents = (element.style.pointerEvents === 'auto');
|
|
return _supportsPointerEvents;
|
|
}
|
|
|
|
|
|
/**
|
|
* Create callback closure.
|
|
* @param {Object} instance - The object instance.
|
|
* @param {String} funcName - The name of the callback function.
|
|
*/
|
|
function callbackFn(instance, funcName) {
|
|
return function() {instance[funcName].apply(instance, arguments);};
|
|
}
|
|
|
|
|
|
/**
|
|
* Dispatch event.
|
|
* @param {Element} element - The DOM element.
|
|
* @param {String} eventType - The event type.
|
|
* @param {Boolean} bubbles=true - If true, event bubbles.
|
|
* @param {Boolean} cancelable=true = If true, event is cancelable
|
|
* @param {Object} [data] - Data to add to event object
|
|
*/
|
|
function dispatchEventFn(element, eventType, bubbles, cancelable, data) {
|
|
var ev = document.createEvent('HTMLEvents'),
|
|
bubbles = (bubbles !== undefined) ? bubbles : true,
|
|
cancelable = (cancelable !== undefined) ? cancelable : true,
|
|
k;
|
|
|
|
ev.initEvent(eventType, bubbles, cancelable);
|
|
|
|
// add data to event object
|
|
if (data) for (k in data) ev[k] = data[k];
|
|
|
|
// dispatch
|
|
if (element) element.dispatchEvent(ev);
|
|
|
|
return ev;
|
|
}
|
|
|
|
|
|
/**
|
|
* Turn on window scroll lock.
|
|
*/
|
|
function enableScrollLockFn() {
|
|
// increment counter
|
|
scrollLock += 1;
|
|
|
|
// add lock
|
|
if (scrollLock === 1) {
|
|
var doc = document,
|
|
win = window,
|
|
htmlEl = doc.documentElement,
|
|
bodyEl = doc.body,
|
|
scrollBarWidth = getScrollBarWidth(),
|
|
cssProps,
|
|
cssStr,
|
|
x;
|
|
|
|
// define scroll lock class dynamically
|
|
cssProps = ['overflow:hidden'];
|
|
|
|
if (scrollBarWidth) {
|
|
// scrollbar-y
|
|
if (htmlEl.scrollHeight > htmlEl.clientHeight) {
|
|
x = parseInt(jqLite.css(bodyEl, 'padding-right')) + scrollBarWidth;
|
|
cssProps.push('padding-right:' + x + 'px');
|
|
}
|
|
|
|
// scrollbar-x
|
|
if (htmlEl.scrollWidth > htmlEl.clientWidth) {
|
|
x = parseInt(jqLite.css(bodyEl, 'padding-bottom')) + scrollBarWidth;
|
|
cssProps.push('padding-bottom:' + x + 'px');
|
|
}
|
|
}
|
|
|
|
// define css class dynamically
|
|
cssStr = '.' + scrollLockCls + '{';
|
|
cssStr += cssProps.join(' !important;') + ' !important;}';
|
|
scrollStyleEl = loadStyleFn(cssStr);
|
|
|
|
// cancel 'scroll' event listener callbacks
|
|
jqLite.on(win, 'scroll', scrollEventHandler, true);
|
|
|
|
// add scroll lock
|
|
scrollLockPos = {left: jqLite.scrollLeft(win), top: jqLite.scrollTop(win)};
|
|
jqLite.addClass(bodyEl, scrollLockCls);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Turn off window scroll lock.
|
|
* @param {Boolean} resetPos - Reset scroll position to original value.
|
|
*/
|
|
function disableScrollLockFn(resetPos) {
|
|
// ignore
|
|
if (scrollLock === 0) return;
|
|
|
|
// decrement counter
|
|
scrollLock -= 1;
|
|
|
|
// remove lock
|
|
if (scrollLock === 0) {
|
|
// remove scroll lock and delete style element
|
|
jqLite.removeClass(document.body, scrollLockCls);
|
|
scrollStyleEl.parentNode.removeChild(scrollStyleEl);
|
|
|
|
// restore scroll position
|
|
if (resetPos) window.scrollTo(scrollLockPos.left, scrollLockPos.top);
|
|
|
|
// restore scroll event listeners
|
|
jqLite.off(window, 'scroll', scrollEventHandler, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return scroll bar width.
|
|
*/
|
|
var getScrollBarWidth = function() {
|
|
// check cache
|
|
if (_scrollBarWidth !== undefined) return _scrollBarWidth;
|
|
|
|
// calculate scroll bar width
|
|
var doc = document,
|
|
bodyEl = doc.body,
|
|
el = doc.createElement('div');
|
|
|
|
el.innerHTML = '<div style="width:50px;height:50px;position:absolute;' +
|
|
'left:-50px;top:-50px;overflow:auto;"><div style="width:1px;' +
|
|
'height:100px;"></div></div>';
|
|
el = el.firstChild;
|
|
bodyEl.appendChild(el);
|
|
_scrollBarWidth = el.offsetWidth - el.clientWidth;
|
|
bodyEl.removeChild(el);
|
|
|
|
return _scrollBarWidth;
|
|
}
|
|
|
|
|
|
/**
|
|
* requestAnimationFrame polyfilled
|
|
* @param {Function} callback - The callback function
|
|
*/
|
|
function requestAnimationFrameFn(callback) {
|
|
var fn = window.requestAnimationFrame;
|
|
if (fn) fn(callback);
|
|
else setTimeout(callback, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Define the module API
|
|
*/
|
|
module.exports = {
|
|
/** Create callback closures */
|
|
callback: callbackFn,
|
|
|
|
/** Classnames object to string */
|
|
classNames: classNamesFn,
|
|
|
|
/** Disable scroll lock */
|
|
disableScrollLock: disableScrollLockFn,
|
|
|
|
/** Dispatch event */
|
|
dispatchEvent: dispatchEventFn,
|
|
|
|
/** Enable scroll lock */
|
|
enableScrollLock: enableScrollLockFn,
|
|
|
|
/** Log messages to the console when debug is turned on */
|
|
log: logFn,
|
|
|
|
/** Load CSS text as new stylesheet */
|
|
loadStyle: loadStyleFn,
|
|
|
|
/** Raise MUI error */
|
|
raiseError: raiseErrorFn,
|
|
|
|
/** Request animation frame */
|
|
requestAnimationFrame: requestAnimationFrameFn,
|
|
|
|
/** Support Pointer Events check */
|
|
supportsPointerEvents: supportsPointerEventsFn
|
|
};
|
|
|
|
},{"../config":2,"./jqLite":5}],7:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS dropdown module
|
|
* @module dropdowns
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
var jqLite = require('./lib/jqLite'),
|
|
util = require('./lib/util'),
|
|
animationHelpers = require('./lib/animationHelpers'),
|
|
attrKey = 'data-mui-toggle',
|
|
attrSelector = '[data-mui-toggle="dropdown"]',
|
|
openClass = 'mui--is-open',
|
|
menuClass = 'mui-dropdown__menu';
|
|
|
|
|
|
/**
|
|
* Initialize toggle element.
|
|
* @param {Element} toggleEl - The toggle element.
|
|
*/
|
|
function initialize(toggleEl) {
|
|
// check flag
|
|
if (toggleEl._muiDropdown === true) return;
|
|
else toggleEl._muiDropdown = true;
|
|
|
|
// use type "button" to prevent form submission by default
|
|
var tagName = toggleEl.tagName;
|
|
if ((tagName === 'INPUT' || tagName === 'BUTTON')
|
|
&& !toggleEl.hasAttribute('type')) {
|
|
toggleEl.type = 'button';
|
|
}
|
|
|
|
// attach click handler
|
|
jqLite.on(toggleEl, 'click', clickHandler);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle click events on dropdown toggle element.
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
function clickHandler(ev) {
|
|
// only left clicks
|
|
if (ev.button !== 0) return;
|
|
|
|
var toggleEl = this;
|
|
|
|
// exit if toggle button is disabled
|
|
if (toggleEl.getAttribute('disabled') !== null) return;
|
|
|
|
// toggle dropdown
|
|
toggleDropdown(toggleEl);
|
|
}
|
|
|
|
|
|
/**
|
|
* Toggle the dropdown.
|
|
* @param {Element} toggleEl - The dropdown toggle element.
|
|
*/
|
|
function toggleDropdown(toggleEl) {
|
|
var wrapperEl = toggleEl.parentNode,
|
|
menuEl = toggleEl.nextElementSibling,
|
|
doc = wrapperEl.ownerDocument;
|
|
|
|
// exit if no menu element
|
|
if (!menuEl || !jqLite.hasClass(menuEl, menuClass)) {
|
|
return util.raiseError('Dropdown menu element not found');
|
|
}
|
|
|
|
// method to close dropdown
|
|
function closeDropdownFn() {
|
|
jqLite.removeClass(menuEl, openClass);
|
|
|
|
// remove event handlers
|
|
jqLite.off(doc, 'click', closeDropdownFn);
|
|
}
|
|
|
|
// method to open dropdown
|
|
function openDropdownFn() {
|
|
// position menu element below toggle button
|
|
var wrapperRect = wrapperEl.getBoundingClientRect(),
|
|
toggleRect = toggleEl.getBoundingClientRect();
|
|
|
|
var top = toggleRect.top - wrapperRect.top + toggleRect.height;
|
|
jqLite.css(menuEl, 'top', top + 'px');
|
|
|
|
// add open class to wrapper
|
|
jqLite.addClass(menuEl, openClass);
|
|
|
|
// close dropdown when user clicks outside of menu
|
|
setTimeout(function() {jqLite.on(doc, 'click', closeDropdownFn);}, 0);
|
|
}
|
|
|
|
// toggle dropdown
|
|
if (jqLite.hasClass(menuEl, openClass)) closeDropdownFn();
|
|
else openDropdownFn();
|
|
}
|
|
|
|
|
|
/** Define module API */
|
|
module.exports = {
|
|
/** Initialize module listeners */
|
|
initListeners: function() {
|
|
// markup elements available when method is called
|
|
var elList = document.querySelectorAll(attrSelector),
|
|
i = elList.length;
|
|
while (i--) {initialize(elList[i]);}
|
|
|
|
// listen for new elements
|
|
animationHelpers.onAnimationStart('mui-dropdown-inserted', function(ev) {
|
|
initialize(ev.target);
|
|
});
|
|
}
|
|
};
|
|
|
|
},{"./lib/animationHelpers":3,"./lib/jqLite":5,"./lib/util":6}],8:[function(require,module,exports){
|
|
module.exports=require(5)
|
|
},{}],9:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS overlay module
|
|
* @module overlay
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
var util = require('./lib/util'),
|
|
jqLite = require('./lib/jqLite'),
|
|
overlayId = 'mui-overlay',
|
|
bodyClass = 'mui--overflow-hidden',
|
|
iosRegex = /(iPad|iPhone|iPod)/g,
|
|
activeElement;
|
|
|
|
|
|
/**
|
|
* Turn overlay on/off.
|
|
* @param {string} action - Turn overlay "on"/"off".
|
|
* @param {object} [options]
|
|
* @config {boolean} [keyboard] - If true, close when escape key is pressed.
|
|
* @config {boolean} [static] - If false, close when backdrop is clicked.
|
|
* @config {Function} [onclose] - Callback function to execute on close
|
|
* @param {Element} [childElement] - Child element to add to overlay.
|
|
*/
|
|
function overlayFn(action) {
|
|
var overlayEl;
|
|
|
|
if (action === 'on') {
|
|
// extract arguments
|
|
var arg, options, childElement;
|
|
|
|
// pull options and childElement from arguments
|
|
for (var i=arguments.length - 1; i > 0; i--) {
|
|
arg = arguments[i];
|
|
|
|
if (jqLite.type(arg) === 'object') options = arg;
|
|
if (arg instanceof Element && arg.nodeType === 1) childElement = arg;
|
|
}
|
|
|
|
// option defaults
|
|
options = options || {};
|
|
if (options.keyboard === undefined) options.keyboard = true;
|
|
if (options.static === undefined) options.static = false;
|
|
|
|
// execute method
|
|
overlayEl = overlayOn(options, childElement);
|
|
|
|
} else if (action === 'off') {
|
|
overlayEl = overlayOff();
|
|
|
|
} else {
|
|
// raise error
|
|
util.raiseError("Expecting 'on' or 'off'");
|
|
|
|
}
|
|
|
|
return overlayEl;
|
|
}
|
|
|
|
|
|
/**
|
|
* Turn on overlay.
|
|
* @param {object} options - Overlay options.
|
|
* @param {Element} childElement - The child element.
|
|
*/
|
|
function overlayOn(options, childElement) {
|
|
var doc = document,
|
|
bodyEl = doc.body,
|
|
overlayEl = doc.getElementById(overlayId);
|
|
|
|
// cache activeElement
|
|
if (doc.activeElement) activeElement = doc.activeElement;
|
|
|
|
// add overlay
|
|
util.enableScrollLock();
|
|
|
|
if (!overlayEl) {
|
|
// create overlayEl
|
|
overlayEl = doc.createElement('div');
|
|
overlayEl.setAttribute('id', overlayId);
|
|
overlayEl.setAttribute('tabindex', '-1');
|
|
|
|
// add child element
|
|
if (childElement) overlayEl.appendChild(childElement);
|
|
|
|
bodyEl.appendChild(overlayEl);
|
|
|
|
} else {
|
|
// remove existing children
|
|
while (overlayEl.firstChild) overlayEl.removeChild(overlayEl.firstChild);
|
|
|
|
// add child element
|
|
if (childElement) overlayEl.appendChild(childElement);
|
|
}
|
|
|
|
// iOS bugfix
|
|
if (iosRegex.test(navigator.userAgent)) {
|
|
jqLite.css(overlayEl, 'cursor', 'pointer');
|
|
}
|
|
|
|
// handle options
|
|
if (options.keyboard) addKeyupHandler();
|
|
else removeKeyupHandler();
|
|
|
|
if (options.static) removeClickHandler(overlayEl);
|
|
else addClickHandler(overlayEl);
|
|
|
|
// attach options
|
|
overlayEl.muiOptions = options;
|
|
|
|
// focus overlay element
|
|
overlayEl.focus();
|
|
|
|
return overlayEl;
|
|
}
|
|
|
|
|
|
/**
|
|
* Turn off overlay.
|
|
*/
|
|
function overlayOff() {
|
|
var overlayEl = document.getElementById(overlayId),
|
|
callbackFn;
|
|
|
|
if (overlayEl) {
|
|
// remove children
|
|
while (overlayEl.firstChild) overlayEl.removeChild(overlayEl.firstChild);
|
|
|
|
// remove overlay element
|
|
overlayEl.parentNode.removeChild(overlayEl);
|
|
|
|
// callback reference
|
|
callbackFn = overlayEl.muiOptions.onclose;
|
|
|
|
// remove click handler
|
|
removeClickHandler(overlayEl);
|
|
}
|
|
|
|
util.disableScrollLock();
|
|
|
|
// remove keyup handler
|
|
removeKeyupHandler();
|
|
|
|
// return focus to activeElement
|
|
if (activeElement) activeElement.focus();
|
|
|
|
// execute callback
|
|
if (callbackFn) callbackFn();
|
|
|
|
return overlayEl;
|
|
}
|
|
|
|
|
|
/**
|
|
* Add keyup handler.
|
|
*/
|
|
function addKeyupHandler() {
|
|
jqLite.on(document, 'keyup', onKeyup);
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove keyup handler.
|
|
*/
|
|
function removeKeyupHandler() {
|
|
jqLite.off(document, 'keyup', onKeyup);
|
|
}
|
|
|
|
|
|
/**
|
|
* Teardown overlay when escape key is pressed.
|
|
*/
|
|
function onKeyup(ev) {
|
|
if (ev.keyCode === 27) overlayOff();
|
|
}
|
|
|
|
|
|
/**
|
|
* Add click handler.
|
|
*/
|
|
function addClickHandler(overlayEl) {
|
|
jqLite.on(overlayEl, 'click', onClick);
|
|
}
|
|
|
|
|
|
/**
|
|
* Remove click handler.
|
|
*/
|
|
function removeClickHandler(overlayEl) {
|
|
jqLite.off(overlayEl, 'click', onClick);
|
|
}
|
|
|
|
|
|
/**
|
|
* Teardown overlay when backdrop is clicked.
|
|
*/
|
|
function onClick(ev) {
|
|
if (ev.target.id === overlayId) overlayOff();
|
|
}
|
|
|
|
|
|
/** Define module API */
|
|
module.exports = overlayFn;
|
|
|
|
},{"./lib/jqLite":5,"./lib/util":6}],10:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS ripple module
|
|
* @module ripple
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
var jqLite = require('./lib/jqLite'),
|
|
util = require('./lib/util'),
|
|
animationHelpers = require('./lib/animationHelpers'),
|
|
supportsTouch = 'ontouchstart' in document.documentElement,
|
|
mouseDownEvents = (supportsTouch) ? 'touchstart' : 'mousedown',
|
|
mouseUpEvents = (supportsTouch) ? 'touchend' : 'mouseup mouseleave';
|
|
|
|
|
|
/**
|
|
* Add ripple effects to button element.
|
|
* @param {Element} buttonEl - The button element.
|
|
*/
|
|
function initialize(buttonEl) {
|
|
// check flag
|
|
if (buttonEl._muiRipple === true) return;
|
|
else buttonEl._muiRipple = true;
|
|
|
|
// exit if element is INPUT (doesn't support absolute positioned children)
|
|
if (buttonEl.tagName === 'INPUT') return;
|
|
|
|
// attach event handler
|
|
jqLite.on(buttonEl, mouseDownEvents, mouseDownHandler);
|
|
}
|
|
|
|
|
|
/**
|
|
* MouseDown Event handler.
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
function mouseDownHandler(ev) {
|
|
// only left clicks
|
|
if (ev.type === 'mousedown' && ev.button !== 0) return;
|
|
|
|
var buttonEl = this,
|
|
rippleEl = buttonEl._rippleEl;
|
|
|
|
// exit if button is disabled
|
|
if (buttonEl.disabled) return;
|
|
|
|
if (!rippleEl) {
|
|
// add ripple container (to avoid https://github.com/muicss/mui/issues/169)
|
|
var el = document.createElement('span');
|
|
el.className = 'mui-btn__ripple-container';
|
|
el.innerHTML = '<span class="mui-ripple"></span>';
|
|
buttonEl.appendChild(el);
|
|
|
|
// cache reference to ripple element
|
|
rippleEl = buttonEl._rippleEl = el.children[0];
|
|
|
|
// add mouseup handler on first-click
|
|
jqLite.on(buttonEl, mouseUpEvents, mouseUpHandler);
|
|
}
|
|
|
|
// get ripple element offset values and (x, y) position of click
|
|
var offset = jqLite.offset(buttonEl),
|
|
clickEv = (ev.type === 'touchstart') ? ev.touches[0] : ev,
|
|
radius,
|
|
diameter;
|
|
|
|
// calculate radius
|
|
radius = Math.sqrt(offset.height * offset.height +
|
|
offset.width * offset.width);
|
|
|
|
diameter = radius * 2 + 'px';
|
|
|
|
// set position and dimensions
|
|
jqLite.css(rippleEl, {
|
|
width: diameter,
|
|
height: diameter,
|
|
top: Math.round(clickEv.pageY - offset.top - radius) + 'px',
|
|
left: Math.round(clickEv.pageX - offset.left - radius) + 'px'
|
|
});
|
|
|
|
jqLite.removeClass(rippleEl, 'mui--is-animating');
|
|
jqLite.addClass(rippleEl, 'mui--is-visible');
|
|
|
|
// start animation
|
|
util.requestAnimationFrame(function() {
|
|
jqLite.addClass(rippleEl, 'mui--is-animating');
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* MouseUp event handler.
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
function mouseUpHandler(ev) {
|
|
// get ripple element
|
|
var rippleEl = this._rippleEl;
|
|
|
|
// allow a repaint to occur before removing class so animation shows for
|
|
// tap events
|
|
util.requestAnimationFrame(function() {
|
|
jqLite.removeClass(rippleEl, 'mui--is-visible');
|
|
});
|
|
}
|
|
|
|
|
|
/** Define module API */
|
|
module.exports = {
|
|
/** Initialize module listeners */
|
|
initListeners: function() {
|
|
// markup elements available when method is called
|
|
var elList = document.getElementsByClassName('mui-btn'),
|
|
i = elList.length;
|
|
while (i--) initialize(elList[i]);
|
|
|
|
// listen for new elements
|
|
animationHelpers.onAnimationStart('mui-btn-inserted', function(ev) {
|
|
initialize(ev.target);
|
|
});
|
|
}
|
|
};
|
|
|
|
},{"./lib/animationHelpers":3,"./lib/jqLite":5,"./lib/util":6}],11:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS select module
|
|
* @module forms/select
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
var jqLite = require('./lib/jqLite'),
|
|
util = require('./lib/util'),
|
|
animationHelpers = require('./lib/animationHelpers'),
|
|
formlib = require('./lib/forms'),
|
|
wrapperClass = 'mui-select',
|
|
cssSelector = '.mui-select > select',
|
|
menuClass = 'mui-select__menu',
|
|
selectedClass = 'mui--is-selected',
|
|
disabledClass = 'mui--is-disabled',
|
|
doc = document,
|
|
win = window;
|
|
|
|
|
|
/**
|
|
* Initialize select element.
|
|
* @param {Element} selectEl - The select element.
|
|
*/
|
|
function initialize(selectEl) {
|
|
// check flag
|
|
if (selectEl._muiSelect === true) return;
|
|
else selectEl._muiSelect = true;
|
|
|
|
// use default behavior on touch devices
|
|
if ('ontouchstart' in doc.documentElement) return;
|
|
|
|
// NOTE: To get around cross-browser issues with <select> behavior we will
|
|
// defer focus to the parent element and handle events there
|
|
|
|
var wrapperEl = selectEl.parentNode;
|
|
|
|
// initialize variables
|
|
wrapperEl._selectEl = selectEl;
|
|
wrapperEl._menu = null;
|
|
wrapperEl._q = '';
|
|
wrapperEl._qTimeout = null;
|
|
|
|
// make wrapper tab focusable, remove tab focus from <select>
|
|
if (!selectEl.disabled) wrapperEl.tabIndex = 0;
|
|
selectEl.tabIndex = -1;
|
|
|
|
// prevent built-in menu from opening on <select>
|
|
jqLite.on(selectEl, 'mousedown', onInnerMouseDown);
|
|
|
|
// attach event listeners for custom menu
|
|
jqLite.on(wrapperEl, 'click', onWrapperClick);
|
|
jqLite.on(wrapperEl, 'blur focus', onWrapperBlurOrFocus);
|
|
jqLite.on(wrapperEl, 'keydown', onWrapperKeyDown);
|
|
jqLite.on(wrapperEl, 'keypress', onWrapperKeyPress);
|
|
|
|
// add element to detect 'disabled' change (using sister element due to
|
|
// IE/Firefox issue
|
|
var el = document.createElement('div');
|
|
el.className = 'mui-event-trigger';
|
|
wrapperEl.appendChild(el);
|
|
|
|
// handle 'disabled' add/remove
|
|
jqLite.on(el, animationHelpers.animationEvents, function(ev) {
|
|
// no need to propagate
|
|
ev.stopPropagation();
|
|
|
|
if (ev.animationName === 'mui-node-disabled') {
|
|
ev.target.parentNode.removeAttribute('tabIndex');
|
|
} else {
|
|
ev.target.parentNode.tabIndex = 0;
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Disable default dropdown on mousedown.
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
function onInnerMouseDown(ev) {
|
|
// only left clicks
|
|
if (ev.button !== 0) return;
|
|
|
|
// prevent built-in menu from opening
|
|
ev.preventDefault();
|
|
}
|
|
|
|
|
|
/**
|
|
* Dispatch focus and blur events on inner <select> element.
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
function onWrapperBlurOrFocus(ev) {
|
|
util.dispatchEvent(this._selectEl, ev.type, false, false);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle keydown events when wrapper is focused
|
|
**/
|
|
function onWrapperKeyDown(ev) {
|
|
if (ev.defaultPrevented) return;
|
|
|
|
var keyCode = ev.keyCode,
|
|
menu = this._menu;
|
|
|
|
if (!menu) {
|
|
// spacebar, down, up
|
|
if (keyCode === 32 || keyCode === 38 || keyCode === 40) {
|
|
ev.preventDefault();
|
|
|
|
// open custom menu
|
|
renderMenu(this);
|
|
}
|
|
|
|
} else {
|
|
// tab
|
|
if (keyCode === 9) return menu.destroy();
|
|
|
|
// escape | up | down | enter
|
|
if (keyCode === 27 || keyCode === 40 || keyCode === 38 || keyCode === 13) {
|
|
ev.preventDefault();
|
|
}
|
|
|
|
if (keyCode === 27) {
|
|
// escape
|
|
menu.destroy();
|
|
} else if (keyCode === 40) {
|
|
// up
|
|
menu.increment();
|
|
} else if (keyCode === 38) {
|
|
// down
|
|
menu.decrement();
|
|
} else if (keyCode === 13) {
|
|
// enter
|
|
menu.selectCurrent();
|
|
menu.destroy();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
*
|
|
*/
|
|
function onWrapperKeyPress(ev) {
|
|
var menu = this._menu;
|
|
|
|
// exit if default prevented or menu is closed
|
|
if (ev.defaultPrevented || !menu) return;
|
|
|
|
// handle query timer
|
|
var self = this;
|
|
clearTimeout(this._qTimeout);
|
|
this._q += ev.key;
|
|
this._qTimeout = setTimeout(function() {self._q = '';}, 300);
|
|
|
|
// select first match alphabetically
|
|
var prefixRegex = new RegExp('^' + this._q, 'i'),
|
|
itemArray = menu.itemArray,
|
|
pos;
|
|
|
|
for (pos in itemArray) {
|
|
if (prefixRegex.test(itemArray[pos].innerText)) {
|
|
menu.selectPos(pos);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle click events on wrapper element.
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
function onWrapperClick(ev) {
|
|
// only left clicks, check default and disabled flags
|
|
if (ev.button !== 0 || this._selectEl.disabled) return;
|
|
|
|
// focus wrapper
|
|
this.focus();
|
|
|
|
// open menu
|
|
renderMenu(this);
|
|
}
|
|
|
|
|
|
/**
|
|
* Render options menu
|
|
*/
|
|
function renderMenu(wrapperEl) {
|
|
// check instance
|
|
if (wrapperEl._menu) return;
|
|
|
|
// render custom menu
|
|
wrapperEl._menu = new Menu(wrapperEl, wrapperEl._selectEl, function() {
|
|
wrapperEl._menu = null; // de-reference instance
|
|
wrapperEl.focus();
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Creates a new Menu
|
|
* @class
|
|
*/
|
|
function Menu(wrapperEl, selectEl, wrapperCallbackFn) {
|
|
// add scroll lock
|
|
util.enableScrollLock();
|
|
|
|
// instance variables
|
|
this.itemArray = [];
|
|
this.origPos = null;
|
|
this.currentPos = null;
|
|
this.selectEl = selectEl;
|
|
this.wrapperEl = wrapperEl;
|
|
this.menuEl = this._createMenuEl(wrapperEl, selectEl);
|
|
|
|
var cb = util.callback;
|
|
|
|
this.onClickCB = cb(this, 'onClick');
|
|
this.destroyCB = cb(this, 'destroy');
|
|
this.wrapperCallbackFn = wrapperCallbackFn;
|
|
|
|
// add to DOM
|
|
wrapperEl.appendChild(this.menuEl);
|
|
jqLite.scrollTop(this.menuEl, this.menuEl._scrollTop);
|
|
|
|
// attach event handlers
|
|
var destroyCB = this.destroyCB;
|
|
jqLite.on(this.menuEl, 'click', this.onClickCB);
|
|
jqLite.on(win, 'resize', destroyCB);
|
|
|
|
// attach event handler after current event loop exits
|
|
setTimeout(function() {jqLite.on(doc, 'click', destroyCB);}, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Create menu element
|
|
* @param {Element} selectEl - The select element
|
|
*/
|
|
Menu.prototype._createMenuEl = function(wrapperEl, selectEl) {
|
|
var menuEl = doc.createElement('div'),
|
|
childEls = selectEl.children,
|
|
itemArray = this.itemArray,
|
|
itemPos = 0,
|
|
origPos = -1,
|
|
selectedPos = 0,
|
|
selectedRow = 0,
|
|
docFrag = document.createDocumentFragment(), // for speed
|
|
loopEl,
|
|
rowEl,
|
|
optionEls,
|
|
inGroup,
|
|
i,
|
|
iMax,
|
|
j,
|
|
jMax;
|
|
|
|
menuEl.className = menuClass;
|
|
|
|
for (i=0, iMax=childEls.length; i < iMax; i++) {
|
|
loopEl = childEls[i];
|
|
|
|
if (loopEl.tagName === 'OPTGROUP') {
|
|
// add row item to menu
|
|
rowEl = doc.createElement('div');
|
|
rowEl.textContent = loopEl.label;
|
|
rowEl.className = 'mui-optgroup__label';
|
|
docFrag.appendChild(rowEl);
|
|
|
|
inGroup = true;
|
|
optionEls = loopEl.children;
|
|
} else {
|
|
inGroup = false;
|
|
optionEls = [loopEl];
|
|
}
|
|
|
|
// loop through option elements
|
|
for (j=0, jMax=optionEls.length; j < jMax; j++) {
|
|
loopEl = optionEls[j];
|
|
|
|
// add row item to menu
|
|
rowEl = doc.createElement('div');
|
|
rowEl.textContent = loopEl.textContent;
|
|
|
|
// handle optgroup options
|
|
if (inGroup) jqLite.addClass(rowEl, 'mui-optgroup__option');
|
|
|
|
if (loopEl.disabled) {
|
|
// do not attach muiIndex to disable <option> elements to make them
|
|
// unselectable.
|
|
jqLite.addClass(rowEl, disabledClass);
|
|
} else {
|
|
rowEl._muiIndex = loopEl.index;
|
|
rowEl._muiPos = itemPos;
|
|
|
|
// handle selected options
|
|
if (loopEl.selected) {
|
|
selectedRow = menuEl.children.length;
|
|
origPos = itemPos;
|
|
selectedPos = itemPos;
|
|
}
|
|
|
|
// add to item array
|
|
itemArray.push(rowEl);
|
|
itemPos += 1;
|
|
}
|
|
|
|
docFrag.appendChild(rowEl);
|
|
}
|
|
}
|
|
|
|
// add rows to menu
|
|
menuEl.appendChild(docFrag);
|
|
|
|
// save indices
|
|
this.origPos = origPos;
|
|
this.currentPos = selectedPos;
|
|
|
|
// paint selectedPos
|
|
if (itemArray.length) jqLite.addClass(itemArray[selectedPos], selectedClass);
|
|
|
|
// set position
|
|
var props = formlib.getMenuPositionalCSS(
|
|
wrapperEl,
|
|
menuEl.children.length,
|
|
selectedRow
|
|
);
|
|
|
|
jqLite.css(menuEl, props);
|
|
menuEl._scrollTop = props.scrollTop;
|
|
|
|
return menuEl;
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle click events on menu element.
|
|
* @param {Event} ev - The DOM event
|
|
*/
|
|
Menu.prototype.onClick = function(ev) {
|
|
// don't allow events to bubble
|
|
ev.stopPropagation();
|
|
|
|
var item = ev.target,
|
|
index = item._muiIndex;
|
|
|
|
// ignore clicks on non-items
|
|
if (index === undefined) return;
|
|
|
|
// select option
|
|
this.currentPos = item._muiPos;
|
|
this.selectCurrent();
|
|
|
|
// destroy menu
|
|
this.destroy();
|
|
}
|
|
|
|
|
|
/**
|
|
* Increment selected item
|
|
*/
|
|
Menu.prototype.increment = function() {
|
|
if (this.currentPos === this.itemArray.length - 1) return;
|
|
|
|
// un-select old row
|
|
jqLite.removeClass(this.itemArray[this.currentPos], selectedClass);
|
|
|
|
// select new row
|
|
this.currentPos += 1;
|
|
jqLite.addClass(this.itemArray[this.currentPos], selectedClass);
|
|
}
|
|
|
|
|
|
/**
|
|
* Decrement selected item
|
|
*/
|
|
Menu.prototype.decrement = function() {
|
|
if (this.currentPos === 0) return;
|
|
|
|
// un-select old row
|
|
jqLite.removeClass(this.itemArray[this.currentPos], selectedClass);
|
|
|
|
// select new row
|
|
this.currentPos -= 1;
|
|
jqLite.addClass(this.itemArray[this.currentPos], selectedClass);
|
|
}
|
|
|
|
|
|
/**
|
|
* Select current item
|
|
*/
|
|
Menu.prototype.selectCurrent = function() {
|
|
if (this.currentPos !== this.origPos) {
|
|
this.selectEl.selectedIndex = this.itemArray[this.currentPos]._muiIndex;
|
|
|
|
// trigger change event
|
|
util.dispatchEvent(this.selectEl, 'change', false, false);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Select item at position
|
|
*/
|
|
Menu.prototype.selectPos = function(pos) {
|
|
// un-select old row
|
|
jqLite.removeClass(this.itemArray[this.currentPos], selectedClass);
|
|
|
|
// select new row
|
|
this.currentPos = pos;
|
|
jqLite.addClass(this.itemArray[pos], selectedClass);
|
|
}
|
|
|
|
|
|
/**
|
|
* Destroy menu and detach event handlers
|
|
*/
|
|
Menu.prototype.destroy = function() {
|
|
// remove scroll lock
|
|
util.disableScrollLock(true);
|
|
|
|
// remove event handlers
|
|
jqLite.off(this.menuEl, 'click', this.clickCallbackFn);
|
|
jqLite.off(doc, 'click', this.destroyCB);
|
|
jqLite.off(win, 'resize', this.destroyCB);
|
|
|
|
// remove element and execute wrapper callback
|
|
var parentNode = this.menuEl.parentNode;
|
|
if (parentNode) {
|
|
parentNode.removeChild(this.menuEl);
|
|
this.wrapperCallbackFn();
|
|
}
|
|
}
|
|
|
|
|
|
/** Define module API */
|
|
module.exports = {
|
|
/** Initialize module listeners */
|
|
initListeners: function() {
|
|
// markup elements available when method is called
|
|
var elList = doc.querySelectorAll(cssSelector),
|
|
i = elList.length;
|
|
while (i--) initialize(elList[i]);
|
|
|
|
// listen for mui-node-inserted events
|
|
animationHelpers.onAnimationStart('mui-select-inserted', function(ev) {
|
|
initialize(ev.target);
|
|
});
|
|
}
|
|
};
|
|
|
|
},{"./lib/animationHelpers":3,"./lib/forms":4,"./lib/jqLite":5,"./lib/util":6}],12:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS tabs module
|
|
* @module tabs
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
var jqLite = require('./lib/jqLite'),
|
|
util = require('./lib/util'),
|
|
animationHelpers = require('./lib/animationHelpers'),
|
|
attrKey = 'data-mui-toggle',
|
|
attrSelector = '[' + attrKey + '="tab"]',
|
|
controlsAttrKey = 'data-mui-controls',
|
|
activeClass = 'mui--is-active',
|
|
showstartKey = 'mui.tabs.showstart',
|
|
showendKey = 'mui.tabs.showend',
|
|
hidestartKey = 'mui.tabs.hidestart',
|
|
hideendKey = 'mui.tabs.hideend';
|
|
|
|
|
|
/**
|
|
* Initialize the toggle element
|
|
* @param {Element} toggleEl - The toggle element.
|
|
*/
|
|
function initialize(toggleEl) {
|
|
// check flag
|
|
if (toggleEl._muiTabs === true) return;
|
|
else toggleEl._muiTabs = true;
|
|
|
|
// attach click handler
|
|
jqLite.on(toggleEl, 'click', clickHandler);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle clicks on the toggle element.
|
|
* @param {Event} ev - The DOM event.
|
|
*/
|
|
function clickHandler(ev) {
|
|
// only left clicks
|
|
if (ev.button !== 0) return;
|
|
|
|
var toggleEl = this;
|
|
|
|
// exit if toggle element is disabled
|
|
if (toggleEl.getAttribute('disabled') !== null) return;
|
|
|
|
activateTab(toggleEl);
|
|
}
|
|
|
|
|
|
/**
|
|
* Activate the tab controlled by the toggle element.
|
|
* @param {Element} toggleEl - The toggle element.
|
|
*/
|
|
function activateTab(currToggleEl) {
|
|
var currTabEl = currToggleEl.parentNode,
|
|
currPaneId = currToggleEl.getAttribute(controlsAttrKey),
|
|
currPaneEl = document.getElementById(currPaneId),
|
|
prevTabEl,
|
|
prevPaneEl,
|
|
prevPaneId,
|
|
prevToggleEl,
|
|
currData,
|
|
prevData,
|
|
ev1,
|
|
ev2,
|
|
cssSelector;
|
|
|
|
// exit if already active
|
|
if (jqLite.hasClass(currTabEl, activeClass)) return;
|
|
|
|
// raise error if pane doesn't exist
|
|
if (!currPaneEl) util.raiseError('Tab pane "' + currPaneId + '" not found');
|
|
|
|
// get previous pane
|
|
prevPaneEl = getActiveSibling(currPaneEl);
|
|
prevPaneId = prevPaneEl.id;
|
|
|
|
// get previous toggle and tab elements
|
|
cssSelector = '[' + controlsAttrKey + '="' + prevPaneId + '"]';
|
|
prevToggleEl = document.querySelectorAll(cssSelector)[0];
|
|
prevTabEl = prevToggleEl.parentNode;
|
|
|
|
// define event data
|
|
currData = {paneId: currPaneId, relatedPaneId: prevPaneId};
|
|
prevData = {paneId: prevPaneId, relatedPaneId: currPaneId};
|
|
|
|
// dispatch 'hidestart', 'showstart' events
|
|
ev1 = util.dispatchEvent(prevToggleEl, hidestartKey, true, true, prevData);
|
|
ev2 = util.dispatchEvent(currToggleEl, showstartKey, true, true, currData);
|
|
|
|
// let events bubble
|
|
setTimeout(function() {
|
|
// exit if either event was canceled
|
|
if (ev1.defaultPrevented || ev2.defaultPrevented) return;
|
|
|
|
// de-activate previous
|
|
if (prevTabEl) jqLite.removeClass(prevTabEl, activeClass);
|
|
if (prevPaneEl) jqLite.removeClass(prevPaneEl, activeClass);
|
|
|
|
// activate current
|
|
jqLite.addClass(currTabEl, activeClass);
|
|
jqLite.addClass(currPaneEl, activeClass);
|
|
|
|
// dispatch 'hideend', 'showend' events
|
|
util.dispatchEvent(prevToggleEl, hideendKey, true, false, prevData);
|
|
util.dispatchEvent(currToggleEl, showendKey, true, false, currData);
|
|
}, 0);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get previous active sibling.
|
|
* @param {Element} el - The anchor element.
|
|
*/
|
|
function getActiveSibling(el) {
|
|
var elList = el.parentNode.children,
|
|
q = elList.length,
|
|
activeEl = null,
|
|
tmpEl;
|
|
|
|
while (q-- && !activeEl) {
|
|
tmpEl = elList[q];
|
|
if (tmpEl !== el && jqLite.hasClass(tmpEl, activeClass)) activeEl = tmpEl
|
|
}
|
|
|
|
return activeEl;
|
|
}
|
|
|
|
|
|
/** Define module API */
|
|
module.exports = {
|
|
/** Initialize module listeners */
|
|
initListeners: function() {
|
|
// markup elements available when method is called
|
|
var elList = document.querySelectorAll(attrSelector),
|
|
i = elList.length;
|
|
while (i--) {initialize(elList[i]);}
|
|
|
|
animationHelpers.onAnimationStart('mui-tab-inserted', function(ev) {
|
|
initialize(ev.target);
|
|
});
|
|
},
|
|
|
|
/** External API */
|
|
api: {
|
|
activate: function(paneId) {
|
|
var cssSelector = '[' + controlsAttrKey + '=' + paneId + ']',
|
|
toggleEl = document.querySelectorAll(cssSelector);
|
|
|
|
if (!toggleEl.length) {
|
|
util.raiseError('Tab control for pane "' + paneId + '" not found');
|
|
}
|
|
|
|
activateTab(toggleEl[0]);
|
|
}
|
|
}
|
|
};
|
|
|
|
},{"./lib/animationHelpers":3,"./lib/jqLite":5,"./lib/util":6}],13:[function(require,module,exports){
|
|
/**
|
|
* MUI CSS/JS form-control module
|
|
* @module forms/form-control
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
|
var jqLite = require('./lib/jqLite'),
|
|
util = require('./lib/util'),
|
|
animationHelpers = require('./lib/animationHelpers'),
|
|
cssSelector = '.mui-textfield > input, .mui-textfield > textarea',
|
|
floatingLabelClass = 'mui-textfield--float-label';
|
|
|
|
|
|
var touchedClass = 'mui--is-touched', // hasn't lost focus yet
|
|
untouchedClass = 'mui--is-untouched',
|
|
pristineClass = 'mui--is-pristine', // user hasn't interacted yet
|
|
dirtyClass = 'mui--is-dirty',
|
|
emptyClass = 'mui--is-empty', // control is empty
|
|
notEmptyClass = 'mui--is-not-empty';
|
|
|
|
|
|
/**
|
|
* Initialize input element.
|
|
* @param {Element} inputEl - The input element.
|
|
*/
|
|
function initialize(inputEl) {
|
|
// check flag
|
|
if (inputEl._muiTextfield === true) return;
|
|
else inputEl._muiTextfield = true;
|
|
|
|
// add initial control state classes
|
|
if (inputEl.value.length) jqLite.addClass(inputEl, notEmptyClass);
|
|
else jqLite.addClass(inputEl, emptyClass);
|
|
|
|
jqLite.addClass(inputEl, untouchedClass + ' ' + pristineClass);
|
|
|
|
// replace `untouched` with `touched` when control loses focus
|
|
jqLite.on(inputEl, 'blur', function blurHandler () {
|
|
// ignore if event is a window blur
|
|
if (document.activeElement === inputEl) return;
|
|
|
|
// replace class and remove event handler
|
|
jqLite.removeClass(inputEl, untouchedClass);
|
|
jqLite.addClass(inputEl, touchedClass);
|
|
jqLite.off(inputEl, 'blur', blurHandler);
|
|
});
|
|
|
|
// replace `pristine` with `dirty` when user interacts with control
|
|
jqLite.one(inputEl, 'input change', function() {
|
|
jqLite.removeClass(inputEl, pristineClass);
|
|
jqLite.addClass(inputEl, dirtyClass);
|
|
});
|
|
|
|
// add change handler
|
|
jqLite.on(inputEl, 'input change', inputHandler);
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle input events.
|
|
*/
|
|
function inputHandler() {
|
|
var inputEl = this;
|
|
|
|
if (inputEl.value.length) {
|
|
jqLite.removeClass(inputEl, emptyClass);
|
|
jqLite.addClass(inputEl, notEmptyClass);
|
|
} else {
|
|
jqLite.removeClass(inputEl, notEmptyClass);
|
|
jqLite.addClass(inputEl, emptyClass)
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Handle autofill events.
|
|
*/
|
|
function autofillHandler(inputEl) {
|
|
// exit if not under css/js control
|
|
if (inputEl._muiTextfield !== true) return;
|
|
|
|
// execute inputHandler
|
|
inputHandler.call(inputEl);
|
|
}
|
|
|
|
|
|
/** Define module API */
|
|
module.exports = {
|
|
/** Initialize input elements */
|
|
initialize: initialize,
|
|
|
|
/** Initialize module listeners */
|
|
initListeners: function() {
|
|
var doc = document;
|
|
|
|
// markup elements available when method is called
|
|
var elList = doc.querySelectorAll(cssSelector),
|
|
i = elList.length;
|
|
while (i--) initialize(elList[i]);
|
|
|
|
// listen for new elements
|
|
animationHelpers.onAnimationStart('mui-textfield-inserted', function(ev) {
|
|
initialize(ev.target);
|
|
});
|
|
|
|
// add transition css for floating labels
|
|
setTimeout(function() {
|
|
var css = '.mui-textfield.mui-textfield--float-label > label {' + [
|
|
'-webkit-transition',
|
|
'-moz-transition',
|
|
'-o-transition',
|
|
'transition',
|
|
''
|
|
].join(':all .15s ease-out;') + '}';
|
|
|
|
util.loadStyle(css);
|
|
}, 150);
|
|
|
|
// listen for autofill events
|
|
animationHelpers.onAnimationStart('mui-textfield-autofill', function(ev) {
|
|
autofillHandler(ev.target);
|
|
});
|
|
|
|
// pointer-events shim for floating labels
|
|
if (util.supportsPointerEvents() === false) {
|
|
jqLite.on(doc, 'click', function(ev) {
|
|
var targetEl = ev.target;
|
|
|
|
if (targetEl.tagName === 'LABEL' &&
|
|
jqLite.hasClass(targetEl.parentNode, floatingLabelClass)) {
|
|
var inputEl = targetEl.previousElementSibling;
|
|
if (inputEl) inputEl.focus();
|
|
}
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
},{"./lib/animationHelpers":3,"./lib/jqLite":5,"./lib/util":6}]},{},[1]) |