svelte-sortable-flat-list-view
a sortable view for flat lists which also supports dragging items into and out of a list
It is based on svelte-drag-and-drop-actions and, consequently, on HTML5 native Drag-and-Drop.
some of its Features:
All examples are live and may be changed on the fly!
- works on mobile devices (when combined with svelte-drag-drop-touch)
- provides a configurable placeholder for empty lists (see example)
- can render list elements itself or use a given template (see example)
- can be styled using a given set of selectors (see example)
- supports single and multiple selection (with a configurable limit of selectable elements, see example)
- supports sorting elements from a given handle only (rather than from the whole element itself, see example)
- recognizes when a draggable stands still (i.e., is "held") over a sortable list view for a given time (see example)
- supports horizontal lists (see example) and - up to a certain extent - even two-dimensonal ones (see example)
- does the sorting itself or gives you full control over it (see example)
- supports dragging of external elements into a list (see example)
- supports dragging of list elements onto external drop zones (see example)
- supports dragging of list elements between lists (see example)
- provides lots of live(!) examples for many use cases
- however, unfortunately,
svelte-sortable-flat-list-view
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
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)
Please note: this package is currently under active development. Be invited to follow me on this road (with many detours searching for a proper build environment, preparing npm packages for Svelte or struggling with singletons that turn out not to be so "single" in reality), but don't expect this package to be stable until perhaps end of July 2021 (sorry for frequent updates until then - sometimes you may see even multiple npm publications per day...).
Installation
npm install svelte-sortable-flat-list-view
Usage
svelte-sortable-flat-list-view
should be imported in a module context (perhaps together with svelte-drag-drop-touch
) and may then be used in your markup:
<script context="module">
import DragDropTouch from 'svelte-drag-drop-touch'
import ListView from 'svelte-sortable-flat-list-view'
</script>
<script>
let List = []
</script>
<ListView {List}/>
More detailled examples for a variety of use cases can be found below.
In addition, this repo also contains a file example_ListView_on_web_page.html
which demonstrates how to use a ListView on a web page (i.e., outside of Svelte)
API
svelte-sortable-flat-list-view
emits some Svelte events and exports some types (for TypeScript users) and properties (for anybody), as shown below.
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 ListDroppableExtras = { List:any[], Item:any, ItemList:any[] }
ListDroppableExtras
defines the shape ofDroppableExtras
(as defined by svelte-drag-and-drop-actions) when aDroppable
which is dragged over a list item that may serve as aDropZone
comes from this (or another)svelte-sortable-flat-list-view
instance -
type ListDropZoneExtras = { List:any[], Item:any }
ListDropZoneExtras
defines the shape ofDropZoneExtras
(as defined by svelte-drag-and-drop-actions) when aDroppable
is dragged over this drop zone
exported Svelte Props
svelte-sortable-flat-list-view
exports the following Svelte "props" (shown with TypeScript type annotations - JavaScript users may simply ignore them):
-
class?:string
list views come with a default styling. However, if you set the (optional)class
attribute (to a CSS class name), the list view assumes that you will take over any styling and remove its defaults (see below for more details) -
style?:string
use the (optional)style
attribute to set important CSS properties for the list view itself (not its item views). "Important" CSS properties could, f.e., control position and size of a list view and set basic visual parameters such as background, borders or text size and color
-
List:{}[]
the mandatoryList
attribute accepts the actual list to be shown. It should be a JavaScript array with arbitrary objects as elements. Note: lists of JavaScript primitives will be rejected! -
Key?:string|Function
the optionalKey
attribute specifies which string to be used as the (unique) "key" of a list item - such "keys" are required by Svelte for proper rendering of lists.Key
may either be set to the (fixed) name of a list item property containing the key or to a function which receives a list item and that item's current index in the list and returns that item's key (which must be a string). If omitted, the list item itself is used as its key (after conversion into a string). List item keys must be unique within the whole list - or Svelte will throw an error -
SelectionLimit?:number
by default, any number of list items may be selected simultaneously. By setting the (optional)SelectionLimit
attribute to an ordinal number, you may set an upper limit for selections -
SelectionList:{}[]
the optionalSelectionList
attribute specifies which elements ofList
are to be selected in the list view. It must contain elements ofList
only and should not contain more thanSelectionLimit
elements (otherwise only the firstSelectionLimit
elements will be considered and all others removed). IfSelectionList
is bound to a variable, that variable also reflects any selection changes made within the list view -
AttachmentRegion?:string
in order to allow for appending list elements while dragging, a specific "attachment region" is rendered at the end of any list. Set the (optional)AttachmentRegion
attribute to the HTML you want to be shown in that region - by default, attachment regions are just empty -
Placeholder?:string
empty lists show a "placeholder" instead of just an empty space. Set the (optional)Placeholder
attribute to the HTML you want to be shown in that placeholder - by default, the text "(empty list)" is shown -
withTransitions:boolean
by default, the appearance and disappearance of individual list items is shown using a short animation. If you prefer immediate display updates, just set the (optional)withTransitions
atttribute tofalse
-
sortable?:boolean
set the (optional)sortable
attribute totrue
if you want the list view to support sorting - orfalse
otherwise. By default, sorting is not supported (such list views just support selections)
-
onlyFrom?:string
onlyFrom
is an optional, comma-separated list of CSS selectors identifying the inner elements of a list item view, from which a drag operation must be started in order to be allowed. IfonlyFrom
is missing, noonlyFrom
restriction is applied -
neverFrom?:string
neverFrom
is an optional, comma-separated list of CSS selectors identifying the inner elements of a list item view, from which a drag operation must never be started in order to be allowed. IfneverFrom
is missing, noneverFrom
restriction is applied -
onSortRequest?:(x:number,y:number, DroppableExtras:ListDroppableExtras, DropZoneExtras:ListDropZoneExtras) => boolean
onSortRequest
is an optional callback which (when invoked during sorting) indicates whether the currently dragged list items may be inserted immediately before the currently hovered list element or not.x
andy
contain the current mouse pointer (or finger) position within the hovered list item,DroppableExtras
are the configured "extras" for the currently dragged list item andDropZoneExtras
those for the currently hovered one. IfonSortRequest
is missing, insertion is allowed everywhere in the list -
onSort?:(beforeItem:any|undefined, ItemList:{}[]) => void
onSort
is an optional callback which (when invoked during sorting) performs the actual reordering of list items - it can be used to update the state of the surrounding Svelte application if the default behaviour (which simply updates the given list and emits an event) does not suffice. The callback receives the sequenceItemList
of list items to be moved (given in their original order) and the list itembeforeItem
before which the dragged list items should be inserted - ifbeforeItem
isundefined
, the dragged items should just be appended to the list
-
Operations?:string
Operations
is either a blank-separated list of drop operations ('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 list items support when dropped outside of their list - by default, no such drop is allowed -
DataToOffer?:DataOfferSet
DataToOffer
is a plain JavaScript object whose keys represent the various data formats a droppable list item 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 -
TypesToAccept?:TypeAcceptanceSet
TypesToAccept
is a plain JavaScript object whose keys represent the various data formats a list item serving as drop zone may accept 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 accepted -
onDroppedOutside?:(x:number,y:number, Operation:DropOperation, TypeTransferred:string|undefined, DataTransferred:any|undefined, DropZoneExtras:any, DroppableExtras:ListDroppableExtras) => void
onDroppedOutside
is an optional callback which is invoked when one or multiple items of this list were dropped somewhere outside this list. It may be used for any housekeeping required - or even for performing the actual data transfer in case that the foreign drop zone is not able to do so.x
andy
contain the mouse pointer (or finger) position within the foreign drop zone when the list item(s) were dropped,Operation
contains the accepted data transfer operation (i.e.,'copy'
,'move'
or'link'
),TypeTransferred
the accepted type andDataTransferred
the actually accepted data,DropZoneExtras
are the configured "extras" for the foreign drop zone andDroppableExtras
those for the actually dragged list item.TypeTransferred
andDataTransferred
may both beundefined
in order to indicate that there values are unknown. IfonDroppedOutside
is missing, it simply does not get called and the actual drop zone has to perform the data transfer itself -
onOuterDropRequest?:(x:number,y:number, Operation:DropOperation, offeredTypeList:string[], DroppableExtras:any, DropZoneExtras:ListDropZoneExtras) => boolean
onDroppedOutside
is an optional callback which is invoked when a draggable object that is not already an item of this list is dragged over an item of this list. It should returntrue
if dropping is allowed orfalse
otherwise.x
andy
contain the current mouse pointer (or finger) position within the list item that should serve as a drop zone,Operation
the requested data transfer operation (i.e.,'copy'
,'move'
or'link'
) andofferedTypeList
a list of types the dragged object offers.DroppableExtras
are the configured "extras" for the currently dragged (foreign) object andDropZoneExtras
those for the currently hovered list item. IfonOuterDropRequest
is missing the permission to drop or not to drop is determined by comparing the requested data transfer types and operations with those configured for this list -
onDropFromOutside?:(x:number,y:number, Operation:DropOperation, DataOffered:DataOfferSet, DroppableExtras:any, DropZoneExtras:ListDropZoneExtras) => string | undefined
onDropFromOutside
is an optional callback which is invoked after a draggable object that is not already an item of this list was dropped onto an item of this list. It should either return the actually accepted data type (i.e., one of the keys fromDataOffered
) ornone
if the drop is not acceptable. Ifundefined
is returned instead, the drop operation is considered accepted, but the accepted data type remains unknwon.x
andy
contain the current mouse pointer (or finger) position within the list item that served as a drop zone,Operation
the requested data transfer operation (i.e.,'copy'
,'move'
or'link'
) andDataOffered
a JavaScript object, whose keys represent the various data formats offered and whose corresponding values contain the offered data in that format.DroppableExtras
are the configured "extras" for the currently dragged (foreign) object andDropZoneExtras
those for the list item acting as drop zone. IfonDropFromOutside
is missing, the list view only accepts items that match the configured data transfer types and operations for this list and look like items of anothersvelte-sortable-flat-list-view
-
HoldDelay?:number
when a droppable has entered a list item view serving as a drop zone and remains there for at leastHoldDelay
milliseconds without much movement, theonDroppableHold
callback is invoked (if it exists). The property is optional: when missing,onDroppableHold
will never be called -
onDroppableHold?: (x:number,y:number, DroppableExtras:any, DropZoneExtras:ListDropZoneExtras) => 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 a list item view.x
andy
contain the current coordinates of the mouse or finger relative to the list item view,DroppableExtras
are anyExtras
configured for the held droppable andDropZoneExtras
anyExtras
configured for the list item view that acts as a drop zone. 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 the list view may get mixed up!
-
PanSensorWidth?:number
svelte-sortable-flat-list-view
supports automatic scrolling (aka "panning") of only partially visible lists while dragging.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 list view. If set to0
, no horizontal panning will be performed -
PanSensorHeight?:number
svelte-sortable-flat-list-view
supports automatic scrolling (aka "panning") of only partially visible lists while dragging.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 list view. If set to0
, no vertical panning will be performed -
PanSpeed?:number
svelte-sortable-flat-list-view
supports automatic scrolling (aka "panning") of only partially visible lists while dragging.PanSpeed
is an optional ordinal number (defaulting to10
) which specifies the "speed" of panning - 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
emitted Svelte Events
-
'selected-item'
(const Item:any = Event.detail
)selected-item
is emitted whenever a list item becomes selected,Event.detail
refers to the newly selected item -
'deselected-item'
(const Item:any = Event.detail
)deselected-item
is emitted whenever a list item becomes deselected,Event.detail
refers to the no longer selected item -
'sorted-items'
(const [sortedItems:any[],InsertionIndex:number] = Event.detail
)sorted-items
is emitted after one or multiple list items have been moved to new positions within their list using the default approach implemented bysvelte-sortable-flat-list-view
itself (i.e., not by a givenonSort
callback).Event.detail
contains a list with two elements: the first one being the list of repositioned items and the second one being the new index of the first repositioned item (all items are placed one after the other) -
'inserted-items'
(const [ItemsToBeInserted:any[],InsertionIndex:number] = Event.detail
)inserted-items
is emitted after one or multiple items of another list have been moved into this one using the default approach implemented bysvelte-sortable-flat-list-view
itself (i.e., not by a givenonDropFromOutside
callback).Event.detail
contains a list with two elements: the first one being the list of inserted (i.e., new) items and the second one being the index of the first inserted item (all items are placed one after the other) -
'removed-items'
(const ItemList:any[] = Event.detail
)removed-items
is emitted after one or multiple items of this list have been dropped onto another drop zone using the default approach implemented bysvelte-sortable-flat-list-view
itself (i.e., not by a givenonDroppedOutside
callback).Event.detail
contains a list of all removed list items
CSS Classes
Without explicitly specifying a CSS class for a list view, standard styling is applied. Otherwise, the following selectors may be used to define custom list view styling (assuming that you instantiate your list view with a class attribute containing ListView
, like so: <sortableFlatListView class="ListView" .../>
):
-
ListView
use this selector to style the list view itself (i.e., not the individual items). In combination with theListView > .ListItemView
selector, this also allows for horizontal or even two-dimensional list views -
ListView > .ListItemView
use this selector to style any list item view. In combination with theListView
selector itself, this also allows for horizontal or even two-dimensional list views -
ListView > .ListItemView > *
use this selector to style the actual contents of a list item view -
ListView:not(.transitioning) > .ListItemView:hover:not(.dragged)
you may want some visual feedback whenever a mouse pointer hovers over a list item. If so, use this selector to provide it (transitioning
is a class added to all list item view elements which are going to appear or disappear - and, usually, you don't want to apply the styling of hovered elements to those) -
ListView:not(.transitioning) > .ListItemView.selected:not(.dragged)
if list items are selected, there should be some visual feedback. Use this selector to provide it (transitioning
is a class added to all list item view elements which are going to appear or disappear - and, usually, you don't want to apply the styling of selected elements to those) -
ListView > .ListItemView.dragged
if list items are dragged, there should be some visual feedback. Use this selector to provide it -
ListView > .ListItemView.hovered:not(.dragged)
if list items are dragged over other list items which may serve a drop zones, those items should visually indicate that a drop would be allowed. Use this selector to do so -
ListView > .AttachmentRegion
in order to allow for appending list elements while dragging, a specific "attachment region" is rendered at the end of any list. Use this selector to style it -
ListView > .AttachmentRegion.hovered
normally, the "attachment region" does not stand out. Use this selector to change that while a list item is dragged over it -
ListView > .Placeholder
empty lists show a "placeholder" instead of just an empty space. Use this selector to style it
Important: whenever you change the style of a list item during dragging, you should take great care that HTML5 drag-and-drop still recognizes the styled list item as a draggable or drop zone. More precisely: you should avoid to move drop zones away from the mouse pointer (or finger, resp.), hide draggables or drop zones completely (e.g., with display:none
) or change their sensitivity to mouse and touch events (with pointer-events:none
)
There is an example (ListView with custom CSS classes) which specifically demonstrates how to style a list view using the abovementioned selectors.
Examples
A few examples may help understanding how svelte-sortable-flat-list-view
may be used.
Visual Appearance
- empty List - empty lists display a placeholder rather than just an empty area
- non-empty List - in the simplest case, a list view shows list item "keys", line by line
- ListView with given Template - if given, a "template" is used to render individual list items
- ListView with custom CSS classes - sometimes, it is sufficient to provide custom CSS classes for rendering
Selection
- single selection - this example shows a ListView which supports the selection of single items only
- multiple selection - multiple selections (with optionally predefined limits) are supported as well
Sorting
- sorting with single selection - in the simplest case, individual list items may be rearranged with mouse or finger
- sorting with multiple selections - rearranging multiple list items at once is supported as well
- sorting with handles - on mobile platforms, it is preferred to drag list items from handles only
- holding a dragged list item - demonstrates what happens if a Droppable is held over a list item for a long time
- sorting a horizontal list - with proper CSS Styling, a sortable list view may also arrange its items horizontally
- sorting a two-dimensional list - with proper CSS Styling, even 2-dimensional lists may become sortable
- sorting with callbacks - optional callbacks give full control over sorting "semantics"
Dragging beyond List Bounds
- dragging items from a source into a list - add new list items by dragging them onto the list view
- dragging list items into a trashcan - delete list items by dragging them into a trashcan
- dragging items between lists - of course, you may also drag items from one list into another
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
See the author's build-configuration-study for a general description of his build environment.