From c426b9a203271ab5e5e5f165a1d686c8281164bf Mon Sep 17 00:00:00 2001 From: Konstantin Dinev Date: Tue, 28 Feb 2017 18:21:46 +0200 Subject: Resizable: Keep user-provided handles on destroy Closes gh-1798 Ref gh-1795 --- ui/widgets/resizable.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'ui/widgets') diff --git a/ui/widgets/resizable.js b/ui/widgets/resizable.js index b5264ee53..36dd12514 100644 --- a/ui/widgets/resizable.js +++ b/ui/widgets/resizable.js @@ -187,15 +187,14 @@ $.widget( "ui.resizable", $.ui.mouse, { _destroy: function() { this._mouseDestroy(); + this._addedHandles.remove(); var wrapper, _destroy = function( exp ) { $( exp ) .removeData( "resizable" ) .removeData( "ui-resizable" ) - .off( ".resizable" ) - .find( ".ui-resizable-handle" ) - .remove(); + .off( ".resizable" ); }; // TODO: Unwrap at same DOM position -- cgit v1.2.3 From 0d25a36eecb9e5598596208e4852b3c3fdbf5510 Mon Sep 17 00:00:00 2001 From: Scott González Date: Tue, 18 Apr 2017 17:37:15 -0400 Subject: Menu: Close menus immediately on selection or click outside Fixes #15034 Closes gh-1807 --- tests/unit/menu/events.js | 4 +++- ui/widgets/menu.js | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'ui/widgets') diff --git a/tests/unit/menu/events.js b/tests/unit/menu/events.js index a8ccb0282..90507bddc 100644 --- a/tests/unit/menu/events.js +++ b/tests/unit/menu/events.js @@ -670,7 +670,9 @@ QUnit.test( "handle keyboard navigation and mouse click on menu with dividers an element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); - assert.equal( logOutput(), "keydown,3,4,7", "Keydown focus skips divider and group label" ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + assert.equal( logOutput(), "keydown,1,2,3,4,7", "Keydown focus skips divider and group label" ); ready(); } } ); diff --git a/ui/widgets/menu.js b/ui/widgets/menu.js index 49da26865..19d9b90df 100644 --- a/ui/widgets/menu.js +++ b/ui/widgets/menu.js @@ -162,7 +162,7 @@ return $.widget( "ui.menu", { this._on( this.document, { click: function( event ) { if ( this._closeOnDocumentClick( event ) ) { - this.collapseAll( event ); + this.collapseAll( event, true ); } // Reset the mouseHandled flag @@ -502,7 +502,7 @@ return $.widget( "ui.menu", { this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" ); this.activeMenu = currentMenu; - }, this.delay ); + }, all ? 0 : this.delay ); }, // With no arguments, closes the currently active menu - if nothing is active -- cgit v1.2.3 From c866e455373028a62a0956455a229fef63e91fac Mon Sep 17 00:00:00 2001 From: "A. Wells" Date: Thu, 16 Feb 2017 13:48:45 -0500 Subject: Sortable: Fix various scrolling issues * Created _scroll extension point and migrated scroll code from _mouseDrag * Cleaned up logic for scrolled * Fixed appendTo functionality to match documentation * Remove unnecessary function calls * Move set-up position functions to appropriate place * Base scrollParent on placeholder and not helper * Update scrollParent when switching containers Fixes #3173 Fixes #15165 Fixes #15166 Fixes #15167 Fixes #15168 Fixes #15169 Fixes #15170 Closes gh-1793 --- ui/widgets/sortable.js | 297 ++++++++++++++++++++++++++++--------------------- 1 file changed, 168 insertions(+), 129 deletions(-) (limited to 'ui/widgets') diff --git a/ui/widgets/sortable.js b/ui/widgets/sortable.js index fcb33ace1..78fad26b9 100644 --- a/ui/widgets/sortable.js +++ b/ui/widgets/sortable.js @@ -195,6 +195,11 @@ return $.widget( "ui.sortable", $.ui.mouse, { // mouseCapture this.refreshPositions(); + //Prepare the dragged items parent + this.appendTo = $( o.appendTo !== "parent" ? + o.appendTo : + this.currentItem.parent() ); + //Create and append the visible helper this.helper = this._createHelper( event ); @@ -209,9 +214,6 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Cache the margins of the original element this._cacheMargins(); - //Get the next scrolling parent - this.scrollParent = this.helper.scrollParent(); - //The element's absolute position on the page minus margins this.offset = this.currentItem.offset(); this.offset = { @@ -236,15 +238,6 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.helper.css( "position", "absolute" ); this.cssPosition = this.helper.css( "position" ); - $.extend( this.offset, { - parent: this._getParentOffset() - } ); - - //Generate the original position - this.originalPosition = this._generatePosition( event ); - this.originalPageX = event.pageX; - this.originalPageY = event.pageY; - //Adjust the mouse offset relative to the helper if "cursorAt" is supplied ( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) ); @@ -263,6 +256,13 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Create the placeholder this._createPlaceholder(); + //Get the next scrolling parent + this.scrollParent = this.placeholder.scrollParent(); + + $.extend( this.offset, { + parent: this._getParentOffset() + } ); + //Set a containment if given in the options if ( o.containment ) { this._setContainment(); @@ -330,77 +330,82 @@ return $.widget( "ui.sortable", $.ui.mouse, { this._addClass( this.helper, "ui-sortable-helper" ); - // Execute the drag once - this causes the helper not to be visiblebefore getting its - // correct position - this._mouseDrag( event ); - return true; + //Move the helper, if needed + if ( !this.helper.parent().is( this.appendTo ) ) { + this.helper.detach().appendTo( this.appendTo ); - }, + //Update position + this.offset.parent = this._getParentOffset(); + } - _mouseDrag: function( event ) { - var i, item, itemElement, intersection, - o = this.options, - scrolled = false; + //Generate the original position + this.position = this.originalPosition = this._generatePosition( event ); + this.originalPageX = event.pageX; + this.originalPageY = event.pageY; + this.lastPositionAbs = this.positionAbs = this._convertPositionTo( "absolute" ); - //Compute the helpers position - this.position = this._generatePosition( event ); - this.positionAbs = this._convertPositionTo( "absolute" ); + this._mouseDrag( event ); - if ( !this.lastPositionAbs ) { - this.lastPositionAbs = this.positionAbs; - } + return true; - //Do scrolling - if ( this.options.scroll ) { - if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && - this.scrollParent[ 0 ].tagName !== "HTML" ) { + }, - if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - - event.pageY < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; - } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollTop = - scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; - } + _scroll: function( event ) { + var o = this.options, + scrolled = false; - if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - - event.pageX < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; - } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { - this.scrollParent[ 0 ].scrollLeft = scrolled = - this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; - } + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { - } else { + if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) - + event.pageY < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed; + } else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollTop = + scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed; + } - if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); - } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); - } + if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) - + event.pageX < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed; + } else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) { + this.scrollParent[ 0 ].scrollLeft = scrolled = + this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed; + } - if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() - o.scrollSpeed - ); - } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < - o.scrollSensitivity ) { - scrolled = this.document.scrollLeft( - this.document.scrollLeft() + o.scrollSpeed - ); - } + } else { + if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed ); + } else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed ); } - if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) { - $.ui.ddmanager.prepareOffsets( this, event ); + if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() - o.scrollSpeed + ); + } else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) < + o.scrollSensitivity ) { + scrolled = this.document.scrollLeft( + this.document.scrollLeft() + o.scrollSpeed + ); } + } - //Regenerate the absolute position used for position checks + return scrolled; + }, + + _mouseDrag: function( event ) { + var i, item, itemElement, intersection, + o = this.options; + + //Compute the helpers position + this.position = this._generatePosition( event ); this.positionAbs = this._convertPositionTo( "absolute" ); //Set the helper position @@ -411,56 +416,79 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.helper[ 0 ].style.top = this.position.top + "px"; } - //Rearrange - for ( i = this.items.length - 1; i >= 0; i-- ) { + //Post events to containers + this._contactContainers( event ); - //Cache variables and intersection, continue if no intersection - item = this.items[ i ]; - itemElement = item.item[ 0 ]; - intersection = this._intersectsWithPointer( item ); - if ( !intersection ) { - continue; - } + if ( this.innermostContainer !== null ) { - // Only put the placeholder inside the current Container, skip all - // items from other containers. This works because when moving - // an item from one container to another the - // currentContainer is switched before the placeholder is moved. - // - // Without this, moving items in "sub-sortables" can cause - // the placeholder to jitter between the outer and inner container. - if ( item.instance !== this.currentContainer ) { - continue; + //Do scrolling + if ( o.scroll ) { + if ( this._scroll( event ) !== false ) { + + //Update item positions used in position checks + this._refreshItemPositions( true ); + + if ( $.ui.ddmanager && !o.dropBehaviour ) { + $.ui.ddmanager.prepareOffsets( this, event ); + } + } } - // Cannot intersect with itself - // no useless actions that have been done before - // no action if the item moved is the parent of the item checked - if ( itemElement !== this.currentItem[ 0 ] && - this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement && - !$.contains( this.placeholder[ 0 ], itemElement ) && - ( this.options.type === "semi-dynamic" ? - !$.contains( this.element[ 0 ], itemElement ) : - true - ) - ) { - - this.direction = intersection === 1 ? "down" : "up"; - - if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) { - this._rearrange( event, item ); - } else { - break; + this.dragDirection = { + vertical: this._getDragVerticalDirection(), + horizontal: this._getDragHorizontalDirection() + }; + + //Rearrange + for ( i = this.items.length - 1; i >= 0; i-- ) { + + //Cache variables and intersection, continue if no intersection + item = this.items[ i ]; + itemElement = item.item[ 0 ]; + intersection = this._intersectsWithPointer( item ); + if ( !intersection ) { + continue; + } + + // Only put the placeholder inside the current Container, skip all + // items from other containers. This works because when moving + // an item from one container to another the + // currentContainer is switched before the placeholder is moved. + // + // Without this, moving items in "sub-sortables" can cause + // the placeholder to jitter between the outer and inner container. + if ( item.instance !== this.currentContainer ) { + continue; } - this._trigger( "change", event, this._uiHash() ); - break; + // Cannot intersect with itself + // no useless actions that have been done before + // no action if the item moved is the parent of the item checked + if ( itemElement !== this.currentItem[ 0 ] && + this.placeholder[ intersection === 1 ? + "next" : "prev" ]()[ 0 ] !== itemElement && + !$.contains( this.placeholder[ 0 ], itemElement ) && + ( this.options.type === "semi-dynamic" ? + !$.contains( this.element[ 0 ], itemElement ) : + true + ) + ) { + + this.direction = intersection === 1 ? "down" : "up"; + + if ( this.options.tolerance === "pointer" || + this._intersectsWithSides( item ) ) { + this._rearrange( event, item ); + } else { + break; + } + + this._trigger( "change", event, this._uiHash() ); + break; + } } } - //Post events to containers - this._contactContainers( event ); - //Interconnect with droppables if ( $.ui.ddmanager ) { $.ui.ddmanager.drag( this, event ); @@ -663,8 +691,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { return false; } - verticalDirection = this._getDragVerticalDirection(); - horizontalDirection = this._getDragHorizontalDirection(); + verticalDirection = this.dragDirection.vertical; + horizontalDirection = this.dragDirection.horizontal; return this.floating ? ( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 ) @@ -678,8 +706,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { this.offset.click.top, item.top + ( item.height / 2 ), item.height ), isOverRightHalf = this._isOverAxis( this.positionAbs.left + this.offset.click.left, item.left + ( item.width / 2 ), item.width ), - verticalDirection = this._getDragVerticalDirection(), - horizontalDirection = this._getDragHorizontalDirection(); + verticalDirection = this.dragDirection.vertical, + horizontalDirection = this.dragDirection.horizontal; if ( this.floating && horizontalDirection ) { return ( ( horizontalDirection === "right" && isOverRightHalf ) || @@ -821,26 +849,14 @@ return $.widget( "ui.sortable", $.ui.mouse, { }, - refreshPositions: function( fast ) { - - // Determine whether items are being displayed horizontally - this.floating = this.items.length ? - this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : - false; - - //This has to be redone because due to the item being moved out/into the offsetParent, - // the offsetParent's position will change - if ( this.offsetParent && this.helper ) { - this.offset.parent = this._getParentOffset(); - } - + _refreshItemPositions: function( fast ) { var i, item, t, p; for ( i = this.items.length - 1; i >= 0; i-- ) { item = this.items[ i ]; //We ignore calculating positions of all connected containers when we're not over them - if ( item.instance !== this.currentContainer && this.currentContainer && + if ( this.currentContainer && item.instance !== this.currentContainer && item.item[ 0 ] !== this.currentItem[ 0 ] ) { continue; } @@ -858,6 +874,20 @@ return $.widget( "ui.sortable", $.ui.mouse, { item.left = p.left; item.top = p.top; } + }, + + refreshPositions: function( fast ) { + + // Determine whether items are being displayed horizontally + this.floating = this.items.length ? + this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) : + false; + + if ( this.innermostContainer !== null ) { + this._refreshItemPositions( fast ); + } + + var i, p; if ( this.options.custom && this.options.custom.refreshContainers ) { this.options.custom.refreshContainers.call( this ); @@ -1003,6 +1033,8 @@ return $.widget( "ui.sortable", $.ui.mouse, { } + this.innermostContainer = innermostContainer; + // If no intersecting containers found, return if ( !innermostContainer ) { return; @@ -1071,6 +1103,15 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Update the placeholder this.options.placeholder.update( this.currentContainer, this.placeholder ); + //Update scrollParent + this.scrollParent = this.placeholder.scrollParent(); + + //Update overflowOffset + if ( this.scrollParent[ 0 ] !== this.document[ 0 ] && + this.scrollParent[ 0 ].tagName !== "HTML" ) { + this.overflowOffset = this.scrollParent.offset(); + } + this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) ); this.containers[ innermostIndex ].containerCache.over = 1; } @@ -1086,9 +1127,7 @@ return $.widget( "ui.sortable", $.ui.mouse, { //Add the helper to the DOM if that didn't happen already if ( !helper.parents( "body" ).length ) { - $( o.appendTo !== "parent" ? - o.appendTo : - this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] ); + this.appendTo[ 0 ].appendChild( helper[ 0 ] ); } if ( helper[ 0 ] === this.currentItem[ 0 ] ) { -- cgit v1.2.3 From abc9e7ce2f3b60a18bf1f461c7cbfccb3fa02b53 Mon Sep 17 00:00:00 2001 From: Alexander Schmitz Date: Wed, 19 Apr 2017 11:32:13 -0400 Subject: Button: Fix backcompat when called on collection of mixed elements Fixes #15109 Closes gh-1808 --- tests/unit/button/deprecated.html | 8 ++++ tests/unit/button/deprecated.js | 18 ++++++++ ui/widgets/button.js | 87 ++++++++++++++++++++++++++++++++------- 3 files changed, 99 insertions(+), 14 deletions(-) (limited to 'ui/widgets') diff --git a/tests/unit/button/deprecated.html b/tests/unit/button/deprecated.html index 73f62921c..8b5270baa 100644 --- a/tests/unit/button/deprecated.html +++ b/tests/unit/button/deprecated.html @@ -56,6 +56,14 @@ Anchor Button +
+ Anchor + + + + +
+ diff --git a/tests/unit/button/deprecated.js b/tests/unit/button/deprecated.js index 81a0281b7..86fca797e 100644 --- a/tests/unit/button/deprecated.js +++ b/tests/unit/button/deprecated.js @@ -194,4 +194,22 @@ QUnit.test( "icon / icons options properly proxied", function( assert ) { "Icons secondary option sets iconPosition option to end on init" ); } ); +QUnit.test( "Calling button on a collection of mixed types works correctly", function( assert ) { + assert.expect( 5 ); + + var group = $( ".mixed" ).children(); + + group.button(); + + $.each( { + anchor: "button", + button: "button", + check: "checkboxradio", + input: "button", + radio: "checkboxradio" + }, function( type, widget ) { + assert.ok( $( "#mixed-" + type )[ widget ]( "instance" ), type + " is a " + widget ); + } ); +} ); + } ); diff --git a/ui/widgets/button.js b/ui/widgets/button.js index 50da9f9e2..42cfec06d 100644 --- a/ui/widgets/button.js +++ b/ui/widgets/button.js @@ -342,22 +342,81 @@ if ( $.uiBackCompat !== false ) { } ); $.fn.button = ( function( orig ) { - return function() { - if ( !this.length || ( this.length && this[ 0 ].tagName !== "INPUT" ) || - ( this.length && this[ 0 ].tagName === "INPUT" && ( - this.attr( "type" ) !== "checkbox" && this.attr( "type" ) !== "radio" - ) ) ) { - return orig.apply( this, arguments ); - } - if ( !$.ui.checkboxradio ) { - $.error( "Checkboxradio widget missing" ); - } - if ( arguments.length === 0 ) { - return this.checkboxradio( { - "icon": false + return function( options ) { + var isMethodCall = typeof options === "string"; + var args = Array.prototype.slice.call( arguments, 1 ); + var returnValue = this; + + if ( isMethodCall ) { + + // If this is an empty collection, we need to have the instance method + // return undefined instead of the jQuery instance + if ( !this.length && options === "instance" ) { + returnValue = undefined; + } else { + this.each( function() { + var methodValue; + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? + "button" : + "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( options === "instance" ) { + returnValue = instance; + return false; + } + + if ( !instance ) { + return $.error( "cannot call methods on button" + + " prior to initialization; " + + "attempted to call method '" + options + "'" ); + } + + if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) { + return $.error( "no such method '" + options + "' for button" + + " widget instance" ); + } + + methodValue = instance[ options ].apply( instance, args ); + + if ( methodValue !== instance && methodValue !== undefined ) { + returnValue = methodValue && methodValue.jquery ? + returnValue.pushStack( methodValue.get() ) : + methodValue; + return false; + } + } ); + } + } else { + + // Allow multiple hashes to be passed on init + if ( args.length ) { + options = $.widget.extend.apply( null, [ options ].concat( args ) ); + } + + this.each( function() { + var type = $( this ).attr( "type" ); + var name = type !== "checkbox" && type !== "radio" ? "button" : "checkboxradio"; + var instance = $.data( this, "ui-" + name ); + + if ( instance ) { + instance.option( options || {} ); + if ( instance._init ) { + instance._init(); + } + } else { + if ( name === "button" ) { + orig.call( $( this ), options ); + return; + } + + $( this ).checkboxradio( $.extend( { icon: false }, options ) ); + } } ); } - return this.checkboxradio.apply( this, arguments ); + + return returnValue; }; } )( $.fn.button ); -- cgit v1.2.3 From a3e953b495905d0c67790e65032841451b470ce1 Mon Sep 17 00:00:00 2001 From: Scott González Date: Mon, 17 Apr 2017 12:26:22 -0400 Subject: Menu: Don't focus dividers when wrapping via keyboard navigation Fixes #15157 Closes gh-1804 --- tests/unit/menu/events.js | 22 ++++++++++++++++++++++ tests/unit/menu/menu.html | 10 ++++++++++ ui/widgets/menu.js | 24 +++++++++++++----------- 3 files changed, 45 insertions(+), 11 deletions(-) (limited to 'ui/widgets') diff --git a/tests/unit/menu/events.js b/tests/unit/menu/events.js index 90507bddc..fd57373c1 100644 --- a/tests/unit/menu/events.js +++ b/tests/unit/menu/events.js @@ -757,4 +757,26 @@ QUnit.test( "#10571: When typing in a menu, only menu-items should be focused", } ); } ); +QUnit.test( "#15157: Must not focus menu dividers with the keyboard", function( assert ) { + var ready = assert.async(); + assert.expect( 6 ); + + var element = $( "#menu-with-dividers" ).menu( { + focus: function( event, ui ) { + assert.hasClasses( ui.item, "ui-menu-item", "Should have menu item class" ); + log( ui.item.text() ); + } + } ); + + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); + element.simulate( "keydown", { keyCode: $.ui.keyCode.UP } ); + setTimeout( function() { + assert.equal( logOutput(), "beginning,middle,end,beginning,end", "Should wrap around items" ); + ready(); + } ); +} ); + } ); diff --git a/tests/unit/menu/menu.html b/tests/unit/menu/menu.html index 2d871a4b3..8d70b19df 100644 --- a/tests/unit/menu/menu.html +++ b/tests/unit/menu/menu.html @@ -323,6 +323,16 @@
  • Addyston
  • Adelphi
  • + + diff --git a/ui/widgets/menu.js b/ui/widgets/menu.js index 19d9b90df..b2d976a2b 100644 --- a/ui/widgets/menu.js +++ b/ui/widgets/menu.js @@ -136,7 +136,7 @@ return $.widget( "ui.menu", { // If there's already an active item, keep it active // If not, activate the first item - var item = this.active || this.element.find( this.options.items ).eq( 0 ); + var item = this.active || this._menuItems().first(); if ( !keepActiveItem ) { this.focus( event, item ); @@ -538,11 +538,7 @@ return $.widget( "ui.menu", { }, expand: function( event ) { - var newItem = this.active && - this.active - .children( ".ui-menu " ) - .find( this.options.items ) - .first(); + var newItem = this.active && this._menuItems( this.active.children( ".ui-menu" ) ).first(); if ( newItem && newItem.length ) { this._open( newItem.parent() ); @@ -570,21 +566,27 @@ return $.widget( "ui.menu", { return this.active && !this.active.nextAll( ".ui-menu-item" ).length; }, + _menuItems: function( menu ) { + return ( menu || this.element ) + .find( this.options.items ) + .filter( ".ui-menu-item" ); + }, + _move: function( direction, filter, event ) { var next; if ( this.active ) { if ( direction === "first" || direction === "last" ) { next = this.active [ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" ) - .eq( -1 ); + .last(); } else { next = this.active [ direction + "All" ]( ".ui-menu-item" ) - .eq( 0 ); + .first(); } } if ( !next || !next.length || !this.active ) { - next = this.activeMenu.find( this.options.items )[ filter ](); + next = this._menuItems( this.activeMenu )[ filter ](); } this.focus( event, next ); @@ -610,7 +612,7 @@ return $.widget( "ui.menu", { this.focus( event, item ); } else { - this.focus( event, this.activeMenu.find( this.options.items ) + this.focus( event, this._menuItems( this.activeMenu ) [ !this.active ? "first" : "last" ]() ); } }, @@ -634,7 +636,7 @@ return $.widget( "ui.menu", { this.focus( event, item ); } else { - this.focus( event, this.activeMenu.find( this.options.items ).first() ); + this.focus( event, this._menuItems( this.activeMenu ).first() ); } }, -- cgit v1.2.3 From 7d992ae29d27cdab8787691a14e689e60c74c05c Mon Sep 17 00:00:00 2001 From: Scott González Date: Tue, 18 Apr 2017 13:57:23 -0400 Subject: Menu: Handle mouse movement mixed with keyboard navigation Fixes #9357 Closes gh-1805 --- ui/widgets/menu.js | 56 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 23 deletions(-) (limited to 'ui/widgets') diff --git a/ui/widgets/menu.js b/ui/widgets/menu.js index b2d976a2b..5df9d3eef 100644 --- a/ui/widgets/menu.js +++ b/ui/widgets/menu.js @@ -78,6 +78,8 @@ return $.widget( "ui.menu", { // them (focus should always stay on UL during navigation). "mousedown .ui-menu-item": function( event ) { event.preventDefault(); + + this._activateItem( event ); }, "click .ui-menu-item": function( event ) { var target = $( event.target ); @@ -107,29 +109,8 @@ return $.widget( "ui.menu", { } } }, - "mouseenter .ui-menu-item": function( event ) { - - // Ignore mouse events while typeahead is active, see #10458. - // Prevents focusing the wrong item when typeahead causes a scroll while the mouse - // is over an item in the menu - if ( this.previousFilter ) { - return; - } - - var actualTarget = $( event.target ).closest( ".ui-menu-item" ), - target = $( event.currentTarget ); - - // Ignore bubbled events on parent items, see #11641 - if ( actualTarget[ 0 ] !== target[ 0 ] ) { - return; - } - - // Remove ui-state-active class from siblings of the newly focused menu item - // to avoid a jump caused by adjacent elements both having a class with a border - this._removeClass( target.siblings().children( ".ui-state-active" ), - null, "ui-state-active" ); - this.focus( event, target ); - }, + "mouseenter .ui-menu-item": "_activateItem", + "mousemove .ui-menu-item": "_activateItem", mouseleave: "collapseAll", "mouseleave .ui-menu": "collapseAll", focus: function( event, keepActiveItem ) { @@ -171,6 +152,35 @@ return $.widget( "ui.menu", { } ); }, + _activateItem: function( event ) { + + // Ignore mouse events while typeahead is active, see #10458. + // Prevents focusing the wrong item when typeahead causes a scroll while the mouse + // is over an item in the menu + if ( this.previousFilter ) { + return; + } + + var actualTarget = $( event.target ).closest( ".ui-menu-item" ), + target = $( event.currentTarget ); + + // Ignore bubbled events on parent items, see #11641 + if ( actualTarget[ 0 ] !== target[ 0 ] ) { + return; + } + + // If the item is already active, there's nothing to do + if ( target.is( ".ui-state-active" ) ) { + return; + } + + // Remove ui-state-active class from siblings of the newly focused menu item + // to avoid a jump caused by adjacent elements both having a class with a border + this._removeClass( target.siblings().children( ".ui-state-active" ), + null, "ui-state-active" ); + this.focus( event, target ); + }, + _destroy: function() { var items = this.element.find( ".ui-menu-item" ) .removeAttr( "role aria-disabled" ), -- cgit v1.2.3 From 50efd6e1b063822c4a0ecb38f324ed3354f387c4 Mon Sep 17 00:00:00 2001 From: Scott González Date: Tue, 18 Apr 2017 16:51:23 -0400 Subject: Menu: Ignore mouse events triggered due to page scrolling Fixes #9356 Closes gh-1806 --- tests/unit/selectmenu/core.js | 4 ++-- tests/unit/selectmenu/events.js | 4 ++-- ui/widgets/menu.js | 12 ++++++++++++ 3 files changed, 16 insertions(+), 4 deletions(-) (limited to 'ui/widgets') diff --git a/tests/unit/selectmenu/core.js b/tests/unit/selectmenu/core.js index cef6dc5bf..708452312 100644 --- a/tests/unit/selectmenu/core.js +++ b/tests/unit/selectmenu/core.js @@ -251,13 +251,13 @@ $.each( [ wrappers = menu.find( "li.ui-menu-item .ui-menu-item-wrapper" ); button.trigger( "click" ); - wrappers.first().simulate( "mouseover" ).trigger( "click" ); + wrappers.first().simulate( "mouseover", { clientX: 2, clientY: 2 } ).trigger( "click" ); assert.equal( element[ 0 ].selectedIndex, 0, "First item is selected" ); button.simulate( "keydown", { keyCode: $.ui.keyCode.UP } ); assert.equal( element[ 0 ].selectedIndex, 0, "No looping beyond first item" ); button.trigger( "click" ); - wrappers.last().simulate( "mouseover" ).trigger( "click" ); + wrappers.last().simulate( "mouseover", { clientX: 3, clientY: 3 } ).trigger( "click" ); assert.equal( element[ 0 ].selectedIndex, wrappers.length - 1, "Last item is selected" ); button.simulate( "keydown", { keyCode: $.ui.keyCode.DOWN } ); assert.equal( element[ 0 ].selectedIndex, wrappers.length - 1, "No looping behind last item" ); diff --git a/tests/unit/selectmenu/events.js b/tests/unit/selectmenu/events.js index 4aed70ac8..ffc0429ee 100644 --- a/tests/unit/selectmenu/events.js +++ b/tests/unit/selectmenu/events.js @@ -89,9 +89,9 @@ QUnit.test( "focus", function( assert ) { button.trigger( "click" ); links = menu.find( "li.ui-menu-item" ); optionIndex = 0; - links.eq( optionIndex ).simulate( "mouseover" ); + links.eq( optionIndex ).simulate( "mouseover", { clientX: 2, clientY: 2 } ); optionIndex += 1; - links.eq( optionIndex ).simulate( "mouseover" ); + links.eq( optionIndex ).simulate( "mouseover", { clientX: 3, clientY: 3 } ); // This tests for unwanted, additional focus event on close that.element.selectmenu( "close" ); diff --git a/ui/widgets/menu.js b/ui/widgets/menu.js index 5df9d3eef..302d202ae 100644 --- a/ui/widgets/menu.js +++ b/ui/widgets/menu.js @@ -64,6 +64,7 @@ return $.widget( "ui.menu", { // Flag used to prevent firing of the click handler // as the event bubbles up through nested menus this.mouseHandled = false; + this.lastMousePosition = { x: null, y: null }; this.element .uniqueId() .attr( { @@ -161,6 +162,17 @@ return $.widget( "ui.menu", { return; } + // If the mouse didn't actually move, but the page was scrolled, ignore the event (#9356) + if ( event.clientX === this.lastMousePosition.x && + event.clientY === this.lastMousePosition.y ) { + return; + } + + this.lastMousePosition = { + x: event.clientX, + y: event.clientY + }; + var actualTarget = $( event.target ).closest( ".ui-menu-item" ), target = $( event.currentTarget ); -- cgit v1.2.3 From 0b7246b6eeadfa9e2696e22f3230f6452f8129dc Mon Sep 17 00:00:00 2001 From: Eirik Sletteberg Date: Fri, 2 Dec 2016 14:41:30 +0100 Subject: Core: Fix JQMIGRATE warning about `jQuery.expr[":"]` This commit polyfills `jQuery.expr.pseudos` for old versions of jQuery. Fixes #15185 Closes gh-1773 --- ui/data.js | 2 +- ui/focusable.js | 2 +- ui/jquery-1-7.js | 14 +++++++++++++- ui/tabbable.js | 2 +- ui/widget.js | 4 ++-- ui/widgets/controlgroup.js | 2 +- ui/widgets/tabs.js | 2 +- 7 files changed, 20 insertions(+), 8 deletions(-) (limited to 'ui/widgets') diff --git a/ui/data.js b/ui/data.js index d0417b8f9..c02e7ffde 100644 --- a/ui/data.js +++ b/ui/data.js @@ -23,7 +23,7 @@ factory( jQuery ); } } ( function( $ ) { -return $.extend( $.expr[ ":" ], { +return $.extend( $.expr.pseudos, { data: $.expr.createPseudo ? $.expr.createPseudo( function( dataName ) { return function( elem ) { diff --git a/ui/focusable.js b/ui/focusable.js index cf4f728b8..b1a7b61e2 100644 --- a/ui/focusable.js +++ b/ui/focusable.js @@ -73,7 +73,7 @@ function visible( element ) { return visibility !== "hidden"; } -$.extend( $.expr[ ":" ], { +$.extend( $.expr.pseudos, { focusable: function( element ) { return $.ui.focusable( element, $.attr( element, "tabindex" ) != null ); } diff --git a/ui/jquery-1-7.js b/ui/jquery-1-7.js index bd40e332f..5e7907a15 100644 --- a/ui/jquery-1-7.js +++ b/ui/jquery-1-7.js @@ -1,5 +1,5 @@ /*! - * jQuery UI Support for jQuery core 1.7.x @VERSION + * jQuery UI Support for jQuery core 1.7.x and newer @VERSION * http://jqueryui.com * * Copyright jQuery Foundation and other contributors @@ -86,4 +86,16 @@ if ( $.fn.jquery.substring( 0, 3 ) === "1.7" ) { }; } +// Support: jQuery 1.9.x or older +// $.expr[ ":" ] is deprecated. +if ( !$.expr.pseudos ) { + $.expr.pseudos = $.expr[ ":" ]; +} + +// Support: jQuery 1.11.x or older +// $.unique has been renamed to $.uniqueSort +if ( !$.uniqueSort ) { + $.uniqueSort = $.unique; +} + } ) ); diff --git a/ui/tabbable.js b/ui/tabbable.js index 3baa641ce..bb79466e8 100644 --- a/ui/tabbable.js +++ b/ui/tabbable.js @@ -24,7 +24,7 @@ } } ( function( $ ) { -return $.extend( $.expr[ ":" ], { +return $.extend( $.expr.pseudos, { tabbable: function( element ) { var tabIndex = $.attr( element, "tabindex" ), hasTabindex = tabIndex != null; diff --git a/ui/widget.js b/ui/widget.js index 011396811..c101e59d4 100644 --- a/ui/widget.js +++ b/ui/widget.js @@ -65,7 +65,7 @@ $.widget = function( name, base, prototype ) { } // Create selector for plugin - $.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) { + $.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) { return !!$.data( elem, fullName ); }; @@ -517,7 +517,7 @@ $.Widget.prototype = { current = that.classesElementLookup[ classes[ i ] ] || $(); if ( options.add ) { bindRemoveEvent(); - current = $( $.unique( current.get().concat( options.element.get() ) ) ); + current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) ); } else { current = $( current.not( options.element ).get() ); } diff --git a/ui/widgets/controlgroup.js b/ui/widgets/controlgroup.js index 6f357948d..c79f3fcaf 100644 --- a/ui/widgets/controlgroup.js +++ b/ui/widgets/controlgroup.js @@ -150,7 +150,7 @@ return $.widget( "ui.controlgroup", { } ); } ); - this.childWidgets = $( $.unique( childWidgets ) ); + this.childWidgets = $( $.uniqueSort( childWidgets ) ); this._addClass( this.childWidgets, "ui-controlgroup-item" ); }, diff --git a/ui/widgets/tabs.js b/ui/widgets/tabs.js index 58a65ebe8..14f94ae83 100644 --- a/ui/widgets/tabs.js +++ b/ui/widgets/tabs.js @@ -96,7 +96,7 @@ $.widget( "ui.tabs", { // Take disabling tabs via class attribute from HTML // into account and update option properly. if ( $.isArray( options.disabled ) ) { - options.disabled = $.unique( options.disabled.concat( + options.disabled = $.uniqueSort( options.disabled.concat( $.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) { return that.tabs.index( li ); } ) -- cgit v1.2.3 From 809f29efa79c3c9aba95e6d7ffef41f567cda3a5 Mon Sep 17 00:00:00 2001 From: Scott González Date: Mon, 8 May 2017 11:52:58 -0400 Subject: All: Remove uses of `jQuery.proxy()` Ref #15160 Closes gh-1813 --- demos/autocomplete/combobox.html | 2 +- ui/widgets/autocomplete.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'ui/widgets') diff --git a/demos/autocomplete/combobox.html b/demos/autocomplete/combobox.html index 403c48550..32c69b97a 100644 --- a/demos/autocomplete/combobox.html +++ b/demos/autocomplete/combobox.html @@ -48,7 +48,7 @@ .autocomplete({ delay: 0, minLength: 0, - source: $.proxy( this, "_source" ) + source: this._source.bind( this ) }) .tooltip({ classes: { diff --git a/ui/widgets/autocomplete.js b/ui/widgets/autocomplete.js index c5bddc074..60d326544 100644 --- a/ui/widgets/autocomplete.js +++ b/ui/widgets/autocomplete.js @@ -447,7 +447,7 @@ $.widget( "ui.autocomplete", { _response: function() { var index = ++this.requestIndex; - return $.proxy( function( content ) { + return function( content ) { if ( index === this.requestIndex ) { this.__response( content ); } @@ -456,7 +456,7 @@ $.widget( "ui.autocomplete", { if ( !this.pending ) { this._removeClass( "ui-autocomplete-loading" ); } - }, this ); + }.bind( this ); }, __response: function( content ) { -- cgit v1.2.3 From 5708046ea1ba4d6d86f431ec9fd32d28ae7542f6 Mon Sep 17 00:00:00 2001 From: Scott González Date: Thu, 11 May 2017 13:58:08 -0400 Subject: Dialog: Fix shared event handler for modal dialogs The old logic worked when all widgets of the same type used the same event namespace. However, now that each instance has its own namespace, we cannot use `_on()` for shared event handlers. Fixes #15182 Closes gh-1817 --- ui/widgets/dialog.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) (limited to 'ui/widgets') diff --git a/ui/widgets/dialog.js b/ui/widgets/dialog.js index c8829331f..01780daf3 100644 --- a/ui/widgets/dialog.js +++ b/ui/widgets/dialog.js @@ -289,7 +289,7 @@ $.widget( "ui.dialog", { that._trigger( "focus" ); } ); - // Track the dialog immediately upon openening in case a focus event + // Track the dialog immediately upon opening in case a focus event // somehow occurs outside of the dialog before an element inside the // dialog is focused (#10152) this._makeFocusTarget(); @@ -863,20 +863,19 @@ $.widget( "ui.dialog", { if ( !this.document.data( "ui-dialog-overlays" ) ) { // Prevent use of anchors and inputs - // Using _on() for an event handler shared across many instances is - // safe because the dialogs stack and must be closed in reverse order - this._on( this.document, { - focusin: function( event ) { - if ( isOpening ) { - return; - } - - if ( !this._allowInteraction( event ) ) { - event.preventDefault(); - this._trackingInstances()[ 0 ]._focusTabbable(); - } + // This doesn't use `_on()` because it is a shared event handler + // across all open modal dialogs. + this.document.on( "focusin.ui-dialog", function( event ) { + if ( isOpening ) { + return; } - } ); + + var instance = this._trackingInstances()[ 0 ]; + if ( !instance._allowInteraction( event ) ) { + event.preventDefault(); + instance._focusTabbable(); + } + }.bind( this ) ); } this.overlay = $( "
    " ) @@ -899,7 +898,7 @@ $.widget( "ui.dialog", { var overlays = this.document.data( "ui-dialog-overlays" ) - 1; if ( !overlays ) { - this._off( this.document, "focusin" ); + this.document.off( "focusin.ui-dialog" ); this.document.removeData( "ui-dialog-overlays" ); } else { this.document.data( "ui-dialog-overlays", overlays ); -- cgit v1.2.3