Index: chrome/browser/resources/shared/js/cr/ui/expandable_bubble.js |
=================================================================== |
--- chrome/browser/resources/shared/js/cr/ui/expandable_bubble.js (revision 0) |
+++ chrome/browser/resources/shared/js/cr/ui/expandable_bubble.js (revision 0) |
@@ -0,0 +1,244 @@ |
+// Copyright (c) 2011 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() { |
+ 'use strict'; |
+ |
+ /** |
+ * ExpandableBubble is a free-floating compact informational bubble with an |
+ * arrow that points at a place of interest on the page. When clicked, the |
+ * bubble expands to show more of its content. Width of the bubble is the |
+ * width of the node it is overlapping when unexpanded. Expanded, it is of a |
+ * fixed width, but variable height. Currently the arrow is always positioned |
+ * at the bottom right and points down. |
+ * @constructor |
+ * @extends {cr.ui.div} |
+ */ |
+ var ExpandableBubble = cr.ui.define('div'); |
+ |
+ ExpandableBubble.prototype = { |
+ __proto__: HTMLDivElement.prototype, |
+ |
+ /** @inheritDoc */ |
+ decorate: function() { |
+ this.className = 'expandable-bubble'; |
+ this.innerHTML = |
+ '<div class="expandable-bubble-contents">' + |
+ '<div class="expandable-bubble-title"></div>' + |
+ '<div class="expandable-bubble-main" hidden></div>' + |
+ '</div>' + |
+ '<div class="expandable-bubble-close" hidden></div>' + |
+ '<div class="expandable-bubble-shadow"></div>' + |
+ '<div class="expandable-bubble-arrow"></div>'; |
+ |
+ this.hidden = true; |
+ }, |
+ |
+ /** |
+ * Sets the title of the bubble. The title is always visible when the |
+ * bubble is visible. |
+ * @type {Node} An HTML element to set as the title. |
+ */ |
+ set contentTitle(node) { |
+ var bubbleTitle = this.querySelector('.expandable-bubble-title'); |
+ bubbleTitle.textContent = ''; |
+ bubbleTitle.appendChild(node); |
+ }, |
+ |
+ /** |
+ * Sets the content node of the bubble. The content node is only visible |
+ * when the bubble is expanded. |
+ * @param {Node} An HTML element. |
+ */ |
+ set content(node) { |
+ var bubbleMain = this.querySelector('.expandable-bubble-main'); |
+ bubbleMain.textContent = ''; |
+ bubbleMain.appendChild(node); |
+ }, |
+ |
+ /** |
+ * Sets the anchor node, i.e. the node that this bubble points at and |
+ * partially overlaps. |
+ * @param {HTMLElement} node The new anchor node. |
+ */ |
+ set anchorNode(node) { |
+ this.anchorNode_ = node; |
+ |
+ if (!this.hidden) |
+ this.resizeAndReposition_(); |
+ }, |
+ |
+ /** |
+ * Updates the position of the bubble. |
+ * @private |
+ */ |
+ reposition_: function() { |
+ var clientRect = this.anchorNode_.getBoundingClientRect(); |
+ this.style.left = this.style.right = clientRect.left + 'px'; |
+ |
+ var top = clientRect.top - 1; |
+ this.style.top = this.expanded ? |
+ (top - this.offsetHeight + this.unexpandedHeight) + 'px' : |
+ top + 'px'; |
+ }, |
+ |
+ /** |
+ * Resizes the bubble and then repositions it. |
+ * @private |
+ */ |
+ resizeAndReposition_: function() { |
+ var clientRect = this.anchorNode_.getBoundingClientRect(); |
+ var width = clientRect.width; |
+ if (this.expanded) { |
+ var expandedWidth = 250; |
+ this.style.marginLeft = (width - expandedWidth) + 'px'; |
+ width = expandedWidth; |
+ } else { |
+ this.style.marginLeft = '0'; |
+ } |
+ |
+ // Width is dynamic (when not expanded) based on the width of the anchor |
+ // node, and the title and shadow need to follow suit. |
+ this.style.width = width + 'px'; |
+ if (width > 0) { |
+ var bubbleTitle = this.querySelector('.expandable-bubble-title'); |
+ bubbleTitle.style.width = width ? width - 2 + 'px' : 0 + 'px'; |
+ var bubbleContent = this.querySelector('.expandable-bubble-main'); |
+ bubbleContent.style.width = width ? width - 12 + 'px' : 0 + 'px'; |
+ var bubbleShadow = this.querySelector('.expandable-bubble-shadow'); |
+ bubbleShadow.style.width = width ? width + 2 + 'px' : 0 + 'px'; |
+ } |
+ |
+ // Also reposition the bubble -- dimensions have potentially changed. |
+ this.reposition_(); |
+ }, |
+ |
+ /* |
+ * Expand the bubble (bringing the full content into view). |
+ * @private |
+ */ |
+ expandBubble_: function() { |
+ this.querySelector('.expandable-bubble-main').hidden = false; |
+ this.querySelector('.expandable-bubble-close').hidden = false; |
+ this.expanded = true; |
+ this.resizeAndReposition_(); |
+ }, |
+ |
+ /** |
+ * Collapse the bubble, hiding the main content and the close button. |
+ * This is automatically called when the window is resized. |
+ * @private |
+ */ |
+ collapseBubble_: function() { |
+ this.querySelector('.expandable-bubble-main').hidden = true; |
+ this.querySelector('.expandable-bubble-close').hidden = true; |
+ this.expanded = false; |
+ this.resizeAndReposition_(); |
+ }, |
+ |
+ /** |
+ * The onclick handler for the notification (expands the bubble). |
+ * @param {Event} e The event. |
+ * @private |
+ */ |
+ onNotificationClick_ : function(e) { |
+ if (!this.contains(e.target)) |
+ return; |
+ |
+ if (!this.expanded) { |
+ // Save the height of the unexpanded bubble, so we can make sure to |
+ // position it correctly (arrow points in the same location) after |
+ // we expand it. |
+ this.unexpandedHeight = this.offsetHeight; |
+ } |
+ |
+ this.expandBubble_(); |
+ }, |
+ |
+ /** |
+ * Shows the bubble. The bubble will start collapsed and expand when |
+ * clicked. |
+ */ |
+ show: function() { |
+ if (!this.hidden) |
+ return; |
+ |
+ document.body.appendChild(this); |
+ this.hidden = false; |
+ this.resizeAndReposition_(); |
+ |
+ this.eventTracker_ = new EventTracker; |
+ this.eventTracker_.add(window, |
+ 'load', this.resizeAndReposition_.bind(this)); |
+ this.eventTracker_.add(window, |
+ 'resize', this.resizeAndReposition_.bind(this)); |
+ this.eventTracker_.add(this, 'click', this.onNotificationClick_); |
+ |
+ 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. |
+ * @private |
+ */ |
+ handleEvent: function(e) { |
+ var handled = false; |
+ switch (e.type) { |
+ case 'keydown': |
+ if (e.keyCode == 27) { // Esc. |
+ if (this.expanded) { |
+ this.collapseBubble_(); |
+ handled = true; |
+ } |
+ } |
+ break; |
+ |
+ case 'mousedown': |
+ if (e.target == this.querySelector('.expandable-bubble-close')) { |
+ this.hide(); |
+ handled = true; |
+ } else if (!this.contains(e.target)) { |
+ if (this.expanded) { |
+ this.collapseBubble_(); |
+ handled = true; |
+ } |
+ } |
+ break; |
+ } |
+ |
+ if (handled) { |
+ // The bubble emulates a focus grab when expanded, so when we've |
+ // collapsed/hide the bubble we consider the event handles and don't |
+ // need to propagate it further. |
+ e.stopPropagation(); |
+ e.preventDefault(); |
+ } |
+ }, |
+ }; |
+ |
+ /** |
+ * Whether the bubble is expanded or not. |
+ * @type {boolean} |
+ */ |
+ cr.defineProperty(ExpandableBubble, 'expanded', cr.PropertyKind.BOOL_ATTR); |
+ |
+ return { |
+ ExpandableBubble: ExpandableBubble |
+ }; |
+}); |
Property changes on: chrome\browser\resources\shared\js\cr\ui\expandable_bubble.js |
___________________________________________________________________ |
Added: svn:eol-style |
+ LF |