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