import { useRef } from "react"; import { DropEnterEvent, DropMoveEvent, useClipboard, useDrop, } from "@react-aria/dnd"; import { useButton } from "@react-aria/button"; import { useHover } from "@react-aria/interactions"; import { mergeProps } from "@react-aria/utils"; import { VisuallyHidden } from "@react-aria/visually-hidden"; import { isFocusable } from "@react-aria/focus"; import { DropActivateEvent, DropEvent, DropExitEvent, } from "@react-types/shared"; import { DomProps, RenderBaseProps, AriaLabelProps } from "../../../types"; import { useFocusRing } from "@hooks/useFocusRing"; import { useRenderProps } from "@hooks"; import { DropZoneWrapper } from "./DropZone.styles"; interface DropZoneRenderProps { isDisabled: boolean; isFocused: boolean; isFocusVisible: boolean; isDropTarget: boolean; } export interface DropZoneProps extends DomProps, RenderBaseProps, AriaLabelProps { /** Handler that is called when a valid drag enters the drop target. */ onDropEnter?: (e: DropEnterEvent) => void; /** Handler that is called when a valid drag is moved within the drop target. */ onDropMove?: (e: DropMoveEvent) => void; /** Handler that is called after a valid drag is held over the drop target for a period of time */ onDropActivate?: (e: DropActivateEvent) => void; /** Handler called when a valid drag exits the drop target. */ onDropExit?: (e: DropExitEvent) => void; /** Handler that is called when a valid drag is dropped on the drop target. */ onDrop?: (e: DropEvent) => void; /** Whether the DropZone is disabled. When disabled, it will not accept any drops */ isDisabled?: boolean; } /** DropZone component. Used to create an area * where one or multiple objects can be dragged and dropped */ export function DropZone(props: DropZoneProps) { const { isDisabled = false } = props; const dropZoneRef = useRef(null); const buttonRef = useRef(null); const { dropProps, dropButtonProps, isDropTarget } = useDrop({ ...props, ref: dropZoneRef, }); const { buttonProps } = useButton(dropButtonProps!, buttonRef); const { focusProps, isFocusVisible, isFocused } = useFocusRing(); const { hoverProps } = useHover(props); const { clipboardProps } = useClipboard({ isDisabled, onPaste: (items) => props.onDrop?.({ type: "drop", items, x: 0, y: 0, dropOperation: "copy", }), }); const renderProps = useRenderProps({ ...props, componentClassName: "aje-dropzone", values: { isDisabled, isDropTarget, isFocused, isFocusVisible }, selectors: { "data-drag-over": isDropTarget, "data-focused": isFocused, "data-focus-visible": isFocusVisible, "data-disabled": isDisabled, }, }); const dropZoneProps = mergeProps(dropProps, hoverProps, renderProps); return ( { let target = e.target as HTMLElement | null; while (target && dropZoneRef?.current?.contains(target)) { if (isFocusable(target)) { break; } else if (target === dropZoneRef.current) { buttonRef.current?.focus(); break; } target = target.parentElement; } }} >