formkit / drag-and-drop

https://drag-and-drop.formkit.com
MIT License
1.39k stars 25 forks source link

Classed not removed on onDragEnd #100

Closed bfourgeaud closed 5 days ago

bfourgeaud commented 5 days ago

The dropZoneClassand some other properties like z-index:9999 are not removed from the child-node when the item has finished beeing dragged. This leads to problems with items beeing displayd on top of everything when they shouldn't and the dropZoneClass is not useable as it is now.

/* draggable.tsx */

import { ComponentProps, ReactNode, useEffect, useTransition } from "react"
import { useDragAndDrop } from "@formkit/drag-and-drop/react"
import {
  animations,
  NodeDragEventData,
  NodeTouchEventData,
} from "@formkit/drag-and-drop"
import { cn } from "@/lib/utils"

type Props<T> = {
  onChange?: (data: T[]) => Promise<void> | void
  items: T[]
  children: (item: T) => ReactNode
  dragHandle?: string
  checkSame?: (curr: T[], changed: T[]) => boolean
} & Omit<ComponentProps<"div">, "children" | "onChange">

const compareArrays = <_, T>(a: T[], b: T[]) =>
  a.length === b.length && a.every((element, index) => element === b[index])

export const Draggable = <_, T>({
  onChange,
  items,
  children,
  dragHandle,
  checkSame = compareArrays,
  ...props
}: Props<T>) => {
  const [pending, startTransition] = useTransition()

  const onDragEnd = async (
    data: NodeDragEventData<T> | NodeTouchEventData<T>
  ) => {
    const parent = data.targetData.parent.el
    const newData = data.targetData.parent.data.getValues(parent)
    const isSame = checkSame(items, newData)

    !isSame && startTransition(async () => await onChange?.(newData))
  }

  const [parent, listItems, setValues, updateConfig] = useDragAndDrop<
    HTMLDivElement,
    T
  >(items, {
    dragHandle,
    plugins: [animations()],
    handleEnd: onDragEnd,
    //draggingClass: "opacity-50 bg-muted border border-blue-500",
    dropZoneClass: "opacity-50 bg-muted",
  })

  useEffect(() => {
    setValues(items)
  }, [items])

  return (
    <div
      ref={parent}
      {...props}
      className={cn(props.className, {
        "opacity-50 animate-pulse pointer-events-none": pending,
      })}
    >
      {listItems.map(children)}
    </div>
  )
}
/* itemList.tsx */

interface Props {
  items: LongueurLitItem[]
}

export function LongueurItems({ items }: Props) {
  const handleChange = async (data: LongueurLitItem[]) => {
    await reorderLongueurs(data)
    toast.success("Longueurs mises à jour")
  }

  return (
    <Draggable
      items={items}
      onChange={handleChange}
      dragHandle=".drag-handle"
      className="flex flex-col gap-2"
    >
      {(item) => <LongueurItem key={item.id} longeur={item} />}
    </Draggable>
  )
}
bfourgeaud commented 5 days ago

Ok I found the solution pointed ou in this thread !

  1. Import handleEnd as coreHandleEnd
  2. Invoke coreHandleEnd at the end of the custom handleEndfunction