From 8aba4ca2ba15c8a1047548077d1afb36b980c88b Mon Sep 17 00:00:00 2001 From: Dzmitry Paulenka Date: Fri, 3 Nov 2017 16:55:26 +0300 Subject: [PATCH] Droppable: Improve over/out events detection, when greedy option is used Fixes #9389 --- tests/unit/droppable/options.js | 45 ++++++++++++++++++++++--- ui/widgets/droppable.js | 58 ++++++++++++++------------------- 2 files changed, 65 insertions(+), 38 deletions(-) diff --git a/tests/unit/droppable/options.js b/tests/unit/droppable/options.js index 1c164495b97..a713eac37ef 100644 --- a/tests/unit/droppable/options.js +++ b/tests/unit/droppable/options.js @@ -79,11 +79,48 @@ QUnit.test( "scope", function( assert ) { assert.equal( draggableOffset.left, oldDraggableOffset.left ); assert.equal( draggableOffset.top, oldDraggableOffset.top ); } ); -/* -Test( "greedy", function() { - ok(false, 'missing test - untested code is broken code'); -}); +QUnit.test( "greedy", function( assert ) { + assert.expect( 2 ); + + var droppableOuter = $( "
" ) + .appendTo( "#qunit-fixture" ) + .css( { position: "absolute", top: 0, left: 0, width: 20, height: 20 } ) + .droppable( { + drop: function() { + assert.notOk( true, "outer droppable should not have drop callback called" ); + }, + over: function() { + assert.notOk( true, "outer droppable should not have over callback called" ); + } + } ); + + $( "
" ) + .appendTo( droppableOuter ) + .css( { position: "absolute", top: 10, left: 10, width: 10, height: 10 } ) + .droppable( { + greedy: true, + drop: function() { + assert.ok( true, "inner droppable should have drop callback called" ); + }, + over: function() { + assert.ok( true, "inner droppable should have over callback called" ); + } + } ); + + //draggable is fully over inner droppable + var draggable = $( "
" ) + .appendTo( "#qunit-fixture" ) + .css( { position: "absolute", top: 12, left: 12, width: 5, height: 5 } ) + .draggable(); + + $( draggable ).simulate( "drag", { + dx: 2, + dy: 2 + } ); +} ); + +/* test( "hoverClass", function() { ok(false, 'missing test - untested code is broken code'); }); diff --git a/ui/widgets/droppable.js b/ui/widgets/droppable.js index 89bff2511b7..db754b718c6 100644 --- a/ui/widgets/droppable.js +++ b/ui/widgets/droppable.js @@ -397,52 +397,42 @@ $.ui.ddmanager = { // Run through all droppables and check their positions based on specific tolerance options $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() { - if ( this.options.disabled || this.greedyChild || !this.visible ) { + if ( this.options.disabled || !this.visible || + typeof this.shouldBeOver !== "undefined" ) { return; } - var parentInstance, scope, parent, - intersects = $.ui.intersect( draggable, this, this.options.tolerance, event ), - c = !intersects && this.isover ? - "isout" : - ( intersects && !this.isover ? "isover" : null ); - if ( !c ) { - return; - } + this.shouldBeOver = $.ui.intersect( draggable, this, this.options.tolerance, event ); - if ( this.options.greedy ) { + if ( this.options.greedy && this.shouldBeOver ) { - // find droppable parents with same scope - scope = this.options.scope; - parent = this.element.parents( ":data(ui-droppable)" ).filter( function() { - return $( this ).droppable( "instance" ).options.scope === scope; + // cancel isover for droppable parents with the same scope + var scope = this.options.scope; + this.element.parents( ":data(ui-droppable)" ).each( function() { + var parentInstance = $( this ).droppable( "instance" ); + if ( parentInstance.options.scope === scope ) { + parentInstance.shouldBeOver = false; + } } ); - - if ( parent.length ) { - parentInstance = $( parent[ 0 ] ).droppable( "instance" ); - parentInstance.greedyChild = ( c === "isover" ); - } } + } ); - // We just moved into a greedy child - if ( parentInstance && c === "isover" ) { - parentInstance.isover = false; - parentInstance.isout = true; - parentInstance._out.call( parentInstance, event ); + // Run through all droppables and change isover status accordingly + $.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() { + if ( this.options.disabled || !this.visible ) { + return; } - this[ c ] = true; - this[ c === "isout" ? "isover" : "isout" ] = false; - this[ c === "isover" ? "_over" : "_out" ].call( this, event ); - - // We just moved out of a greedy child - if ( parentInstance && c === "isout" ) { - parentInstance.isout = false; - parentInstance.isover = true; - parentInstance._over.call( parentInstance, event ); + if ( this.shouldBeOver && !this.isover ) { + this.isover = true; + this._over( event ); + } else if ( !this.shouldBeOver && this.isover ) { + this.isover = false; + this._out( event ); } - } ); + delete this.shouldBeOver; + } ); }, dragStop: function( draggable, event ) { draggable.element.parentsUntil( "body" ).off( "scroll.droppable" );