2023-08-14 23:42:02 +00:00
|
|
|
class DraggableContainer extends HTMLElement {
|
|
|
|
constructor () {
|
|
|
|
super();
|
|
|
|
this.attachShadow({ mode: "open" });
|
|
|
|
this.shadowRoot.innerHTML = `
|
|
|
|
<label id="title"></label>
|
|
|
|
<div id="wrapper">
|
|
|
|
<draggable-item id="bottom" class="drop-target"></draggable-item>
|
|
|
|
</div>
|
|
|
|
`;
|
|
|
|
this.content = this.shadowRoot.querySelector("#wrapper");
|
|
|
|
this.bottom = this.shadowRoot.querySelector("#bottom");
|
|
|
|
this.titleElem = this.shadowRoot.querySelector("#title");
|
|
|
|
}
|
|
|
|
|
|
|
|
get title () {
|
|
|
|
return this.titleElem.innerText;
|
|
|
|
}
|
|
|
|
|
|
|
|
set title (title) {
|
|
|
|
this.titleElem.innerText = title;
|
|
|
|
}
|
|
|
|
|
|
|
|
append (newNode) {
|
|
|
|
this.content.insertBefore(newNode, this.bottom);
|
|
|
|
}
|
|
|
|
|
|
|
|
insertBefore (newNode, referenceNode) {
|
|
|
|
this.content.insertBefore(newNode, referenceNode);
|
|
|
|
}
|
2023-08-16 23:14:42 +00:00
|
|
|
|
2023-08-22 21:23:14 +00:00
|
|
|
getItemByID (nodeid) {
|
|
|
|
return this.content.querySelector(`#${nodeid}`);
|
|
|
|
}
|
|
|
|
|
2023-08-16 23:14:42 +00:00
|
|
|
deleteItemByID (nodeid) {
|
|
|
|
const node = this.content.querySelector(`#${nodeid}`);
|
|
|
|
if (node) {
|
|
|
|
this.content.removeChild(node);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2023-08-19 03:32:52 +00:00
|
|
|
|
|
|
|
set value (value) {}
|
|
|
|
|
|
|
|
get value () {
|
|
|
|
const value = [];
|
|
|
|
this.content.childNodes.forEach((element) => {
|
|
|
|
if (element.value) {
|
|
|
|
value.push(element.value);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
return value;
|
|
|
|
}
|
2023-08-14 23:42:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
class DraggableItem extends HTMLElement {
|
|
|
|
constructor () {
|
|
|
|
super();
|
|
|
|
this.attachShadow({ mode: "open" });
|
2023-08-16 23:14:42 +00:00
|
|
|
// for whatever reason, only grid layout seems to respect the parent's content bounds
|
2023-08-14 23:42:02 +00:00
|
|
|
this.shadowRoot.innerHTML = `
|
|
|
|
<style>
|
2023-08-19 03:32:52 +00:00
|
|
|
#drag {
|
|
|
|
cursor: move;
|
|
|
|
}
|
2023-08-16 23:14:42 +00:00
|
|
|
img {
|
|
|
|
height: 1em;
|
|
|
|
width: 1em;
|
2023-08-14 23:42:02 +00:00
|
|
|
}
|
|
|
|
</style>
|
2023-08-16 23:14:42 +00:00
|
|
|
<div id="wrapper">
|
|
|
|
<div style="min-height: 1.5em;"></div>
|
|
|
|
</div>
|
2023-08-14 23:42:02 +00:00
|
|
|
`;
|
|
|
|
this.content = this.shadowRoot.querySelector("#wrapper");
|
|
|
|
// add drag and drop listeners
|
|
|
|
this.addEventListener("dragstart", (event) => {
|
|
|
|
this.content.style.opacity = "0.5";
|
2023-08-29 21:26:57 +00:00
|
|
|
const data = { id: this.id, data: this.data, content: this.content.innerHTML, value: this.value };
|
2023-08-19 03:32:52 +00:00
|
|
|
event.dataTransfer.setData("application/json", JSON.stringify(data));
|
2023-08-14 23:42:02 +00:00
|
|
|
event.dataTransfer.effectAllowed = "move";
|
|
|
|
});
|
|
|
|
this.addEventListener("dragend", (event) => {
|
|
|
|
if (event.dataTransfer.dropEffect === "move") {
|
|
|
|
this.parentElement.removeChild(this);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.content.attributeStyleMap.clear();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
this.addEventListener("dragenter", (event) => {
|
|
|
|
if (event.target.classList.contains("drop-target")) {
|
|
|
|
event.target.classList.add("drag-over");
|
|
|
|
event.target.borderTop = true;
|
|
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
});
|
|
|
|
this.addEventListener("dragleave", (event) => {
|
|
|
|
if (event.target.classList.contains("drag-over")) {
|
|
|
|
event.target.classList.remove("drag-over");
|
|
|
|
event.target.borderTop = false;
|
|
|
|
}
|
|
|
|
event.preventDefault();
|
|
|
|
});
|
|
|
|
this.addEventListener("dragover", (event) => {
|
|
|
|
event.preventDefault();
|
|
|
|
});
|
|
|
|
this.addEventListener("drop", (event) => {
|
|
|
|
if (event.target.classList.contains("drag-over")) {
|
|
|
|
event.target.classList.remove("drag-over");
|
|
|
|
event.target.borderTop = false;
|
|
|
|
}
|
|
|
|
if (event.target.classList.contains("drop-target")) {
|
2023-08-22 21:23:14 +00:00
|
|
|
const transfer = JSON.parse(event.dataTransfer.getData("application/json"));
|
2023-08-14 23:42:02 +00:00
|
|
|
const item = document.createElement("draggable-item");
|
2023-08-22 21:23:14 +00:00
|
|
|
item.data = transfer.data;
|
|
|
|
item.innerHTML = transfer.content;
|
2023-08-14 23:42:02 +00:00
|
|
|
item.draggable = true;
|
|
|
|
item.classList.add("drop-target");
|
2023-08-29 21:26:57 +00:00
|
|
|
item.id = transfer.id;
|
2023-08-22 21:23:14 +00:00
|
|
|
item.value = transfer.data.value;
|
2023-08-14 23:42:02 +00:00
|
|
|
event.target.parentElement.insertBefore(item, event.target);
|
|
|
|
}
|
|
|
|
this.content.attributeStyleMap.clear();
|
|
|
|
event.preventDefault();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
get innerHTML () {
|
|
|
|
return this.content.innerHTML;
|
|
|
|
}
|
|
|
|
|
|
|
|
set innerHTML (innerHTML) {
|
|
|
|
this.content.innerHTML = innerHTML;
|
|
|
|
}
|
|
|
|
|
|
|
|
get borderTop () {
|
|
|
|
return this.content.style.borderTop === "";
|
|
|
|
}
|
|
|
|
|
|
|
|
set borderTop (borderTop) {
|
|
|
|
if (borderTop) {
|
|
|
|
this.content.style.borderTop = "1px dotted";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this.content.style.borderTop = "";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
customElements.define("draggable-container", DraggableContainer);
|
|
|
|
customElements.define("draggable-item", DraggableItem);
|