|
1 /*! |
|
2 * jQuery UI Tooltip 1.12.1 |
|
3 * http://jqueryui.com |
|
4 * |
|
5 * Copyright jQuery Foundation and other contributors |
|
6 * Released under the MIT license. |
|
7 * http://jquery.org/license |
|
8 */ |
|
9 |
|
10 //>>label: Tooltip |
|
11 //>>group: Widgets |
|
12 //>>description: Shows additional information for any element on hover or focus. |
|
13 //>>docs: http://api.jqueryui.com/tooltip/ |
|
14 //>>demos: http://jqueryui.com/tooltip/ |
|
15 //>>css.structure: ../../themes/base/core.css |
|
16 //>>css.structure: ../../themes/base/tooltip.css |
|
17 //>>css.theme: ../../themes/base/theme.css |
|
18 |
|
19 ( function( factory ) { |
|
20 if ( typeof define === "function" && define.amd ) { |
|
21 |
|
22 // AMD. Register as an anonymous module. |
|
23 define( [ |
|
24 "jquery", |
|
25 "./core" |
|
26 ], factory ); |
|
27 } else { |
|
28 |
|
29 // Browser globals |
|
30 factory( jQuery ); |
|
31 } |
|
32 }( function( $ ) { |
|
33 |
|
34 $.widget( "ui.tooltip", { |
|
35 version: "1.12.1", |
|
36 options: { |
|
37 classes: { |
|
38 "ui-tooltip": "ui-corner-all ui-widget-shadow" |
|
39 }, |
|
40 content: function() { |
|
41 |
|
42 // support: IE<9, Opera in jQuery <1.7 |
|
43 // .text() can't accept undefined, so coerce to a string |
|
44 var title = $( this ).attr( "title" ) || ""; |
|
45 |
|
46 // Escape title, since we're going from an attribute to raw HTML |
|
47 return $( "<a>" ).text( title ).html(); |
|
48 }, |
|
49 hide: true, |
|
50 |
|
51 // Disabled elements have inconsistent behavior across browsers (#8661) |
|
52 items: "[title]:not([disabled])", |
|
53 position: { |
|
54 my: "left top+15", |
|
55 at: "left bottom", |
|
56 collision: "flipfit flip" |
|
57 }, |
|
58 show: true, |
|
59 track: false, |
|
60 |
|
61 // Callbacks |
|
62 close: null, |
|
63 open: null |
|
64 }, |
|
65 |
|
66 _addDescribedBy: function( elem, id ) { |
|
67 var describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ); |
|
68 describedby.push( id ); |
|
69 elem |
|
70 .data( "ui-tooltip-id", id ) |
|
71 .attr( "aria-describedby", $.trim( describedby.join( " " ) ) ); |
|
72 }, |
|
73 |
|
74 _removeDescribedBy: function( elem ) { |
|
75 var id = elem.data( "ui-tooltip-id" ), |
|
76 describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ), |
|
77 index = $.inArray( id, describedby ); |
|
78 |
|
79 if ( index !== -1 ) { |
|
80 describedby.splice( index, 1 ); |
|
81 } |
|
82 |
|
83 elem.removeData( "ui-tooltip-id" ); |
|
84 describedby = $.trim( describedby.join( " " ) ); |
|
85 if ( describedby ) { |
|
86 elem.attr( "aria-describedby", describedby ); |
|
87 } else { |
|
88 elem.removeAttr( "aria-describedby" ); |
|
89 } |
|
90 }, |
|
91 |
|
92 _create: function() { |
|
93 this._on( { |
|
94 mouseover: "open", |
|
95 focusin: "open" |
|
96 } ); |
|
97 |
|
98 // IDs of generated tooltips, needed for destroy |
|
99 this.tooltips = {}; |
|
100 |
|
101 // IDs of parent tooltips where we removed the title attribute |
|
102 this.parents = {}; |
|
103 |
|
104 // Append the aria-live region so tooltips announce correctly |
|
105 this.liveRegion = $( "<div>" ) |
|
106 .attr( { |
|
107 role: "log", |
|
108 "aria-live": "assertive", |
|
109 "aria-relevant": "additions" |
|
110 } ) |
|
111 .appendTo( this.document[ 0 ].body ); |
|
112 this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" ); |
|
113 |
|
114 this.disabledTitles = $( [] ); |
|
115 }, |
|
116 |
|
117 _setOption: function( key, value ) { |
|
118 var that = this; |
|
119 |
|
120 this._super( key, value ); |
|
121 |
|
122 if ( key === "content" ) { |
|
123 $.each( this.tooltips, function( id, tooltipData ) { |
|
124 that._updateContent( tooltipData.element ); |
|
125 } ); |
|
126 } |
|
127 }, |
|
128 |
|
129 _setOptionDisabled: function( value ) { |
|
130 this[ value ? "_disable" : "_enable" ](); |
|
131 }, |
|
132 |
|
133 _disable: function() { |
|
134 var that = this; |
|
135 |
|
136 // Close open tooltips |
|
137 $.each( this.tooltips, function( id, tooltipData ) { |
|
138 var event = $.Event( "blur" ); |
|
139 event.target = event.currentTarget = tooltipData.element[ 0 ]; |
|
140 that.close( event, true ); |
|
141 } ); |
|
142 |
|
143 // Remove title attributes to prevent native tooltips |
|
144 this.disabledTitles = this.disabledTitles.add( |
|
145 this.element.find( this.options.items ).addBack() |
|
146 .filter( function() { |
|
147 var element = $( this ); |
|
148 if ( element.is( "[title]" ) ) { |
|
149 return element |
|
150 .data( "ui-tooltip-title", element.attr( "title" ) ) |
|
151 .removeAttr( "title" ); |
|
152 } |
|
153 } ) |
|
154 ); |
|
155 }, |
|
156 |
|
157 _enable: function() { |
|
158 |
|
159 // restore title attributes |
|
160 this.disabledTitles.each( function() { |
|
161 var element = $( this ); |
|
162 if ( element.data( "ui-tooltip-title" ) ) { |
|
163 element.attr( "title", element.data( "ui-tooltip-title" ) ); |
|
164 } |
|
165 } ); |
|
166 this.disabledTitles = $( [] ); |
|
167 }, |
|
168 |
|
169 open: function( event ) { |
|
170 var that = this, |
|
171 target = $( event ? event.target : this.element ) |
|
172 |
|
173 // we need closest here due to mouseover bubbling, |
|
174 // but always pointing at the same event target |
|
175 .closest( this.options.items ); |
|
176 |
|
177 // No element to show a tooltip for or the tooltip is already open |
|
178 if ( !target.length || target.data( "ui-tooltip-id" ) ) { |
|
179 return; |
|
180 } |
|
181 |
|
182 if ( target.attr( "title" ) ) { |
|
183 target.data( "ui-tooltip-title", target.attr( "title" ) ); |
|
184 } |
|
185 |
|
186 target.data( "ui-tooltip-open", true ); |
|
187 |
|
188 // Kill parent tooltips, custom or native, for hover |
|
189 if ( event && event.type === "mouseover" ) { |
|
190 target.parents().each( function() { |
|
191 var parent = $( this ), |
|
192 blurEvent; |
|
193 if ( parent.data( "ui-tooltip-open" ) ) { |
|
194 blurEvent = $.Event( "blur" ); |
|
195 blurEvent.target = blurEvent.currentTarget = this; |
|
196 that.close( blurEvent, true ); |
|
197 } |
|
198 if ( parent.attr( "title" ) ) { |
|
199 parent.uniqueId(); |
|
200 that.parents[ this.id ] = { |
|
201 element: this, |
|
202 title: parent.attr( "title" ) |
|
203 }; |
|
204 parent.attr( "title", "" ); |
|
205 } |
|
206 } ); |
|
207 } |
|
208 |
|
209 this._registerCloseHandlers( event, target ); |
|
210 this._updateContent( target, event ); |
|
211 }, |
|
212 |
|
213 _updateContent: function( target, event ) { |
|
214 var content, |
|
215 contentOption = this.options.content, |
|
216 that = this, |
|
217 eventType = event ? event.type : null; |
|
218 |
|
219 if ( typeof contentOption === "string" || contentOption.nodeType || |
|
220 contentOption.jquery ) { |
|
221 return this._open( event, target, contentOption ); |
|
222 } |
|
223 |
|
224 content = contentOption.call( target[ 0 ], function( response ) { |
|
225 |
|
226 // IE may instantly serve a cached response for ajax requests |
|
227 // delay this call to _open so the other call to _open runs first |
|
228 that._delay( function() { |
|
229 |
|
230 // Ignore async response if tooltip was closed already |
|
231 if ( !target.data( "ui-tooltip-open" ) ) { |
|
232 return; |
|
233 } |
|
234 |
|
235 // JQuery creates a special event for focusin when it doesn't |
|
236 // exist natively. To improve performance, the native event |
|
237 // object is reused and the type is changed. Therefore, we can't |
|
238 // rely on the type being correct after the event finished |
|
239 // bubbling, so we set it back to the previous value. (#8740) |
|
240 if ( event ) { |
|
241 event.type = eventType; |
|
242 } |
|
243 this._open( event, target, response ); |
|
244 } ); |
|
245 } ); |
|
246 if ( content ) { |
|
247 this._open( event, target, content ); |
|
248 } |
|
249 }, |
|
250 |
|
251 _open: function( event, target, content ) { |
|
252 var tooltipData, tooltip, delayedShow, a11yContent, |
|
253 positionOption = $.extend( {}, this.options.position ); |
|
254 |
|
255 if ( !content ) { |
|
256 return; |
|
257 } |
|
258 |
|
259 // Content can be updated multiple times. If the tooltip already |
|
260 // exists, then just update the content and bail. |
|
261 tooltipData = this._find( target ); |
|
262 if ( tooltipData ) { |
|
263 tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content ); |
|
264 return; |
|
265 } |
|
266 |
|
267 // If we have a title, clear it to prevent the native tooltip |
|
268 // we have to check first to avoid defining a title if none exists |
|
269 // (we don't want to cause an element to start matching [title]) |
|
270 // |
|
271 // We use removeAttr only for key events, to allow IE to export the correct |
|
272 // accessible attributes. For mouse events, set to empty string to avoid |
|
273 // native tooltip showing up (happens only when removing inside mouseover). |
|
274 if ( target.is( "[title]" ) ) { |
|
275 if ( event && event.type === "mouseover" ) { |
|
276 target.attr( "title", "" ); |
|
277 } else { |
|
278 target.removeAttr( "title" ); |
|
279 } |
|
280 } |
|
281 |
|
282 tooltipData = this._tooltip( target ); |
|
283 tooltip = tooltipData.tooltip; |
|
284 this._addDescribedBy( target, tooltip.attr( "id" ) ); |
|
285 tooltip.find( ".ui-tooltip-content" ).html( content ); |
|
286 |
|
287 // Support: Voiceover on OS X, JAWS on IE <= 9 |
|
288 // JAWS announces deletions even when aria-relevant="additions" |
|
289 // Voiceover will sometimes re-read the entire log region's contents from the beginning |
|
290 this.liveRegion.children().hide(); |
|
291 a11yContent = $( "<div>" ).html( tooltip.find( ".ui-tooltip-content" ).html() ); |
|
292 a11yContent.removeAttr( "name" ).find( "[name]" ).removeAttr( "name" ); |
|
293 a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" ); |
|
294 a11yContent.appendTo( this.liveRegion ); |
|
295 |
|
296 function position( event ) { |
|
297 positionOption.of = event; |
|
298 if ( tooltip.is( ":hidden" ) ) { |
|
299 return; |
|
300 } |
|
301 tooltip.position( positionOption ); |
|
302 } |
|
303 if ( this.options.track && event && /^mouse/.test( event.type ) ) { |
|
304 this._on( this.document, { |
|
305 mousemove: position |
|
306 } ); |
|
307 |
|
308 // trigger once to override element-relative positioning |
|
309 position( event ); |
|
310 } else { |
|
311 tooltip.position( $.extend( { |
|
312 of: target |
|
313 }, this.options.position ) ); |
|
314 } |
|
315 |
|
316 tooltip.hide(); |
|
317 |
|
318 this._show( tooltip, this.options.show ); |
|
319 |
|
320 // Handle tracking tooltips that are shown with a delay (#8644). As soon |
|
321 // as the tooltip is visible, position the tooltip using the most recent |
|
322 // event. |
|
323 // Adds the check to add the timers only when both delay and track options are set (#14682) |
|
324 if ( this.options.track && this.options.show && this.options.show.delay ) { |
|
325 delayedShow = this.delayedShow = setInterval( function() { |
|
326 if ( tooltip.is( ":visible" ) ) { |
|
327 position( positionOption.of ); |
|
328 clearInterval( delayedShow ); |
|
329 } |
|
330 }, $.fx.interval ); |
|
331 } |
|
332 |
|
333 this._trigger( "open", event, { tooltip: tooltip } ); |
|
334 }, |
|
335 |
|
336 _registerCloseHandlers: function( event, target ) { |
|
337 var events = { |
|
338 keyup: function( event ) { |
|
339 if ( event.keyCode === $.ui.keyCode.ESCAPE ) { |
|
340 var fakeEvent = $.Event( event ); |
|
341 fakeEvent.currentTarget = target[ 0 ]; |
|
342 this.close( fakeEvent, true ); |
|
343 } |
|
344 } |
|
345 }; |
|
346 |
|
347 // Only bind remove handler for delegated targets. Non-delegated |
|
348 // tooltips will handle this in destroy. |
|
349 if ( target[ 0 ] !== this.element[ 0 ] ) { |
|
350 events.remove = function() { |
|
351 this._removeTooltip( this._find( target ).tooltip ); |
|
352 }; |
|
353 } |
|
354 |
|
355 if ( !event || event.type === "mouseover" ) { |
|
356 events.mouseleave = "close"; |
|
357 } |
|
358 if ( !event || event.type === "focusin" ) { |
|
359 events.focusout = "close"; |
|
360 } |
|
361 this._on( true, target, events ); |
|
362 }, |
|
363 |
|
364 close: function( event ) { |
|
365 var tooltip, |
|
366 that = this, |
|
367 target = $( event ? event.currentTarget : this.element ), |
|
368 tooltipData = this._find( target ); |
|
369 |
|
370 // The tooltip may already be closed |
|
371 if ( !tooltipData ) { |
|
372 |
|
373 // We set ui-tooltip-open immediately upon open (in open()), but only set the |
|
374 // additional data once there's actually content to show (in _open()). So even if the |
|
375 // tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in |
|
376 // the period between open() and _open(). |
|
377 target.removeData( "ui-tooltip-open" ); |
|
378 return; |
|
379 } |
|
380 |
|
381 tooltip = tooltipData.tooltip; |
|
382 |
|
383 // Disabling closes the tooltip, so we need to track when we're closing |
|
384 // to avoid an infinite loop in case the tooltip becomes disabled on close |
|
385 if ( tooltipData.closing ) { |
|
386 return; |
|
387 } |
|
388 |
|
389 // Clear the interval for delayed tracking tooltips |
|
390 clearInterval( this.delayedShow ); |
|
391 |
|
392 // Only set title if we had one before (see comment in _open()) |
|
393 // If the title attribute has changed since open(), don't restore |
|
394 if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) { |
|
395 target.attr( "title", target.data( "ui-tooltip-title" ) ); |
|
396 } |
|
397 |
|
398 this._removeDescribedBy( target ); |
|
399 |
|
400 tooltipData.hiding = true; |
|
401 tooltip.stop( true ); |
|
402 this._hide( tooltip, this.options.hide, function() { |
|
403 that._removeTooltip( $( this ) ); |
|
404 } ); |
|
405 |
|
406 target.removeData( "ui-tooltip-open" ); |
|
407 this._off( target, "mouseleave focusout keyup" ); |
|
408 |
|
409 // Remove 'remove' binding only on delegated targets |
|
410 if ( target[ 0 ] !== this.element[ 0 ] ) { |
|
411 this._off( target, "remove" ); |
|
412 } |
|
413 this._off( this.document, "mousemove" ); |
|
414 |
|
415 if ( event && event.type === "mouseleave" ) { |
|
416 $.each( this.parents, function( id, parent ) { |
|
417 $( parent.element ).attr( "title", parent.title ); |
|
418 delete that.parents[ id ]; |
|
419 } ); |
|
420 } |
|
421 |
|
422 tooltipData.closing = true; |
|
423 this._trigger( "close", event, { tooltip: tooltip } ); |
|
424 if ( !tooltipData.hiding ) { |
|
425 tooltipData.closing = false; |
|
426 } |
|
427 }, |
|
428 |
|
429 _tooltip: function( element ) { |
|
430 var tooltip = $( "<div>" ).attr( "role", "tooltip" ), |
|
431 content = $( "<div>" ).appendTo( tooltip ), |
|
432 id = tooltip.uniqueId().attr( "id" ); |
|
433 |
|
434 this._addClass( content, "ui-tooltip-content" ); |
|
435 this._addClass( tooltip, "ui-tooltip", "ui-widget ui-widget-content" ); |
|
436 |
|
437 tooltip.appendTo( this._appendTo( element ) ); |
|
438 |
|
439 return this.tooltips[ id ] = { |
|
440 element: element, |
|
441 tooltip: tooltip |
|
442 }; |
|
443 }, |
|
444 |
|
445 _find: function( target ) { |
|
446 var id = target.data( "ui-tooltip-id" ); |
|
447 return id ? this.tooltips[ id ] : null; |
|
448 }, |
|
449 |
|
450 _removeTooltip: function( tooltip ) { |
|
451 tooltip.remove(); |
|
452 delete this.tooltips[ tooltip.attr( "id" ) ]; |
|
453 }, |
|
454 |
|
455 _appendTo: function( target ) { |
|
456 var element = target.closest( ".ui-front, dialog" ); |
|
457 |
|
458 if ( !element.length ) { |
|
459 element = this.document[ 0 ].body; |
|
460 } |
|
461 |
|
462 return element; |
|
463 }, |
|
464 |
|
465 _destroy: function() { |
|
466 var that = this; |
|
467 |
|
468 // Close open tooltips |
|
469 $.each( this.tooltips, function( id, tooltipData ) { |
|
470 |
|
471 // Delegate to close method to handle common cleanup |
|
472 var event = $.Event( "blur" ), |
|
473 element = tooltipData.element; |
|
474 event.target = event.currentTarget = element[ 0 ]; |
|
475 that.close( event, true ); |
|
476 |
|
477 // Remove immediately; destroying an open tooltip doesn't use the |
|
478 // hide animation |
|
479 $( "#" + id ).remove(); |
|
480 |
|
481 // Restore the title |
|
482 if ( element.data( "ui-tooltip-title" ) ) { |
|
483 |
|
484 // If the title attribute has changed since open(), don't restore |
|
485 if ( !element.attr( "title" ) ) { |
|
486 element.attr( "title", element.data( "ui-tooltip-title" ) ); |
|
487 } |
|
488 element.removeData( "ui-tooltip-title" ); |
|
489 } |
|
490 } ); |
|
491 this.liveRegion.remove(); |
|
492 } |
|
493 } ); |
|
494 |
|
495 // DEPRECATED |
|
496 // TODO: Switch return back to widget declaration at top of file when this is removed |
|
497 if ( $.uiBackCompat !== false ) { |
|
498 |
|
499 // Backcompat for tooltipClass option |
|
500 $.widget( "ui.tooltip", $.ui.tooltip, { |
|
501 options: { |
|
502 tooltipClass: null |
|
503 }, |
|
504 _tooltip: function() { |
|
505 var tooltipData = this._superApply( arguments ); |
|
506 if ( this.options.tooltipClass ) { |
|
507 tooltipData.tooltip.addClass( this.options.tooltipClass ); |
|
508 } |
|
509 return tooltipData; |
|
510 } |
|
511 } ); |
|
512 } |
|
513 |
|
514 return $.ui.tooltip; |
|
515 |
|
516 } ) ); |