| OLD | NEW |
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 // require: event_tracker.js | 5 // require: event_tracker.js |
| 6 | 6 |
| 7 cr.define('cr.ui', function() { | 7 cr.define('cr.ui', function() { |
| 8 | 8 |
| 9 /** | 9 /** |
| 10 * The arrow location specifies how the arrow and bubble are positioned in | 10 * The arrow location specifies how the arrow and bubble are positioned in |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 // left to right mode this is the bottom left. The entire bubble is | 24 // left to right mode this is the bottom left. The entire bubble is |
| 25 // positioned above the anchor node. | 25 // positioned above the anchor node. |
| 26 BOTTOM_START: 'bottom-start', | 26 BOTTOM_START: 'bottom-start', |
| 27 // The arrow is positioned at the bottom and the end of the bubble. In | 27 // The arrow is positioned at the bottom and the end of the bubble. In |
| 28 // left to right mode this is the bottom right. The entire bubble is | 28 // left to right mode this is the bottom right. The entire bubble is |
| 29 // positioned above the anchor node. | 29 // positioned above the anchor node. |
| 30 BOTTOM_END: 'bottom-end' | 30 BOTTOM_END: 'bottom-end' |
| 31 }; | 31 }; |
| 32 | 32 |
| 33 /** | 33 /** |
| 34 * The bubble alignment specifies the position of the bubble in relation to |
| 35 * the anchor node. |
| 36 * @enum |
| 37 */ |
| 38 var BubbleAlignment = { |
| 39 // The bubble is positioned just above or below the anchor node (as |
| 40 // specified by the arrow location) so that the arrow points at the midpoint |
| 41 // of the anchor. |
| 42 ARROW_TO_MID_ANCHOR: 'arrow-to-mid-anchor', |
| 43 // The bubble is positioned just above or below the anchor node (as |
| 44 // specified by the arrow location) so that its reference edge lines up with |
| 45 // the edge of the anchor. |
| 46 BUBBLE_EDGE_TO_ANCHOR_EDGE: 'bubble-edge-anchor-edge', |
| 47 // The bubble is positioned so that it is entirely within view and does not |
| 48 // obstruct the anchor element, if possible. The specified arrow location is |
| 49 // taken into account as the preferred alignment but may be overruled if |
| 50 // there is insufficient space (see BubbleBase.reposition for the exact |
| 51 // placement algorithm). |
| 52 ENTIRELY_VISIBLE: 'entirely-visible' |
| 53 }; |
| 54 |
| 55 /** |
| 34 * Abstract base class that provides common functionality for implementing | 56 * Abstract base class that provides common functionality for implementing |
| 35 * free-floating informational bubbles with a triangular arrow pointing at an | 57 * free-floating informational bubbles with a triangular arrow pointing at an |
| 36 * anchor node. | 58 * anchor node. |
| 37 */ | 59 */ |
| 38 var BubbleBase = cr.ui.define('div'); | 60 var BubbleBase = cr.ui.define('div'); |
| 39 | 61 |
| 40 /** | 62 /** |
| 41 * The horizontal distance between the tip of the arrow and the reference edge | 63 * The horizontal distance between the tip of the arrow and the reference edge |
| 42 * of the bubble (as specified by the arrow location). | 64 * of the bubble (as specified by the arrow location). |
| 43 * @const | 65 * @const |
| 44 */ | 66 */ |
| 45 BubbleBase.ARROW_OFFSET = 30; | 67 BubbleBase.ARROW_OFFSET = 30; |
| 46 | 68 |
| 47 BubbleBase.prototype = { | 69 BubbleBase.prototype = { |
| 48 // Set up the prototype chain | 70 // Set up the prototype chain. |
| 49 __proto__: HTMLDivElement.prototype, | 71 __proto__: HTMLDivElement.prototype, |
| 50 | 72 |
| 51 /** | 73 /** |
| 52 * Initialization function for the cr.ui framework. | 74 * Initialization function for the cr.ui framework. |
| 53 */ | 75 */ |
| 54 decorate: function() { | 76 decorate: function() { |
| 55 this.className = 'bubble'; | 77 this.className = 'bubble'; |
| 56 this.innerHTML = | 78 this.innerHTML = |
| 57 '<div class="bubble-content"></div>' + | 79 '<div class="bubble-content"></div>' + |
| 58 '<div class="bubble-shadow"></div>' + | 80 '<div class="bubble-shadow"></div>' + |
| 59 '<div class="bubble-arrow"></div>'; | 81 '<div class="bubble-arrow"></div>'; |
| 60 this.hidden = true; | 82 this.hidden = true; |
| 83 this.bubbleAlignment = BubbleAlignment.ENTIRELY_VISIBLE; |
| 61 }, | 84 }, |
| 62 | 85 |
| 63 /** | 86 /** |
| 64 * Set the anchor node, i.e. the node that this bubble points at. Only | 87 * Set the anchor node, i.e. the node that this bubble points at. Only |
| 65 * available when the bubble is not being shown. | 88 * available when the bubble is not being shown. |
| 66 * @param {HTMLElement} node The new anchor node. | 89 * @param {HTMLElement} node The new anchor node. |
| 67 */ | 90 */ |
| 68 set anchorNode(node) { | 91 set anchorNode(node) { |
| 69 if (!this.hidden) | 92 if (!this.hidden) |
| 70 return; | 93 return; |
| (...skipping 26 matching lines...) Expand all Loading... |
| 97 | 120 |
| 98 this.arrowAtRight_ = location == ArrowLocation.TOP_END || | 121 this.arrowAtRight_ = location == ArrowLocation.TOP_END || |
| 99 location == ArrowLocation.BOTTOM_END; | 122 location == ArrowLocation.BOTTOM_END; |
| 100 if (document.documentElement.dir == 'rtl') | 123 if (document.documentElement.dir == 'rtl') |
| 101 this.arrowAtRight_ = !this.arrowAtRight_; | 124 this.arrowAtRight_ = !this.arrowAtRight_; |
| 102 this.arrowAtTop_ = location == ArrowLocation.TOP_START || | 125 this.arrowAtTop_ = location == ArrowLocation.TOP_START || |
| 103 location == ArrowLocation.TOP_END; | 126 location == ArrowLocation.TOP_END; |
| 104 }, | 127 }, |
| 105 | 128 |
| 106 /** | 129 /** |
| 130 * Set the bubble alignment. Only available when the bubble is not being |
| 131 * shown. |
| 132 * @param {cr.ui.BubbleAlignment} alignment The new bubble alignment. |
| 133 */ |
| 134 set bubbleAlignment(alignment) { |
| 135 if (!this.hidden) |
| 136 return; |
| 137 |
| 138 this.bubbleAlignment_ = alignment; |
| 139 }, |
| 140 |
| 141 /** |
| 142 * Update the position of the bubble. Whenever the layout may have changed, |
| 143 * the bubble should either be repositioned by calling this function or |
| 144 * hidden so that it does not point to a nonsensical location on the page. |
| 145 */ |
| 146 reposition: function() { |
| 147 var documentWidth = document.documentElement.clientWidth; |
| 148 var documentHeight = document.documentElement.clientHeight; |
| 149 var anchor = this.anchorNode_.getBoundingClientRect(); |
| 150 var anchorMid = (anchor.left + anchor.right) / 2; |
| 151 var bubble = this.getBoundingClientRect(); |
| 152 var arrow = this.querySelector('.bubble-arrow').getBoundingClientRect(); |
| 153 |
| 154 if (this.bubbleAlignment_ == BubbleAlignment.ENTIRELY_VISIBLE) { |
| 155 // Work out horizontal placement. The bubble is initially positioned so |
| 156 // that the arrow tip points toward the midpoint of the anchor and is |
| 157 // BubbleBase.ARROW_OFFSET pixels from the reference edge and (as |
| 158 // specified by the arrow location). If the bubble is not entirely |
| 159 // within view, it is then shifted, preserving the arrow tip position. |
| 160 var left = this.arrowAtRight_ ? |
| 161 anchorMid + BubbleBase.ARROW_OFFSET - bubble.width : |
| 162 anchorMid - BubbleBase.ARROW_OFFSET; |
| 163 if (document.documentElement.dir == 'rtl') |
| 164 left = Math.min(Math.max(left, 0), documentWidth - bubble.width); |
| 165 else |
| 166 left = Math.max(Math.min(left, documentWidth - bubble.width), 0); |
| 167 var arrowTip = Math.min(Math.max(this.arrowAtRight_ ? |
| 168 left + bubble.width - anchorMid : anchorMid - left, |
| 169 arrow.width / 2), bubble.width - arrow.width / 2); |
| 170 |
| 171 // Work out the vertical placement, attempting to fit the bubble |
| 172 // entirely into view. The following placements are considered in |
| 173 // decreasing order of preference: |
| 174 // * Outside the anchor, arrow tip touching the anchor (arrow at |
| 175 // top/bottom as specified by the arrow location). |
| 176 // * Outside the anchor, arrow tip touching the anchor (arrow at |
| 177 // bottom/top, opposite the specified arrow location). |
| 178 // * Outside the anchor, arrow tip overlapping the anchor (arrow at |
| 179 // top/bottom as specified by the arrow location). |
| 180 // * Outside the anchor, arrow tip overlapping the anchor (arrow at |
| 181 // bottom/top, opposite the specified arrow location). |
| 182 // * Overlapping the anchor. |
| 183 var offsetTop = Math.min(documentHeight - anchor.bottom - bubble.height, |
| 184 arrow.height / 2); |
| 185 var offsetBottom = Math.min(anchor.top - bubble.height, |
| 186 arrow.height / 2); |
| 187 if (offsetTop < 0 && offsetBottom < 0) { |
| 188 var top = 0; |
| 189 this.updateArrowPosition_(false, false, arrowTip); |
| 190 } else if (offsetTop > offsetBottom || |
| 191 offsetTop == offsetBottom && this.arrowAtTop_) { |
| 192 var top = anchor.bottom + offsetTop; |
| 193 this.updateArrowPosition_(true, true, arrowTip); |
| 194 } else { |
| 195 var top = anchor.top - bubble.height - offsetBottom; |
| 196 this.updateArrowPosition_(true, false, arrowTip); |
| 197 } |
| 198 } else { |
| 199 if (this.bubbleAlignment_ == |
| 200 BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) { |
| 201 var left = this.arrowAtRight_ ? anchor.right - bubble.width : |
| 202 anchor.left; |
| 203 } else { |
| 204 var left = this.arrowAtRight_ ? |
| 205 anchorMid - this.clientWidth + BubbleBase.ARROW_OFFSET : |
| 206 anchorMid - BubbleBase.ARROW_OFFSET; |
| 207 } |
| 208 var top = this.arrowAtTop_ ? anchor.bottom + arrow.height / 2 : |
| 209 anchor.top - this.clientHeight - arrow.height / 2; |
| 210 this.updateArrowPosition_(true, this.arrowAtTop_, |
| 211 BubbleBase.ARROW_OFFSET); |
| 212 } |
| 213 |
| 214 this.style.left = left + 'px'; |
| 215 this.style.top = top + 'px'; |
| 216 }, |
| 217 |
| 218 /** |
| 107 * Show the bubble. | 219 * Show the bubble. |
| 108 */ | 220 */ |
| 109 show: function() { | 221 show: function() { |
| 110 if (!this.hidden) | 222 if (!this.hidden) |
| 111 return; | 223 return; |
| 112 | 224 |
| 113 document.body.appendChild(this); | 225 this.attachToDOM_(); |
| 114 this.hidden = false; | 226 this.hidden = false; |
| 227 this.reposition(); |
| 228 this.anchorNode_.showingBubble = true; |
| 115 | 229 |
| 116 var doc = this.ownerDocument; | 230 var doc = this.ownerDocument; |
| 117 this.eventTracker_ = new EventTracker; | 231 this.eventTracker_ = new EventTracker; |
| 118 this.eventTracker_.add(doc, 'keydown', this, true); | 232 this.eventTracker_.add(doc, 'keydown', this, true); |
| 119 this.eventTracker_.add(doc, 'mousedown', this, true); | 233 this.eventTracker_.add(doc, 'mousedown', this, true); |
| 120 }, | 234 }, |
| 121 | 235 |
| 122 /** | 236 /** |
| 123 * Hide the bubble. | 237 * Hide the bubble. |
| 124 */ | 238 */ |
| 125 hide: function() { | 239 hide: function() { |
| 126 if (this.hidden) | 240 if (this.hidden) |
| 127 return; | 241 return; |
| 128 | 242 |
| 243 this.eventTracker_.removeAll(); |
| 244 this.anchorNode_.showingBubble = false; |
| 129 this.hidden = true; | 245 this.hidden = true; |
| 130 this.eventTracker_.removeAll(); | |
| 131 this.parentNode.removeChild(this); | 246 this.parentNode.removeChild(this); |
| 132 }, | 247 }, |
| 133 | 248 |
| 134 /** | 249 /** |
| 135 * Handle keyboard events, dismissing the bubble if necessary. | 250 * Handle keyboard events, dismissing the bubble if necessary. |
| 136 * @param {Event} event The event. | 251 * @param {Event} event The event. |
| 137 */ | 252 */ |
| 138 handleEvent: function(event) { | 253 handleEvent: function(event) { |
| 139 // Close the bubble when the user presses <Esc>. | 254 // Close the bubble when the user presses <Esc>. |
| 140 if (event.type == 'keydown' && event.keyCode == 27) { | 255 if (event.type == 'keydown' && event.keyCode == 27) { |
| 141 this.hide(); | 256 this.hide(); |
| 142 event.preventDefault(); | 257 event.preventDefault(); |
| 143 event.stopPropagation(); | 258 event.stopPropagation(); |
| 144 } | 259 } |
| 145 }, | 260 }, |
| 146 | 261 |
| 147 /** | 262 /** |
| 263 * Attach the bubble to the document's DOM. |
| 264 * @private |
| 265 */ |
| 266 attachToDOM_: function() { |
| 267 document.body.appendChild(this); |
| 268 }, |
| 269 |
| 270 /** |
| 148 * Update the arrow so that it appears at the correct position. | 271 * Update the arrow so that it appears at the correct position. |
| 149 * @param {Boolean} visible Whether the arrow should be visible. | 272 * @param {Boolean} visible Whether the arrow should be visible. |
| 150 * @param {number} The horizontal distance between the tip of the arrow and | 273 * @param {Boolean} atTop Whether the arrow should be at the top of the |
| 151 * the reference edge of the bubble (as specified by the arrow location). | 274 * bubble. |
| 275 * @param {number} tipOffset The horizontal distance between the tip of the |
| 276 * arrow and the reference edge of the bubble (as specified by the arrow |
| 277 * location). |
| 152 * @private | 278 * @private |
| 153 */ | 279 */ |
| 154 updateArrowPosition_: function(visible, tipOffset) { | 280 updateArrowPosition_: function(visible, atTop, tipOffset) { |
| 155 var bubbleArrow = this.querySelector('.bubble-arrow'); | 281 var bubbleArrow = this.querySelector('.bubble-arrow'); |
| 156 | 282 bubbleArrow.hidden = !visible; |
| 157 if (visible) { | 283 if (!visible) |
| 158 bubbleArrow.style.display = 'block'; | |
| 159 } else { | |
| 160 bubbleArrow.style.display = 'none'; | |
| 161 return; | 284 return; |
| 162 } | |
| 163 | 285 |
| 164 var edgeOffset = (-bubbleArrow.clientHeight / 2) + 'px'; | 286 var edgeOffset = (-bubbleArrow.clientHeight / 2) + 'px'; |
| 165 bubbleArrow.style.top = this.arrowAtTop_ ? edgeOffset : 'auto'; | 287 bubbleArrow.style.top = atTop ? edgeOffset : 'auto'; |
| 166 bubbleArrow.style.bottom = this.arrowAtTop_ ? 'auto' : edgeOffset; | 288 bubbleArrow.style.bottom = atTop ? 'auto' : edgeOffset; |
| 167 | 289 |
| 168 edgeOffset = (tipOffset - bubbleArrow.clientHeight / 2) + 'px'; | 290 edgeOffset = (tipOffset - bubbleArrow.offsetWidth / 2) + 'px'; |
| 169 bubbleArrow.style.left = this.arrowAtRight_ ? 'auto' : edgeOffset; | 291 bubbleArrow.style.left = this.arrowAtRight_ ? 'auto' : edgeOffset; |
| 170 bubbleArrow.style.right = this.arrowAtRight_ ? edgeOffset : 'auto'; | 292 bubbleArrow.style.right = this.arrowAtRight_ ? edgeOffset : 'auto'; |
| 171 }, | 293 }, |
| 172 }; | 294 }; |
| 173 | 295 |
| 174 /** | 296 /** |
| 175 * The bubble alignment specifies the horizontal position of the bubble in | |
| 176 * relation to the anchor node. | |
| 177 * @enum | |
| 178 */ | |
| 179 var BubbleAlignment = { | |
| 180 // The bubble is positioned so that the tip of the arrow points to the | |
| 181 // middle of the anchor node. | |
| 182 ARROW_TO_MID_ANCHOR: 'arrow-to-mid-anchor', | |
| 183 // The bubble is positioned so that the edge nearest to the arrow is lined | |
| 184 // up with the edge of the anchor node. | |
| 185 BUBBLE_EDGE_TO_ANCHOR_EDGE: 'bubble-edge-anchor-edge' | |
| 186 }; | |
| 187 | |
| 188 /** | |
| 189 * A bubble that remains open until the user explicitly dismisses it or clicks | 297 * A bubble that remains open until the user explicitly dismisses it or clicks |
| 190 * outside the bubble after it has been shown for at least the specified | 298 * outside the bubble after it has been shown for at least the specified |
| 191 * amount of time (making it less likely that the user will unintentionally | 299 * amount of time (making it less likely that the user will unintentionally |
| 192 * dismiss the bubble). The bubble repositions itself on layout changes. | 300 * dismiss the bubble). The bubble repositions itself on layout changes. |
| 193 */ | 301 */ |
| 194 var Bubble = cr.ui.define('div'); | 302 var Bubble = cr.ui.define('div'); |
| 195 | 303 |
| 196 Bubble.prototype = { | 304 Bubble.prototype = { |
| 197 // Set up the prototype chain | 305 // Set up the prototype chain |
| 198 __proto__: BubbleBase.prototype, | 306 __proto__: BubbleBase.prototype, |
| (...skipping 20 matching lines...) Expand all Loading... |
| 219 * @param {function} handler The new handler, a function with no parameters. | 327 * @param {function} handler The new handler, a function with no parameters. |
| 220 */ | 328 */ |
| 221 set handleCloseEvent(handler) { | 329 set handleCloseEvent(handler) { |
| 222 if (!this.hidden) | 330 if (!this.hidden) |
| 223 return; | 331 return; |
| 224 | 332 |
| 225 this.handleCloseEvent_ = handler; | 333 this.handleCloseEvent_ = handler; |
| 226 }, | 334 }, |
| 227 | 335 |
| 228 /** | 336 /** |
| 229 * Set the bubble alignment. Only available when the bubble is not being | |
| 230 * shown. | |
| 231 * @param {cr.ui.BubbleAlignment} alignment The new bubble alignment. | |
| 232 */ | |
| 233 set bubbleAlignment(alignment) { | |
| 234 if (!this.hidden) | |
| 235 return; | |
| 236 | |
| 237 this.bubbleAlignment_ = alignment; | |
| 238 }, | |
| 239 | |
| 240 /** | |
| 241 * Set the delay before the user is allowed to click outside the bubble to | 337 * Set the delay before the user is allowed to click outside the bubble to |
| 242 * dismiss it. Using a delay makes it less likely that the user will | 338 * dismiss it. Using a delay makes it less likely that the user will |
| 243 * unintentionally dismiss the bubble. | 339 * unintentionally dismiss the bubble. |
| 244 * @param {number} delay The delay in milliseconds. | 340 * @param {number} delay The delay in milliseconds. |
| 245 */ | 341 */ |
| 246 set deactivateToDismissDelay(delay) { | 342 set deactivateToDismissDelay(delay) { |
| 247 this.deactivateToDismissDelay_ = delay; | 343 this.deactivateToDismissDelay_ = delay; |
| 248 }, | 344 }, |
| 249 | 345 |
| 250 /** | 346 /** |
| 251 * Hide or show the close button. | 347 * Hide or show the close button. |
| 252 * @param {Boolean} isVisible True if the close button should be visible. | 348 * @param {Boolean} isVisible True if the close button should be visible. |
| 253 */ | 349 */ |
| 254 set closeButtonVisible(isVisible) { | 350 set closeButtonVisible(isVisible) { |
| 255 this.querySelector('.bubble-close').hidden = !isVisible; | 351 this.querySelector('.bubble-close').hidden = !isVisible; |
| 256 }, | 352 }, |
| 257 | 353 |
| 258 /** | 354 /** |
| 259 * Update the position of the bubble. This is automatically called when the | |
| 260 * window is resized, but should also be called any time the layout may have | |
| 261 * changed. | |
| 262 */ | |
| 263 reposition: function() { | |
| 264 var clientRect = this.anchorNode_.getBoundingClientRect(); | |
| 265 var bubbleArrow = this.querySelector('.bubble-arrow'); | |
| 266 var arrowOffsetY = bubbleArrow.offsetHeight / 2; | |
| 267 | |
| 268 var left; | |
| 269 if (this.bubbleAlignment_ == | |
| 270 BubbleAlignment.BUBBLE_EDGE_TO_ANCHOR_EDGE) { | |
| 271 left = this.arrowAtRight_ ? clientRect.right - this.clientWidth : | |
| 272 clientRect.left; | |
| 273 } else { | |
| 274 var anchorMid = (clientRect.left + clientRect.right) / 2; | |
| 275 left = this.arrowAtRight_ ? | |
| 276 anchorMid - this.clientWidth + BubbleBase.ARROW_OFFSET : | |
| 277 anchorMid - BubbleBase.ARROW_OFFSET; | |
| 278 } | |
| 279 var top = this.arrowAtTop_ ? clientRect.bottom + arrowOffsetY : | |
| 280 clientRect.top - this.clientHeight - arrowOffsetY; | |
| 281 | |
| 282 this.style.left = left + 'px'; | |
| 283 this.style.top = top + 'px'; | |
| 284 | |
| 285 this.updateArrowPosition_(true, BubbleBase.ARROW_OFFSET); | |
| 286 }, | |
| 287 | |
| 288 /** | |
| 289 * Show the bubble. | 355 * Show the bubble. |
| 290 */ | 356 */ |
| 291 show: function() { | 357 show: function() { |
| 292 if (!this.hidden) | 358 if (!this.hidden) |
| 293 return; | 359 return; |
| 294 | 360 |
| 295 BubbleBase.prototype.show.call(this); | 361 BubbleBase.prototype.show.call(this); |
| 296 | 362 |
| 297 this.reposition(); | |
| 298 this.showTime_ = Date.now(); | 363 this.showTime_ = Date.now(); |
| 299 this.eventTracker_.add(window, 'resize', this.reposition.bind(this)); | 364 this.eventTracker_.add(window, 'resize', this.reposition.bind(this)); |
| 300 }, | 365 }, |
| 301 | 366 |
| 302 /** | 367 /** |
| 303 * Handle keyboard and mouse events, dismissing the bubble if necessary. | 368 * Handle keyboard and mouse events, dismissing the bubble if necessary. |
| 304 * @param {Event} event The event. | 369 * @param {Event} event The event. |
| 305 */ | 370 */ |
| 306 handleEvent: function(event) { | 371 handleEvent: function(event) { |
| 307 BubbleBase.prototype.handleEvent.call(this, event); | 372 BubbleBase.prototype.handleEvent.call(this, event); |
| 308 | 373 |
| 309 if (event.type == 'mousedown') { | 374 if (event.type == 'mousedown') { |
| 310 // Dismiss the bubble when the user clicks on the close button. | 375 // Dismiss the bubble when the user clicks on the close button. |
| 311 if (event.target == this.querySelector('.bubble-close')) { | 376 if (event.target == this.querySelector('.bubble-close')) { |
| 312 this.handleCloseEvent_(); | 377 this.handleCloseEvent_(); |
| 313 // Dismiss the bubble when the user clicks outside it after the | 378 // Dismiss the bubble when the user clicks outside it after the |
| 314 // specified delay has passed. | 379 // specified delay has passed. |
| 315 } else if (!this.contains(event.target) && | 380 } else if (!this.contains(event.target) && |
| 316 Date.now() - this.showTime_ >= this.deactivateToDismissDelay_) { | 381 Date.now() - this.showTime_ >= this.deactivateToDismissDelay_) { |
| 317 this.hide(); | 382 this.hide(); |
| 318 } | 383 } |
| 319 } | 384 } |
| 320 }, | 385 }, |
| 321 }; | 386 }; |
| 322 | 387 |
| 323 return { | 388 return { |
| 324 ArrowLocation: ArrowLocation, | 389 ArrowLocation: ArrowLocation, |
| 390 BubbleAlignment: BubbleAlignment, |
| 325 BubbleBase: BubbleBase, | 391 BubbleBase: BubbleBase, |
| 326 BubbleAlignment: BubbleAlignment, | |
| 327 Bubble: Bubble | 392 Bubble: Bubble |
| 328 }; | 393 }; |
| 329 }); | 394 }); |
| OLD | NEW |