// 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 horizontal position of the bubble in
* relation to the anchor node.
* @enum
*/
var BubbleAlignment = {
// The bubble is positioned so that the tip of the arrow points to the
// middle of the anchor node.
ARROW_TO_MID_ANCHOR: 'arrow-to-mid-anchor',
// The bubble is positioned so that the edge nearest to the arrow is lined
// up with the edge of the anchor node.
BUBBLE_EDGE_TO_ANCHOR_EDGE: 'bubble-edge-anchor-edge'
};
/**
* The horizontal distance between the tip of the arrow and the start or the
* end of the bubble (as specified by the arrow location).
* @const
*/
var ARROW_OFFSET_X = 30;
/**
* The vertical distance between the tip of the arrow and the bottom or top of
* the bubble (as specified by the arrow location). Note, if you change this
* then you should also change the "top" and "bottom" values for .bubble-arrow
* in bubble.css.
* @const
*/
var ARROW_OFFSET_Y = 8;
/**
* Bubble is a free-floating informational bubble with a triangular arrow
* that points at a place of interest on the page.
*/
var Bubble = cr.ui.define('div');
Bubble.prototype = {
__proto__: HTMLDivElement.prototype,
decorate: function() {
this.className = 'bubble';
this.innerHTML =
'<div class="bubble-contents"></div>' +
'<div class="bubble-close"></div>' +
'<div class="bubble-shadow"></div>' +
'<div class="bubble-arrow"></div>';
this.hidden = true;
this.handleCloseEvent = this.hide;
this.deactivateToDismissDelay_ = 0;
this.bubbleAlignment = BubbleAlignment.ARROW_TO_MID_ANCHOR;
},
/**
* Sets the child node of the bubble.
* @param {node} An HTML element
*/
set content(node) {
var bubbleContent = this.querySelector('.bubble-contents');
bubbleContent.innerHTML = '';
bubbleContent.appendChild(node);
},
/**
* Handles close event which is triggered when the close button
* is clicked. By default is set to this.hide.
* @param {function} A function with no parameters
*/
set handleCloseEvent(func) {
this.handleCloseEvent_ = func;
},
/**
* Sets the anchor node, i.e. the node that this bubble points at.
* @param {HTMLElement} node The new anchor node.
*/
set anchorNode(node) {
this.anchorNode_ = node;
if (!this.hidden)
this.reposition();
},
/**
* Sets the arrow location.
* @param {cr.ui.ArrowLocation} arrowLocation The new arrow location.
*/
setArrowLocation: function(arrowLocation) {
this.isRight_ = arrowLocation == ArrowLocation.TOP_END ||
arrowLocation == ArrowLocation.BOTTOM_END;
if (document.documentElement.dir == 'rtl')
this.isRight_ = !this.isRight_;
this.isTop_ = arrowLocation == ArrowLocation.TOP_START ||
arrowLocation == ArrowLocation.TOP_END;
var bubbleArrow = this.querySelector('.bubble-arrow');
bubbleArrow.setAttribute('is-right', this.isRight_);
bubbleArrow.setAttribute('is-top', this.isTop_);
if (!this.hidden)
this.reposition();
},
/**
* Sets the bubble alignment.
* @param {cr.ui.BubbleAlignment} alignment The new bubble alignment.
*/
set bubbleAlignment(alignment) {
this.bubbleAlignment_ = alignment;
},
/**
* Sets 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 {int} delay The delay in miliseconds.
*/
set deactivateToDismissDelay(delay) {
this.deactivateToDismissDelay_ = delay;
},
/**
* Hides or shows the close button.
* @param {Boolean} isVisible True if the close button should be visible.
*/
setCloseButtonVisible: function(isVisible) {
this.querySelector('.bubble-close').hidden = !isVisible;
},
/**
* Updates the position of the bubble. This is automatically called when
* the window is resized, but should also be called any time the layout
* may have changed.
*/
reposition: function() {
var clientRect = this.anchorNode_.getBoundingClientRect();
var left;
if (this.bubbleAlignment_ ==
BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) {
left = this.isRight_ ? clientRect.right - this.clientWidth :
clientRect.left;
} else {
var anchorMid = (clientRect.left + clientRect.right) / 2;
left = this.isRight_ ? anchorMid - this.clientWidth + ARROW_OFFSET_X :
anchorMid - ARROW_OFFSET_X;
}
var top = this.isTop_ ? clientRect.bottom + ARROW_OFFSET_Y :
clientRect.top - this.clientHeight - ARROW_OFFSET_Y;
this.style.left = left + 'px';
this.style.top = top + 'px';
},
/**
* Starts showing the bubble. The bubble will show until the user clicks
* away or presses Escape.
*/
show: function() {
if (!this.hidden)
return;
document.body.appendChild(this);
this.hidden = false;
this.reposition();
this.showTime_ = Date.now();
this.eventTracker_ = new EventTracker;
this.eventTracker_.add(window, 'resize', this.reposition.bind(this));
var doc = this.ownerDocument;
this.eventTracker_.add(doc, 'keydown', this, true);
this.eventTracker_.add(doc, 'mousedown', this, true);
},
/**
* Hides the bubble from view.
*/
hide: function() {
this.hidden = true;
this.eventTracker_.removeAll();
this.parentNode.removeChild(this);
},
/**
* Handles keydown and mousedown events, dismissing the bubble if
* necessary.
* @param {Event} e The event.
*/
handleEvent: function(e) {
switch (e.type) {
case 'keydown': {
if (e.keyCode == 27) // Esc
this.hide();
break;
}
case 'mousedown': {
if (e.target == this.querySelector('.bubble-close')) {
this.handleCloseEvent_();
} else if (!this.contains(e.target)) {
if (Date.now() - this.showTime_ < this.deactivateToDismissDelay_)
return;
this.hide();
} else {
return;
}
break;
}
}
},
};
return {
ArrowLocation: ArrowLocation,
Bubble: Bubble,
BubbleAlignment: BubbleAlignment
};
});