mui-sass 0.2.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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])