Easier scrolling while dragging in sortable
This commit is contained in:
parent
ecd3f45081
commit
8f86a99116
@ -1,51 +1,80 @@
|
||||
.sortable {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
--element-height: 50px;
|
||||
--gap: 8px;
|
||||
|
||||
.sortable__element {
|
||||
height: var(--element-height);
|
||||
background-color: white;
|
||||
box-sizing: border-box;
|
||||
min-width: 400px;
|
||||
font-size: 20px;
|
||||
border: 1px solid black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
grid-column: 1/2;
|
||||
grid-row: calc((var(--index) + 1) * 3 + 1) / calc((var(--index) + 1) * 3 + 3);
|
||||
|
||||
&.is-dragged {
|
||||
opacity: 0.2;
|
||||
}
|
||||
@keyframes sortable-enter {
|
||||
from {
|
||||
transform: scale(0);
|
||||
}
|
||||
|
||||
.sortable__hole {
|
||||
grid-column: 1/2;
|
||||
opacity: 0.5;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
grid-row: calc((var(--index) + 1) * 3 + 2) / calc((var(--index) + 1) * 3 + 5);
|
||||
|
||||
&.ready-to-drop + .sortable__spacer {
|
||||
height: var(--element-height) !important;
|
||||
}
|
||||
to {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
.sortable-wrapper {
|
||||
.edge-detector {
|
||||
height: 30px;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
display: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&:has(.is-dragged) {
|
||||
.sortable__hole {
|
||||
pointer-events: all;
|
||||
.edge-detector {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.sortable__spacer {
|
||||
grid-column: 1/2;
|
||||
grid-row: calc((var(--index) + 1) * 3 + 3) / calc((var(--index) + 1) * 3 + 4);
|
||||
transition: all 100ms;
|
||||
height: 8px;
|
||||
.sortable {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
--element-height: 50px;
|
||||
--gap: 8px;
|
||||
|
||||
.sortable__element {
|
||||
height: var(--element-height);
|
||||
grid-column: 1/2;
|
||||
grid-row: calc((var(--index) + 1) * 3 + 1) / calc((var(--index) + 1) * 3 + 3);
|
||||
|
||||
background-color: white;
|
||||
box-sizing: border-box;
|
||||
min-width: 400px;
|
||||
font-size: 20px;
|
||||
border: 1px solid black;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: move;
|
||||
user-select: none;
|
||||
|
||||
animation: sortable-enter 100ms;
|
||||
|
||||
&.is-dragged {
|
||||
opacity: 0.2;
|
||||
}
|
||||
}
|
||||
|
||||
.sortable__hole {
|
||||
grid-column: 1/2;
|
||||
z-index: 1;
|
||||
/* background-color: red; */
|
||||
pointer-events: none;
|
||||
grid-row: calc((var(--index) + 1) * 3 + 2) / calc((var(--index) + 1) * 3 + 5);
|
||||
|
||||
&.ready-to-drop + .sortable__spacer {
|
||||
height: var(--element-height) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:has(.is-dragged) {
|
||||
.sortable__hole {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
.sortable__spacer {
|
||||
grid-column: 1/2;
|
||||
grid-row: calc((var(--index) + 1) * 3 + 3) / calc((var(--index) + 1) * 3 + 4);
|
||||
transition: all 200ms;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,11 @@
|
||||
import { Controller } from "stimulus";
|
||||
|
||||
async function sleep(time: number) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, time);
|
||||
});
|
||||
}
|
||||
|
||||
export default class Sortable extends Controller {
|
||||
dragged_element: HTMLDivElement | null = null;
|
||||
|
||||
@ -79,7 +85,7 @@ export default class Sortable extends Controller {
|
||||
connect() {
|
||||
this.element.querySelectorAll(".sortable__element").forEach((element) => {
|
||||
element.addEventListener("dragstart", (e: DragEvent) => {
|
||||
event.dataTransfer.effectAllowed = "move";
|
||||
e.dataTransfer.effectAllowed = "move";
|
||||
const target = e.target as HTMLDivElement;
|
||||
this.dragged_element = target;
|
||||
setTimeout(() => {
|
||||
@ -94,6 +100,32 @@ export default class Sortable extends Controller {
|
||||
target.classList.remove("is-dragged");
|
||||
});
|
||||
});
|
||||
this.element
|
||||
.querySelectorAll(".edge-detector")
|
||||
.forEach((detector: HTMLDivElement) => {
|
||||
let is_hovered = false;
|
||||
detector.addEventListener("dragenter", async (e) => {
|
||||
e.preventDefault();
|
||||
const target = e.target as HTMLDivElement;
|
||||
const step = parseInt(target.getAttribute("data-step"));
|
||||
is_hovered = true;
|
||||
while (is_hovered && this.dragged_element) {
|
||||
window.scrollTo(window.scrollX, window.scrollY + step);
|
||||
await sleep(16);
|
||||
}
|
||||
});
|
||||
|
||||
detector.addEventListener("dragover ", (e) => {
|
||||
// necessary for drag events;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
detector.addEventListener("dragleave", (e) => {
|
||||
const target = e.target as HTMLDivElement;
|
||||
e.preventDefault();
|
||||
is_hovered = false;
|
||||
});
|
||||
});
|
||||
this.element
|
||||
.querySelectorAll(".sortable__hole")
|
||||
.forEach((dropElement: HTMLDivElement) =>
|
||||
|
@ -2,7 +2,7 @@ import { TempstreamJSX } from "tempstream";
|
||||
|
||||
export function sortable({ items }: { items: JSX.Element[] }) {
|
||||
return (
|
||||
<div>
|
||||
<div class="sortable-wrapper">
|
||||
<div
|
||||
data-controller="sortable"
|
||||
class="sortable"
|
||||
@ -10,6 +10,8 @@ export function sortable({ items }: { items: JSX.Element[] }) {
|
||||
items.length * 2
|
||||
}, minmax(8px, min-content))`}
|
||||
>
|
||||
<div class="edge-detector" style="top: 0" data-step="-10"></div>
|
||||
<div class="edge-detector" style="bottom: 0" data-step="10"></div>
|
||||
<div class="sortable__hole" data-index="-1" style="--index: -1"></div>
|
||||
<div class="sortable__spacer" data-index="-1" style="--index: -1"></div>
|
||||
{items.map((item, index) => (
|
||||
|
@ -16,9 +16,22 @@ export default new (class SortableDemoPage extends Page {
|
||||
return html(
|
||||
ctx,
|
||||
"SortableDemo",
|
||||
sortable({
|
||||
items: ["One", "Two", "Three", "Four", "Five"].map((e) => <div>{e}</div>),
|
||||
})
|
||||
<div>
|
||||
<h2>Short list</h2>
|
||||
{sortable({
|
||||
items: ["One", "Two", "Three", "Four", "Five"].map((e) => (
|
||||
<div>{e}</div>
|
||||
)),
|
||||
})}
|
||||
|
||||
<h2>Long list</h2>
|
||||
{sortable({
|
||||
items: "a"
|
||||
.repeat(100)
|
||||
.split("")
|
||||
.map((_, index) => <div>{index}</div>),
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})();
|
||||
|
Loading…
x
Reference in New Issue
Block a user