A directive based drag and drop container for solid-js

Overview

SOLID DND DIRECTIVE

This is a feature-complete implementation of drag and drop for Solid JS using a custom directive.
It supports almost every imaginable drag and drop use-case, any input device and is fully accessible.
It requires very minimal configuration, while offering a rich set of primitives that allow overriding basically any of its default behaviours (using the handler functions).

The reason it is so feature rich, robust and production ready is that under the hood it utilises the most popular drag and drop library for svelte (which actually has no dependency on svelte whatsoever): svelte-dnd-action solid-dnd-directive-gif
Play with this example here

Current Status

While the core of this library is used in commercial svelte apps in production and has a very stable API, the thin adapter that makes it Solid friendly is very new and still experimental.
It introduces its own, very minimal, implementation of flip animations, which still needs to be tested under fire.
I will add examples and improvements to this repo. The greater the interest from the Solid community - the faster I will do it 😄

Features

  • Awesome drag and drop with minimal fuss
  • Supports horizontal, vertical or any other type of container (it doesn't care much about the shape)
  • Supports nested dnd-zones (draggable containers with other draggable elements inside, think Trello)
  • Rich animations (can be opted out of)
  • Touch support
  • Define what can be dropped where (dnd-zones optionally have a "type")
  • Scroll dnd-zones and/or the window horizontally or vertically by placing the dragged element next to the edge
  • Supports advanced use-cases such as various flavours of copy-on-drag and custom drag handles (see examples below)
  • Performant and small footprint
  • Fully accessible (beta) - keyboard support, aria attributes and assistive instructions for screen readers

Installation

Pre-requisites: solid-js ^1.0.0

yarn add solid-dnd-directive

or

npm install solid-dnd-directive

Usage

{item =>
{item.title}
}
); } export default App; ">
import { createSignal } from "solid-js";
import {dndzone} from "solid-dnd-directive";
const containerStyle = {border: "1px solid black", padding: "0.3em", "max-width": "200px"};
const itemStyle = {border: "1px solid blue", padding: "0.3em", margin: "0.2em 0"};

function App() {
  const [items, setItems] = createSignal([
    {id: 1, title: "item 1"},
    {id: 2, title: "item 2"},
    {id: 3, title: "item 3"}
  ]);

  function handleDndEvent(e) {
     const {items: newItems} = e.detail;
     setItems(newItems);
  }
  return (
    <main>
         <section style={containerStyle} use:dndzone={{items}} on:consider={handleDndEvent} on:finalize={handleDndEvent}>
            <For each={items()}>{item => <div style={itemStyle}>{item.title}</div>}</For>
        </section>
    </main>
  );
}

export default App;

Play with this example here

Input:

Except for items, all the other options can be either a value (won't be reactive), or a function that returns a value (Signal, or function to a Store, will update reactively) An options-object with the following attributes:

Name Type Required? Default Value Description
items () => Array a Signal, or a function that returns a Store Yes. Each object in the array has to have an id property (key name can be overridden globally) with a unique value (within all dnd-zones of the same type) N/A The data array that is used to produce the list with the draggable items (the same thing you run your For block on). The dndzone should not have children that don't originate in items
flipDurationMs Number No 150 The duration of the items animations. Set to zero if you don't want animations
type String No Internal dnd-zones that share the same type can have elements from one dragged into another. By default, all dnd-zones have the same type
dragDisabled Boolean No false Setting it to true will make it impossible to drag elements out of the dnd-zone. You can change it at any time (if you passed in a Signal), and the zone will adjust on the fly
morphDisabled Boolean No false By default, when dragging over a zone, the dragged element is morphed to look like it would if dropped. You can prevent it by setting this option.
dropFromOthersDisabled Boolean No false Setting it to true will make it impossible to drop elements from other dnd-zones of the same type. Can be useful if you want to limit the max number of items for example. You can change it at any time, and the zone will adjust on the fly
zoneTabIndex Number No 0 Allow user to set custom tabindex to the list container when not dragging. Can be useful if you want to make the screen reader to skip the list container. You can change it at any time.
dropTargetStyle Object No {outline: 'rgba(255, 255, 102, 0.7) solid 2px'} An object of styles to apply to the dnd-zone when items can be dragged into it. Note: the styles override any inline styles applied to the dnd-zone. When the styles are removed, any original inline styles will be lost
dropTargetClasses Array No [] A list of classes to apply to the dnd-zone when items can be dragged into it. Note: make sure the classes you use are available globally.
transformDraggedElement Function No () => {} A function that is invoked when the draggable element enters the dnd-zone or hover overs a new index in the current dnd-zone.
Signature:
function(element, data, index) {}
element: The dragged element.
data: The data of the item from the items array.
index: The index the dragged element will become in the new dnd-zone.

This allows you to override properties on the dragged element, such as innerHTML to change how it displays. If what you are after is altering styles, do it to the children, not to the dragged element itself
autoAriaDisabled Boolean No false Setting it to true will disable all the automatically added aria attributes and aria alerts (for example when the user starts/ stops dragging using the keyboard).
Use it only if you intend to implement your own custom instructions, roles and alerts. In such a case, you might find the exported function alertToScreenReader(string) useful.
centreDraggedOnCursor Boolean No false Setting it to true will cause elements from this dnd-zone to position their center on the cursor on drag start, effectively turning the cursor to the focal point that triggers all the dnd events (ex: entering another zone). Useful for dnd-zones with large items that can be dragged over small items.
Output:

The action dispatches two custom events:

  • consider - dispatched whenever the dragged element needs to make room for itself in a new position in the items list and when it leaves. The host (your component) is expected to update the items list (you can keep a copy of the original list if you need to)
  • finalize - dispatched on the target and origin dnd-zones when the dragged element is dropped into position. This is the event you want to use to save the items to the server for example.

The expectation is the same for both event handlers - update the list of items. In both cases the payload (within e.detail) is the same: an object with two attributes: items and info.

  • items: contains the updated items list.
  • info: This one can be used to achieve very advanced custom behaviours (ex: copy on drag). In most cases, don't worry about it. It is an object with the following properties:
    • trigger: will be one of the exported list of TRIGGERS (Please import if you plan to use): [DRAG_STARTED, DRAGGED_ENTERED, DRAGGED_ENTERED_ANOTHER, DRAGGED_OVER_INDEX, DRAGGED_LEFT, DRAGGED_LEFT_ALL, DROPPED_INTO_ZONE, DROPPED_INTO_ANOTHER, DROPPED_OUTSIDE_OF_ANY, DRAG_STOPPED]. Most triggers apply to both pointer and keyboard, but some are only relevant for pointer (dragged_entered, dragged_over_index and dragged_left), and some only for keyboard (drag_stopped).
    • id: the item id of the dragged element
    • source: will be one of the exported list of SOURCES (Please import if you plan to use): [POINTER, KEYBOARD]

You have to listen for both events and update the list of items in order for this library to work correctly.

For advanced use-cases (ex: custom styling for the placeholder element, you might also need to import SHADOW_ITEM_MARKER_PROPERTY_NAME, which marks the placeholder element that is temporarily added to the list the dragged element hovers over. For use cases that have recursively nested zones, you might want to import SHADOW_PLACEHOLDER_ITEM_ID in order to filter the placeholder out when passing the items in to the nested component. If you need to manipulate the dragged element either dynamically (and don't want to use the transformDraggedElement option), or statically targeting it or its children with CSS, you can import and use DRAGGED_ELEMENT_ID;

Accessibility (beta)

If you want screen-readers to tell the user which item is being dragged and which container it interacts with, please add aria-label on the container and on every draggable item. The library will take care of the rest. For example:

{item =>
{item.name}
}
">
<h2>{listName}</h2>
<section aria-label="{listName}" use:dndzone="{{items}}" on:consider="{handleDndConsider}" on:finalize="{handleDndFinalize}">
    <For each={items()}> 
        {item => <div aria-label="{item.name}">{item.name}</div>}
    </For>
</section>

If you don't provide the aria-labels everything will still work, but the messages to the user will be less informative. Note: in general you probably want to use semantic-html (ex: ol and li elements rather than section and div) but the library is screen readers friendly regardless (or at least that's the goal :)). If you want to implement your own custom screen-reader alerts, roles and instructions, you can use the autoAriaDisabled options and wire everything up yourself using markup and the consider and finalize handlers (for example: unsortable list).

Keyboard support
  • Tab into a dnd container to get a description and instructions
  • Tab into an item and press the Space/Enter key to enter dragging-mode. The reader will tell the user a drag has started.
  • Use the arrow keys while in dragging-mode to change the item's position in the list (down and right are the same, up and left are the same). The reader will tell the user about position changes.
  • Tab to another dnd container while in dragging-mode in order to move the item to it (the item will be moved to it when it gets focus). The reader will tell the user that item was added to the new list.
  • Press Space/Enter key while focused on an item, or the Escape key anywhere to exit dragging mode. The reader will tell the user that they are no longer dragging.
  • Clicking on another item while in drag mode will make it the new drag target. Clicking outside of any draggable will exit dragging-mode (and tell the user)
  • Mouse drag and drop can be preformed independently of keyboard dragging (as in an item can be dragged with the mouse while in or out of keyboard initiated dragging-mode)
  • Keyboard drag uses the same consider (only on drag start) and finalize (every time the item is moved) events but share only some of the TRIGGERS. The same handlers should work fine for both.

Examples and Recipes

Rules/ assumptions to keep in mind

  • Only one element can be dragged in any given time
  • The data that represents items within dnd-zones of the same type is expected to have the same shape (as in a data object that represents an item in one container can be added to another without conversion).
  • Item ids are unique in all dnd containers of the same type. EVERY DRAGGABLE ITEM (passed in through items) MUST HAVE AN ID PROPERTY CALLED id. You can override it globally if you'd like to use a different key (see below)
  • If you need to make a copy an item, you allocate a new id for the copy upon creation.
  • The items in the list that is passed-in are render using a element, and the container has no extra (and no fewer) children.
  • Any data that should "survive" when the items are dragged around and dropped should be included in the items array that is passed in.
  • The host component must refresh the items that are passed in to the custom-action when receiving consider and finalize events (do not omit any handler).
  • FYI, the library assumes it is okay to add a temporary item to the items list in any of the dnd-zones while an element is dragged around.
  • If you want dragged items to be able to scroll the container, make sure the scroll-container (the element with overflow:scroll) is the dnd-zone (the element decorated with this custom action)

Overriding the item id key name

Sometimes it is useful to use a different key for your items instead of id, for example when working with PouchDB which expects _id. It can save some annoying conversions back and forth. In such cases you can import and call overrideItemIdKeyNameBeforeInitialisingDndZones. This function accepts one parameter of type string which is the new id key name. For example:

import {overrideItemIdKeyNameBeforeInitialisingDndZones} from "solid-dnd-directive";
overrideItemIdKeyNameBeforeInitialisingDndZones("_id");

It applies globally (as in, all of your items everywhere are expected to have a unique identifier with this name). It can only be called when there are no rendered dndzones (I recommend calling it within the top-level

You might also like...
React Drag and Drop file input
React Drag and Drop file input

React Drag and Drop file input

React drag and drop framework with inbuilt virtualizing scrollbars.

About us This library was made by Forecast - powered by AI, Forecast is supporting your work process with a complete Resource & Project Management pla

🦋 Component for building file fields - from basic file inputs to drag and drop image galleries.
🦋 Component for building file fields - from basic file inputs to drag and drop image galleries.

🦋 react-butterfiles A small component for building file upload fields of any type, for example a simple file upload button or an image gallery field

Drag and Drop library for React.

react-tiny-dnd Drag and Drop library for React. Demo Install via npm npm install react-tiny-dnd or yarn add react-tiny-dnd Features Vertical lists Eas

 Creating an app using Drag and Drop with React without libraries 🤏
Creating an app using Drag and Drop with React without libraries 🤏

Creating an app using Drag and Drop with React without libraries 👆 ! This time, we are going to implement the functionality to do a Drag & Drop with

Simple HTML5 drag-drop zone with React.js.

react-dropzone Simple React hook to create a HTML5-compliant drag'n'drop zone for files. Documentation and examples at https://react-dropzone.js.org.

example how to use react-dropzone for drag 'n drop uploader

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Taskboard with drag'n drop feature. Built w/ React, TypeScript
Taskboard with drag'n drop feature. Built w/ React, TypeScript

Drag & Drop Taskboard A taskboard application with drag and drop feature. Live demo is here. Tech Stack Language: TypeScript UI-Components: Ant Design

"Drag to resize" (sizing) as React Component.

react-drag-sizing "Drag to resize" (sizing) as React Component Rewritten with TS & React-hooks Polyfill workaround with React 16.8 Support both mous

Comments
  • Question: Nesting DND

    Question: Nesting DND

    @isaacHagoel this utility is fantastic. I've been able to use it successfully in a project that requires extremely complex DND functionality. Here's an example of my current prototype: https://radius-stage.pilotio.workers.dev/

    The next battle I'm facing is making the cards sortable within nested cards. Imagine if I could "expand" a card which reveals a drop area allowing me to move cards from anywhere into it.

    Do you have any suggestions on how to solve this? I'd appreciate the pointers!

    question 
    opened by davedbase 9
  • dndzone is not defined

    dndzone is not defined

    Hi, tried this directive on a fresh solid + vite app

    1. npx degit solidjs/templates/ts solid-dnd-test
    2. npm install && npm install solid-dnd-directive
    3. Add demo code
    import { createSignal, For } from "solid-js";
    import { dndzone } from "solid-dnd-directive";
    const containerStyle = {
      border: "1px solid black",
      padding: "0.3em",
      "max-width": "200px",
    };
    const itemStyle = {
      border: "1px solid blue",
      padding: "0.3em",
      margin: "0.2em 0",
    };
    
    function App() {
      const [items, setItems] = createSignal([
        { id: 1, title: "item 1" },
        { id: 2, title: "item 2" },
        { id: 3, title: "item 3" },
      ]);
    
      function handleDndEvent(e) {
        const { items: newItems } = e.detail;
        setItems(newItems);
      }
      return (
        <main>
          <section
            style={containerStyle}
            use:dndzone={{ items }}
            on:consider={handleDndEvent}
            on:finalize={handleDndEvent}
          >
            <For each={items()}>
              {(item) => <div style={itemStyle}>{item.title}</div>}
            </For>
          </section>
        </main>
      );
    }
    
    export default App;
    

    Error: Screen Shot 2022-02-10 at 12 07 35 PM

    Screen Shot 2022-02-10 at 12 07 54 PM
    opened by walmartwarlord 8
  • Is there a way to use a different element when dragging (rather than transforming)?

    Is there a way to use a different element when dragging (rather than transforming)?

    I'm having a little trouble using transformDraggedElement to get the effect I'm looking for. The DND elements I am using have a title and a body, and the body text can be long. When it's dragging, I'd like to show an element that has only the title, and not the body (or an abbreviated body). I've tried using transformDraggedElement and CSS but I haven't gotten the desired effect yet.

    Is it possible to use a different element when dragging? (I checked the issues on svelte-dnd-action as well and I haven't found an example of this yet.)


    Note: There's another DND library for Solid — that seems less feature-complete than svelte-dnd-action/solid-dnd-directive — but it implements a "drag overlay" feature (code, demo), so it might offer useful example code of what I mean. I would offer to adapt it myself, but since it could involve both svelte-dnd-action and solid-dnd-directive the change might not be simple, so I figured I'd ask for your thoughts first.

    opened by floer32 4
  • How to use items from props?

    How to use items from props?

    Hi, I was wondering how you would go about using items from props instead of from a signal? I know I can just createMemo, but that doesn't seem to be working as I'm getting this error: image

    Thanks for this awesome library in both svelte and solid!

    opened by Zachiah 3
Owner
Isaac Hagoel
Studio Dev Lead @Pearson (Phoenix Sparrow)
Isaac Hagoel
Beautiful and accessible drag and drop for lists with React

react-beautiful-dnd (rbd) Beautiful and accessible drag and drop for lists with React Play with this example if you want! Core characteristics Beautif

Atlassian 28.9k Dec 31, 2022
🔀 Drag and drop for your React lists and tables. Accessible. Tiny.

react-movable See all the other examples and their source code! Installation yarn add react-movable Usage import * as React from 'react'; import { Li

Vojtech Miksu 1.3k Dec 30, 2022
ReactJS drag and drop functionality for mouse and touch devices

DragDropContainer and DropTarget Live demo: peterh32.github.io/react-drag-drop-container Features Very easy to implement and understand. Works on mous

Peter Hollingsworth 142 Sep 26, 2022
React drag and drop sort support flex layout and nested.

react-flex-dnd React drag and drop sort support flex layout and nested. This package using hooks, note that your React version is above 16.8 :) Why fl

lvshihao 7 Nov 14, 2022
Drag and Drop for React

React DnD Drag and Drop for React. See the docs, tutorials and examples on the website: http://react-dnd.github.io/react-dnd/ See the changelog on the

React DnD 18.7k Jan 7, 2023
:ok_hand: Drag and drop so simple it hurts

Drag and drop so simple it hurts Official React wrapper for dragula. Demo Try out the demo! Install You can get it on npm. npm install react-dragula -

Nicolás Bevacqua 976 Nov 26, 2022
Drag and Drop for React

React DnD Drag and Drop for React. See the docs, tutorials and examples on the website: http://react-dnd.github.io/react-dnd/ See the changelog on the

React DnD 18.7k Jan 6, 2023
A modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.

A modern, lightweight, performant, accessible and extensible drag & drop toolkit for React.

Claudéric Demers 6.5k Jan 7, 2023
Drag and Drop for React

Drag and Drop for React

React DnD 18.7k Jan 4, 2023
Light React Drag & Drop files and images library styled by styled-components

Light React Drag & Drop files and images library styled by styled-components

null 143 Dec 28, 2022