Modifier is the base class for transforming drag operation coordinates. Modifiers enable features like snapping to grids, restricting movement to specific boundaries, applying physics-based constraints, and implementing custom drag behaviors.
Modifier Class
Constructor
Creates a new modifier instance.
class MyModifier extends Modifier < T , U > {
constructor ( manager : T , options ?: U ) {
super ( manager , options );
}
}
manager
T extends DragDropManager
required
The drag and drop manager that owns this modifier.
options
U extends ModifierOptions
Optional configuration for the modifier.
T
extends DragDropManager<any, any>
default: "DragDropManager<any, any>"
Type parameter for the drag and drop manager.
U
extends ModifierOptions
default: "ModifierOptions"
Type parameter for modifier options.
Properties
manager
The drag and drop manager instance that this modifier is bound to. const manager = modifier . manager ;
options
The configuration options for this modifier instance. const options = modifier . options ;
disabled
Whether the modifier instance is disabled. modifier . disabled = true ;
Inherited from Plugin.
Methods
apply()
Applies the modifier to the current drag operation.
public apply ( operation : DragOperationSnapshot ): Coordinates ;
operation
DragOperationSnapshot<any, any>
required
The current state of the drag operation. Show DragOperationSnapshot properties
The draggable being dragged.
Current position with history (current, initial, previous).
Current transform coordinates before this modifier.
shape
WithHistory<Shape> | null
Current shape with history.
Event that initiated the drag.
Current operation status.
Whether the operation was canceled.
The transformed coordinates.
The default implementation returns the original transform unchanged. Override this method to implement custom transformation logic.
Inherited from Plugin
enable()
Enables a disabled modifier instance.
disable()
Disables an enabled modifier instance.
isDisabled()
Checks if the modifier instance is disabled.
const disabled = modifier . isDisabled ();
true if the modifier is disabled.
Configures a modifier instance with new options.
modifier . configure ({ option: 'value' });
The new options to apply.
destroy()
Destroys a modifier instance and cleans up its resources.
Static Methods
Configures a modifier constructor with default options.
const ConfiguredModifier = MyModifier . configure ({
gridSize: 20 ,
boundary: 'window'
});
The options to configure the constructor with.
A configured modifier descriptor that can be passed to the manager or draggable.
Types
ModifierOptions
Base type for modifier options.
type ModifierOptions = PluginOptions ;
type PluginOptions = Record < string , any >;
ModifierConstructor
Constructor type for creating modifier instances.
type ModifierConstructor < T extends DragDropManager < any , any >> =
PluginConstructor < T , Modifier < T , any >>;
interface PluginConstructor < T , U > {
new ( manager : T , options ?: PluginOptions ) : U ;
}
ModifierDescriptor
Descriptor type for configuring modifiers.
type ModifierDescriptor < T extends DragDropManager < any , any >> =
PluginDescriptor < T , Modifier < T , any >, ModifierConstructor < T >>;
type PluginDescriptor < T , U , V > = {
plugin : V ;
options ?: PluginOptions ;
};
Modifiers
Array type for multiple modifier configurations.
type Modifiers < T extends DragDropManager < any , any >> =
( ModifierConstructor < T > | ModifierDescriptor < T >)[];
Implementing a Custom Modifier
Basic Implementation
import { Modifier , type ModifierOptions } from '@dnd-kit/abstract' ;
import type { DragOperationSnapshot , Coordinates } from '@dnd-kit/abstract' ;
interface SnapModifierOptions extends ModifierOptions {
gridSize : number ;
}
class SnapModifier extends Modifier < DragDropManager , SnapModifierOptions > {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform } = operation ;
const gridSize = this . options ?. gridSize ?? 20 ;
return {
x: Math . round ( transform . x / gridSize ) * gridSize ,
y: Math . round ( transform . y / gridSize ) * gridSize
};
}
}
Advanced Implementation
import { Modifier , type ModifierOptions } from '@dnd-kit/abstract' ;
import type { Coordinates , Shape } from '@dnd-kit/geometry' ;
interface RestrictModifierOptions extends ModifierOptions {
boundary : 'window' | 'parent' | Shape ;
}
class RestrictModifier extends Modifier < DragDropManager , RestrictModifierOptions > {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform , shape } = operation ;
const boundary = this . options ?. boundary ;
if ( ! boundary || ! shape ) {
return transform ;
}
const bounds = this . getBounds ( boundary );
const draggableRect = shape . current ;
// Calculate restricted position
let x = transform . x ;
let y = transform . y ;
if ( draggableRect . x + x < bounds . left ) {
x = bounds . left - draggableRect . x ;
}
if ( draggableRect . x + x + draggableRect . width > bounds . right ) {
x = bounds . right - draggableRect . x - draggableRect . width ;
}
if ( draggableRect . y + y < bounds . top ) {
y = bounds . top - draggableRect . y ;
}
if ( draggableRect . y + y + draggableRect . height > bounds . bottom ) {
y = bounds . bottom - draggableRect . y - draggableRect . height ;
}
return { x , y };
}
private getBounds ( boundary : RestrictModifierOptions [ 'boundary' ]) {
if ( boundary === 'window' ) {
return {
left: 0 ,
top: 0 ,
right: window . innerWidth ,
bottom: window . innerHeight
};
}
// Handle other boundary types...
}
}
Chaining Modifiers
Modifiers are applied in sequence, with each modifier receiving the output of the previous one:
class LoggingModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
console . log ( 'Before:' , operation . transform );
// Pass through unchanged
return operation . transform ;
}
}
const manager = new DragDropManager ({
modifiers: [
SnapModifier . configure ({ gridSize: 20 }),
RestrictModifier . configure ({ boundary: 'window' }),
LoggingModifier
]
});
// Execution order: Snap -> Restrict -> Logging
Usage Examples
Using Modifiers with Manager
import { DragDropManager } from '@dnd-kit/abstract' ;
import { SnapModifier , RestrictModifier } from '@dnd-kit/abstract/modifiers' ;
const manager = new DragDropManager ({
modifiers: [
SnapModifier . configure ({ gridSize: 20 }),
RestrictModifier . configure ({ boundary: 'window' })
]
});
Per-Draggable Modifiers
import { Draggable } from '@dnd-kit/abstract' ;
import { SnapModifier } from '@dnd-kit/abstract/modifiers' ;
const draggable = new Draggable (
{
id: 'item-1' ,
modifiers: [
SnapModifier . configure ({ gridSize: 10 })
]
},
manager
);
// This draggable uses its own modifiers, overriding the manager's
Dynamic Modifier Configuration
const manager = new DragDropManager ();
// Add modifiers later
manager . modifiers = [
SnapModifier . configure ({ gridSize: 20 })
];
// Update modifiers
manager . modifiers = [
SnapModifier . configure ({ gridSize: 40 }),
RestrictModifier . configure ({ boundary: 'parent' })
];
Conditional Modification
class ConditionalModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform , source } = operation ;
// Only apply to certain draggables
if ( source ?. data . snapToGrid ) {
const gridSize = 20 ;
return {
x: Math . round ( transform . x / gridSize ) * gridSize ,
y: Math . round ( transform . y / gridSize ) * gridSize
};
}
return transform ;
}
}
Accessing Operation State
class SmartModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const {
transform ,
source ,
target ,
position ,
shape ,
activatorEvent
} = operation ;
// Use source data
if ( source ?. data . restricted ) {
// Apply restrictions
}
// Check if over a target
if ( target ) {
// Snap to target center
const targetShape = target . shape ;
if ( targetShape && shape ) {
return {
x: targetShape . center . x - shape . initial . center . x ,
y: targetShape . center . y - shape . initial . center . y
};
}
}
// Check movement delta
const delta = position . delta ;
if ( Math . abs ( delta . x ) < 5 && Math . abs ( delta . y ) < 5 ) {
// Minimal movement, return to origin
return { x: 0 , y: 0 };
}
return transform ;
}
}
Disable/Enable Modifiers
const snapModifier = new SnapModifier ( manager , { gridSize: 20 });
// Temporarily disable
snapModifier . disable ();
// Re-enable
snapModifier . enable ();
// Check status
if ( snapModifier . isDisabled ()) {
console . log ( 'Snapping is disabled' );
}
Common Modifier Patterns
Grid Snapping
class GridSnapModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform } = operation ;
const gridSize = this . options ?. gridSize ?? 20 ;
return {
x: Math . round ( transform . x / gridSize ) * gridSize ,
y: Math . round ( transform . y / gridSize ) * gridSize
};
}
}
Restrict to Container
class RestrictToContainerModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform , shape } = operation ;
if ( ! shape ) return transform ;
const container = this . options ?. container ;
if ( ! container ) return transform ;
const bounds = container . getBoundingClientRect ();
const rect = shape . current ;
return {
x: Math . max (
bounds . left - rect . x ,
Math . min ( transform . x , bounds . right - rect . x - rect . width )
),
y: Math . max (
bounds . top - rect . y ,
Math . min ( transform . y , bounds . bottom - rect . y - rect . height )
)
};
}
}
Axis Lock
class AxisLockModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform } = operation ;
const axis = this . options ?. axis ; // 'x' | 'y'
if ( axis === 'x' ) {
return { x: transform . x , y: 0 };
}
if ( axis === 'y' ) {
return { x: 0 , y: transform . y };
}
return transform ;
}
}
Resistance
class ResistanceModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform } = operation ;
const factor = this . options ?. factor ?? 0.5 ;
return {
x: transform . x * factor ,
y: transform . y * factor
};
}
}
Snap to Elements
class SnapToElementsModifier extends Modifier {
public apply ( operation : DragOperationSnapshot ) : Coordinates {
const { transform , shape } = operation ;
if ( ! shape ) return transform ;
const snapTargets = this . options ?. targets ?? [];
const threshold = this . options ?. threshold ?? 20 ;
const currentCenter = {
x: shape . initial . center . x + transform . x ,
y: shape . initial . center . y + transform . y
};
for ( const target of snapTargets ) {
const targetRect = target . getBoundingClientRect ();
const targetCenter = {
x: targetRect . left + targetRect . width / 2 ,
y: targetRect . top + targetRect . height / 2
};
const distance = Math . hypot (
currentCenter . x - targetCenter . x ,
currentCenter . y - targetCenter . y
);
if ( distance < threshold ) {
return {
x: targetCenter . x - shape . initial . center . x ,
y: targetCenter . y - shape . initial . center . y
};
}
}
return transform ;
}
}
See Also