You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

523 lines
14 KiB

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