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