Home | History | Annotate | Download | only in ui
      1 // Copyright (c) 2010 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 
      9 cr.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   const 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's top
     26      * edge.
     27      */
     28     BEFORE: 1,  // p: right, a: left, p: top, a: top
     29 
     30     /**
     31      * The popop's left edge is aligned with the right edge of the anchor.
     32      * The popup's top edge is aligned with the top edge of the anchor's top
     33      * edge.
     34      */
     35     AFTER: 2,  // p: left a: right, p: top, a: top
     36 
     37     /**
     38      * The popop's bottom edge is aligned with the top edge of the anchor.
     39      * The popup's left edge is aligned with the left edge of the anchor's top
     40      * edge.
     41      */
     42     ABOVE: 3,  // p: bottom, a: top, p: left, a: left
     43 
     44     /**
     45      * The popop's top edge is aligned with the bottom edge of the anchor.
     46      * The popup's left edge is aligned with the left edge of the anchor's top
     47      * edge.
     48      */
     49     BELOW: 4  // p: top, a: bottom, p: left, a: left
     50   };
     51 
     52   /**
     53    * Helper function for positionPopupAroundElement and positionPopupAroundRect.
     54    * @param {!Rect} anchorRect The rect for the anchor.
     55    * @param {!HTMLElement} popupElement The element used for the popup.
     56    * @param {AnchorType} type The type of anchoring to do.
     57    */
     58   function positionPopupAroundRect(anchorRect, popupElement, type) {
     59     var popupRect = popupElement.getBoundingClientRect();
     60     var availRect;
     61     var ownerDoc = popupElement.ownerDocument;
     62     var cs = ownerDoc.defaultView.getComputedStyle(popupElement);
     63     var docElement = ownerDoc.documentElement;
     64 
     65     if (cs.position == 'fixed') {
     66       // For 'fixed' positioned popups, the available rectangle should be based
     67       // on the viewport rather than the document.
     68       availRect = {
     69         height: docElement.clientHeight,
     70         width: docElement.clientWidth,
     71         top: 0,
     72         bottom: docElement.clientHeight,
     73         left: 0,
     74         right: docElement.clientWidth
     75       };
     76     } else {
     77       availRect = popupElement.offsetParent.getBoundingClientRect();
     78     }
     79 
     80     var rtl = cs.direction == 'rtl';
     81 
     82     // Flip BEFORE, AFTER based on RTL.
     83     if (rtl) {
     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 (rtl) {
    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    */
    211   function positionPopupAroundElement(anchorElement, popupElement, type) {
    212     var anchorRect = anchorElement.getBoundingClientRect();
    213     positionPopupAroundRect(anchorRect, popupElement, type);
    214   }
    215 
    216   /**
    217    * Positions a popup around a point.
    218    * @param {number} x The client x position.
    219    * @param {number} y The client y position.
    220    * @param {!HTMLElement} popupElement The popup element we are positioning.
    221    */
    222   function positionPopupAtPoint(x, y, popupElement) {
    223     var rect = {
    224       left: x,
    225       top: y,
    226       width: 0,
    227       height: 0,
    228       right: x,
    229       bottom: y
    230     };
    231     positionPopupAroundRect(rect, popupElement, AnchorType.BELOW);
    232   }
    233 
    234   // Export
    235   return {
    236     AnchorType: AnchorType,
    237     positionPopupAroundElement: positionPopupAroundElement,
    238     positionPopupAtPoint: positionPopupAtPoint
    239   };
    240 });
    241