fix: adjusted drag and drop
This commit is contained in:
@@ -0,0 +1,154 @@
|
||||
import { computed, onMounted, onUnmounted, ref, shallowRef } from 'vue';
|
||||
import type { TicketModel } from '@/types/models/ticket-model';
|
||||
|
||||
/** Pointer capture keeps mouse, pen and touch drags on the same code path. */
|
||||
export function useBoardDrag(options: {
|
||||
canStart: (ticket: TicketModel) => boolean;
|
||||
drop: (ticket: TicketModel, columnId: number) => void;
|
||||
}) {
|
||||
const ticket = shallowRef<TicketModel | null>(null);
|
||||
const active = ref(false);
|
||||
const x = ref(0);
|
||||
const y = ref(0);
|
||||
const columnId = ref<number | null>(null);
|
||||
let pointerId: number | null = null;
|
||||
let handle: HTMLElement | null = null;
|
||||
let startX = 0;
|
||||
let startY = 0;
|
||||
let frame = 0;
|
||||
|
||||
const draggedTicket = computed(() => (active.value ? ticket.value : null));
|
||||
|
||||
function locateTarget(): void {
|
||||
const target = document
|
||||
.elementFromPoint(x.value, y.value)
|
||||
?.closest<HTMLElement>('[data-board-drop-column]');
|
||||
columnId.value = target ? Number(target.dataset.boardDropColumn) : null;
|
||||
}
|
||||
|
||||
function scroll(): void {
|
||||
if (!active.value) {
|
||||
return;
|
||||
}
|
||||
const element = document.elementFromPoint(x.value, y.value);
|
||||
const region = element?.closest<HTMLElement>(
|
||||
'[data-board-drag-scroll]',
|
||||
);
|
||||
if (region) {
|
||||
const bounds = region.getBoundingClientRect();
|
||||
const left = Math.max(0, bounds.left);
|
||||
const right = Math.min(window.innerWidth, bounds.right);
|
||||
const delta =
|
||||
x.value < left + 44 ? -10 : x.value > right - 44 ? 10 : 0;
|
||||
region.scrollLeft += delta;
|
||||
}
|
||||
if (!element?.closest('[data-board-mobile-targets]')) {
|
||||
const delta =
|
||||
y.value < 64 ? -10 : y.value > window.innerHeight - 64 ? 10 : 0;
|
||||
window.scrollBy(0, delta);
|
||||
}
|
||||
locateTarget();
|
||||
frame = requestAnimationFrame(scroll);
|
||||
}
|
||||
|
||||
function cancel(): void {
|
||||
const previousPointer = pointerId;
|
||||
pointerId = null;
|
||||
active.value = false;
|
||||
ticket.value = null;
|
||||
columnId.value = null;
|
||||
cancelAnimationFrame(frame);
|
||||
if (
|
||||
previousPointer !== null &&
|
||||
handle?.hasPointerCapture(previousPointer)
|
||||
) {
|
||||
handle.releasePointerCapture(previousPointer);
|
||||
}
|
||||
handle = null;
|
||||
}
|
||||
|
||||
function start(event: PointerEvent, source: TicketModel): void {
|
||||
if (
|
||||
pointerId !== null ||
|
||||
!event.isPrimary ||
|
||||
event.button !== 0 ||
|
||||
!options.canStart(source)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
handle = event.currentTarget as HTMLElement;
|
||||
handle.focus({ preventScroll: true });
|
||||
handle.setPointerCapture(event.pointerId);
|
||||
pointerId = event.pointerId;
|
||||
ticket.value = source;
|
||||
startX = x.value = event.clientX;
|
||||
startY = y.value = event.clientY;
|
||||
}
|
||||
|
||||
function move(event: PointerEvent): void {
|
||||
if (event.pointerId !== pointerId) {
|
||||
return;
|
||||
}
|
||||
x.value = event.clientX;
|
||||
y.value = event.clientY;
|
||||
if (
|
||||
!active.value &&
|
||||
Math.hypot(x.value - startX, y.value - startY) >= 6
|
||||
) {
|
||||
active.value = true;
|
||||
frame = requestAnimationFrame(scroll);
|
||||
}
|
||||
if (active.value) {
|
||||
event.preventDefault();
|
||||
locateTarget();
|
||||
}
|
||||
}
|
||||
|
||||
function finish(event: PointerEvent): void {
|
||||
if (event.pointerId !== pointerId) {
|
||||
return;
|
||||
}
|
||||
x.value = event.clientX;
|
||||
y.value = event.clientY;
|
||||
locateTarget();
|
||||
const source = draggedTicket.value;
|
||||
const target = columnId.value;
|
||||
cancel();
|
||||
if (source && target !== null) {
|
||||
options.drop(source, target);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelPointer(event: PointerEvent): void {
|
||||
if (event.pointerId === pointerId) {
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
function keydown(event: KeyboardEvent): void {
|
||||
if (event.key === 'Escape' && pointerId !== null) {
|
||||
event.preventDefault();
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
document.addEventListener('pointermove', move, { passive: false });
|
||||
document.addEventListener('pointerup', finish);
|
||||
document.addEventListener('pointercancel', cancelPointer);
|
||||
document.addEventListener('lostpointercapture', cancelPointer);
|
||||
document.addEventListener('keydown', keydown);
|
||||
window.addEventListener('blur', cancel);
|
||||
});
|
||||
onUnmounted(() => {
|
||||
cancel();
|
||||
document.removeEventListener('pointermove', move);
|
||||
document.removeEventListener('pointerup', finish);
|
||||
document.removeEventListener('pointercancel', cancelPointer);
|
||||
document.removeEventListener('lostpointercapture', cancelPointer);
|
||||
document.removeEventListener('keydown', keydown);
|
||||
window.removeEventListener('blur', cancel);
|
||||
});
|
||||
|
||||
return { draggedTicket, columnId, x, y, start, cancel };
|
||||
}
|
||||
Reference in New Issue
Block a user