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

Side by Side 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: Reword comment Created 4 years, 5 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 unified diff | Download patch
OLDNEW
1 // Copyright 2016 The Chromium Authors. All rights reserved. 1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** Same as paper-menu-button's custom easing cubic-bezier param. */ 5 /** Same as paper-menu-button's custom easing cubic-bezier param. */
6 var SLIDE_CUBIC_BEZIER = 'cubic-bezier(0.3, 0.95, 0.5, 1)'; 6 var SLIDE_CUBIC_BEZIER = 'cubic-bezier(0.3, 0.95, 0.5, 1)';
7 var FADE_CUBIC_BEZIER = 'cubic-bezier(0.4, 0, 0.2, 1)';
8 7
9 Polymer({ 8 Polymer({
10 is: 'cr-shared-menu', 9 is: 'cr-shared-menu',
11 10
11 behaviors: [Polymer.IronA11yKeysBehavior],
12
12 properties: { 13 properties: {
13 menuOpen: { 14 menuOpen: {
14 type: Boolean, 15 type: Boolean,
16 observer: 'menuOpenChanged_',
15 value: false, 17 value: false,
16 }, 18 },
17 19
18 /** 20 /**
19 * The contextual item that this menu was clicked for. 21 * The contextual item that this menu was clicked for.
20 * e.g. the data used to render an item in an <iron-list> or <dom-repeat> 22 * e.g. the data used to render an item in an <iron-list> or <dom-repeat>
21 * @type {?Object} 23 * @type {?Object}
22 */ 24 */
23 itemData: { 25 itemData: {
24 type: Object, 26 type: Object,
25 value: null, 27 value: null,
26 }, 28 },
29
Dan Beam 2016/07/07 17:38:24 do all of these need /** @override */?
tsergeant 2016/07/11 03:30:49 keyEventTarget should have one, the others should
30 keyEventTarget: {
31 type: Object,
32 value: function() {
33 return this.$.menu;
34 }
35 },
36
37 openAnimationConfig: {
38 type: Object,
39 value: function() {
40 return [{
41 name: 'fade-in-animation',
42 timing: {
43 delay: 50,
44 duration: 200
45 }
46 }, {
47 name: 'paper-menu-grow-width-animation',
48 timing: {
49 delay: 50,
50 duration: 150,
51 easing: SLIDE_CUBIC_BEZIER
52 }
53 }, {
54 name: 'paper-menu-grow-height-animation',
55 timing: {
56 delay: 100,
57 duration: 275,
58 easing: SLIDE_CUBIC_BEZIER
59 }
60 }];
61 }
62 },
63
64 closeAnimationConfig: {
65 type: Object,
66 value: function() {
67 return [{
68 name: 'fade-out-animation',
69 timing: {
70 duration: 150
71 }
72 }, {
73 name: 'paper-menu-shrink-width-animation',
74 timing: {
75 delay: 100,
76 duration: 50,
77 easing: SLIDE_CUBIC_BEZIER
78 }
79 }, {
80 name: 'paper-menu-shrink-height-animation',
81 timing: {
82 delay: 200,
83 easing: 'ease-in'
84 }
85 }];
86 }
87 }
27 }, 88 },
28 89
29 /** 90 keyBindings: {
30 * Current animation being played, or null if there is none. 91 'tab': 'onTabPressed_',
31 * @type {?Animation} 92 },
32 * @private
33 */
34 animation_: null,
35 93
36 /** 94 /**
37 * The last anchor that was used to open a menu. It's necessary for toggling. 95 * The last anchor that was used to open a menu. It's necessary for toggling.
38 * @type {?Element} 96 * @type {?Element}
97 * @private
39 */ 98 */
40 lastAnchor_: null, 99 lastAnchor_: null,
41 100
42 /** 101 /**
43 * Adds listeners to the window in order to dismiss the menu on resize and 102 * The first focusable child in the menu's light DOM.
44 * when escape is pressed. 103 * @private
104 * @type {?Element}
Dan Beam 2016/07/07 17:38:24 nit: @private {?Element}
tsergeant 2016/07/11 03:30:49 Done.
45 */ 105 */
106 firstFocus_: null,
107
108 /**
109 * The last focusable child in the menu's light DOM.
110 * @private
111 * @type {?Element}
112 */
113 lastFocus_: null,
114
115 /** @override */
46 attached: function() { 116 attached: function() {
47 window.addEventListener('resize', this.closeMenu.bind(this)); 117 window.addEventListener('resize', this.closeMenu.bind(this));
48 window.addEventListener('keydown', function(e) { 118
49 // Escape button on keyboard 119 var focusableChildren = Polymer.dom(this).querySelectorAll(
Dan Beam 2016/07/07 17:38:24 why are you doing this only once on attached? what
50 if (e.keyCode == 27) 120 '[tabindex],button');
Dan Beam 2016/07/07 17:38:24 what if the thing is [disabled] or [hidden]? this
tsergeant 2016/07/11 03:30:49 My hope was to keep this as simple as possible unt
51 this.closeMenu(); 121 if (focusableChildren.length > 0) {
52 }.bind(this)); 122 this.$.dropdown.focusTarget = focusableChildren[0];
123 this.firstFocus_ = focusableChildren[0];
124 this.lastFocus_ = focusableChildren[focusableChildren.length - 1];
125 }
53 }, 126 },
54 127
55 /** Closes the menu. */ 128 /** Closes the menu. */
56 closeMenu: function() { 129 closeMenu: function() {
57 if (!this.menuOpen)
58 return;
59 // If there is a open menu animation going, cancel it and start closing.
60 this.cancelAnimation_();
61 this.menuOpen = false; 130 this.menuOpen = false;
62 this.itemData = null;
63 this.animation_ = this.animateClose_();
64 this.animation_.addEventListener('finish', function() {
65 this.style.display = 'none';
66 // Reset the animation for the next time the menu opens.
67 this.cancelAnimation_();
68 }.bind(this));
69 }, 131 },
70 132
71 /** 133 /**
134 * Close the menu without refocusing the menu button which opened it. Should
135 * be used when the menu action causes another element to be focused.
Dan Beam 2016/07/07 17:38:24 can we combine both closeMenu*() and just restore
tsergeant 2016/07/11 03:30:49 Good idea, Done.
136 */
137 closeMenuNoRefocus: function() {
138 this.$.dropdown.restoreFocusOnClose = false;
139 this.closeMenu();
140 this.$.dropdown.restoreFocusOnClose = true;
141 },
142
143 /**
72 * Opens the menu at the anchor location. 144 * Opens the menu at the anchor location.
73 * @param {!Element} anchor The location to display the menu. 145 * @param {!Element} anchor The location to display the menu.
74 * @param {!Object} itemData The contextual item's data. 146 * @param {!Object} itemData The contextual item's data.
75 */ 147 */
76 openMenu: function(anchor, itemData) { 148 openMenu: function(anchor, itemData) {
77 this.menuOpen = true;
78 this.style.display = 'block';
79 this.itemData = itemData; 149 this.itemData = itemData;
80 this.lastAnchor_ = anchor; 150 this.lastAnchor_ = anchor;
81 151
82 // Move the menu to the anchor. 152 // Move the menu to the anchor.
83 var anchorRect = anchor.getBoundingClientRect(); 153 this.$.dropdown.positionTarget = anchor;
84 var parentRect = this.offsetParent.getBoundingClientRect(); 154 this.menuOpen = true;
85
86 var left = (isRTL() ? anchorRect.left : anchorRect.right) - parentRect.left;
87 var top = anchorRect.top - parentRect.top;
88
89 cr.ui.positionPopupAtPoint(left, top, this, cr.ui.AnchorType.BEFORE);
90
91 // Handle the bottom of the screen.
92 if (this.getBoundingClientRect().top != anchorRect.top) {
93 var bottom = anchorRect.bottom - parentRect.top;
94 cr.ui.positionPopupAtPoint(left, bottom, this, cr.ui.AnchorType.BEFORE);
95 }
96
97 this.$.menu.focus();
98
99 this.cancelAnimation_();
100 this.animation_ = this.animateOpen_();
101 }, 155 },
102 156
103 /** 157 /**
104 * Toggles the menu for the anchor that is passed in. 158 * Toggles the menu for the anchor that is passed in.
105 * @param {!Element} anchor The location to display the menu. 159 * @param {!Element} anchor The location to display the menu.
106 * @param {!Object} itemData The contextual item's data. 160 * @param {!Object} itemData The contextual item's data.
107 */ 161 */
108 toggleMenu: function(anchor, itemData) { 162 toggleMenu: function(anchor, itemData) {
109 // If there is an animation going (e.g. user clicks too fast), cancel it and
110 // start the new action.
111 this.cancelAnimation_();
112 if (anchor == this.lastAnchor_ && this.menuOpen) 163 if (anchor == this.lastAnchor_ && this.menuOpen)
113 this.closeMenu(); 164 this.closeMenu();
114 else 165 else
115 this.openMenu(anchor, itemData); 166 this.openMenu(anchor, itemData);
116 }, 167 },
117 168
118 /** @private */ 169 /**
119 cancelAnimation_: function() { 170 * Trap focus inside the menu. As a very basic heuristic, will wrap focus from
120 if (this.animation_) { 171 * the first element with a nonzero tabindex to the last such element.
121 this.animation_.cancel(); 172 * TODO(tsergeant): Use iron-focus-wrap-behavior once it is available
122 this.animation_ = null; 173 * (https://github.com/PolymerElements/iron-overlay-behavior/issues/179).
174 * @param {CustomEvent} e
175 */
176 onTabPressed_: function(e) {
177 var keyEvent = e.detail.keyboardEvent;
Dan Beam 2016/07/07 17:38:24 declare vars as low as possible
tsergeant 2016/07/11 03:30:48 Done.
178 if (this.firstFocus_ && this.lastFocus_) {
Dan Beam 2016/07/07 17:38:24 arguable nit: just bail if either of these aren't
tsergeant 2016/07/11 03:30:49 Done.
179 if (keyEvent.shiftKey && keyEvent.target == this.firstFocus_) {
180 e.preventDefault();
181 this.lastFocus_.focus();
Dan Beam 2016/07/07 17:38:24 make a variable |toFocus| (or something) and preve
tsergeant 2016/07/11 03:30:49 Done.
182 } else if (keyEvent.target == this.lastFocus_) {
183 e.preventDefault();
184 this.firstFocus_.focus();
185 }
123 } 186 }
Dan Beam 2016/07/07 17:38:24 tl;dr - my suggestions combined if (!this.firstFo
tsergeant 2016/07/11 03:30:49 Done.
124 }, 187 },
125 188
126 /** 189 /**
127 * @param {!Array<!KeyframeEffect>} effects 190 * Ensure the menu is reset properly when it is closed by the dropdown (eg,
128 * @return {!Animation} 191 * clicking outside).
192 * @private
129 */ 193 */
130 playEffects: function(effects) { 194 menuOpenChanged_: function() {
131 /** @type {function(new:Object, !Array<!KeyframeEffect>)} */ 195 if (!this.menuOpen) {
132 window.GroupEffect; 196 this.itemData = null;
133 197 }
Dan Beam 2016/07/07 17:38:24 no curlies
tsergeant 2016/07/11 03:30:49 Done.
134 /** @type {{play: function(Object): !Animation}} */
135 document.timeline;
136
137 return document.timeline.play(new window.GroupEffect(effects));
138 },
139
140 /**
141 * Slide-in animation when opening the menu. The animation configuration is
142 * the same as paper-menu-button except for a shorter delay time.
143 * @private
144 * @return {!Animation}
145 */
146 animateOpen_: function() {
147 var rect = this.getBoundingClientRect();
148 var height = rect.height;
149 var width = rect.width;
150
151 var fadeIn = new KeyframeEffect(/** @type {Animatable} */(this), [{
152 'opacity': '0'
153 }, {
154 'opacity': '1'
155 }], /** @type {!KeyframeEffectOptions} */({
156 delay: 50,
157 duration: 200,
158 easing: FADE_CUBIC_BEZIER,
159 fill: 'both'
160 }));
161
162 var growHeight = new KeyframeEffect(/** @type {Animatable} */(this), [{
163 height: (height / 2) + 'px'
164 }, {
165 height: height + 'px'
166 }], /** @type {!KeyframeEffectOptions} */({
167 delay: 50,
168 duration: 275,
169 easing: SLIDE_CUBIC_BEZIER,
170 fill: 'both'
171 }));
172
173 var growWidth = new KeyframeEffect(/** @type {Animatable} */(this), [{
174 width: (width / 2) + 'px'
175 }, {
176 width: width + 'px'
177 }], /** @type {!KeyframeEffectOptions} */({
178 delay: 50,
179 duration: 150,
180 easing: SLIDE_CUBIC_BEZIER,
181 fill: 'both'
182 }));
183
184 return this.playEffects([fadeIn, growHeight, growWidth]);
185 },
186
187 /**
188 * Slide-out animation when closing the menu. The animation configuration is
189 * the same as paper-menu-button.
190 * @private
191 * @return {!Animation}
192 */
193 animateClose_: function() {
194 var rect = this.getBoundingClientRect();
195 var height = rect.height;
196 var width = rect.width;
197
198 var fadeOut = new KeyframeEffect(/** @type {Animatable} */(this), [{
199 'opacity': '1'
200 }, {
201 'opacity': '0'
202 }], /** @type {!KeyframeEffectOptions} */({
203 duration: 150,
204 easing: FADE_CUBIC_BEZIER,
205 fill: 'both'
206 }));
207
208 var shrinkHeight = new KeyframeEffect(/** @type {Animatable} */(this), [{
209 height: height + 'px',
210 transform: 'translateY(0)'
211 }, {
212 height: height / 2 + 'px',
213 transform: 'translateY(-20px)'
214 }], /** @type {!KeyframeEffectOptions} */({
215 duration: 200,
216 easing: 'ease-in',
217 fill: 'both'
218 }));
219
220 var shrinkWidth = new KeyframeEffect(/** @type {Animatable} */(this), [{
221 width: width + 'px'
222 }, {
223 width: width - (width / 20) + 'px'
224 }], /** @type {!KeyframeEffectOptions} */({
225 delay: 100,
226 duration: 50,
227 easing: SLIDE_CUBIC_BEZIER,
228 fill: 'both'
229 }));
230
231 return this.playEffects([fadeOut, shrinkHeight, shrinkWidth]);
232 }, 198 },
233 }); 199 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698