upgrade jquery-ui tooltip to unpatched 1.9.0-beta.1
Part of unblocking the jQuery 2.x upgrade. refs FOO-4258 flag=none Test Plan: - Jenkins passes [skip-crystalball] Change-Id: I6a5d125a9944af1528a0f70fbcccba5efc06abf6 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/340321 QA-Review: Jen Smith <jen.smith@instructure.com> Product-Review: Jen Smith <jen.smith@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Michael Hulse <michael.hulse@instructure.com>
This commit is contained in:
parent
c59b72393c
commit
e3692e8f35
|
@ -0,0 +1,302 @@
|
|||
/*!
|
||||
* jQuery UI Tooltip 1.9.0-beta.1
|
||||
*
|
||||
* Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
|
||||
* Dual licensed under the MIT or GPL Version 2 licenses.
|
||||
* http://jquery.org/license
|
||||
*
|
||||
* Depends:
|
||||
* jquery.ui.core.js
|
||||
* jquery.ui.widget.js
|
||||
* jquery.ui.position.js
|
||||
*/
|
||||
(function( $ ) {
|
||||
|
||||
var increments = 0;
|
||||
|
||||
function addDescribedBy( elem, id ) {
|
||||
var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
|
||||
describedby.push( id );
|
||||
elem
|
||||
.data( "ui-tooltip-id", id )
|
||||
.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
|
||||
}
|
||||
|
||||
function removeDescribedBy( elem ) {
|
||||
var id = elem.data( "ui-tooltip-id" ),
|
||||
describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
|
||||
index = $.inArray( id, describedby );
|
||||
if ( index !== -1 ) {
|
||||
describedby.splice( index, 1 );
|
||||
}
|
||||
|
||||
elem.removeData( "ui-tooltip-id" );
|
||||
describedby = $.trim( describedby.join( " " ) );
|
||||
if ( describedby ) {
|
||||
elem.attr( "aria-describedby", describedby );
|
||||
} else {
|
||||
elem.removeAttr( "aria-describedby" );
|
||||
}
|
||||
}
|
||||
|
||||
$.widget( "ui.tooltip", {
|
||||
version: "1.9.0-beta.1",
|
||||
options: {
|
||||
content: function() {
|
||||
return $( this ).attr( "title" );
|
||||
},
|
||||
hide: true,
|
||||
items: "[title]",
|
||||
position: {
|
||||
my: "left+15 center",
|
||||
at: "right center",
|
||||
collision: "flipfit flipfit"
|
||||
},
|
||||
show: true,
|
||||
tooltipClass: null,
|
||||
track: false,
|
||||
|
||||
// callbacks
|
||||
close: null,
|
||||
open: null
|
||||
},
|
||||
|
||||
_create: function() {
|
||||
this._on({
|
||||
mouseover: "open",
|
||||
focusin: "open"
|
||||
});
|
||||
|
||||
// IDs of generated tooltips, needed for destroy
|
||||
this.tooltips = {};
|
||||
},
|
||||
|
||||
_setOption: function( key, value ) {
|
||||
if ( key === "disabled" ) {
|
||||
this[ value ? "_disable" : "_enable" ]();
|
||||
this.options[ key ] = value;
|
||||
// disable element style changes
|
||||
return;
|
||||
}
|
||||
this._super( key, value );
|
||||
},
|
||||
|
||||
_disable: function() {
|
||||
var that = this;
|
||||
|
||||
// close open tooltips
|
||||
$.each( this.tooltips, function( id, element ) {
|
||||
var event = $.Event( "blur" );
|
||||
event.target = event.currentTarget = element[0];
|
||||
that.close( event, true );
|
||||
});
|
||||
|
||||
// remove title attributes to prevent native tooltips
|
||||
this.element.find( this.options.items ).andSelf().each(function() {
|
||||
var element = $( this );
|
||||
if ( element.is( "[title]" ) ) {
|
||||
element
|
||||
.data( "ui-tooltip-title", element.attr( "title" ) )
|
||||
.attr( "title", "" );
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_enable: function() {
|
||||
// restore title attributes
|
||||
this.element.find( this.options.items ).andSelf().each(function() {
|
||||
var element = $( this );
|
||||
if ( element.data( "ui-tooltip-title" ) ) {
|
||||
element.attr( "title", element.data( "ui-tooltip-title" ) );
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
open: function( event ) {
|
||||
var content,
|
||||
that = this,
|
||||
target = $( event ? event.target : this.element )
|
||||
.closest( this.options.items );
|
||||
|
||||
// No element to show a tooltip for
|
||||
if ( !target.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the tooltip is open and we're tracking then reposition the tooltip.
|
||||
// This makes sure that a tracking tooltip doesn't obscure a focused element
|
||||
// if the user was hovering when the element gained focused.
|
||||
if ( this.options.track && target.data( "ui-tooltip-id" ) ) {
|
||||
this._find( target ).position( $.extend({
|
||||
of: target
|
||||
}, this.options.position ) );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( target.attr( "title" ) ) {
|
||||
target.data( "ui-tooltip-title", target.attr( "title" ) );
|
||||
}
|
||||
|
||||
target.data( "tooltip-open", true );
|
||||
|
||||
content = this.options.content.call( target[0], function( response ) {
|
||||
// ignore async response if tooltip was closed already
|
||||
if ( !target.data( "tooltip-open" ) ) {
|
||||
return;
|
||||
}
|
||||
// IE may instantly serve a cached response for ajax requests
|
||||
// delay this call to _open so the other call to _open runs first
|
||||
setTimeout(function() {
|
||||
that._open( event, target, response );
|
||||
}, 1 );
|
||||
});
|
||||
if ( content ) {
|
||||
that._open( event, target, content );
|
||||
}
|
||||
},
|
||||
|
||||
_open: function( event, target, content ) {
|
||||
var tooltip, positionOption;
|
||||
if ( !content ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Content can be updated multiple times. If the tooltip already
|
||||
// exists, then just update the content and bail.
|
||||
tooltip = this._find( target );
|
||||
if ( tooltip.length ) {
|
||||
tooltip.find( ".ui-tooltip-content" ).html( content );
|
||||
return;
|
||||
}
|
||||
|
||||
// if we have a title, clear it to prevent the native tooltip
|
||||
// we have to check first to avoid defining a title if none exists
|
||||
// (we don't want to cause an element to start matching [title])
|
||||
//
|
||||
// We use removeAttr only for key events, to allow IE to export the correct
|
||||
// accessible attributes. For mouse events, set to empty string to avoid
|
||||
// native tooltip showing up (happens only when removing inside mouseover).
|
||||
if ( target.is( "[title]" ) ) {
|
||||
if ( event && event.type === "mouseover" ) {
|
||||
target.attr( "title", "" );
|
||||
} else {
|
||||
target.removeAttr( "title" );
|
||||
}
|
||||
}
|
||||
|
||||
tooltip = this._tooltip( target );
|
||||
addDescribedBy( target, tooltip.attr( "id" ) );
|
||||
tooltip.find( ".ui-tooltip-content" ).html( content );
|
||||
|
||||
function position( event ) {
|
||||
positionOption.of = event;
|
||||
tooltip.position( positionOption );
|
||||
}
|
||||
if ( this.options.track && /^mouse/.test( event.originalEvent.type ) ) {
|
||||
positionOption = $.extend( {}, this.options.position );
|
||||
this._on( this.document, {
|
||||
mousemove: position
|
||||
});
|
||||
// trigger once to override element-relative positioning
|
||||
position( event );
|
||||
} else {
|
||||
tooltip.position( $.extend({
|
||||
of: target
|
||||
}, this.options.position ) );
|
||||
}
|
||||
|
||||
tooltip.hide();
|
||||
|
||||
this._show( tooltip, this.options.show );
|
||||
|
||||
this._trigger( "open", event, { tooltip: tooltip } );
|
||||
|
||||
this._on( target, {
|
||||
mouseleave: "close",
|
||||
focusout: "close",
|
||||
keyup: function( event ) {
|
||||
if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
|
||||
var fakeEvent = $.Event(event);
|
||||
fakeEvent.currentTarget = target[0];
|
||||
this.close( fakeEvent, true );
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
close: function( event, force ) {
|
||||
var that = this,
|
||||
target = $( event ? event.currentTarget : this.element ),
|
||||
tooltip = this._find( target );
|
||||
|
||||
// disabling closes the tooltip, so we need to track when we're closing
|
||||
// to avoid an infinite loop in case the tooltip becomes disabled on close
|
||||
if ( this.closing ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// don't close if the element has focus
|
||||
// this prevents the tooltip from closing if you hover while focused
|
||||
//
|
||||
// we have to check the event type because tabbing out of the document
|
||||
// may leave the element as the activeElement
|
||||
if ( !force && event && event.type !== "focusout" &&
|
||||
this.document[0].activeElement === target[0] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only set title if we had one before (see comment in _open())
|
||||
if ( target.data( "ui-tooltip-title" ) ) {
|
||||
target.attr( "title", target.data( "ui-tooltip-title" ) );
|
||||
}
|
||||
|
||||
removeDescribedBy( target );
|
||||
|
||||
tooltip.stop( true );
|
||||
this._hide( tooltip, this.options.hide, function() {
|
||||
$( this ).remove();
|
||||
delete that.tooltips[ this.id ];
|
||||
});
|
||||
|
||||
target.removeData( "tooltip-open" );
|
||||
this._off( target, "mouseleave focusout keyup" );
|
||||
this._off( this.document, "mousemove" );
|
||||
|
||||
this.closing = true;
|
||||
this._trigger( "close", event, { tooltip: tooltip } );
|
||||
this.closing = false;
|
||||
},
|
||||
|
||||
_tooltip: function( element ) {
|
||||
var id = "ui-tooltip-" + increments++,
|
||||
tooltip = $( "<div>" )
|
||||
.attr({
|
||||
id: id,
|
||||
role: "tooltip"
|
||||
})
|
||||
.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
|
||||
( this.options.tooltipClass || "" ) );
|
||||
$( "<div>" )
|
||||
.addClass( "ui-tooltip-content" )
|
||||
.appendTo( tooltip );
|
||||
tooltip.appendTo( this.document[0].body );
|
||||
if ( $.fn.bgiframe ) {
|
||||
tooltip.bgiframe();
|
||||
}
|
||||
this.tooltips[ id ] = element;
|
||||
return tooltip;
|
||||
},
|
||||
|
||||
_find: function( target ) {
|
||||
var id = target.data( "ui-tooltip-id" );
|
||||
return id ? $( "#" + id ) : $();
|
||||
},
|
||||
|
||||
_destroy: function() {
|
||||
$.each( this.tooltips, function( id ) {
|
||||
$( "#" + id ).remove();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}( jQuery ) );
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2024 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import $ from 'jquery'
|
||||
import 'jqueryui-unpatched/core'
|
||||
import 'jqueryui-unpatched/widget'
|
||||
import 'jqueryui-unpatched/position'
|
||||
import 'jqueryui-unpatched/tooltip'
|
||||
|
||||
QUnit.module('tooltip widget', {
|
||||
beforeEach() {
|
||||
$('#fixtures').append('<div id="test-tooltip" title="tooltip text">hover over me</div>')
|
||||
$('#test-tooltip').tooltip()
|
||||
},
|
||||
afterEach() {
|
||||
const $tooltipTarget = $('#test-tooltip')
|
||||
// prevents garbage collection test from breaking
|
||||
if ($tooltipTarget.data('ui-tooltip')) {
|
||||
$tooltipTarget.tooltip('destroy')
|
||||
}
|
||||
$('#fixtures').empty()
|
||||
},
|
||||
})
|
||||
|
||||
QUnit.test('tooltip shows on mouseenter', function (assert) {
|
||||
const done = assert.async()
|
||||
const $tooltipTarget = $('#test-tooltip')
|
||||
$tooltipTarget.on('tooltipopen', function () {
|
||||
assert.ok(true, 'tooltip is shown on mouseenter')
|
||||
done()
|
||||
})
|
||||
$tooltipTarget.trigger('mouseenter')
|
||||
})
|
||||
|
||||
QUnit.test('tooltip hides on mouseleave', function (assert) {
|
||||
const done = assert.async()
|
||||
const $tooltipTarget = $('#test-tooltip')
|
||||
$tooltipTarget.tooltip('open')
|
||||
$tooltipTarget.on('tooltipclose', function () {
|
||||
assert.ok(true, 'tooltip is hidden on mouseleave')
|
||||
done()
|
||||
})
|
||||
$tooltipTarget.trigger('mouseleave')
|
||||
})
|
||||
|
||||
QUnit.test('Custom content is displayed', function (assert) {
|
||||
const customContent = 'Custom tooltip content'
|
||||
const $tooltipTarget = $('#test-tooltip')
|
||||
$tooltipTarget.tooltip('option', 'content', function () {
|
||||
return customContent
|
||||
})
|
||||
$tooltipTarget.tooltip('open')
|
||||
const tooltipContent = $('.ui-tooltip-content').text()
|
||||
assert.equal(tooltipContent, customContent, 'tooltip displays custom content')
|
||||
})
|
||||
|
||||
QUnit.test('tooltip can be disabled and re-enabled', function (assert) {
|
||||
const done = assert.async()
|
||||
const $tooltipTarget = $('#test-tooltip')
|
||||
$tooltipTarget.tooltip('disable')
|
||||
$tooltipTarget.tooltip('open')
|
||||
assert.strictEqual($('.ui-tooltip').length, 0, 'tooltip does not open when disabled')
|
||||
$tooltipTarget.tooltip('enable')
|
||||
// wait for the UI to update before checking the tooltip’s presence
|
||||
setTimeout(() => {
|
||||
$('#test-tooltip').tooltip('open')
|
||||
setTimeout(() => {
|
||||
// check that at least one tooltip is present
|
||||
assert.ok($('.ui-tooltip').length > 0, 'tooltip opens after being re-enabled')
|
||||
// signal QUnit that the asynchronous operations are complete
|
||||
done()
|
||||
}, 100)
|
||||
}, 100)
|
||||
})
|
||||
|
||||
QUnit.test('tooltip is fully cleaned up after destruction (garbage collection)', function (assert) {
|
||||
const $tooltipTarget = $('#test-tooltip')
|
||||
$tooltipTarget.tooltip('destroy')
|
||||
assert.strictEqual($('.ui-tooltip').length, 0, 'no tooltip elements remain after destruction')
|
||||
assert.strictEqual(
|
||||
$tooltipTarget.attr('title'),
|
||||
'tooltip text',
|
||||
'title attribute is restored after tooltip destruction'
|
||||
)
|
||||
})
|
Loading…
Reference in New Issue