Vibro call-back for drag & Drop (does not work in Firefox). New chart - Number of Workouts.

This commit is contained in:
AG
2025-12-13 00:30:12 +02:00
parent f169c7c4d3
commit dbb4beb56d
4 changed files with 256 additions and 22 deletions

View File

@@ -68,12 +68,67 @@ const SortablePlanStep: React.FC<SortablePlanStepProps> = ({ step, index, toggle
position: 'relative' as 'relative',
};
const handlePointerDown = (e: React.PointerEvent) => {
listeners?.onPointerDown?.(e);
// Only trigger vibration for touch input (long press logic)
if (e.pointerType === 'touch') {
const startTime = Date.now();
// Use pattern [0, 300, 50] to vibrate after 300ms delay, triggered synchronously by user gesture
// This works around Firefox Android blocking async vibrate calls
if (typeof navigator !== 'undefined' && navigator.vibrate) {
try {
navigator.vibrate([0, 300, 50]);
} catch (err) {
// Ignore potential errors if vibrate is blocked or invalid
}
}
// Cleanup / Cancel logic
const cancelVibration = () => {
// Only cancel if less than 300ms has passed (meaning we aborted the long press)
// If > 300ms, the vibration (50ms) is either playing or done, we let it finish.
if (Date.now() - startTime < 300) {
if (typeof navigator !== 'undefined' && navigator.vibrate) {
navigator.vibrate(0);
}
}
cleanup();
};
const startX = e.clientX;
const startY = e.clientY;
const onMove = (me: PointerEvent) => {
const diff = Math.hypot(me.clientX - startX, me.clientY - startY);
if (diff > 10) { // 10px tolerance
cancelVibration();
}
};
const cleanup = () => {
window.removeEventListener('pointermove', onMove);
window.removeEventListener('pointerup', cancelVibration);
window.removeEventListener('pointercancel', cancelVibration);
};
window.addEventListener('pointermove', onMove);
window.addEventListener('pointerup', cancelVibration);
window.addEventListener('pointercancel', cancelVibration);
}
};
return (
<div ref={setNodeRef} style={style} {...attributes}>
<Card
className={`flex items-center gap-3 transition-all hover:bg-surface-container-high ${isDragging ? 'bg-surface-container-high shadow-elevation-3' : ''}`}
>
<div className="text-on-surface-variant p-1 cursor-grab touch-none" {...listeners}>
<div
className="text-on-surface-variant p-1 cursor-grab touch-none"
{...listeners}
onPointerDown={handlePointerDown}
>
<GripVertical size={20} />
</div>
@@ -136,17 +191,19 @@ const Plans: React.FC<PlansProps> = ({ lang }) => {
// Dnd Sensors
const sensors = useSensors(
useSensor(PointerSensor), // Handle mouse and basic pointer events
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
useSensor(MouseSensor, {
activationConstraint: {
distance: 10,
},
}),
useSensor(TouchSensor, {
// Small delay or tolerance can help distinguish scrolling from dragging,
// but usually for a handle drag, instant is fine or defaults work.
// Let's add a small activation constraint to prevent accidental drags while scrolling if picking by handle
activationConstraint: {
distance: 5,
delay: 300,
tolerance: 5,
},
}),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
@@ -288,6 +345,16 @@ const Plans: React.FC<PlansProps> = ({ lang }) => {
setSteps(steps.filter(s => s.id !== stepId));
};
/* Vibration handled in SortablePlanStep locally for better touch support */
/*
const handleDragStart = () => {
console.log('handleDragStart called');
if (typeof navigator !== 'undefined' && navigator.vibrate) {
navigator.vibrate(50);
}
};
*/
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;