Files
penx/apps/web/lib/dnd-projection/getProjection.tsx
2025-04-30 15:01:54 +08:00

83 lines
2.0 KiB
TypeScript

import { arrayMove } from '@dnd-kit/sortable'
export type UniqueIdentifier = string
export type WithFlattenedProps<T> = T & {
parentId: string | null // parent node id
depth: number
index: number
}
type FlattenedItem = WithFlattenedProps<any>
// type FlattenedItem = any
function getDragDepth(offset: number, indentationWidth: number) {
return Math.round(offset / indentationWidth)
}
function getMaxDepth({ previousItem }: { previousItem: FlattenedItem }) {
if (previousItem) {
return previousItem.depth + 1
}
return 0
}
function getMinDepth({ nextItem }: { nextItem: FlattenedItem }) {
if (nextItem) {
return nextItem.depth
}
return 0
}
export function getProjection(
items: FlattenedItem[],
activeId: UniqueIdentifier,
overId: UniqueIdentifier,
dragOffset: number,
indentationWidth: number,
) {
const overItemIndex = items.findIndex(({ id }) => id === overId)
const activeItemIndex = items.findIndex(({ id }) => id === activeId)
const activeItem = items[activeItemIndex]
const newItems = arrayMove(items, activeItemIndex, overItemIndex)
const previousItem = newItems[overItemIndex - 1]
const nextItem = newItems[overItemIndex + 1]
const dragDepth = getDragDepth(dragOffset, indentationWidth)
const projectedDepth = activeItem.depth + dragDepth
const maxDepth = getMaxDepth({
previousItem,
})
const minDepth = getMinDepth({ nextItem })
let depth = projectedDepth
if (projectedDepth >= maxDepth) {
depth = maxDepth
} else if (projectedDepth < minDepth) {
depth = minDepth
}
return { depth, maxDepth, minDepth, parentId: getParentId() }
function getParentId() {
if (depth === 0 || !previousItem) {
return null
}
if (depth === previousItem.depth) {
return previousItem.parentId
}
if (depth > previousItem.depth) {
return previousItem.id
}
const newParent = newItems
.slice(0, overItemIndex)
.reverse()
.find((item) => item.depth === depth)?.parentId
return newParent ?? null
}
}