lit-demos
lit-demos copied to clipboard
Drag and drop | draggable=true demo
Would be nice to see an example for this as I'm struggling pretty badly trying to get drag and drop to work in LitElement at the moment. I can get it working with Vanilla js easily, but LitElement is doing my head in trying to figure it out without having to import the whole Polymer Gesture library.
<div draggable="true"></div> should work in LitElement, do you have a more specific example/repro I could take a look at?
Additionally:
<div draggable=${true}></div> <- works
<div draggable=${'true'}></div> <- works
<div ?draggable=${true}></div> <- does not work
Apparently the draggable attribute is not an actual boolean attribute, but an enumerated attribute.
More info here: https://github.com/Polymer/lit-element/issues/565
Neither of the elements in the below become draggable either through setting the draggable attribute through connectedCallback or directly on the custom element. I have drag and drop peppered through the apps I work on in plain vanilla JS and React but for some reason my brain isn't wrapping around using LitElement custom elements...
import {html, css, LitElement} from "lit-element";
class DragTestInElement extends LitElement {
static get styles() {
return css`
div {
display: block;
width: 120px;
height: 40px;
background: green;
}
`;
}
connectedCallback() {
super.connectedCallback();
this.setAttribute("draggable", true);
}
render() {
return html`
<div>DragInElement</div>
`;
}
}
window.customElements.define("drag-test-in", DragTestInElement);
class DragTestOnElement extends LitElement {
static get styles() {
return css`
div {
display: block;
width: 120px;
height: 40px;
background: purple;
}
`;
}
render() {
return html`
<div>DragOnElement</div>
`;
}
}
window.customElements.define("drag-test-on", DragTestOnElement);
class DragArea extends LitElement {
render() {
return html`
<div class="drag-area">
<drag-test-on draggable="true"></drag-test-on>
<drag-test-in></drag-test-in>
</div>
`;
}
}
window.customElements.define("drag-area", DragArea);
let el = document.getElementById("application");
el.innerHTML = "";
let root = document.createElement("drag-area");
el.appendChild(root);
I've also tried a number of different naming variations on the ondragstart event, appending a plain vanilla DOM node with draggables and el.ondragstart = function() {...} works fine in the same example. As well I overrode createRenderRoot within the DragArea component to remove the shadow dom and render DragArea as a plain element tree in case it was something to do with nested Shadow DOMS and even then the drag-test-in and drag-test-on elements won't drag or fire the ondragstart event under those circumstance. Maybe a bug in LitElement/LitHTML not propagating the event or draggable property?
Right I've figured this out after poking and prodding it for a good hour or so.
class DragTestOnElement extends LitElement {
static get styles() {
return css`
:host {
width: 120px;
height: 40px;
background: purple;
border: 1px solid black;
display: block;
}
`;
}
render() {
return html`
<div></div>
`;
}
}
window.customElements.define("drag-test-on", DragTestOnElement);
class DragArea extends LitElement {
static get styles() {
return css`
.drag-test {
display: block;
width: 40px;
height: 40px;
background: pink;
border: 1px solid black;
}
`;
}
hitTest(e) {
e.dataTransfer.setData("e", "TEST");
console.log(e);
}
render() {
return html`
<style>
.drag-test {
display: block;
width: 40px;
height: 40px;
background: pink;
border: 1px solid black;
}
</style>
<div class="drag-area">
<drag-test-on
draggable="true"
@dragstart=${this.hitTest}></drag-test-on>
</div>
`;
}
}
window.customElements.define("drag-area", DragArea);
let el = document.getElementById("application");
let root = document.createElement("drag-area");
el.appendChild(root);
So there was a couple things I was doing wrong, that I'll document here in case others come across this, these may have been clear for someone working in LitElement but coming from Vanilla and React I didn't get it right away:
- The component that is going to be dragged MUST have
:hoststyles, for instancedisplay: block; width: 40px; height: 40px;even if the inner element has styling and size.@clickwork on the component without styling the:hostnode but@dragstartwon't for some reason. - Use the
@prefixed event markup in lit-html, unprefixedondragstart,onDragStart,dragStartdon't seem to work
I think this is still worth setting up an example of drag and drop as it's a fairly common use case for more complex events than click, hover, etc... and it's clearly been a pain point for me (a moderately experienced developer) to see what the issue in my implementation was.
Sorry for the delay in getting back to you
I think this could be good to add to the demos under the Advanced section, and additionally it might be good as a write up for the faq like this one: https://open-wc.org/faq/rerender.html as well
Would you be willing to make a PR to add a demo and add the writeup to the faq?
See https://github.com/Polymer/lit-html/issues/460
Gist: You need to have the drag events mutate the underlying data as you move stuff around. You cannot ever allow the DOM to change due to your drop/move, every DOM change has to be driven from the model.
I'll share some code:
case "start":
this._draggedId = itemId;
e.target.closest(".option-item").classList.add("drag-item");
break;
case "enter": {
if (this._draggedId == null) return; // ignore drag from other places
e.target.closest(".option-item").classList.add("drag-hover");
this._moveItem(this._draggedId, itemId);
// See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
e.preventDefault();
break;
}
case "over": {
// See https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API/Drag_operations#droptargets
e.preventDefault();
break;
}
case "leave":
if (this._draggedId == null) return; // ignore drag from other places
e.target.closest(".option-item").classList.remove("drag-hover");
break;
case "drop": {
if (this._draggedId == null) return; // ignore drag from other places
e.target.closest(".option-item").classList.remove("drag-hover");
e.target.closest(".option-item").classList.remove("drag-item");
this._moveItem(this._draggedId, itemId, true);
break;
}
case "end":
if (this._draggedId == null) return; // ignore drag from other places
e.target.closest(".option-item").classList.remove("drag-item");
this._draggedId = null;
break;
Some DOM:
this._model.map(
(m, idx) => html`
<div
@dragstart="${e => this.dndEvent("start", m.id, e)}"
@dragenter="${e => this.dndEvent("enter", m.id, e)}"
@dragover="${e => this.dndEvent("over", m.id, e)}"
@dragend="${e => this.dndEvent("end", m.id, e)}"
@dragleave="${e => this.dndEvent("leave", m.id, e)}"
@drop="${e => this.dndEvent("drop", m.id, e)}"
draggable="true"
>Item ${m.id}</div>`;
Here, _moveItem is just mutating the underlying list.
Works well for dragging stuff within a list... still figuring out how to drag and drop between different lists, etc. but the principle is the same: data-driven DOM changes
@jdu- Thanks, your pointers saved my day. I wanted to contribute some other details to get dnd working. The dropzone element needs to have handlers for @drop & @dragover, make sure to call preventDefault() in the handler for dragover otherwise the drop won't fire.
Look into this complete sample draggable web-component project which was built using LitElement with JavaScript. I implement this with help of @jdu code. I hope this project will help you on this problem.
https://github.com/rayanaradha/Draggable-web-component
This project contains 3 classes.
DragTestOnElement - Component for Draggable item. DragContainer - Component for Item Container. DragArea - UI In this project Draggable Items can be dragged and dropped between Item Containers.