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

Reading keyboard ctrlKey (Alt, Shift) state from monitor #512

Closed
OlegZee opened this issue Aug 18, 2016 · 10 comments
Closed

Reading keyboard ctrlKey (Alt, Shift) state from monitor #512

OlegZee opened this issue Aug 18, 2016 · 10 comments
Labels

Comments

@OlegZee
Copy link

OlegZee commented Aug 18, 2016

I would like to modify drop action behavior depending on ctrl button state.
E.g. if Ctrl is pressed currently dragged item has to be copied to a new place or if Alt is pressed I want to snap dragged item to a grid.
I could obtain button state from DOM events but React DND hides such a detail completely. Opening respective methods via monitor methods would be one of the options to fulfill my request.

@kesne
Copy link
Collaborator

kesne commented Aug 20, 2016

I don't think this is currently possible. Right now you'd have to maintain that state in the component with document key listeners likely.

I think the major issue with this is that key events and drag events are pretty separate, so I don't see a clean way of having react-dnd provide key information alongside drag information.

@OlegZee
Copy link
Author

OlegZee commented Aug 20, 2016

According to my sources, drag events contain all required stuff:
https://developer.mozilla.org/en-US/docs/Web/Events/dragend. I'm going to
make PR next week
Сб, 20 авг. 2016 г. в 13:12, Jordan Gensler notifications@github.com:

I don't think this is currently possible. Right now you'd have to maintain
that state in the component with document key listeners likely.

I think the major issue with this is that key events and drag events are
pretty separate, so I don't see a clean way of having react-dnd provide key
information alongside drag information.


You are receiving this because you authored the thread.
Reply to this email directly, view it on GitHub
#512 (comment),
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAfVFf10W0cbAabNEwVFWVsFpXRvKSHdks5qhprcgaJpZM4JnTsb
.

@kesne
Copy link
Collaborator

kesne commented Aug 20, 2016

Oh rad, I stand corrected! That'd be interesting to see implemented!

@richardolsson
Copy link

Was there ever a PR submitted for this? I solved this in our fork of react-dnd but it broke with recent changes to the library.

We solved it by adding a meta property that back-ends could use to send arbitrary data, and used that object in the HTML5 back-end to store the event key information.

@OlegZee
Copy link
Author

OlegZee commented Oct 30, 2016

@richardolsson That's a great news! I must confess I've made no progress on this case yet. React-dnd is so multi-layered so I need a pen and piece of paper to design the changes. Taking unit tests and docs into account makes such PR way too complicated for me (at least not a one evening task).
Besides that I'd like to see it a first-class citizen, while meta looks like a w.

@richardolsson
Copy link

richardolsson commented Oct 31, 2016

At the time, all I did was these two commits (to react-dnd and dnd-core respectively):

This broke later when the react-dnd HTML5 backend was split out into a separate library. I'm sure it could be added back in, but it would likely require changes to all three libraries, so it would be three parallel pull requests.

The problem with adding a feature like this as a "first class citizen" is that react-dnd is all about drag and drop, and not all devices where drag and drop might be relevant have a keyboard.

This is why I added it as a "meta" object. It was a way for the back-end to send meta-data that is specific for that back-end, rather than universal to any drag and drop operation.

@stale
Copy link

stale bot commented Jul 6, 2019

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the wontfix label Jul 6, 2019
@stale stale bot closed this as completed Jul 13, 2019
@cdauth
Copy link

cdauth commented Nov 10, 2020

What I could find about this:

  • An older discussion where someone suggested to handle the bubbling drag events on a parent component and cause it to rerender the dragging child component with props according to the pressed modifier keys
  • This pull request where hard-coded Apple behaviour was added to the code, allowing to switch to Copy mode using the Alt key.

On Windows and Linux, it seems that Ctrl+Drag stands for copy, Shift+Drag for move and Alt+Drag for link. On Apple, it seems that Alt+Drag stands for copy and god knows what other modifier keys there are and what they stand for. There seem to be other platform-specific restrictions, for example on Linux you cannot Alt+Drag at all (because it moves the current window) and on Firefox on Apple you cannot Shift+Drag for some reason.

In our case, we need to distinguish only between two actions, link and copy, so we decided to make a drag without any keys do the link action and a drag with any modifier key a copy action. This way we circumvent the platform differences and people can just use whatever modifier key works on their platform.

We implemented this by hacking the existing Alt key functionality:

function isModifierKeyPressed(event) {
    return event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
}

export const HTML5BackendWithModifierKeys = (manager, context) => {
    const result = HTML5Backend(manager, context);

    result.handleTopDragEnter_ = result.handleTopDragEnter;
    result.handleTopDragEnter = (e) => {
        result.handleTopDragEnter_(e);
        result.altKeyPressed = isModifierKeyPressed(e);
    };

    result.handleTopDragOver_ = result.handleTopDragOver;
    result.handleTopDragOver = (e) => {
        result.handleTopDragOver_(e);
        result.altKeyPressed = isModifierKeyPressed(e);
    };

    return result;
};

The key can be pressed either before starting the drag operation or during it. There is one oddity however, that is that pressing or releasing the key is only registered when moving the mouse afterwards. When pressing or releasing the key and then immediately releasing the mouse without moving it, the key press/release is not registered. This seems to be a browser limitation though.

What's really annoying about this workaround still is that react-dnd only allows us to access the dropEffect in the drag end handler (using monitor.getDropResult()), but not during the drag operation. This makes it impossible to show which mode is active in the drag preview. I suppose the suggestion of reacting to the bubbling drag events might be a better solution here, but I haven't tried that yet.

@d07RiV
Copy link

d07RiV commented Apr 10, 2021

@cdauth the fix doesn't work (anymore?) because getCurrentDropEffect is called inside the original handleTopDragX. I patched around it by defining altKeyPressed as a property with an empty setter and a getter that reads a local variable, but that seems really crude.

As for bubbling the drop effect, an even more crude solution is to wrap the entire app in a context that provides the drop effect...

Shame that the issue still hasn't been addressed properly, so we don't have to write such crude hacks.

import React from 'react';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';

export const DragEffectContext = React.createContext();

function isModifierKeyPressed(event) {
  return !!(event.altKey || event.ctrlKey || event.metaKey || event.shiftKey);
}

export function DndProviderWrapper({ children }) {
  const [dragEffect, setDragEffect] = React.useState(false);

  const isMounted = React.useRef();
  React.useEffect(() => {
    isMounted.current = true;
    return () => isMounted.current = false;
  }, []);

  const backendRef = React.useRef();
  // to make absolutely sure that we never re-create the backend
  if (!backendRef.current) {
    backendRef.current = function HTML5BackendWithModifierKeys(manager, context) {
      const result = HTML5Backend(manager, context);

      let altKeyPressed = false;
      Object.defineProperty(result, 'altKeyPressed', {
        set: () => {},
        get: () => altKeyPressed,
      });

      result.handleTopDragEnter_ = result.handleTopDragEnter;
      result.handleTopDragEnter = (e) => {
        // avoid re-render at all costs
        if (isModifierKeyPressed(e) !== altKeyPressed) {
          altKeyPressed = !altKeyPressed;
          if (isMounted.current) setDragEffect(altKeyPressed);
        }
        result.handleTopDragEnter_(e);
      };

      result.handleTopDragOver_ = result.handleTopDragOver;
      result.handleTopDragOver = (e) => {
        if (isModifierKeyPressed(e) !== altKeyPressed) {
          altKeyPressed = !altKeyPressed;
          if (isMounted.current) setDragEffect(altKeyPressed);
        }
        result.handleTopDragOver_(e);
      };

      return result;
    };
  }

  return (
    <DragEffectContext.Provider value={dragEffect}>
      <DndProvider backend={backendRef.current}>{children}</DndProvider>
    </DragEffectContext.Provider>
  );
}

@cdauth
Copy link

cdauth commented Apr 11, 2021

I'm not sure, when I wrote the hack we were using version 11.1.3, and from the source code it doesn't look like a lot has changed in that method since then. I guess if it doesn't work for you you could also try to modify the event object itself (haven’t tried that code, that’s just how I imagine it):

const altKeyHack = (event) => Object.assign(event, { altKey: event.altKey || event.ctrlKey || event.metaKey || event.shiftKey });

export const HTML5BackendWithModifierKeys = (manager, context) => Object.assign(HTML5Backend(manager, context), {
    handleTopDragEnter(e) {
        HTML5Backend.prototype.handleTopDragEnter.call(this, altKeyHack(e));
    }

    handleTopDragOver(e) {
        HTML5Backend.prototype.handleTopDragOver.call(this, altKeyHack(e));
    }
});

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

No branches or pull requests

5 participants