1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 * @fileoverview Bubble implementation.
9 // TODO(xiyuan): Move this into shared.
10 cr
.define('cr.ui', function() {
12 * Creates a bubble div.
14 * @extends {HTMLDivElement}
16 var Bubble
= cr
.ui
.define('div');
30 * Bubble attachment side.
34 RIGHT
: 'bubble-right',
37 BOTTOM
: 'bubble-bottom'
41 __proto__
: HTMLDivElement
.prototype,
43 // Anchor element for this bubble.
46 // If defined, sets focus to this element once bubble is closed. Focus is
47 // set to this element only if there's no any other focused element.
48 elementToFocusOnHide_
: undefined,
50 // With help of these elements we create closed artificial tab-cycle through
52 firstBubbleElement_
: undefined,
53 lastBubbleElement_
: undefined,
55 // Whether to hide bubble when key is pressed.
56 hideOnKeyPress_
: true,
59 decorate: function() {
60 this.docKeyDownHandler_
= this.handleDocKeyDown_
.bind(this);
61 this.selfClickHandler_
= this.handleSelfClick_
.bind(this);
62 this.ownerDocument
.addEventListener('click',
63 this.handleDocClick_
.bind(this));
64 this.ownerDocument
.addEventListener('keydown',
65 this.docKeyDownHandler_
);
66 window
.addEventListener('blur', this.handleWindowBlur_
.bind(this));
67 this.addEventListener('webkitTransitionEnd',
68 this.handleTransitionEnd_
.bind(this));
69 // Guard timer for 200ms + epsilon.
70 ensureTransitionEndEvent(this, 250);
74 * Element that should be focused on hide.
77 set elementToFocusOnHide(value
) {
78 this.elementToFocusOnHide_
= value
;
82 * Element that should be focused on shift-tab of first bubble element
83 * to create artificial closed tab-cycle through bubble.
84 * Usually close-button.
87 set lastBubbleElement(value
) {
88 this.lastBubbleElement_
= value
;
92 * Element that should be focused on tab of last bubble element
93 * to create artificial closed tab-cycle through bubble.
94 * Same element as first focused on bubble opening.
97 set firstBubbleElement(value
) {
98 this.firstBubbleElement_
= value
;
102 * Whether to hide bubble when key is pressed.
105 set hideOnKeyPress(value
) {
106 this.hideOnKeyPress_
= value
;
110 * Whether to hide bubble when clicked inside bubble element.
114 set hideOnSelfClick(value
) {
116 this.removeEventListener('click', this.selfClickHandler_
);
118 this.addEventListener('click', this.selfClickHandler_
);
122 * Handler for click event which prevents bubble auto hide.
125 handleSelfClick_: function(e
) {
126 // Allow clicking on [x] button.
127 if (e
.target
&& e
.target
.classList
.contains('close-button'))
133 * Sets the attachment of the bubble.
134 * @param {!Attachment} attachment Bubble attachment.
136 setAttachment_: function(attachment
) {
137 for (var k
in Bubble
.Attachment
) {
138 var v
= Bubble
.Attachment
[k
];
139 this.classList
.toggle(v
, v
== attachment
);
144 * Shows the bubble for given anchor element.
145 * @param {!Object} pos Bubble position (left, top, right, bottom in px).
146 * @param {!Attachment} attachment Bubble attachment (on which side of the
147 * specified position it should be displayed).
148 * @param {HTMLElement} opt_content Content to show in bubble.
149 * If not specified, bubble element content is shown.
152 showContentAt_: function(pos
, attachment
, opt_content
) {
153 this.style
.top
= this.style
.left
= this.style
.right
= this.style
.bottom
=
156 if (typeof pos
[k
] == 'number')
157 this.style
[k
] = pos
[k
] + 'px';
159 if (opt_content
!== undefined) {
161 this.appendChild(opt_content
);
163 this.setAttachment_(attachment
);
165 this.classList
.remove('faded');
169 * Shows the bubble for given anchor element. Bubble content is not cleared.
170 * @param {!HTMLElement} el Anchor element of the bubble.
171 * @param {!Attachment} attachment Bubble attachment (on which side of the
172 * element it should be displayed).
173 * @param {number=} opt_offset Offset of the bubble.
174 * @param {number=} opt_padding Optional padding of the bubble.
176 showForElement: function(el
, attachment
, opt_offset
, opt_padding
) {
177 this.showContentForElement(
178 el
, attachment
, undefined, opt_offset
, opt_padding
);
182 * Shows the bubble for given anchor element.
183 * @param {!HTMLElement} el Anchor element of the bubble.
184 * @param {!Attachment} attachment Bubble attachment (on which side of the
185 * element it should be displayed).
186 * @param {HTMLElement} opt_content Content to show in bubble.
187 * If not specified, bubble element content is shown.
188 * @param {number=} opt_offset Offset of the bubble attachment point from
189 * left (for vertical attachment) or top (for horizontal attachment)
190 * side of the element. If not specified, the bubble is positioned to
191 * be aligned with the left/top side of the element but not farther than
192 * half of its width/height.
193 * @param {number=} opt_padding Optional padding of the bubble.
195 showContentForElement: function(el
, attachment
, opt_content
,
196 opt_offset
, opt_padding
) {
197 /** @const */ var ARROW_OFFSET
= 25;
198 /** @const */ var DEFAULT_PADDING
= 18;
200 if (opt_padding
== undefined)
201 opt_padding
= DEFAULT_PADDING
;
203 var origin
= cr
.ui
.login
.DisplayManager
.getPosition(el
);
204 var offset
= opt_offset
== undefined ?
205 [Math
.min(ARROW_OFFSET
, el
.offsetWidth
/ 2),
206 Math
.min(ARROW_OFFSET
, el
.offsetHeight
/ 2)] :
207 [opt_offset
, opt_offset
];
211 switch (attachment
) {
212 case Bubble
.Attachment
.TOP
:
213 pos
.right
= origin
.right
+ offset
[0] - ARROW_OFFSET
;
214 pos
.bottom
= origin
.bottom
+ el
.offsetHeight
+ opt_padding
;
216 case Bubble
.Attachment
.RIGHT
:
217 pos
.top
= origin
.top
+ offset
[1] - ARROW_OFFSET
;
218 pos
.right
= origin
.right
+ el
.offsetWidth
+ opt_padding
;
220 case Bubble
.Attachment
.BOTTOM
:
221 pos
.right
= origin
.right
+ offset
[0] - ARROW_OFFSET
;
222 pos
.top
= origin
.top
+ el
.offsetHeight
+ opt_padding
;
224 case Bubble
.Attachment
.LEFT
:
225 pos
.top
= origin
.top
+ offset
[1] - ARROW_OFFSET
;
226 pos
.left
= origin
.left
+ el
.offsetWidth
+ opt_padding
;
230 switch (attachment
) {
231 case Bubble
.Attachment
.TOP
:
232 pos
.left
= origin
.left
+ offset
[0] - ARROW_OFFSET
;
233 pos
.bottom
= origin
.bottom
+ el
.offsetHeight
+ opt_padding
;
235 case Bubble
.Attachment
.RIGHT
:
236 pos
.top
= origin
.top
+ offset
[1] - ARROW_OFFSET
;
237 pos
.left
= origin
.left
+ el
.offsetWidth
+ opt_padding
;
239 case Bubble
.Attachment
.BOTTOM
:
240 pos
.left
= origin
.left
+ offset
[0] - ARROW_OFFSET
;
241 pos
.top
= origin
.top
+ el
.offsetHeight
+ opt_padding
;
243 case Bubble
.Attachment
.LEFT
:
244 pos
.top
= origin
.top
+ offset
[1] - ARROW_OFFSET
;
245 pos
.right
= origin
.right
+ el
.offsetWidth
+ opt_padding
;
251 this.showContentAt_(pos
, attachment
, opt_content
);
255 * Shows the bubble for given anchor element.
256 * @param {!HTMLElement} el Anchor element of the bubble.
257 * @param {string} text Text content to show in bubble.
258 * @param {!Attachment} attachment Bubble attachment (on which side of the
259 * element it should be displayed).
260 * @param {number=} opt_offset Offset of the bubble attachment point from
261 * left (for vertical attachment) or top (for horizontal attachment)
262 * side of the element. If not specified, the bubble is positioned to
263 * be aligned with the left/top side of the element but not farther than
264 * half of its weight/height.
265 * @param {number=} opt_padding Optional padding of the bubble.
267 showTextForElement: function(el
, text
, attachment
,
268 opt_offset
, opt_padding
) {
269 var span
= this.ownerDocument
.createElement('span');
270 span
.textContent
= text
;
271 this.showContentForElement(el
, attachment
, span
, opt_offset
, opt_padding
);
278 if (!this.classList
.contains('faded'))
279 this.classList
.add('faded');
283 * Hides the bubble anchored to the given element (if any).
284 * @param {!Object} el Anchor element.
286 hideForElement: function(el
) {
287 if (!this.hidden
&& this.anchor_
== el
)
292 * Handler for faded transition end.
295 handleTransitionEnd_: function(e
) {
296 if (this.classList
.contains('faded')) {
298 if (this.elementToFocusOnHide_
)
299 this.elementToFocusOnHide_
.focus();
304 * Handler of document click event.
307 handleDocClick_: function(e
) {
308 // Ignore clicks on anchor element.
309 if (e
.target
== this.anchor_
)
317 * Handle of document keydown event.
320 handleDocKeyDown_: function(e
) {
324 if (this.hideOnKeyPress_
) {
328 // Artificial tab-cycle.
329 if (e
.keyCode
== KeyCodes
.TAB
&& e
.shiftKey
== true &&
330 e
.target
== this.firstBubbleElement_
) {
331 this.lastBubbleElement_
.focus();
334 if (e
.keyCode
== KeyCodes
.TAB
&& e
.shiftKey
== false &&
335 e
.target
== this.lastBubbleElement_
) {
336 this.firstBubbleElement_
.focus();
339 // Close bubble on ESC or on hitting spacebar or Enter at close-button.
340 if (e
.keyCode
== KeyCodes
.ESC
||
341 ((e
.keyCode
== KeyCodes
.ENTER
|| e
.keyCode
== KeyCodes
.SPACE
) &&
342 e
.target
&& e
.target
.classList
.contains('close-button')))
347 * Handler of window blur event.
350 handleWindowBlur_: function(e
) {