feat: organize lists with drag and drop
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
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) }
|
||||
}
|
||||
Reference in New Issue
Block a user