diff options
Diffstat (limited to 'chromium/chrome/browser/resources/login/bubble.js')
-rw-r--r-- | chromium/chrome/browser/resources/login/bubble.js | 359 |
1 files changed, 359 insertions, 0 deletions
diff --git a/chromium/chrome/browser/resources/login/bubble.js b/chromium/chrome/browser/resources/login/bubble.js new file mode 100644 index 00000000000..92bf987d51d --- /dev/null +++ b/chromium/chrome/browser/resources/login/bubble.js @@ -0,0 +1,359 @@ +// Copyright 2014 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. + +/** + * @fileoverview Bubble implementation. + */ + +// TODO(xiyuan): Move this into shared. +cr.define('cr.ui', function() { + /** + * Creates a bubble div. + * @constructor + * @extends {HTMLDivElement} + */ + var Bubble = cr.ui.define('div'); + + /** + * Bubble key codes. + * @enum {number} + */ + var KeyCodes = { + TAB: 9, + ENTER: 13, + ESC: 27, + SPACE: 32 + }; + + /** + * Bubble attachment side. + * @enum {string} + */ + Bubble.Attachment = { + RIGHT: 'bubble-right', + LEFT: 'bubble-left', + TOP: 'bubble-top', + BOTTOM: 'bubble-bottom' + }; + + Bubble.prototype = { + __proto__: HTMLDivElement.prototype, + + // Anchor element for this bubble. + anchor_: undefined, + + // If defined, sets focus to this element once bubble is closed. Focus is + // set to this element only if there's no any other focused element. + elementToFocusOnHide_: undefined, + + // With help of these elements we create closed artificial tab-cycle through + // bubble elements. + firstBubbleElement_: undefined, + lastBubbleElement_: undefined, + + // Whether to hide bubble when key is pressed. + hideOnKeyPress_: true, + + /** @override */ + decorate: function() { + this.docKeyDownHandler_ = this.handleDocKeyDown_.bind(this); + this.selfClickHandler_ = this.handleSelfClick_.bind(this); + this.ownerDocument.addEventListener('click', + this.handleDocClick_.bind(this)); + this.ownerDocument.addEventListener('keydown', + this.docKeyDownHandler_); + window.addEventListener('blur', this.handleWindowBlur_.bind(this)); + this.addEventListener('webkitTransitionEnd', + this.handleTransitionEnd_.bind(this)); + // Guard timer for 200ms + epsilon. + ensureTransitionEndEvent(this, 250); + }, + + /** + * Element that should be focused on hide. + * @type {HTMLElement} + */ + set elementToFocusOnHide(value) { + this.elementToFocusOnHide_ = value; + }, + + /** + * Element that should be focused on shift-tab of first bubble element + * to create artificial closed tab-cycle through bubble. + * Usually close-button. + * @type {HTMLElement} + */ + set lastBubbleElement(value) { + this.lastBubbleElement_ = value; + }, + + /** + * Element that should be focused on tab of last bubble element + * to create artificial closed tab-cycle through bubble. + * Same element as first focused on bubble opening. + * @type {HTMLElement} + */ + set firstBubbleElement(value) { + this.firstBubbleElement_ = value; + }, + + /** + * Whether to hide bubble when key is pressed. + * @type {boolean} + */ + set hideOnKeyPress(value) { + this.hideOnKeyPress_ = value; + }, + + /** + * Whether to hide bubble when clicked inside bubble element. + * Default is true. + * @type {boolean} + */ + set hideOnSelfClick(value) { + if (value) + this.removeEventListener('click', this.selfClickHandler_); + else + this.addEventListener('click', this.selfClickHandler_); + }, + + /** + * Handler for click event which prevents bubble auto hide. + * @private + */ + handleSelfClick_: function(e) { + // Allow clicking on [x] button. + if (e.target && e.target.classList.contains('close-button')) + return; + e.stopPropagation(); + }, + + /** + * Sets the attachment of the bubble. + * @param {!Attachment} attachment Bubble attachment. + */ + setAttachment_: function(attachment) { + for (var k in Bubble.Attachment) { + var v = Bubble.Attachment[k]; + this.classList.toggle(v, v == attachment); + } + }, + + /** + * Shows the bubble for given anchor element. + * @param {!Object} pos Bubble position (left, top, right, bottom in px). + * @param {!Attachment} attachment Bubble attachment (on which side of the + * specified position it should be displayed). + * @param {HTMLElement} opt_content Content to show in bubble. + * If not specified, bubble element content is shown. + * @private + */ + showContentAt_: function(pos, attachment, opt_content) { + this.style.top = this.style.left = this.style.right = this.style.bottom = + 'auto'; + for (var k in pos) { + if (typeof pos[k] == 'number') + this.style[k] = pos[k] + 'px'; + } + if (opt_content !== undefined) { + this.innerHTML = ''; + this.appendChild(opt_content); + } + this.setAttachment_(attachment); + this.hidden = false; + this.classList.remove('faded'); + }, + + /** + * Shows the bubble for given anchor element. Bubble content is not cleared. + * @param {!HTMLElement} el Anchor element of the bubble. + * @param {!Attachment} attachment Bubble attachment (on which side of the + * element it should be displayed). + * @param {number=} opt_offset Offset of the bubble. + * @param {number=} opt_padding Optional padding of the bubble. + */ + showForElement: function(el, attachment, opt_offset, opt_padding) { + this.showContentForElement( + el, attachment, undefined, opt_offset, opt_padding); + }, + + /** + * Shows the bubble for given anchor element. + * @param {!HTMLElement} el Anchor element of the bubble. + * @param {!Attachment} attachment Bubble attachment (on which side of the + * element it should be displayed). + * @param {HTMLElement} opt_content Content to show in bubble. + * If not specified, bubble element content is shown. + * @param {number=} opt_offset Offset of the bubble attachment point from + * left (for vertical attachment) or top (for horizontal attachment) + * side of the element. If not specified, the bubble is positioned to + * be aligned with the left/top side of the element but not farther than + * half of its width/height. + * @param {number=} opt_padding Optional padding of the bubble. + */ + showContentForElement: function(el, attachment, opt_content, + opt_offset, opt_padding) { + /** @const */ var ARROW_OFFSET = 25; + /** @const */ var DEFAULT_PADDING = 18; + + if (opt_padding == undefined) + opt_padding = DEFAULT_PADDING; + + var origin = cr.ui.login.DisplayManager.getPosition(el); + var offset = opt_offset == undefined ? + [Math.min(ARROW_OFFSET, el.offsetWidth / 2), + Math.min(ARROW_OFFSET, el.offsetHeight / 2)] : + [opt_offset, opt_offset]; + + var pos = {}; + if (isRTL()) { + switch (attachment) { + case Bubble.Attachment.TOP: + pos.right = origin.right + offset[0] - ARROW_OFFSET; + pos.bottom = origin.bottom + el.offsetHeight + opt_padding; + break; + case Bubble.Attachment.RIGHT: + pos.top = origin.top + offset[1] - ARROW_OFFSET; + pos.right = origin.right + el.offsetWidth + opt_padding; + break; + case Bubble.Attachment.BOTTOM: + pos.right = origin.right + offset[0] - ARROW_OFFSET; + pos.top = origin.top + el.offsetHeight + opt_padding; + break; + case Bubble.Attachment.LEFT: + pos.top = origin.top + offset[1] - ARROW_OFFSET; + pos.left = origin.left + el.offsetWidth + opt_padding; + break; + } + } else { + switch (attachment) { + case Bubble.Attachment.TOP: + pos.left = origin.left + offset[0] - ARROW_OFFSET; + pos.bottom = origin.bottom + el.offsetHeight + opt_padding; + break; + case Bubble.Attachment.RIGHT: + pos.top = origin.top + offset[1] - ARROW_OFFSET; + pos.left = origin.left + el.offsetWidth + opt_padding; + break; + case Bubble.Attachment.BOTTOM: + pos.left = origin.left + offset[0] - ARROW_OFFSET; + pos.top = origin.top + el.offsetHeight + opt_padding; + break; + case Bubble.Attachment.LEFT: + pos.top = origin.top + offset[1] - ARROW_OFFSET; + pos.right = origin.right + el.offsetWidth + opt_padding; + break; + } + } + + this.anchor_ = el; + this.showContentAt_(pos, attachment, opt_content); + }, + + /** + * Shows the bubble for given anchor element. + * @param {!HTMLElement} el Anchor element of the bubble. + * @param {string} text Text content to show in bubble. + * @param {!Attachment} attachment Bubble attachment (on which side of the + * element it should be displayed). + * @param {number=} opt_offset Offset of the bubble attachment point from + * left (for vertical attachment) or top (for horizontal attachment) + * side of the element. If not specified, the bubble is positioned to + * be aligned with the left/top side of the element but not farther than + * half of its weight/height. + * @param {number=} opt_padding Optional padding of the bubble. + */ + showTextForElement: function(el, text, attachment, + opt_offset, opt_padding) { + var span = this.ownerDocument.createElement('span'); + span.textContent = text; + this.showContentForElement(el, attachment, span, opt_offset, opt_padding); + }, + + /** + * Hides the bubble. + */ + hide: function() { + if (!this.classList.contains('faded')) + this.classList.add('faded'); + }, + + /** + * Hides the bubble anchored to the given element (if any). + * @param {!Object} el Anchor element. + */ + hideForElement: function(el) { + if (!this.hidden && this.anchor_ == el) + this.hide(); + }, + + /** + * Handler for faded transition end. + * @private + */ + handleTransitionEnd_: function(e) { + if (this.classList.contains('faded')) { + this.hidden = true; + if (this.elementToFocusOnHide_) + this.elementToFocusOnHide_.focus(); + } + }, + + /** + * Handler of document click event. + * @private + */ + handleDocClick_: function(e) { + // Ignore clicks on anchor element. + if (e.target == this.anchor_) + return; + + if (!this.hidden) + this.hide(); + }, + + /** + * Handle of document keydown event. + * @private + */ + handleDocKeyDown_: function(e) { + if (this.hidden) + return; + + if (this.hideOnKeyPress_) { + this.hide(); + return; + } + // Artificial tab-cycle. + if (e.keyCode == KeyCodes.TAB && e.shiftKey == true && + e.target == this.firstBubbleElement_) { + this.lastBubbleElement_.focus(); + e.preventDefault(); + } + if (e.keyCode == KeyCodes.TAB && e.shiftKey == false && + e.target == this.lastBubbleElement_) { + this.firstBubbleElement_.focus(); + e.preventDefault(); + } + // Close bubble on ESC or on hitting spacebar or Enter at close-button. + if (e.keyCode == KeyCodes.ESC || + ((e.keyCode == KeyCodes.ENTER || e.keyCode == KeyCodes.SPACE) && + e.target && e.target.classList.contains('close-button'))) + this.hide(); + }, + + /** + * Handler of window blur event. + * @private + */ + handleWindowBlur_: function(e) { + if (!this.hidden) + this.hide(); + } + }; + + return { + Bubble: Bubble + }; +}); |