Neodrag v2 to v3
Migrating from Neodrag v2 to v3
This guide covers migrating from Neodrag v2’s options-based API to v3’s plugin-based architecture.
Why Migrate to v3?
v2 Limitations:
- Monolithic bundle - pay for unused features
- Performance issues - 3 event listeners per draggable
- Limited extensibility - can’t add custom behavior
- Manual reactivity management
v3 Improvements:
- Tree-shakable plugins - 50% smaller bundle
- Event delegation - 3 listeners total regardless of draggable count
- Custom plugin support
- Better performance and pointer capture
Architecture Overview
Aspect | v2 | v3 |
---|---|---|
Configuration | Single options object | Plugin array |
Reactivity | Automatic | Manual compartments |
Events | Built-in callbacks | Event plugin |
Bounds | String or object | BoundsFrom helpers |
Bundle Size | ~2.2KB all features | ~1.1KB plus only used plugins |
Installation
npm install @neodrag/solid@next
// Old imports
import { createDraggable } from '@neodrag/solid';
// New imports
import {
useDraggable,
axis,
bounds,
BoundsFrom,
events,
} from '@neodrag/solid';
Basic Usage Examples
Before - v2 Options:
import { createDraggable } from '@neodrag/solid';
function App() {
const { draggable } = createDraggable();
return (
<div
use:draggable={{
axis: 'x',
bounds: 'parent',
grid: [10, 10],
onDrag: (data) => console.log(data),
}}
>
Drag me
</div>
);
}
After - v3 Plugins:
import { createSignal } from 'solid-js';
import {
useDraggable,
axis,
bounds,
BoundsFrom,
grid,
events,
} from '@neodrag/solid';
function App() {
const [element, setElement] = createSignal();
useDraggable(element, [
axis('x'),
bounds(BoundsFrom.parent()),
grid([10, 10]),
events({ onDrag: (data) => console.log(data) }),
]);
return <div ref={setElement}>Drag me</div>;
}
Plugin Conversion Reference
Movement Control
v2 Option | v3 Plugin | Purpose |
---|---|---|
axis: 'x' | axis('x') | Horizontal only |
axis: 'y' | axis('y') | Vertical only |
axis: 'both' | no plugin needed | Both directions (default) |
disabled: true | disabled() | Disable dragging |
Bounds and Constraints
v2 Option | v3 Plugin | Purpose |
---|---|---|
bounds: 'parent' | bounds(BoundsFrom.parent()) | Constrain to parent element |
bounds: '#container' | bounds(BoundsFrom.selector('#container')) | Constrain to specific element |
bounds: element | bounds(BoundsFrom.element(element)) | Constrain to DOM element |
bounds: { top: 50 } | bounds(BoundsFrom.viewport({ top: 50 })) | Constrain to viewport with padding |
Grid and Snapping
v2 Option | v3 Plugin | Purpose |
---|---|---|
grid: [20, 20] | grid([20, 20]) | Snap to grid |
threshold: { distance: 5 } | threshold({ distance: 5 }) | Movement threshold |
threshold: { delay: 100 } | threshold({ delay: 100 }) | Time delay threshold |
Controls and Interaction
v2 Option | v3 Plugin | Purpose |
---|---|---|
handle: '.handle' | controls({ allow: ControlFrom.selector('.handle') }) | Drag only from handle |
cancel: '.cancel' | controls({ block: ControlFrom.selector('.cancel') }) | Prevent drag from area |
ignoreMultitouch: true | ignoreMultitouch(true) | Ignore multiple touches |
Events and Callbacks
Events moved from options to dedicated plugin. Event data structure changed:
- v2:
offsetX
,offsetY
,rootNode
,currentNode
,event
- v3:
offset
object withx
andy
properties,rootNode
,currentNode
,event
v2 Events:
<div use:draggable={{
onDragStart: ({ offsetX, offsetY }) => console.log('start', offsetX, offsetY),
onDrag: ({ offsetX, offsetY }) => console.log('drag', offsetX, offsetY),
onDragEnd: ({ offsetX, offsetY }) => console.log('end', offsetX, offsetY)
}}>
v3 Events:
useDraggable(element, [
events({
onDragStart: ({ offset }) =>
console.log('start', offset.x, offset.y),
onDrag: ({ offset }) => console.log('drag', offset.x, offset.y),
onDragEnd: ({ offset }) => console.log('end', offset.x, offset.y),
}),
]);
Reactive Updates: The Biggest Change
v2 automatically updated when options changed. v3 requires manual compartments for reactive behavior.
v2 Automatic Reactivity:
function App() {
const [currentAxis, setCurrentAxis] = createSignal('x');
const [isConstrained, setIsConstrained] = createSignal(false);
const options = () => ({
axis: currentAxis(),
bounds: isConstrained() ? 'parent' : undefined,
});
return (
<div>
<div use:draggable={options()}>Drag me</div>
<button onClick={() => setCurrentAxis('y')}>Switch to Y</button>
<button onClick={() => setIsConstrained(!isConstrained())}>
Toggle bounds
</button>
</div>
);
}
v3 Manual Compartments:
import { createCompartment } from '@neodrag/solid';
function App() {
const [element, setElement] = createSignal();
const [currentAxis, setCurrentAxis] = createSignal('x');
const [isConstrained, setIsConstrained] = createSignal(false);
const axisComp = createCompartment(() =>
currentAxis() === 'both' ? null : axis(currentAxis()),
);
const boundsComp = createCompartment(() =>
isConstrained() ? bounds(BoundsFrom.parent()) : null,
);
useDraggable(element, [axisComp, boundsComp]);
return (
<div>
<div ref={setElement}>Drag me</div>
<button onClick={() => setCurrentAxis('y')}>Switch to Y</button>
<button onClick={() => setIsConstrained(!isConstrained())}>
Toggle bounds
</button>
</div>
);
}
New v3 Features
Custom Plugins
Create your own dragging behavior:
import { useDraggable, unstable_definePlugin } from '@neodrag/solid';
const loggerPlugin = unstable_definePlugin(() => ({
name: 'logger',
setup(ctx) {
console.log('Drag initialized');
return { startTime: 0 };
},
start(ctx, state, event) {
state.startTime = Date.now();
console.log('Drag started');
},
drag(ctx, state, event) {
const duration = Date.now() - state.startTime;
console.log(`Dragging for ${duration}ms`);
},
end(ctx, state, event) {
console.log('Drag ended');
},
}));
function CustomExample() {
const [element, setElement] = createSignal();
useDraggable(element, [loggerPlugin]);
return <div ref={setElement}>Custom behavior</div>;
}
ScrollLock Plugin
Prevent page scrolling during drag:
import { useDraggable, scrollLock } from '@neodrag/solid';
function ScrollLockExample() {
const [element, setElement] = createSignal();
useDraggable(element, [
scrollLock({
lockAxis: 'both',
allowScrollbar: false,
}),
]);
return <div ref={setElement}>No page scroll while dragging</div>;
}
Framework-Specific Syntax Changes
Key Solid Changes:
createDraggable()
pattern →useDraggable()
hookuse:draggable
directive →useDraggable(element, plugins)
- Add
createCompartment
for reactive plugins - Works with Solid’s reactivity system
Migration Checklist
Step 1: Update Dependencies
- Install v3 package for your framework
- Update import statements to include needed plugins
- Update TypeScript types if using TypeScript
Step 2: Convert Options to Plugins
- Replace options object with plugin array
- Convert each option using the reference tables above
- Import required plugin functions
Step 3: Handle Reactive Updates
- Identify dynamic/reactive options in v2 code
- Create compartments for each reactive value
- Wire up compartment updates to state changes
Step 4: Update Framework Syntax
- Apply framework-specific syntax changes
- Update event handlers to use events plugin
- Test basic functionality
Step 5: Update Event Data Usage
- Change from offsetX/offsetY to offset.x/offset.y
- Update any event handler logic
- Verify all event callbacks work correctly
Step 6: Test and Validate
- Test all drag behaviors
- Verify reactive updates work with compartments
- Check performance and bundle size improvements
- Test on different devices and browsers
Common Migration Issues
Missing Plugin Imports Make sure to import all plugins you use. Forgetting imports is the most common issue.
Reactivity Not Working Remember to use compartments for any value that should update dynamically. v3 doesn’t have automatic reactivity like v2.
Events Not Firing Events are now handled by the events plugin, not built-in options. Add the events plugin with your callbacks.
TypeScript Errors Update type imports and ensure you’re using the correct v3 types for your framework.
Performance Benefits
Memory Usage: Event delegation means 3 total listeners instead of 3 per draggable
Reliability: Pointer capture prevents losing drag when moving over iframes
Extensibility: Custom plugins allow community-driven extensions
The migration requires some refactoring, but v3 provides significant improvements in performance, bundle size, and flexibility. Take it step-by-step, use compartments for reactivity, and enjoy the enhanced capabilities of Neodrag v3!