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

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: address comment Created 3 years, 8 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 * anchorConfigX: (number| undefined),
12 * anchorConfigY: (number| undefined),
13 * minX: (number| undefined),
14 * minY: (number| undefined),
15 * maxX: (number| undefined),
16 * maxY: (number| undefined),
17 * }}
18 */
19 var ShowConfig;
20
5 Polymer({ 21 Polymer({
6 is: 'cr-action-menu', 22 is: 'cr-action-menu',
7 extends: 'dialog', 23 extends: 'dialog',
8 24
9 /** 25 /**
10 * List of all options in this action menu. 26 * List of all options in this action menu.
11 * @private {?NodeList<!Element>} 27 * @private {?NodeList<!Element>}
12 */ 28 */
13 options_: null, 29 options_: null,
14 30
15 /** 31 /**
16 * The element which the action menu will be anchored to. Also the element 32 * The element which the action menu will be anchored to. Also the element
17 * where focus will be returned after the menu is closed. 33 * where focus will be returned after the menu is closed. Only populated if
34 * menu is opened with showAt().
18 * @private {?Element} 35 * @private {?Element}
19 */ 36 */
20 anchorElement_: null, 37 anchorElement_: null,
21 38
22 /** 39 /**
23 * Bound reference to an event listener function such that it can be removed 40 * Bound reference to an event listener function such that it can be removed
24 * on detach. 41 * on detach.
25 * @private {?Function} 42 * @private {?Function}
26 */ 43 */
27 boundClose_: null, 44 boundClose_: null,
(...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 } while (!nextOption && counter < numOptions); 161 } while (!nextOption && counter < numOptions);
145 162
146 return nextOption; 163 return nextOption;
147 }, 164 },
148 165
149 /** @override */ 166 /** @override */
150 close: function() { 167 close: function() {
151 // Removing 'resize' and 'popstate' listeners when dialog is closed. 168 // Removing 'resize' and 'popstate' listeners when dialog is closed.
152 this.removeListeners_(); 169 this.removeListeners_();
153 HTMLDialogElement.prototype.close.call(this); 170 HTMLDialogElement.prototype.close.call(this);
154 this.anchorElement_.focus(); 171 if (this.anchorElement_) {
155 this.anchorElement_ = null; 172 this.anchorElement_.focus();
173 this.anchorElement_ = null;
174 }
156 }, 175 },
157 176
158 /** 177 /**
159 * Shows the menu anchored to the given element. 178 * Shows the menu anchored to the given element.
160 * @param {!Element} anchorElement 179 * @param {!Element} anchorElement
161 */ 180 */
162 showAt: function(anchorElement) { 181 showAt: function(anchorElement) {
163 this.anchorElement_ = anchorElement; 182 this.anchorElement_ = anchorElement;
183 this.anchorElement_.scrollIntoViewIfNeeded();
184 var rect = this.anchorElement_.getBoundingClientRect();
185 this.showAtPosition({
186 top: rect.top,
187 left: rect.left,
188 height: rect.height,
189 width: rect.width,
190 // Default to anchoring towards the left.
191 anchorConfigX: -1,
192 });
193 },
194
195 /**
196 * Shows the menu anchored to the given box. The anchor configuration is
197 * specified as an X and Y alignment which represents a point in the anchor
198 * and the menu that will be aligned where (0, 0) and (1, 1) are the center
199 * and bottom-right of each box respectively.
200 *
201 * For example, having the menu centered around the anchor box would use an
202 * anchor config of (0, 0), whereas aligning the menu to the top-right edge of
203 * the anchor would use a config of (1, -1).
dpapad 2017/04/25 17:42:43 This explanation is much better than before. Can w
calamity 2017/04/26 03:15:50 Done.
204 *
205 * To align the menu outside the given box, use either a point or a line
206 * instead of the full box. e.g aligning a dropdown to center beneath a div
207 * at ((0, 0), (100, 100)) would use ((0, 100), (100, 100)) as the box and
208 * (0, 1) as the anchor, effectively using the bottom edge of the div as the
209 * anchor and centering horizontally beneath that.
210 *
211 * @param {!ShowConfig} config
212 */
213 showAtPosition: function(config) {
214 var c = Object.assign(this.getDefaultShowConfig_(), config);
215
216 var top = c.top;
217 var left = c.left;
218 var bottom = top + c.height;
219 var right = left + c.width;
220
164 this.boundClose_ = this.boundClose_ || function() { 221 this.boundClose_ = this.boundClose_ || function() {
165 if (this.open) 222 if (this.open)
166 this.close(); 223 this.close();
167 }.bind(this); 224 }.bind(this);
168 window.addEventListener('resize', this.boundClose_); 225 window.addEventListener('resize', this.boundClose_);
169 window.addEventListener('popstate', this.boundClose_); 226 window.addEventListener('popstate', this.boundClose_);
170 227
171 // Reset position to prevent previous values from affecting layout. 228 // Reset position to prevent previous values from affecting layout.
172 this.style.left = ''; 229 this.style.left = '';
173 this.style.right = ''; 230 this.style.right = '';
174 this.style.top = ''; 231 this.style.top = '';
175 232
176 this.anchorElement_.scrollIntoViewIfNeeded();
177 this.showModal(); 233 this.showModal();
178 234
179 var rect = this.anchorElement_.getBoundingClientRect(); 235 // Flip the X anchor in RTL.
180 if (getComputedStyle(this.anchorElement_).direction == 'rtl') { 236 var rtl = getComputedStyle(this).direction == 'rtl';
181 var right = window.innerWidth - rect.left - this.offsetWidth; 237 if (rtl)
182 this.style.right = right + 'px'; 238 c.anchorConfigX *= -1;
239
240 var menuLeft = this.getStartPointWithAnchor_(
241 left, right, this.offsetWidth, c.anchorConfigX, c.minX, c.maxX);
242
243 if (rtl) {
244 var menuRight = window.innerWidth - menuLeft - this.offsetWidth;
245 this.style.right = menuRight + 'px';
183 } else { 246 } else {
184 var left = rect.right - this.offsetWidth; 247 this.style.left = menuLeft + 'px';
185 this.style.left = left + 'px';
186 } 248 }
187 249
188 // Attempt to show the menu starting from the top of the rectangle and 250 var menuTop = this.getStartPointWithAnchor_(
189 // extending downwards. If that does not fit within the window, fallback to 251 top, bottom, this.offsetHeight, c.anchorConfigY, c.minY, c.maxY);
190 // starting from the bottom and extending upwards. 252 this.style.top = menuTop + 'px';
191 var top = rect.top + this.offsetHeight <= window.innerHeight ? rect.top : 253 },
192 rect.bottom -
193 this.offsetHeight - Math.max(rect.bottom - window.innerHeight, 0);
194 254
195 this.style.top = top + 'px'; 255 /**
256 * Returns the point to start along the X or Y axis given a start and end
257 * point to anchor to, the length of the target and the direction to anchor
258 * in. If honoring the anchor would force the menu outside of min/max, this
259 * will ignore the anchor position and try to keep the menu within min/max.
260 * @private
261 * @param {number} start
262 * @param {number} end
263 * @param {number} length
264 * @param {number} anchorConfig
265 * @param {number} min
266 * @param {number} max
267 * @return {number}
268 */
269 getStartPointWithAnchor_: function(
270 start, end, length, anchorConfig, min, max) {
271 var startPoint =
272 ((start + end - length) + (start - end + length) * anchorConfig) / 2;
273 if (startPoint + length > max)
274 startPoint = end - length;
275 if (startPoint < min)
276 startPoint = start;
277 return startPoint;
278 },
279
280
281 /**
282 * @private
283 * @return {!ShowConfig}
284 */
285 getDefaultShowConfig_: function() {
286 return {
287 top: 0,
288 left: 0,
289 height: 0,
290 width: 0,
291 anchorConfigX: 1,
292 anchorConfigY: 1,
293 minX: 0,
294 minY: 0,
295 maxX: window.innerWidth,
296 maxY: window.innerHeight,
297 };
196 }, 298 },
197 }); 299 });
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