import { createDraggable, type DragOptions } from'@neodrag/solid';constoptions:DragOptions= { axis: 'y', bounds: 'parent',};const { draggable } =createDraggable();<divuse:draggable={options}>I am draggable</div>;
Reactive options:
import { createSignal } from'solid-js';import { createDraggable } from'@neodrag/solid';const [options, setOptions] =createSignal({ axis: 'y', bounds: 'parent',});<divuse:draggable={options()}>I am draggable</div>;// You can update `options` with `setOptions` anytime and it'll change. Neodrag will automatically update to the new options 😉
Or, you can specify any selector and it will be bound to that.
Note: This library doesn’t check whether the selector is bigger than the node element. You yourself will have to make sure of that, or it may lead to unexpected behavior.
Or, finally, you can pass an object of type DragBoundsCoords. These mimic the css top, right, bottom and left, in the sense that bottom starts from the bottom of the window, and right from right of window. If any of these properties are unspecified, they are assumed to be 0.
exporttypeDragBoundsCoords= {/** Number of pixels from left of the document */left:number;/** Number of pixels from top of the document */top:number;/** Number of pixels from the right side of document */right:number;/** Number of pixels from the bottom of the document */bottom:number;};
Can't go outside the parent element
0, 0
<divuse:draggable={{ bounds: 'parent' }}> Can't go outside the parent element</div>
Can't go outside <body>
0, 0
<divuse:draggable={{ bounds: 'body' }}> Can't go outside body</div>
Limited to: top: 60 left: 20 bottom: 35 right: 30
Bounded by these coordinates from the window's edges.
When to recalculate the dimensions of the `bounds` element.
By default, bounds are recomputed only on dragStart. Use this options to change that behavior.
grid
Type: [number, number] Default Value: undefined
Applies a grid on the page to which the element snaps to when dragging, rather than the default continuous grid.
If you’re programmatically creating the grid, do not set it to [0, 0] ever, that will stop drag at all. Set it to undefined to make it continuous once again
Snaps to 50x50 grid
0, 0
<divuse:draggable={{ grid: [50, 50] }}> Snaps to 50x50 grid</div>
Snaps to 72x91 grid
0, 0
<divuse:draggable={{ grid: [72, 91] }}> Snaps to 72x91 grid</div>
Snaps to 0x0 grid - Won't drag at all
0, 0
<divuse:draggable={{ grid: [0, 0] }}> Snaps to 0x0 grid - Won't drag at all</div>
Applies user-select: none on <body /> element when dragging, to prevent the irritating effect where dragging doesn’t happen and the text is selected. Applied when dragging starts and removed when it stops.
User Select disabled
Hit ctrl + A while dragging - Nothing will be selected
0, 0
<divuse:draggable={{ applyUserSelectHack: true }}> User Select disabled</div>
User Select enabled
Hit ctrl + A while dragging - Text will be selected
0, 0
<divuse:draggable={{ applyUserSelectHack: false }}> User Select enabled</div>
ignoreMultitouch
Type: boolean Default Value: true
Ignores touch events with more than 1 touch.
This helps when you have multiple elements on a canvas where you want to implement pinch-to-zoom behaviour.
Multi touch ignored
0, 0
<divuse:draggable={{ ignoreMultitouch: true }}> Multi touch ignored</div>
Multi touch allowed
0, 0
<divuse:draggable={{ ignoreMultitouch: false }}> Multi touch allowed</div>
CSS Selector of an element or multiple elements inside the parent node(on which use:draggable is applied). Can be an element or elements too. If it is provided, Only clicking and dragging on this element will allow the parent to drag, anywhere else on the parent won’t work.
Won't drag ❌
Drag me ✅
Single handle with selector
0, 0
<divuse:draggable={{ handle: '.handle' }}> Won't drag ❌ <divclass="handle">Drag me ✅</div></div>
Won't drag ❌
Drag me ✅
Single handle with element
0, 0
let handleEl:HTMLDivElement;<divuse:draggable={{ handle: handleEl }}> Won't drag ❌ <divref={handleEl}>Drag me ✅</div></div>
Won't drag ❌
Drag me ✅
Drag me ✅
Multiple handle with selector
0, 0
<divuse:draggable={{ handle: '.handle' }}> Won't drag ❌ <divclass="handle">Drag me ✅</div> <divclass="handle">Drag me ✅</div></div>
Won't drag ❌
Drag me ✅
Drag me ✅
Multiple handle with element
0, 0
let handle1:HTMLDivElement;let handle2:HTMLDivElement;<divuse:draggable={{ handle: [handle1, handle2] }}> Won't drag ❌ <divref={handle1}>Drag me ✅</div> <divref={handle2}>Drag me ✅</div></div>
CSS Selector of an element or multiple elements inside the parent node(on which use:draggable is applied). Can be an element or elements too. If it is provided, Trying to drag inside the cancel element(s) will prevent dragging.
This will drag!
This won't drag
Single cancel with selector
0, 0
<divuse:draggable={{ cancel: '.cancel' }}> This will drag! <divclass="cancel">This won't drag</div></div>
This will drag!
This won't drag
Single cancel passed as element.
0, 0
let cancelEl:HTMLDivElement;<divuse:draggable={{ cancel: cancelEl }}> This will drag! <divref={cancelEl}>This won't drag</div></div>
This will drag!
This won't drag
This won't drag
Multiple cancel passed as element.
0, 0
<divuse:draggable={{ cancel: '.cancel' }}> This will drag! <divclass="cancel">This won't drag</div> <divclass="cancel">This won't drag</div></div>
This will drag!
This won't drag
This won't drag
Multiple cancel passed as array of elements.
0, 0
let cancel1:HTMLDivElement;let cancel2:HTMLDivElement;<divuse:draggable={{ cancel: [cancel1, cancel2] }}> This will drag! <divref={cancel1}>This won't drag</div> <divref={cancel2}>This won't drag</div></div>
defaultClass
Type: string Default Value: undefined
Class to apply to draggable element.
If handle is provided, it will still apply class on the parent element, NOT the handle.
defaultClassDragging
Type: string Default Value: 'neodrag-dragging'
Class to apply on the parent element when it is dragging
defaultClassDragged
Type: string Default Value: 'neodrag-dragged'
Class to apply on the parent element if it has been dragged at least once. Removed once dragging stops.
DragOptions is the documented list of all options provided by the component.
DragAxis is the type of axis option, and is equal to 'both' | 'x' | 'y' | 'none'.
DragBounds is 'parent' | string | Partial<DragBoundsCoords>, the complete type of bounds option.
DragBoundsCoords is when you’re specifying the bounds field using an object, this is the type needed for that.
exporttypeDragBoundsCoords= {/** Number of pixels from left of the window */left:number;/** Number of pixels from top of the window */top:number;/** Number of pixels from the right side of window */right:number;/** Number of pixels from the bottom of the window */bottom:number;};
Controlled vs Uncontrolled
This is taken straight from React’s philosophy(After all, this package is inspired from react-draggable).
Uncontrolled means your app doesn’t control the dragging of the app. Meaning, the user drags the element, it changes position, and you do something with that action. You yourself don’t change position of the element or anything. This is the default behavior of this library.
Controlled means your app, using state variables, changes the position of the element, or in simple terms, programmatically drag the element. You basically set the position property to { x: 10, y: 50 }(or any other numbers), and voila! yur now controlling the position of the element programmatically 🥳🥳
OFC, this library doesn’t go fully Controlled. The user can still drag it around even when position is set.
So, when you change position, the element position changes. However, when the element is dragged by user interaction, position is not changed. This is done intentionally, as two-way data binding here isn’t possible and also will lead to unexpected behavior. To keep the position variable up to date, use the onDrag event to keep your state up to date to the draggable’s internal state.
To have it be strictly Controlled, meaning it can only be moved programmatically, add the disabled option to your draggable element’s config
So why an extra function? Because use:draggable, after compiled, is just treated as a string. This means, the typescript compiler/rollup will just remove the import named draggable completely, breaking the whole code. Hence a wrapper function is needed.
In future, if SolidJS introduces a mechanism similar to Svelte’s actions, I’ll be able to get rid of that extra call entirely! So if you find this syntax a little more verbose, bear with me, it might become better :)
Credits
Inspired from the amazing react-draggable library, and implements the same API.