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

Side by Side Diff: ui/webui/resources/cr_elements/cr_action_menu/cr_action_menu.js

Issue 2814743007: [cr-action-menu] Allow configurable anchors. (Closed)
Patch Set: use AnchorAlignment Created 3 years, 7 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 | « chrome/test/data/webui/cr_elements/cr_action_menu_test.js ('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 /**
6 * @typedef {{
7 * top: number,
8 * left: number,
9 * width: (number| undefined),
10 * height: (number| undefined),
11 * anchorAlignmentX: (number| undefined),
12 * anchorAlignmentY: (number| undefined),
13 * minX: (number| undefined),
14 * minY: (number| undefined),
15 * maxX: (number| undefined),
16 * maxY: (number| undefined),
17 * }}
18 */
19 var ShowConfig;
20
21 /**
22 * @enum
dpapad 2017/04/27 01:30:23 @enum {number}
calamity 2017/04/27 03:16:24 Done.
23 * @const
24 */
25 var AnchorAlignment = {
26 BEFORE_START: -2,
27 AFTER_START: -1,
28 CENTER: 0,
29 BEFORE_END: 1,
30 AFTER_END: 2,
31 };
32
5 Polymer({ 33 Polymer({
6 is: 'cr-action-menu', 34 is: 'cr-action-menu',
7 extends: 'dialog', 35 extends: 'dialog',
8 36
9 /** 37 /**
10 * List of all options in this action menu. 38 * List of all options in this action menu.
11 * @private {?NodeList<!Element>} 39 * @private {?NodeList<!Element>}
12 */ 40 */
13 options_: null, 41 options_: null,
14 42
15 /** 43 /**
16 * The element which the action menu will be anchored to. Also the element 44 * The element which the action menu will be anchored to. Also the element
17 * where focus will be returned after the menu is closed. 45 * where focus will be returned after the menu is closed. Only populated if
46 * menu is opened with showAt().
18 * @private {?Element} 47 * @private {?Element}
19 */ 48 */
20 anchorElement_: null, 49 anchorElement_: null,
21 50
22 /** 51 /**
23 * Bound reference to an event listener function such that it can be removed 52 * Bound reference to an event listener function such that it can be removed
24 * on detach. 53 * on detach.
25 * @private {?Function} 54 * @private {?Function}
26 */ 55 */
27 boundClose_: null, 56 boundClose_: null,
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 } while (!nextOption && counter < numOptions); 173 } while (!nextOption && counter < numOptions);
145 174
146 return nextOption; 175 return nextOption;
147 }, 176 },
148 177
149 /** @override */ 178 /** @override */
150 close: function() { 179 close: function() {
151 // Removing 'resize' and 'popstate' listeners when dialog is closed. 180 // Removing 'resize' and 'popstate' listeners when dialog is closed.
152 this.removeListeners_(); 181 this.removeListeners_();
153 HTMLDialogElement.prototype.close.call(this); 182 HTMLDialogElement.prototype.close.call(this);
154 this.anchorElement_.focus(); 183 if (this.anchorElement_) {
155 this.anchorElement_ = null; 184 this.anchorElement_.focus();
185 this.anchorElement_ = null;
186 }
156 }, 187 },
157 188
158 /** 189 /**
159 * Shows the menu anchored to the given element. 190 * Shows the menu anchored to the given element.
160 * @param {!Element} anchorElement 191 * @param {!Element} anchorElement
161 */ 192 */
162 showAt: function(anchorElement) { 193 showAt: function(anchorElement) {
163 this.anchorElement_ = anchorElement; 194 this.anchorElement_ = anchorElement;
195 this.anchorElement_.scrollIntoViewIfNeeded();
196 var rect = this.anchorElement_.getBoundingClientRect();
197 this.showAtPosition({
198 top: rect.top,
199 left: rect.left,
200 height: rect.height,
201 width: rect.width,
202 // Default to anchoring towards the left.
203 anchorAlignmentX: -1,
dpapad 2017/04/27 01:30:23 Should we be using the AnchorAlignment enum now th
calamity 2017/04/27 03:16:24 Done.
204 });
205 },
206
207 /**
208 * Shows the menu anchored to the given box. The anchor alignment is
209 * specified as an X and Y alignment which represents a point in the anchor
210 * where the menu will align to, which can have the menu either before or
211 * after the given point in each axis. Center alignment places the center of
212 * the menu in line with the center of the anchor.
213 *
214 * y-start
215 * _____________
216 * | |
217 * | |
218 * | CENTER |
219 * x-start | x | x-end
220 * | |
221 * |anchor box |
222 * |___________|
223 *
224 * y-end
225 *
226 * For example, aligning the menu to the inside of the top-right edge of
227 * the anchor, extending towards the bottom-left would use a alignment of
228 * (BEFORE_END, AFTER_START), whereas centering the menu below the bottom edge
229 * of the anchor would use (CENTER, AFTER_END).
230 *
231 * @param {!ShowConfig} config
232 */
233 showAtPosition: function(config) {
234 var c = Object.assign(this.getDefaultShowConfig_(), config);
235
236 var top = c.top;
237 var left = c.left;
238 var bottom = top + c.height;
239 var right = left + c.width;
240
164 this.boundClose_ = this.boundClose_ || function() { 241 this.boundClose_ = this.boundClose_ || function() {
165 if (this.open) 242 if (this.open)
166 this.close(); 243 this.close();
167 }.bind(this); 244 }.bind(this);
168 window.addEventListener('resize', this.boundClose_); 245 window.addEventListener('resize', this.boundClose_);
169 window.addEventListener('popstate', this.boundClose_); 246 window.addEventListener('popstate', this.boundClose_);
170 247
171 // Reset position to prevent previous values from affecting layout. 248 // Reset position to prevent previous values from affecting layout.
172 this.style.left = ''; 249 this.style.left = '';
173 this.style.right = ''; 250 this.style.right = '';
174 this.style.top = ''; 251 this.style.top = '';
175 252
176 this.anchorElement_.scrollIntoViewIfNeeded();
177 this.showModal(); 253 this.showModal();
178 254
179 var rect = this.anchorElement_.getBoundingClientRect(); 255 // Flip the X anchor in RTL.
180 if (getComputedStyle(this.anchorElement_).direction == 'rtl') { 256 var rtl = getComputedStyle(this).direction == 'rtl';
181 var right = window.innerWidth - rect.left - this.offsetWidth; 257 if (rtl)
182 this.style.right = right + 'px'; 258 c.anchorAlignmentX *= -1;
259
260 var menuLeft = this.getStartPointWithAnchor_(
261 left, right, this.offsetWidth, c.anchorAlignmentX, c.minX, c.maxX);
262
263 if (rtl) {
264 var menuRight = window.innerWidth - menuLeft - this.offsetWidth;
265 this.style.right = menuRight + 'px';
183 } else { 266 } else {
184 var left = rect.right - this.offsetWidth; 267 this.style.left = menuLeft + 'px';
185 this.style.left = left + 'px';
186 } 268 }
187 269
188 // Attempt to show the menu starting from the top of the rectangle and 270 var menuTop = this.getStartPointWithAnchor_(
189 // extending downwards. If that does not fit within the window, fallback to 271 top, bottom, this.offsetHeight, c.anchorAlignmentY, c.minY, c.maxY);
190 // starting from the bottom and extending upwards. 272 this.style.top = menuTop + 'px';
191 var top = rect.top + this.offsetHeight <= window.innerHeight ? rect.top : 273 },
192 rect.bottom -
193 this.offsetHeight - Math.max(rect.bottom - window.innerHeight, 0);
194 274
195 this.style.top = top + 'px'; 275 /**
276 * Returns the point to start along the X or Y axis given a start and end
277 * point to anchor to, the length of the target and the direction to anchor
278 * in. If honoring the anchor would force the menu outside of min/max, this
279 * will ignore the anchor position and try to keep the menu within min/max.
280 * @private
281 * @param {number} start
282 * @param {number} end
283 * @param {number} length
284 * @param {AnchorAlignment} anchorAlignment
285 * @param {number} min
286 * @param {number} max
287 * @return {number}
288 */
289 getStartPointWithAnchor_: function(
290 start, end, length, anchorAlignment, min, max) {
291 var startPoint = 0;
292 switch (anchorAlignment) {
293 case AnchorAlignment.BEFORE_START:
294 startPoint = -length;
295 break;
296 case AnchorAlignment.AFTER_START:
297 startPoint = start;
298 break;
299 case AnchorAlignment.CENTER:
300 startPoint = (start + end - length) / 2;
301 break;
302 case AnchorAlignment.BEFORE_END:
303 startPoint = end - length;
304 break;
305 case AnchorAlignment.AFTER_END:
306 startPoint = end;
307 break;
308 }
309
310 if (startPoint + length > max)
311 startPoint = end - length;
312 if (startPoint < min)
313 startPoint = start;
314 return startPoint;
315 },
316
317
318 /**
319 * @private
320 * @return {!ShowConfig}
321 */
322 getDefaultShowConfig_: function() {
dpapad 2017/04/27 01:30:23 This method (and also getStartPointWithAnchor_ abo
calamity 2017/04/27 03:16:24 Done.
323 return {
324 top: 0,
325 left: 0,
326 height: 0,
327 width: 0,
328 anchorAlignmentX: AnchorAlignment.AFTER_START,
329 anchorAlignmentY: AnchorAlignment.AFTER_START,
330 minX: 0,
331 minY: 0,
332 maxX: window.innerWidth,
333 maxY: window.innerHeight,
334 };
196 }, 335 },
197 }); 336 });
OLDNEW
« no previous file with comments | « chrome/test/data/webui/cr_elements/cr_action_menu_test.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698