Giter Club home page Giter Club logo

neodrag's Introduction

Neodrag

One draggable to rule em all

Multi-framework libraries for dragging. Choose your framework, the dragging API behavior will stay the same ๐Ÿ”ฅ

Getting Started

This is a monorepo containing the following packages:

Roadmap

Contributors โœจ

Thanks goes to these wonderful people (emoji key):

Puru Vijay
Puru Vijay

๐Ÿš‡ ๐Ÿ’ป ๐Ÿšง ๐Ÿ–‹ ๐Ÿ“– ๐Ÿ’ต ๐Ÿ”ฌ
Bjorn Lu
Bjorn Lu

๐Ÿ’ป ๐Ÿค”
Baris Gumustas
Baris Gumustas

๐Ÿ’ป
Sidharth Anand
Sidharth Anand

๐Ÿ’ป
Tropical
Tropical

๐Ÿ“–
AphLute
AphLute

๐Ÿ’ป
Tas
Tas

๐Ÿš‡ ๐Ÿ’ป โš ๏ธ

This project follows the all-contributors specification. Contributions of any kind welcome!

neodrag's People

Contributors

allcontributors[bot] avatar aphlute avatar bluwy avatar github-actions[bot] avatar houtan-rocky avatar matrushka avatar puruvj avatar sidharth-anand avatar tascodes avatar tropix126 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

neodrag's Issues

Replace Jest with uvu

For testing, this project uses Jest now. But it is slow and heavy, so it would be the best to remove jest completely and use Uvu

Angular support

Angular support is very easy to integrate using a directive:

import { Directive, ElementRef, inject, Input } from "@angular/core";
import { Draggable, DragOptions } from '@neodrag/vanilla';

@Directive({
    standalone: true,
    selector: "[draggable]"
})
export class DraggableDirective {
    #elementRef = inject(ElementRef);
    #dragInstance: Draggable | null = null;

    @Input('draggable') set dragOptions(dragOptions: DragOptions | undefined | null | "") {
        let options = dragOptions || {};
        
        if (this.#dragInstance) {
            this.#dragInstance.updateOptions(options);
        } else { // create new instance
            this.#dragInstance = new Draggable(this.#elementRef.nativeElement, options);
        }
    }
    
    ngOnDestroy() {
        this.#dragInstance?.destroy();
        this.#dragInstance = null;
    }
}

Usage:

@Component({
  selector: 'app-root',
  template: `
    <div draggable style="width: 100px">
      Drag me!
    </div>

    <!-- With options -->
    <div [draggable]="dragOptions" style="width: 100px">
      Drag me!
    </div>
  `
})
export class AppComponent {
  dragOptions: DragOptions = {
    axis: 'y',
    onDrag: (event) => {
      console.log(event);
    }
  }
}

Grid Snap Not Working?

Hey, love the clean API on this package!

Just having some trouble getting grid-snapping to work. Can you provide an example, or elucidate what might be wrong with the following code? I'm not seeing any snap behavior change with or without the grid options provided in the DragOptions object.
image

simple way to get position of element on svelte-drag:end event?

I see the svelte-drag event returning offsetX and offsetY. Would be great to have these accessible also when the element is dropped. Right now, the only way I found to have access to the element position is extracting it from the element.style.transform value, which doesn't seem optimal to me. But I'm not sure if I'm missing something.

Context: I'm trying to persist the locations of multiple draggable elements: save position to some storage when element is dropped, and be able to restore it to this position, - assuming this would easiest be done with defaultPosition.

On second thought: I'm currently not clear why the transform value is used in the first place. A regular element position x/y could be bound directly, which would simplify what I'm trying to achieve. Again, not sure if I'm possibly missing something, and if there is a simple and possibly better way to achieve the described persisting/restoring of elements.

Option to reset element position

Hey @PuruVJ , thanks for the nice lib, it works great.

I got a use case that I can't achieve currently and I would like to discuss it:

I have an element that I should be able to drag in one state and shouldn't in another, this can be achieved using the disabled option. But when we are in a non-draggable state ({ disabled: true }), I also need the element to go back to its original position and this is something not easy to achieve. If I will remove the transform property from the element's style attribute, the element will get back to the original position. But when next time I will switch to { disabled: false } and will start dragging, the element will jump back to the last position because the action has an internal state and use it to calculate new position on every move.

So basically right now the only way to reset the position is to destroy the action and create it again. I think it would be nice to have a way to do that it using action's API. E.g.:

  • Add another option (e.g. resetPositionWhenDisabled), if it's true then action will reset the position when it will get disabled.
  • Add a way to set position through options, something like this:
<script lang="ts">
  let offsetX = 0
  let offsetY = 0

  function reset() {
    offsetX = 0
    offsetY = 0
  }
</script>
<div
  use:draggable={{ offsetX, offsetY }}
  on:svelte-drag={{ detail } => { offsetX = detail.offsetX; offsetY = detail.offsetY }}
>
  Hello
</div>

What do you think?

(Two suggestions): Add Error overrides for options + reactive disabled param?

Essentially, I think making the error messages more modular (as in being able to replace/remove the default error messages when using the handle or class options) might be an interesting addition to the action. In this way, you don't have to wrap the @neoDrag action around your own custom svelte action. Currently, this is the strategy I'm using to hack my way around some error messages that I don't want to enforce.

In specific, when I right click on a card that is draggable, I remove the .handle class from the component, thus rendering it undraggable. When attempting to drag this undraggable element, the following error message gets logged:

Uncaught Error: Selector passed for `handle` option should be child of the element on which the action is applied

Normally, this error message would make perfect sense, as the child of the neodrag element does not have the .handle class anymore. In my implementation, this is actually correct. It's the only way I've found to turn draggability on/off in a reactive fashion.

I was kinda forced to do this since the disabled option didn't work with a reactive ref... Honestly though, this strategy is working great other than that error stuff. So, I guess another thing that I might suggest looking into is how to incorporate reactive refs as params for the disabled option! Thanks for such a great library!

Use CSS `translate` property instead of `transform`

Task Description

Replace, or provide the option to use, the CSS translate property in place of the currently used transform property.

CSS translate property

The translate CSS property allows you to specify translation transforms individually and independently of the transform property. This maps better to typical user interface usage, and saves having to remember the exact order of transform functions to specify in the transform value.

MDN

Lit

Lit element integration

<neo-draggable options={{}}>
  Drag me
</neo-draggable>

Request to Add to NeoDrag

I know it wouldn't match the name, but I'd love to see resizing come to this library if possible, along with dragging. It would make it perfect for my use case.

Also, I'm using Svelte, and want people to be able to drag (and hopefully resize) UI components. When the page reloads or they revisit, I want the last position they put a div in to be in localstorage. So that they can customize where the UI components are. Is there any way to do this in Neodrag?

Custom transform function

Allow user to bring in their custom transform function.

API surface:

{
  transform: ({ offsetX, offsetY, rootNode }) => void
}

You can do whatever you want with the node inside this function. Note this doesn't return a string. If you want to set a transform, you to set it on the node directly, like rootNode.style.transform = translate(${offsetX}px, ${offsetY}px).

Will allow changing top and bottom too. Not encouraged, but if you need it, you can have it

Minor fix - readme.md handle example

handle example in the readme.md has the wrong options object. looks like copy/paste snafu.

thanks for the awesome work!

`<div use:draggable={{ cancel: '.cancel' }}>
You shall not drag!!๐Ÿง™โ€โ™‚๏ธ

This will drag ๐Ÿ˜
`

Should be:

`<div use:draggable={{ handle: '.handle' }}>
You shall not drag!!๐Ÿง™โ€โ™‚๏ธ

This will drag ๐Ÿ˜
`

Right click fires dragging

By making a right-click (opening the contextmenu) on a draggable element, it triggers dragging. I think this is not supposed to happen. Tested in the Svelte version.

I propose removing the bug or if this is actually intentional, add an option to disable dragging on right-click ๐Ÿ˜„

Neodrag not working when using environment variables?

I just recently learned about svelte and am currently learning it. I saw a svelte summit where you held a talk and got to know your projects, that MacOS Web thing is really cool btw :)

I wanted to try neodrag, so I added it to the Svelte Realworld project that I'm using to play around with.

It worked like a charm... until I used a environment variable in my code. Neodrag seems not to generate the whole code (including classes like neodrag on the element itself) when accessing process.env.

I created a stackblitz for you, so you can easily see what I mean. What's interessting, it's not only that when I use an environment variable "inside" an element that should be draggable, but also when I just access the process.env property within a script tag.

Stackblitz Container.

Maybe you can help me to figure out what the issue is.

Borders are not considered in bounds calculation

Hey @PuruVJ, very cool project, saved me from reimplementing drag on my own.

One thing I noticed, when I add a draggable div with {bounds: 'parent'} as a child of another div,
the draggable is able to move outside the parent's bounds if it has a border of any size greater than 0.

I tried working around it with DragBoundsCoords, but my border thickness is stored as a global CSS variable and extracting it in Svelte proved unsuccessful. I'm using the Svelte bindings if that makes a difference.

Please let me know if there's anything I can do to fix this myself!
The picture below shows the blue border of the white panel moving past its parent's bounds (background image)
image

Perhaps it can be fixed with a flag in DragOptions whether to include border or not?

Setting position on OnDragEnd has undefined behavior.

here's an example svelte component:

<script>
    import { draggable } from '@neodrag/svelte'

    let x = 100
    let y = 100

    function onDragEnd(e){
        x = 300
        y = 300
    }
</script>
<div
     use:draggable={{
         position: { x, y },
         onDragEnd,
     }}
>

Say after I drop it, i want the component to move to (300,300).
On first drag and drop, it works as expected. However, on subsequent ones, the component doesnt snap back to (300,300), it instead stays where it was dropped.

Furthermore, if this was:

    function onDragEnd(e){
        x = 0
        y = 0
    }

Then it doesnt even move the first time.

On bounds update call computeBounds function?

Good day! Firstly, thanks for great lib.

Iam updating bounds in ondrag stage, but because you are calling calculation of computedBounds in ondragstart stage, updated bounds not working. Maybe we should recalculate computedBounds in update function?

Best pratice to get dragged element ?

Hey there!
Currently trying out neodrag to replace sortablejs, as i find it far more enjoyable and more responsive! :)
Atm i am not 100% sure, whats the best way to get the currently dragged element (as a DomElement) or the dropzone..

Maybe it might be worth to add this directly to the eventData ?

Check mouse state

So, I put this into vedges (an SVGEdit -- svelte version)

It's a nice idea. But, I ran into problems in two different contexts.

  1. I can drag rulers. Cool! But, you can always expect the mouse to get ahead of the element being dragged. So, if you do mouseUp when your dragging element goes past the boundary (which it does at time) the element will still be dragging. So, you need to check that the mouse is actually in the desired state for dragging.

  2. I tried this for drag handles on an edit region and gave up. The thing went crazy. Not too long all the handles were all over the place being dragged while the mouse was up. So, I had to put my own handle dragging code into can_edit (a submodule -- may make into an npm component at some point.

I am not sure of what to expect from your code passed in options. So, I am not putting time into figuring it out at the moment.

But, the main issue is that the mouse state needs to be checked. There should be some double checking on bounds as well.

Otherwise, I like the idea of this component (action). It would be good to make it robust.

Making things draggable blocks touch scrolling

First of all: this is a brilliant library- I've been looking for a drag and drop lib like this for a while.

But: I've got a long scrollable list of scrollable images that I want to drag and drop onto a page. When on a desktop: mousewheel, arrows, page up/down works fine. On a touchscreen, however the expected swipe to scroll doesn't happen- probably because of this:

// On mobile, touch can become extremely janky without it node.style.touchAction = 'none';

Is there a way around this? Is there a way to add a delay to the action so that a swipe won't trigger the dragging, but holding for a half second will?

Cheers!

Add tests

Hi!!

No library/framework is complete without its tests. Would appreciate if you could help me out in writing tests for this library!

Specifications:

  • Tests svelte code using this action, not the action directly in the form of a function

That's it!!

if you wish to add tests, pls comment here, before making a PR ๐Ÿ™‚

Drag actions do not reset.

I'm trying to create a draggable button that returns to original position on "dragend".
The first drag action in either direction works as expected.
However, subsequent drag actions result in the following unexpected behaviors:

I've created this REPL to illustrate the problem:
https://svelte.dev/repl/b981d02745a14df0bd95ba7b41746803?version=3.44.1

Draggable distance increases with each drag...
Draggable escapes "bounds" element.
DevTools Error (starts to happen after two swipe gestures): [Intervention] Unable to preventDefault inside passive event listener due to target being treated as passive.

Please help explain if/how my implementation is at fault.
Thank you in advance for your help and consideration.

Allow controlled behavior

react-draggable library allows you to dynamically change the current x and y, getting rid of user controls, only controllable by the user.

Implement this

Would love your comments on this if you got any

dragged element moves faster than mouse

I have this component installed and it is working well, except when I drag the circle it's bound to, the circle moves farther than the mouse does.

When I inspect the moved circle, I see a "transform" style rule; when I remove that rule, the circle moves to it's expected position.

When I remove the translation in the dragEnd event, the circle snaps to where I expect it to be.

The simplest solution I can see to allow a fix for this (without breaking existing functionality) is to add a setting to the options object which will disable the transform behavior.

Draggables should use position as their defaultPosition

Hi, first of all, love this library.

In this example

Setting the value of x and y

<script>
	import {draggable} from 'svelte-drag'
	
	let x = 100;
	let y = 0;
</script>

Should intuitively set the box at x=100 on first render. It does not. I see that defaultPosition achieves this, but I think intuitively, the x and y I pass in should be the initial position, as these can be props exposed to its parent.

adjusting on window resize

Draggables don't seem to react to window resize events.

They can become lost outside of the boundary because of this.

Not sure if this is because of something I've done on my end though.

Can't drag elements inside shadow DOM

So far, the only code I found preventing using this library in shadow DOM is this condition:

if (
dragEls.some((el) => el.contains(<HTMLElement>e.target)) &&
!cancelElementContains(cancelEls, [<HTMLElement>e.target])
) {

The problem is with e.target which has some parent custom element instead of the one I'm actually trying to drag.

I'm now testing if this simple patch is sufficient:

// use composedPath instead of e.target
const eventTarget = e.composedPath()[0];
if (dragEls.some((el) => el.contains(eventTarget)) && !cancelElementContains(cancelEls, [eventTarget])) {
  currentlyDraggedEl = dragEls.length === 1 ? node : dragEls.find((el) => el.contains(eventTarget));
  active = true;
}

I can send a PR if you want me to, but I'm unsure if using composedPath is a good solution for you or if you have maybe a better idea. Fixing this makes it possible to use this library with Lit before there is a specific Lit implementation (which is my use case).

Add docs for all options

  • axis
  • bounds
  • grid
  • position (TODO: Examples)
  • gpuAcceleration
  • applyUserSelectHack
  • ignoreMultitouch
  • disabled
  • handle
  • cancel
  • defaultClass
  • defaultClassDragging
  • defaultClassDragged
  • defaultPosition
  • onDragStart (TODO: Examples)
  • onDrag (TODO: Examples)
  • onDragEnd (TODO: Examples)

Proper tests

Write proper tests. Use playwright and vitest(Try for one only one, but both just in case)

Allow elements to be swapped with each other?

Hi, I'm working on a new side project, and it involves having a grid of draggable elements. I'm using this library in React. The goal is to drag an element the element somewhere on the grid, when dropped, find the closest element to its position, and swap the elements around. I have looked at the "Position" option, but this doesn't allow to change the X,Y relative to the container.

Is it possible to have a swap option in this library, so we can easily swap elements?

data-neodrag-count

Append a data-neodrag-count on the root node. This will be the number of times the thing has been dragged.

Also expose this in events? ๐Ÿค”

Add a way to trigger the drag start programatically

I'm trying to create a draggable element that's dragged after a certain delay pressing into it and inside a certain distance from the pointer down event.

There currently doesn't seem to be a way to implement this: if I disable the draggable, when enabling it after the delay is done, the draggable doesn't start dragging (because the disabled property exists the onDragStart early internally and doesn't even start tracking the state).

If I try to let the draggable enabled but set the position to the initial position on the onDrag handler, the element jumps back an forth between the internal draggable's position and the initial position I want it to be.

Is this solvable in some way? Maybe calling something to imperatively start the internal state when I want?

Drag to grid

Can I have multiple elements in a grid and have the elements rearrangable in the grid by dragging? And have it snap to only certain neatly arranged grid positions?

Prevent "mousedown" event bubbling

I don't want the click falling through my draggable component. Is there a way to prevent mousedown event from bubbling but still triggering dragStart?

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.