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