Code Monkey home page Code Monkey logo

solid-dnd's Introduction

Solid DnD - A lightweight drag and drop toolkit for Solid.

  • Built for Solid: leverages fine-grained reactivity primitives for coordination.
  • Flexible: built to support a wide range of cases, from plain drag and drop to sortable lists, multiple containers and beyond.
  • Extendable: build your own sensors, collision detection algorithms and presets like sortable lists out of the primitives.
  • Zero dependencies: Just pair with Solid and good to go.
  • Performant: No component re-rendering, coupled with CSS transforms and transitions for silky smooth performance.

solid drag and drop preview

How do I get started? ๐Ÿงญ

Install it:

npm install @thisbeyond/solid-dnd

Use it:

import {
  DragDropProvider,
  DragDropSensors,
  useDragDropContext,
  createDraggable,
  createDroppable,
} from "@thisbeyond/solid-dnd";

const Draggable = (props) => {
  const draggable = createDraggable(props.id);
  return <div use:draggable>draggable</div>;
};

const Droppable = (props) => {
  const droppable = createDroppable(props.id);
  return <div use:droppable>droppable</div>;
};

const Sandbox = () => {
  const [, { onDragEnd }] = useDragDropContext();

  onDragEnd(({draggable, droppable}) => {
    if (droppable) {
      // Handle the drop. Note that solid-dnd doesn't move a draggable into a
      // droppable on drop. It leaves it up to you how you want to handle the
      // drop.
    }
  });

  return (
    <div>
      <Draggable id="draggable-1" />
      <Droppable id="droppable-1" />
    </div>
  );
};

const App = () => {
  return (
    <DragDropProvider>
      <DragDropSensors>
        <Sandbox />
      </DragDropSensors>
    </DragDropProvider>
  );
};

export default App;

See more examples at https://solid-dnd.com

What's implemented? โœ”๏ธ

  • Use createDraggable with your elements to easily integrate drag behaviour. Maintain full control over how your element looks and behaves.
  • Manage droppable areas with createDroppable. Conditionally enable and disable droppable areas based on the current context.
  • Use DragOverlay when you want to drag a representation of your element that is removed from the normal flow.
  • Support for different sensors to detect and manage dragging (pointer sensor provided by default).
  • Layout collision detection algorithms (mostIntersecting, closestCorners and closestCenter) for common usage. You can also add your own.
  • Sortable list primitives for drag and drop list reordering (currently only vertical sorting supported).
  • Use multiple (or nested) DragDropProvider for containers isolated from each other.

Who made this? โœ

Martin Pengelly-Phillips

Why did you make it?

Solid first caught my eye when I was looking for a way to improve performance of a React app I'd been working on. I was feeling frustrated by the rules of hooks and the effort / complexity of performance improvements - especially what felt like a lot of manual book-keeping across renders. In the end, I changed my app behaviour to sidestep the issues and carried on with React.

But I also found myself watching Solid's progress too, commenting a bit here and there in the community. So, when I started a new side project I decided to jump in and give Solid a go. Performance was great, but what kept me invested in Solid was the clean lines of its primitives / API and the incredibly helpful community. It felt quick to be productive, and I liked how there seemed to be a focus on real world problems and getting it done (progress over perfection or even a hacker spirit). Somehow it also felt closer to vanilla JS and that I was working more with the language than against it.

However, there are always tradeoffs. In this case it was that Solid was not particularly well known and there was not an ecosystem of libraries available to solve common problems. This was a double-edged sword. On the one hand I liked how writing solutions myself kept my app lean, and my solutions focused. On the other, I was spending time building these solutions rather than my app.

One of the more challenging ones was adding drag and drop sorting of list items to my app. I could have hacked in a third-party library, but I didn't want to give up the granular reactivity of Solid to do so. I was also interested in the challenge - how hard would it be with Solid? Inspired by dnd-kit (a modern approach to dnd in React), I built something out for my app in around ~700 lines. I shared a gif of it with the community and decided to try to extract it into a library for others. And so, solid-dnd came to be :)

solid-dnd's People

Contributors

alllex avatar areknawo avatar hnagrath09 avatar jpdutoit avatar martinpengellyphillips avatar pardeshirzadeh avatar thetarnav avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

solid-dnd's Issues

Sortable movement constraints.

Hello there! I've stumbled across this toolkit today and I really liked what I saw, however I was missing a few user experience options, that I saw in libraries like JqueryUI. It would be awesome if those could be added as part of the Rewrite I read about in another Issue, or later down the line in another update. If wanted I can help out in developing these requested features, however, due to the planned Rewrite, opening an Issue first seemed like a smarter idea to me. (I am mainly referring to Sortable lists in this issue, since that is the feature I need for my project, however some of these ideas, might very well be useful for other draggable objects)

X/Y axis constraints:

When designing a App, like a TODO app or similar, you most often only want the users to re-arrange items in one direction, so X || Y, depending on if it's a vertical or horizontal sortable. With the current system, users are able to move the Component anywhere in the whole page, along any axis, which might be a bit confusing for the user experience.
Inspiration: JqueryUI Axis

Parent constraints:

Currently a draggable container can be moved anywhere in the page without constraints. However, bigger sites with multiple Lists or overlapping UIs, might just want to limit the movement of a item to it's parent container.
Inspiration: JqueryUI Containment

Thanks for the time, the toolkit looks pretty good ๐Ÿ‘

Edge case can happen where dragging breaks until page reload (โ€” sometimes state.active.draggable becomes null and stays null?)

(Unfortunately I don't have a reproduction for this yet, because my attempt at reproduction code isn't showing the problem; it's only showing in private code that I'm not able to share. I'll try to sort that out.)

It seems like a subtle state bug can happen in drag-drop-context. Given an application similar to the Board example โ€” i.e. two types of draggables, containers and items โ€” if I drag an item, then drag a container, then drag an item again,1 sometimes solid-dnd breaks and dragging stops happening. (EDIT: Dragging stops working for items; containers can still be dragged.) It doesn't start working again until page refresh. What I've figured out so far is that somehow, state.active.draggable stops being set.

I'll keep trying to reproduce it cleanly. In the meantime, I wanted to share what I'm seeing, in case you have a sense of what might be going on.

(One thing I might try is to have state.active.draggable be its own signal in drag-drop-context; have a hunch that something weird is going on with the nested store get/sets and/or the usages of untrack.)

Footnotes

  1. It seems to involve most items, but usually not all? EDIT: It affects the Items under any Container that's been dragged. โ†ฉ

Account for page scroll

Transform should account for page scroll delta as well. Otherwise on drag scroll the draggabel will become disconnected from the pointer position.

Projects fail when used with Vite 3.1.0

Vite 3.1.0 just got released, and won't run any projects with solid-dnd included. Downgrading to 3.0.9 works fine still though.

Not sure if it's a Vite thing, or something that was in solid-dnd that Vite just let slip until now? Seems Vite-y, as it's complaining about JSX, but the file is clearly .jsx? I figured I'd report here just in case.

Failed to parse source for import analysis because the content contains invalid JS syntax. If you are using JSX, make sure to name the file with the .jsx or .tsx extension.
/Development/Web/project/node_modules/@thisbeyond/solid-dnd/dist/source/sortable-context.jsx:41:35
39 |      return <Context.Provider value={context}>{props.children}</Context.Provider>;
40 |  };
41 |  const useSortableContext = () => {
   |                                    ^
42 |      return useContext(Context) || null;
43 |  };

As far as I can tell, to replicate, just update any project using vite + solid-dnd to [email protected].

`useSortableContext` sometimes returns `null`

In create-sortable.ts you use the useSortableContext hook here and destructure the return value as an array. You typed useSortableContext so that it could return null. In theory this should never happen, if createSortable gets called inside a Component, that is a child of a SortableProvider, but in praxis i get this error

index.js:3452 Uncaught TypeError: useSortableContext is not a function or its return value is not iterable
    at createSortable (index.js:3452:29)

despite using the createSortable function inside of a Component that is a child of a SortableProvider.
I suspect that there is some weird circumstance, where the SortableContext is not yet established, but createSortable gets already called. If you wish i could share the code with you in which i'm using the SortableProvider and createSortable. As this is in an open source project, it's no problem if i show it here...

onDragMove only called once

onDragMove is only called once (when the active draggable value changes). It should be called repeatedly for each move.

Issue with Multiple Lists example.

I need to work with multiple lists in my project so I started out by copy pasting the Multiple List example from the website in my CodeSanbox to get familiar with it, the code in untouched I really changed nothing except added some inline styles for better visibility.

The issue is that the dnd is not working properly, dragging items from one to the other Column is working mostly only the first time and dragging all in to one almost never and generally it stops working properly after first drag. I'm confused and asking myself now is this really an issue with the way I try to test this maybe a style issue, or maybe something with example itself?

https://codesandbox.io/s/solid-multiple-dnd-4b7n4l?file=/src/main.tsx

Sorting across multiple lists - kanban table

From #14 there is a request to be able to sort across multiple lists (like a kanban table) easily. Whilst this is possible today, it likely requires lower level use of the library.

Work up an example and then see if any updates to the library are needed to make this more plug and play, especially when using the existing sortable layer.

Support variable sized sortables

The current (0.6.0) Sortable algorithm requires items to be the same dimensions for correct visual sorting. Otherwise, gaps and overlays occur. The previous algorithm attempted to handle this (for specific vertical orientation), but still had issues (e.g. #9) .

Try a holistic approach to this instead.

droppable data inside `onDragOver` (dragenter)?

Native HTML5 DND has dragenter, which allows us to execute logic when a dragged element enters. Getting droppable data only inside onDragEnd is not enough I think because there are legit use-cases where the data of the droppable is needed when entered with a dragged element.

One way could be give us droppable data inside onDragOver, I have even tested this if I'm able to read droppable data inside onDragOver and interestingly I can log the ID of the droppable when "pretending " to drag (press and hold) and move around but the element does not actually go with me for some reason.

What I have also tried is to add the onDragEnter event to the element directly but it seems not to be able to work in combination with this library.

If there is any way to get droppable data when entering it with and dragged element, even with workarounds I would appreciate it.

Thanks

Update component type definitions to add children prop

Getting the following TS error for all the solid-dnd components.

Type '{ children: Element[]; onDragEnd: ({ draggable, droppable, }: { draggable: Draggable; droppable: Droppable; }) => void; onDragStart: ({ draggable }: { draggable: Draggable; }) => void; collisionDetector: CollisionDetector; }' is not assignable to type 'IntrinsicAttributes & DragDropContextProps'.
  Property 'children' does not exist on type 'IntrinsicAttributes & DragDropContextProps'.

solidjs updated their type definition of Component in v1.4.0. A copy from their release notes: https://github.com/solidjs/solid/releases/tag/v1.4.0

Better Types around Components
This one took the effort of many resident TypeScript experts, but we've now landed on some better types for components. The biggest change is Component no longer has an opinion on whether it should have children or not. We've added supplementary types ParentComponent and FlowComponent to denote Components that may have children or always have children. And we've added VoidComponent for those which may never have children.

Pass more context to collision detection algorithms

Whilst it feels purer to have collision detection abstracted away into considering just layouts, it also creates some awkwardness for more advanced situations. For example, a collision algorithm might want to tie-break overlapping hits by the 'type' of droppable.

Consider just passing the list of droppables and the active draggable directly.

Support sorting in other directions than vertically

For example, horizontal lists or even grid layouts.

Likely requires introducing a configuration for sortable context for the layout strategy to use. Similar to how a collision algorithm can be specified on drag drop context.

`onDragEnd` doesn't give x or y coordinates

While using this library, I noticed that there doesn't seem to be any way to get the exact x and y coordinate of the dragged item onto the drop area. The onDragEnd function seems to run after transforms are cleared, which means they are 0 when I try to use them. The most comfortable usage would give me exact x and y coordinates relative to the drop area, though relative to the entire window would also be fine.

codesandbox link demonstrating the problem

The for loop in <SortableProvider> doesn't accept objects?

I want to send an object to a component I want to make a sortable list out of, e.g.:

<SortableProvider ids={ids()}>
  <For each={items()}>{(item) => <><h1>{item.title}</h1><p>{item.description}</p></>}</For>
</SortableProvider>

But that just gives an error: Uncaught TypeError: can't convert Proxy to string.

It's also not very clear what ids() does. How can I fix this?

Update for Solid 1.0

Wohoo, solid 1.0 is released. Update this library with any changes needed to work with it and publish a new release.

Improve typing for DragOverlay function element

DragOverlay now supports a function element child form so consumers don't have to track the active draggable just to pass it to the overlay child. However, the typings are not right and will currently give a typing error:

E.g.

<DragOverlay>
    {(draggable) => {...}}
</DragOverlay>

gives

Type '(draggable: any) => Element' is not assignable to type 'Element'.
  Type '(draggable: any) => Element' is not assignable to type 'FunctionElement'.ts(2322)

Sortable list gap is hardcoded

The gap between items in a sortable list should be computed from the item layouts rather than hardcoded. Otherwise it just breaks visually.

Add typings

Make it easier to reason about the code, logic and provide some rudimentary documentation.

Delay when starting drag causes quirky behaviour

I haven't tried the library out yet, but noticed this issue on https://solid-dnd.com, so I figured I might report them.

There's a delay before an item starts tracking under the cursor, which leads to a big jump when clicking and dragging quickly on an item. You should easily be able to reproduce this on the webpage.

It becomes more quirky when dragging quickly on the sortable list in the examples, because then the item underneath the cursor changes to the ones you have passed on the way, and so the first item that starts tracking under the cursor is not the same one that you started dragging on. Try to for example drag "Sortable 3" quickly upwards over the other sortables. It will never render with Sortable 3 under the cursor. This makes dragging feel a bit clunky.

Doesn't work on Codesandbox

The library seems to have issues working on the codesandbox platform. Just wanted to test it there and couldn't, while the same code (from your website example) works just fine locally.
Also can I use this package for multiple (shared) sortable lists, like in a kanban table?

Preventing right click activating drag.

Hey Martin,

Right clicking activates onDragStart, and the corresponding DragEventHandler doesn't seem like we can cancel the actual click event on our end. Is this something we'd intercept and preventDefault() with a custom Sensor or something?

To replicate, on the examples page, right clicking on a draggable object, then clearing the context menu ends up locking the draggable to the cursor until you either left or right click again.

Explanatory documentation

Consider adding more documentation to the solid-dnd.com site to help folks get started and use the primitives. Could be more explanation of how things are structured/work alongside examples.

And also cover concepts about how it works under the hood/common gotchas etc.

Property 'children' does not exist on type

Hey,

"solid-js": "^1.3.13",
"@thisbeyond/solid-dnd": "^0.6.1",

When rendering both a DragDropProvider and SortableProvider, I'm getting the error:

property 'children' does not exist on type 'IntrinsicAttributes & DragDropContextProps'

Copied the code straight from the docs.

Update for Solid 1.4.x

Typings and other notable changes in 1.4, so update and make this the min compat version for solid-dnd now.

Lean more into reactivity?

There are currently aspects of the library that could be more reactive / effect driven, but are not today.

It might be worth experimenting with leaning more into reactivity (e.g. for recomputing layouts, detecting collisions etc). This has the benefit that it is more a state machine that does not require explicit coordination.

Alternatively, it may overcomplicate flows particularly if we need to explicitly coodinate effects. In that case we should go the other way instead - removing reactivity where not strictly necessary (such as the onX handlers that could just be callback registers called at specific points).

Unit tests

Might make it easier to collaborate and enhance with confidence if some unit tests are added.

Collisions not detected on drag start

Starting a drag without moving the item at all means collisions are not detected. This can result in items not being considered dropped over the correct droppable even when they are.

Likely should just detect collisions on dragStart as well.

Nested drag and drop

Would be good to have an example of nested drag and drop, as well as test and improve the API if necessary for this use case.

Imagine a classic filetree for example.

Request for another example: like the Board example, but using createResource and/or real documents

The examples are really helpful, but without another example that uses some multi-field records as the data, it's hard to generalize from the current examples. Part of the reason is that the examples treat all of the data as basically IDs; rather than displaying any additional content, they are just displaying IDs. The other missing piece is that these examples are using simple data that is synchronous, whereas real use cases more likely use createResource and/or data from async functions such as fetches. As I adapt the current examples to use real data from createResource, it feels like things get complicated quickly.

Drag overlay layout not used for collision detection

Currently it is assumed that the drag overlay will have the same dimensions as the draggable it represents. But that is not always true. As a result, a droppable may not activate even if a drag overlay appears over it (because the underlying draggable layout is actually smaller).

Instead should use drag overlay layout in collision detection (when a drag overlay is being used).

Support css transition of transform to final position

Currently the way onDragEnd works results in items jumping back to their original position before animating to their new position when a css transition is used for transforms. Investigate how to improve this so that the draggable transitions instead from its current dragged position. Perhaps by not clearing the transform on drag end immediately - instead do it in a queued microtask etc.

Solid-dnd help for sortable needed (CodeSanbox) Example.

Besides being new to Solid I try also to implement dnd for my use-case and I have created an CodeSandBox which I try now for the last 2 hours to get working without success, maybe I'm just doing something wrong in the solid side coming from react.js or maybe also some issues with the way how I try to read the dnd data, I really don't know what it could be.

https://codesandbox.io/s/solid-dnd-problem-gjlp8f
(open codesanbox preview in new window, for some reason only in the real tab console works.)

Thanks

Examples / storybook

Would be good to add more examples / a storybook to illustrate usage and capabilities.

Intermittent error from removeDraggable

This is probably self-inflicted due to a usage error on my part, but I'm seeing this sometimes (and it breaks behavior):

Uncaught (in promise) TypeError: Cannot read properties of undefined (reading '_pendingCleanup')
    at updatePath (dev.js:168:12)
    at updatePath (dev.js:165:7)
    at updatePath (dev.js:165:7)
    at dev.js:193:17
    at batch (dev.js:415:14)
    at setStore (dev.js:193:5)
    at removeDraggable (drag-drop-context.jsx:91:9)
    at Array.<anonymous> (create-draggable.js?t=1652401249008:19:21)
    at cleanNode (dev.js:936:62)
    at cleanNode (dev.js:932:45)

Is it possible that no cleanup is required in that case? (Could it soft-fail?)

Undefined eventMap[key] when using dragActivators returned from createDraggable

When attempting to drag a draggable that has been composed manually using the `draggable.dragActivators' property, an error will occur:

Uncaught TypeError: eventMap[key] is undefined
    key drag-drop-context.jsx:92

This is due to the asHandlers logic renaming a key naively causing a lookup error:

for (let key in eventMap) {
  if (asHandlers) {
    key = `on${key}`;  // <- renaming here
  }
  listeners[key] = (event) => {
    for (const { activator } of eventMap[key]) {  // <- breaks lookup here
      if (anySensorActive()) {
        break;
      }
      activator({ event, draggableId });
    }
  };
}

This doesn't affect usage as a directive, because that does not use the asHandlers form.

Consider using components instead of directives

Let me first say a big "Thank you!" for the work you've done on this project. It's great to see the Solid community and ecosystem grow!

I understand you're using directives as a way to inject dnd functionality to the elements while leaving the user full control over them. The idea is good, however, I'd argue it's not the way to go.

I consider directives the most "whacky" feature of Solid. As to why:

  • they're unique among JSX-based frameworks and thus harder to understand for newcomers;
  • they're tough to strongly type in TypeScript;
  • they don't work with Solid custom components

That's my personal view, and I've stumbled upon the above issues while using this library.
I'd recommend exploring ways to provide an alternative or complete replacement for directives. Potential solutions include wrapper components, <Dynamic/> component as well as e.g. ref-passable function.

I'm interested to hear your thoughts on this.
I'm already working on strong TS typings for this library and could explore implementing such an alternative.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.