Plugin is the abstract base class for extending drag and drop functionality. Plugins provide a way to add custom features and behaviors to the drag and drop system through a consistent, composable API.
Plugin Class
Constructor
Creates a new plugin instance.
class MyPlugin extends Plugin<T, U> {
constructor(manager: T, options?: U) {
super(manager, options);
}
}
manager
T extends DragDropManager
required
The drag and drop manager that owns this plugin.
Optional configuration for the plugin.
T
extends DragDropManager<any, any>
default:"DragDropManager<any, any>"
Type parameter for the drag and drop manager.
U
extends PluginOptions
default:"PluginOptions"
Type parameter for plugin options.
Properties
manager
The drag and drop manager instance that this plugin is bound to.const manager = plugin.manager;
options
The configuration options for this plugin instance.const options = plugin.options;
plugin.options = newOptions;
disabled
Whether the plugin instance is disabled.This property is reactive and triggers effects when accessed or modified.
Methods
enable()
Enables a disabled plugin instance.
This method triggers effects when called.
disable()
Disables an enabled plugin instance.
This method triggers effects when called.
isDisabled()
Checks if the plugin instance is disabled.
const disabled = plugin.isDisabled();
true if the plugin is disabled.
This method does not trigger effects when accessed.
Configures a plugin instance with new options.
plugin.configure({option: 'value'});
The new options to apply.
destroy()
Destroys a plugin instance and cleans up its resources.
This method:
- Calls all registered cleanup functions
- Should be overridden by subclasses to clean up additional resources
Protected Methods
registerEffect()
Registers an effect that will be cleaned up when the plugin is destroyed.
protected registerEffect(callback: () => void): () => void
The effect callback to register. Should be bound to this.
A function to dispose of the effect.
Static Methods
Configures a plugin constructor with default options.
const ConfiguredPlugin = MyPlugin.configure({
option1: 'value1',
option2: true
});
The options to configure the constructor with.
A configured plugin descriptor that can be passed to the manager.
This method is used to configure the options that the plugin constructor will use to create plugin instances.
Types
PluginOptions
Base type for plugin options.
type PluginOptions = Record<string, any>;
PluginConstructor
Constructor type for creating plugin instances.
interface PluginConstructor<
T extends DragDropManager<any, any> = DragDropManager<any, any>,
U extends Plugin<T> = Plugin<T>,
V extends PluginOptions = InferPluginOptions<U>
> {
new (manager: T, options?: V): U;
}
PluginDescriptor
Descriptor type for configuring plugins.
type PluginDescriptor<
T extends DragDropManager<any, any> = DragDropManager<any, any>,
U extends Plugin<T> = Plugin<T>,
V extends PluginConstructor<T, U> = PluginConstructor<T, U>
> = {
plugin: V;
options?: InferPluginOptions<U>;
};
Plugins
Array type for multiple plugin configurations.
type Plugins<T extends DragDropManager<any, any> = DragDropManager<any, any>> =
(PluginConstructor<T> | PluginDescriptor<T>)[];
InferPluginOptions
Infers the options type from a plugin constructor or instance.
type InferPluginOptions<P> =
P extends PluginConstructor<any, any, infer T> ? T :
P extends Plugin<any, infer T> ? T :
never;
InferManager
Infers the manager type from a plugin instance.
type InferManager<P> =
P extends Plugin<infer T extends DragDropManager<any, any>> ? T : never;
CorePlugin Class
Base class for core plugins that ship with the library.
class CorePlugin<
T extends DragDropManager<any, any> = DragDropManager<any, any>,
U extends PluginOptions = PluginOptions
> extends Plugin<T, U> {}
Core plugins follow the same API as regular plugins but are marked as part of the library.
Utility Functions
Utility function to create a plugin descriptor.
import {configure} from '@dnd-kit/abstract';
const descriptor = configure(MyPlugin, {option: 'value'});
plugin
PluginConstructor
required
The plugin constructor.
The options to configure with.
configurator()
Utility function to create a plugin configurator.
import {configurator} from '@dnd-kit/abstract';
const configureMyPlugin = configurator(MyPlugin);
const configured = configureMyPlugin({option: 'value'});
descriptor()
Utility function to extract plugin and options from a descriptor or constructor.
import {descriptor} from '@dnd-kit/abstract';
const {plugin, options} = descriptor(MyPlugin.configure({option: 'value'}));
// or
const {plugin, options} = descriptor(MyPlugin);
entry
PluginConstructor | PluginDescriptor
required
The plugin constructor or descriptor.
return
{plugin: PluginConstructor, options?: PluginOptions}
An object containing the plugin constructor and optional options.
Implementing a Custom Plugin
Basic Implementation
import {Plugin, type PluginOptions} from '@dnd-kit/abstract';
import type {DragDropManager} from '@dnd-kit/abstract';
interface MyPluginOptions extends PluginOptions {
debug?: boolean;
}
class MyPlugin extends Plugin<DragDropManager, MyPluginOptions> {
constructor(manager: DragDropManager, options?: MyPluginOptions) {
super(manager, options);
// Register effects that should run while the plugin is active
this.registerEffect(() => {
if (this.options?.debug) {
console.log('Plugin activated');
}
});
}
public destroy() {
if (this.options?.debug) {
console.log('Plugin destroyed');
}
super.destroy();
}
}
With Event Listeners
import {Plugin} from '@dnd-kit/abstract';
import type {DragDropManager, DragStartEvent} from '@dnd-kit/abstract';
class EventListenerPlugin extends Plugin {
constructor(manager: DragDropManager, options?: PluginOptions) {
super(manager, options);
// Register effect to add event listener
this.registerEffect(() => {
const handleDragStart = (event: DragStartEvent, manager: DragDropManager) => {
if (this.isDisabled()) return;
console.log('Drag started:', event.operation.source?.id);
};
// Add listener
const cleanup = this.manager.monitor.addEventListener(
'dragstart',
handleDragStart
);
// Return cleanup function
return cleanup;
});
}
}
With State Management
import {Plugin} from '@dnd-kit/abstract';
import {reactive, effect} from '@dnd-kit/state';
class StatefulPlugin extends Plugin {
@reactive
private accessor counter = 0;
constructor(manager: DragDropManager, options?: PluginOptions) {
super(manager, options);
this.registerEffect(() => {
const cleanup = this.manager.monitor.addEventListener('dragmove', () => {
if (!this.isDisabled()) {
this.counter++;
}
});
return cleanup;
});
// React to counter changes
this.registerEffect(() => {
console.log('Move count:', this.counter);
});
}
}
Advanced Plugin with Multiple Features
import {Plugin, type PluginOptions} from '@dnd-kit/abstract';
import type {DragDropManager} from '@dnd-kit/abstract';
interface AdvancedPluginOptions extends PluginOptions {
onDragStart?: (sourceId: string) => void;
onDragEnd?: (sourceId: string, targetId?: string) => void;
trackHistory?: boolean;
}
class AdvancedPlugin extends Plugin<DragDropManager, AdvancedPluginOptions> {
private history: Array<{source: string; target?: string}> = [];
constructor(manager: DragDropManager, options?: AdvancedPluginOptions) {
super(manager, options);
// Listen to drag start
this.registerEffect(() => {
return this.manager.monitor.addEventListener('dragstart', (event) => {
if (this.isDisabled()) return;
const sourceId = event.operation.source?.id?.toString();
if (sourceId) {
this.options?.onDragStart?.(sourceId);
}
});
});
// Listen to drag end
this.registerEffect(() => {
return this.manager.monitor.addEventListener('dragend', (event) => {
if (this.isDisabled()) return;
const sourceId = event.operation.source?.id?.toString();
const targetId = event.operation.target?.id?.toString();
if (sourceId) {
this.options?.onDragEnd?.(sourceId, targetId);
if (this.options?.trackHistory) {
this.history.push({source: sourceId, target: targetId});
}
}
});
});
}
public getHistory() {
return [...this.history];
}
public clearHistory() {
this.history = [];
}
public destroy() {
this.clearHistory();
super.destroy();
}
}
Usage Examples
Using Plugins with Manager
import {DragDropManager} from '@dnd-kit/abstract';
import {MyPlugin, AnotherPlugin} from './plugins';
const manager = new DragDropManager({
plugins: [MyPlugin, AnotherPlugin]
});
Configuring Plugins
const manager = new DragDropManager({
plugins: [
MyPlugin.configure({
debug: true,
option: 'value'
}),
AnotherPlugin
]
});
Per-Draggable Plugins
import {Draggable} from '@dnd-kit/abstract';
const draggable = new Draggable(
{
id: 'item-1',
plugins: [MyPlugin.configure({option: 'value'})]
},
manager
);
Extending Defaults
const manager = new DragDropManager({
plugins: (defaults) => [
...defaults,
MyPlugin,
AnotherPlugin
]
});
Dynamic Plugin Management
const manager = new DragDropManager();
// Add plugins later
manager.plugins = [MyPlugin, AnotherPlugin];
// Disable a plugin
const myPlugin = manager.plugins.find(p => p instanceof MyPlugin);
myPlugin?.disable();
// Re-enable
myPlugin?.enable();
Accessing Plugin Configuration
const draggable = new Draggable(
{
id: 'item-1',
plugins: [
MyPlugin.configure({option1: 'value1'})
]
},
manager
);
// Get plugin config for this draggable
const config = draggable.pluginConfig(MyPlugin);
console.log(config); // {option1: 'value1'}
Plugin Registry
The PluginRegistry class manages plugin instances:
class PluginRegistry {
// Get all registered plugins
get values(): Plugin[];
// Set plugins (replaces existing)
set values(plugins: Plugins);
// Register a plugin constructor
register(plugin: PluginConstructor): void;
// Unregister a plugin constructor
unregister(plugin: PluginConstructor): void;
}
Access via manager:
const manager = new DragDropManager();
// Get active plugins
const plugins = manager.registry.plugins.values;
// Register a plugin
manager.registry.plugins.register(MyPlugin);
Common Plugin Patterns
Logging Plugin
class LoggingPlugin extends Plugin {
constructor(manager: DragDropManager, options?: PluginOptions) {
super(manager, options);
this.registerEffect(() => {
const events = ['dragstart', 'dragmove', 'dragover', 'dragend'] as const;
const cleanups = events.map(eventName =>
this.manager.monitor.addEventListener(eventName, (event) => {
if (!this.isDisabled()) {
console.log(`[${eventName}]`, event);
}
})
);
return () => cleanups.forEach(cleanup => cleanup());
});
}
}
Analytics Plugin
class AnalyticsPlugin extends Plugin {
private startTime?: number;
constructor(manager: DragDropManager, options?: PluginOptions) {
super(manager, options);
this.registerEffect(() => {
return this.manager.monitor.addEventListener('dragstart', () => {
this.startTime = Date.now();
});
});
this.registerEffect(() => {
return this.manager.monitor.addEventListener('dragend', (event) => {
if (this.startTime) {
const duration = Date.now() - this.startTime;
analytics.track('drag_completed', {
duration,
canceled: event.canceled,
sourceId: event.operation.source?.id,
targetId: event.operation.target?.id
});
}
});
});
}
}
class AutoScrollPlugin extends Plugin {
private rafId?: number;
constructor(manager: DragDropManager, options?: PluginOptions) {
super(manager, options);
this.registerEffect(() => {
const handleDragMove = () => {
if (this.isDisabled()) return;
this.rafId = requestAnimationFrame(() => {
// Auto-scroll logic
const {position} = this.manager.dragOperation;
const threshold = 50;
if (position.current.y < threshold) {
window.scrollBy(0, -10);
} else if (window.innerHeight - position.current.y < threshold) {
window.scrollBy(0, 10);
}
});
};
const cleanup = this.manager.monitor.addEventListener(
'dragmove',
handleDragMove
);
return () => {
cleanup();
if (this.rafId) {
cancelAnimationFrame(this.rafId);
}
};
});
}
}
See Also