diff --git a/demos/menu.html b/demos/menu.html index 184a56d6eef..594cb76190b 100644 --- a/demos/menu.html +++ b/demos/menu.html @@ -215,6 +215,9 @@
+
+ +

Menu Sizes: @@ -265,6 +268,11 @@ menu.rememberSelection = evt.target.checked; }); + var animation = document.querySelector('input[name="animation"]'); + animation.addEventListener('change', function(evt) { + menu.quickOpen = evt.target.checked; + }); + var radios = document.querySelectorAll('input[name="position"]'); var anchor = document.querySelector('.mdc-menu-anchor'); // Initialize to top left. diff --git a/packages/mdc-menu/README.md b/packages/mdc-menu/README.md index 866234a46c0..d4380b438cd 100644 --- a/packages/mdc-menu/README.md +++ b/packages/mdc-menu/README.md @@ -139,6 +139,9 @@ CSS Class | Description // Set Anchor Corner to Bottom End menu.setAnchorCorner(Corner.BOTTOM_END); + + // Turn off menu open animations + menu.quickOpen = false; ``` ### `MDCMenu` @@ -150,6 +153,7 @@ Property | Value Type | Description `open` | Boolean | Proxies to the foundation's `isOpen`/(`open`, `close`) methods. `items` | Array | Proxies to the foundation's container to query for all `.mdc-list-item[role]` elements. `itemsContainer` | Element | Queries the foundation's root element for the `mdc-menu__items` container element. +`quickOpen` | Boolean | Proxies to the foundation's `setQuickOpen()` method. Method Signature | Description --- | --- @@ -201,6 +205,7 @@ Method Signature | Description `open({focusIndex: ?number}) => void` | Opens the menu. Optionally accepts an object with a `focusIndex` parameter to indicate which list item should receive focus when the menu is opened. `close(evt: ?Event)` | Closes the menu. Optionally accepts the event to check if the target is disabled before closing the menu. `isOpen() => boolean` | Returns a boolean indicating whether the menu is open. +`setQuickOpen(quickOpen: boolean) => void` | Sets whether the menu should open and close without animation when the `open`/`close` methods are called. ### Events diff --git a/packages/mdc-menu/foundation.js b/packages/mdc-menu/foundation.js index 4510d6f7124..a80b2daefbe 100644 --- a/packages/mdc-menu/foundation.js +++ b/packages/mdc-menu/foundation.js @@ -145,6 +145,8 @@ class MDCMenuFoundation extends MDCFoundation { this.selectedIndex_ = -1; /** @private {boolean} */ this.rememberSelection_ = false; + /** @private {boolean} */ + this.quickOpen_ = false; // A keyup event on the menu needs to have a corresponding keydown // event on the menu. If the user opens the menu with a keydown event on a @@ -208,6 +210,11 @@ class MDCMenuFoundation extends MDCFoundation { this.setSelectedIndex(-1); } + /** @param {boolean} quickOpen */ + setQuickOpen(quickOpen) { + this.quickOpen_ = quickOpen; + } + /** * @param {?number} focusIndex * @private @@ -555,7 +562,10 @@ class MDCMenuFoundation extends MDCFoundation { */ open({focusIndex = null} = {}) { this.adapter_.saveFocus(); - this.adapter_.addClass(MDCMenuFoundation.cssClasses.ANIMATING_OPEN); + + if (!this.quickOpen_) { + this.adapter_.addClass(MDCMenuFoundation.cssClasses.ANIMATING_OPEN); + } this.animationRequestId_ = requestAnimationFrame(() => { this.dimensions_ = this.adapter_.getInnerDimensions(); @@ -563,10 +573,12 @@ class MDCMenuFoundation extends MDCFoundation { this.adapter_.addClass(MDCMenuFoundation.cssClasses.OPEN); this.focusOnOpen_(focusIndex); this.adapter_.registerBodyClickHandler(this.documentClickHandler_); - this.openAnimationEndTimerId_ = setTimeout(() => { - this.openAnimationEndTimerId_ = 0; - this.adapter_.removeClass(MDCMenuFoundation.cssClasses.ANIMATING_OPEN); - }, numbers.TRANSITION_OPEN_DURATION); + if (!this.quickOpen_) { + this.openAnimationEndTimerId_ = setTimeout(() => { + this.openAnimationEndTimerId_ = 0; + this.adapter_.removeClass(MDCMenuFoundation.cssClasses.ANIMATING_OPEN); + }, numbers.TRANSITION_OPEN_DURATION); + } }); this.isOpen_ = true; } @@ -585,13 +597,19 @@ class MDCMenuFoundation extends MDCFoundation { } this.adapter_.deregisterBodyClickHandler(this.documentClickHandler_); - this.adapter_.addClass(MDCMenuFoundation.cssClasses.ANIMATING_CLOSED); + + if (!this.quickOpen_) { + this.adapter_.addClass(MDCMenuFoundation.cssClasses.ANIMATING_CLOSED); + } + requestAnimationFrame(() => { this.adapter_.removeClass(MDCMenuFoundation.cssClasses.OPEN); - this.closeAnimationEndTimerId_ = setTimeout(() => { - this.closeAnimationEndTimerId_ = 0; - this.adapter_.removeClass(MDCMenuFoundation.cssClasses.ANIMATING_CLOSED); - }, numbers.TRANSITION_CLOSE_DURATION); + if (!this.quickOpen_) { + this.closeAnimationEndTimerId_ = setTimeout(() => { + this.closeAnimationEndTimerId_ = 0; + this.adapter_.removeClass(MDCMenuFoundation.cssClasses.ANIMATING_CLOSED); + }, numbers.TRANSITION_CLOSE_DURATION); + } }); this.isOpen_ = false; this.adapter_.restoreFocus(); diff --git a/packages/mdc-menu/index.js b/packages/mdc-menu/index.js index ee6e507be66..83ac949ffe2 100644 --- a/packages/mdc-menu/index.js +++ b/packages/mdc-menu/index.js @@ -126,6 +126,11 @@ class MDCMenu extends MDCComponent { this.foundation_.setRememberSelection(rememberSelection); } + /** @param {boolean} quickOpen */ + set quickOpen(quickOpen) { + this.foundation_.setQuickOpen(quickOpen); + } + /** @return {!MDCMenuFoundation} */ getDefaultFoundation() { return new MDCMenuFoundation({ diff --git a/test/unit/mdc-menu/mdc-simple-menu.test.js b/test/unit/mdc-menu/mdc-simple-menu.test.js index 545dfacd631..1650636c269 100644 --- a/test/unit/mdc-menu/mdc-simple-menu.test.js +++ b/test/unit/mdc-menu/mdc-simple-menu.test.js @@ -99,6 +99,12 @@ test('rememberSelection', () => { // The method sets private variable on the foundation, nothing to verify. }); +test('setQuickOpen', () => { + const {component} = setupTest(); + component.quickOpen = false; + // The method sets private variable on the foundation, nothing to verify. +}); + test('items returns all menu items', () => { const {root, component} = setupTest(); const items = [].slice.call(root.querySelectorAll('[role="menuitem"]')); diff --git a/test/unit/mdc-menu/simple.foundation.test.js b/test/unit/mdc-menu/simple.foundation.test.js index b25a2dfc35c..459427677e8 100644 --- a/test/unit/mdc-menu/simple.foundation.test.js +++ b/test/unit/mdc-menu/simple.foundation.test.js @@ -116,7 +116,15 @@ test('#init throws error when the necessary DOM is not present', () => { testFoundation('#open adds the animation class to start an animation', ({foundation, mockAdapter}) => { foundation.open(); - td.verify(mockAdapter.addClass(cssClasses.ANIMATING_OPEN)); + td.verify(mockAdapter.addClass(cssClasses.ANIMATING_OPEN), {times: 1}); + }); + +testFoundation('#open does not add the animation class to start an animation when setQuickOpen is false', + ({foundation, mockAdapter}) => { + foundation.setQuickOpen(true); + foundation.open(); + td.verify(mockAdapter.addClass(cssClasses.ANIMATING_OPEN), {times: 0}); + td.verify(mockAdapter.removeClass(cssClasses.ANIMATING_OPEN), {times: 0}); }); testFoundation('#open adds the open class to the menu', ({foundation, mockAdapter, mockRaf}) => { @@ -476,6 +484,13 @@ testFoundation('#close adds the animation class to start an animation', ({founda td.verify(mockAdapter.addClass(cssClasses.ANIMATING_CLOSED)); }); +testFoundation('#close does not add animation class if quickOpen is set to true', ({foundation, mockAdapter}) => { + foundation.setQuickOpen(true); + foundation.close(); + td.verify(mockAdapter.addClass(cssClasses.ANIMATING_CLOSED), {times: 0}); + td.verify(mockAdapter.removeClass(cssClasses.ANIMATING_CLOSED), {times: 0}); +}); + testFoundation('#close removes the open class from the menu', ({foundation, mockAdapter, mockRaf}) => { foundation.close(); mockRaf.flush(); @@ -1229,4 +1244,3 @@ test('getSelectedValue should return the last selected item', () => { assert.isTrue(foundation.getSelectedIndex() === expectedIndex); clock.uninstall(); }); -