Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/add-snap-center-to-cursor-modifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@dnd-kit/dom': minor
Comment thread
dooohun marked this conversation as resolved.
Outdated
---

Added `SnapCenterToCursor` modifier that offsets the drag transform so the dragged element's center snaps to the cursor position.
16 changes: 16 additions & 0 deletions apps/docs/extend/modifiers.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ Environment-specific modifiers for the DOM, available in `@dnd-kit/dom/modifiers
<Card title="RestrictToElement">
Constrain movement within a container element
</Card>
<Card title="SnapCenterToCursor">
Comment thread
dooohun marked this conversation as resolved.
Outdated
Offset the drag transform so the element's center snaps to the cursor position
</Card>
</CardGroup>

## Usage
Expand Down Expand Up @@ -106,6 +109,19 @@ const draggable = new Draggable({
}, manager);
```

### SnapCenterToCursor

When dragging starts from the edge of an element, `SnapCenterToCursor` immediately repositions the element so its center aligns with the cursor.

```ts
import {DragDropManager} from '@dnd-kit/dom';
import {SnapCenterToCursor} from '@dnd-kit/dom/modifiers';

const manager = new DragDropManager({
modifiers: [SnapCenterToCursor],
});
```

<Info>
Local modifiers on draggable elements take precedence over global modifiers.
</Info>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import {
RestrictToHorizontalAxis,
RestrictToVerticalAxis,
} from '@dnd-kit/abstract/modifiers';
import {RestrictToElement, RestrictToWindow} from '@dnd-kit/dom/modifiers';
import {
RestrictToElement,
RestrictToWindow,
SnapCenterToCursor,
} from '@dnd-kit/dom/modifiers';

import docs from './docs/ModifierDocs.mdx';
import {DraggableExample} from '../DraggableExample';
Expand Down Expand Up @@ -69,3 +73,10 @@ export const SnapModifierExample: Story = {
name: 'Snap to grid',
render: SnapToGridExample,
};

export const SnapCenterToCursorModifier: Story = {
name: 'Snap center to cursor',
args: {
modifiers: [SnapCenterToCursor],
},
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {RestrictToElement} from '@dnd-kit/dom/modifiers';
import {RestrictToElement, SnapCenterToCursor} from '@dnd-kit/dom/modifiers';

import {Info, Preview} from '../../../../components';
import {DraggableExample} from '../../DraggableExample';
Expand Down Expand Up @@ -26,6 +26,44 @@ Modifiers let you dynamically modify the movement coordinates that are detected
- Restricting motion to the draggable node's scroll container bounding rectangle
- Applying resistance or clamping the motion

## Built-in modifiers

### `@dnd-kit/abstract/modifiers`

| Modifier | Description |
|----------|-------------|
| `RestrictToVerticalAxis` | Restricts drag movement to the vertical axis |
| `RestrictToHorizontalAxis` | Restricts drag movement to the horizontal axis |
| `SnapModifier` | Snaps drag movement to a grid |

### `@dnd-kit/dom/modifiers`

| Modifier | Description |
|----------|-------------|
| `RestrictToWindow` | Restricts the draggable element to stay within the viewport |
| `RestrictToElement` | Restricts the draggable element to stay within a given element |
| `SnapCenterToCursor` | Offsets the drag transform so the element's center snaps to the cursor position |

#### `SnapCenterToCursor`

When dragging starts from the edge of an element, `SnapCenterToCursor` immediately repositions the element so its center aligns with the cursor. This is useful for drag-and-drop interactions where the element should always be centered under the pointer.

<Preview>
<DraggableExample modifiers={[SnapCenterToCursor]} />
</Preview>

```jsx
import {useDraggable} from '@dnd-kit/react';
import {SnapCenterToCursor} from '@dnd-kit/dom/modifiers';

function Draggable({id}) {
const {ref} = useDraggable({
id,
modifiers: [SnapCenterToCursor],
});
}
```

## Usage

Modifiers can be applied globally or to individual draggable elements.
Expand Down
2 changes: 1 addition & 1 deletion packages/abstract/src/core/plugins/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ export abstract class Plugin<
* @param callback - The effect callback to register
* @returns A function to dispose of the effect
*/
protected registerEffect(callback: () => void) {
protected registerEffect(callback: () => void): CleanupFunction {
const dispose = effect(callback.bind(this));

this.#cleanupFunctions.add(dispose);
Expand Down
2 changes: 1 addition & 1 deletion packages/dom/src/core/sensors/keyboard/KeyboardSensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export class KeyboardSensor extends Sensor<

protected listeners = new Listeners();

public bind(source: Draggable, options = this.options) {
public bind(source: Draggable, options = this.options): CleanupFunction {
const unbind = effect(() => {
const target = source.handle ?? source.element;
const listener: EventListener = (event: Event) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/dom/src/core/sensors/pointer/PointerSensor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export class PointerSensor extends Sensor<
return constraints;
}

public bind(source: Draggable, options = this.options) {
public bind(source: Draggable, options = this.options): CleanupFunction {
const unbind = effect(() => {
const controller = new AbortController();
const {signal} = controller;
Expand Down
18 changes: 18 additions & 0 deletions packages/dom/src/modifiers/SnapCenterToCursor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {Modifier, configurator} from '@dnd-kit/abstract';
import type {DragOperation} from '@dnd-kit/abstract';
import type {DragDropManager} from '@dnd-kit/dom';

export class SnapCenterToCursor extends Modifier<DragDropManager> {
Comment thread
dooohun marked this conversation as resolved.
Outdated
apply({activatorEvent, shape, transform}: DragOperation) {
if (!shape || !(activatorEvent instanceof PointerEvent)) {
return transform;
}

return {
x: transform.x + activatorEvent.clientX - shape.initial.center.x,
y: transform.y + activatorEvent.clientY - shape.initial.center.y,
};
}

static configure = configurator(SnapCenterToCursor);
}
1 change: 1 addition & 0 deletions packages/dom/src/modifiers/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export {RestrictToWindow} from './RestrictToWindow.ts';
export {RestrictToElement} from './RestrictToElement.ts';
export {SnapCenterToCursor} from './SnapCenterToCursor.ts';