diff --git a/gulpfile.js b/gulpfile.js
index 7378ec22..d8c527b0 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -130,7 +130,7 @@ gulp.task('scripts', function () {
'src/checkbox/checkbox.js',
'src/column-layout/column-layout.js',
'src/icon-toggle/icon-toggle.js',
- 'src/item/item.js',
+ 'src/menu/menu.js',
'src/radio/radio.js',
'src/slider/slider.js',
'src/spinner/spinner.js',
diff --git a/src/dropdown-menu/_dropdown-menu.scss b/src/dropdown-menu/_dropdown-menu.scss
deleted file mode 100644
index f22414a0..00000000
--- a/src/dropdown-menu/_dropdown-menu.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-@import "../typography/typography";
-@import "../colors";
-@import "../shadow/shadow";
-
-.wsk-dropdown-menu {
- list-style : none;
- background : $default-dropdown-bg-color;
- border : none;
- border-radius: 2px;
- display : inline-block;
- min-width : 124px;
- position : relative;
- overflow : hidden;
- padding : 0;
- @include shadow-z1();
- will-change: box-shadow, transform;
- transition: box-shadow 0.2s ease-out;
-}
-
-.wsk-dropdown-menu .wsk-item {
- display: block;
- width: 100%;
-}
-
-/** TODO: DropdownMenu attached to other elements. */
diff --git a/src/dropdown-menu/demo.html b/src/dropdown-menu/demo.html
deleted file mode 100644
index eb7be5ba..00000000
--- a/src/dropdown-menu/demo.html
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
diff --git a/src/item/_item.scss b/src/item/_item.scss
deleted file mode 100644
index 4c2843f2..00000000
--- a/src/item/_item.scss
+++ /dev/null
@@ -1,53 +0,0 @@
-@import "../typography/typography";
-@import "../colors";
-@import "../ripple/ripple";
-
-.wsk-item {
- border : none;
- color : $default-item-text-color;
- background : transparent;
- display : inline-block;
- text-align : left;
- margin : 0;
- padding : 0.6em 1.2em;
- outline-color: $default-item-outline-color;
- position : relative;
- overflow : hidden;
- font : inherit;
- @include typo-body-1();
- text-decoration: none;
- cursor : pointer;
-}
-
-.wsk-item[disabled] {
- color : $disabled-item-text-color;
- cursor : auto;
-}
-
-.wsk-item:hover {
- background-color: $default-item-hover-bg-color;
-}
-
-.wsk-item:focus {
- outline: none;
- background-color: $default-item-focus-bg-color;
-}
-
-.wsk-item::-moz-focus-inner {
- border: 0;
-}
-
-.wsk-item:active {
- background-color: $default-item-active-bg-color;
-}
-
-.wsk-item--ripple-container {
- display: block;
- height: 100%;
- left: 0px;
- position: absolute;
- top: 0px;
- width: 100%;
- z-index: 0;
- overflow: hidden;
-}
diff --git a/src/item/demo.html b/src/item/demo.html
deleted file mode 100644
index f877bcc0..00000000
--- a/src/item/demo.html
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
Item
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Web Starter Kit
-
-
-
-
-
-
-
-
-
-
diff --git a/src/item/demo.scss b/src/item/demo.scss
deleted file mode 100644
index 5225fab4..00000000
--- a/src/item/demo.scss
+++ /dev/null
@@ -1,2 +0,0 @@
-@import "../styleguide_demo_bp";
-@import "_item";
diff --git a/src/item/item.js b/src/item/item.js
deleted file mode 100644
index e2f38899..00000000
--- a/src/item/item.js
+++ /dev/null
@@ -1,64 +0,0 @@
-/**
- * Class constructor for Item WSK component.
- * Implements WSK component design pattern defined at:
- * https://github.com/jasonmayes/wsk-component-design-pattern
- * @param {HTMLElement} element The element that will be upgraded.
- */
-function MaterialItem(element) {
- 'use strict';
-
- this.element_ = element;
-
- // Initialize instance.
- this.init();
-}
-
-/**
- * Store constants in one place so they can be updated easily.
- * @enum {string | number}
- * @private
- */
-MaterialItem.prototype.Constant_ = {
- // None for now.
-};
-
-/**
- * Store strings for class names defined by this component that are used in
- * JavaScript. This allows us to simply change it in one place should we
- * decide to modify at a later date.
- * @enum {string}
- * @private
- */
-MaterialItem.prototype.CssClasses_ = {
- WSK_ITEM_RIPPLE_CONTAINER: 'wsk-item--ripple-container',
-
- WSK_RIPPLE: 'wsk-ripple'
-};
-
-
-/**
- * Initialize element.
- */
-MaterialItem.prototype.init = function() {
- 'use strict';
-
- if (this.element_) {
- var rippleContainer = document.createElement('span');
- rippleContainer.classList.add(this.CssClasses_.WSK_ITEM_RIPPLE_CONTAINER);
-
- var ripple = document.createElement('span');
- ripple.classList.add(this.CssClasses_.WSK_RIPPLE);
- rippleContainer.appendChild(ripple);
-
- this.element_.appendChild(rippleContainer);
- }
-};
-
-
-// The component registers itself. It can assume componentHandler is available
-// in the global scope.
-componentHandler.register({
- constructor: MaterialItem,
- classAsString: 'MaterialItem',
- cssClass: 'wsk-js-ripple-effect'
-});
diff --git a/src/menu/_menu.scss b/src/menu/_menu.scss
new file mode 100644
index 00000000..74528097
--- /dev/null
+++ b/src/menu/_menu.scss
@@ -0,0 +1,174 @@
+@import "../typography/typography";
+@import "../animation/animation";
+@import "../colors";
+@import "../shadow/shadow";
+@import "../ripple/ripple";
+
+$menu-expand-duration: 0.3s;
+$menu-fade-duration: 0.2s;
+
+.wsk-menu__container {
+ display: block;
+ margin: 0;
+ padding: 0;
+ border: none;
+ position: absolute;
+ overflow: visible;
+ height: 0;
+ width: 0;
+}
+
+.wsk-menu__outline {
+ display: block;
+ background: $default-dropdown-bg-color;
+ margin: 0;
+ padding: 0;
+ border: none;
+ border-radius: 2px;
+ position: absolute;
+ top: 0;
+ left: 0;
+ overflow: hidden;
+ opacity: 0;
+ transform: scale(0);
+ transform-origin: 0 0;
+ @include shadow-z1();
+ will-change: transform;
+ transition: transform $menu-expand-duration $animation-curve-default,
+ opacity $menu-fade-duration $animation-curve-default;
+
+ .wsk-menu__container.is-visible & {
+ opacity: 1;
+ transform: scale(1);
+ }
+
+ &.wsk-menu--bottom-right {
+ transform-origin: 100% 0;
+ }
+
+ &.wsk-menu--top-left {
+ transform-origin: 0 100%;
+ }
+
+ &.wsk-menu--top-right {
+ transform-origin: 100% 100%;
+ }
+}
+
+.wsk-menu {
+ position: absolute;
+ list-style: none;
+ top: 0;
+ left: 0;
+ height: auto;
+ width: auto;
+ min-width: 124px;
+ padding: 8px 0;
+ margin: 0;
+ opacity: 0;
+ clip: rect(0 0 0 0);
+
+ .wsk-menu__container.is-visible & {
+ opacity: 1;
+ }
+
+ &.is-animating {
+ transition: opacity $menu-fade-duration $animation-curve-default,
+ clip $menu-expand-duration $animation-curve-default;
+ }
+
+ &.wsk-menu--bottom-right {
+ left: auto;
+ right: 0;
+ }
+
+ &.wsk-menu--top-left {
+ top: auto;
+ bottom: 0;
+ }
+
+ &.wsk-menu--top-right {
+ top: auto;
+ left: auto;
+ bottom: 0;
+ right: 0;
+ }
+
+ &.wsk-menu--unaligned {
+ top: auto;
+ left: auto;
+ }
+}
+
+.wsk-menu__item {
+ display: block;
+ border: none;
+ color: $default-item-text-color;
+ background-color: transparent;
+ text-align: left;
+ margin: 0;
+ padding: 0 16px;
+ outline-color: $default-item-outline-color;
+ position: relative;
+ overflow: hidden;
+ @include typo-body-1();
+ text-decoration: none;
+ cursor: pointer;
+ height: 48px;
+ line-height: 48px;
+ white-space: nowrap;
+ opacity: 0;
+ transition: opacity $menu-fade-duration $animation-curve-default;
+ user-select: none;
+
+ .wsk-menu__container.is-visible & {
+ opacity: 1;
+ }
+
+ &::-moz-focus-inner {
+ border: 0;
+ }
+
+ &[disabled] {
+ color: $disabled-item-text-color;
+ background-color: transparent;
+ cursor: auto;
+
+ &:hover {
+ background-color: transparent;
+ }
+
+ &:focus {
+ background-color: transparent;
+ }
+
+ & .wsk-ripple {
+ background: transparent;
+ }
+ }
+
+ &:hover {
+ background-color: $default-item-hover-bg-color;
+ }
+
+ &:focus {
+ outline: none;
+ background-color: $default-item-focus-bg-color;
+ }
+
+ &:active {
+ background-color: $default-item-active-bg-color;
+ }
+}
+
+
+.wsk-menu__item--ripple-container {
+ display: block;
+ height: 100%;
+ left: 0px;
+ position: absolute;
+ top: 0px;
+ width: 100%;
+ z-index: 0;
+ overflow: hidden;
+}
diff --git a/src/menu/demo.html b/src/menu/demo.html
new file mode 100644
index 00000000..0399d90b
--- /dev/null
+++ b/src/menu/demo.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
Menu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/menu/demo.scss b/src/menu/demo.scss
new file mode 100644
index 00000000..2fd7c61e
--- /dev/null
+++ b/src/menu/demo.scss
@@ -0,0 +1,33 @@
+@import "../styleguide_demo_bp";
+@import "../button/button";
+@import "../icons/icons";
+@import "_menu";
+
+.PreviewBlock {
+ position: relative;
+ height: 500px;
+}
+
+.demo-button-left {
+ position: absolute;
+ left: 24px;
+ top: 250px;
+}
+
+.demo-button-left2 {
+ position: absolute;
+ left: 72px;
+ top: 250px;
+}
+
+.demo-button-right {
+ position: absolute;
+ right: 24px;
+ top: 250px;
+}
+
+.demo-button-right2 {
+ position: absolute;
+ right: 72px;
+ top: 250px;
+}
diff --git a/src/menu/menu.js b/src/menu/menu.js
new file mode 100644
index 00000000..0aa75934
--- /dev/null
+++ b/src/menu/menu.js
@@ -0,0 +1,448 @@
+/**
+ * Class constructor for dropdown WSK component.
+ * Implements WSK component design pattern defined at:
+ * https://github.com/jasonmayes/wsk-component-design-pattern
+ * @param {HTMLElement} element The element that will be upgraded.
+ */
+function MaterialMenu(element) {
+ 'use strict';
+
+ this.element_ = element;
+
+ // Initialize instance.
+ this.init();
+}
+
+/**
+ * Store constants in one place so they can be updated easily.
+ * @enum {string | number}
+ * @private
+ */
+MaterialMenu.prototype.Constant_ = {
+ // Total duration of the menu animation.
+ TRANSITION_DURATION_SECONDS: 0.3,
+ // The fraction of the total duration we want to use for menu item animations.
+ TRANSITION_DURATION_FRACTION: 0.8,
+ // How long the menu stays open after choosing an option (so the user can see
+ // the ripple).
+ CLOSE_TIMEOUT: 150
+};
+
+/**
+ * Keycodes, for code readability.
+ * @enum {number}
+ * @private
+ */
+MaterialMenu.prototype.Keycodes_ = {
+ ENTER: 13,
+ ESCAPE: 27,
+ SPACE: 32,
+ UP_ARROW: 38,
+ DOWN_ARROW: 40
+};
+
+/**
+ * Store strings for class names defined by this component that are used in
+ * JavaScript. This allows us to simply change it in one place should we
+ * decide to modify at a later date.
+ * @enum {string}
+ * @private
+ */
+MaterialMenu.prototype.CssClasses_ = {
+ CONTAINER: 'wsk-menu__container',
+ OUTLINE: 'wsk-menu__outline',
+ ITEM: 'wsk-menu__item',
+ ITEM_RIPPLE_CONTAINER: 'wsk-menu__item-ripple-container',
+ RIPPLE_EFFECT: 'wsk-js-ripple-effect',
+ RIPPLE_IGNORE_EVENTS: 'wsk-js-ripple-effect--ignore-events',
+ RIPPLE: 'wsk-ripple',
+ // Statuses
+ IS_UPGRADED: 'is-upgraded',
+ IS_VISIBLE: 'is-visible',
+ IS_ANIMATING: 'is-animating',
+ // Alignment options
+ BOTTOM_LEFT: 'wsk-menu--bottom-left', // This is the default.
+ BOTTOM_RIGHT: 'wsk-menu--bottom-right',
+ TOP_LEFT: 'wsk-menu--top-left',
+ TOP_RIGHT: 'wsk-menu--top-right',
+ UNALIGNED: 'wsk-menu--unaligned'
+};
+
+/**
+ * Initialize element.
+ */
+MaterialMenu.prototype.init = function() {
+ 'use strict';
+
+ if (this.element_) {
+ // Create container for the menu.
+ var container = document.createElement('div');
+ container.classList.add(this.CssClasses_.CONTAINER);
+ this.element_.parentElement.insertBefore(container, this.element_);
+ this.element_.parentElement.removeChild(this.element_);
+ container.appendChild(this.element_);
+ this.container_ = container;
+
+ // Create outline for the menu (shadow and background).
+ var outline = document.createElement('div');
+ outline.classList.add(this.CssClasses_.OUTLINE);
+ this.outline_ = outline;
+ container.insertBefore(outline, this.element_);
+
+ // Find the "for" element and bind events to it.
+ var forElId = this.element_.getAttribute('for');
+ var forEl = null;
+ if (forElId) {
+ forEl = document.getElementById(forElId);
+ if (forEl) {
+ this.forElement_ = forEl;
+ forEl.addEventListener('click', this.handleForClick_.bind(this));
+ forEl.addEventListener('keydown',
+ this.handleForKeyboardEvent_.bind(this));
+ }
+ }
+
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
+
+ for (var i = 0; i < items.length; i++) {
+ // Add a listener to each menu item.
+ items[i].addEventListener('click', this.handleItemClick_.bind(this));
+ // Add a tab index to each menu item.
+ items[i].tabIndex = '-1';
+ // Add a keyboard listener to each menu item.
+ items[i].addEventListener('keydown',
+ this.handleItemKeyboardEvent_.bind(this));
+ }
+
+ // Add ripple classes to each item, if the user has enabled ripples.
+ if (this.element_.classList.contains(this.CssClasses_.RIPPLE_EFFECT)) {
+ this.element_.classList.add(this.CssClasses_.RIPPLE_IGNORE_EVENTS);
+
+ for (i = 0; i < items.length; i++) {
+ var item = items[i];
+
+ var rippleContainer = document.createElement('span');
+ rippleContainer.classList.add(this.CssClasses_.ITEM_RIPPLE_CONTAINER);
+
+ var ripple = document.createElement('span');
+ ripple.classList.add(this.CssClasses_.RIPPLE);
+ rippleContainer.appendChild(ripple);
+
+ item.appendChild(rippleContainer);
+ item.classList.add(this.CssClasses_.RIPPLE_EFFECT);
+ }
+ }
+
+ // Copy alignment classes to the container, so the outline can use them.
+ if (this.element_.classList.contains(this.CssClasses_.BOTTOM_LEFT)) {
+ this.outline_.classList.add(this.CssClasses_.BOTTOM_LEFT);
+ }
+ if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
+ this.outline_.classList.add(this.CssClasses_.BOTTOM_RIGHT);
+ }
+ if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
+ this.outline_.classList.add(this.CssClasses_.TOP_LEFT);
+ }
+ if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
+ this.outline_.classList.add(this.CssClasses_.TOP_RIGHT);
+ }
+ if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
+ this.outline_.classList.add(this.CssClasses_.UNALIGNED);
+ }
+
+ container.classList.add(this.CssClasses_.IS_UPGRADED);
+ }
+};
+
+/**
+ * Handles a click on the "for" element, by positioning the menu and then
+ * toggling it.
+ * @private
+ */
+MaterialMenu.prototype.handleForClick_ = function(evt) {
+ 'use strict';
+
+ if (this.element_ && this.forElement_) {
+ var rect = this.forElement_.getBoundingClientRect();
+ var forRect = this.forElement_.parentElement.getBoundingClientRect();
+
+ if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
+ // Do not position the menu automatically. Requires the developer to
+ // manually specify position.
+ } else if (this.element_.classList.contains(
+ this.CssClasses_.BOTTOM_RIGHT)) {
+ // Position below the "for" element, aligned to its right.
+ this.container_.style.right = (forRect.right - rect.right) + 'px';
+ this.container_.style.top =
+ this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
+ // Position above the "for" element, aligned to its left.
+ this.container_.style.left = this.forElement_.offsetLeft + 'px';
+ this.container_.style.bottom = (forRect.bottom - rect.top) + 'px';
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
+ // Position above the "for" element, aligned to its right.
+ this.container_.style.right = (forRect.right - rect.right) + 'px';
+ this.container_.style.bottom = (forRect.bottom - rect.top) + 'px';
+ } else {
+ // Default: position below the "for" element, aligned to its left.
+ this.container_.style.left = this.forElement_.offsetLeft + 'px';
+ this.container_.style.top =
+ this.forElement_.offsetTop + this.forElement_.offsetHeight + 'px';
+ }
+ }
+
+ this.toggle(evt);
+};
+
+/**
+ * Handles a keyboard event on the "for" element.
+ * @private
+ */
+MaterialMenu.prototype.handleForKeyboardEvent_ = function(evt) {
+ 'use strict';
+
+ if (this.element_ && this.container_ && this.forElement_) {
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM +
+ ':not([disabled])');
+
+ if (items && items.length > 0 &&
+ this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
+ if (evt.keyCode === this.Keycodes_.UP_ARROW) {
+ evt.preventDefault();
+ items[items.length - 1].focus();
+ } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
+ evt.preventDefault();
+ items[0].focus();
+ }
+ }
+ }
+};
+
+/**
+ * Handles a keyboard event on an item.
+ * @private
+ */
+MaterialMenu.prototype.handleItemKeyboardEvent_ = function(evt) {
+ 'use strict';
+
+ if (this.element_ && this.container_) {
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM +
+ ':not([disabled])');
+
+ if (items && items.length > 0 &&
+ this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
+ var currentIndex = Array.prototype.slice.call(items).indexOf(evt.target);
+
+ if (evt.keyCode === this.Keycodes_.UP_ARROW) {
+ evt.preventDefault();
+ if (currentIndex > 0) {
+ items[currentIndex - 1].focus();
+ } else {
+ items[items.length - 1].focus();
+ }
+ } else if (evt.keyCode === this.Keycodes_.DOWN_ARROW) {
+ evt.preventDefault();
+ if (items.length > currentIndex + 1) {
+ items[currentIndex + 1].focus();
+ } else {
+ items[0].focus();
+ }
+ } else if (evt.keyCode === this.Keycodes_.SPACE ||
+ evt.keyCode === this.Keycodes_.ENTER) {
+ evt.preventDefault();
+ // Send mousedown and mouseup to trigger ripple.
+ var e = new MouseEvent('mousedown');
+ evt.target.dispatchEvent(e);
+ e = new MouseEvent('mouseup');
+ evt.target.dispatchEvent(e);
+ // Send click.
+ evt.target.click();
+ } else if (evt.keyCode === this.Keycodes_.ESCAPE) {
+ evt.preventDefault();
+ this.hide();
+ }
+ }
+ }
+};
+
+/**
+ * Handles a click event on an item.
+ * @private
+ */
+MaterialMenu.prototype.handleItemClick_ = function(evt) {
+ 'use strict';
+
+ if (evt.target.getAttribute('disabled') !== null) {
+ evt.stopPropagation();
+ } else {
+ // Wait some time before closing menu, so the user can see the ripple.
+ this.closing_ = true;
+ window.setTimeout(function(evt) {
+ this.hide();
+ this.closing_ = false;
+ }.bind(this), this.Constant_.CLOSE_TIMEOUT);
+ }
+};
+
+/**
+ * Calculates the initial clip (for opening the menu) or final clip (for closing
+ * it), and applies it. This allows us to animate from or to the correct point,
+ * that is, the point it's aligned to in the "for" element.
+ * @private
+ */
+MaterialMenu.prototype.applyClip_ = function(height, width) {
+ 'use strict';
+
+ if (this.element_.classList.contains(this.CssClasses_.UNALIGNED)) {
+ // Do not clip.
+ this.element_.style.clip = null;
+ } else if (this.element_.classList.contains(this.CssClasses_.BOTTOM_RIGHT)) {
+ // Clip to the top right corner of the menu.
+ this.element_.style.clip =
+ 'rect(0 ' + width + 'px ' + '0 ' + width + 'px)';
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT)) {
+ // Clip to the bottom left corner of the menu.
+ this.element_.style.clip =
+ 'rect(' + height + 'px 0 ' + height + 'px 0)';
+ } else if (this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
+ // Clip to the bottom right corner of the menu.
+ this.element_.style.clip = 'rect(' + height + 'px ' + width + 'px ' +
+ height + 'px ' + width + 'px)';
+ } else {
+ // Default: do not clip (same as clipping to the top left corner).
+ this.element_.style.clip = null;
+ }
+};
+
+/**
+ * Adds an event listener to clean up after the animation ends.
+ * @private
+ */
+MaterialMenu.prototype.addAnimationEndListener_ = function() {
+ 'use strict';
+
+ var cleanup = function() {
+ this.element_.classList.remove(this.CssClasses_.IS_ANIMATING);
+ }.bind(this);
+
+ // Remove animation class once the transition is done.
+ this.element_.addEventListener('transitionend', cleanup);
+ this.element_.addEventListener('webkitTransitionEnd', cleanup);
+};
+
+/**
+ * Displays the menu.
+ * @public
+ */
+MaterialMenu.prototype.show = function(evt) {
+ 'use strict';
+
+ if (this.element_ && this.container_ && this.outline_) {
+ // Measure the inner element.
+ var height = this.element_.getBoundingClientRect().height;
+ var width = this.element_.getBoundingClientRect().width;
+
+ // Apply the inner element's size to the container and outline.
+ this.container_.style.width = width + 'px';
+ this.container_.style.height = height + 'px';
+ this.outline_.style.width = width + 'px';
+ this.outline_.style.height = height + 'px';
+
+ var transitionDuration = this.Constant_.TRANSITION_DURATION_SECONDS *
+ this.Constant_.TRANSITION_DURATION_FRACTION;
+
+ // Calculate transition delays for individual menu items, so that they fade
+ // in one at a time.
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
+ for (var i = 0; i < items.length; i++) {
+ var itemDelay = null;
+ if (this.element_.classList.contains(this.CssClasses_.TOP_LEFT) ||
+ this.element_.classList.contains(this.CssClasses_.TOP_RIGHT)) {
+ itemDelay = ((height - items[i].offsetTop - items[i].offsetHeight) /
+ height * transitionDuration) + 's';
+ } else {
+ itemDelay = (items[i].offsetTop / height * transitionDuration) + 's';
+ }
+ items[i].style.transitionDelay = itemDelay;
+ }
+
+ // Apply the initial clip to the text before we start animating.
+ this.applyClip_(height, width);
+
+ // Wait for the next frame, turn on animation, and apply the final clip.
+ // Also make it visible. This triggers the transitions.
+ window.requestAnimFrame(function() {
+ this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
+ this.element_.style.clip = 'rect(0 ' + width + 'px ' + height + 'px 0)';
+ this.container_.classList.add(this.CssClasses_.IS_VISIBLE);
+ }.bind(this));
+
+ // Clean up after the animation is complete.
+ this.addAnimationEndListener_();
+
+ // Add a click listener to the document, to close the menu.
+ var callback = function(e) {
+ // Check to see if the document is processing the same event that
+ // displayed the menu in the first place. If so, do nothing.
+ // Also check to see if the menu is in the process of closing itself, and
+ // do nothing in that case.
+ if (e !== evt && !this.closing_) {
+ document.removeEventListener('click', callback);
+ this.hide();
+ }
+ }.bind(this);
+ document.addEventListener('click', callback);
+ }
+};
+
+/**
+ * Hides the menu.
+ * @public
+ */
+MaterialMenu.prototype.hide = function(evt) {
+ 'use strict';
+
+ if (this.element_ && this.container_ && this.outline_) {
+ var items = this.element_.querySelectorAll('.' + this.CssClasses_.ITEM);
+
+ // Remove all transition delays; menu items fade out concurrently.
+ for (var i = 0; i < items.length; i++) {
+ items[i].style.transitionDelay = null;
+ }
+
+ // Measure the inner element.
+ var height = this.element_.getBoundingClientRect().height;
+ var width = this.element_.getBoundingClientRect().width;
+
+ // Turn on animation, and apply the final clip. Also make invisible.
+ // This triggers the transitions.
+ this.element_.classList.add(this.CssClasses_.IS_ANIMATING);
+ this.applyClip_(height, width);
+ this.container_.classList.remove(this.CssClasses_.IS_VISIBLE);
+
+ // Clean up after the animation is complete.
+ this.addAnimationEndListener_();
+ }
+};
+
+/**
+ * Displays or hides the menu, depending on current state.
+ * @public
+ */
+MaterialMenu.prototype.toggle = function(evt) {
+ 'use strict';
+
+ if (this.container_.classList.contains(this.CssClasses_.IS_VISIBLE)) {
+ this.hide(evt);
+ } else {
+ this.show(evt);
+ }
+};
+
+// The component registers itself. It can assume componentHandler is available
+// in the global scope.
+componentHandler.register({
+ constructor: MaterialMenu,
+ classAsString: 'MaterialMenu',
+ cssClass: 'wsk-js-menu'
+});
diff --git a/src/styleguide.scss b/src/styleguide.scss
index 65daeadc..ad352cbc 100644
--- a/src/styleguide.scss
+++ b/src/styleguide.scss
@@ -11,11 +11,10 @@
@import "card/card";
@import "checkbox/checkbox";
@import "column-layout/column-layout";
-@import "dropdown-menu/dropdown-menu";
@import "footer/mega_footer";
@import "footer/mini_footer";
@import "icon-toggle/icon-toggle";
-@import "item/item";
+@import "menu/menu";
@import "layout/layout";
@import "list/list";
@import "radio/radio";