Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1781)

Unified Diff: ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js

Issue 2104013004: MD WebUI: Reimplement cr-shared-menu using iron-dropdown. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Cleanup Created 4 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js
diff --git a/ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js b/ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js
index ab746bdf9f4df1b0af4a6f2f285f95b5f4d39b22..78fb44d51692db764d659d575dc19441dba8a328 100644
--- a/ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js
+++ b/ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.js
@@ -4,14 +4,16 @@
/** Same as paper-menu-button's custom easing cubic-bezier param. */
var SLIDE_CUBIC_BEZIER = 'cubic-bezier(0.3, 0.95, 0.5, 1)';
-var FADE_CUBIC_BEZIER = 'cubic-bezier(0.4, 0, 0.2, 1)';
Polymer({
is: 'cr-shared-menu',
+ behaviors: [Polymer.IronA11yKeysBehavior],
+
properties: {
menuOpen: {
type: Boolean,
+ observer: 'menuOpenChanged_',
value: false,
},
@@ -24,48 +26,118 @@ Polymer({
type: Object,
value: null,
},
+
+ keyEventTarget: {
+ type: Object,
+ value: function() {
+ return this.$.menu;
+ }
+ },
+
+ openAnimationConfig: {
+ type: Object,
+ value: function() {
+ return [{
+ name: 'fade-in-animation',
+ timing: {
+ delay: 50,
+ duration: 200
+ }
+ }, {
+ name: 'paper-menu-grow-width-animation',
+ timing: {
+ delay: 50,
+ duration: 150,
+ easing: SLIDE_CUBIC_BEZIER
+ }
+ }, {
+ name: 'paper-menu-grow-height-animation',
+ timing: {
+ delay: 100,
+ duration: 275,
+ easing: SLIDE_CUBIC_BEZIER
+ }
+ }];
+ }
+ },
+
+ closeAnimationConfig: {
+ type: Object,
+ value: function() {
+ return [{
+ name: 'fade-out-animation',
+ timing: {
+ duration: 150
+ }
+ }, {
+ name: 'paper-menu-shrink-width-animation',
+ timing: {
+ delay: 100,
+ duration: 50,
+ easing: SLIDE_CUBIC_BEZIER
+ }
+ }, {
+ name: 'paper-menu-shrink-height-animation',
+ timing: {
+ delay: 200,
+ easing: 'ease-in'
+ }
+ }];
+ }
+ }
+ },
+
+ keyBindings: {
+ 'tab': 'onTabPressed_',
},
/**
- * Current animation being played, or null if there is none.
- * @type {?Animation}
+ * The last anchor that was used to open a menu. It's necessary for toggling.
+ * @type {?Element}
* @private
*/
- animation_: null,
+ lastAnchor_: null,
/**
- * The last anchor that was used to open a menu. It's necessary for toggling.
+ * The first focusable child in the menu's light DOM.
+ * @private
* @type {?Element}
*/
- lastAnchor_: null,
+ firstFocus_: null,
/**
- * Adds listeners to the window in order to dismiss the menu on resize and
- * when escape is pressed.
+ * The last focusable child in the menu's light DOM.
+ * @private
+ * @type {?Element}
*/
+ lastFocus_: null,
+
+ /** @override */
attached: function() {
window.addEventListener('resize', this.closeMenu.bind(this));
- window.addEventListener('keydown', function(e) {
- // Escape button on keyboard
- if (e.keyCode == 27)
- this.closeMenu();
- }.bind(this));
+
+ var focusableChildren = Polymer.dom(this).querySelectorAll(
+ '[tabindex],button');
+ if (focusableChildren.length > 0) {
+ this.$.dropdown.focusTarget = focusableChildren[0];
+ this.firstFocus_ = focusableChildren[0];
+ this.lastFocus_ = focusableChildren[focusableChildren.length - 1];
calamity 2016/07/06 06:49:15 Do you need to recompute this if new elements are
tsergeant 2016/07/06 07:16:29 Ideally, yes. However, I'd like to keep this as si
+ }
},
/** Closes the menu. */
closeMenu: function() {
- if (!this.menuOpen)
- return;
- // If there is a open menu animation going, cancel it and start closing.
- this.cancelAnimation_();
this.menuOpen = false;
- this.itemData = null;
- this.animation_ = this.animateClose_();
- this.animation_.addEventListener('finish', function() {
- this.style.display = 'none';
- // Reset the animation for the next time the menu opens.
- this.cancelAnimation_();
- }.bind(this));
+ },
+
+ /**
+ * Close the menu without refocusing the menu button which opened it. Should
+ * be used when the menu action causes another element to be focused.
+ */
+ closeMenuNoRefocus: function() {
+ this.$.dropdown.restoreFocusOnClose = false;
+ this.closeMenu();
+ this.$.dropdown.restoreFocusOnClose = true;
},
/**
@@ -74,30 +146,12 @@ Polymer({
* @param {!Object} itemData The contextual item's data.
*/
openMenu: function(anchor, itemData) {
- this.menuOpen = true;
- this.style.display = 'block';
this.itemData = itemData;
this.lastAnchor_ = anchor;
// Move the menu to the anchor.
- var anchorRect = anchor.getBoundingClientRect();
- var parentRect = this.offsetParent.getBoundingClientRect();
-
- var left = (isRTL() ? anchorRect.left : anchorRect.right) - parentRect.left;
- var top = anchorRect.top - parentRect.top;
-
- cr.ui.positionPopupAtPoint(left, top, this, cr.ui.AnchorType.BEFORE);
-
- // Handle the bottom of the screen.
- if (this.getBoundingClientRect().top != anchorRect.top) {
- var bottom = anchorRect.bottom - parentRect.top;
- cr.ui.positionPopupAtPoint(left, bottom, this, cr.ui.AnchorType.BEFORE);
- }
-
- this.$.menu.focus();
-
- this.cancelAnimation_();
- this.animation_ = this.animateOpen_();
+ this.$.dropdown.positionTarget = anchor;
+ this.menuOpen = true;
},
/**
@@ -106,128 +160,40 @@ Polymer({
* @param {!Object} itemData The contextual item's data.
*/
toggleMenu: function(anchor, itemData) {
- // If there is an animation going (e.g. user clicks too fast), cancel it and
- // start the new action.
- this.cancelAnimation_();
if (anchor == this.lastAnchor_ && this.menuOpen)
this.closeMenu();
else
this.openMenu(anchor, itemData);
},
- /** @private */
- cancelAnimation_: function() {
- if (this.animation_) {
- this.animation_.cancel();
- this.animation_ = null;
- }
- },
-
/**
- * @param {!Array<!KeyframeEffect>} effects
- * @return {!Animation}
+ * Trap focus inside the menu. As a very basic heuristic, will wrap focus from
+ * the first element with a nonzero tabindex to the last such element.
+ * TODO(tsergeant): Use iron-focus-wrap-behavior once it is available
+ * (https://github.com/PolymerElements/iron-overlay-behavior/issues/179).
+ * @param {CustomEvent} e
*/
- playEffects: function(effects) {
- /** @type {function(new:Object, !Array<!KeyframeEffect>)} */
- window.GroupEffect;
-
- /** @type {{play: function(Object): !Animation}} */
- document.timeline;
-
- return document.timeline.play(new window.GroupEffect(effects));
- },
-
- /**
- * Slide-in animation when opening the menu. The animation configuration is
- * the same as paper-menu-button except for a shorter delay time.
- * @private
- * @return {!Animation}
- */
- animateOpen_: function() {
- var rect = this.getBoundingClientRect();
- var height = rect.height;
- var width = rect.width;
-
- var fadeIn = new KeyframeEffect(/** @type {Animatable} */(this), [{
- 'opacity': '0'
- }, {
- 'opacity': '1'
- }], /** @type {!KeyframeEffectOptions} */({
- delay: 50,
- duration: 200,
- easing: FADE_CUBIC_BEZIER,
- fill: 'both'
- }));
-
- var growHeight = new KeyframeEffect(/** @type {Animatable} */(this), [{
- height: (height / 2) + 'px'
- }, {
- height: height + 'px'
- }], /** @type {!KeyframeEffectOptions} */({
- delay: 50,
- duration: 275,
- easing: SLIDE_CUBIC_BEZIER,
- fill: 'both'
- }));
-
- var growWidth = new KeyframeEffect(/** @type {Animatable} */(this), [{
- width: (width / 2) + 'px'
- }, {
- width: width + 'px'
- }], /** @type {!KeyframeEffectOptions} */({
- delay: 50,
- duration: 150,
- easing: SLIDE_CUBIC_BEZIER,
- fill: 'both'
- }));
-
- return this.playEffects([fadeIn, growHeight, growWidth]);
+ onTabPressed_: function(e) {
+ var keyEvent = e.detail.keyboardEvent;
+ if (this.firstFocus_ && this.lastFocus_) {
+ if (keyEvent.shiftKey && keyEvent.target == this.firstFocus_) {
+ e.preventDefault();
+ this.lastFocus_.focus();
+ } else if (keyEvent.target == this.lastFocus_) {
+ e.preventDefault();
+ this.firstFocus_.focus();
+ }
+ }
},
/**
- * Slide-out animation when closing the menu. The animation configuration is
- * the same as paper-menu-button.
+ * Ensure the menu is reset properly when it is closed by the dropdown (eg,
+ * clicking outside).
* @private
- * @return {!Animation}
*/
- animateClose_: function() {
- var rect = this.getBoundingClientRect();
- var height = rect.height;
- var width = rect.width;
-
- var fadeOut = new KeyframeEffect(/** @type {Animatable} */(this), [{
- 'opacity': '1'
- }, {
- 'opacity': '0'
- }], /** @type {!KeyframeEffectOptions} */({
- duration: 150,
- easing: FADE_CUBIC_BEZIER,
- fill: 'both'
- }));
-
- var shrinkHeight = new KeyframeEffect(/** @type {Animatable} */(this), [{
- height: height + 'px',
- transform: 'translateY(0)'
- }, {
- height: height / 2 + 'px',
- transform: 'translateY(-20px)'
- }], /** @type {!KeyframeEffectOptions} */({
- duration: 200,
- easing: 'ease-in',
- fill: 'both'
- }));
-
- var shrinkWidth = new KeyframeEffect(/** @type {Animatable} */(this), [{
- width: width + 'px'
- }, {
- width: width - (width / 20) + 'px'
- }], /** @type {!KeyframeEffectOptions} */({
- delay: 100,
- duration: 50,
- easing: SLIDE_CUBIC_BEZIER,
- fill: 'both'
- }));
-
- return this.playEffects([fadeOut, shrinkHeight, shrinkWidth]);
+ menuOpenChanged_: function() {
+ if (!this.menuOpen) {
+ this.itemData = null;
+ }
},
});

Powered by Google App Engine
This is Rietveld 408576698