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

feat: pass activatorEvent to modifiers and add snapCenterToCursor modifier #334

Merged
merged 6 commits into from
Jun 22, 2021

Conversation

trentmwillis
Copy link
Contributor

@trentmwillis trentmwillis commented Jun 17, 2021

Fixes #122 as described in #122 (comment). I also added a new modifier, snapCenterToCursor which should help folks that encounter similar use cases in the future.

Here's a short demo video of the modifier in action:

dndkit-center-cursor.mp4

Note: This doesn't solve the issue of needing to resize the dragged node's rect used in collision detection (as described in #122 (comment)), but I am looking into that as well.

@changeset-bot
Copy link

changeset-bot bot commented Jun 17, 2021

🦋 Changeset detected

Latest commit: 89eaec3

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 4 packages
Name Type
@dnd-kit/core Minor
@dnd-kit/utilities Minor
@dnd-kit/modifiers Major
@dnd-kit/sortable Major

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Copy link
Owner

@clauderic clauderic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @trentmwillis, thanks for taking the time to open a PR and add an example modifier of how this would be used, overall this is looking really good 💯

Is there any reason why the snapCenterToCursor modifier couldn't be made to work with touch events as well? You could probably use the getEventCoordinates helper which works for both touch and mouse events, though you may need to move it to @dnd-kit/utilities to share it with @dnd-kit/modifiers

@trentmwillis
Copy link
Contributor Author

Only reason was that I didn't know how to idiomatically get the coordinates for touch events. I'll update the modifier to use that helper!

@trentmwillis
Copy link
Contributor Author

Updated to support Touch as well. Two notes:

  1. I exported getEventCoordinates from @dnd-kit/core since several other utilities are already exported there. If you'd like, I can move it to @dnd-kit/utilities.
  2. Since getEventCoordinates returns {x: 0, y: 0} for non-mouse/touch events, I added a check to ensure the activatorEvent is not a KeyboardEvent. Otherwise, it'll behave oddly for keyboard users. This is similar to this check in getRelativeTransformOrigin:

@enjoykcc456
Copy link

Hi, would like to know how can i use this modifier? Will this eventually be added into the library? Thanks.

Copy link
Owner

@clauderic clauderic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks for your contribution 🙏

@clauderic clauderic merged commit 13be602 into clauderic:master Jun 22, 2021
@github-actions github-actions bot mentioned this pull request Jun 22, 2021
@trentmwillis trentmwillis deleted the snap-center-to-cursor branch June 24, 2021 14:42
@VFC-elindgren
Copy link

VFC-elindgren commented Jun 29, 2021

Hey! So happy to see this feature getting worked on and accepted quickly. I the snapCenterToCursor modifier works as expected in a minimal Draggable example, but when I try to use it in the vertical sort example like this:

{useDragOverlay
        ? createPortal(
            <DragOverlay
              adjustScale={adjustScale}
              dropAnimation={dropAnimation}
              modifiers={[snapCenterToCursor]}
            >
              {activeId ? (
                <DraggerCount count={1} />
              ) :null}
            </DragOverlay>,
            document.body
          )
        : null}

Instead of centering, the overlay just appears at a consistent offset from the cursor. The offset appears to be the offset from the corner of the draggables bounding box to the center of it

clicking in the lower right:
image

clicking middle:
image

clicking upper left:
image

for reference the DraggerCount element:

const DraggerCount = ({count}: {count: number}) => (
  <div
    style={{
      width: 20,
      height: 20,
      fontWeight: 'bold',
      fontSize: 12,
      backgroundColor: 'red',
      color: 'white',
      padding: '10 0',
      borderRadius: 10,
      boxSizing: 'border-box',
      alignItems: 'center',
      justifyContent: 'center',
      display: 'flex',
    }}
  >
    {count}
  </div>
)

I've tried tweaking the DraggerCount down to something more minimal with the same effect.

Am I doing something wrong? Since the spacing is consistent I can definitely hack together a solution the gets me a mouse-centered overlay, but I thought I'd ask if there was a less smelly approach before going that route.

Thanks! The library is great!

@VFC-elindgren
Copy link

VFC-elindgren commented Jun 30, 2021

took me a bit to figure out, but in case anyone finds this, the issue was that my little red dot was in the upper left of the invisible overlay that was centering correctly, so wrapping the my DraggerCount with

       {activeId ? (
                <div
                  style={{
                    width: '100%',
                    height: '100%',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    userSelect: 'none'
                  }}
                >
                  <DraggerCount count={1} />
                </div>
              ) : null}

centered the component inside the overlay and now it works exactly as expected. Thanks again

@VFC-elindgren
Copy link

@clauderic I See this is in master, any idea when master will get published to npm?

@clauderic
Copy link
Owner

@VFC-elindgren you can install @dnd-kit packages tagged with the next tag on npm to get the latest version of master:

Screen Shot 2021-06-30 at 9 34 36 PM

@levikline
Copy link

levikline commented Jan 20, 2023

@trentmwillis Were you ever able to figure out how to update the collision detection so that it worked with the center-snapped overlay? I've found that a temporary solution is using the pointerWithin collision detection, but that makes the UX really difficult for small drop areas (which I'm currently working with). Ideally, this would work well with the standard rectangular collision detection.

Thanks for creating this modifier! It's been super useful in my latest project.

@atonyma7
Copy link

atonyma7 commented Jul 12, 2023

@levikline, did you find any solution for making the center snapped DragOverlay compatible with rectIntersect?

@levikline
Copy link

levikline commented Jul 12, 2023

@levikline, did you find any solution for making the center snapped DragOverlay compatible with rectIntersect?

@atonyma7 Yes! I used the snapCenterToCursor modifier in the dnd context and then I added the following lines to my own collision detection algorithm:

// Update the target to account for snapping to center
target = { ...target };
target.top = pointerCoordinates.y - target.height / 2;
target.left = pointerCoordinates.x - target.width / 2;

If you want a full example of writing a custom collision detection algorithm, DND Kit has a good page on it:
https://docs.dndkit.com/api-documentation/context-provider/collision-detection-algorithms#building-custom-collision-detection-algorithms

In my case, I ended up developing an entirely new collision algorithm for my app, but you could copy over the rectIntersect code and this should still work (I think). And if not, that should hopefully give you an idea of how to account for the center offset. Hope that helps!

@AndrewRayCode
Copy link

AndrewRayCode commented Feb 27, 2024

DndKit documentation leaves a lot to be desired, there is no documentation on how to use custom collision detection algorithms. I'm not sure what your post is referring to @levikline, there is no "target" related to collision detection.

Anyway, for folks looking for how to snap the drag overlay center to the cursor, and to make sure the updated collision rectangle works properly, here's the complete example.

import { snapCenterToCursor } from '@dnd-kit/modifiers';
import {
  CollisionDetection, DndContext, DragOverlay, rectIntersection
} from '@dnd-kit/core';

const fixCursorSnapOffset: CollisionDetection = (args) => {
  // Bail out if keyboard activated
  if (!args.pointerCoordinates) {
    return rectIntersection(args);
  }
  const { x, y } = args.pointerCoordinates;
  const { width, height } = args.collisionRect;
  const updated = {
    ...args,
    // The collision rectangle is broken when using snapCenterToCursor. Reset
    // the collision rectangle based on pointer location and overlay size.
    collisionRect: {
      width,
      height,
      bottom: y + height / 2,
      left: x - width / 2,
      right: x + width / 2,
      top: y - height / 2,
    },
  };
  return rectIntersection(updated);
};

const Component = () => {
  return (
    <DndContext
      collisionDetection={fixCursorSnapOffset}
    >
      {/* ... */}
      <DragOverlay modifiers={[snapCenterToCursor]}>
          <div>
            Your overlay
          </div>
      </DragOverlay>
    </DndContext>
  );
};

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

Successfully merging this pull request may close these issues.

DragOverlay cursor offset
7 participants