SortableJS / Sortable

Reorderable drag-and-drop lists for modern browsers and touch devices. No jQuery or framework required.
https://sortablejs.github.io/Sortable/
MIT License
29.78k stars 3.7k forks source link

Help with creating a lit based element #2275

Closed joezappie closed 4 months ago

joezappie commented 1 year ago

Since LIT is not a currently supported framework, I'm trying to figure out how to turn this into a lit component myself. I've looked at the sortable libraries for other frameworks to get ideas but haven't gotten it working well yet. Since LIT doesn't allow anything else to modify the DOM its managing, it needs to stop sortable from moving stuff around, and instead internally manage its list of items.

My current approach is to use onMove and always return false so it doesn't modify the HTML directly. I then want to manually update the items array to reflect the dragged change and trigger a lit update so it all stays in sync.

import { LitElement, html, nothing } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { map } from 'lit/directives/map.js';
import Sortable from 'sortablejs';

@customElement('lit-sortable')
export class LitSortable extends LitElement {
  @property({ type: Array }) items;
  @property({ type: Object }) options;
  @property({}) itemTemplate = (value) => html`${value}`;

  willUpdate(changedProperties) {
    super.willUpdate(changedProperties);

    if (changedProperties.has('options')) {
      this.sortable?.destroy();

      this.options.onMove = (evt) => {
        const children = [...this.querySelectorAll(`:scope > ${this.options.draggable || '*'}`)];
        const oldIndex = children.indexOf(evt.dragged);
        const newIndex = children.indexOf(evt.related);

        const el = this.items[oldIndex];
        this.items.splice(oldIndex, 1);
        this.items.splice(newIndex, 0, el);
        this.requestUpdate();

        return false;
      };

      this.sortable = Sortable.create(this, this.options);
    }
  }

  render() {
    console.log(this.items);
    return map(this.items, this.itemTemplate);
  }

  createRenderRoot() {
    return this;
  }
}

This kind of works... but causes the item I'm dragging and the one I'm hovering to infinitely flip while dragging. I'm curious if you have any suggestions on ways to make the library work without being in control of the DOM itself.

I think my issue is that Lit reuses the elements, so after reording the list, technically Lit never moves the "dragged" element, only updates the changed text. I'd need a way to tell sortablejs that the "dragged" element reference has changed.

enkelmedia commented 10 months ago

Hi!

I've spent quite some time on getting Sortable to work with Lit and shared the best approach I've found so far here: https://github.com/SortableJS/Sortable/issues/2089#issuecomment-1902094024 hope this helps =D

PirAt39 commented 4 months ago

Hello, @joezappie Did you find any solution for this? I am also having same issue.

joezappie commented 4 months ago

@PirAt39 Unfortunately no, I ended up just making my own bare necessity sorting code for my problem at the time. As a general solution, i think enkelmedia's is your best bet to hook into sortable's callbacks.

jdcoldsmith commented 4 months ago

@joezappie I found this solution to work very well for me. You can find the full comment here.

joezappie commented 4 months ago

@jdcoldsmith That looks like a great solution and will definately use it next time I need a sortable list.

Only reason I didnt use enkelmedias solution was by the time he posted I had already just made a minimal drag and drop element myself. Definately prefer using sortablejs as its such a more robust library so thanks for posting your solution!

I'm going to close this issue.

jdcoldsmith commented 4 months ago

You're welcome! To be clear I didn't create that solution but I found it here