]>
Commit | Line | Data |
---|---|---|
947f0963 TL |
1 | /** |
2 | * ToolTip is a {@link Ext.tip.Tip} implementation that handles the common case of displaying a | |
3 | * tooltip when hovering over a certain element or elements on the page. It allows fine-grained | |
4 | * control over the tooltip's alignment relative to the target element or mouse, and the timing | |
5 | * of when it is automatically shown and hidden. | |
6 | * | |
7 | * This implementation does **not** have a built-in method of automatically populating the tooltip's | |
8 | * text based on the target element; you must either configure a fixed {@link #html} value for each | |
9 | * ToolTip instance, or implement custom logic (e.g. in a {@link #beforeshow} event listener) to | |
10 | * generate the appropriate tooltip content on the fly. See {@link Ext.tip.QuickTip} for a more | |
11 | * convenient way of automatically populating and configuring a tooltip based on specific DOM | |
12 | * attributes of each target element. | |
13 | * | |
14 | * # Basic Example | |
15 | * | |
16 | * @example | |
17 | * Ext.getBody().appendChild({ | |
18 | * id: 'clearButton', | |
19 | * html: 'Clear Button', | |
20 | * style: 'display:inline-block;background:#A2C841;padding:7px;cursor:pointer;' | |
21 | * }); | |
22 | * | |
23 | * var tip = Ext.create('Ext.tip.ToolTip', { | |
24 | * target: 'clearButton', | |
25 | * html: 'Press this button to clear the form' | |
26 | * }); | |
27 | * | |
28 | * # Delegation | |
29 | * | |
30 | * In addition to attaching a ToolTip to a single element, you can also use delegation to attach | |
31 | * one ToolTip to many elements under a common parent. This is more efficient than creating many | |
32 | * ToolTip instances. To do this, point the {@link #target} config to a common ancestor of all the | |
33 | * elements, and then set the {@link #delegate} config to a CSS selector that will select all the | |
34 | * appropriate sub-elements. | |
35 | * | |
36 | * When using delegation, it is likely that you will want to programmatically change the content | |
37 | * of the ToolTip based on each delegate element; you can do this by implementing a custom | |
38 | * listener for the {@link #beforeshow} event. Example: | |
39 | * | |
40 | * @example | |
41 | * var store = Ext.create('Ext.data.ArrayStore', { | |
42 | * fields: ['company', 'price', 'change'], | |
43 | * data: [ | |
44 | * ['3m Co', 71.72, 0.02], | |
45 | * ['Alcoa Inc', 29.01, 0.42], | |
46 | * ['Altria Group Inc', 83.81, 0.28], | |
47 | * ['American Express Company', 52.55, 0.01], | |
48 | * ['American International Group, Inc.', 64.13, 0.31], | |
49 | * ['AT&T Inc.', 31.61, -0.48] | |
50 | * ] | |
51 | * }); | |
52 | * | |
53 | * var grid = Ext.create('Ext.grid.Panel', { | |
54 | * title: 'Array Grid', | |
55 | * store: store, | |
56 | * columns: [ | |
57 | * {text: 'Company', flex: 1, dataIndex: 'company'}, | |
58 | * {text: 'Price', width: 75, dataIndex: 'price'}, | |
59 | * {text: 'Change', width: 75, dataIndex: 'change'} | |
60 | * ], | |
61 | * height: 200, | |
62 | * width: 400, | |
63 | * renderTo: Ext.getBody() | |
64 | * }); | |
65 | * | |
66 | * var view = grid.getView(); | |
67 | * var tip = Ext.create('Ext.tip.ToolTip', { | |
68 | * // The overall target element. | |
69 | * target: view.el, | |
70 | * // Each grid row causes its own separate show and hide. | |
71 | * delegate: view.itemSelector, | |
72 | * // Moving within the row should not hide the tip. | |
73 | * trackMouse: true, | |
74 | * // Render immediately so that tip.body can be referenced prior to the first show. | |
75 | * renderTo: Ext.getBody(), | |
76 | * listeners: { | |
77 | * // Change content dynamically depending on which element triggered the show. | |
78 | * beforeshow: function updateTipBody(tip) { | |
79 | * tip.update('Over company "' + view.getRecord(tip.triggerElement).get('company') + | |
80 | * '"'); | |
81 | * } | |
82 | * } | |
83 | * }); | |
84 | * | |
85 | * # Alignment | |
86 | * | |
87 | * The following configuration properties allow control over how the ToolTip is aligned relative to | |
88 | * the target element and/or mouse pointer: | |
89 | * | |
90 | * - {@link #anchor} | |
91 | * - {@link #anchorToTarget} | |
92 | * - {@link #trackMouse} | |
93 | * - {@link #mouseOffset} | |
94 | * | |
95 | * # Showing/Hiding | |
96 | * | |
97 | * The following configuration properties allow control over how and when the ToolTip | |
98 | * is automatically shown and hidden: | |
99 | * | |
100 | * - {@link #autoHide} | |
101 | * - {@link #showDelay} | |
102 | * - {@link #hideDelay} | |
103 | * - {@link #dismissDelay} | |
104 | */ | |
105 | Ext.define('Ext.tip.ToolTip', { | |
106 | extend: 'Ext.tip.Tip', | |
107 | alias: 'widget.tooltip', | |
108 | alternateClassName: 'Ext.ToolTip', | |
109 | ||
110 | requires: ['Ext.util.Offset'], | |
111 | ||
112 | /** | |
113 | * @property {HTMLElement} triggerElement | |
114 | * When a ToolTip is configured with the `{@link #delegate}` | |
115 | * option to cause selected child elements of the `{@link #target}` | |
116 | * Element to each trigger a separate show event, this property is set to | |
117 | * the DOM element which triggered the show. | |
118 | */ | |
119 | ||
120 | /** | |
121 | * @cfg {HTMLElement/Ext.dom.Element/String} target | |
122 | * The target element or string id to monitor for mouseover events to trigger | |
123 | * showing this ToolTip. | |
124 | */ | |
125 | ||
126 | /** | |
127 | * @cfg {Boolean} [autoHide=true] | |
128 | * True to automatically hide the tooltip after the | |
129 | * mouse exits the target element or after the `{@link #dismissDelay}` | |
130 | * has expired if set. If `{@link #closable} = true` | |
131 | * a close tool button will be rendered into the tooltip header. | |
132 | */ | |
133 | autoHide: true, | |
134 | ||
135 | /** | |
136 | * @cfg {Number} showDelay | |
137 | * Delay in milliseconds before the tooltip displays after the mouse enters the target element. | |
138 | */ | |
139 | showDelay: 500, | |
140 | ||
141 | /** | |
142 | * @cfg {Number} hideDelay | |
143 | * Delay in milliseconds after the mouse exits the target element but before the tooltip | |
144 | * actually hides. Set to 0 for the tooltip to hide immediately. | |
145 | */ | |
146 | hideDelay: 200, | |
147 | ||
148 | /** | |
149 | * @cfg {Number} dismissDelay | |
150 | * Delay in milliseconds before the tooltip automatically hides. To disable automatic hiding, | |
151 | * set dismissDelay = 0. | |
152 | */ | |
153 | dismissDelay: 5000, | |
154 | ||
155 | /** | |
156 | * @cfg {Number[]} [targetOffset=[0, 0]] | |
157 | * When {@link #anchorToTarget} is being used to position this tip relative to its target | |
158 | * element, this may be used as an extra XY offset from the target element. | |
159 | */ | |
160 | ||
161 | /** | |
162 | * @cfg {Number[]} [mouseOffset=[15, 18]] | |
163 | * An XY offset from the mouse position where the tooltip should be shown. | |
164 | */ | |
165 | mouseOffset: [15, 18], | |
166 | ||
167 | /** | |
168 | * @cfg {Boolean} trackMouse | |
169 | * True to have the tooltip follow the mouse as it moves over the target element. | |
170 | */ | |
171 | trackMouse: false, | |
172 | ||
173 | /** | |
174 | * @cfg {String} anchor | |
175 | * If specified, indicates that the tip should be anchored to a | |
176 | * particular side of the target element or mouse pointer ("top", "right", "bottom", | |
177 | * or "left"), with an arrow pointing back at the target or mouse pointer. If | |
178 | * {@link #constrainPosition} is enabled, this will be used as a preferred value | |
179 | * only and may be flipped as needed. | |
180 | */ | |
181 | ||
182 | /** | |
183 | * @cfg {Boolean} anchorToTarget | |
184 | * True to anchor the tooltip to the target element, false to anchor it relative to the mouse | |
185 | * coordinates. When `anchorToTarget` is true, use `{@link #defaultAlign}` to control tooltip | |
186 | * alignment to the target element. When `anchorToTarget` is false, use `{@link #anchor}` | |
187 | * instead to control alignment. | |
188 | */ | |
189 | anchorToTarget: true, | |
190 | ||
191 | /** | |
192 | * @cfg {String} delegate | |
193 | * | |
194 | * A {@link Ext.DomQuery DomQuery} simple selector which allows selection of individual elements | |
195 | * within the `{@link #target}` element to trigger showing and hiding the ToolTip as the mouse | |
196 | * moves within the target. See {@link Ext.dom.Query} for information about simple selectors. | |
197 | * | |
198 | * When specified, the child element of the target which caused a show event is placed into the | |
199 | * `{@link #triggerElement}` property before the ToolTip is shown. | |
200 | * | |
201 | * This may be useful when a Component has regular, repeating elements in it, each of which need | |
202 | * a ToolTip which contains information specific to that element. | |
203 | * | |
204 | * See the delegate example in class documentation of {@link Ext.tip.ToolTip}. | |
205 | */ | |
206 | ||
207 | /** | |
208 | * @cfg {Boolean} [showOnTap=false] | |
209 | * On touch platforms, if {@link #showOnTap} is `true`, a tap on the target shows the tip. | |
210 | * In this case any {@link #showDelay} is ignored. | |
211 | * | |
212 | * This is useful for adding tips on elements which do not have tap listeners. It would | |
213 | * not be appropriate for a ToolTip on a {@link Ext.Button Button}. | |
214 | */ | |
215 | ||
216 | /** | |
217 | * @private | |
218 | */ | |
219 | targetCounter: 0, | |
220 | ||
221 | quickShowInterval: 250, | |
222 | ||
223 | /** | |
224 | * @cfg {String} [hideAction="hide"] | |
225 | * The method to use to hide the tooltip. Another useful method for this is `fadeOut`. | |
226 | */ | |
227 | hideAction: 'hide', | |
228 | ||
229 | /** | |
230 | * @cfg {Number} [fadeOutDuration=1000] | |
231 | * The number of milliseconds for the `fadeOut` animation. Only valid if `hideAction` | |
232 | * is set to `fadeOut`. | |
233 | */ | |
234 | fadeOutDuration: 1000, | |
235 | ||
236 | /** | |
237 | * @cfg {String} defaultAlign | |
238 | * A string which specifies how this ToolTip is to align with regard to its | |
239 | * {@link #currentTarget} by means of identifying the point of the tooltip to | |
240 | * join to the point of the target. | |
241 | * | |
242 | * By default, the tooltip shows at {@link #mouseOffset} pixels from the | |
243 | * triggering pointer event. Using this config anchors the ToolTip to its target | |
244 | * instead. | |
245 | * | |
246 | * This may take the following forms: | |
247 | * | |
248 | * - **Blank**: Defaults to aligning the element's top-left corner to the target's | |
249 | * bottom-left corner ("tl-bl"). | |
250 | * - **Two anchors**: If two values from the table below are passed separated by a dash, | |
251 | * the first value is used as the element's anchor point, and the second value is | |
252 | * used as the target's anchor point. | |
253 | * - **Two edge/offset descriptors:** An edge/offset descriptor is an edge initial | |
254 | * (`t`/`r`/`b`/`l`) followed by a percentage along that side. This describes a | |
255 | * point to align with a similar point in the target. So `'t0-b0'` would be | |
256 | * the same as `'tl-bl'`, `'l0-r50'` would place the top left corner of this item | |
257 | * halfway down the right edge of the target item. This allows more flexibility | |
258 | * and also describes which two edges are considered adjacent when positioning a tip pointer. | |
259 | * | |
260 | * Following are all of the supported predefined anchor positions: | |
261 | * | |
262 | * Value Description | |
263 | * ----- ----------------------------- | |
264 | * tl The top left corner | |
265 | * t The center of the top edge | |
266 | * tr The top right corner | |
267 | * l The center of the left edge | |
268 | * c The center | |
269 | * r The center of the right edge | |
270 | * bl The bottom left corner | |
271 | * b The center of the bottom edge | |
272 | * br The bottom right corner | |
273 | * | |
274 | * You can put a '?' at the end of the alignment string to constrain the positioned element | |
275 | * to the {@link Ext.Viewport Viewport}. The element will attempt to align as specified, | |
276 | * but the position will be adjusted to constrain to the viewport if necessary. Note that | |
277 | * the element being aligned might be swapped to align to a different position than that | |
278 | * specified in order to enforce the viewport constraints. | |
279 | * | |
280 | * Example Usage: | |
281 | * | |
282 | * // align the top left corner of the tooltip with the top right corner of its target. | |
283 | * defaultAlign: 'tl-tr' | |
284 | * | |
285 | * // align the bottom right corner of the tooltip with the center left edge of its target. | |
286 | * defaultAlign: 'br-l' | |
287 | * | |
288 | * // align the top center of the tooltip with the bottom left corner of its target. | |
289 | * defaultAlign: 't-bl' | |
290 | * | |
291 | * // align the 25% point on the bottom edge of this tooltip | |
292 | * // with the 75% point on the top edge of its target. | |
293 | * defaultAlign: 'b25-t75' | |
294 | */ | |
295 | defaultAlign: 'bl-tl', | |
296 | ||
297 | ariaRole: 'tooltip', | |
298 | ||
299 | alwaysOnTop: true, | |
300 | ||
301 | initComponent: function() { | |
302 | var me = this; | |
303 | ||
304 | me.callParent(); | |
305 | me.setTarget(me.target); | |
306 | ||
307 | // currentTarget is a flyweight which points to the activeTarget. | |
308 | me.currentTarget = new Ext.dom.Fly(); | |
309 | }, | |
310 | ||
311 | onRender: function(ct, position) { | |
312 | var me = this; | |
313 | ||
314 | me.callParent(arguments); | |
315 | ||
316 | //<debug> | |
317 | if (me.sticky) { | |
318 | // tell the spec runner to ignore this element when checking if the dom is clean | |
319 | me.el.dom.setAttribute('data-sticky', true); | |
320 | } | |
321 | //</debug> | |
322 | ||
323 | me.anchorEl = me.el.createChild({ | |
324 | role: 'presentation', | |
325 | cls: Ext.baseCSSPrefix + 'tip-anchor' | |
326 | }); | |
327 | }, | |
328 | ||
329 | show: function() { | |
330 | // A programmatic show should align to the target | |
331 | if (!this.currentTarget.dom && this.target) { | |
332 | return this.showBy(this.target); | |
333 | } | |
334 | ||
335 | this.callParent(); | |
336 | }, | |
337 | ||
338 | /** | |
339 | * Binds this ToolTip to the specified element. The tooltip will be displayed when the mouse | |
340 | * moves over the element. | |
341 | * @param {String/HTMLElement/Ext.dom.Element} target The Element, HTMLElement, or | |
342 | * ID of an element to bind to | |
343 | */ | |
344 | setTarget: function(target) { | |
345 | var me = this, | |
346 | listeners; | |
347 | ||
348 | if (me.targetListeners) { | |
349 | me.targetListeners.destroy(); | |
350 | } | |
351 | ||
352 | if (target) { | |
353 | me.target = target = Ext.get(target.el || target); | |
354 | listeners = { | |
355 | mouseover: 'onTargetOver', | |
356 | mouseout: 'onTargetOut', | |
357 | mousemove: 'onMouseMove', | |
358 | tap: 'onTargetTap', | |
359 | scope: me, | |
360 | destroyable: true | |
361 | }; | |
362 | ||
363 | me.targetListeners = target.on(listeners); | |
364 | } | |
365 | else { | |
366 | me.target = null; | |
367 | } | |
368 | }, | |
369 | ||
370 | /** | |
371 | * @private | |
372 | */ | |
373 | onMouseMove: function(e) { | |
374 | var me = this, | |
375 | dismissDelay = me.dismissDelay; | |
376 | ||
377 | // Always update pointerEvent, so that if there's a delayed show | |
378 | // scheduled, it gets the latest pointer to align to. | |
379 | me.pointerEvent = e; | |
380 | ||
381 | if (me.isVisible() && me.currentTarget.contains(e.target)) { | |
382 | // If they move the mouse, restart the dismiss delay | |
383 | if (dismissDelay && me.autoHide !== false) { | |
384 | me.clearTimer('dismiss'); | |
385 | me.dismissTimer = Ext.defer(me.hide, dismissDelay, me); | |
386 | } | |
387 | ||
388 | if (me.trackMouse) { | |
389 | me.doAlignment(me.getAlignRegion()); | |
390 | } | |
391 | } | |
392 | }, | |
393 | ||
394 | /** | |
395 | * @private | |
396 | */ | |
397 | getAlignRegion: function() { | |
398 | var me = this, | |
399 | anchorEl = me.anchorEl, | |
400 | align = me.getAnchorAlign(), | |
401 | overlap, | |
402 | alignSpec, | |
403 | target, | |
404 | mouseOffset = me.mouseOffset; | |
405 | ||
406 | if (!me.anchorSize) { | |
407 | anchorEl.addCls(Ext.baseCSSPrefix + 'tip-anchor-top'); | |
408 | anchorEl.show(); | |
409 | ||
410 | me.anchorSize = new Ext.util.Offset( | |
411 | anchorEl.getWidth(false, true), anchorEl.getHeight(false, true) | |
412 | ); | |
413 | ||
414 | anchorEl.removeCls(Ext.baseCSSPrefix + 'tip-anchor-top'); | |
415 | anchorEl.hide(); | |
416 | } | |
417 | ||
418 | // Target region from the anchorTarget element unless trackMouse set | |
419 | if ((me.anchor || me.align) && me.anchorToTarget && !me.trackMouse) { | |
420 | target = me.currentTarget.getRegion(); | |
421 | } | |
422 | ||
423 | // Here, we're either trackMouse: true, or we're not anchored to the target | |
424 | // element, so we should show offset from the mouse. | |
425 | // If we are being shown programatically, use 0, 0 | |
426 | else { | |
427 | target = me.pointerEvent | |
428 | ? me.pointerEvent.getPoint().adjust( | |
429 | -Math.abs(mouseOffset[1]), Math.abs(mouseOffset[0]), | |
430 | Math.abs(mouseOffset[1]), -Math.abs(mouseOffset[0]) | |
431 | ) | |
432 | : new Ext.util.Point(); | |
433 | ||
434 | if (!me.anchor) { | |
435 | overlap = true; | |
436 | ||
437 | if (mouseOffset[0] > 0) { | |
438 | if (mouseOffset[1] > 0) { | |
439 | align = 'tl-br'; | |
440 | } | |
441 | else { | |
442 | align = 'bl-tr'; | |
443 | } | |
444 | } | |
445 | else { | |
446 | if (mouseOffset[1] > 0) { | |
447 | align = 'tr-bl'; | |
448 | } | |
449 | else { | |
450 | align = 'br-tl'; | |
451 | } | |
452 | } | |
453 | } | |
454 | } | |
455 | ||
456 | alignSpec = { | |
457 | align: me.convertPositionSpec(align), | |
458 | axisLock: me.axisLock, | |
459 | target: target, | |
460 | overlap: overlap, | |
461 | offset: me.targetOffset, | |
462 | inside: me.constrainPosition | |
463 | ? (me.constrainTo || Ext.getBody().getRegion().adjust(5, -5, -5, 5)) | |
464 | : null | |
465 | }; | |
466 | ||
467 | if (me.anchor) { | |
468 | alignSpec.anchorSize = me.anchorSize; | |
469 | } | |
470 | ||
471 | return me.getRegion().alignTo(alignSpec); | |
472 | }, | |
473 | ||
474 | fadeOut: function() { | |
475 | var me = this; | |
476 | ||
477 | me.el.fadeOut({ | |
478 | duration: me.fadeOutDuration, | |
479 | callback: function() { | |
480 | me.hide(); | |
481 | me.el.setOpacity(''); | |
482 | } | |
483 | }); | |
484 | }, | |
485 | ||
486 | /** | |
487 | * @private | |
488 | */ | |
489 | getAnchorAlign: function() { | |
490 | switch (this.anchor) { | |
491 | case 'top': | |
492 | return 'tl-bl'; | |
493 | ||
494 | case 'left': | |
495 | return 'tl-tr'; | |
496 | ||
497 | case 'right': | |
498 | return 'tr-tl'; | |
499 | ||
500 | default: | |
501 | return this.defaultAlign; | |
502 | } | |
503 | }, | |
504 | ||
505 | onTargetTap: function(e) { | |
506 | // On hybrid mouse/touch systems, we want to show the tip on touch, but | |
507 | // we don't want to show it if this is coming from a click event, because | |
508 | // the mouse is already hovered. Tap occasionally hides - eg: pickers, menus. | |
509 | if (this.showOnTap && e.pointerType !== 'mouse' && Ext.fly(e.target).isVisible(true)) { | |
510 | this.onTargetOver(e); | |
511 | } | |
512 | }, | |
513 | ||
514 | /** | |
515 | * @private | |
516 | */ | |
517 | onTargetOver: function(e) { | |
518 | var me = this, | |
519 | delegate = me.delegate, | |
520 | currentTarget = me.currentTarget, | |
521 | fromElement = e.relatedTarget || e.fromElement, | |
522 | newTarget, | |
523 | myListeners = me.hasListeners; | |
524 | ||
525 | if (me.disabled) { | |
526 | return; | |
527 | } | |
528 | ||
529 | if (delegate) { | |
530 | // Moving inside a delegate | |
531 | if (currentTarget.contains(e.target)) { | |
532 | return; | |
533 | } | |
534 | ||
535 | newTarget = e.getTarget(delegate); | |
536 | ||
537 | // Mouseovers while within a target do nothing | |
538 | if (newTarget && e.getRelatedTarget(delegate) === newTarget) { | |
539 | return; | |
540 | } | |
541 | } | |
542 | // Moved from outside the target | |
543 | else if (!me.target.contains(fromElement)) { | |
544 | newTarget = me.target.dom; | |
545 | } | |
546 | // Moving inside the target | |
547 | else { | |
548 | return; | |
549 | } | |
550 | ||
551 | // If pointer entered the target or a delegate child, then show. | |
552 | if (newTarget) { | |
553 | // If users need to see show events on target change, we must hide. | |
554 | if ((myListeners.beforeshow || myListeners.show) && me.isVisible()) { | |
555 | me.hide(); | |
556 | } | |
557 | ||
558 | me.triggerElement = newTarget; | |
559 | me.pointerEvent = e; | |
560 | currentTarget.attach(newTarget); | |
561 | me.handleTargetOver(newTarget, e); | |
562 | } | |
563 | // If over a non-delegate child, behave as in target out | |
564 | else if (currentTarget.dom) { | |
565 | me.handleTargetOut(); | |
566 | } | |
567 | }, | |
568 | ||
569 | handleTargetOver: function(target, event) { | |
570 | // Separated from onTargetOver so that subclasses can handle target over in any way. | |
571 | ||
572 | // If we are showing on tap, show immediately | |
573 | if (event.pointerType !== 'mouse') { | |
574 | this.showFromDelay(); | |
575 | } | |
576 | else { | |
577 | this.delayShow(); | |
578 | } | |
579 | }, | |
580 | ||
581 | /** | |
582 | * @private | |
583 | */ | |
584 | delayShow: function() { | |
585 | var me = this; | |
586 | ||
587 | me.clearTimer('hide'); | |
588 | ||
589 | if (me.hidden && !me.showTimer) { | |
590 | if (me.delegate && Ext.Date.getElapsed(me.lastHidden) < me.quickShowInterval) { | |
591 | me.showFromDelay(); | |
592 | } | |
593 | else { | |
594 | me.showTimer = Ext.defer( | |
595 | me.showFromDelay, | |
596 | me.pointerEvent.pointerType !== 'mouse' ? 0 : me.showDelay, | |
597 | me | |
598 | ); | |
599 | } | |
600 | } | |
601 | else if (!me.hidden && me.autoHide !== false) { | |
602 | me.showFromDelay(); | |
603 | } | |
604 | }, | |
605 | ||
606 | showFromDelay: function() { | |
607 | var me = this; | |
608 | ||
609 | // Need to check this here since onDisable only gets called after render, which | |
610 | // the show call below may trigger | |
611 | if (!me.disabled) { | |
612 | me.fireEvent('hovertarget', me, me.currentTarget, me.currentTarget.dom); | |
613 | ||
614 | if (me.isVisible()) { | |
615 | me.realignToTarget(); | |
616 | } | |
617 | else { | |
618 | me.triggerElement = me.currentTarget.dom; | |
619 | me.fromDelayShow = true; | |
620 | me.show(); | |
621 | me.fromDelayShow = false; | |
622 | } | |
623 | } | |
624 | }, | |
625 | ||
626 | /** | |
627 | * @private | |
628 | */ | |
629 | onTargetOut: function(e) { | |
630 | // We have exited the current target | |
631 | if (this.currentTarget.dom && !this.currentTarget.contains(e.relatedTarget)) { | |
632 | this.handleTargetOut(); | |
633 | } | |
634 | }, | |
635 | ||
636 | handleTargetOut: function() { | |
637 | var me = this; | |
638 | ||
639 | if (me.showTimer) { | |
640 | me.clearTimer('show'); | |
641 | } | |
642 | ||
643 | if (me.isVisible() && me.autoHide) { | |
644 | me.delayHide(); | |
645 | } | |
646 | }, | |
647 | ||
648 | /** | |
649 | * @private | |
650 | */ | |
651 | delayHide: function() { | |
652 | var me = this; | |
653 | ||
654 | if (!me.hidden && !me.hideTimer) { | |
655 | me.clearTimer('dismiss'); | |
656 | me.hideTimer = Ext.defer(me[me.hideAction], me.hideDelay, me); | |
657 | } | |
658 | }, | |
659 | ||
660 | /** | |
661 | * Hides this tooltip if visible. | |
662 | */ | |
663 | hide: function() { | |
664 | var me = this; | |
665 | ||
666 | // Must also do this on hide in case it was dismissed while over | |
667 | me.currentTarget.detach(); | |
668 | ||
669 | me.clearTimer('dismiss'); | |
670 | me.lastHidden = new Date(); | |
671 | ||
672 | if (me.anchorEl) { | |
673 | me.anchorEl.hide(); | |
674 | } | |
675 | ||
676 | me.callParent(arguments); | |
677 | ||
678 | me.triggerElement = null; | |
679 | }, | |
680 | ||
681 | /** | |
682 | * Ensures this tooltip at the current event target XY position. | |
683 | */ | |
684 | afterShow: function() { | |
685 | this.callParent(); | |
686 | this.realignToTarget(); | |
687 | }, | |
688 | ||
689 | /** | |
690 | * Realign this tooltip to the {@link #cfg-target}. | |
691 | * | |
692 | * @since 6.2.1 | |
693 | */ | |
694 | realignToTarget: function() { | |
695 | var me = this; | |
696 | ||
697 | me.clearTimers(); | |
698 | ||
699 | if (!me.calledFromShowAt) { | |
700 | me.doAlignment(me.getAlignRegion()); | |
701 | } | |
702 | ||
703 | if (me.dismissDelay && me.autoHide !== false) { | |
704 | me.dismissTimer = Ext.defer(me.hide, me.dismissDelay, me); | |
705 | } | |
706 | }, | |
707 | ||
708 | /** | |
709 | * Shows this ToolTip aligned to the passed Component or element or event according to the | |
710 | * {@link #anchor} config. | |
711 | * @param {Ext.Component/Ext.event.Event/Ext.dom.Element} target The {@link Ext.Component} | |
712 | * or {@link Ext.dom.Element}, or (Ext.event.Event} to show this ToolTip by. | |
713 | */ | |
714 | showBy: function(target) { | |
715 | var me = this; | |
716 | ||
717 | me.align = me.defaultAlign; | |
718 | ||
719 | if (target.isEvent) { | |
720 | me.currentTarget.attach(target.target); | |
721 | me.pointerEvent = target; | |
722 | } | |
723 | else { | |
724 | me.currentTarget.attach(Ext.getDom(target.el || target)); | |
725 | me.triggerElement = me.currentTarget.dom; | |
726 | } | |
727 | ||
728 | if (me.isVisible()) { | |
729 | me.realignToTarget(); | |
730 | } | |
731 | else { | |
732 | me.show(); | |
733 | } | |
734 | ||
735 | return me; | |
736 | }, | |
737 | ||
738 | _timerNames: {}, | |
739 | ||
740 | /** | |
741 | * @private | |
742 | */ | |
743 | clearTimer: function(name) { | |
744 | var me = this, | |
745 | names = me._timerNames, | |
746 | propName = names[name] || (names[name] = name + 'Timer'), | |
747 | timer = me[propName]; | |
748 | ||
749 | if (timer) { | |
750 | Ext.undefer(timer); | |
751 | me[propName] = null; | |
752 | ||
753 | // We were going to show against the target, but now not. | |
754 | if (name === 'show' && me.isHidden()) { | |
755 | me.currentTarget.detach(); | |
756 | } | |
757 | } | |
758 | }, | |
759 | ||
760 | /** | |
761 | * @private | |
762 | */ | |
763 | clearTimers: function() { | |
764 | var me = this; | |
765 | ||
766 | me.clearTimer('show'); | |
767 | me.clearTimer('dismiss'); | |
768 | me.clearTimer('hide'); | |
769 | me.clearTimer('enable'); | |
770 | }, | |
771 | ||
772 | onShow: function() { | |
773 | var me = this; | |
774 | ||
775 | me.callParent(); | |
776 | ||
777 | me.mousedownListener = Ext.on({ | |
778 | mousedown: 'onDocMouseDown', | |
779 | scope: me, | |
780 | destroyable: true | |
781 | }); | |
782 | }, | |
783 | ||
784 | onHide: function() { | |
785 | var me = this; | |
786 | ||
787 | me.callParent(); | |
788 | ||
789 | Ext.destroy(me.mousedownListener); | |
790 | }, | |
791 | ||
792 | /** | |
793 | * @private | |
794 | */ | |
795 | onDocMouseDown: function(e) { | |
796 | var me = this, | |
797 | delegate = me.delegate; | |
798 | ||
799 | if (e.within(me.el.dom)) { | |
800 | // A real touch event inside the tip is the equivalent of | |
801 | // mousing over the tip to keep it visible, so cancel the | |
802 | // dismiss timer. | |
803 | if (e.pointerType !== 'mouse' && me.allowOver) { | |
804 | me.clearTimer('dismiss'); | |
805 | } | |
806 | } | |
807 | // Only respond to the mousedown if it's not on this tip, and it's not on a target. | |
808 | // If it's on a target, onTargetTap will handle it. | |
809 | else if (!me.closable) { | |
810 | if (e.within(me.target) && (!delegate || e.getTarget(delegate))) { | |
811 | me.delayHide(); | |
812 | } | |
813 | else { | |
814 | me.disable(); | |
815 | me.enableTimer = Ext.defer(me.enable, 100, me); | |
816 | } | |
817 | } | |
818 | }, | |
819 | ||
820 | /** | |
821 | * @private | |
822 | */ | |
823 | doEnable: function() { | |
824 | if (!this.destroyed) { | |
825 | this.enable(); | |
826 | } | |
827 | }, | |
828 | ||
829 | onDisable: function() { | |
830 | this.callParent(); | |
831 | this.clearTimers(); | |
832 | this.hide(); | |
833 | }, | |
834 | ||
835 | doDestroy: function() { | |
836 | var me = this; | |
837 | ||
838 | me.clearTimers(); | |
839 | ||
840 | me.destroyMembers('mousedownListener', 'anchorEl'); | |
841 | ||
842 | me.callParent(); | |
843 | }, | |
844 | ||
845 | privates: { | |
846 | /** | |
847 | * Implementation for universal apps so that the Tooltip interface they are using works | |
848 | * when common code uses the ToolTip API. | |
849 | */ | |
850 | getTrackMouse: function() { | |
851 | return this.trackMouse; | |
852 | }, | |
853 | ||
854 | clipTo: function(clippingEl, sides) { | |
855 | // Override because we also need to clip the anchor | |
856 | var clippingRegion; | |
857 | ||
858 | // Allow a Region to be passed | |
859 | if (clippingEl.isRegion) { | |
860 | clippingRegion = clippingEl; | |
861 | } | |
862 | else { | |
863 | // eslint-disable-next-line max-len | |
864 | clippingRegion = (clippingEl.isComponent ? clippingEl.el : Ext.fly(clippingEl)).getConstrainRegion(); | |
865 | } | |
866 | ||
867 | this.callParent([clippingRegion, sides]); | |
868 | ||
869 | // Clip the anchor to the same bounds | |
870 | this.anchorEl.clipTo(clippingRegion, sides); | |
871 | } | |
872 | } | |
873 | }); |