• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview This file provides utility functions for position popups.
7 */
8
9cr.define('cr.ui', function() {
10
11  /**
12   * Type def for rects as returned by getBoundingClientRect.
13   * @typedef { {left: number, top: number, width: number, height: number,
14   *             right: number, bottom: number}}
15   */
16  var Rect;
17
18  /**
19   * Enum for defining how to anchor a popup to an anchor element.
20   * @enum {number}
21   */
22  var AnchorType = {
23    /**
24     * The popup's right edge is aligned with the left edge of the anchor.
25     * The popup's top edge is aligned with the top edge of the anchor.
26     */
27    BEFORE: 1,  // p: right, a: left, p: top, a: top
28
29    /**
30     * The popop's left edge is aligned with the right edge of the anchor.
31     * The popup's top edge is aligned with the top edge of the anchor.
32     */
33    AFTER: 2,  // p: left a: right, p: top, a: top
34
35    /**
36     * The popop's bottom edge is aligned with the top edge of the anchor.
37     * The popup's left edge is aligned with the left edge of the anchor.
38     */
39    ABOVE: 3,  // p: bottom, a: top, p: left, a: left
40
41    /**
42     * The popop's top edge is aligned with the bottom edge of the anchor.
43     * The popup's left edge is aligned with the left edge of the anchor.
44     */
45    BELOW: 4  // p: top, a: bottom, p: left, a: left
46  };
47
48  /**
49   * Helper function for positionPopupAroundElement and positionPopupAroundRect.
50   * @param {!Rect} anchorRect The rect for the anchor.
51   * @param {!HTMLElement} popupElement The element used for the popup.
52   * @param {AnchorType} type The type of anchoring to do.
53   * @param {boolean} invertLeftRight Whether to invert the right/left
54   *     alignment.
55   */
56  function positionPopupAroundRect(anchorRect, popupElement, type,
57                                   invertLeftRight) {
58    var popupRect = popupElement.getBoundingClientRect();
59    var availRect;
60    var ownerDoc = popupElement.ownerDocument;
61    var cs = ownerDoc.defaultView.getComputedStyle(popupElement);
62    var docElement = ownerDoc.documentElement;
63
64    if (cs.position == 'fixed') {
65      // For 'fixed' positioned popups, the available rectangle should be based
66      // on the viewport rather than the document.
67      availRect = {
68        height: docElement.clientHeight,
69        width: docElement.clientWidth,
70        top: 0,
71        bottom: docElement.clientHeight,
72        left: 0,
73        right: docElement.clientWidth
74      };
75    } else {
76      availRect = popupElement.offsetParent.getBoundingClientRect();
77    }
78
79    if (cs.direction == 'rtl')
80      invertLeftRight = !invertLeftRight;
81
82    // Flip BEFORE, AFTER based on alignment.
83    if (invertLeftRight) {
84      if (type == AnchorType.BEFORE)
85        type = AnchorType.AFTER;
86      else if (type == AnchorType.AFTER)
87        type = AnchorType.BEFORE;
88    }
89
90    // Flip type based on available size
91    switch (type) {
92      case AnchorType.BELOW:
93        if (anchorRect.bottom + popupRect.height > availRect.height &&
94            popupRect.height <= anchorRect.top) {
95          type = AnchorType.ABOVE;
96        }
97        break;
98      case AnchorType.ABOVE:
99        if (popupRect.height > anchorRect.top &&
100            anchorRect.bottom + popupRect.height <= availRect.height) {
101          type = AnchorType.BELOW;
102        }
103        break;
104      case AnchorType.AFTER:
105        if (anchorRect.right + popupRect.width > availRect.width &&
106            popupRect.width <= anchorRect.left) {
107          type = AnchorType.BEFORE;
108        }
109        break;
110      case AnchorType.BEFORE:
111        if (popupRect.width > anchorRect.left &&
112            anchorRect.right + popupRect.width <= availRect.width) {
113          type = AnchorType.AFTER;
114        }
115        break;
116    }
117    // flipping done
118
119    var style = popupElement.style;
120    // Reset all directions.
121    style.left = style.right = style.top = style.bottom = 'auto';
122
123    // Primary direction
124    switch (type) {
125      case AnchorType.BELOW:
126        if (anchorRect.bottom + popupRect.height <= availRect.height)
127          style.top = anchorRect.bottom + 'px';
128        else
129          style.bottom = '0';
130        break;
131      case AnchorType.ABOVE:
132        if (availRect.height - anchorRect.top >= 0)
133          style.bottom = availRect.height - anchorRect.top + 'px';
134        else
135          style.top = '0';
136        break;
137      case AnchorType.AFTER:
138        if (anchorRect.right + popupRect.width <= availRect.width)
139          style.left = anchorRect.right + 'px';
140        else
141          style.right = '0';
142        break;
143      case AnchorType.BEFORE:
144        if (availRect.width - anchorRect.left >= 0)
145          style.right = availRect.width - anchorRect.left + 'px';
146        else
147          style.left = '0';
148        break;
149    }
150
151    // Secondary direction
152    switch (type) {
153      case AnchorType.BELOW:
154      case AnchorType.ABOVE:
155        if (invertLeftRight) {
156          // align right edges
157          if (anchorRect.right - popupRect.width >= 0) {
158            style.right = availRect.width - anchorRect.right + 'px';
159
160          // align left edges
161          } else if (anchorRect.left + popupRect.width <= availRect.width) {
162            style.left = anchorRect.left + 'px';
163
164          // not enough room on either side
165          } else {
166            style.right = '0';
167          }
168        } else {
169          // align left edges
170          if (anchorRect.left + popupRect.width <= availRect.width) {
171            style.left = anchorRect.left + 'px';
172
173          // align right edges
174          } else if (anchorRect.right - popupRect.width >= 0) {
175            style.right = availRect.width - anchorRect.right + 'px';
176
177          // not enough room on either side
178          } else {
179            style.left = '0';
180          }
181        }
182        break;
183
184      case AnchorType.AFTER:
185      case AnchorType.BEFORE:
186        // align top edges
187        if (anchorRect.top + popupRect.height <= availRect.height) {
188          style.top = anchorRect.top + 'px';
189
190        // align bottom edges
191        } else if (anchorRect.bottom - popupRect.height >= 0) {
192          style.bottom = availRect.height - anchorRect.bottom + 'px';
193
194          // not enough room on either side
195        } else {
196          style.top = '0';
197        }
198        break;
199    }
200  }
201
202  /**
203   * Positions a popup element relative to an anchor element. The popup element
204   * should have position set to absolute and it should be a child of the body
205   * element.
206   * @param {!HTMLElement} anchorElement The element that the popup is anchored
207   *     to.
208   * @param {!HTMLElement} popupElement The popup element we are positioning.
209   * @param {AnchorType} type The type of anchoring we want.
210   * @param {boolean} invertLeftRight Whether to invert the right/left
211   *     alignment.
212   */
213  function positionPopupAroundElement(anchorElement, popupElement, type,
214                                      invertLeftRight) {
215    var anchorRect = anchorElement.getBoundingClientRect();
216    positionPopupAroundRect(anchorRect, popupElement, type, invertLeftRight);
217  }
218
219  /**
220   * Positions a popup around a point.
221   * @param {number} x The client x position.
222   * @param {number} y The client y position.
223   * @param {!HTMLElement} popupElement The popup element we are positioning.
224   */
225  function positionPopupAtPoint(x, y, popupElement) {
226    var rect = {
227      left: x,
228      top: y,
229      width: 0,
230      height: 0,
231      right: x,
232      bottom: y
233    };
234    positionPopupAroundRect(rect, popupElement, AnchorType.BELOW);
235  }
236
237  // Export
238  return {
239    AnchorType: AnchorType,
240    positionPopupAroundElement: positionPopupAroundElement,
241    positionPopupAtPoint: positionPopupAtPoint
242  };
243});
244