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

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: Nits 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
« no previous file with comments | « ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.html ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
30 /** @override */
31 keyEventTarget: {
32 type: Object,
33 value: function() {
34 return this.$.menu;
35 }
36 },
37
38 openAnimationConfig: {
39 type: Object,
40 value: function() {
41 return [{
42 name: 'fade-in-animation',
43 timing: {
44 delay: 50,
45 duration: 200
46 }
47 }, {
48 name: 'paper-menu-grow-width-animation',
49 timing: {
50 delay: 50,
51 duration: 150,
52 easing: SLIDE_CUBIC_BEZIER
53 }
54 }, {
55 name: 'paper-menu-grow-height-animation',
56 timing: {
57 delay: 100,
58 duration: 275,
59 easing: SLIDE_CUBIC_BEZIER
60 }
61 }];
62 }
63 },
64
65 closeAnimationConfig: {
66 type: Object,
67 value: function() {
68 return [{
69 name: 'fade-out-animation',
70 timing: {
71 duration: 150
72 }
73 }, {
74 name: 'paper-menu-shrink-width-animation',
75 timing: {
76 delay: 100,
77 duration: 50,
78 easing: SLIDE_CUBIC_BEZIER
79 }
80 }, {
81 name: 'paper-menu-shrink-height-animation',
82 timing: {
83 delay: 200,
84 easing: 'ease-in'
85 }
86 }];
87 }
88 }
89 },
90
91 keyBindings: {
92 'tab': 'onTabPressed_',
27 }, 93 },
28 94
29 /** 95 /**
30 * Current animation being played, or null if there is none.
31 * @type {?Animation}
32 * @private
33 */
34 animation_: null,
35
36 /**
37 * The last anchor that was used to open a menu. It's necessary for toggling. 96 * The last anchor that was used to open a menu. It's necessary for toggling.
38 * @type {?Element} 97 * @private {?Element}
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 {?Element}
45 */ 104 */
105 firstFocus_: null,
106
107 /**
108 * The last focusable child in the menu's light DOM.
109 * @private {?Element}
110 */
111 lastFocus_: null,
112
113 /** @override */
46 attached: function() { 114 attached: function() {
47 window.addEventListener('resize', this.closeMenu.bind(this)); 115 window.addEventListener('resize', this.closeMenu.bind(this));
48 window.addEventListener('keydown', function(e) { 116
49 // Escape button on keyboard 117 var focusableChildren = Polymer.dom(this).querySelectorAll(
50 if (e.keyCode == 27) 118 '[tabindex],button');
51 this.closeMenu(); 119 if (focusableChildren.length > 0) {
52 }.bind(this)); 120 this.$.dropdown.focusTarget = focusableChildren[0];
121 this.firstFocus_ = focusableChildren[0];
122 this.lastFocus_ = focusableChildren[focusableChildren.length - 1];
123 }
53 }, 124 },
54 125
55 /** Closes the menu. */ 126 /** Closes the menu. */
56 closeMenu: function() { 127 closeMenu: function() {
57 if (!this.menuOpen) 128 if (this.root.activeElement == null) {
58 return; 129 // Something else has taken focus away from the menu. Do not attempt to
59 // If there is a open menu animation going, cancel it and start closing. 130 // restore focus to the button which opened the menu.
60 this.cancelAnimation_(); 131 this.$.dropdown.restoreFocusOnClose = false;
132 }
61 this.menuOpen = false; 133 this.menuOpen = false;
62 this.itemData = null; 134 this.$.dropdown.restoreFocusOnClose = true;
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 }, 135 },
70 136
71 /** 137 /**
72 * Opens the menu at the anchor location. 138 * Opens the menu at the anchor location.
73 * @param {!Element} anchor The location to display the menu. 139 * @param {!Element} anchor The location to display the menu.
74 * @param {!Object} itemData The contextual item's data. 140 * @param {!Object} itemData The contextual item's data.
75 */ 141 */
76 openMenu: function(anchor, itemData) { 142 openMenu: function(anchor, itemData) {
77 this.menuOpen = true;
78 this.style.display = 'block';
79 this.itemData = itemData; 143 this.itemData = itemData;
80 this.lastAnchor_ = anchor; 144 this.lastAnchor_ = anchor;
81 145
82 // Move the menu to the anchor. 146 // Move the menu to the anchor.
83 var anchorRect = anchor.getBoundingClientRect(); 147 this.$.dropdown.positionTarget = anchor;
84 var parentRect = this.offsetParent.getBoundingClientRect(); 148 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 }, 149 },
102 150
103 /** 151 /**
104 * Toggles the menu for the anchor that is passed in. 152 * Toggles the menu for the anchor that is passed in.
105 * @param {!Element} anchor The location to display the menu. 153 * @param {!Element} anchor The location to display the menu.
106 * @param {!Object} itemData The contextual item's data. 154 * @param {!Object} itemData The contextual item's data.
107 */ 155 */
108 toggleMenu: function(anchor, itemData) { 156 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) 157 if (anchor == this.lastAnchor_ && this.menuOpen)
113 this.closeMenu(); 158 this.closeMenu();
114 else 159 else
115 this.openMenu(anchor, itemData); 160 this.openMenu(anchor, itemData);
116 }, 161 },
117 162
118 /** @private */ 163 /**
119 cancelAnimation_: function() { 164 * Trap focus inside the menu. As a very basic heuristic, will wrap focus from
120 if (this.animation_) { 165 * the first element with a nonzero tabindex to the last such element.
121 this.animation_.cancel(); 166 * TODO(tsergeant): Use iron-focus-wrap-behavior once it is available
122 this.animation_ = null; 167 * (https://github.com/PolymerElements/iron-overlay-behavior/issues/179).
123 } 168 * @param {CustomEvent} e
169 */
170 onTabPressed_: function(e) {
171 if (!this.firstFocus_ || !this.lastFocus_)
172 return;
173
174 var toFocus;
175 var keyEvent = e.detail.keyboardEvent;
176 if (keyEvent.shiftKey && keyEvent.target == this.firstFocus_)
177 toFocus = this.lastFocus_;
178 else if (keyEvent.target == this.lastFocus_)
179 toFocus = this.firstFocus_;
180
181 if (!toFocus)
182 return;
183
184 e.preventDefault();
185 toFocus.focus();
124 }, 186 },
125 187
126 /** 188 /**
127 * @param {!Array<!KeyframeEffect>} effects 189 * Ensure the menu is reset properly when it is closed by the dropdown (eg,
128 * @return {!Animation} 190 * clicking outside).
191 * @private
129 */ 192 */
130 playEffects: function(effects) { 193 menuOpenChanged_: function() {
131 /** @type {function(new:Object, !Array<!KeyframeEffect>)} */ 194 if (!this.menuOpen)
132 window.GroupEffect; 195 this.itemData = null;
133
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 }, 196 },
233 }); 197 });
OLDNEW
« no previous file with comments | « ui/webui/resources/cr_elements/cr_shared_menu/cr_shared_menu.html ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698