Postlagerkarte / blazor-dragdrop

Easy-to-use Drag and Drop Library for Blazor
MIT License
407 stars 97 forks source link

`DragTargetItem` is always null because `DragEnter` is immediately followed by a `DragLeave` #99

Open ViRuSTriNiTy opened 3 years ago

ViRuSTriNiTy commented 3 years ago

Hi there,

I am trying to implement a "user can drop this specific item on that specific target item only" functionality. This requires to check for specifics of the target item like it's type.

This requires issue [TBD] fixed. Even when this issue is fixed this does not seem to work reliably in all cases because DragEnter is immediately followed by a DragLeave when first start dragging.

You can test this for yourself by

I have done this in the AllowsDrag example and when dragging Item 1 slowly to Item 2 the output renders the following lines:

Warning: DragEnter {Item 1} <- user starts dragging item 1
Warning: DragLeave ActiveItem = {Item 1} DragTargetItem = null <- user drags item 1 outside of it's own bounds
Warning: DragEnter {Item 2} <- user enters item 2 as drop target
Warning: DragEnter {Item 2} <- now things begin to break, we get a second enter although we are already hovering over item 2 ...
Warning: DragLeave ActiveItem = {Item 1} DragTargetItem = {Item 2} <- ... but then `DragLeave` is called immediatelly afterwards removing `Item 2` as current drag target from the `DragDropService`

The last DragLeave clears the DragTargetItem in DragDropService which in return means that logic implemented in OnDrop does not have access to the drag target anymore. So if you need the drag target in your logic to persist the drag operation then you cannot do this right now because there is no drag target at this point.

Now, in the beginning of this issue I wrote "does not seem to work reliably" and "when first start dragging". This is because after a while, when entering / leaving a specific area multiple times, the event order changes to DragLeave then DragEnter and everything works as expected. So lets just continue dragging Item 1 outside of Item 2 then again onto Item 2 and repeat until the Output window suddenly changes its output to:

... 
Warning: DragLeave ActiveItem = {Item 1} DragTargetItem = {Item 2} <- user leaves item 2
Warning: DragEnter {Item 2} <- user enters item 1
Warning: DragLeave ActiveItem = {Item 1} DragTargetItem = {Item 2}
Warning: DragEnter {Item 2}
Warning: DragLeave ActiveItem = {Item 1} DragTargetItem = {Item 2}
Warning: DragEnter {Item 2}

Does anyone have an idea what could cause the wrong event call order in the beginning? Can we fix this somehow?

So lonG

Daniel

ViRuSTriNiTy commented 3 years ago

I found the following sections while reading the gist from @MackinnonBuck which most likely describes what the issue with the event order is, quote:

I think there could be scenarios where event callbacks occur in the wrong order, resulting in undesirable behavior. For example, if onDragStart blocks for five seconds somehow, and onDragEnd fires before onDragStart completes, things could be executed in the wrong order.

https://gist.github.com/MackinnonBuck/374e529e9c3fdb9d228214b4cc79e617

Postlagerkarte commented 3 years ago

@ViRuSTriNiTy I am pretty sure you did but just want to make sure , you disabled pointer events?

.plk-dd-inprogess > * { pointer-events: none; }

ViRuSTriNiTy commented 3 years ago

@Postlagerkarte Yes, I have this section in my css file.

ViRuSTriNiTy commented 3 years ago

Ok, somehow the issue seems to be the outer draggable item.

Lets say I have the following structure of draggable items each in a separate dropzone

Page
  Section Y
    Question Y2

then when dragging Question Y2 the debug output looks like this (error and warning is just for coloring):

image

As we can see, OnDragEnter is first called for Section Y, then for Question Y2 but then OnDragLeave for Section Y is directly called afterwards.

Btw: using

.plk-dd-inprogess > * {
pointer-events: none;
}

in the hope to fix this issue makes it even worse as often plk-dd-inprogess is left in the draggables and then no pointer events are received anymore and drag & drop is completely broken.

Also I more and more think that the wrong event order is a Blazor bug as setting @on[mouse@|drag]***:stopPropagation on the root div and the draggables doesn't make a difference whatsoever. Either Blazor ignores it, does not implement it correctly or the browsers do their own thing. The events are always bubbling up.

Perhaps we could change OnDragLeave from

public void OnDragLeave()
{
    DragDropService.DragTargetItem = default;
    DragDropService.ShouldRender = true;
    StateHasChanged();
    DragDropService.ShouldRender = false;
}

to

public void OnDragLeave(TItem item)
{
    // condition ensures that we only reset the target item that has been previously set
    if (item == DragDropService.DragTargetItem)
    {
        DragDropService.DragTargetItem = default;
        DragDropService.ShouldRender = true;
        StateHasChanged();
        DragDropService.ShouldRender = false;
    }
}