mirror of
https://github.com/we-promise/sure.git
synced 2026-04-19 20:14:08 +00:00
Merge pull request #1210 from lolimmlost/fix/dashboard-touch-hold
Fix dashboard mobile: require press-and-hold to reorder sections
This commit is contained in:
@@ -3,9 +3,9 @@ import { Controller } from "@hotwired/stimulus";
|
|||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = ["section", "handle"];
|
static targets = ["section", "handle"];
|
||||||
|
|
||||||
// Short delay to prevent accidental touches on the grip handle
|
// Hold delay to require deliberate press-and-hold before activating drag mode
|
||||||
static values = {
|
static values = {
|
||||||
holdDelay: { type: Number, default: 150 },
|
holdDelay: { type: Number, default: 800 },
|
||||||
};
|
};
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
@@ -22,6 +22,14 @@ export default class extends Controller {
|
|||||||
|
|
||||||
// ===== Mouse Drag Events =====
|
// ===== Mouse Drag Events =====
|
||||||
dragStart(event) {
|
dragStart(event) {
|
||||||
|
// If a touch interaction is in progress, cancel native drag —
|
||||||
|
// use touch events with hold delay instead.
|
||||||
|
// This avoids blocking mouse/trackpad drag on touch-capable laptops.
|
||||||
|
if (this.isTouching || this.pendingSection) {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.draggedElement = event.currentTarget;
|
this.draggedElement = event.currentTarget;
|
||||||
this.draggedElement.classList.add("opacity-50");
|
this.draggedElement.classList.add("opacity-50");
|
||||||
this.draggedElement.setAttribute("aria-grabbed", "true");
|
this.draggedElement.setAttribute("aria-grabbed", "true");
|
||||||
@@ -88,6 +96,10 @@ export default class extends Controller {
|
|||||||
this.currentTouchY = this.touchStartY;
|
this.currentTouchY = this.touchStartY;
|
||||||
this.holdActivated = false;
|
this.holdActivated = false;
|
||||||
|
|
||||||
|
// Prevent text selection while waiting for hold to activate
|
||||||
|
section.style.userSelect = "none";
|
||||||
|
section.style.webkitUserSelect = "none";
|
||||||
|
|
||||||
// Start hold timer
|
// Start hold timer
|
||||||
this.holdTimer = setTimeout(() => {
|
this.holdTimer = setTimeout(() => {
|
||||||
this.activateDrag();
|
this.activateDrag();
|
||||||
@@ -110,11 +122,25 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
touchMove(event) {
|
touchMove(event) {
|
||||||
if (!this.holdActivated || !this.isTouching || !this.draggedElement) return;
|
const touchX = event.touches[0].clientX;
|
||||||
|
const touchY = event.touches[0].clientY;
|
||||||
|
|
||||||
|
// If hold hasn't activated yet, cancel if user moves too far (scrolling or swiping)
|
||||||
|
// Uses Euclidean distance to catch diagonal gestures too
|
||||||
|
if (!this.holdActivated) {
|
||||||
|
const dx = touchX - this.touchStartX;
|
||||||
|
const dy = touchY - this.touchStartY;
|
||||||
|
if (dx * dx + dy * dy > 100) { // 10px radius
|
||||||
|
this.cancelHold();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.isTouching || !this.draggedElement) return;
|
||||||
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.currentTouchX = event.touches[0].clientX;
|
this.currentTouchX = touchX;
|
||||||
this.currentTouchY = event.touches[0].clientY;
|
this.currentTouchY = touchY;
|
||||||
|
|
||||||
const afterElement = this.getDragAfterElement(this.currentTouchX, this.currentTouchY);
|
const afterElement = this.getDragAfterElement(this.currentTouchX, this.currentTouchY);
|
||||||
this.clearPlaceholders();
|
this.clearPlaceholders();
|
||||||
@@ -159,6 +185,16 @@ export default class extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resetTouchState() {
|
resetTouchState() {
|
||||||
|
// Restore text selection
|
||||||
|
if (this.pendingSection) {
|
||||||
|
this.pendingSection.style.userSelect = "";
|
||||||
|
this.pendingSection.style.webkitUserSelect = "";
|
||||||
|
}
|
||||||
|
if (this.draggedElement) {
|
||||||
|
this.draggedElement.style.userSelect = "";
|
||||||
|
this.draggedElement.style.webkitUserSelect = "";
|
||||||
|
}
|
||||||
|
|
||||||
this.isTouching = false;
|
this.isTouching = false;
|
||||||
this.draggedElement = null;
|
this.draggedElement = null;
|
||||||
this.pendingSection = null;
|
this.pendingSection = null;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
touchstart->dashboard-sortable#touchStart
|
touchstart->dashboard-sortable#touchStart
|
||||||
touchmove->dashboard-sortable#touchMove
|
touchmove->dashboard-sortable#touchMove
|
||||||
touchend->dashboard-sortable#touchEnd
|
touchend->dashboard-sortable#touchEnd
|
||||||
|
touchcancel->dashboard-sortable#touchEnd
|
||||||
keydown->dashboard-sortable#handleKeyDown">
|
keydown->dashboard-sortable#handleKeyDown">
|
||||||
<div class="px-4 py-2 flex items-center justify-between">
|
<div class="px-4 py-2 flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
|
|||||||
Reference in New Issue
Block a user