material_design_lite-sass 1.0.1

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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +11 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +11 -0
  5. data/CHANGELOG.md +5 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +108 -0
  9. data/Rakefile +4 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +7 -0
  12. data/lib/material_design_lite-sass.rb +46 -0
  13. data/lib/material_design_lite/sass/engine.rb +13 -0
  14. data/lib/material_design_lite/sass/version.rb +5 -0
  15. data/material_design_lite-sass.gemspec +28 -0
  16. data/vendor/assets/javascripts/material.js +3919 -0
  17. data/vendor/assets/javascripts/material/button.js +132 -0
  18. data/vendor/assets/javascripts/material/checkbox.js +265 -0
  19. data/vendor/assets/javascripts/material/data-table.js +149 -0
  20. data/vendor/assets/javascripts/material/icon-toggle.js +248 -0
  21. data/vendor/assets/javascripts/material/layout.js +434 -0
  22. data/vendor/assets/javascripts/material/mdlComponentHandler.js +346 -0
  23. data/vendor/assets/javascripts/material/menu.js +468 -0
  24. data/vendor/assets/javascripts/material/progress.js +116 -0
  25. data/vendor/assets/javascripts/material/rAF.js +38 -0
  26. data/vendor/assets/javascripts/material/radio.js +257 -0
  27. data/vendor/assets/javascripts/material/ripple.js +244 -0
  28. data/vendor/assets/javascripts/material/slider.js +252 -0
  29. data/vendor/assets/javascripts/material/spinner.js +140 -0
  30. data/vendor/assets/javascripts/material/switch.js +269 -0
  31. data/vendor/assets/javascripts/material/tabs.js +152 -0
  32. data/vendor/assets/javascripts/material/textfield.js +247 -0
  33. data/vendor/assets/javascripts/material/tooltip.js +146 -0
  34. data/vendor/assets/stylesheets/_material.scss +50 -0
  35. data/vendor/assets/stylesheets/material/_animation.scss +34 -0
  36. data/vendor/assets/stylesheets/material/_badge.scss +66 -0
  37. data/vendor/assets/stylesheets/material/_button.scss +298 -0
  38. data/vendor/assets/stylesheets/material/_card.scss +111 -0
  39. data/vendor/assets/stylesheets/material/_checkbox.scss +175 -0
  40. data/vendor/assets/stylesheets/material/_color-definitions.scss +599 -0
  41. data/vendor/assets/stylesheets/material/_data-table.scss +105 -0
  42. data/vendor/assets/stylesheets/material/_functions.scss +3 -0
  43. data/vendor/assets/stylesheets/material/_grid.scss +180 -0
  44. data/vendor/assets/stylesheets/material/_icon-toggle.scss +121 -0
  45. data/vendor/assets/stylesheets/material/_layout.scss +580 -0
  46. data/vendor/assets/stylesheets/material/_mega_footer.scss +309 -0
  47. data/vendor/assets/stylesheets/material/_menu.scss +193 -0
  48. data/vendor/assets/stylesheets/material/_mini_footer.scss +88 -0
  49. data/vendor/assets/stylesheets/material/_mixins.scss +268 -0
  50. data/vendor/assets/stylesheets/material/_palette.scss +2303 -0
  51. data/vendor/assets/stylesheets/material/_progress.scss +115 -0
  52. data/vendor/assets/stylesheets/material/_radio.scss +155 -0
  53. data/vendor/assets/stylesheets/material/_resets.scss +55 -0
  54. data/vendor/assets/stylesheets/material/_ripple.scss +42 -0
  55. data/vendor/assets/stylesheets/material/_shadow.scss +42 -0
  56. data/vendor/assets/stylesheets/material/_slider.scss +396 -0
  57. data/vendor/assets/stylesheets/material/_spinner.scss +248 -0
  58. data/vendor/assets/stylesheets/material/_switch.scss +199 -0
  59. data/vendor/assets/stylesheets/material/_tabs.scss +115 -0
  60. data/vendor/assets/stylesheets/material/_textfield.scss +190 -0
  61. data/vendor/assets/stylesheets/material/_tooltip.scss +66 -0
  62. data/vendor/assets/stylesheets/material/_typography.scss +297 -0
  63. data/vendor/assets/stylesheets/material/_variables.scss +572 -0
  64. data/vendor/assets/stylesheets/material/resets/_h5bp.scss +284 -0
  65. data/vendor/assets/stylesheets/material/resets/_mobile.scss +25 -0
  66. metadata +151 -0
@@ -0,0 +1,346 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2015 Google Inc. All Rights Reserved.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * A component handler interface using the revealing module design pattern.
20
+ * More details on this design pattern here:
21
+ * https://github.com/jasonmayes/mdl-component-design-pattern
22
+ * @author Jason Mayes.
23
+ */
24
+ /* exported componentHandler */
25
+ var componentHandler = (function() {
26
+ 'use strict';
27
+
28
+ var registeredComponents_ = [];
29
+ var createdComponents_ = [];
30
+ var downgradeMethod_ = 'mdlDowngrade_';
31
+ var componentConfigProperty_ = 'mdlComponentConfigInternal_';
32
+
33
+ /**
34
+ * Searches registered components for a class we are interested in using.
35
+ * Optionally replaces a match with passed object if specified.
36
+ * @param {string} name The name of a class we want to use.
37
+ * @param {Object=} optReplace Optional object to replace match with.
38
+ * @return {Object | boolean}
39
+ * @private
40
+ */
41
+ function findRegisteredClass_(name, optReplace) {
42
+ for (var i = 0; i < registeredComponents_.length; i++) {
43
+ if (registeredComponents_[i].className === name) {
44
+ if (optReplace !== undefined) {
45
+ registeredComponents_[i] = optReplace;
46
+ }
47
+ return registeredComponents_[i];
48
+ }
49
+ }
50
+ return false;
51
+ }
52
+
53
+ /**
54
+ * Returns an array of the classNames of the upgraded classes on the element.
55
+ * @param {HTMLElement} element The element to fetch data from.
56
+ * @return {Array<string>}
57
+ * @private
58
+ */
59
+ function getUpgradedListOfElement_(element) {
60
+ var dataUpgraded = element.getAttribute('data-upgraded');
61
+ // Use `['']` as default value to conform the `,name,name...` style.
62
+ return dataUpgraded === null ? [''] : dataUpgraded.split(',');
63
+ }
64
+
65
+ /**
66
+ * Returns true if the given element has already been upgraded for the given
67
+ * class.
68
+ * @param {HTMLElement} element The element we want to check.
69
+ * @param {string} jsClass The class to check for.
70
+ * @return boolean
71
+ * @private
72
+ */
73
+ function isElementUpgraded_(element, jsClass) {
74
+ var upgradedList = getUpgradedListOfElement_(element);
75
+ return upgradedList.indexOf(jsClass) !== -1;
76
+ }
77
+
78
+ /**
79
+ * Searches existing DOM for elements of our component type and upgrades them
80
+ * if they have not already been upgraded.
81
+ * @param {!string=} optJsClass the programatic name of the element class we
82
+ * need to create a new instance of.
83
+ * @param {!string=} optCssClass the name of the CSS class elements of this
84
+ * type will have.
85
+ */
86
+ function upgradeDomInternal(optJsClass, optCssClass) {
87
+ if (optJsClass === undefined && optCssClass === undefined) {
88
+ for (var i = 0; i < registeredComponents_.length; i++) {
89
+ upgradeDomInternal(registeredComponents_[i].className,
90
+ registeredComponents_[i].cssClass);
91
+ }
92
+ } else {
93
+ var jsClass = /** @type {!string} */ (optJsClass);
94
+ if (optCssClass === undefined) {
95
+ var registeredClass = findRegisteredClass_(jsClass);
96
+ if (registeredClass) {
97
+ optCssClass = registeredClass.cssClass;
98
+ }
99
+ }
100
+
101
+ var elements = document.querySelectorAll('.' + optCssClass);
102
+ for (var n = 0; n < elements.length; n++) {
103
+ upgradeElementInternal(elements[n], jsClass);
104
+ }
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Upgrades a specific element rather than all in the DOM.
110
+ * @param {HTMLElement} element The element we wish to upgrade.
111
+ * @param {!string=} optJsClass Optional name of the class we want to upgrade
112
+ * the element to.
113
+ */
114
+ function upgradeElementInternal(element, optJsClass) {
115
+ // Verify argument type.
116
+ if (!(typeof element === 'object' && element instanceof Element)) {
117
+ throw new Error('Invalid argument provided to upgrade MDL element.');
118
+ }
119
+ var upgradedList = getUpgradedListOfElement_(element);
120
+ var classesToUpgrade = [];
121
+ // If jsClass is not provided scan the registered components to find the
122
+ // ones matching the element's CSS classList.
123
+ if (!optJsClass) {
124
+ var classList = element.classList;
125
+ registeredComponents_.forEach(function (component) {
126
+ // Match CSS & Not to be upgraded & Not upgraded.
127
+ if (classList.contains(component.cssClass) &&
128
+ classesToUpgrade.indexOf(component) === -1 &&
129
+ !isElementUpgraded_(element, component.className)) {
130
+ classesToUpgrade.push(component);
131
+ }
132
+ });
133
+ } else if (!isElementUpgraded_(element, optJsClass)) {
134
+ classesToUpgrade.push(findRegisteredClass_(optJsClass));
135
+ }
136
+
137
+ // Upgrade the element for each classes.
138
+ for (var i = 0, n = classesToUpgrade.length, registeredClass; i < n; i++) {
139
+ registeredClass = classesToUpgrade[i];
140
+ if (registeredClass) {
141
+ // Mark element as upgraded.
142
+ upgradedList.push(registeredClass.className);
143
+ element.setAttribute('data-upgraded', upgradedList.join(','));
144
+ var instance = new registeredClass.classConstructor(element);
145
+ instance[componentConfigProperty_] = registeredClass;
146
+ createdComponents_.push(instance);
147
+ // Call any callbacks the user has registered with this component type.
148
+ for (var j = 0, m = registeredClass.callbacks.length; j < m; j++) {
149
+ registeredClass.callbacks[j](element);
150
+ }
151
+
152
+ if (registeredClass.widget) {
153
+ // Assign per element instance for control over API
154
+ element[registeredClass.className] = instance;
155
+ }
156
+ } else {
157
+ throw new Error(
158
+ 'Unable to find a registered component for the given class.');
159
+ }
160
+
161
+ var ev = document.createEvent('Events');
162
+ ev.initEvent('mdl-componentupgraded', true, true);
163
+ element.dispatchEvent(ev);
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Upgrades a specific list of elements rather than all in the DOM.
169
+ * @param {HTMLElement | Array<HTMLElement> | NodeList | HTMLCollection} elements
170
+ * The elements we wish to upgrade.
171
+ */
172
+ function upgradeElementsInternal(elements) {
173
+ if (!Array.isArray(elements)) {
174
+ if (typeof elements.item === 'function') {
175
+ elements = Array.prototype.slice.call(elements);
176
+ } else {
177
+ elements = [elements];
178
+ }
179
+ }
180
+ for (var i = 0, n = elements.length, element; i < n; i++) {
181
+ element = elements[i];
182
+ if (element instanceof HTMLElement) {
183
+ if (element.children.length > 0) {
184
+ upgradeElementsInternal(element.children);
185
+ }
186
+ upgradeElementInternal(element);
187
+ }
188
+ }
189
+ }
190
+
191
+ /**
192
+ * Registers a class for future use and attempts to upgrade existing DOM.
193
+ * @param {Object} config An object containing:
194
+ * {constructor: Constructor, classAsString: string, cssClass: string}
195
+ */
196
+ function registerInternal(config) {
197
+ var newConfig = {
198
+ 'classConstructor': config.constructor,
199
+ 'className': config.classAsString,
200
+ 'cssClass': config.cssClass,
201
+ 'widget': config.widget === undefined ? true : config.widget,
202
+ 'callbacks': []
203
+ };
204
+
205
+ registeredComponents_.forEach(function(item) {
206
+ if (item.cssClass === newConfig.cssClass) {
207
+ throw new Error('The provided cssClass has already been registered.');
208
+ }
209
+ if (item.className === newConfig.className) {
210
+ throw new Error('The provided className has already been registered');
211
+ }
212
+ });
213
+
214
+ if (config.constructor.prototype
215
+ .hasOwnProperty(componentConfigProperty_)) {
216
+ throw new Error(
217
+ 'MDL component classes must not have ' + componentConfigProperty_ +
218
+ ' defined as a property.');
219
+ }
220
+
221
+ var found = findRegisteredClass_(config.classAsString, newConfig);
222
+
223
+ if (!found) {
224
+ registeredComponents_.push(newConfig);
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Allows user to be alerted to any upgrades that are performed for a given
230
+ * component type
231
+ * @param {string} jsClass The class name of the MDL component we wish
232
+ * to hook into for any upgrades performed.
233
+ * @param {!Function} callback The function to call upon an upgrade. This
234
+ * function should expect 1 parameter - the HTMLElement which got upgraded.
235
+ */
236
+ function registerUpgradedCallbackInternal(jsClass, callback) {
237
+ var regClass = findRegisteredClass_(jsClass);
238
+ if (regClass) {
239
+ regClass.callbacks.push(callback);
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Upgrades all registered components found in the current DOM. This is
245
+ * automatically called on window load.
246
+ */
247
+ function upgradeAllRegisteredInternal() {
248
+ for (var n = 0; n < registeredComponents_.length; n++) {
249
+ upgradeDomInternal(registeredComponents_[n].className);
250
+ }
251
+ }
252
+
253
+ /**
254
+ * Finds a created component by a given DOM node.
255
+ *
256
+ * @param {!Element} node
257
+ * @return {*}
258
+ */
259
+ function findCreatedComponentByNodeInternal(node) {
260
+ for (var n = 0; n < createdComponents_.length; n++) {
261
+ var component = createdComponents_[n];
262
+ if (component.element_ === node) {
263
+ return component;
264
+ }
265
+ }
266
+ }
267
+
268
+ /**
269
+ * Check the component for the downgrade method.
270
+ * Execute if found.
271
+ * Remove component from createdComponents list.
272
+ *
273
+ * @param {*} component
274
+ */
275
+ function deconstructComponentInternal(component) {
276
+ if (component &&
277
+ component[componentConfigProperty_]
278
+ .classConstructor.prototype
279
+ .hasOwnProperty(downgradeMethod_)) {
280
+ component[downgradeMethod_]();
281
+ var componentIndex = createdComponents_.indexOf(component);
282
+ createdComponents_.splice(componentIndex, 1);
283
+
284
+ var upgrades = component.element_.dataset.upgraded.split(',');
285
+ var componentPlace = upgrades.indexOf(
286
+ component[componentConfigProperty_].classAsString);
287
+ upgrades.splice(componentPlace, 1);
288
+ component.element_.dataset.upgraded = upgrades.join(',');
289
+
290
+ var ev = document.createEvent('Events');
291
+ ev.initEvent('mdl-componentdowngraded', true, true);
292
+ component.element_.dispatchEvent(ev);
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Downgrade either a given node, an array of nodes, or a NodeList.
298
+ *
299
+ * @param {*} nodes
300
+ */
301
+ function downgradeNodesInternal(nodes) {
302
+ var downgradeNode = function(node) {
303
+ deconstructComponentInternal(findCreatedComponentByNodeInternal(node));
304
+ };
305
+ if (nodes instanceof Array || nodes instanceof NodeList) {
306
+ for (var n = 0; n < nodes.length; n++) {
307
+ downgradeNode(nodes[n]);
308
+ }
309
+ } else if (nodes instanceof Node) {
310
+ downgradeNode(nodes);
311
+ } else {
312
+ throw new Error('Invalid argument provided to downgrade MDL nodes.');
313
+ }
314
+ }
315
+
316
+ // Now return the functions that should be made public with their publicly
317
+ // facing names...
318
+ return {
319
+ upgradeDom: upgradeDomInternal,
320
+ upgradeElement: upgradeElementInternal,
321
+ upgradeElements: upgradeElementsInternal,
322
+ upgradeAllRegistered: upgradeAllRegisteredInternal,
323
+ registerUpgradedCallback: registerUpgradedCallbackInternal,
324
+ register: registerInternal,
325
+ downgradeElements: downgradeNodesInternal
326
+ };
327
+ })();
328
+
329
+ window.addEventListener('load', function() {
330
+ 'use strict';
331
+
332
+ /**
333
+ * Performs a "Cutting the mustard" test. If the browser supports the features
334
+ * tested, adds a mdl-js class to the <html> element. It then upgrades all MDL
335
+ * components requiring JavaScript.
336
+ */
337
+ if ('classList' in document.createElement('div') &&
338
+ 'querySelector' in document &&
339
+ 'addEventListener' in window && Array.prototype.forEach) {
340
+ document.documentElement.classList.add('mdl-js');
341
+ componentHandler.upgradeAllRegistered();
342
+ } else {
343
+ componentHandler.upgradeElement =
344
+ componentHandler.register = function() {};
345
+ }
346
+ });
@@ -0,0 +1,468 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2015 Google Inc. All Rights Reserved.
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ /**
19
+ * Class constructor for dropdown MDL component.
20
+ * Implements MDL component design pattern defined at:
21
+ * https://github.com/jasonmayes/mdl-component-design-pattern
22
+ * @param {HTMLElement} element The element that will be upgraded.
23
+ */
24
+ function MaterialMenu(element) {
25
+ 'use strict';
26
+
27
+ this.element_ = element;
28
+
29
+ // Initialize instance.
30
+ this.init();
31
+ }
32
+
33
+ /**
34
+ * Store constants in one place so they can be updated easily.
35
+ * @enum {string | number}
36
+ * @private
37
+ */
38
+ MaterialMenu.prototype.Constant_ = {
39
+ // Total duration of the menu animation.
40
+ TRANSITION_DURATION_SECONDS: 0.3,
41
+ // The fraction of the total duration we want to use for menu item animations.
42
+ TRANSITION_DURATION_FRACTION: 0.8,
43
+ // How long the menu stays open after choosing an option (so the user can see
44
+ // the ripple).
45
+ CLOSE_TIMEOUT: 150
46
+ };
47
+
48
+ /**
49
+ * Keycodes, for code readability.
50
+ * @enum {number}
51
+ * @private
52
+ */
53
+ MaterialMenu.prototype.Keycodes_ = {
54
+ ENTER: 13,
55
+ ESCAPE: 27,
56
+ SPACE: 32,
57
+ UP_ARROW: 38,
58
+ DOWN_ARROW: 40
59
+ };
60
+
61
+ /**
62
+ * Store strings for class names defined by this component that are used in
63
+ * JavaScript. This allows us to simply change it in one place should we
64
+ * decide to modify at a later date.
65
+ * @enum {string}
66
+ * @private
67
+ */
68
+ MaterialMenu.prototype.CssClasses_ = {
69
+ CONTAINER: 'mdl-menu__container',
70
+ OUTLINE: 'mdl-menu__outline',
71
+ ITEM: 'mdl-menu__item',
72
+ ITEM_RIPPLE_CONTAINER: 'mdl-menu__item-ripple-container',
73
+ RIPPLE_EFFECT: 'mdl-js-ripple-effect',
74
+ RIPPLE_IGNORE_EVENTS: 'mdl-js-ripple-effect--ignore-events',
75
+ RIPPLE: 'mdl-ripple',
76
+ // Statuses
77
+ IS_UPGRADED: 'is-upgraded',
78
+ IS_VISIBLE: 'is-visible',
79
+ IS_ANIMATING: 'is-animating',
80
+ // Alignment options
81
+ BOTTOM_LEFT: 'mdl-menu--bottom-left', // This is the default.
82
+ BOTTOM_RIGHT: 'mdl-menu--bottom-right',
83
+ TOP_LEFT: 'mdl-menu--top-left',
84
+ TOP_RIGHT: 'mdl-menu--top-right',
85
+ UNALIGNED: 'mdl-menu--unaligned'
86
+ };
87
+
88
+ /**
89
+ * Initialize element.
90
+ */
91
+ MaterialMenu.prototype.init = function() {
92
+ 'use strict';
93
+
94
+ if (this.element_) {
95
+ // Create container for the menu.
96
+ var container = document.createElement('div');
97
+ container.classList.add(this.CssClasses_.CONTAINER);
98
+ this.element_.parentElement.insertBefore(container, this.element_);
99
+ this.element_.parentElement.removeChild(this.element_);
100
+ container.appendChild(this.element_);
101
+ this.container_ = container;
102
+
103
+ // Create outline for the menu (shadow and background).
104
+ var outline = document.createElement('div');
105
+ outline.classList.add(this.CssClasses_.OUTLINE);
106
+ this.outline_ = outline;
107
+ container.insertBefore(outline, this.element_);
108
+
109
+ // Find the "for" element and bind events to it.
110
+ var forElId = this.element_.getAttribute('for');
111
+ var forEl = null;
112
+ if (forElId) {
113
+ forEl = document.getElementById(forElId);
114
+ if (forEl) {
115
+ this.forElement_ = forEl;
116
+ forEl.addEventListener('click', this.handleForClick_.bind(this));
117
+ forEl.addEventListener('keydown',
118
+ this.handleForKeyboardEvent_.bind(this));
119
+ }
120
+ }
121
+
122
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
123
+
124
+ for (var i = 0; i < items.length; i++) {
125
+ // Add a listener to each menu item.
126
+ items[i].addEventListener('click', this.handleItemClick_.bind(this));
127
+ // Add a tab index to each menu item.
128
+ items[i].tabIndex = '-1';
129
+ // Add a keyboard listener to each menu item.
130
+ items[i].addEventListener('keydown',
131
+ this.handleItemKeyboardEvent_.bind(this));
132
+ }
133
+
134
+ // Add ripple classes to each item, if the user has enabled ripples.
135
+ if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
136
+ this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
137
+
138
+ for (i = 0; i < items.length; i++) {
139
+ var item = items[i];
140
+
141
+ var rippleContainer = document.createElement('span');
142
+ rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER);
143
+
144
+ var ripple = document.createElement('span');
145
+ ripple.classList.add(this.CssClasses_.RIPPLE);
146
+ rippleContainer.appendChild(ripple);
147
+
148
+ item.appendChild(rippleContainer);
149
+ item.classList.add(this.CssClasses_.RIPPLE_EFFECT);
150
+ }
151
+ }
152
+
153
+ // Copy alignment classes to the container, so the outline can use them.
154
+ if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) {
155
+ this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT);
156
+ }
157
+ if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
158
+ this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT);
159
+ }
160
+ if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
161
+ this.outline_.classList.add(this.CssClasses_.TOP_LEFT);
162
+ }
163
+ if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
164
+ this.outline_.classList.add(this.CssClasses_.TOP_RIGHT);
165
+ }
166
+ if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
167
+ this.outline_.classList.add(this.CssClasses_.UNALIGNED);
168
+ }
169
+
170
+ container.classList.add(this.CssClasses_.IS_UPGRADED);
171
+ }
172
+ };
173
+
174
+ /**
175
+ * Handles a click on the "for" element, by positioning the menu and then
176
+ * toggling it.
177
+ * @private
178
+ */
179
+ MaterialMenu.prototype.handleForClick_ = function(evt) {
180
+ 'use strict';
181
+
182
+ if (this.element_ && this.forElement_) {
183
+ var rect = this.forElement_.getBoundingClientRect();
184
+ var forRect = this.forElement_.parentElement.getBoundingClientRect();
185
+
186
+ if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
187
+ // Do not position the menu automatically. Requires the developer to
188
+ // manually specify position.
189
+ } else if (this.element_.classList.contains(
190
+ this.CssClasses_.BOTTOM_RIGHT)) {
191
+ // Position below the "for" element, aligned to its right.
192
+ this.container_.style.right = (forRect.right - rect.right) + 'px';
193
+ this.container_.style.top =
194
+ this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
195
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
196
+ // Position above the "for" element, aligned to its left.
197
+ this.container_.style.left = this.forElement_.offsetLeft + 'px';
198
+ this.container_.style.bottom = (forRect.bottom - rect.top) + 'px';
199
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
200
+ // Position above the "for" element, aligned to its right.
201
+ this.container_.style.right = (forRect.right - rect.right) + 'px';
202
+ this.container_.style.bottom = (forRect.bottom - rect.top) + 'px';
203
+ } else {
204
+ // Default: position below the "for" element, aligned to its left.
205
+ this.container_.style.left = this.forElement_.offsetLeft + 'px';
206
+ this.container_.style.top =
207
+ this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
208
+ }
209
+ }
210
+
211
+ this.toggle(evt);
212
+ };
213
+
214
+ /**
215
+ * Handles a keyboard event on the "for" element.
216
+ * @private
217
+ */
218
+ MaterialMenu.prototype.handleForKeyboardEvent_ = function(evt) {
219
+ 'use strict';
220
+
221
+ if (this.element_ && this.container_ && this.forElement_) {
222
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM +
223
+ ':not([disabled])');
224
+
225
+ if (items && items.length > 0 &&
226
+ this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
227
+ if (evt.keyCode === this.Keycodes_.UP_ARROW) {
228
+ evt.preventDefault();
229
+ items[items.length - 1].focus();
230
+ } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
231
+ evt.preventDefault();
232
+ items[0].focus();
233
+ }
234
+ }
235
+ }
236
+ };
237
+
238
+ /**
239
+ * Handles a keyboard event on an item.
240
+ * @private
241
+ */
242
+ MaterialMenu.prototype.handleItemKeyboardEvent_ = function(evt) {
243
+ 'use strict';
244
+
245
+ if (this.element_ && this.container_) {
246
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM +
247
+ ':not([disabled])');
248
+
249
+ if (items && items.length > 0 &&
250
+ this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
251
+ var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target);
252
+
253
+ if (evt.keyCode === this.Keycodes_.UP_ARROW) {
254
+ evt.preventDefault();
255
+ if (currentIndex > 0) {
256
+ items[currentIndex - 1].focus();
257
+ } else {
258
+ items[items.length - 1].focus();
259
+ }
260
+ } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
261
+ evt.preventDefault();
262
+ if (items.length > currentIndex + 1) {
263
+ items[currentIndex + 1].focus();
264
+ } else {
265
+ items[0].focus();
266
+ }
267
+ } else if (evt.keyCode === this.Keycodes_.SPACE ||
268
+ evt.keyCode === this.Keycodes_.ENTER) {
269
+ evt.preventDefault();
270
+ // Send mousedown and mouseup to trigger ripple.
271
+ var e = new MouseEvent('mousedown');
272
+ evt.target.dispatchEvent(e);
273
+ e = new MouseEvent('mouseup');
274
+ evt.target.dispatchEvent(e);
275
+ // Send click.
276
+ evt.target.click();
277
+ } else if (evt.keyCode === this.Keycodes_.ESCAPE) {
278
+ evt.preventDefault();
279
+ this.hide();
280
+ }
281
+ }
282
+ }
283
+ };
284
+
285
+ /**
286
+ * Handles a click event on an item.
287
+ * @private
288
+ */
289
+ MaterialMenu.prototype.handleItemClick_ = function(evt) {
290
+ 'use strict';
291
+
292
+ if (evt.target.getAttribute('disabled') !== null) {
293
+ evt.stopPropagation();
294
+ } else {
295
+ // Wait some time before closing menu, so the user can see the ripple.
296
+ this.closing_ = true;
297
+ window.setTimeout(function(evt) {
298
+ this.hide();
299
+ this.closing_ = false;
300
+ }.bind(this), this.Constant_.CLOSE_TIMEOUT);
301
+ }
302
+ };
303
+
304
+ /**
305
+ * Calculates the initial clip (for opening the menu) or final clip (for closing
306
+ * it), and applies it. This allows us to animate from or to the correct point,
307
+ * that is, the point it's aligned to in the "for" element.
308
+ * @private
309
+ */
310
+ MaterialMenu.prototype.applyClip_ = function(height, width) {
311
+ 'use strict';
312
+
313
+ if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
314
+ // Do not clip.
315
+ this.element_.style.clip = null;
316
+ } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
317
+ // Clip to the top right corner of the menu.
318
+ this.element_.style.clip =
319
+ 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)';
320
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
321
+ // Clip to the bottom left corner of the menu.
322
+ this.element_.style.clip =
323
+ 'rect(' + height + 'px 0 ' + height + 'px 0)';
324
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
325
+ // Clip to the bottom right corner of the menu.
326
+ this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' +
327
+ height + 'px ' + width + 'px)';
328
+ } else {
329
+ // Default: do not clip (same as clipping to the top left corner).
330
+ this.element_.style.clip = null;
331
+ }
332
+ };
333
+
334
+ /**
335
+ * Adds an event listener to clean up after the animation ends.
336
+ * @private
337
+ */
338
+ MaterialMenu.prototype.addAnimationEndListener_ = function() {
339
+ 'use strict';
340
+
341
+ var cleanup = function () {
342
+ this.element_.removeEventListener('transitionend', cleanup);
343
+ this.element_.removeEventListener('webkitTransitionEnd', cleanup);
344
+ this.element_.classList.remove(this.CssClasses_.IS_ANIMATING);
345
+ }.bind(this);
346
+
347
+ // Remove animation class once the transition is done.
348
+ this.element_.addEventListener('transitionend', cleanup);
349
+ this.element_.addEventListener('webkitTransitionEnd', cleanup);
350
+ };
351
+
352
+ /**
353
+ * Displays the menu.
354
+ * @public
355
+ */
356
+ MaterialMenu.prototype.show = function(evt) {
357
+ 'use strict';
358
+
359
+ if (this.element_ && this.container_ && this.outline_) {
360
+ // Measure the inner element.
361
+ var height = this.element_.getBoundingClientRect().height;
362
+ var width = this.element_.getBoundingClientRect().width;
363
+
364
+ // Apply the inner element's size to the container and outline.
365
+ this.container_.style.width = width + 'px';
366
+ this.container_.style.height = height + 'px';
367
+ this.outline_.style.width = width + 'px';
368
+ this.outline_.style.height = height + 'px';
369
+
370
+ var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS *
371
+ this.Constant_.TRANSITION_DURATION_FRACTION;
372
+
373
+ // Calculate transition delays for individual menu items, so that they fade
374
+ // in one at a time.
375
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
376
+ for (var i = 0; i < items.length; i++) {
377
+ var itemDelay = null;
378
+ if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) ||
379
+ this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
380
+ itemDelay = ((height - items[i].offsetTop - items[i].offsetHeight) /
381
+ height * transitionDuration) + 's';
382
+ } else {
383
+ itemDelay = (items[i].offsetTop / height * transitionDuration) + 's';
384
+ }
385
+ items[i].style.transitionDelay = itemDelay;
386
+ }
387
+
388
+ // Apply the initial clip to the text before we start animating.
389
+ this.applyClip_(height, width);
390
+
391
+ // Wait for the next frame, turn on animation, and apply the final clip.
392
+ // Also make it visible. This triggers the transitions.
393
+ window.requestAnimationFrame(function() {
394
+ this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
395
+ this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)';
396
+ this.container_.classList.add(this.CssClasses_.IS_VISIBLE);
397
+ }.bind(this));
398
+
399
+ // Clean up after the animation is complete.
400
+ this.addAnimationEndListener_();
401
+
402
+ // Add a click listener to the document, to close the menu.
403
+ var callback = function(e) {
404
+ // Check to see if the document is processing the same event that
405
+ // displayed the menu in the first place. If so, do nothing.
406
+ // Also check to see if the menu is in the process of closing itself, and
407
+ // do nothing in that case.
408
+ if (e !== evt && !this.closing_) {
409
+ document.removeEventListener('click', callback);
410
+ this.hide();
411
+ }
412
+ }.bind(this);
413
+ document.addEventListener('click', callback);
414
+ }
415
+ };
416
+
417
+ /**
418
+ * Hides the menu.
419
+ * @public
420
+ */
421
+ MaterialMenu.prototype.hide = function() {
422
+ 'use strict';
423
+
424
+ if (this.element_ && this.container_ && this.outline_) {
425
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
426
+
427
+ // Remove all transition delays; menu items fade out concurrently.
428
+ for (var i = 0; i < items.length; i++) {
429
+ items[i].style.transitionDelay = null;
430
+ }
431
+
432
+ // Measure the inner element.
433
+ var height = this.element_.getBoundingClientRect().height;
434
+ var width = this.element_.getBoundingClientRect().width;
435
+
436
+ // Turn on animation, and apply the final clip. Also make invisible.
437
+ // This triggers the transitions.
438
+ this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
439
+ this.applyClip_(height, width);
440
+ this.container_.classList.remove(this.CssClasses_.IS_VISIBLE);
441
+
442
+ // Clean up after the animation is complete.
443
+ this.addAnimationEndListener_();
444
+ }
445
+ };
446
+
447
+ /**
448
+ * Displays or hides the menu, depending on current state.
449
+ * @public
450
+ */
451
+ MaterialMenu.prototype.toggle = function(evt) {
452
+ 'use strict';
453
+
454
+ if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
455
+ this.hide();
456
+ } else {
457
+ this.show(evt);
458
+ }
459
+ };
460
+
461
+ // The component registers itself. It can assume componentHandler is available
462
+ // in the global scope.
463
+ componentHandler.register({
464
+ constructor: MaterialMenu,
465
+ classAsString: 'MaterialMenu',
466
+ cssClass: 'mdl-js-menu',
467
+ widget: true
468
+ });