Files
dodo/frontend/src/lib/list-drag.ts
T
bboysoul 0e291ddb9c
ci / gitleaks (push) Successful in 7s
ci / docker (push) Successful in 3m22s
feat: organize lists with drag and drop
2026-09-09 20:28:29 +08:00

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) }
}