svelte-drag-and-drop-actions
a lightweight but flexible HTML5 Drag-and-Drop implemented as Svelte actions
some of its Features:
All examples are live and may be changed on the fly!
- HTML5 drag-and-drop features remain available (see example)
- works on mobile devices (when combined with svelte-drag-drop-touch)
- supports custom drag images (from existing DOM elements or from HTML markup)
- drag images may even be dynamically created (see example)
- supports simple dragging (i.e., without dropping, see example, don't worry if the example seems to lag - it's just because of the amount of console output)
- provides mechanisms to reduce the code needed to implement element dragging (see example)
- can restrict dragging to a given region (see example)
- supports automatic scrolling (aka "panning") of partially visible elements while s.th. is dragged over them (see example), works fine even on Chrome for desktop (which already provides panning out of the box)
- can mimic real dragging of an element (and not just of a "ghost image" while the original stands still, see many examples, f.e. this one)
- can suppress the drag image at all (offering a bunch of new possibilites using the same code base, see many of the examples, f.e. this one)
- supports dragging elements from a given handle only (instead of the whole element itself, see example)
- offers visual feedback of dragged elements and drop zones by simple styling (using dynamically assigned CSS classes, see several examples, such as this one)
- recognizes when a draggable stands still (i.e., it's "held") over a drop zone for a given time (see example)
- provides lots of live(!) examples for many use cases
- however, unfortunately,
svelte-drag-and-drop-actions
may also suffer from any bugs in the browser implementations of native HTML drag-and-drop (thinking of Safari 13.0/13.1, f.e.) if they can not be compensated by the author
HTML5 Drag-and-Drop allows web applications to provide a visual user interface for transfering data between HTML elements or exchanging data with a browser's environment. Using HTML5 Drag-and-Drop for the operation of an application (i.e. the repositioning, resizing or reordering of elements) is difficult and suffers from some browser-specific bugs.
Instead of fully re-implementing the visual operation of web applications with mouse and touch gestures (as done in agnostic-draggable or svelte-dnd-action), svelte-drag-and-drop-actions
builds upon already existing HTML5 Drag-and-Drop functionality and simply extends it. The result is a lightweight package with a surprisingly simple programming interface. (And because this module is tree-shakable, using the plain dragging functionality only - i.e., without support for dropping - will reduce the import size even further)
A first Svelte component based on svelte-drag-and-drop-actions
is the svelte-sortable-flat-list-view.
But try yourself: there are a number of examples that can be tried out live
NPM users: please consider the Github README for the latest description of this package (as updating the docs would otherwise always require a new NPM package version)
Mobile Developers: since many mobile platforms lack support for native HTML5 drag-and-drop, you should consider importing svelte-drag-drop-touch as a polyfill (a simple import of that package will suffice - there is no extra programming needed)
Just a small note: if you like this module and plan to use it, consider "starring" this repository (you will find the "Star" button on the top right of this page), so that I know which of my repositories to take most care of.
Installation
npm install svelte-drag-and-drop-actions
Usage Example
The following example illustrates plain dragging of a "Draggable" within the bounds of a given "Arena". Read on to understand how it is working.
<script context="module">
import DragDropTouch from 'svelte-drag-drop-touch' // for mobile platforms
import { asDraggable } from 'svelte-drag-and-drop-actions'
</script>
<script>
let DraggableX = 20, DraggableY = 20, DraggableWidth = 80, DraggableHeight = 30
let ArenaWidth = 400, ArenaHeight = 400
function onDragMove (x,y, dx,dy) { DraggableX = x; DraggableY = y }
function onDragEnd (x,y, dx,dy) { DraggableX = x; DraggableY = y }
</script>
<div style="
display:block; position:relative;
width:{ArenaWidth}px; height:{ArenaHeight}px; margin:20px;
border:dotted 1px black; border-radius:4px;
">
<div style="
display:block; position:absolute;
left:{DraggableX}px; top:{DraggableY}px; width:{DraggableWidth}px; height:{DraggableHeight}px;
background:forestgreen; color:white; line-height:30px; text-align:center; cursor:move;
" use:asDraggable={{
minX:0,minY:0, maxX:ArenaWidth-DraggableWidth,maxY:ArenaHeight-DraggableHeight,
onDragStart:{x:DraggableX,y:DraggableY}, onDragMove, onDragEnd
}}
>Drag me!</div>
</div>
Additional, more detailled examples can be found below.
Functional Principle
Svelte is a Framework for building reactive applications, i.e. it assumes, that there is some application state which is used to construct the application's user interface - and whenever any part of this state changes, the corresponding interface elements are updated accordingly.
svelte-drag-and-drop-actions
takes this mechanism into account and assumes that position and size of draggable elements are also part of the application's state. For that reason, the module does not perform any dragging, resizing, sorting (or similar) itself but only provides the coordinates and dimensions needed to update state and - in succession - the visual appearance of any affected elements.
Combined with a rather declarative API (designed with the most common use cases in mind), such an approach leads to a lightweight and easily usable module which still offers programmers full control over the actual side-effects of dragging (and dropping)
If this sounds too abstract, just have a look at the examples: many of them illustrate specific use cases and may therefore serve as a basis for your own development.
Nota bene: since it is based on native HTML5 drag-and-drop, svelte-drag-and-drop-actions
principally also allows dragging and dropping between multiple windows (or tabs) of the same browser or even between browsers. To what extent this works, depends on the participating browsers, the data types involved (transferring text often works best), and may also depend on the operating system used.
Exported Types
TypeScript programmers may import the following types in order to benefit from static type checking (JavaScript programmers may simply skip this section):
type Position = { x:number, y:number }
aPosition
instance represents a single point in a linearly scaled cartesic coordinate system. It may be considered as the same coordinate system a browser uses when coordinates are measured in pixels (px) - with one important exception: the origin of this system can be chosen by the programmer. An intelligent choice of such an origin may simplify the callbacks provided forsvelte-drag-and-drop-actions
and allow to use delivered coordinates, f.e., to set the size of an element directly and without any need for additional calculations.type PositionReference = (
'parent' | 'body' | string | HTMLElement | SVGElement // | MathMLElement
)
aPositionReference
specifies, relative to which element the mouse or touch position is measured. This decision becomes important, if the referred element is scrollable and/or its position or size may be changed by external code (which is neither part of this module nor any of the configured callbacks)type DragDummy = (
string | HTMLElement | SVGElement | 'standard' | 'none' |
((DraggableExtras:any, Element:HTMLElement | SVGElement) => HTMLElement | SVGElement)
)
aDragDummy
specifies which drag image will be shown during dragging (see below for details)type DraggableOptions = {
Extras?:any, relativeTo?:PositionReference, onlyFrom?:string, neverFrom?:string,
Dummy?:DragDummy, DummyOffsetX?:number, DummyOffsetY?:number,
minX?:number, minY?:number, maxX?:number, maxY?:number,
Pannable?:string|HTMLElement|SVGElement,
PanSensorWidth?:number, PanSensorHeight?:number, PanSpeed?:number,
onDragStart?:Position | ((DraggableExtras:any) => Position),
onDragMove?:(x:number,y:number, dx:number,dy:number, DraggableExtras:any) => void,
onDragEnd?: (x:number,y:number, dx:number,dy:number, DraggableExtras:any) => void,
}
aDraggableOptions
instance holds all options for a "Draggable" (see below for details)
type DropOperation = 'copy'|'move'|'link'
aDropOperation
specifies whether the data associated with a droppable element should be copied, moved or linked to a drop target (native HTML5 drag-and-drop jargon calls this an "effect")type DataOfferSet = { [Type:string]:string }
DataOfferSet
instances are dictionaries listing the various data formats offered by a droppable object and the actually offered data for every format. The keys into aDataOfferSet
are often MIME types (or the special value "DownloadURL") but could well be any kind of string (exceptnone
) - a detail which is often used to overcome some limitations of native HTML 5 drag-and-droptype DroppableOptions = DraggableOptions & {
Extras?:any, Operations?:string, DataToOffer?:DataOfferSet,
onDropZoneEnter?:(x:number,y:number, DropZoneExtras:any, DroppableExtras:any) => void,
onDropZoneHover?:(x:number,y:number, DropZoneExtras:any, DroppableExtras:any) => void,
onDropZoneLeave?:(DropZoneExtras:any, DroppableExtras:any) => void,
onDropped?: (x:number,y:number, Operation:DropOperation,
TypeTransferred:string, DataTransferred:any, DropZoneExtras:any, DroppableExtras:any) => void
}
aDroppableOptions
instance holds all extra options for a "Droppable" (please note, that this includes allDraggableOptions
shown above, see below for details)
type TypeAcceptanceSet = { [Type:string]:string }
TypeAcceptanceSet
instances are dictionaries listing the various data formats accepted by a drop zone and the permitted drop operations for every format. The keys into aTypeAcceptanceSet
are the same as for the abovementionedDataOfferSet
stype DropZoneOptions = {
Extras?:any, TypesToAccept?:TypeAcceptanceSet, HoldDelay?:number,
Pannable?:string|'this'|HTMLElement|SVGElement,
PanSensorWidth?:number, PanSensorHeight?:number, PanSpeed?:number,
onDroppableEnter?: (x:number,y:number, Operation:DropOperation,
offeredTypeList:string[], DroppableExtras:any, DropZoneExtras:any) => boolean|undefined,
onDroppableMove?: (x:number,y:number, Operation:DropOperation,
offeredTypeList:string[], DroppableExtras:any, DropZoneExtras:any) => boolean|undefined,
onDroppableHold?: (x:number,y:number, DroppableExtras:any, DropZoneExtras:any) => void,
onDroppableLeave?: (DroppableExtras:any, DropZoneExtras:any) => void,
onDrop?: (x:number,y:number, Operation:DropOperation,
DataOffered:any, DroppableExtras:any, DropZoneExtras:any) => string,
}
aDropZoneOptions
instance holds all options for a drop target (see below for details)
use:asDraggable
use:asDraggable
should be used for elements which will only be dragged around (but never dropped onto another element). Many use cases (from draggable windows over draggable nodes of graphical shapes to the resize handles found in many visual designers) only need this kind of behaviour.
The type annotiations shown below are relevant for TypeScript programmers only, JavaScript programmers may simply ignore them.
use:asDraggable={options}
is the classical pattern for Svelte actions. use:asDraggable
supports the following options (bundled into an instance of type DraggableOptions
):
Extras?:any
Extras
are an optional, user-defined value which can be used during drag-and-drop operations within the same application to identify the draggable component. They become useful as soon as multiple draggables share the same callbacks
relativeTo?:PositionReference
relativeTo
is an optionalPositionReference
(defaulting toparent
) which specifies relative to which element the current mouse or touch position is measured during dragging. It may be set tobody
for measurements relative to the current document body, toparent
for measurements relative to the draggable's containing element, to a CSS selector for measurements relative to the Draggable's closest element matching the given selector (or 'body' otherwise) or to a given HTML or SVG element (which must be part of the document)onlyFrom?:string
onlyFrom
is an optional, comma-separated list of CSS selectors identifying the inner elements of a draggable element, from which a drag operation must be started in order to be allowed. IfonlyFrom
is missing, noonlyFrom
restriction is appliedneverFrom?:string
neverFrom
is an optional, comma-separated list of CSS selectors identifying the inner elements of a draggable element, from which a drag operation must never be started in order to be allowed. IfneverFrom
is missing, noneverFrom
restriction is applied
Dummy?:DragDummy
Dummy
specifies which "drag image" to show at the current mouse or touch position during dragging.Dummy
is optional (defaulting tonone
) and may be set tostandard
(for the HTML5 standard behaviour which usually shows a semi-transparent copy of the actual draggable), tonone
(effectively showing nothing), to some HTML code (which is used to construct the element to be shown during dragging), to a function which receives the draggable's configured extras and the element to be dragged as its arguments and returns an HTML element to be used as the drag dummy or to an already existing HTML element (which must be visible). Important: creating a drag image from HTML or from a function is quite tricky - the approach used here is lightweight but may cause some flickering when dragging is started. Such flickering can usually be avoided by setting the CSS "overflow" of the document body to "hidden". Where this is not possible, constructing an image from HTML using html2canvas may be an (albeit no longer lightweight) alternative. If a function is used to construct the dummy, that function should also care itself about removing the newly created HTML element after use (see below for a suitable example)DummyOffsetX?:number
DummyOffsetX
is an optional number (defaulting to 0) specifying the horizontal offset between the current x position of a mouse or finger during dragging and the shown drag image. It is used ay the second argument toDataTransfer.setDragImage
without prior changeDummyOffsetY?:number
DummyOffsetY
is an optional number (defaulting to 0) specifying the vertical offset between the current y position of a mouse or finger during dragging and the shown drag image. It is used ay the third argument toDataTransfer.setDragImage
without prior change
minX?:number
minX
is an optional number (defaulting to -Infinity) specifying the smallest possible value for thex
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
to effectively hide the drag imageminY?:number
minY
is an optional number (defaulting to -Infinity) specifying the smallest possible value for they
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
to effectively hide the drag imagemaxX?:number
maxX
is an optional number (defaulting to Infinity) specifying the largest possible value for thex
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
tto effectively hide the drag imagemaxY?:number
maxY
is an optional number (defaulting to Infinity) specifying the largest possible value for they
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
to effectively hide the drag image
Pannable?:string|HTMLElement|SVGElement
some browsers provide automatic scrolling of only partially visible elements while a Draggable is dragged over them - but most do not, which is whysvelte-drag-and-drop-actions
implements its own "panning".Pannable
is an optional parameter specifying the element which should be scrolled automatically. It should be a (not necessarily immediate) container of the Draggable and may be set to the DOM element itself or a CSS selector which identifies the desired container. If omitted, no panning will be performed.PanSensorWidth?:number
PanSensorWidth
is an optional ordinal number (defaulting to20
) which specifies the width (in pixels) of the horizontal pan sensor area: panning starts as soon as the mouse pointer (or finger) gets closer than the given number of pixels to the left or right border of the configuredPannable
. If set to0
, no horizontal panning will be performedPanSensorHeight?:number
PanSensorHeight
is an optional ordinal number (defaulting to20
) which specifies the height (in pixels) of the vertical pan sensor area: panning starts as soon as the mouse pointer (or finger) gets closer than the given number of pixels to the upper or lower border of the configuredPannable
. If set to0
, no vertical panning will be performedPanSpeed?:number
PanSpeed
is an optional ordinal number (defaulting to10
) which specifies the "speed" of automatic scrolling - values in the range of 10...20 are reasonable choices, but it is always a good idea to make this parameter configurable for the users of your application. If set to0
, no panning will be performed
onDragStart?:Position | ((DraggableExtras:any) => Position)
onDragStart
is either an optionalPosition
(defaulting to{x:0, y:0}
) or a function returning such aPosition
. When invoked, that function receives anyExtras
configured for the affected draggable. In either case, this parameter specifies the coordinate values from which to start dragging. Important: these "coordinates" do not necessarily represent a particular point on the screen - in fact, e.g., a resize handle may choose to start with the current width and height of the element to be resized and then directly use the results of anyonDragMove
oronDragEnd
as the new element width and height without additional computationonDragMove?: (x:number,y:number, dx:number,dy:number, DraggableExtras:any) => void
onDragMove
is an optional callback which (if given) is called repeatedly during dragging. When invoked, it receives the current drag result (inx
andy
, with an initial value chosen byonDragStart
), the offset between this and the previous invocation (indx
anddy
) and anyExtras
configured for the affected draggableonDragEnd?: (x:number,y:number, dx:number,dy:number, DraggableExtras:any) => void
onDragEnd
is is an optional callback which (if given) is called once when dragging has finished. When invoked, it receives the final drag result (inx
andy
, with the origin chosen byonDragStart
), the offset between this and the most recent invocation ofonDragMove
(indx
anddy
) and anyExtras
configured for the affected draggable
While being dragged, the CSS class dragged
is assigned to the draggable element (not the drag image!). It will be removed again as soon as dragging has ended.
use:asDraggable
should never be combined with use:asDroppable
(as the latter already includes the former) - if you want an element to be dropped somewhere, simply use use:asDroppable
alone.
use:asDraggable
may, however, be combined with use:asDropZone
in order to implement draggable drop zones.
use:asDroppable
use:asDroppable
is an extension of use:asDraggable
and should be used for elements which will not only be dragged around but also dropped onto another element. Because of the underlying HTML5 drag-and-drop, dropping an element onto another one may lead to an exchange of data - but svelte-drag-and-drop-actions
extends that functionality (for elements within the same application) and gives the programmer full control over what a "drop" will trigger (you could even delegate the actual action to take after dropping to the droppable, which sometimes turns out to be easier than the "classical" approach where the drop target is responsible)
Warning: some platforms show proper feedback while dragging a droppable over a drop target only for copy
as configured drop operation.
The type annotiations shown below are relevant for TypeScript programmers only, JavaScript programmers may simply ignore them.
use:asDroppable={options}
is the classical pattern for Svelte actions. use:asDroppable
supports the following options (some of them being equal or, at least, similar to those listed under use:asDraggable
):
Extras?:any
Extras
are an optional, user-defined value which can be used during drag-and-drop operations within the same application to identify the droppable component or its associated data. They become useful as soon as multiple droppables share the same callbacks
relativeTo?:PositionReference
relativeTo
is an optionalPositionReference
(defaulting toparent
) which specifies relative to which element the current mouse or touch position is measured during dragging. It may be set tobody
for measurements relative to the current document body, toparent
for measurements relative to the draggable's containing element, to a CSS selector for measurements relative to the Draggable's closest element matching the given selector (or 'body' otherwise) or to a given HTML or SVG element (which must be part of the document)onlyFrom?:string
onlyFrom
is an optional, comma-separated list of CSS selectors identifying the inner elements of a droppable element, from which a drag-and-drop operation must be started in order to be allowed. IfonlyFrom
is missing, noonlyFrom
restriction is appliedneverFrom?:string
neverFrom
is an optional, comma-separated list of CSS selectors identifying the inner elements of a droppable element, from which a drag-and-drop operation must never be started in order to be allowed. IfneverFrom
is missing, noneverFrom
restriction is applied
Dummy?:DragDummy
Dummy
specifies which "drag image" to show at the current mouse or touch position during dragging.Dummy
is optional (defaulting tostandard
) and may be set tonone
(effectively showing nothing), tostandard
(for the HTML5 standard behaviour which usually shows a semi-transparent copy of the actual draggable), to some HTML code (which is used to construct the element to be shown during dragging), to a function which receives the draggable's configured extras and the element to be dragged as its arguments and returns an HTML element to be used as the drag dummy or to an already existing HTML element (which must be visible). Important: creating a drag image from HTML is quite tricky - the approach used here is lightweight but may cause some flickering when dragging is started. Such flickering can usually be avoided by setting the CSS "overflow" of the document body to "hidden". Where this is not possible, constructing an image from HTML using html2canvas may be an (albeit no longer lightweight) alternative. If a function is used to construct the dummy, that function should also care itself about removing the newly created HTML element after use (see below for a suitable example)DummyOffsetX?:number
DummyOffsetX
is an optional number (defaulting to 0) specifying the horizontal offset between the current x position of a mouse or finger during dragging and the shown drag image. It is used ay the second argument toDataTransfer.setDragImage
without prior changeDummyOffsetY?:number
DummyOffsetY
is an optional number (defaulting to 0) specifying the vertical offset between the current y position of a mouse or finger during dragging and the shown drag image. It is used ay the third argument toDataTransfer.setDragImage
without prior change
minX?:number
minX
is an optional number (defaulting to -Infinity) specifying the smallest possible value for thex
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
to effectively hide the drag imageminY?:number
minY
is an optional number (defaulting to -Infinity) specifying the smallest possible value for they
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
to effectively hide the drag imagemaxX?:number
maxX
is an optional number (defaulting to Infinity) specifying the largest possible value for thex
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
tto effectively hide the drag imagemaxY?:number
maxY
is an optional number (defaulting to Infinity) specifying the largest possible value for they
component of any drag result. Please note: a configured drag image may well be dragged beyond configured limits - for that reason, such limits are most often combined withDummy:'none'
to effectively hide the drag image
Pannable?:string|HTMLElement|SVGElement
some browsers provide automatic scrolling of only partially visible elements while a Draggable is dragged over them - but most do not, which is whysvelte-drag-and-drop-actions
implements its own "panning".Pannable
is an optional parameter specifying the element which should be scrolled automatically. It should be a (not necessarily immediate) container of the Draggable and may be set to the DOM element itself or a CSS selector which identifies the desired container. If omitted, no panning will be performed. Please note:Pannable
s should be configured for Droppables only if panning should solely be performed while these Droppables are dragged over the givenPannable
(which then does not necessarily have to be a DropZone). If you want a DropZone to be automatically scrolled when an arbitrary Droppable is dragged over it, configure panning for the DropZone instead. If you want both, configure both - it's safe to do soPanSensorWidth?:number
PanSensorWidth
is an optional ordinal number (defaulting to20
) which specifies the width (in pixels) of the horizontal pan sensor area: panning starts as soon as the mouse pointer (or finger) gets closer than the given number of pixels to the left or right border of the configuredPannable
. If set to0
, no horizontal panning will be performedPanSensorHeight?:number
PanSensorHeight
is an optional ordinal number (defaulting to20
) which specifies the height (in pixels) of the vertical pan sensor area: panning starts as soon as the mouse pointer (or finger) gets closer than the given number of pixels to the upper or lower border of the configuredPannable
. If set to0
, no vertical panning will be performedPanSpeed?:number
PanSpeed
is an optional ordinal number (defaulting to10
) which specifies the "speed" of automatic scrolling - values in the range of 10...20 are reasonable choices, but it is always a good idea to make this parameter configurable for the users of your application. If set to0
, no panning will be performed
Operations?:string
Operations
is either a blank-separated list ofDropOperation
s ('copy'
,'move'
or'link'
), the keywordall
(which includes all three available operations) or the keywordnone
(which effectively suppresses dropping) and specifies which kind of data transfer the droppable component is going to supportDataToOffer?:DataOfferSet
DataToOffer
is a plain JavaScript object whose keys represent the various data formats a droppable component supports and whose corresponding values contain the transferrable data in that format. Often, the given keys denote MIME formats (which simplifies data transfer between different applications) or contain the special value "DownloadURL", but - in principle - any string (exceptnone
) may be used
onDragStart?:Position | ((DroppableExtras:any) => Position)
onDragStart
is either an optionalPosition
(defaulting to{x:0, y:0}
) or a function returning such aPosition
. When invoked, that function receives anyExtras
configured for the affected droppable. In either case, this parameter specifies the coordinate values from which to start dragging. Important: these "coordinates" do not necessarily represent a particular point on the screen - in fact, e.g., a resize handle may choose to start with the current width and height of the element to be resized and then directly use the results of anyonDragMove
oronDragEnd
as the new element width and height without additional computationonDragMove?: (x:number,y:number, dx:number,dy:number, DroppableExtras:any) => void
onDragMove
is an optional callback which (if given) is called repeatedly during dragging. When invoked, it receives the current drag result (inx
andy
, with an initial value chosen byonDragStart
), the offset between this and the previous invocation (indx
anddy
) and anyExtras
configured for the affected droppableonDragEnd?: (x:number,y:number, dx:number,dy:number, DroppableExtras:any) => void
onDragEnd
is an optional callback which (if given) is called once when dragging has finished. When invoked, it receives the final drag result (inx
andy
, with the origin chosen byonDragStart
), the offset between this and the most recent invocation ofonDragMove
(indx
anddy
) and anyExtras
configured for the affected droppable
onDropZoneEnter?: (x:number,y:number, DropZoneExtras:any, DroppableExtras:any) => void
onDropZoneEnter
is an optional callback which (when invoked) indicates that the droppable has entered the bounds of a drop target which accepts (some of) the given data it offers.x
andy
contain the current coordinates of the mouse or finger relative to the drop zone,DropZoneExtras
are anyExtras
configured for the entered drop zone andDroppableExtras
anyExtras
configured for the affected droppable (which becomes useful as soon as multiple droppables share the same callbacks)onDropZoneHover?: (x:number,y:number, DropZoneExtras:any, DroppableExtras:any) => void
onDropZoneHover
is an optional callback which (when invoked) indicates that the droppable is still moving within the bounds of a drop zone which accepts (some of) the given data it offers.x
andy
contain the current coordinates of the mouse or finger relative to the drop zone,DropZoneExtras
are anyExtras
configured for the hovered drop zone andDroppableExtras
anyExtras
configured for the affected droppable (which becomes useful as soon as multiple droppables share the same callbacks)onDropZoneLeave?: (DropZoneExtras:any, DroppableExtras:any) => void
onDropZoneLeave
is an optional callback which (when invoked) indicates that the droppable has either just left the bounds of a previously entered drop zone or that drop zone has decided to no longer accept any data offered by the droppable.DropZoneExtras
are anyExtras
configured for the left drop zone andDroppableExtras
anyExtras
configured for the affected droppable (which becomes useful as soon as multiple droppables share the same callbacks)onDropped?: (x:number,y:number, Operation:DropOperation, TypeTransferred:string, DataTransferred:any, DropZoneExtras:any, DroppableExtras:any) => void}
onDropped
is an optional callback which (when invoked) indicates that the droppable has just been dropped onto a drop zone - and it could now be up to the droppable to react accordingly (including the possibility to perform the requested operation itself, instead of the drop zone).x
andy
contain the coordinates of the mouse or finger relative to the drop zone when the droppable was dropped,Operation
contains the performed drop operation (if known - orundefined
otherwise),TypeTransferred
indicates the type of the actually transferred data (if known - orundefined
otherwise),DataTransferred
is the actually transferred data itself (if known - orundefined
otherwise),DropZoneExtras
are anyExtras
configured for the drop zone andDroppableExtras
anyExtras
configured for the affected droppable (which becomes useful as soon as multiple droppables share the same callbacks)
While being dragged, the CSS class dragged
is assigned to the draggable element (not the drag image!). It will be removed again as soon as dragging has ended.
While over a drop zone which accepts (some of) the data offered, the CSS class droppable
is assigned to the draggable element (not the drag image!). It will be removed again as soon as dragging has ended or the droppable has been left.
use:asDraggable
should never be combined with use:asDroppable
(as the latter already includes the former) - if you want an element to be dropped somewhere, simply use use:asDroppable
alone.
use:asDroppable
may, however, be combined with use:asDropZone
in order to implement draggable drop zones which may itself be dropped over other drop zones.
use:asDropZone
use:asDropZone
marks an element as a "drop zone" which allows "droppable" elements to be dropped onto it in order to trigger an operation.
The type annotiations shown below are relevant for TypeScript programmers only, JavaScript programmers may simply ignore them.
use:asDropZone={options}
is the classical pattern for Svelte actions. use:asDropZone
supports the following options (bundled into an instance of type DropZoneOptions
):
Extras?:any
Extras
are an optional, user-defined value which can be used during drag-and-drop operations within the same application to identify the drop zone component. They become useful as soon as multiple drop zones share the same callbacks
TypesToAccept?:TypeAcceptanceSet
TypesToAccept
is a plain JavaScript object whose keys represent the various data formats a drop zone accepts and whose corresponding values contain a blank-separated, perhaps empty, list of supported drop operations for that format. Often, the given keys denote MIME formats (which simplifies data transfer between different applications) or contain the special value "DownloadURL", but - in principle - any string (exceptnone
) may be used. Note: since native HTML5 drag-and-drop implementations often fail reporting a correct "dropEffect", the given drop operations can not be properly checked - with the exception, that types with empty operation lists will never be acceptedHoldDelay?:number
when a droppable has entered a drop zone and remains there for at leastHoldDelay
milliseconds without much movement, theonDroppableHold
callback of that drop zone is invoked (if it exists). Such a callback may be used to perform activities such as expanding a collapsed tree list entry, opening an input dialog or similar. The property is optional: when missing,onDroppableHold
will never be calledPannable?:string|HTMLElement|SVGElement
some browsers provide automatic scrolling of only partially visible elements while a Draggable is dragged over them - but most do not, which is whysvelte-drag-and-drop-actions
implements its own "panning".Pannable
is an optional parameter specifying the element which should be scrolled automatically. It may be set tothis
(if the DropZone itself should be scrolled), to the DropZone's own DOM element, the DOM element of a (not necessarily immediate) container of the DropZone, or a CSS selector which identifies such a container. If omitted, no panning will be performed. Please note:Pannable
s should be configured for DropZones only if panning should always be performed while an arbitrary Droppable is dragged over them. If you want aPannable
to be automatically scrolled only when its inner Draggables are dragged over it, configure panning for those Draggables instead (thePannable
then does not necessarily have to be a DropZone itself). If you want both, configure both - it's safe to do soPanSensorWidth?:number
PanSensorWidth
is an optional ordinal number (defaulting to20
) which specifies the width (in pixels) of the horizontal pan sensor area: panning starts as soon as the mouse pointer (or finger) gets closer than the given number of pixels to the left or right border of the configuredPannable
. If set to0
, no horizontal panning will be performedPanSensorHeight?:number
PanSensorHeight
is an optional ordinal number (defaulting to20
) which specifies the height (in pixels) of the vertical pan sensor area: panning starts as soon as the mouse pointer (or finger) gets closer than the given number of pixels to the upper or lower border of the configuredPannable
. If set to0
, no vertical panning will be performedPanSpeed?:number
PanSpeed
is an optional ordinal number (defaulting to10
) which specifies the "speed" of automatic scrolling - values in the range of 10...20 are reasonable choices, but it is always a good idea to make this parameter configurable for the users of your application. If set to0
, no panning will be performed
onDroppableEnter?: (x:number,y:number, Operation:DropOperation, offeredTypeList:string[], DroppableExtras:any, DropZoneExtras:any) => boolean|undefined
onDroppableEnter
is an optional callback which (when invoked) indicates that a droppable has entered this drop zone and that this droppable's data is at least partially acceptable.x
andy
contain the current coordinates of the mouse or finger relative to the drop zone,Operation
is the requested drop operation (if known - orundefined
otherwise),offeredTypeList
is a JavaScript array containing the offered data "types",DroppableExtras
are anyExtras
configured for the entering droppable andDropZoneExtras
anyExtras
configured for the affected drop zone (which becomes useful as soon as multiple drop zones share the same callbacks). The callback should returnfalse
if the offered data turns out not to be acceptable after all - or anything else (includingundefined
) otherwiseonDroppableMove?: (x:number,y:number, Operation:DropOperation, offeredTypeList:string[], DroppableExtras:any, DropZoneExtras:any) => boolean|undefined
onDroppableMove
is an optional callback which (when invoked) indicates that a droppable is still moving within the bounds of this drop zone and that this droppable's data is at least partially acceptable.x
andy
contain the current coordinates of the mouse or finger relative to the drop zone,Operation
is the requested drop operation (if known - orundefined
otherwise),offeredTypeList
is a JavaScript array containing the offered data "types",DroppableExtras
are anyExtras
configured for the hovering droppable andDropZoneExtras
anyExtras
configured for the affected drop zone (which becomes useful as soon as multiple drop zones share the same callbacks). The callback should returnfalse
if the offered data turns out not to be acceptable after all - or anything else (includingundefined
) otherwiseonDroppableHold?: (x:number,y:number, DroppableExtras:any, DropZoneExtras:any) => void
onDroppableHold
is an optional callback which (when invoked) indicates that a droppable whose data is at least partially acceptable, stood still for at leastHoldDelay
milliseconds within the bounds of this drop zone.x
andy
contain the current coordinates of the mouse or finger relative to the drop zone,DroppableExtras
are anyExtras
configured for the held droppable andDropZoneExtras
anyExtras
configured for the affected drop zone (which becomes useful as soon as multiple drop zones share the same callbacks). Warning: be careful with what to do within that callback - if you disturb the flow of events (e.g., by invokingwindow.alert
), the visual feedback for drag-and-drop may get mixed up!onDroppableLeave?: (DroppableExtras:any, DropZoneExtras:any) => void
onDroppableLeave
is an optional callback which (when invoked) indicates that a droppable whose data was at least partially acceptable, moved outside the bounds of this drop zone.DroppableExtras
are anyExtras
configured for the leaving droppable andDropZoneExtras
anyExtras
configured for the affected drop zone (which becomes useful as soon as multiple drop zones share the same callbacks)onDrop?: (x:number,y:number, Operation:DropOperation, DataOffered:any, DroppableExtras:any, DropZoneExtras:any) => string
onDrop
is an optional callback which (when invoked) indicates that a droppable (whose data is at least partially acceptable) was dropped within the bounds of this drop zone - and it may now be up to the drop zone to perform the requested operation.x
andy
contain the current coordinates of the mouse or finger relative to the drop zone,Operation
is the requested drop operation (if known - orundefined
otherwise),DataOffered
is a JavaScript object, whose keys represent the various data formats offered and whose corresponding values contain the offered data in that format,DroppableExtras
are anyExtras
configured for the droppable andDropZoneExtras
anyExtras
configured for the affected drop zone (which becomes useful as soon as multiple drop zones share the same callbacks). The callback should return the data format actually accepted or, at least,undefined
to report that any offered data was accepted - orfalse
if the offered data turned out not to be acceptable after all
While being hovered by a droppable whose data offered is at least partially acceptable, the CSS class hovered
is assigned to the drop zone element. It will be removed again as soon as either dragging has ended, the droppable has left the drop zone or the droppable's offered data is no longer acceptable.
use:asDropZone
may be combined with either use:asDraggable
or use:asDroppable
(not both together) in order to implement draggable drop zones which may perhaps itself be dropped over other drop zones.
Examples
Dragging only
- standard dragging - shows hardly more than HTML5 drag-and-drop, but proves that it still works
- plain dragging - drags an object itself (not a "ghost" or "dummy")
- dragging with CSS class "dragged" - demonstrates usage of CSS class "dragged" assigned to a Draggable during dragging
- plain dragging within region - restricts dragging to a given region
- dragging a custom dummy - drags a custom dummy made from HTML
- dragging an existing dummy - drags a dummy built from an existing HTML element (which must be visible, however)
- dragging an ad-hoc dummy - demonstrates how to dynamically create a drag image for a draggable
- plain dragging from a handle - illustrates dragging from a given handle rather than the whole draggable
- holding over a drop zone - detects when a dragged element stands still for a while (i.e., is "held") over a drop zone
- plain dragging with panning - demonstrates automatic scrolling while a Draggable is dragged over a scrollable container
- plain draggable Button - drags a button which remains clickable
- plain draggable Note - drags a "note", but only from its title bar
- plain draggable resizable Note - adds a "resize" handle to the draggable note
- plain Line Node Dragging - lets drag the handles at the end of a straight line
- plain Polygon Node Dragging - lets drag the nodes of a polygon
- plain quadratic Bezier Curve Node Dragging - lets drag the control points of a quadratic bezier curve
- plain cubic Bezier Curve Node Dragging - lets drag the control points of a cubic bezier curve
- plain draggable Resize Handles - demonstrates the "classical" positioning and shaping of a rectangular element using a selection frame and handles
- plain draggable Resize Handles for Mouse, Pen and Touch Input - like above but with proper handle sizes for devices with mouse, pen or touch input
Drag-and-Drop
- standard drag-and-drop - demonstrates the sequence of callbacks for droppables and drop zones
- standard drop - logs callbacks for actual drop operations only
- plain drag-and-drop with CSS class "dragged" - demonstrates usage of CSS class "dragged" assigned to a Droppable during dragging
- type-specific drop - illustrates determination of droppability based on offered types
- type-specific drop with CSS class "hovered" - demonstrates visual feedback of drop zones while hovered by a matching droppable based on CSS class "hovered"
- operation-specific drop - would illustrate the determination of droppability based on offered operations, if browsers would properly implement HTML5 drag-and-drop (i.e., may work in some browsers and "fail" in others)
- drag-and-drop into Trashcan - drag any "Waste" into the Trashcan
Caveats
Simply extending already existing native HTML5 drag-and-drop functionality has a lot of advantages - but also some disadvantages, as there are:
- the cursor shown while dragging is under full control of the native drag-and-drop implementation and can not be influenced programmatically (with the minor exception of setting a proper drop operation for a Droppable)
- a custom drag image must either be an image object or a visible(!) element within the document from which a snapshot is taken
- the configured drag image can not be changed during dragging as it is constructed when dragging starts and never updated again
- in some browsers, as soon as the document is scrolled (even a little bit only) a custom drag dummy "slides in" to its designated position when dragging starts rather than appears there from the beginning - this seems to be a browser bug which the author cannot compensate
Build Instructions
You may easily build this package yourself.
Just install NPM according to the instructions for your platform and follow these steps:
- either clone this repository using git or download a ZIP archive with its contents to your disk and unpack it there
- open a shell and navigate to the root directory of this repository
- run
npm install
in order to install the complete build environment - execute
npm run build
to create a new build
You may also look into the author's build-configuration-study for a general description of his build environment.