Open jdu opened 5 years ago
<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:
1) The component that is going to be dragged MUST have :host
styles, for instance display: block; width: 40px; height: 40px;
even if the inner element has styling and size. @click
work on the component without styling the :host
node but @dragstart
won't for some reason.
2) Use the @
prefixed event markup in lit-html, unprefixed ondragstart
, onDragStart
, dragStart
don'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.
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.