marcospont / agnostic-draggable

An agnostic set of libraries implementing draggable, droppable, sortable and resizable behaviors inspired by the jQuery UI implementation
MIT License
39 stars 7 forks source link

Droppable doesn't work if used on an element with `display: contents` #10

Open connoratmyxt opened 2 years ago

connoratmyxt commented 2 years ago

Hi, I found this bug while using your library. I am willing to create a PR to fix this if you'd like.

Steps to reproduce

  1. Use display: contents on an element
  2. Make a Droppable with it
  3. It doesn't work (the Droppable only uses the bounding box of the container to calculate the droppable area, which is always [0,0,0,0] on elements with display: contents)

Expected behavior

If the bounding box is [0,0,0,0], the Droppable should use the union of the bounding boxes of any not-absolutely-positioned children as the droppable area.

Code

This code doesn't work ```js import agnosticDraggable from "https://cdn.skypack.dev/agnostic-draggable@1.4.3"; const { Draggable, Droppable } = agnosticDraggable const app = document.createElement("div") const data = [ { name: "foo", number: 100, date: new Date(Date.now() - 2 * 60 * 1000).toLocaleString() }, { name: "bar", number: 200, date: new Date(Date.now() - 60 * 1000).toLocaleString() }, { name: "baz", number: 300, date: new Date(Date.now()).toLocaleString() } ] const table = document.createElement("table") table.style.width = "calc(100% - 40px)" table.style.margin = "20px" table.style.display = "grid" table.style.gridTemplateColumns = "auto auto auto" const thead = document.createElement("thead") thead.style.display = "contents" const tbody = document.createElement("tbody") tbody.style.display = "contents" const tr = document.createElement("tr") tr.style.display = "contents" for (const col of Object.keys(data[0])) { const th = document.createElement("th") th.style.background = "rgba(127, 127, 127, .1)" th.style.padding = "8px" th.style.border = "solid rgb(127,127,127)" th.style.borderWidth = "1px 0" th.textContent = col tr.appendChild(th) } thead.appendChild(tr) for (const row of data) { const tr = document.createElement("tr") tr.style.display = "contents" const cells = Object.values(row).map((cell) => { const td = document.createElement("td") td.style.padding = "8px" td.style.border = "solid rgb(127,127,127)" td.style.borderWidth = "1px 0" td.textContent = cell tr.appendChild(td) }) tbody.appendChild(tr) new Droppable( tr, { tolerance: "pointer", }, { "droppable:over": () => { tr.style.background = "blue" }, "droppable:out": () => { tr.style.background = "" }, "droppable:drop": () => { tr.style.background = "" const td = tr.querySelector("td:nth-child(2)") td.textContent = parseInt(td.textContent) + 1 } } ) } table.appendChild(thead) table.appendChild(tbody) app.appendChild(table) const addDiv = document.createElement("div") addDiv.style.display = "inline-block" addDiv.style.margin = "20px" addDiv.style.padding = "8px" addDiv.style.background = "rgb(127,127,127)" addDiv.textContent = "Drop me on a row to add 1" app.appendChild(addDiv) document.body.appendChild(app) let listener = (e) => { e.preventDefault() } new Draggable( addDiv, { revert: true, }, { "drag:start": () => { document.body.addEventListener("selectstart", listener) }, "drag:stop": () => { document.body.removeEventListener("selectstart", listener) } } ) ```
This code works ```js import agnosticDraggable from "https://cdn.skypack.dev/agnostic-draggable@1.4.3"; const { Draggable, Droppable } = agnosticDraggable const app = document.createElement("div") const data = [ { name: "foo", number: 100, date: new Date(Date.now() - 2 * 60 * 1000).toLocaleString() }, { name: "bar", number: 200, date: new Date(Date.now() - 60 * 1000).toLocaleString() }, { name: "baz", number: 300, date: new Date(Date.now()).toLocaleString() } ] const table = document.createElement("table") table.style.width = "calc(100% - 40px)" table.style.margin = "20px" const thead = document.createElement("thead") const tbody = document.createElement("tbody") const tr = document.createElement("tr") for (const col of Object.keys(data[0])) { const th = document.createElement("th") th.style.background = "rgba(127, 127, 127, .1)" th.style.padding = "8px" th.style.border = "solid rgb(127,127,127)" th.style.borderWidth = "1px 0" th.textContent = col tr.appendChild(th) } thead.appendChild(tr) for (const row of data) { const tr = document.createElement("tr") const cells = Object.values(row).map((cell) => { const td = document.createElement("td") td.style.padding = "8px" td.style.border = "solid rgb(127,127,127)" td.style.borderWidth = "1px 0" td.textContent = cell tr.appendChild(td) }) tbody.appendChild(tr) new Droppable( tr, { tolerance: "pointer", }, { "droppable:over": () => { tr.style.background = "blue" }, "droppable:out": () => { tr.style.background = "" }, "droppable:drop": () => { tr.style.background = "" const td = tr.querySelector("td:nth-child(2)") td.textContent = parseInt(td.textContent) + 1 } } ) } table.appendChild(thead) table.appendChild(tbody) app.appendChild(table) const addDiv = document.createElement("div") addDiv.style.display = "inline-block" addDiv.style.margin = "20px" addDiv.style.padding = "8px" addDiv.style.background = "rgb(127,127,127)" addDiv.textContent = "Drop me on a row to add 1" app.appendChild(addDiv) document.body.appendChild(app) let listener = (e) => { e.preventDefault() } new Draggable( addDiv, { revert: true, }, { "drag:start": () => { document.body.addEventListener("selectstart", listener) }, "drag:stop": () => { document.body.removeEventListener("selectstart", listener) } } ) ```

Suggested solution

  1. If the Droppable element has a bounding box of [0,0,0,0], search deeply for any children (excluding position: absolute or position: fixed) that have a not-zero bounding box
  2. Use those bounding boxes to test whether a Draggable is over the Droppable
marcospont commented 2 years ago

Hi, @connoratmyxt

I will try to come up with a solution based on your suggestion.

Thanks for the detailed information!