Index: chrome/browser/resources/shared/js/cr/ui/bubble.js |
=================================================================== |
--- chrome/browser/resources/shared/js/cr/ui/bubble.js (revision 177292) |
+++ chrome/browser/resources/shared/js/cr/ui/bubble.js (working copy) |
@@ -1,514 +0,0 @@ |
-// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-// require: event_tracker.js |
- |
-cr.define('cr.ui', function() { |
- |
- /** |
- * The arrow location specifies how the arrow and bubble are positioned in |
- * relation to the anchor node. |
- * @enum |
- */ |
- var ArrowLocation = { |
- // The arrow is positioned at the top and the start of the bubble. In left |
- // to right mode this is the top left. The entire bubble is positioned below |
- // the anchor node. |
- TOP_START: 'top-start', |
- // The arrow is positioned at the top and the end of the bubble. In left to |
- // right mode this is the top right. The entire bubble is positioned below |
- // the anchor node. |
- TOP_END: 'top-end', |
- // The arrow is positioned at the bottom and the start of the bubble. In |
- // left to right mode this is the bottom left. The entire bubble is |
- // positioned above the anchor node. |
- BOTTOM_START: 'bottom-start', |
- // The arrow is positioned at the bottom and the end of the bubble. In |
- // left to right mode this is the bottom right. The entire bubble is |
- // positioned above the anchor node. |
- BOTTOM_END: 'bottom-end' |
- }; |
- |
- /** |
- * The bubble alignment specifies the position of the bubble in relation to |
- * the anchor node. |
- * @enum |
- */ |
- var BubbleAlignment = { |
- // The bubble is positioned just above or below the anchor node (as |
- // specified by the arrow location) so that the arrow points at the midpoint |
- // of the anchor. |
- ARROW_TO_MID_ANCHOR: 'arrow-to-mid-anchor', |
- // The bubble is positioned just above or below the anchor node (as |
- // specified by the arrow location) so that its reference edge lines up with |
- // the edge of the anchor. |
- BUBBLE_EDGE_TO_ANCHOR_EDGE: 'bubble-edge-anchor-edge', |
- // The bubble is positioned so that it is entirely within view and does not |
- // obstruct the anchor element, if possible. The specified arrow location is |
- // taken into account as the preferred alignment but may be overruled if |
- // there is insufficient space (see BubbleBase.reposition for the exact |
- // placement algorithm). |
- ENTIRELY_VISIBLE: 'entirely-visible' |
- }; |
- |
- /** |
- * Abstract base class that provides common functionality for implementing |
- * free-floating informational bubbles with a triangular arrow pointing at an |
- * anchor node. |
- */ |
- var BubbleBase = cr.ui.define('div'); |
- |
- /** |
- * The horizontal distance between the tip of the arrow and the reference edge |
- * of the bubble (as specified by the arrow location). In pixels. |
- * @type {number} |
- * @const |
- */ |
- BubbleBase.ARROW_OFFSET = 30; |
- |
- /** |
- * Minimum horizontal spacing between edge of bubble and edge of viewport |
- * (when using the ENTIRELY_VISIBLE alignment). In pixels. |
- * @type {number} |
- * @const |
- */ |
- BubbleBase.MIN_VIEWPORT_EDGE_MARGIN = 2; |
- |
- BubbleBase.prototype = { |
- // Set up the prototype chain. |
- __proto__: HTMLDivElement.prototype, |
- |
- /** |
- * Initialization function for the cr.ui framework. |
- */ |
- decorate: function() { |
- this.className = 'bubble'; |
- this.innerHTML = |
- '<div class="bubble-content"></div>' + |
- '<div class="bubble-shadow"></div>' + |
- '<div class="bubble-arrow"></div>'; |
- this.hidden = true; |
- this.bubbleAlignment = BubbleAlignment.ENTIRELY_VISIBLE; |
- }, |
- |
- /** |
- * Set the anchor node, i.e. the node that this bubble points at. Only |
- * available when the bubble is not being shown. |
- * @param {HTMLElement} node The new anchor node. |
- */ |
- set anchorNode(node) { |
- if (!this.hidden) |
- return; |
- |
- this.anchorNode_ = node; |
- }, |
- |
- /** |
- * Set the conent of the bubble. Only available when the bubble is not being |
- * shown. |
- * @param {HTMLElement} node The root node of the new content. |
- */ |
- set content(node) { |
- if (!this.hidden) |
- return; |
- |
- var bubbleContent = this.querySelector('.bubble-content'); |
- bubbleContent.innerHTML = ''; |
- bubbleContent.appendChild(node); |
- }, |
- |
- /** |
- * Set the arrow location. Only available when the bubble is not being |
- * shown. |
- * @param {cr.ui.ArrowLocation} location The new arrow location. |
- */ |
- set arrowLocation(location) { |
- if (!this.hidden) |
- return; |
- |
- this.arrowAtRight_ = location == ArrowLocation.TOP_END || |
- location == ArrowLocation.BOTTOM_END; |
- if (document.documentElement.dir == 'rtl') |
- this.arrowAtRight_ = !this.arrowAtRight_; |
- this.arrowAtTop_ = location == ArrowLocation.TOP_START || |
- location == ArrowLocation.TOP_END; |
- }, |
- |
- /** |
- * Set the bubble alignment. Only available when the bubble is not being |
- * shown. |
- * @param {cr.ui.BubbleAlignment} alignment The new bubble alignment. |
- */ |
- set bubbleAlignment(alignment) { |
- if (!this.hidden) |
- return; |
- |
- this.bubbleAlignment_ = alignment; |
- }, |
- |
- /** |
- * Update the position of the bubble. Whenever the layout may have changed, |
- * the bubble should either be repositioned by calling this function or |
- * hidden so that it does not point to a nonsensical location on the page. |
- */ |
- reposition: function() { |
- var documentWidth = document.documentElement.clientWidth; |
- var documentHeight = document.documentElement.clientHeight; |
- var anchor = this.anchorNode_.getBoundingClientRect(); |
- var anchorMid = (anchor.left + anchor.right) / 2; |
- var bubble = this.getBoundingClientRect(); |
- var arrow = this.querySelector('.bubble-arrow').getBoundingClientRect(); |
- |
- if (this.bubbleAlignment_ == BubbleAlignment.ENTIRELY_VISIBLE) { |
- // Work out horizontal placement. The bubble is initially positioned so |
- // that the arrow tip points toward the midpoint of the anchor and is |
- // BubbleBase.ARROW_OFFSET pixels from the reference edge and (as |
- // specified by the arrow location). If the bubble is not entirely |
- // within view, it is then shifted, preserving the arrow tip position. |
- var left = this.arrowAtRight_ ? |
- anchorMid + BubbleBase.ARROW_OFFSET - bubble.width : |
- anchorMid - BubbleBase.ARROW_OFFSET; |
- var max_left_pos = |
- documentWidth - bubble.width - BubbleBase.MIN_VIEWPORT_EDGE_MARGIN; |
- var min_left_pos = BubbleBase.MIN_VIEWPORT_EDGE_MARGIN; |
- if (document.documentElement.dir == 'rtl') |
- left = Math.min(Math.max(left, min_left_pos), max_left_pos); |
- else |
- left = Math.max(Math.min(left, max_left_pos), min_left_pos); |
- var arrowTip = Math.min( |
- Math.max(arrow.width / 2, |
- this.arrowAtRight_ ? left + bubble.width - anchorMid : |
- anchorMid - left), |
- bubble.width - arrow.width / 2); |
- |
- // Work out the vertical placement, attempting to fit the bubble |
- // entirely into view. The following placements are considered in |
- // decreasing order of preference: |
- // * Outside the anchor, arrow tip touching the anchor (arrow at |
- // top/bottom as specified by the arrow location). |
- // * Outside the anchor, arrow tip touching the anchor (arrow at |
- // bottom/top, opposite the specified arrow location). |
- // * Outside the anchor, arrow tip overlapping the anchor (arrow at |
- // top/bottom as specified by the arrow location). |
- // * Outside the anchor, arrow tip overlapping the anchor (arrow at |
- // bottom/top, opposite the specified arrow location). |
- // * Overlapping the anchor. |
- var offsetTop = Math.min(documentHeight - anchor.bottom - bubble.height, |
- arrow.height / 2); |
- var offsetBottom = Math.min(anchor.top - bubble.height, |
- arrow.height / 2); |
- if (offsetTop < 0 && offsetBottom < 0) { |
- var top = 0; |
- this.updateArrowPosition_(false, false, arrowTip); |
- } else if (offsetTop > offsetBottom || |
- offsetTop == offsetBottom && this.arrowAtTop_) { |
- var top = anchor.bottom + offsetTop; |
- this.updateArrowPosition_(true, true, arrowTip); |
- } else { |
- var top = anchor.top - bubble.height - offsetBottom; |
- this.updateArrowPosition_(true, false, arrowTip); |
- } |
- } else { |
- if (this.bubbleAlignment_ == |
- BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) { |
- var left = this.arrowAtRight_ ? anchor.right - bubble.width : |
- anchor.left; |
- } else { |
- var left = this.arrowAtRight_ ? |
- anchorMid - this.clientWidth + BubbleBase.ARROW_OFFSET : |
- anchorMid - BubbleBase.ARROW_OFFSET; |
- } |
- var top = this.arrowAtTop_ ? anchor.bottom + arrow.height / 2 : |
- anchor.top - this.clientHeight - arrow.height / 2; |
- this.updateArrowPosition_(true, this.arrowAtTop_, |
- BubbleBase.ARROW_OFFSET); |
- } |
- |
- this.style.left = left + 'px'; |
- this.style.top = top + 'px'; |
- }, |
- |
- /** |
- * Show the bubble. |
- */ |
- show: function() { |
- if (!this.hidden) |
- return; |
- |
- this.attachToDOM_(); |
- this.hidden = false; |
- this.reposition(); |
- |
- var doc = this.ownerDocument; |
- this.eventTracker_ = new EventTracker; |
- this.eventTracker_.add(doc, 'keydown', this, true); |
- this.eventTracker_.add(doc, 'mousedown', this, true); |
- }, |
- |
- /** |
- * Hide the bubble. |
- */ |
- hide: function() { |
- if (this.hidden) |
- return; |
- |
- this.eventTracker_.removeAll(); |
- this.hidden = true; |
- this.parentNode.removeChild(this); |
- }, |
- |
- /** |
- * Handle keyboard events, dismissing the bubble if necessary. |
- * @param {Event} event The event. |
- */ |
- handleEvent: function(event) { |
- // Close the bubble when the user presses <Esc>. |
- if (event.type == 'keydown' && event.keyCode == 27) { |
- this.hide(); |
- event.preventDefault(); |
- event.stopPropagation(); |
- } |
- }, |
- |
- /** |
- * Attach the bubble to the document's DOM. |
- * @private |
- */ |
- attachToDOM_: function() { |
- document.body.appendChild(this); |
- }, |
- |
- /** |
- * Update the arrow so that it appears at the correct position. |
- * @param {boolean} visible Whether the arrow should be visible. |
- * @param {boolean} atTop Whether the arrow should be at the top of the |
- * bubble. |
- * @param {number} tipOffset The horizontal distance between the tip of the |
- * arrow and the reference edge of the bubble (as specified by the arrow |
- * location). |
- * @private |
- */ |
- updateArrowPosition_: function(visible, atTop, tipOffset) { |
- var bubbleArrow = this.querySelector('.bubble-arrow'); |
- bubbleArrow.hidden = !visible; |
- if (!visible) |
- return; |
- |
- var edgeOffset = (-bubbleArrow.clientHeight / 2) + 'px'; |
- bubbleArrow.style.top = atTop ? edgeOffset : 'auto'; |
- bubbleArrow.style.bottom = atTop ? 'auto' : edgeOffset; |
- |
- edgeOffset = (tipOffset - bubbleArrow.offsetWidth / 2) + 'px'; |
- bubbleArrow.style.left = this.arrowAtRight_ ? 'auto' : edgeOffset; |
- bubbleArrow.style.right = this.arrowAtRight_ ? edgeOffset : 'auto'; |
- }, |
- }; |
- |
- /** |
- * A bubble that remains open until the user explicitly dismisses it or clicks |
- * outside the bubble after it has been shown for at least the specified |
- * amount of time (making it less likely that the user will unintentionally |
- * dismiss the bubble). The bubble repositions itself on layout changes. |
- */ |
- var Bubble = cr.ui.define('div'); |
- |
- Bubble.prototype = { |
- // Set up the prototype chain. |
- __proto__: BubbleBase.prototype, |
- |
- /** |
- * Initialization function for the cr.ui framework. |
- */ |
- decorate: function() { |
- BubbleBase.prototype.decorate.call(this); |
- |
- var close = document.createElement('div'); |
- close.className = 'bubble-close'; |
- this.insertBefore(close, this.querySelector('.bubble-content')); |
- |
- this.handleCloseEvent = this.hide; |
- this.deactivateToDismissDelay_ = 0; |
- this.bubbleAlignment = BubbleAlignment.ARROW_TO_MID_ANCHOR; |
- }, |
- |
- /** |
- * Handler for close events triggered when the close button is clicked. By |
- * default, set to this.hide. Only available when the bubble is not being |
- * shown. |
- * @param {function} handler The new handler, a function with no parameters. |
- */ |
- set handleCloseEvent(handler) { |
- if (!this.hidden) |
- return; |
- |
- this.handleCloseEvent_ = handler; |
- }, |
- |
- /** |
- * Set the delay before the user is allowed to click outside the bubble to |
- * dismiss it. Using a delay makes it less likely that the user will |
- * unintentionally dismiss the bubble. |
- * @param {number} delay The delay in milliseconds. |
- */ |
- set deactivateToDismissDelay(delay) { |
- this.deactivateToDismissDelay_ = delay; |
- }, |
- |
- /** |
- * Hide or show the close button. |
- * @param {boolean} isVisible True if the close button should be visible. |
- */ |
- set closeButtonVisible(isVisible) { |
- this.querySelector('.bubble-close').hidden = !isVisible; |
- }, |
- |
- /** |
- * Show the bubble. |
- */ |
- show: function() { |
- if (!this.hidden) |
- return; |
- |
- BubbleBase.prototype.show.call(this); |
- |
- this.showTime_ = Date.now(); |
- this.eventTracker_.add(window, 'resize', this.reposition.bind(this)); |
- }, |
- |
- /** |
- * Handle keyboard and mouse events, dismissing the bubble if necessary. |
- * @param {Event} event The event. |
- */ |
- handleEvent: function(event) { |
- BubbleBase.prototype.handleEvent.call(this, event); |
- |
- if (event.type == 'mousedown') { |
- // Dismiss the bubble when the user clicks on the close button. |
- if (event.target == this.querySelector('.bubble-close')) { |
- this.handleCloseEvent_(); |
- // Dismiss the bubble when the user clicks outside it after the |
- // specified delay has passed. |
- } else if (!this.contains(event.target) && |
- Date.now() - this.showTime_ >= this.deactivateToDismissDelay_) { |
- this.hide(); |
- } |
- } |
- }, |
- }; |
- |
- /** |
- * A bubble that closes automatically when the user clicks or moves the focus |
- * outside the bubble and its target element, scrolls the underlying document |
- * or resizes the window. |
- */ |
- var AutoCloseBubble = cr.ui.define('div'); |
- |
- AutoCloseBubble.prototype = { |
- // Set up the prototype chain. |
- __proto__: BubbleBase.prototype, |
- |
- /** |
- * Initialization function for the cr.ui framework. |
- */ |
- decorate: function() { |
- BubbleBase.prototype.decorate.call(this); |
- this.classList.add('auto-close-bubble'); |
- }, |
- |
- /** |
- * Set the DOM sibling node, i.e. the node as whose sibling the bubble |
- * should join the DOM to ensure that focusable elements inside the bubble |
- * follow the target element in the document's tab order. Only available |
- * when the bubble is not being shown. |
- * @param {HTMLElement} node The new DOM sibling node. |
- */ |
- set domSibling(node) { |
- if (!this.hidden) |
- return; |
- |
- this.domSibling_ = node; |
- }, |
- |
- /** |
- * Show the bubble. |
- */ |
- show: function() { |
- if (!this.hidden) |
- return; |
- |
- BubbleBase.prototype.show.call(this); |
- this.domSibling_.showingBubble = true; |
- |
- var doc = this.ownerDocument; |
- this.eventTracker_.add(doc, 'mousewheel', this, true); |
- this.eventTracker_.add(doc, 'scroll', this, true); |
- this.eventTracker_.add(doc, 'elementFocused', this, true); |
- this.eventTracker_.add(window, 'resize', this); |
- }, |
- |
- /** |
- * Hide the bubble. |
- */ |
- hide: function() { |
- BubbleBase.prototype.hide.call(this); |
- this.domSibling_.showingBubble = false; |
- }, |
- |
- /** |
- * Handle events, closing the bubble when the user clicks or moves the focus |
- * outside the bubble and its target element, scrolls the underlying |
- * document or resizes the window. |
- * @param {Event} event The event. |
- */ |
- handleEvent: function(event) { |
- BubbleBase.prototype.handleEvent.call(this, event); |
- |
- switch (event.type) { |
- // Close the bubble when the user clicks outside it, except if it is a |
- // left-click on the bubble's target element (allowing the target to |
- // handle the event and close the bubble itself). |
- case 'mousedown': |
- if (event.button == 0 && this.anchorNode_.contains(event.target)) |
- break; |
- // Close the bubble when the underlying document is scrolled. |
- case 'mousewheel': |
- case 'scroll': |
- if (this.contains(event.target)) |
- break; |
- // Close the bubble when the window is resized. |
- case 'resize': |
- this.hide(); |
- break; |
- // Close the bubble when the focus moves to an element that is not the |
- // bubble target and is not inside the bubble. |
- case 'elementFocused': |
- if (!this.anchorNode_.contains(event.target) && |
- !this.contains(event.target)) { |
- this.hide(); |
- } |
- break; |
- } |
- }, |
- |
- /** |
- * Attach the bubble to the document's DOM, making it a sibling of the |
- * |domSibling_| so that focusable elements inside the bubble follow the |
- * target element in the document's tab order. |
- * @private |
- */ |
- attachToDOM_: function() { |
- var parent = this.domSibling_.parentNode; |
- parent.insertBefore(this, this.domSibling_.nextSibling); |
- }, |
- }; |
- |
- |
- return { |
- ArrowLocation: ArrowLocation, |
- BubbleAlignment: BubbleAlignment, |
- BubbleBase: BubbleBase, |
- Bubble: Bubble, |
- AutoCloseBubble: AutoCloseBubble |
- }; |
-}); |