Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve how raw input events are handled #375

Closed
alexreardon opened this issue Mar 7, 2018 · 3 comments
Closed

Improve how raw input events are handled #375

alexreardon opened this issue Mar 7, 2018 · 3 comments
Assignees

Comments

@alexreardon
Copy link
Collaborator

alexreardon commented Mar 7, 2018

Right now we put a fair amount of effort into preventing click events from being published after a drag.

As a part of implementing a muti-drag pattern (#10) we need to be consistent with how we are processing events (mousedown, mouseup, click, and so on). Right now we sometimes block these events (using event.stopPropogation()) and prevent their default behaviour (using event.preventDefault()), sometimes let them pass through. This can result in unmatched event pairs.

For example, in a mouse drag we stop the mousedown event being propogated. However, if they do not start a drag we allow the mouseup event to go through. The result of this is that consumers can get a mouseup without a proceeding mousedown event which can be confusing. In that situation we could also block the mousedown even if the user was not dragging but then a click will be published which might be unexpected given that there was no previous mouseup and mousedown event. There are other scenarios where we block on events in a pair (eg only block one of keydown and keyup).

Proposed solution

Rather than conditionally stopping events with event.stopPropagation() we should publish every event and only use event.preventDefault() on events we are controlling. This way consumers can get every event as well as knowing if they should use it or not through the event.defaultPrevented property.

@alexreardon alexreardon self-assigned this Mar 7, 2018
@alexreardon alexreardon changed the title Improve how raw input events are published Improve how raw input events are handled Mar 7, 2018
@alexreardon
Copy link
Collaborator Author

Getting serious

series

@alexreardon
Copy link
Collaborator Author

alexreardon commented Mar 8, 2018

WIP

User events

This page details how we use DOM input events, what we do with them, and how you can build things on top of our usage. Generally you will not need to know this information but it can be helpful if you are also binding your own event handlers to the window or to a drag handle.

Prior knowledge

This page assumes a working knowledge of DOM events. For a good introduction to DOM events see:

Safe event bindings

Without needing going into all the details below, here are the safest event handlers to build on top of react-beautiful-dnd:

These can be added on the drag handle, anywhere else higher on the tree or to the window directly.

  • onClick: the event.defaultPrevented property will be set to true if occurred as a part of the drag interaction.
  • onKeyDown: the event.defaultPrevented property will be set to true if it was used as a part of a drag. If you add onKeyDown to the drag handle you will need to monkey patch the DragHandleProps onKeyDown event handler.

You may need to provide your event handlers with information from onDragStart and onDragEnd to know about whether a drag is occuring while those events fire.

You are welcome to add other event handlers but you may be more reliant on onDragStart and onDragEnd information.

General rules

Event prevention

When we use an input event as part of a drag and drop interaction we generally call event.preventDefault() on the event to opt out of standard browser behaviour for the event. We do not stop the propagation of events (event.stopPropagation()) that we call event.preventDefault() on so even though we may use a mousemove event for dragging we will not block that event from being published and received by your event handlers.

Some event handlers we add on the drag handle itself (see DragHandleProps) and others we add to the window in the capture phase. What this means is as long as you are applying your events handlers in the bubbling phase (which is the default for event handlers) then behaviour of events will be as described on this page.

In order to know if we have already used the event for the purpose of drag and drop you need to check the event.defaultPrevented property.

So let's say you want to add a window click handler. You could do something like this:

window.addEventListener('click', (event: MouseEvent) => {
  // event has already been used for drag and drop
  if (event.defaultPrevented) {
    return;
  }

  doMyCoolThing();
});

Direct and indirect events

Some user events directly cause actions: such as a mousemove when dragging with a mouse or the up arrow keydown event while dragging with a keyboard. These direct events will have event.preventDefault() called on them. Some events indirectly impact a drag such as a resize event which cancels a drag. For events that indirectly impact a drag we do not call preventDefault() on them. These events are usually events that cancel a drag

Mouse dragging 🐭

Initial mousedown

  • preventDefault() not called on mousedown

When the user first performs a mousedown on a drag handle we are not sure if they are intending to click or drag. Because at this stage we are not sure, we do not call preventDefault() on the event.

We are not sure yet if a drag will start

  • preventDefault() not called on mousemove

The user needs to move a small threshold before we consider the movement to be a drag. In this period of time we do not call preventDefault() on any mousemove events as we are not sure if they are dragging or just performing a sloppy click

The user has indicated that they are not mouse dragging

  • preventDefault() not called on the event that caused the pending drag to end (such as mouseup and keydown)

A mouse drag has started and the user is now dragging

  • preventDefault() is called on mousemove events
  • preventDefault() is called on a few keydown events to prevent their standard browser behaviour
  • preventDefault() is not called on keyup events even if the keydown was prevented

A drag is ending

  • preventDefault() is called on a mouseup if it ended the drag
  • preventDefault() is called on a escape esc keydown as it explicitly ends the drag
  • preventDefault() is called on the next click event regardless of how the drag ended. See sloppy clicks and click blocking
  • preventDefault() is not called on other events such as resize that indirectly ended a drag
  • preventDefault() is not called on keyup events

Touch dragging 📱

The logic for touch dragging works in a similar way to mouse dragging

Initial touchstart

  • preventDefault() is not called on touchstart.

When a user presses their finger (or other input) on a Draggable we are not sure if they where intending to tap, force press, scroll the container or drag. Because we do not know what the user is trying to do yet we do not call preventDefault() on the event.

The user has indicated that they are not touch dragging

  • preventDefault() is not called on any events

A user can start a drag by holding their finger 👇 on an element for a small period of time 🕑 (long press). If the user moves during this time with touchmove then we do not call preventDefault() on the event.

It is possible to cancel a touch drag with over events such as an orientationchange or a touchcancel. We do not call preventDefault on these events.

A touch drag has started and the user is now dragging

  • preventDefault() is called on touchmove events

✌️

A touch drag is ending

  • preventDefault() is called on touchend
  • preventDefault() is called on touchcancel
  • preventDefault() is not called on other events such as orientationchange that can cancel a drag

Keyboard dragging 🎹

We only use keydown events for keyboard dragging so keyup events never have preventDefault() called on them

Drag start

preventDefault() is called on keydown

Unlike mouse dragging a keyboard drag starts as soon as the user presses the spacebar space. This initial keyboard interaction has event.preventDefault() called on it.

While dragging

  • preventDefault() is called on a keydown event if the event is used as part of the drag (such as the up arrow )
  • preventDefault() is called on keydown events were we want to block the stanard browser behaviours (such as enter for submission)
  • preventDefault() is not called on keydown events that we do not use for the drag

Drag ending

  • preventDefault() is called on a keydown if it is the spacebar space key as it is dropping the item
  • preventDefault() is called on a keydown if it is the escape esc key as it is explicitly cancelling the drag
  • preventDefault() is not called on events that indirectly cancel a drag such as resize or mousedown.

@dexteritus
Copy link

dexteritus commented Mar 29, 2019

Hi @alexreardon

I was able to hiJAck the resize event by using evt.stopImmediatePropagation() inside my droppable item.

Would this be something we would like to see as a boolean flag?
e.g. disableCancelOnResize = {true} [defualt = false]

Thanks
Dec

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants