From 8f86a99116cbfa7d01de251d2eea9f4d911a8f98 Mon Sep 17 00:00:00 2001 From: Kuba Orlik Date: Sat, 31 Aug 2024 16:36:30 +0200 Subject: [PATCH] Easier scrolling while dragging in sortable --- src/back/routes/common/sortable/sortable.css | 111 +++++++++++------- .../common/sortable/sortable.stimulus.ts | 34 +++++- src/back/routes/common/sortable/sortable.tsx | 4 +- src/back/routes/demos/sortable.page.tsx | 19 ++- 4 files changed, 122 insertions(+), 46 deletions(-) diff --git a/src/back/routes/common/sortable/sortable.css b/src/back/routes/common/sortable/sortable.css index c10e8f4..95a71fb 100644 --- a/src/back/routes/common/sortable/sortable.css +++ b/src/back/routes/common/sortable/sortable.css @@ -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; + } } } diff --git a/src/back/routes/common/sortable/sortable.stimulus.ts b/src/back/routes/common/sortable/sortable.stimulus.ts index 884324e..9280ba9 100644 --- a/src/back/routes/common/sortable/sortable.stimulus.ts +++ b/src/back/routes/common/sortable/sortable.stimulus.ts @@ -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) => diff --git a/src/back/routes/common/sortable/sortable.tsx b/src/back/routes/common/sortable/sortable.tsx index 4fe672d..5c1ee4d 100644 --- a/src/back/routes/common/sortable/sortable.tsx +++ b/src/back/routes/common/sortable/sortable.tsx @@ -2,7 +2,7 @@ import { TempstreamJSX } from "tempstream"; export function sortable({ items }: { items: JSX.Element[] }) { return ( -
+
+
+
{items.map((item, index) => ( diff --git a/src/back/routes/demos/sortable.page.tsx b/src/back/routes/demos/sortable.page.tsx index 8cbe19f..d2f371f 100644 --- a/src/back/routes/demos/sortable.page.tsx +++ b/src/back/routes/demos/sortable.page.tsx @@ -16,9 +16,22 @@ export default new (class SortableDemoPage extends Page { return html( ctx, "SortableDemo", - sortable({ - items: ["One", "Two", "Three", "Four", "Five"].map((e) =>
{e}
), - }) +
+

Short list

+ {sortable({ + items: ["One", "Two", "Three", "Four", "Five"].map((e) => ( +
{e}
+ )), + })} + +

Long list

+ {sortable({ + items: "a" + .repeat(100) + .split("") + .map((_, index) =>
{index}
), + })} +
); } })();