Skip to content

Add dragSnapToCursor prop to motion components#3723

Open
mattgperry wants to merge 1 commit into
mainfrom
worktree-fix-issue-2677
Open

Add dragSnapToCursor prop to motion components#3723
mattgperry wants to merge 1 commit into
mainfrom
worktree-fix-issue-2677

Conversation

@mattgperry
Copy link
Copy Markdown
Collaborator

Summary

  • Adds a new dragSnapToCursor prop to drag-enabled motion components, exposing the snap-to-cursor behavior previously only available via dragControls.start(event, { snapToCursor: true }).
  • Lets dragSnapToCursor and dragSnapToOrigin be declared together on the same component (the previous dragControls-only path forced you to forgo declarative dragSnapToOrigin).
<motion.div drag dragSnapToCursor dragSnapToOrigin />

Cause

VisualElementDragControls.addListeners attached a pointerdown handler that called this.start(event) with no options, so the prop had no way to influence behavior — snapToCursor was only ever set when the user passed it explicitly to dragControls.start.

Fix

Read dragSnapToCursor from props in the pointerdown handler and forward it as { snapToCursor: dragSnapToCursor } to this.start(event, ...). Added a public type field on MotionNodeDraggableOptions so the prop is accepted.

Test plan

  • Added unit tests asserting start is invoked with { snapToCursor: true } when the prop is set, and without the option when it isn't.
  • yarn build succeeds.
  • yarn test — all 778 unit tests pass.

Fixes #2677

Adds a new `dragSnapToCursor` prop on drag-enabled motion components so
the snap-to-cursor behavior previously only reachable via
`dragControls.start(event, { snapToCursor: true })` is also available
declaratively. This lets `dragSnapToCursor` work alongside
`dragSnapToOrigin` on the same component.

Fixes #2677
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 12, 2026

Greptile Summary

This PR exposes the existing snapToCursor drag behaviour as a declarative dragSnapToCursor prop on all motion components, forwarding it from the internal pointerdown handler to VisualElementDragControls.start().

  • VisualElementDragControls.ts: destructures dragSnapToCursor from props in addListeners and passes { snapToCursor: dragSnapToCursor } to start(). Passing undefined is safe because start already defaults snapToCursor to false via destructuring.
  • types.ts: adds dragSnapToCursor?: boolean to MotionNodeDraggableOptions with JSDoc; the doc omits the dragListener={false} caveat noted in the inline comment.
  • Tests: two spy-based unit tests verify the option is forwarded when the prop is set and is absent/falsy when it is not.

Confidence Score: 4/5

Safe to merge — the change is small, additive, and builds on existing well-tested infrastructure.

The implementation is functionally correct: passing { snapToCursor: undefined } when the prop is absent is equivalent to the previous no-argument call because start() already defaults snapToCursor to false. The only gap is a JSDoc omission about the dragListener={false} interaction, which does not affect runtime behaviour.

The dragSnapToCursor JSDoc in packages/motion-dom/src/node/types.ts would benefit from a note about dragListener={false}.

Important Files Changed

Filename Overview
packages/framer-motion/src/gestures/drag/VisualElementDragControls.ts Reads dragSnapToCursor from props in the pointerdown handler and forwards it to start() as { snapToCursor: dragSnapToCursor }. Functionally correct — passing undefined triggers the existing default false inside start.
packages/motion-dom/src/node/types.ts Adds dragSnapToCursor?: boolean to MotionNodeDraggableOptions with JSDoc. Documentation omits the caveat that the prop has no effect when dragListener={false}.
packages/framer-motion/src/gestures/drag/tests/use-drag-controls.test.tsx Adds two unit tests that spy on VisualElementDragControls.prototype.start to verify the option is forwarded correctly when the prop is set and is falsy when it isn't.

Sequence Diagram

sequenceDiagram
    participant User
    participant DOM
    participant VisualElementDragControls
    participant PanSession

    User->>DOM: pointerdown
    DOM->>VisualElementDragControls: pointerdown handler fires
    VisualElementDragControls->>VisualElementDragControls: "getProps() → { drag, dragListener, dragSnapToCursor }"
    alt "drag && dragListener && !isClickingTextInputChild"
        VisualElementDragControls->>VisualElementDragControls: "start(event, { snapToCursor: dragSnapToCursor })"
        VisualElementDragControls->>PanSession: new PanSession(originEvent, handlers)
        alt "snapToCursor === true"
            PanSession->>VisualElementDragControls: onSessionStart → snapToCursor(point)
        end
    end
Loading

Reviews (1): Last reviewed commit: "Add dragSnapToCursor prop to motion comp..." | Re-trigger Greptile

Comment on lines +736 to +746
/**
* If `true`, the element will snap so that the cursor is centered on it
* when a drag gesture starts. This is the equivalent of passing
* `{ snapToCursor: true }` to a `dragControls.start()` call but works
* for any drag-enabled motion component.
*
* ```jsx
* <motion.div drag dragSnapToCursor />
* ```
*/
dragSnapToCursor?: boolean
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 The JSDoc states this prop "works for any drag-enabled motion component," but it is silently ignored when dragListener={false} is set — a pattern commonly used together with dragControls. A user who combines dragSnapToCursor with dragControls + dragListener={false} will see no snap behaviour and get no warning.

Suggested change
/**
* If `true`, the element will snap so that the cursor is centered on it
* when a drag gesture starts. This is the equivalent of passing
* `{ snapToCursor: true }` to a `dragControls.start()` call but works
* for any drag-enabled motion component.
*
* ```jsx
* <motion.div drag dragSnapToCursor />
* ```
*/
dragSnapToCursor?: boolean
/**
* If `true`, the element will snap so that the cursor is centered on it
* when a drag gesture starts. This is the equivalent of passing
* `{ snapToCursor: true }` to a `dragControls.start()` call but works
* for any drag-enabled motion component.
*
* Note: has no effect when `dragListener` is `false`, in which case pass
* `{ snapToCursor: true }` directly to `dragControls.start()`.
*
* ```jsx
* <motion.div drag dragSnapToCursor />
* ```
*/
dragSnapToCursor?: boolean

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.

[FEATURE] SnapToCursor should also be a drag prop called dragSnapToCursor

1 participant