Easier scrolling while dragging in sortable
This commit is contained in:
parent
ecd3f45081
commit
8f86a99116
@ -1,3 +1,28 @@
|
|||||||
|
@keyframes sortable-enter {
|
||||||
|
from {
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.sortable-wrapper {
|
||||||
|
.edge-detector {
|
||||||
|
height: 30px;
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:has(.is-dragged) {
|
||||||
|
.edge-detector {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.sortable {
|
.sortable {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
@ -6,6 +31,9 @@
|
|||||||
|
|
||||||
.sortable__element {
|
.sortable__element {
|
||||||
height: var(--element-height);
|
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;
|
background-color: white;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
min-width: 400px;
|
min-width: 400px;
|
||||||
@ -16,8 +44,8 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
cursor: move;
|
cursor: move;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
grid-column: 1/2;
|
|
||||||
grid-row: calc((var(--index) + 1) * 3 + 1) / calc((var(--index) + 1) * 3 + 3);
|
animation: sortable-enter 100ms;
|
||||||
|
|
||||||
&.is-dragged {
|
&.is-dragged {
|
||||||
opacity: 0.2;
|
opacity: 0.2;
|
||||||
@ -26,8 +54,8 @@
|
|||||||
|
|
||||||
.sortable__hole {
|
.sortable__hole {
|
||||||
grid-column: 1/2;
|
grid-column: 1/2;
|
||||||
opacity: 0.5;
|
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
|
/* background-color: red; */
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
grid-row: calc((var(--index) + 1) * 3 + 2) / calc((var(--index) + 1) * 3 + 5);
|
grid-row: calc((var(--index) + 1) * 3 + 2) / calc((var(--index) + 1) * 3 + 5);
|
||||||
|
|
||||||
@ -45,7 +73,8 @@
|
|||||||
.sortable__spacer {
|
.sortable__spacer {
|
||||||
grid-column: 1/2;
|
grid-column: 1/2;
|
||||||
grid-row: calc((var(--index) + 1) * 3 + 3) / calc((var(--index) + 1) * 3 + 4);
|
grid-row: calc((var(--index) + 1) * 3 + 3) / calc((var(--index) + 1) * 3 + 4);
|
||||||
transition: all 100ms;
|
transition: all 200ms;
|
||||||
height: 8px;
|
height: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -1,5 +1,11 @@
|
|||||||
import { Controller } from "stimulus";
|
import { Controller } from "stimulus";
|
||||||
|
|
||||||
|
async function sleep(time: number) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
setTimeout(resolve, time);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export default class Sortable extends Controller {
|
export default class Sortable extends Controller {
|
||||||
dragged_element: HTMLDivElement | null = null;
|
dragged_element: HTMLDivElement | null = null;
|
||||||
|
|
||||||
@ -79,7 +85,7 @@ export default class Sortable extends Controller {
|
|||||||
connect() {
|
connect() {
|
||||||
this.element.querySelectorAll(".sortable__element").forEach((element) => {
|
this.element.querySelectorAll(".sortable__element").forEach((element) => {
|
||||||
element.addEventListener("dragstart", (e: DragEvent) => {
|
element.addEventListener("dragstart", (e: DragEvent) => {
|
||||||
event.dataTransfer.effectAllowed = "move";
|
e.dataTransfer.effectAllowed = "move";
|
||||||
const target = e.target as HTMLDivElement;
|
const target = e.target as HTMLDivElement;
|
||||||
this.dragged_element = target;
|
this.dragged_element = target;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -94,6 +100,32 @@ export default class Sortable extends Controller {
|
|||||||
target.classList.remove("is-dragged");
|
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
|
this.element
|
||||||
.querySelectorAll(".sortable__hole")
|
.querySelectorAll(".sortable__hole")
|
||||||
.forEach((dropElement: HTMLDivElement) =>
|
.forEach((dropElement: HTMLDivElement) =>
|
||||||
|
@ -2,7 +2,7 @@ import { TempstreamJSX } from "tempstream";
|
|||||||
|
|
||||||
export function sortable({ items }: { items: JSX.Element[] }) {
|
export function sortable({ items }: { items: JSX.Element[] }) {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div class="sortable-wrapper">
|
||||||
<div
|
<div
|
||||||
data-controller="sortable"
|
data-controller="sortable"
|
||||||
class="sortable"
|
class="sortable"
|
||||||
@ -10,6 +10,8 @@ export function sortable({ items }: { items: JSX.Element[] }) {
|
|||||||
items.length * 2
|
items.length * 2
|
||||||
}, minmax(8px, min-content))`}
|
}, 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__hole" data-index="-1" style="--index: -1"></div>
|
||||||
<div class="sortable__spacer" data-index="-1" style="--index: -1"></div>
|
<div class="sortable__spacer" data-index="-1" style="--index: -1"></div>
|
||||||
{items.map((item, index) => (
|
{items.map((item, index) => (
|
||||||
|
@ -16,9 +16,22 @@ export default new (class SortableDemoPage extends Page {
|
|||||||
return html(
|
return html(
|
||||||
ctx,
|
ctx,
|
||||||
"SortableDemo",
|
"SortableDemo",
|
||||||
sortable({
|
<div>
|
||||||
items: ["One", "Two", "Three", "Four", "Five"].map((e) => <div>{e}</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