75 lines
2.5 KiB
TypeScript
75 lines
2.5 KiB
TypeScript
export type DraggableList = {
|
|
id: string
|
|
folder_id: string | null
|
|
is_inbox: boolean
|
|
}
|
|
|
|
export type ListDragPointer = {
|
|
pointerId: number
|
|
clientX: number
|
|
clientY: number
|
|
captureTarget: Element | null
|
|
}
|
|
|
|
export function snapshotListDragPointer(event: PointerEvent): ListDragPointer {
|
|
return {
|
|
pointerId: event.pointerId,
|
|
clientX: event.clientX,
|
|
clientY: event.clientY,
|
|
captureTarget: event.currentTarget as Element | null,
|
|
}
|
|
}
|
|
|
|
export function captureListDragPointer(pointer: ListDragPointer) {
|
|
try { pointer.captureTarget?.setPointerCapture(pointer.pointerId) } catch { /* synthetic events */ }
|
|
}
|
|
|
|
export function hasExceededLongPressMovement(
|
|
start: { x: number; y: number },
|
|
current: { x: number; y: number },
|
|
threshold: number,
|
|
) {
|
|
return Math.hypot(current.x - start.x, current.y - start.y) > threshold
|
|
}
|
|
|
|
export function getAdjacentListMove<T extends DraggableList>(
|
|
items: T[],
|
|
sourceId: string,
|
|
direction: 'up' | 'down',
|
|
): { targetId: string; placement: 'before' | 'after' } | null {
|
|
const source = items.find((item) => item.id === sourceId)
|
|
if (!source || source.is_inbox) return null
|
|
const scope = items.filter((item) => !item.is_inbox && item.folder_id === source.folder_id)
|
|
const index = scope.findIndex((item) => item.id === sourceId)
|
|
const target = scope[index + (direction === 'up' ? -1 : 1)]
|
|
if (!target) return null
|
|
return { targetId: target.id, placement: direction === 'up' ? 'before' : 'after' }
|
|
}
|
|
|
|
export function moveListToScope<T extends DraggableList>(
|
|
items: T[],
|
|
sourceId: string,
|
|
folderId: string | null,
|
|
targetId?: string,
|
|
placement: 'before' | 'after' = 'after',
|
|
) {
|
|
const source = items.find((item) => item.id === sourceId)
|
|
if (!source || source.is_inbox) return { items, orderedIds: [] as string[] }
|
|
|
|
const withoutSource = items.filter((item) => item.id !== sourceId)
|
|
const moved = { ...source, folder_id: folderId }
|
|
const scope = withoutSource.filter((item) => !item.is_inbox && item.folder_id === folderId)
|
|
const targetIndex = targetId ? scope.findIndex((item) => item.id === targetId) : -1
|
|
const insertAt = targetIndex < 0 ? scope.length : targetIndex + (placement === 'after' ? 1 : 0)
|
|
scope.splice(insertAt, 0, moved)
|
|
|
|
const iterator = scope[Symbol.iterator]()
|
|
const next = withoutSource.map((item) => {
|
|
if (item.is_inbox || item.folder_id !== folderId) return item
|
|
return iterator.next().value!
|
|
})
|
|
const remaining = [...iterator]
|
|
if (remaining.length) next.push(...remaining)
|
|
return { items: next, orderedIds: scope.map((item) => item.id) }
|
|
}
|