mui-sass 0.2.10 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fe53f0d8927ebe00141e68c019cfcb90f6b00c1e
4
- data.tar.gz: b1cfa7561628f6cfa2b2442d4f94c10b98e36da3
3
+ metadata.gz: 6f7ac0d12fc7dcc7819feba207f38ee95926e3cd
4
+ data.tar.gz: 3ad9ea39687180a74e2addccda24cdc0d6a6cbf8
5
5
  SHA512:
6
- metadata.gz: 61d7d22b20922ef845ae24b5b97d3eff74a8b9ebbb6d9689e5689e5f23742160eb86391f541347858a54672e836f9d205dc2ba8d1beb1ebd6abc99a83151cb33
7
- data.tar.gz: 0250e716b81e1fc5b31fd42da8f260a83da5d62ea853664b2138bae046cf4f5995f3d2a4265e1ec70d5a576444c49ecf86d847101c32c1cf9268a7213be87d15
6
+ metadata.gz: bc253ee837f1cd0b8b71b6f72b9f5dcb34f3584a38fec60b4d8188c83ec58a8ffda6ae183918f8c891781b79fb316742597d8865d66704d70bff99023ce27971
7
+ data.tar.gz: 816ddf101f9ff4b1ba08edf17c07ae23913443a4c73a210f7abcaf74626410a7e51951574f413e7c2062be64a1e1050b0c18573cbb9c4d51795dac62c742303a
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.3.0 (2016-01-26)
2
+
3
+ - Update assets to match upstream version
4
+
5
+ Framework version: MUI v0.3.0
6
+
1
7
  ## 0.2.10 (2016-01-13)
2
8
 
3
9
  - Update assets to match upstream version
@@ -1,5 +1,5 @@
1
1
  module Mui
2
2
  module Sass
3
- VERSION = '0.2.10'
3
+ VERSION = '0.3.0'
4
4
  end
5
5
  end
@@ -1,4 +1,42 @@
1
- (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);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.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){
1
+ (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){
2
+ /**
3
+ * MUI CSS/JS main module
4
+ * @module main
5
+ */
6
+
7
+ (function(win) {
8
+ 'use strict';
9
+
10
+ // return if library has been loaded already
11
+ if (win._muiLoadedJS) return;
12
+ else win._muiLoadedJS = true;
13
+
14
+ // load dependencies
15
+ var jqLite = require('src/js/lib/jqLite'),
16
+ textfield = require('src/js/forms/textfield'),
17
+ select = require('src/js/forms/select'),
18
+ ripple = require('src/js/ripple'),
19
+ dropdowns = require('src/js/dropdowns'),
20
+ tabs = require('src/js/tabs'),
21
+ overlay = require('src/js/overlay');
22
+
23
+ // expose api
24
+ win.mui = {
25
+ overlay: overlay,
26
+ tabs: tabs.api
27
+ };
28
+
29
+ // init libraries
30
+ jqLite.ready(function() {
31
+ textfield.initListeners();
32
+ select.initListeners();
33
+ ripple.initListeners();
34
+ dropdowns.initListeners();
35
+ tabs.initListeners();
36
+ });
37
+ })(window);
38
+
39
+ },{"src/js/dropdowns":6,"src/js/forms/select":7,"src/js/forms/textfield":8,"src/js/lib/jqLite":9,"src/js/overlay":10,"src/js/ripple":11,"src/js/tabs":12}],2:[function(require,module,exports){
2
40
  /**
3
41
  * MUI config module
4
42
  * @module config
@@ -10,1286 +48,1296 @@ module.exports = {
10
48
  debug: true
11
49
  };
12
50
 
13
- },{}],2:[function(require,module,exports){
51
+ },{}],3:[function(require,module,exports){
14
52
  /**
15
- * MUI CSS/JS dropdown module
16
- * @module dropdowns
53
+ * MUI CSS/JS form helpers module
54
+ * @module lib/forms.py
17
55
  */
18
56
 
19
57
  'use strict';
20
58
 
21
-
22
- var jqLite = require('./lib/jqLite.js'),
23
- util = require('./lib/util.js'),
24
- attrKey = 'data-mui-toggle',
25
- attrSelector = '[data-mui-toggle="dropdown"]',
26
- openClass = 'mui--is-open',
27
- menuClass = 'mui-dropdown__menu';
59
+ var wrapperPadding = 15, // from CSS
60
+ inputHeight = 32, // from CSS
61
+ optionHeight = 42, // from CSS
62
+ menuPadding = 8; // from CSS
28
63
 
29
64
 
30
65
  /**
31
- * Initialize toggle element.
32
- * @param {Element} toggleEl - The toggle element.
66
+ * Menu position/size/scroll helper
67
+ * @returns {Object} Object with keys 'height', 'top', 'scrollTop'
33
68
  */
34
- function initialize(toggleEl) {
35
- // check flag
36
- if (toggleEl._muiDropdown === true) return;
37
- else toggleEl._muiDropdown = true;
69
+ function getMenuPositionalCSSFn(wrapperEl, numOptions, currentIndex) {
70
+ var viewHeight = document.documentElement.clientHeight;
38
71
 
39
- // attach click handler
40
- jqLite.on(toggleEl, 'click', clickHandler);
41
- }
72
+ // determine 'height'
73
+ var h = numOptions * optionHeight + 2 * menuPadding,
74
+ height = Math.min(h, viewHeight);
42
75
 
76
+ // determine 'top'
77
+ var top, initTop, minTop, maxTop;
43
78
 
44
- /**
45
- * Handle click events on dropdown toggle element.
46
- * @param {Event} ev - The DOM event
47
- */
48
- function clickHandler(ev) {
49
- // only left clicks
50
- if (ev.button !== 0) return;
79
+ initTop = (menuPadding + optionHeight) - (wrapperPadding + inputHeight);
80
+ initTop -= currentIndex * optionHeight;
51
81
 
52
- var toggleEl = this;
53
-
54
- // exit if toggle button is disabled
55
- if (toggleEl.getAttribute('disabled') !== null) return;
82
+ minTop = -1 * wrapperEl.getBoundingClientRect().top;
83
+ maxTop = (viewHeight - height) + minTop;
56
84
 
57
- // prevent form submission
58
- ev.preventDefault();
59
- ev.stopPropagation();
85
+ top = Math.min(Math.max(initTop, minTop), maxTop);
60
86
 
61
- // toggle dropdown
62
- toggleDropdown(toggleEl);
87
+ // determine 'scrollTop'
88
+ var scrollTop = 0,
89
+ scrollIdeal,
90
+ scrollMax;
91
+
92
+ if (h > viewHeight) {
93
+ scrollIdeal = (menuPadding + (currentIndex + 1) * optionHeight) -
94
+ (-1 * top + wrapperPadding + inputHeight);
95
+ scrollMax = numOptions * optionHeight + 2 * menuPadding - height;
96
+ scrollTop = Math.min(scrollIdeal, scrollMax);
97
+ }
98
+
99
+ return {
100
+ 'height': height + 'px',
101
+ 'top': top + 'px',
102
+ 'scrollTop': scrollTop
103
+ };
63
104
  }
64
105
 
65
106
 
107
+ /** Define module API */
108
+ module.exports = {
109
+ getMenuPositionalCSS: getMenuPositionalCSSFn
110
+ };
111
+
112
+ },{}],4:[function(require,module,exports){
66
113
  /**
67
- * Toggle the dropdown.
68
- * @param {Element} toggleEl - The dropdown toggle element.
114
+ * MUI CSS/JS jqLite module
115
+ * @module lib/jqLite
69
116
  */
70
- function toggleDropdown(toggleEl) {
71
- var wrapperEl = toggleEl.parentNode,
72
- menuEl = toggleEl.nextElementSibling,
73
- doc = wrapperEl.ownerDocument;
74
-
75
- // exit if no menu element
76
- if (!menuEl || !jqLite.hasClass(menuEl, menuClass)) {
77
- return util.raiseError('Dropdown menu element not found');
78
- }
79
117
 
80
- // method to close dropdown
81
- function closeDropdownFn() {
82
- jqLite.removeClass(menuEl, openClass);
83
-
84
- // remove event handlers
85
- jqLite.off(doc, 'click', closeDropdownFn);
86
- }
118
+ 'use strict';
87
119
 
88
- // method to open dropdown
89
- function openDropdownFn() {
90
- // position menu element below toggle button
91
- var wrapperRect = wrapperEl.getBoundingClientRect(),
92
- toggleRect = toggleEl.getBoundingClientRect();
93
120
 
94
- var top = toggleRect.top - wrapperRect.top + toggleRect.height;
95
- jqLite.css(menuEl, 'top', top + 'px');
121
+ /**
122
+ * Add a class to an element.
123
+ * @param {Element} element - The DOM element.
124
+ * @param {string} cssClasses - Space separated list of class names.
125
+ */
126
+ function jqLiteAddClass(element, cssClasses) {
127
+ if (!cssClasses || !element.setAttribute) return;
96
128
 
97
- // add open class to wrapper
98
- jqLite.addClass(menuEl, openClass);
129
+ var existingClasses = _getExistingClasses(element),
130
+ splitClasses = cssClasses.split(' '),
131
+ cssClass;
99
132
 
100
- // close dropdown when user clicks outside of menu
101
- jqLite.on(doc, 'click', closeDropdownFn);
133
+ for (var i=0; i < splitClasses.length; i++) {
134
+ cssClass = splitClasses[i].trim();
135
+ if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
136
+ existingClasses += cssClass + ' ';
137
+ }
102
138
  }
103
-
104
- // toggle dropdown
105
- if (jqLite.hasClass(menuEl, openClass)) closeDropdownFn();
106
- else openDropdownFn();
107
- }
108
-
109
139
 
110
- /** Define module API */
111
- module.exports = {
112
- /** Initialize module listeners */
113
- initListeners: function() {
114
- var doc = document;
115
-
116
- // markup elements available when method is called
117
- var elList = doc.querySelectorAll(attrSelector);
118
- for (var i=elList.length - 1; i >= 0; i--) initialize(elList[i]);
140
+ element.setAttribute('class', existingClasses.trim());
141
+ }
119
142
 
120
- // listen for new elements
121
- util.onNodeInserted(function(el) {
122
- if (el.getAttribute(attrKey) === 'dropdown') initialize(el);
123
- });
124
- }
125
- };
126
143
 
127
- },{"./lib/jqLite.js":5,"./lib/util.js":6}],3:[function(require,module,exports){
128
144
  /**
129
- * MUI CSS/JS select module
130
- * @module forms/select
145
+ * Get or set CSS properties.
146
+ * @param {Element} element - The DOM element.
147
+ * @param {string} [name] - The property name.
148
+ * @param {string} [value] - The property value.
131
149
  */
150
+ function jqLiteCss(element, name, value) {
151
+ // Return full style object
152
+ if (name === undefined) {
153
+ return getComputedStyle(element);
154
+ }
132
155
 
133
- 'use strict';
156
+ var nameType = jqLiteType(name);
134
157
 
158
+ // Set multiple values
159
+ if (nameType === 'object') {
160
+ for (var key in name) element.style[_camelCase(key)] = name[key];
161
+ return;
162
+ }
135
163
 
136
- var jqLite = require('../lib/jqLite.js'),
137
- util = require('../lib/util.js'),
138
- wrapperClass = 'mui-select',
139
- cssSelector = '.mui-select > select',
140
- menuClass = 'mui-select__menu',
141
- wrapperPadding = 15, // from CSS
142
- inputHeight = 32, // from CSS
143
- optionHeight = 42, // from CSS
144
- menuPadding = 8, // from CSS
145
- doc = document,
146
- win = window;
164
+ // Set a single value
165
+ if (nameType === 'string' && value !== undefined) {
166
+ element.style[_camelCase(name)] = value;
167
+ }
147
168
 
169
+ var styleObj = getComputedStyle(element),
170
+ isArray = (jqLiteType(name) === 'array');
148
171
 
149
- /**
150
- * Initialize select element.
151
- * @param {Element} selectEl - The select element.
152
- */
153
- function initialize(selectEl) {
154
- // check flag
155
- if (selectEl._muiSelect === true) return;
156
- else selectEl._muiSelect = true;
172
+ // Read single value
173
+ if (!isArray) return _getCurrCssProp(element, name, styleObj);
157
174
 
158
- // use default behavior on touch devices
159
- if ('ontouchstart' in doc.documentElement) return;
175
+ // Read multiple values
176
+ var outObj = {},
177
+ key;
160
178
 
161
- // initialize element
162
- new Select(selectEl);
179
+ for (var i=0; i < name.length; i++) {
180
+ key = name[i];
181
+ outObj[key] = _getCurrCssProp(element, key, styleObj);
182
+ }
183
+
184
+ return outObj;
163
185
  }
164
186
 
165
187
 
166
188
  /**
167
- * Creates a new Select object
168
- * @class
189
+ * Check if element has class.
190
+ * @param {Element} element - The DOM element.
191
+ * @param {string} cls - The class name string.
169
192
  */
170
- function Select(selectEl) {
171
- // instance variables
172
- this.selectEl = selectEl;
173
- this.wrapperEl = selectEl.parentNode;
174
- this.useDefault = false; // currently unused but let's keep just in case
175
-
176
- // attach event handlers
177
- jqLite.on(selectEl, 'mousedown', util.callback(this, 'mousedownHandler'));
178
- jqLite.on(selectEl, 'focus', util.callback(this, 'focusHandler'));
179
- jqLite.on(selectEl, 'click', util.callback(this, 'clickHandler'));
180
-
181
- // make wrapper focusable and fix firefox bug
182
- this.wrapperEl.tabIndex = -1;
183
- var callbackFn = util.callback(this, 'wrapperFocusHandler');
184
- jqLite.on(this.wrapperEl, 'focus', callbackFn);
193
+ function jqLiteHasClass(element, cls) {
194
+ if (!cls || !element.getAttribute) return false;
195
+ return (_getExistingClasses(element).indexOf(' ' + cls + ' ') > -1);
185
196
  }
186
197
 
187
198
 
188
199
  /**
189
- * Disable default dropdown on mousedown.
190
- * @param {Event} ev - The DOM event
200
+ * Return the type of a variable.
201
+ * @param {} somevar - The JavaScript variable.
191
202
  */
192
- Select.prototype.mousedownHandler = function(ev) {
193
- if (ev.button !== 0 || this.useDefault === true) return;
194
- ev.preventDefault();
203
+ function jqLiteType(somevar) {
204
+ // handle undefined
205
+ if (somevar === undefined) return 'undefined';
206
+
207
+ // handle others (of type [object <Type>])
208
+ var typeStr = Object.prototype.toString.call(somevar);
209
+ if (typeStr.indexOf('[object ') === 0) {
210
+ return typeStr.slice(8, -1).toLowerCase();
211
+ } else {
212
+ throw new Error("MUI: Could not understand type: " + typeStr);
213
+ }
195
214
  }
196
215
 
197
216
 
198
217
  /**
199
- * Handle focus event on select element.
200
- * @param {Event} ev - The DOM event
218
+ * Attach an event handler to a DOM element
219
+ * @param {Element} element - The DOM element.
220
+ * @param {string} type - The event type name.
221
+ * @param {Function} callback - The callback function.
222
+ * @param {Boolean} useCapture - Use capture flag.
201
223
  */
202
- Select.prototype.focusHandler = function(ev) {
203
- // check flag
204
- if (this.useDefault === true) return;
224
+ function jqLiteOn(element, type, callback, useCapture) {
225
+ useCapture = (useCapture === undefined) ? false : useCapture;
205
226
 
206
- var selectEl = this.selectEl,
207
- wrapperEl = this.wrapperEl,
208
- origIndex = selectEl.tabIndex,
209
- keydownFn = util.callback(this, 'keydownHandler');
210
-
211
- // attach keydown handler
212
- jqLite.on(doc, 'keydown', keydownFn);
227
+ // add to DOM
228
+ element.addEventListener(type, callback, useCapture);
213
229
 
214
- // disable tabfocus once
215
- selectEl.tabIndex = -1;
216
- jqLite.one(wrapperEl, 'blur', function() {
217
- selectEl.tabIndex = origIndex;
218
- jqLite.off(doc, 'keydown', keydownFn);
219
- });
220
-
221
- // defer focus to parent
222
- wrapperEl.focus();
230
+ // add to cache
231
+ var cache = element._muiEventCache = element._muiEventCache || {};
232
+ cache[type] = cache[type] || [];
233
+ cache[type].push([callback, useCapture]);
223
234
  }
224
235
 
225
236
 
226
237
  /**
227
- * Handle keydown events on doc
228
- **/
229
- Select.prototype.keydownHandler = function(ev) {
230
- // spacebar, down, up
231
- if (ev.keyCode === 32 || ev.keyCode === 38 || ev.keyCode === 40) {
232
- // prevent win scroll
233
- ev.preventDefault();
234
-
235
- if (this.selectEl.disabled !== true) this.renderMenu();
236
- }
237
- }
238
+ * Remove an event handler from a DOM element
239
+ * @param {Element} element - The DOM element.
240
+ * @param {string} type - The event type name.
241
+ * @param {Function} callback - The callback function.
242
+ * @param {Boolean} useCapture - Use capture flag.
243
+ */
244
+ function jqLiteOff(element, type, callback, useCapture) {
245
+ useCapture = (useCapture === undefined) ? false : useCapture;
238
246
 
247
+ // remove from cache
248
+ var cache = element._muiEventCache = element._muiEventCache || {},
249
+ argsList = cache[type] || [],
250
+ args,
251
+ i;
239
252
 
240
- /**
241
- * Handle focus event on wrapper element.
242
- */
243
- Select.prototype.wrapperFocusHandler = function() {
244
- // firefox bugfix
245
- if (this.selectEl.disabled) return this.wrapperEl.blur();
246
- }
253
+ i = argsList.length;
254
+ while (i--) {
255
+ args = argsList[i];
247
256
 
257
+ // remove all events if callback is undefined
258
+ if (callback === undefined ||
259
+ (args[0] === callback && args[1] === useCapture)) {
248
260
 
249
- /**
250
- * Handle click events on select element.
251
- * @param {Event} ev - The DOM event
252
- */
253
- Select.prototype.clickHandler = function(ev) {
254
- // only left clicks
255
- if (ev.button !== 0) return;
256
- this.renderMenu();
261
+ // remove from cache
262
+ argsList.splice(i, 1);
263
+
264
+ // remove from DOM
265
+ element.removeEventListener(type, args[0], args[1]);
266
+ }
267
+ }
257
268
  }
258
269
 
259
270
 
260
271
  /**
261
- * Render options dropdown.
272
+ * Attach an event hander which will only execute once
273
+ * @param {Element} element - The DOM element.
274
+ * @param {string} type - The event type name.
275
+ * @param {Function} callback - The callback function.
276
+ * @param {Boolean} useCapture - Use capture flag.
262
277
  */
263
- Select.prototype.renderMenu = function() {
264
- // check and reset flag
265
- if (this.useDefault === true) return this.useDefault = false;
278
+ function jqLiteOne(element, type, callback, useCapture) {
279
+ jqLiteOn(element, type, function onFn(ev) {
280
+ // execute callback
281
+ if (callback) callback.apply(this, arguments);
266
282
 
267
- new Menu(this.wrapperEl, this.selectEl);
283
+ // remove wrapper
284
+ jqLiteOff(element, type, onFn);
285
+ }, useCapture);
268
286
  }
269
287
 
270
288
 
271
289
  /**
272
- * Creates a new Menu
273
- * @class
290
+ * Get or set horizontal scroll position
291
+ * @param {Element} element - The DOM element
292
+ * @param {number} [value] - The scroll position
274
293
  */
275
- function Menu(wrapperEl, selectEl) {
276
- // add scroll lock
277
- util.enableScrollLock();
278
-
279
- // instance variables
280
- this.origIndex = null;
281
- this.currentIndex = null;
282
- this.selectEl = selectEl;
283
- this.menuEl = this._createMenuEl(wrapperEl, selectEl);
284
- this.clickCallbackFn = util.callback(this, 'clickHandler');
285
- this.keydownCallbackFn = util.callback(this, 'keydownHandler');
286
- this.destroyCallbackFn = util.callback(this, 'destroy');
287
-
288
- // add to DOM
289
- wrapperEl.appendChild(this.menuEl);
290
- jqLite.scrollTop(this.menuEl, this.menuEl._muiScrollTop);
294
+ function jqLiteScrollLeft(element, value) {
295
+ var win = window;
291
296
 
292
- // blur active element
293
- setTimeout(function() {
294
- // ie10 bugfix
295
- if (doc.activeElement.nodeName.toLowerCase() !== "body") {
296
- doc.activeElement.blur();
297
+ // get
298
+ if (value === undefined) {
299
+ if (element === win) {
300
+ var docEl = document.documentElement;
301
+ return (win.pageXOffset || docEl.scrollLeft) - (docEl.clientLeft || 0);
302
+ } else {
303
+ return element.scrollLeft;
297
304
  }
298
- }, 0);
299
-
300
- // attach event handlers
301
- jqLite.on(this.menuEl, 'click', this.clickCallbackFn);
302
- jqLite.on(doc, 'keydown', this.keydownCallbackFn);
303
- jqLite.on(win, 'resize', this.destroyCallbackFn);
305
+ }
304
306
 
305
- // attach event handler after current event loop exits
306
- var fn = this.destroyCallbackFn;
307
- setTimeout(function() {jqLite.on(doc, 'click', fn);}, 0);
307
+ // set
308
+ if (element === win) win.scrollTo(value, jqLiteScrollTop(win));
309
+ else element.scrollLeft = value;
308
310
  }
309
311
 
310
312
 
311
313
  /**
312
- * Create menu element
313
- * @param {Element} selectEl - The select element
314
+ * Get or set vertical scroll position
315
+ * @param {Element} element - The DOM element
316
+ * @param {number} value - The scroll position
314
317
  */
315
- Menu.prototype._createMenuEl = function(wrapperEl, selectEl) {
316
- var optionEl, itemEl, i, minTop, maxTop, top;
317
-
318
- var menuEl = doc.createElement('div'),
319
- optionList = selectEl.children,
320
- m = optionList.length,
321
- selectedPos = 0,
322
- initTop = (menuPadding + optionHeight) - (wrapperPadding + inputHeight);
323
-
324
- // create element
325
- menuEl.className = menuClass;
326
-
327
- // add options
328
- for (i=0; i < m; i++) {
329
- optionEl = optionList[i];
330
-
331
- itemEl = doc.createElement('div');
332
- itemEl.textContent = optionEl.textContent;
333
- itemEl._muiPos = i;
334
-
335
- if (optionEl.selected) selectedPos = i;
318
+ function jqLiteScrollTop(element, value) {
319
+ var win = window;
336
320
 
337
- menuEl.appendChild(itemEl);
321
+ // get
322
+ if (value === undefined) {
323
+ if (element === win) {
324
+ var docEl = document.documentElement;
325
+ return (win.pageYOffset || docEl.scrollTop) - (docEl.clientTop || 0);
326
+ } else {
327
+ return element.scrollTop;
328
+ }
338
329
  }
339
330
 
340
- // add selected attribute
341
- menuEl.children[selectedPos].setAttribute('selected', true);
342
-
343
- // save indices
344
- this.origIndex = selectedPos;
345
- this.currentIndex = selectedPos;
346
-
347
- var viewHeight = doc.documentElement.clientHeight;
348
-
349
- // set height (use viewport as maximum height)
350
- var height = m * optionHeight + 2 * menuPadding,
351
- isOverflow = height > viewHeight;
352
-
353
- height = Math.min(height, viewHeight);
354
- jqLite.css(menuEl, 'height', height + 'px');
331
+ // set
332
+ if (element === win) win.scrollTo(jqLiteScrollLeft(win), value);
333
+ else element.scrollTop = value;
334
+ }
355
335
 
356
- // ideal position
357
- initTop -= selectedPos * optionHeight;
358
336
 
359
- // minimum position
360
- minTop = -1 * wrapperEl.getBoundingClientRect().top;
337
+ /**
338
+ * Return object representing top/left offset and element height/width.
339
+ * @param {Element} element - The DOM element.
340
+ */
341
+ function jqLiteOffset(element) {
342
+ var win = window,
343
+ rect = element.getBoundingClientRect(),
344
+ scrollTop = jqLiteScrollTop(win),
345
+ scrollLeft = jqLiteScrollLeft(win);
361
346
 
362
- // maximium position
363
- maxTop = (viewHeight - height) + minTop;
347
+ return {
348
+ top: rect.top + scrollTop,
349
+ left: rect.left + scrollLeft,
350
+ height: rect.height,
351
+ width: rect.width
352
+ };
353
+ }
364
354
 
365
- // prevent overflow-y
366
- top = Math.max(initTop, minTop);
367
- top = Math.min(top, maxTop);
368
355
 
369
- jqLite.css(menuEl, 'top', top + 'px');
356
+ /**
357
+ * Attach a callback to the DOM ready event listener
358
+ * @param {Function} fn - The callback function.
359
+ */
360
+ function jqLiteReady(fn) {
361
+ var done = false,
362
+ top = true,
363
+ doc = document,
364
+ win = doc.defaultView,
365
+ root = doc.documentElement,
366
+ add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
367
+ rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
368
+ pre = doc.addEventListener ? '' : 'on';
370
369
 
371
- // set menu scroll position
372
- if (isOverflow) {
373
- var scrollIdeal, scrollMax;
370
+ var init = function(e) {
371
+ if (e.type == 'readystatechange' && doc.readyState != 'complete') {
372
+ return;
373
+ }
374
374
 
375
- scrollIdeal = (menuPadding + (selectedPos + 1) * optionHeight) -
376
- (-1 * top + wrapperPadding + inputHeight);
375
+ (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
376
+ if (!done && (done = true)) fn.call(win, e.type || e);
377
+ };
377
378
 
378
- scrollMax = m * optionHeight + 2 * menuPadding - height;
379
+ var poll = function() {
380
+ try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
381
+ init('poll');
382
+ };
379
383
 
380
- menuEl._muiHasOverflow = true;
381
- menuEl._muiScrollTop = Math.min(scrollIdeal, scrollMax);
384
+ if (doc.readyState == 'complete') {
385
+ fn.call(win, 'lazy');
382
386
  } else {
383
- menuEl._muiHasOverflow = false;
384
- menuEl._muiScrollTop = 0;
387
+ if (doc.createEventObject && root.doScroll) {
388
+ try { top = !win.frameElement; } catch(e) { }
389
+ if (top) poll();
390
+ }
391
+ doc[add](pre + 'DOMContentLoaded', init, false);
392
+ doc[add](pre + 'readystatechange', init, false);
393
+ win[add](pre + 'load', init, false);
385
394
  }
386
-
387
- return menuEl;
388
395
  }
389
396
 
390
397
 
391
398
  /**
392
- * Handle keydown events on doc element.
393
- * @param {Event} ev - The DOM event
399
+ * Remove classes from a DOM element
400
+ * @param {Element} element - The DOM element.
401
+ * @param {string} cssClasses - Space separated list of class names.
394
402
  */
395
- Menu.prototype.keydownHandler = function(ev) {
396
- var keyCode = ev.keyCode;
403
+ function jqLiteRemoveClass(element, cssClasses) {
404
+ if (!cssClasses || !element.setAttribute) return;
397
405
 
398
- // tab
399
- if (keyCode === 9) return this.destroy();
406
+ var existingClasses = _getExistingClasses(element),
407
+ splitClasses = cssClasses.split(' '),
408
+ cssClass;
400
409
 
401
- // escape | up | down | enter
402
- if (keyCode === 27 || keyCode === 40 || keyCode === 38 || keyCode === 13) {
403
- ev.preventDefault();
410
+ for (var i=0; i < splitClasses.length; i++) {
411
+ cssClass = splitClasses[i].trim();
412
+ while (existingClasses.indexOf(' ' + cssClass + ' ') >= 0) {
413
+ existingClasses = existingClasses.replace(' ' + cssClass + ' ', ' ');
414
+ }
404
415
  }
405
416
 
406
- if (keyCode === 27) {
407
- this.destroy();
408
- } else if (keyCode === 40) {
409
- this.increment();
410
- } else if (keyCode === 38) {
411
- this.decrement();
412
- } else if (keyCode === 13) {
413
- this.selectCurrent();
414
- this.destroy();
415
- }
417
+ element.setAttribute('class', existingClasses.trim());
416
418
  }
417
419
 
418
420
 
419
- /**
420
- * Handle click events on menu element.
421
- * @param {Event} ev - The DOM event
422
- */
423
- Menu.prototype.clickHandler = function(ev) {
424
- // don't allow events to bubble
425
- ev.stopPropagation();
421
+ // ------------------------------
422
+ // Utilities
423
+ // ------------------------------
424
+ var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g,
425
+ MOZ_HACK_REGEXP = /^moz([A-Z])/,
426
+ ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g,
427
+ BOOLEAN_ATTRS;
426
428
 
427
- var pos = ev.target._muiPos;
428
429
 
429
- // ignore clicks on non-items
430
- if (pos === undefined) return;
430
+ BOOLEAN_ATTRS = {
431
+ multiple: true,
432
+ selected: true,
433
+ checked: true,
434
+ disabled: true,
435
+ readonly: true,
436
+ required: true,
437
+ open: true
438
+ }
431
439
 
432
- // select option
433
- this.currentIndex = pos;
434
- this.selectCurrent();
435
440
 
436
- // destroy menu
437
- this.destroy();
441
+ function _getExistingClasses(element) {
442
+ var classes = (element.getAttribute('class') || '').replace(/[\n\t]/g, '');
443
+ return ' ' + classes + ' ';
438
444
  }
439
445
 
440
446
 
441
- /**
442
- * Increment selected item
443
- */
444
- Menu.prototype.increment = function() {
445
- if (this.currentIndex === this.menuEl.children.length - 1) return;
446
-
447
- this.menuEl.children[this.currentIndex].removeAttribute('selected');
448
- this.currentIndex += 1;
449
- this.menuEl.children[this.currentIndex].setAttribute('selected', true);
447
+ function _camelCase(name) {
448
+ return name.
449
+ replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
450
+ return offset ? letter.toUpperCase() : letter;
451
+ }).
452
+ replace(MOZ_HACK_REGEXP, 'Moz$1');
450
453
  }
451
454
 
452
455
 
453
- /**
454
- * Decrement selected item
455
- */
456
- Menu.prototype.decrement = function() {
457
- if (this.currentIndex === 0) return;
458
-
459
- this.menuEl.children[this.currentIndex].removeAttribute('selected');
460
- this.currentIndex -= 1;
461
- this.menuEl.children[this.currentIndex].setAttribute('selected', true);
456
+ function _escapeRegExp(string) {
457
+ return string.replace(ESCAPE_REGEXP, "\\$1");
462
458
  }
463
459
 
464
460
 
465
- /**
466
- * Select current item
467
- */
468
- Menu.prototype.selectCurrent = function() {
469
- if (this.currentIndex !== this.origIndex) {
470
- this.selectEl.children[this.origIndex].selected = false;
471
- this.selectEl.children[this.currentIndex].selected = true;
461
+ function _getCurrCssProp(elem, name, computed) {
462
+ var ret;
472
463
 
473
- // trigger change event
474
- util.dispatchEvent(this.selectEl, 'change');
475
- }
464
+ // try computed style
465
+ ret = computed.getPropertyValue(name);
466
+
467
+ // try style attribute (if element is not attached to document)
468
+ if (ret === '' && !elem.ownerDocument) ret = elem.style[_camelCase(name)];
469
+
470
+ return ret;
476
471
  }
477
472
 
478
473
 
479
474
  /**
480
- * Destroy menu and detach event handlers
475
+ * Module API
481
476
  */
482
- Menu.prototype.destroy = function() {
483
- // remove element and focus element
484
- this.menuEl.parentNode.removeChild(this.menuEl);
485
- this.selectEl.focus();
477
+ module.exports = {
478
+ /** Add classes */
479
+ addClass: jqLiteAddClass,
486
480
 
487
- // remove scroll lock
488
- util.disableScrollLock();
481
+ /** Get or set CSS properties */
482
+ css: jqLiteCss,
489
483
 
490
- // remove event handlers
491
- jqLite.off(this.menuEl, 'click', this.clickCallbackFn);
492
- jqLite.off(doc, 'keydown', this.keydownCallbackFn);
493
- jqLite.off(doc, 'click', this.destroyCallbackFn);
494
- jqLite.off(win, 'resize', this.destroyCallbackFn);
495
- }
484
+ /** Check for class */
485
+ hasClass: jqLiteHasClass,
496
486
 
487
+ /** Remove event handlers */
488
+ off: jqLiteOff,
497
489
 
498
- /** Define module API */
499
- module.exports = {
500
- /** Initialize module listeners */
501
- initListeners: function() {
502
- // markup elements available when method is called
503
- var elList = doc.querySelectorAll(cssSelector);
504
- for (var i=elList.length - 1; i >= 0; i--) initialize(elList[i]);
490
+ /** Return offset values */
491
+ offset: jqLiteOffset,
505
492
 
506
- // listen for new elements
507
- util.onNodeInserted(function(el) {
508
- if (el.tagName === 'SELECT' &&
509
- jqLite.hasClass(el.parentNode, wrapperClass)) {
510
- initialize(el);
511
- }
512
- });
513
- }
514
- };
493
+ /** Add event handlers */
494
+ on: jqLiteOn,
515
495
 
516
- },{"../lib/jqLite.js":5,"../lib/util.js":6}],4:[function(require,module,exports){
517
- /**
518
- * MUI CSS/JS form-control module
519
- * @module forms/form-control
520
- */
496
+ /** Add an execute-once event handler */
497
+ one: jqLiteOne,
521
498
 
522
- 'use strict';
499
+ /** DOM ready event handler */
500
+ ready: jqLiteReady,
501
+
502
+ /** Remove classes */
503
+ removeClass: jqLiteRemoveClass,
523
504
 
505
+ /** Check JavaScript variable instance type */
506
+ type: jqLiteType,
524
507
 
525
- var jqLite = require('../lib/jqLite.js'),
526
- util = require('../lib/util.js'),
527
- cssSelector = '.mui-textfield > input, .mui-textfield > textarea',
528
- emptyClass = 'mui--is-empty',
529
- notEmptyClass = 'mui--is-not-empty',
530
- dirtyClass = 'mui--is-dirty',
531
- floatingLabelClass = 'mui-textfield--float-label';
508
+ /** Get or set horizontal scroll position */
509
+ scrollLeft: jqLiteScrollLeft,
532
510
 
511
+ /** Get or set vertical scroll position */
512
+ scrollTop: jqLiteScrollTop
513
+ };
533
514
 
515
+ },{}],5:[function(require,module,exports){
534
516
  /**
535
- * Initialize input element.
536
- * @param {Element} inputEl - The input element.
517
+ * MUI CSS/JS utilities module
518
+ * @module lib/util
537
519
  */
538
- function initialize(inputEl) {
539
- // check flag
540
- if (inputEl._muiTextfield === true) return;
541
- else inputEl._muiTextfield = true;
542
520
 
543
- if (inputEl.value.length) jqLite.addClass(inputEl, notEmptyClass);
544
- else jqLite.addClass(inputEl, emptyClass);
521
+ 'use strict';
545
522
 
546
- jqLite.on(inputEl, 'input', inputHandler);
547
523
 
548
- // add dirty class on focus
549
- jqLite.on(inputEl, 'focus', function(){jqLite.addClass(this, dirtyClass);});
550
- }
524
+ var config = require('../config'),
525
+ jqLite = require('./jqLite'),
526
+ nodeInsertedCallbacks = [],
527
+ scrollLock = 0,
528
+ scrollLockCls = 'mui-body--scroll-lock',
529
+ scrollLockPos,
530
+ _supportsPointerEvents;
551
531
 
552
532
 
553
533
  /**
554
- * Handle input events.
534
+ * Logging function
555
535
  */
556
- function inputHandler() {
557
- var inputEl = this;
536
+ function logFn() {
537
+ var win = window;
558
538
 
559
- if (inputEl.value.length) {
560
- jqLite.removeClass(inputEl, emptyClass);
561
- jqLite.addClass(inputEl, notEmptyClass);
562
- } else {
563
- jqLite.removeClass(inputEl, notEmptyClass);
564
- jqLite.addClass(inputEl, emptyClass)
539
+ if (config.debug && typeof win.console !== "undefined") {
540
+ try {
541
+ win.console.log.apply(win.console, arguments);
542
+ } catch (a) {
543
+ var e = Array.prototype.slice.call(arguments);
544
+ win.console.log(e.join("\n"));
545
+ }
565
546
  }
566
-
567
- jqLite.addClass(inputEl, dirtyClass);
568
547
  }
569
548
 
570
549
 
571
- /** Define module API */
572
- module.exports = {
573
- /** Initialize input elements */
574
- initialize: initialize,
550
+ /**
551
+ * Load CSS text in new stylesheet
552
+ * @param {string} cssText - The css text.
553
+ */
554
+ function loadStyleFn(cssText) {
555
+ var doc = document,
556
+ head;
557
+
558
+ // copied from jQuery
559
+ head = doc.head ||
560
+ doc.getElementsByTagName('head')[0] ||
561
+ doc.documentElement;
575
562
 
576
- /** Initialize module listeners */
577
- initListeners: function() {
578
- var doc = document;
563
+ var e = doc.createElement('style');
564
+ e.type = 'text/css';
579
565
 
580
- // markup elements available when method is called
581
- var elList = doc.querySelectorAll(cssSelector);
582
- for (var i=elList.length - 1; i >= 0; i--) initialize(elList[i]);
566
+ if (e.styleSheet) e.styleSheet.cssText = cssText;
567
+ else e.appendChild(doc.createTextNode(cssText));
568
+
569
+ // add to document
570
+ head.insertBefore(e, head.firstChild);
583
571
 
584
- // listen for new elements
585
- util.onNodeInserted(function(el) {
586
- if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') initialize(el);
587
- });
572
+ return e;
573
+ }
588
574
 
589
- // add transition css for floating labels
590
- setTimeout(function() {
591
- var css = '.mui-textfield.mui-textfield--float-label > label {' + [
592
- '-webkit-transition',
593
- '-moz-transition',
594
- '-o-transition',
595
- 'transition',
596
- ''
597
- ].join(':all .15s ease-out;') + '}';
598
-
599
- util.loadStyle(css);
600
- }, 150);
601
575
 
602
- // pointer-events shim for floating labels
603
- if (util.supportsPointerEvents() === false) {
604
- jqLite.on(document, 'click', function(ev) {
605
- var targetEl = ev.target;
576
+ /**
577
+ * Raise an error
578
+ * @param {string} msg - The error message.
579
+ */
580
+ function raiseErrorFn(msg) {
581
+ throw new Error("MUI: " + msg);
582
+ }
606
583
 
607
- if (targetEl.tagName === 'LABEL' &&
608
- jqLite.hasClass(targetEl.parentNode, floatingLabelClass)) {
609
- var inputEl = targetEl.previousElementSibling;
610
- if (inputEl) inputEl.focus();
611
- }
612
- });
613
- }
614
- }
615
- };
616
584
 
617
- },{"../lib/jqLite.js":5,"../lib/util.js":6}],5:[function(require,module,exports){
618
585
  /**
619
- * MUI CSS/JS jqLite module
620
- * @module lib/jqLite
586
+ * Register callbacks on muiNodeInserted event
587
+ * @param {function} callbackFn - The callback function.
621
588
  */
589
+ function onNodeInsertedFn(callbackFn) {
590
+ nodeInsertedCallbacks.push(callbackFn);
622
591
 
623
- 'use strict';
592
+ // initalize listeners
593
+ if (nodeInsertedCallbacks._initialized === undefined) {
594
+ var doc = document;
595
+
596
+ jqLite.on(doc, 'animationstart', animationHandlerFn);
597
+ jqLite.on(doc, 'mozAnimationStart', animationHandlerFn);
598
+ jqLite.on(doc, 'webkitAnimationStart', animationHandlerFn);
624
599
 
625
- // Global vars
626
- var gDoc = document,
627
- gDocEl = gDoc.documentElement,
628
- gWin = window;
600
+ nodeInsertedCallbacks._initialized = true;
601
+ }
602
+ }
629
603
 
630
604
 
631
605
  /**
632
- * Add a class to an element.
633
- * @param {Element} element - The DOM element.
634
- * @param {string} cssClasses - Space separated list of class names.
606
+ * Execute muiNodeInserted callbacks
607
+ * @param {Event} ev - The DOM event.
635
608
  */
636
- function jqLiteAddClass(element, cssClasses) {
637
- if (!cssClasses || !element.setAttribute) return;
609
+ function animationHandlerFn(ev) {
610
+ // check animation name
611
+ if (ev.animationName !== 'mui-node-inserted') return;
638
612
 
639
- var existingClasses = _getExistingClasses(element),
640
- splitClasses = cssClasses.split(' '),
641
- cssClass;
613
+ var el = ev.target;
642
614
 
643
- for (var i=0; i < splitClasses.length; i++) {
644
- cssClass = splitClasses[i].trim();
645
- if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
646
- existingClasses += cssClass + ' ';
647
- }
615
+ // iterate through callbacks
616
+ for (var i=nodeInsertedCallbacks.length - 1; i >= 0; i--) {
617
+ nodeInsertedCallbacks[i](el);
648
618
  }
649
-
650
- element.setAttribute('class', existingClasses.trim());
651
619
  }
652
620
 
653
621
 
654
622
  /**
655
- * Get or set CSS properties.
656
- * @param {Element} element - The DOM element.
657
- * @param {string} [name] - The property name.
658
- * @param {string} [value] - The property value.
623
+ * Convert Classname object, with class as key and true/false as value, to an
624
+ * class string.
625
+ * @param {Object} classes The classes
626
+ * @return {String} class string
659
627
  */
660
- function jqLiteCss(element, name, value) {
661
- // Return full style object
662
- if (name === undefined) {
663
- return getComputedStyle(element);
664
- }
665
-
666
- var nameType = jqLiteType(name);
667
-
668
- // Set multiple values
669
- if (nameType === 'object') {
670
- for (var key in name) element.style[_camelCase(key)] = name[key];
671
- return;
672
- }
673
-
674
- // Set a single value
675
- if (nameType === 'string' && value !== undefined) {
676
- element.style[_camelCase(name)] = value;
677
- }
678
-
679
- var styleObj = getComputedStyle(element),
680
- isArray = (jqLiteType(name) === 'array');
681
-
682
- // Read single value
683
- if (!isArray) return _getCurrCssProp(element, name, styleObj);
684
-
685
- // Read multiple values
686
- var outObj = {},
687
- key;
688
-
689
- for (var i=0; i < name.length; i++) {
690
- key = name[i];
691
- outObj[key] = _getCurrCssProp(element, key, styleObj);
628
+ function classNamesFn(classes) {
629
+ var cs = '';
630
+ for (var i in classes) {
631
+ cs += (classes[i]) ? i + ' ' : '';
692
632
  }
693
-
694
- return outObj;
633
+ return cs.trim();
695
634
  }
696
635
 
697
636
 
698
637
  /**
699
- * Check if element has class.
700
- * @param {Element} element - The DOM element.
701
- * @param {string} cls - The class name string.
638
+ * Check if client supports pointer events.
702
639
  */
703
- function jqLiteHasClass(element, cls) {
704
- if (!cls || !element.getAttribute) return false;
705
- return (_getExistingClasses(element).indexOf(' ' + cls + ' ') > -1);
640
+ function supportsPointerEventsFn() {
641
+ // check cache
642
+ if (_supportsPointerEvents !== undefined) return _supportsPointerEvents;
643
+
644
+ var element = document.createElement('x');
645
+ element.style.cssText = 'pointer-events:auto';
646
+ _supportsPointerEvents = (element.style.pointerEvents === 'auto');
647
+ return _supportsPointerEvents;
706
648
  }
707
649
 
708
650
 
709
651
  /**
710
- * Return the type of a variable.
711
- * @param {} somevar - The JavaScript variable.
652
+ * Create callback closure.
653
+ * @param {Object} instance - The object instance.
654
+ * @param {String} funcName - The name of the callback function.
712
655
  */
713
- function jqLiteType(somevar) {
714
- // handle undefined
715
- if (somevar === undefined) return 'undefined';
716
-
717
- // handle others (of type [object <Type>])
718
- var typeStr = Object.prototype.toString.call(somevar);
719
- if (typeStr.indexOf('[object ') === 0) {
720
- return typeStr.slice(8, -1).toLowerCase();
721
- } else {
722
- throw new Error("MUI: Could not understand type: " + typeStr);
723
- }
656
+ function callbackFn(instance, funcName) {
657
+ return function() {instance[funcName].apply(instance, arguments);};
724
658
  }
725
659
 
726
660
 
727
661
  /**
728
- * Attach an event handler to a DOM element
662
+ * Dispatch event.
729
663
  * @param {Element} element - The DOM element.
730
- * @param {string} type - The event type name.
731
- * @param {Function} callback - The callback function.
732
- * @param {Boolean} useCapture - Use capture flag.
664
+ * @param {String} eventType - The event type.
665
+ * @param {Boolean} bubbles=true - If true, event bubbles.
666
+ * @param {Boolean} cancelable=true = If true, event is cancelable
667
+ * @param {Object} [data] - Data to add to event object
733
668
  */
734
- function jqLiteOn(element, type, callback, useCapture) {
735
- useCapture = (useCapture === undefined) ? false : useCapture;
669
+ function dispatchEventFn(element, eventType, bubbles, cancelable, data) {
670
+ var ev = document.createEvent('HTMLEvents'),
671
+ bubbles = (bubbles !== undefined) ? bubbles : true,
672
+ cancelable = (cancelable !== undefined) ? cancelable : true,
673
+ k;
674
+
675
+ ev.initEvent(eventType, bubbles, cancelable);
736
676
 
737
- // add to DOM
738
- element.addEventListener(type, callback, useCapture);
677
+ // add data to event object
678
+ if (data) for (k in data) ev[k] = data[k];
739
679
 
740
- // add to cache
741
- var cache = element._muiEventCache = element._muiEventCache || {};
742
- cache[type] = cache[type] || [];
743
- cache[type].push([callback, useCapture]);
680
+ // dispatch
681
+ if (element) element.dispatchEvent(ev);
682
+
683
+ return ev;
744
684
  }
745
685
 
746
686
 
747
687
  /**
748
- * Remove an event handler from a DOM element
749
- * @param {Element} element - The DOM element.
750
- * @param {string} type - The event type name.
751
- * @param {Function} callback - The callback function.
752
- * @param {Boolean} useCapture - Use capture flag.
688
+ * Turn on window scroll lock.
753
689
  */
754
- function jqLiteOff(element, type, callback, useCapture) {
755
- useCapture = (useCapture === undefined) ? false : useCapture;
756
-
757
- // remove from cache
758
- var cache = element._muiEventCache = element._muiEventCache || {},
759
- argsList = cache[type] || [],
760
- args,
761
- i;
762
-
763
- i = argsList.length;
764
- while (i--) {
765
- args = argsList[i];
690
+ function enableScrollLockFn() {
691
+ // increment counter
692
+ scrollLock += 1
766
693
 
767
- // remove all events if callback is undefined
768
- if (callback === undefined ||
769
- (args[0] === callback && args[1] === useCapture)) {
694
+ // add lock
695
+ if (scrollLock === 1) {
696
+ var win = window,
697
+ doc = document;
770
698
 
771
- // remove from cache
772
- argsList.splice(i, 1);
773
-
774
- // remove from DOM
775
- element.removeEventListener(type, args[0], args[1]);
776
- }
699
+ scrollLockPos = {left: jqLite.scrollLeft(win), top: jqLite.scrollTop(win)};
700
+ jqLite.addClass(doc.body, scrollLockCls);
701
+ win.scrollTo(scrollLockPos.left, scrollLockPos.top);
777
702
  }
778
703
  }
779
704
 
780
705
 
781
706
  /**
782
- * Attach an event hander which will only execute once
783
- * @param {Element} element - The DOM element.
784
- * @param {string} type - The event type name.
785
- * @param {Function} callback - The callback function.
786
- * @param {Boolean} useCapture - Use capture flag.
707
+ * Turn off window scroll lock.
787
708
  */
788
- function jqLiteOne(element, type, callback, useCapture) {
789
- jqLiteOn(element, type, function onFn(ev) {
790
- // execute callback
791
- if (callback) callback.apply(this, arguments);
709
+ function disableScrollLockFn() {
710
+ // ignore
711
+ if (scrollLock === 0) return;
792
712
 
793
- // remove wrapper
794
- jqLiteOff(element, type, onFn);
795
- }, useCapture);
796
- }
713
+ // decrement counter
714
+ scrollLock -= 1
797
715
 
716
+ // remove lock
717
+ if (scrollLock === 0) {
718
+ var win = window,
719
+ doc = document;
798
720
 
799
- /**
800
- * Get or set horizontal scroll position
801
- * @param {Element} element - The DOM element
802
- * @param {number} [value] - The scroll position
803
- */
804
- function jqLiteScrollLeft(element, value) {
805
- // get
806
- if (value === undefined) {
807
- if (element === gWin) {
808
- var t = (gWin.pageXOffset || gDocEl.scrollLeft)
809
- return t - (gDocEl.clientLeft || 0);
810
- } else {
811
- return element.scrollLeft;
812
- }
721
+ jqLite.removeClass(doc.body, scrollLockCls);
722
+ win.scrollTo(scrollLockPos.left, scrollLockPos.top);
813
723
  }
814
-
815
- // set
816
- if (element === gWin) gWin.scrollTo(value, jqLiteScrollTop(gWin));
817
- else element.scrollLeft = value;
818
724
  }
819
725
 
820
726
 
821
727
  /**
822
- * Get or set vertical scroll position
823
- * @param {Element} element - The DOM element
824
- * @param {number} value - The scroll position
728
+ * Define the module API
825
729
  */
826
- function jqLiteScrollTop(element, value) {
827
- // get
828
- if (value === undefined) {
829
- if (element === gWin) {
830
- return (gWin.pageYOffset || gDocEl.scrollTop) - (gDocEl.clientTop || 0);
831
- } else {
832
- return element.scrollTop;
833
- }
834
- }
730
+ module.exports = {
731
+ /** Create callback closures */
732
+ callback: callbackFn,
733
+
734
+ /** Classnames object to string */
735
+ classNames: classNamesFn,
835
736
 
836
- // set
837
- if (element === gWin) gWin.scrollTo(jqLiteScrollLeft(gWin), value);
838
- else element.scrollTop = value;
839
- }
737
+ /** Disable scroll lock */
738
+ disableScrollLock: disableScrollLockFn,
739
+
740
+ /** Dispatch event */
741
+ dispatchEvent: dispatchEventFn,
742
+
743
+ /** Enable scroll lock */
744
+ enableScrollLock: enableScrollLockFn,
840
745
 
746
+ /** Log messages to the console when debug is turned on */
747
+ log: logFn,
841
748
 
842
- /**
843
- * Return object representing top/left offset and element height/width.
844
- * @param {Element} element - The DOM element.
845
- */
846
- function jqLiteOffset(element) {
847
- var rect = element.getBoundingClientRect(),
848
- scrollTop = jqLiteScrollTop(gWin),
849
- scrollLeft = jqLiteScrollLeft(gWin);
749
+ /** Load CSS text as new stylesheet */
750
+ loadStyle: loadStyleFn,
850
751
 
851
- return {
852
- top: rect.top + scrollTop,
853
- left: rect.left + scrollLeft,
854
- height: rect.height,
855
- width: rect.width
856
- };
857
- }
752
+ /** Register muiNodeInserted handler */
753
+ onNodeInserted: onNodeInsertedFn,
754
+
755
+ /** Raise MUI error */
756
+ raiseError: raiseErrorFn,
858
757
 
758
+ /** Support Pointer Events check */
759
+ supportsPointerEvents: supportsPointerEventsFn
760
+ };
859
761
 
762
+ },{"../config":2,"./jqLite":4}],6:[function(require,module,exports){
860
763
  /**
861
- * Attach a callback to the DOM ready event listener
862
- * @param {Function} fn - The callback function.
863
- */
864
- function jqLiteReady(fn) {
865
- var done = false,
866
- top = true,
867
- doc = document,
868
- win = doc.defaultView,
869
- root = doc.documentElement,
870
- add = doc.addEventListener ? 'addEventListener' : 'attachEvent',
871
- rem = doc.addEventListener ? 'removeEventListener' : 'detachEvent',
872
- pre = doc.addEventListener ? '' : 'on';
764
+ * MUI CSS/JS dropdown module
765
+ * @module dropdowns
766
+ */
873
767
 
874
- var init = function(e) {
875
- if (e.type == 'readystatechange' && doc.readyState != 'complete') {
876
- return;
877
- }
768
+ 'use strict';
878
769
 
879
- (e.type == 'load' ? win : doc)[rem](pre + e.type, init, false);
880
- if (!done && (done = true)) fn.call(win, e.type || e);
881
- };
882
770
 
883
- var poll = function() {
884
- try { root.doScroll('left'); } catch(e) { setTimeout(poll, 50); return; }
885
- init('poll');
886
- };
771
+ var jqLite = require('./lib/jqLite'),
772
+ util = require('./lib/util'),
773
+ attrKey = 'data-mui-toggle',
774
+ attrSelector = '[data-mui-toggle="dropdown"]',
775
+ openClass = 'mui--is-open',
776
+ menuClass = 'mui-dropdown__menu';
887
777
 
888
- if (doc.readyState == 'complete') {
889
- fn.call(win, 'lazy');
890
- } else {
891
- if (doc.createEventObject && root.doScroll) {
892
- try { top = !win.frameElement; } catch(e) { }
893
- if (top) poll();
894
- }
895
- doc[add](pre + 'DOMContentLoaded', init, false);
896
- doc[add](pre + 'readystatechange', init, false);
897
- win[add](pre + 'load', init, false);
898
- }
778
+
779
+ /**
780
+ * Initialize toggle element.
781
+ * @param {Element} toggleEl - The toggle element.
782
+ */
783
+ function initialize(toggleEl) {
784
+ // check flag
785
+ if (toggleEl._muiDropdown === true) return;
786
+ else toggleEl._muiDropdown = true;
787
+
788
+ // attach click handler
789
+ jqLite.on(toggleEl, 'click', clickHandler);
899
790
  }
900
791
 
901
792
 
902
793
  /**
903
- * Remove classes from a DOM element
904
- * @param {Element} element - The DOM element.
905
- * @param {string} cssClasses - Space separated list of class names.
794
+ * Handle click events on dropdown toggle element.
795
+ * @param {Event} ev - The DOM event
906
796
  */
907
- function jqLiteRemoveClass(element, cssClasses) {
908
- if (!cssClasses || !element.setAttribute) return;
797
+ function clickHandler(ev) {
798
+ // only left clicks
799
+ if (ev.button !== 0) return;
909
800
 
910
- var existingClasses = _getExistingClasses(element),
911
- splitClasses = cssClasses.split(' '),
912
- cssClass;
801
+ var toggleEl = this;
913
802
 
914
- for (var i=0; i < splitClasses.length; i++) {
915
- cssClass = splitClasses[i].trim();
916
- while (existingClasses.indexOf(' ' + cssClass + ' ') >= 0) {
917
- existingClasses = existingClasses.replace(' ' + cssClass + ' ', ' ');
918
- }
919
- }
803
+ // exit if toggle button is disabled
804
+ if (toggleEl.getAttribute('disabled') !== null) return;
920
805
 
921
- element.setAttribute('class', existingClasses.trim());
806
+ // prevent form submission
807
+ ev.preventDefault();
808
+ ev.stopPropagation();
809
+
810
+ // toggle dropdown
811
+ toggleDropdown(toggleEl);
922
812
  }
923
813
 
924
814
 
925
- // ------------------------------
926
- // Utilities
927
- // ------------------------------
928
- var SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g,
929
- MOZ_HACK_REGEXP = /^moz([A-Z])/,
930
- ESCAPE_REGEXP = /([.*+?^=!:${}()|\[\]\/\\])/g,
931
- BOOLEAN_ATTRS;
815
+ /**
816
+ * Toggle the dropdown.
817
+ * @param {Element} toggleEl - The dropdown toggle element.
818
+ */
819
+ function toggleDropdown(toggleEl) {
820
+ var wrapperEl = toggleEl.parentNode,
821
+ menuEl = toggleEl.nextElementSibling,
822
+ doc = wrapperEl.ownerDocument;
932
823
 
824
+ // exit if no menu element
825
+ if (!menuEl || !jqLite.hasClass(menuEl, menuClass)) {
826
+ return util.raiseError('Dropdown menu element not found');
827
+ }
933
828
 
934
- BOOLEAN_ATTRS = {
935
- multiple: true,
936
- selected: true,
937
- checked: true,
938
- disabled: true,
939
- readonly: true,
940
- required: true,
941
- open: true
942
- }
829
+ // method to close dropdown
830
+ function closeDropdownFn() {
831
+ jqLite.removeClass(menuEl, openClass);
832
+
833
+ // remove event handlers
834
+ jqLite.off(doc, 'click', closeDropdownFn);
835
+ }
943
836
 
837
+ // method to open dropdown
838
+ function openDropdownFn() {
839
+ // position menu element below toggle button
840
+ var wrapperRect = wrapperEl.getBoundingClientRect(),
841
+ toggleRect = toggleEl.getBoundingClientRect();
944
842
 
945
- function _getExistingClasses(element) {
946
- var classes = (element.getAttribute('class') || '').replace(/[\n\t]/g, '');
947
- return ' ' + classes + ' ';
948
- }
843
+ var top = toggleRect.top - wrapperRect.top + toggleRect.height;
844
+ jqLite.css(menuEl, 'top', top + 'px');
949
845
 
846
+ // add open class to wrapper
847
+ jqLite.addClass(menuEl, openClass);
950
848
 
951
- function _camelCase(name) {
952
- return name.
953
- replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
954
- return offset ? letter.toUpperCase() : letter;
955
- }).
956
- replace(MOZ_HACK_REGEXP, 'Moz$1');
849
+ // close dropdown when user clicks outside of menu
850
+ jqLite.on(doc, 'click', closeDropdownFn);
851
+ }
852
+
853
+ // toggle dropdown
854
+ if (jqLite.hasClass(menuEl, openClass)) closeDropdownFn();
855
+ else openDropdownFn();
957
856
  }
958
857
 
858
+
859
+ /** Define module API */
860
+ module.exports = {
861
+ /** Initialize module listeners */
862
+ initListeners: function() {
863
+ var doc = document;
959
864
 
960
- function _escapeRegExp(string) {
961
- return string.replace(ESCAPE_REGEXP, "\\$1");
962
- }
865
+ // markup elements available when method is called
866
+ var elList = doc.querySelectorAll(attrSelector);
867
+ for (var i=elList.length - 1; i >= 0; i--) initialize(elList[i]);
963
868
 
869
+ // listen for new elements
870
+ util.onNodeInserted(function(el) {
871
+ if (el.getAttribute(attrKey) === 'dropdown') initialize(el);
872
+ });
873
+ }
874
+ };
964
875
 
965
- function _getCurrCssProp(elem, name, computed) {
966
- var ret;
876
+ },{"./lib/jqLite":4,"./lib/util":5}],7:[function(require,module,exports){
877
+ /**
878
+ * MUI CSS/JS select module
879
+ * @module forms/select
880
+ */
967
881
 
968
- // try computed style
969
- ret = computed.getPropertyValue(name);
882
+ 'use strict';
970
883
 
971
- // try style attribute (if element is not attached to document)
972
- if (ret === '' && !elem.ownerDocument) ret = elem.style[_camelCase(name)];
973
884
 
974
- return ret;
975
- }
885
+ var jqLite = require('../lib/jqLite'),
886
+ util = require('../lib/util'),
887
+ formlib = require('../lib/forms'),
888
+ wrapperClass = 'mui-select',
889
+ cssSelector = '.mui-select > select',
890
+ menuClass = 'mui-select__menu',
891
+ selectedClass = 'mui--is-selected',
892
+ doc = document,
893
+ win = window;
976
894
 
977
895
 
978
896
  /**
979
- * Module API
897
+ * Initialize select element.
898
+ * @param {Element} selectEl - The select element.
980
899
  */
981
- module.exports = {
982
- /** Add classes */
983
- addClass: jqLiteAddClass,
900
+ function initialize(selectEl) {
901
+ // check flag
902
+ if (selectEl._muiSelect === true) return;
903
+ else selectEl._muiSelect = true;
984
904
 
985
- /** Get or set CSS properties */
986
- css: jqLiteCss,
905
+ // use default behavior on touch devices
906
+ if ('ontouchstart' in doc.documentElement) return;
987
907
 
988
- /** Check for class */
989
- hasClass: jqLiteHasClass,
908
+ // initialize element
909
+ new Select(selectEl);
910
+ }
990
911
 
991
- /** Remove event handlers */
992
- off: jqLiteOff,
993
912
 
994
- /** Return offset values */
995
- offset: jqLiteOffset,
913
+ /**
914
+ * Creates a new Select object
915
+ * @class
916
+ */
917
+ function Select(selectEl) {
918
+ // instance variables
919
+ this.selectEl = selectEl;
920
+ this.wrapperEl = selectEl.parentNode;
921
+ this.useDefault = false; // currently unused but let's keep just in case
996
922
 
997
- /** Add event handlers */
998
- on: jqLiteOn,
923
+ // attach event handlers
924
+ jqLite.on(selectEl, 'mousedown', util.callback(this, 'mousedownHandler'));
925
+ jqLite.on(selectEl, 'focus', util.callback(this, 'focusHandler'));
926
+ jqLite.on(selectEl, 'click', util.callback(this, 'clickHandler'));
927
+
928
+ // make wrapper focusable and fix firefox bug
929
+ this.wrapperEl.tabIndex = -1;
930
+ var callbackFn = util.callback(this, 'wrapperFocusHandler');
931
+ jqLite.on(this.wrapperEl, 'focus', callbackFn);
932
+ }
999
933
 
1000
- /** Add an execute-once event handler */
1001
- one: jqLiteOne,
1002
934
 
1003
- /** DOM ready event handler */
1004
- ready: jqLiteReady,
935
+ /**
936
+ * Disable default dropdown on mousedown.
937
+ * @param {Event} ev - The DOM event
938
+ */
939
+ Select.prototype.mousedownHandler = function(ev) {
940
+ if (ev.button !== 0 || this.useDefault === true) return;
941
+ ev.preventDefault();
942
+ }
1005
943
 
1006
- /** Remove classes */
1007
- removeClass: jqLiteRemoveClass,
1008
944
 
1009
- /** Check JavaScript variable instance type */
1010
- type: jqLiteType,
945
+ /**
946
+ * Handle focus event on select element.
947
+ * @param {Event} ev - The DOM event
948
+ */
949
+ Select.prototype.focusHandler = function(ev) {
950
+ // check flag
951
+ if (this.useDefault === true) return;
1011
952
 
1012
- /** Get or set horizontal scroll position */
1013
- scrollLeft: jqLiteScrollLeft,
953
+ var selectEl = this.selectEl,
954
+ wrapperEl = this.wrapperEl,
955
+ origIndex = selectEl.tabIndex,
956
+ keydownFn = util.callback(this, 'keydownHandler');
1014
957
 
1015
- /** Get or set vertical scroll position */
1016
- scrollTop: jqLiteScrollTop
1017
- };
958
+ // attach keydown handler
959
+ jqLite.on(doc, 'keydown', keydownFn);
1018
960
 
1019
- },{}],6:[function(require,module,exports){
1020
- /**
1021
- * MUI CSS/JS utilities module
1022
- * @module lib/util
1023
- */
961
+ // disable tabfocus once
962
+ selectEl.tabIndex = -1;
963
+ jqLite.one(wrapperEl, 'blur', function() {
964
+ selectEl.tabIndex = origIndex;
965
+ jqLite.off(doc, 'keydown', keydownFn);
966
+ });
967
+
968
+ // defer focus to parent
969
+ wrapperEl.focus();
970
+ }
1024
971
 
1025
- 'use strict';
1026
972
 
973
+ /**
974
+ * Handle keydown events on doc
975
+ **/
976
+ Select.prototype.keydownHandler = function(ev) {
977
+ // spacebar, down, up
978
+ if (ev.keyCode === 32 || ev.keyCode === 38 || ev.keyCode === 40) {
979
+ // prevent win scroll
980
+ ev.preventDefault();
981
+
982
+ if (this.selectEl.disabled !== true) this.renderMenu();
983
+ }
984
+ }
1027
985
 
1028
- var config = require('../config.js'),
1029
- jqLite = require('./jqLite.js'),
1030
- win = window,
1031
- doc = document,
1032
- nodeInsertedCallbacks = [],
1033
- scrollLock = 0,
1034
- scrollLockCls = 'mui-body--scroll-lock',
1035
- scrollLockPos,
1036
- head,
1037
- _supportsPointerEvents;
1038
986
 
1039
- head = doc.head || doc.getElementsByTagName('head')[0] || doc.documentElement;
987
+ /**
988
+ * Handle focus event on wrapper element.
989
+ */
990
+ Select.prototype.wrapperFocusHandler = function() {
991
+ // firefox bugfix
992
+ if (this.selectEl.disabled) return this.wrapperEl.blur();
993
+ }
1040
994
 
1041
995
 
1042
996
  /**
1043
- * Logging function
997
+ * Handle click events on select element.
998
+ * @param {Event} ev - The DOM event
1044
999
  */
1045
- function logFn() {
1046
- if (config.debug && typeof win.console !== "undefined") {
1047
- try {
1048
- win.console.log.apply(win.console, arguments);
1049
- } catch (a) {
1050
- var e = Array.prototype.slice.call(arguments);
1051
- win.console.log(e.join("\n"));
1052
- }
1053
- }
1000
+ Select.prototype.clickHandler = function(ev) {
1001
+ // only left clicks
1002
+ if (ev.button !== 0) return;
1003
+ this.renderMenu();
1054
1004
  }
1055
1005
 
1056
1006
 
1057
1007
  /**
1058
- * Load CSS text in new stylesheet
1059
- * @param {string} cssText - The css text.
1008
+ * Render options dropdown.
1060
1009
  */
1061
- function loadStyleFn(cssText) {
1062
- var e = doc.createElement('style');
1063
- e.type = 'text/css';
1064
-
1065
- if (e.styleSheet) e.styleSheet.cssText = cssText;
1066
- else e.appendChild(doc.createTextNode(cssText));
1067
-
1068
- // add to document
1069
- head.insertBefore(e, head.firstChild);
1010
+ Select.prototype.renderMenu = function() {
1011
+ // check and reset flag
1012
+ if (this.useDefault === true) return this.useDefault = false;
1070
1013
 
1071
- return e;
1014
+ new Menu(this.wrapperEl, this.selectEl);
1072
1015
  }
1073
1016
 
1074
1017
 
1075
1018
  /**
1076
- * Raise an error
1077
- * @param {string} msg - The error message.
1019
+ * Creates a new Menu
1020
+ * @class
1078
1021
  */
1079
- function raiseErrorFn(msg) {
1080
- throw new Error("MUI: " + msg);
1022
+ function Menu(wrapperEl, selectEl) {
1023
+ // add scroll lock
1024
+ util.enableScrollLock();
1025
+
1026
+ // instance variables
1027
+ this.origIndex = null;
1028
+ this.currentIndex = null;
1029
+ this.selectEl = selectEl;
1030
+ this.menuEl = this._createMenuEl(wrapperEl, selectEl);
1031
+ this.clickCallbackFn = util.callback(this, 'clickHandler');
1032
+ this.keydownCallbackFn = util.callback(this, 'keydownHandler');
1033
+ this.destroyCallbackFn = util.callback(this, 'destroy');
1034
+
1035
+ // add to DOM
1036
+ wrapperEl.appendChild(this.menuEl);
1037
+ jqLite.scrollTop(this.menuEl, this.menuEl._muiScrollTop);
1038
+
1039
+ // blur active element
1040
+ setTimeout(function() {
1041
+ // ie10 bugfix
1042
+ if (doc.activeElement.nodeName.toLowerCase() !== "body") {
1043
+ doc.activeElement.blur();
1044
+ }
1045
+ }, 0);
1046
+
1047
+ // attach event handlers
1048
+ jqLite.on(this.menuEl, 'click', this.clickCallbackFn);
1049
+ jqLite.on(doc, 'keydown', this.keydownCallbackFn);
1050
+ jqLite.on(win, 'resize', this.destroyCallbackFn);
1051
+
1052
+ // attach event handler after current event loop exits
1053
+ var fn = this.destroyCallbackFn;
1054
+ setTimeout(function() {jqLite.on(doc, 'click', fn);}, 0);
1081
1055
  }
1082
1056
 
1083
1057
 
1084
1058
  /**
1085
- * Register callbacks on muiNodeInserted event
1086
- * @param {function} callbackFn - The callback function.
1059
+ * Create menu element
1060
+ * @param {Element} selectEl - The select element
1087
1061
  */
1088
- function onNodeInsertedFn(callbackFn) {
1089
- nodeInsertedCallbacks.push(callbackFn);
1062
+ Menu.prototype._createMenuEl = function(wrapperEl, selectEl) {
1063
+ var menuEl = doc.createElement('div'),
1064
+ optionEls = selectEl.children,
1065
+ numOptions = optionEls.length,
1066
+ selectedPos = 0,
1067
+ optionEl,
1068
+ itemEl,
1069
+ i;
1090
1070
 
1091
- // initalize listeners
1092
- if (nodeInsertedCallbacks._initialized === undefined) {
1093
- jqLite.on(doc, 'animationstart', animationHandlerFn);
1094
- jqLite.on(doc, 'mozAnimationStart', animationHandlerFn);
1095
- jqLite.on(doc, 'webkitAnimationStart', animationHandlerFn);
1071
+ menuEl.className = menuClass;
1096
1072
 
1097
- nodeInsertedCallbacks._initialized = true;
1073
+ // add options
1074
+ for (i=0; i < numOptions; i++) {
1075
+ optionEl = optionEls[i];
1076
+
1077
+ itemEl = doc.createElement('div');
1078
+ itemEl.textContent = optionEl.textContent;
1079
+ itemEl._muiPos = i;
1080
+
1081
+ if (optionEl.selected) {
1082
+ itemEl.setAttribute('class', selectedClass);
1083
+ selectedPos = i;
1084
+ }
1085
+
1086
+ menuEl.appendChild(itemEl);
1098
1087
  }
1088
+
1089
+ // save indices
1090
+ this.origIndex = selectedPos;
1091
+ this.currentIndex = selectedPos;
1092
+
1093
+ // set position
1094
+ var props = formlib.getMenuPositionalCSS(
1095
+ wrapperEl,
1096
+ numOptions,
1097
+ selectedPos
1098
+ );
1099
+
1100
+ jqLite.css(menuEl, props);
1101
+ menuEl._muiScrollTop = props.scrollTop;
1102
+
1103
+ return menuEl;
1099
1104
  }
1100
1105
 
1101
1106
 
1102
1107
  /**
1103
- * Execute muiNodeInserted callbacks
1104
- * @param {Event} ev - The DOM event.
1108
+ * Handle keydown events on doc element.
1109
+ * @param {Event} ev - The DOM event
1105
1110
  */
1106
- function animationHandlerFn(ev) {
1107
- // check animation name
1108
- if (ev.animationName !== 'mui-node-inserted') return;
1111
+ Menu.prototype.keydownHandler = function(ev) {
1112
+ var keyCode = ev.keyCode;
1109
1113
 
1110
- var el = ev.target;
1114
+ // tab
1115
+ if (keyCode === 9) return this.destroy();
1116
+
1117
+ // escape | up | down | enter
1118
+ if (keyCode === 27 || keyCode === 40 || keyCode === 38 || keyCode === 13) {
1119
+ ev.preventDefault();
1120
+ }
1111
1121
 
1112
- // iterate through callbacks
1113
- for (var i=nodeInsertedCallbacks.length - 1; i >= 0; i--) {
1114
- nodeInsertedCallbacks[i](el);
1122
+ if (keyCode === 27) {
1123
+ this.destroy();
1124
+ } else if (keyCode === 40) {
1125
+ this.increment();
1126
+ } else if (keyCode === 38) {
1127
+ this.decrement();
1128
+ } else if (keyCode === 13) {
1129
+ this.selectCurrent();
1130
+ this.destroy();
1115
1131
  }
1116
1132
  }
1117
1133
 
1118
1134
 
1119
1135
  /**
1120
- * Convert Classname object, with class as key and true/false as value, to an
1121
- * class string.
1122
- * @param {Object} classes The classes
1123
- * @return {String} class string
1136
+ * Handle click events on menu element.
1137
+ * @param {Event} ev - The DOM event
1124
1138
  */
1125
- function classNamesFn(classes) {
1126
- var cs = '';
1127
- for (var i in classes) {
1128
- cs += (classes[i]) ? i + ' ' : '';
1129
- }
1130
- return cs.trim();
1131
- }
1139
+ Menu.prototype.clickHandler = function(ev) {
1140
+ // don't allow events to bubble
1141
+ ev.stopPropagation();
1132
1142
 
1143
+ var pos = ev.target._muiPos;
1133
1144
 
1134
- /**
1135
- * Check if client supports pointer events.
1136
- */
1137
- function supportsPointerEventsFn() {
1138
- // check cache
1139
- if (_supportsPointerEvents !== undefined) return _supportsPointerEvents;
1140
-
1141
- var element = document.createElement('x');
1142
- element.style.cssText = 'pointer-events:auto';
1143
- _supportsPointerEvents = (element.style.pointerEvents === 'auto');
1144
- return _supportsPointerEvents;
1145
+ // ignore clicks on non-items
1146
+ if (pos === undefined) return;
1147
+
1148
+ // select option
1149
+ this.currentIndex = pos;
1150
+ this.selectCurrent();
1151
+
1152
+ // destroy menu
1153
+ this.destroy();
1145
1154
  }
1146
1155
 
1147
1156
 
1148
1157
  /**
1149
- * Create callback closure.
1150
- * @param {Object} instance - The object instance.
1151
- * @param {String} funcName - The name of the callback function.
1158
+ * Increment selected item
1152
1159
  */
1153
- function callbackFn(instance, funcName) {
1154
- return function() {instance[funcName].apply(instance, arguments);};
1160
+ Menu.prototype.increment = function() {
1161
+ if (this.currentIndex === this.menuEl.children.length - 1) return;
1162
+
1163
+ var optionEls = this.menuEl.children;
1164
+
1165
+ jqLite.removeClass(optionEls[this.currentIndex], selectedClass);
1166
+ this.currentIndex += 1;
1167
+ jqLite.addClass(optionEls[this.currentIndex], selectedClass);
1155
1168
  }
1156
1169
 
1157
1170
 
1158
1171
  /**
1159
- * Dispatch event.
1160
- * @param {Element} element - The DOM element.
1161
- * @param {String} eventType - The event type.
1162
- * @param {Boolean} bubbles=true - If true, event bubbles.
1163
- * @param {Boolean} cancelable=true = If true, event is cancelable
1164
- * @param {Object} [data] - Data to add to event object
1172
+ * Decrement selected item
1165
1173
  */
1166
- function dispatchEventFn(element, eventType, bubbles, cancelable, data) {
1167
- var ev = document.createEvent('HTMLEvents'),
1168
- bubbles = (bubbles !== undefined) ? bubbles : true,
1169
- cancelable = (cancelable !== undefined) ? cancelable : true,
1170
- k;
1171
-
1172
- ev.initEvent(eventType, bubbles, cancelable);
1173
-
1174
- // add data to event object
1175
- if (data) for (k in data) ev[k] = data[k];
1174
+ Menu.prototype.decrement = function() {
1175
+ if (this.currentIndex === 0) return;
1176
1176
 
1177
- // dispatch
1178
- if (element) element.dispatchEvent(ev);
1177
+ var optionEls = this.menuEl.children;
1179
1178
 
1180
- return ev;
1179
+ jqLite.removeClass(optionEls[this.currentIndex], selectedClass);
1180
+ this.currentIndex -= 1;
1181
+ jqLite.addClass(optionEls[this.currentIndex], selectedClass);
1181
1182
  }
1182
1183
 
1183
1184
 
1184
1185
  /**
1185
- * Turn on window scroll lock.
1186
+ * Select current item
1186
1187
  */
1187
- function enableScrollLockFn() {
1188
- // increment counter
1189
- scrollLock += 1
1188
+ Menu.prototype.selectCurrent = function() {
1189
+ if (this.currentIndex !== this.origIndex) {
1190
+ var optionEls = this.selectEl.children;
1191
+ optionEls[this.origIndex].selected = false;
1192
+ optionEls[this.currentIndex].selected = true;
1190
1193
 
1191
- // add lock
1192
- if (scrollLock === 1) {
1193
- scrollLockPos = {left: jqLite.scrollLeft(win), top: jqLite.scrollTop(win)};
1194
- jqLite.addClass(doc.body, scrollLockCls);
1195
- win.scrollTo(scrollLockPos.left, scrollLockPos.top);
1194
+ // trigger change event
1195
+ util.dispatchEvent(this.selectEl, 'change');
1196
1196
  }
1197
1197
  }
1198
1198
 
1199
1199
 
1200
1200
  /**
1201
- * Turn off window scroll lock.
1201
+ * Destroy menu and detach event handlers
1202
1202
  */
1203
- function disableScrollLockFn() {
1204
- // ignore
1205
- if (scrollLock === 0) return;
1203
+ Menu.prototype.destroy = function() {
1204
+ // remove element and focus element
1205
+ this.menuEl.parentNode.removeChild(this.menuEl);
1206
+ this.selectEl.focus();
1206
1207
 
1207
- // decrement counter
1208
- scrollLock -= 1
1208
+ // remove scroll lock
1209
+ util.disableScrollLock();
1209
1210
 
1210
- // remove lock
1211
- if (scrollLock === 0) {
1212
- jqLite.removeClass(doc.body, scrollLockCls);
1213
- win.scrollTo(scrollLockPos.left, scrollLockPos.top);
1214
- }
1211
+ // remove event handlers
1212
+ jqLite.off(this.menuEl, 'click', this.clickCallbackFn);
1213
+ jqLite.off(doc, 'keydown', this.keydownCallbackFn);
1214
+ jqLite.off(doc, 'click', this.destroyCallbackFn);
1215
+ jqLite.off(win, 'resize', this.destroyCallbackFn);
1215
1216
  }
1216
1217
 
1217
1218
 
1219
+ /** Define module API */
1220
+ module.exports = {
1221
+ /** Initialize module listeners */
1222
+ initListeners: function() {
1223
+ // markup elements available when method is called
1224
+ var elList = doc.querySelectorAll(cssSelector);
1225
+ for (var i=elList.length - 1; i >= 0; i--) initialize(elList[i]);
1226
+
1227
+ // listen for new elements
1228
+ util.onNodeInserted(function(el) {
1229
+ if (el.tagName === 'SELECT' &&
1230
+ jqLite.hasClass(el.parentNode, wrapperClass)) {
1231
+ initialize(el);
1232
+ }
1233
+ });
1234
+ }
1235
+ };
1236
+
1237
+ },{"../lib/forms":3,"../lib/jqLite":4,"../lib/util":5}],8:[function(require,module,exports){
1218
1238
  /**
1219
- * Define the module API
1239
+ * MUI CSS/JS form-control module
1240
+ * @module forms/form-control
1220
1241
  */
1221
- module.exports = {
1222
- /** Create callback closures */
1223
- callback: callbackFn,
1224
-
1225
- /** Classnames object to string */
1226
- classNames: classNamesFn,
1227
1242
 
1228
- /** Disable scroll lock */
1229
- disableScrollLock: disableScrollLockFn,
1243
+ 'use strict';
1230
1244
 
1231
- /** Dispatch event */
1232
- dispatchEvent: dispatchEventFn,
1233
-
1234
- /** Enable scroll lock */
1235
- enableScrollLock: enableScrollLockFn,
1236
1245
 
1237
- /** Log messages to the console when debug is turned on */
1238
- log: logFn,
1246
+ var jqLite = require('../lib/jqLite'),
1247
+ util = require('../lib/util'),
1248
+ cssSelector = '.mui-textfield > input, .mui-textfield > textarea',
1249
+ emptyClass = 'mui--is-empty',
1250
+ notEmptyClass = 'mui--is-not-empty',
1251
+ dirtyClass = 'mui--is-dirty',
1252
+ floatingLabelClass = 'mui-textfield--float-label';
1239
1253
 
1240
- /** Load CSS text as new stylesheet */
1241
- loadStyle: loadStyleFn,
1242
1254
 
1243
- /** Register muiNodeInserted handler */
1244
- onNodeInserted: onNodeInsertedFn,
1255
+ /**
1256
+ * Initialize input element.
1257
+ * @param {Element} inputEl - The input element.
1258
+ */
1259
+ function initialize(inputEl) {
1260
+ // check flag
1261
+ if (inputEl._muiTextfield === true) return;
1262
+ else inputEl._muiTextfield = true;
1245
1263
 
1246
- /** Raise MUI error */
1247
- raiseError: raiseErrorFn,
1264
+ if (inputEl.value.length) jqLite.addClass(inputEl, notEmptyClass);
1265
+ else jqLite.addClass(inputEl, emptyClass);
1266
+
1267
+ jqLite.on(inputEl, 'input', inputHandler);
1268
+
1269
+ // add dirty class on focus
1270
+ jqLite.on(inputEl, 'focus', function(){jqLite.addClass(this, dirtyClass);});
1271
+ }
1248
1272
 
1249
- /** Support Pointer Events check */
1250
- supportsPointerEvents: supportsPointerEventsFn
1251
- };
1252
1273
 
1253
- },{"../config.js":1,"./jqLite.js":5}],7:[function(require,module,exports){
1254
1274
  /**
1255
- * MUI CSS/JS main module
1256
- * @module main
1275
+ * Handle input events.
1257
1276
  */
1277
+ function inputHandler() {
1278
+ var inputEl = this;
1258
1279
 
1259
- (function(win) {
1260
- 'use strict';
1280
+ if (inputEl.value.length) {
1281
+ jqLite.removeClass(inputEl, emptyClass);
1282
+ jqLite.addClass(inputEl, notEmptyClass);
1283
+ } else {
1284
+ jqLite.removeClass(inputEl, notEmptyClass);
1285
+ jqLite.addClass(inputEl, emptyClass)
1286
+ }
1261
1287
 
1262
- // return if library has been loaded already
1263
- if (win._muiLoadedJS) return;
1264
- else win._muiLoadedJS = true;
1265
-
1266
- // load dependencies
1267
- var jqLite = require('./lib/jqLite.js'),
1268
- util = require('./lib/util.js'),
1269
- textfield = require('./forms/textfield.js'),
1270
- select = require('./forms/select.js'),
1271
- ripple = require('./ripple.js'),
1272
- dropdowns = require('./dropdowns.js'),
1273
- tabs = require('./tabs.js'),
1274
- overlay = require('./overlay.js');
1288
+ jqLite.addClass(inputEl, dirtyClass);
1289
+ }
1275
1290
 
1276
- // expose api
1277
- win.mui = {
1278
- overlay: overlay,
1279
- tabs: tabs.api
1280
- };
1291
+
1292
+ /** Define module API */
1293
+ module.exports = {
1294
+ /** Initialize input elements */
1295
+ initialize: initialize,
1281
1296
 
1282
- // init libraries
1283
- jqLite.ready(function() {
1284
- textfield.initListeners();
1285
- select.initListeners();
1286
- ripple.initListeners();
1287
- dropdowns.initListeners();
1288
- tabs.initListeners();
1289
- });
1290
- })(window);
1297
+ /** Initialize module listeners */
1298
+ initListeners: function() {
1299
+ var doc = document;
1300
+
1301
+ // markup elements available when method is called
1302
+ var elList = doc.querySelectorAll(cssSelector);
1303
+ for (var i=elList.length - 1; i >= 0; i--) initialize(elList[i]);
1304
+
1305
+ // listen for new elements
1306
+ util.onNodeInserted(function(el) {
1307
+ if (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA') initialize(el);
1308
+ });
1309
+
1310
+ // add transition css for floating labels
1311
+ setTimeout(function() {
1312
+ var css = '.mui-textfield.mui-textfield--float-label > label {' + [
1313
+ '-webkit-transition',
1314
+ '-moz-transition',
1315
+ '-o-transition',
1316
+ 'transition',
1317
+ ''
1318
+ ].join(':all .15s ease-out;') + '}';
1319
+
1320
+ util.loadStyle(css);
1321
+ }, 150);
1322
+
1323
+ // pointer-events shim for floating labels
1324
+ if (util.supportsPointerEvents() === false) {
1325
+ jqLite.on(document, 'click', function(ev) {
1326
+ var targetEl = ev.target;
1327
+
1328
+ if (targetEl.tagName === 'LABEL' &&
1329
+ jqLite.hasClass(targetEl.parentNode, floatingLabelClass)) {
1330
+ var inputEl = targetEl.previousElementSibling;
1331
+ if (inputEl) inputEl.focus();
1332
+ }
1333
+ });
1334
+ }
1335
+ }
1336
+ };
1291
1337
 
1292
- },{"./dropdowns.js":2,"./forms/select.js":3,"./forms/textfield.js":4,"./lib/jqLite.js":5,"./lib/util.js":6,"./overlay.js":8,"./ripple.js":9,"./tabs.js":10}],8:[function(require,module,exports){
1338
+ },{"../lib/jqLite":4,"../lib/util":5}],9:[function(require,module,exports){
1339
+ module.exports=require(4)
1340
+ },{}],10:[function(require,module,exports){
1293
1341
  /**
1294
1342
  * MUI CSS/JS overlay module
1295
1343
  * @module overlay
@@ -1298,8 +1346,8 @@ module.exports = {
1298
1346
  'use strict';
1299
1347
 
1300
1348
 
1301
- var util = require('./lib/util.js'),
1302
- jqLite = require('./lib/jqLite.js'),
1349
+ var util = require('./lib/util'),
1350
+ jqLite = require('./lib/jqLite'),
1303
1351
  overlayId = 'mui-overlay',
1304
1352
  bodyClass = 'mui--overflow-hidden',
1305
1353
  iosRegex = /(iPad|iPhone|iPod)/g;
@@ -1483,7 +1531,7 @@ function onClick(ev) {
1483
1531
  /** Define module API */
1484
1532
  module.exports = overlayFn;
1485
1533
 
1486
- },{"./lib/jqLite.js":5,"./lib/util.js":6}],9:[function(require,module,exports){
1534
+ },{"./lib/jqLite":4,"./lib/util":5}],11:[function(require,module,exports){
1487
1535
  /**
1488
1536
  * MUI CSS/JS ripple module
1489
1537
  * @module ripple
@@ -1492,8 +1540,8 @@ module.exports = overlayFn;
1492
1540
  'use strict';
1493
1541
 
1494
1542
 
1495
- var jqLite = require('./lib/jqLite.js'),
1496
- util = require('./lib/util.js'),
1543
+ var jqLite = require('./lib/jqLite'),
1544
+ util = require('./lib/util'),
1497
1545
  btnClass = 'mui-btn',
1498
1546
  btnFABClass = 'mui-btn--fab',
1499
1547
  rippleClass = 'mui-ripple-effect',
@@ -1588,7 +1636,7 @@ module.exports = {
1588
1636
  }
1589
1637
  };
1590
1638
 
1591
- },{"./lib/jqLite.js":5,"./lib/util.js":6}],10:[function(require,module,exports){
1639
+ },{"./lib/jqLite":4,"./lib/util":5}],12:[function(require,module,exports){
1592
1640
  /**
1593
1641
  * MUI CSS/JS tabs module
1594
1642
  * @module tabs
@@ -1597,8 +1645,8 @@ module.exports = {
1597
1645
  'use strict';
1598
1646
 
1599
1647
 
1600
- var jqLite = require('./lib/jqLite.js'),
1601
- util = require('./lib/util.js'),
1648
+ var jqLite = require('./lib/jqLite'),
1649
+ util = require('./lib/util'),
1602
1650
  attrKey = 'data-mui-toggle',
1603
1651
  attrSelector = '[' + attrKey + '="tab"]',
1604
1652
  controlsAttrKey = 'data-mui-controls',
@@ -1749,4 +1797,4 @@ module.exports = {
1749
1797
  }
1750
1798
  };
1751
1799
 
1752
- },{"./lib/jqLite.js":5,"./lib/util.js":6}]},{},[7]);
1800
+ },{"./lib/jqLite":4,"./lib/util":5}]},{},[1])