feat(dashboard): implement movement threshold for widget dragging to allow clicks
- Add 5px mouse movement threshold before drag starts - Prevent dragging when clicking interactive elements (contenteditable, input, button, etc.) - Store pending drag state and wait for movement before preventing default behavior - Allow normal click/edit interactions on widget content - Touch behavior unchanged (existing 150ms delay still works) - Fixes issue where contenteditable fields and buttons were not clickable
This commit is contained in:
@@ -30,6 +30,7 @@ export class DragDropHandler {
|
|||||||
enableSnap: true,
|
enableSnap: true,
|
||||||
ghostOpacity: 0.5,
|
ghostOpacity: 0.5,
|
||||||
touchDelay: 150, // Delay before touch drag starts (ms)
|
touchDelay: 150, // Delay before touch drag starts (ms)
|
||||||
|
mouseMoveThreshold: 5, // Pixels mouse must move before drag starts
|
||||||
...options
|
...options
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -37,6 +38,7 @@ export class DragDropHandler {
|
|||||||
this.dragHandlers = new Map();
|
this.dragHandlers = new Map();
|
||||||
this.gridOverlay = null;
|
this.gridOverlay = null;
|
||||||
this.touchTimer = null;
|
this.touchTimer = null;
|
||||||
|
this.mouseDragPending = null; // Tracks potential mouse drag before threshold
|
||||||
|
|
||||||
// Bound event handlers for cleanup
|
// Bound event handlers for cleanup
|
||||||
this.boundMouseMove = this.onMouseMove.bind(this);
|
this.boundMouseMove = this.onMouseMove.bind(this);
|
||||||
@@ -44,6 +46,8 @@ export class DragDropHandler {
|
|||||||
this.boundTouchMove = this.onTouchMove.bind(this);
|
this.boundTouchMove = this.onTouchMove.bind(this);
|
||||||
this.boundTouchEnd = this.onTouchEnd.bind(this);
|
this.boundTouchEnd = this.onTouchEnd.bind(this);
|
||||||
this.boundKeyDown = this.onKeyDown.bind(this);
|
this.boundKeyDown = this.onKeyDown.bind(this);
|
||||||
|
this.boundPendingMouseMove = this.onPendingMouseMove.bind(this);
|
||||||
|
this.boundPendingMouseUp = this.onPendingMouseUp.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -65,8 +69,26 @@ export class DragDropHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
// Don't drag if clicking on interactive elements
|
||||||
this.startDrag(e, element, widget, onDragEnd, widgets);
|
const interactiveElements = 'input, button, select, textarea, a, [contenteditable="true"]';
|
||||||
|
if (e.target.closest(interactiveElements)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store pending drag info - wait for movement threshold before starting drag
|
||||||
|
this.mouseDragPending = {
|
||||||
|
startX: e.clientX,
|
||||||
|
startY: e.clientY,
|
||||||
|
element,
|
||||||
|
widget,
|
||||||
|
onDragEnd,
|
||||||
|
widgets,
|
||||||
|
event: e
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add temporary listeners to detect movement or mouseup
|
||||||
|
document.addEventListener('mousemove', this.boundPendingMouseMove);
|
||||||
|
document.addEventListener('mouseup', this.boundPendingMouseUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
const touchStartHandler = (e) => {
|
const touchStartHandler = (e) => {
|
||||||
@@ -75,6 +97,12 @@ export class DragDropHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't drag if touching interactive elements
|
||||||
|
const interactiveElements = 'input, button, select, textarea, a, [contenteditable="true"]';
|
||||||
|
if (e.target.closest(interactiveElements)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Delay touch drag to allow scrolling
|
// Delay touch drag to allow scrolling
|
||||||
this.touchTimer = setTimeout(() => {
|
this.touchTimer = setTimeout(() => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -199,6 +227,43 @@ export class DragDropHandler {
|
|||||||
this.updateDragPosition(touch.clientX, touch.clientY);
|
this.updateDragPosition(touch.clientX, touch.clientY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse move before drag threshold is reached
|
||||||
|
* @param {MouseEvent} e - Mouse event
|
||||||
|
*/
|
||||||
|
onPendingMouseMove(e) {
|
||||||
|
if (!this.mouseDragPending) return;
|
||||||
|
|
||||||
|
const { startX, startY, element, widget, onDragEnd, widgets } = this.mouseDragPending;
|
||||||
|
const deltaX = Math.abs(e.clientX - startX);
|
||||||
|
const deltaY = Math.abs(e.clientY - startY);
|
||||||
|
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
||||||
|
// Check if movement threshold exceeded
|
||||||
|
if (distance >= this.options.mouseMoveThreshold) {
|
||||||
|
// Clean up pending listeners
|
||||||
|
document.removeEventListener('mousemove', this.boundPendingMouseMove);
|
||||||
|
document.removeEventListener('mouseup', this.boundPendingMouseUp);
|
||||||
|
|
||||||
|
// Start actual drag
|
||||||
|
this.startDrag(this.mouseDragPending.event, element, widget, onDragEnd, widgets);
|
||||||
|
this.mouseDragPending = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle mouse up before drag threshold is reached (click, not drag)
|
||||||
|
* @param {MouseEvent} e - Mouse event
|
||||||
|
*/
|
||||||
|
onPendingMouseUp(e) {
|
||||||
|
if (!this.mouseDragPending) return;
|
||||||
|
|
||||||
|
// Clean up pending listeners - this was a click, not a drag
|
||||||
|
document.removeEventListener('mousemove', this.boundPendingMouseMove);
|
||||||
|
document.removeEventListener('mouseup', this.boundPendingMouseUp);
|
||||||
|
this.mouseDragPending = null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update drag position and visual feedback
|
* Update drag position and visual feedback
|
||||||
* @param {number} clientX - Pointer X coordinate
|
* @param {number} clientX - Pointer X coordinate
|
||||||
@@ -338,6 +403,8 @@ export class DragDropHandler {
|
|||||||
document.removeEventListener('touchmove', this.boundTouchMove);
|
document.removeEventListener('touchmove', this.boundTouchMove);
|
||||||
document.removeEventListener('touchend', this.boundTouchEnd);
|
document.removeEventListener('touchend', this.boundTouchEnd);
|
||||||
document.removeEventListener('keydown', this.boundKeyDown);
|
document.removeEventListener('keydown', this.boundKeyDown);
|
||||||
|
document.removeEventListener('mousemove', this.boundPendingMouseMove);
|
||||||
|
document.removeEventListener('mouseup', this.boundPendingMouseUp);
|
||||||
|
|
||||||
// Clear touch timer
|
// Clear touch timer
|
||||||
if (this.touchTimer) {
|
if (this.touchTimer) {
|
||||||
@@ -345,6 +412,9 @@ export class DragDropHandler {
|
|||||||
this.touchTimer = null;
|
this.touchTimer = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clear pending drag state
|
||||||
|
this.mouseDragPending = null;
|
||||||
|
|
||||||
this.dragState = null;
|
this.dragState = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user