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.07k stars 3.68k forks source link

Help with creating a lit based element #2275

Open joezappie opened 1 year 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 5 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