OLD | NEW |
---|---|
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 Loading... | |
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 }); |
OLD | NEW |