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