diff --git a/packages/core/components/DragDropContext/NestedDroppablePlugin.ts b/packages/core/components/DragDropContext/NestedDroppablePlugin.ts new file mode 100644 index 000000000..b3ffac1f6 --- /dev/null +++ b/packages/core/components/DragDropContext/NestedDroppablePlugin.ts @@ -0,0 +1,202 @@ +import { DragDropManager } from "@dnd-kit/dom"; +import { Plugin } from "@dnd-kit/abstract"; + +import type { Droppable } from "@dnd-kit/dom"; + +import { effects } from "../../../../../dnd-kit/packages/state/dist"; +import { BoundingRectangle } from "@dnd-kit/geometry"; +import { throttle } from "../../lib/throttle"; + +interface Position { + x: number; + y: number; +} + +function isPositionInsideRect( + position: Position, + rect: BoundingRectangle +): boolean { + return ( + position.x >= rect.left && + position.x <= rect.right && + position.y >= rect.top && + position.y <= rect.bottom + ); +} + +type NestedDroppablePluginOptions = { + onChange: (params: { + deepestAreaId: string | null; + deepestZoneId: string | null; + }) => void; +}; + +// Something is going wrong with tearing down the classes. +// This is an awful hack to prevent the plugin from running more than once. +let globallyRegistered = false; + +// Pixels to buffer sortable items by, helping when +// 2 items are butted up against each-other +const BUFFER_ZONE = 8; + +// Force shapes to refresh on mouse move. +// TODO Expensive - can I remove this? Or restrict to during drag only? +const REFRESH_ON_MOVE = true; + +const depthSort = (candidates: Droppable[]) => { + return candidates.sort((a, b) => { + if (!a.element || !b.element) return 0; + + // TODO could be swapped out for `data.depth` on the candidate for better performance + if (a.element.contains(b.element)) return -1; + + return 1; + }); +}; + +const getZoneId = (candidate: Droppable | undefined) => { + let id: string | null = candidate?.id as string; + + if (!candidate) return null; + + if (!candidate.data.zone) { + if (candidate.data.containsActiveZone) { + id = null; + } else { + id = candidate.data.group; + } + } + + return id; +}; + +const getAreaId = (candidate: Droppable) => { + if (candidate.data.containsActiveZone) { + return candidate.id as string; + } + + return null; +}; + +const getDeepestId = ( + candidates: Droppable[], + idFn: (candidate: Droppable) => string | null +) => { + let id: string | null = null; + + for (let i = 0; i < candidates.length; i++) { + const candidate = candidates[i]; + + id = idFn(candidate); + + if (id) break; + } + + return id; +}; + +const expandHitBox = (rect: BoundingRectangle): BoundingRectangle => { + return { + bottom: rect.bottom + BUFFER_ZONE, + top: rect.top - BUFFER_ZONE, + width: rect.width + BUFFER_ZONE * 2, + height: rect.height + BUFFER_ZONE * 2, + left: rect.left - BUFFER_ZONE, + right: rect.right + BUFFER_ZONE, + }; +}; + +export const createNestedDroppablePlugin = ({ + onChange, +}: NestedDroppablePluginOptions) => + class NestedDroppablePlugin extends Plugin { + constructor(manager: DragDropManager, options?: {}) { + super(manager); + + const cleanupEffect = effects(() => { + const getPointerCollisions = (position: Position) => { + const candidates: Droppable[] = []; + for (const droppable of manager.registry.droppables.value) { + if (droppable.shape) { + let rect = droppable.shape.boundingRectangle; + + const isNotSourceZone = + droppable.id !== + (manager.dragOperation.source?.data.group || + manager.dragOperation.source?.id); + + const isNotTargetZone = + droppable.id !== + (manager.dragOperation.source?.data.group || + manager.dragOperation.source?.id); + + // Expand hitboxes on zones + if (droppable.data.zone && isNotSourceZone && isNotTargetZone) { + rect = expandHitBox(rect); + } + + if (isPositionInsideRect(position, rect)) { + candidates.push(droppable); + } + } + } + + return candidates; + }; + + const handleMove = (position: Position) => { + if (REFRESH_ON_MOVE) { + for (const droppable of manager.registry.droppables.value) { + droppable.refreshShape(); + } + } + + const candidates = getPointerCollisions(position); + + if (candidates.length > 0) { + const sortedCandidates = depthSort(candidates); + + const draggedCandidateIndex = sortedCandidates.findIndex( + (candidate) => candidate.id === manager.dragOperation.source?.id + ); + + const nonDraggedCandidates = + draggedCandidateIndex > -1 + ? sortedCandidates.slice(0, draggedCandidateIndex) + : sortedCandidates; + + nonDraggedCandidates.reverse(); + + const deepestZoneId = getZoneId(nonDraggedCandidates[0]); + const deepestAreaId = getDeepestId(nonDraggedCandidates, getAreaId); + + onChange({ deepestZoneId, deepestAreaId }); + } + }; + + const handleMoveThrottled = throttle(handleMove, 50); + + const handlePointerMove = (event: PointerEvent) => { + handleMoveThrottled({ + x: event.clientX, + y: event.clientY, + }); + }; + + // For some reason, this is getting instantiated multiple times. Hack to avoid it. + if (globallyRegistered) { + return; + } + + document.body.addEventListener("pointermove", handlePointerMove); + + globallyRegistered = true; + + this.destroy = () => { + globallyRegistered = false; + document.body.removeEventListener("pointermove", handlePointerMove); + cleanupEffect(); + }; + }); + } + }; diff --git a/packages/core/components/DragDropContext/index.tsx b/packages/core/components/DragDropContext/index.tsx index cc3b7ab20..373d2021f 100644 --- a/packages/core/components/DragDropContext/index.tsx +++ b/packages/core/components/DragDropContext/index.tsx @@ -18,6 +18,7 @@ import { getItem, ItemSelector } from "../../lib/get-item"; import { PathData } from "../DropZone/context"; import { getZoneId } from "../../lib/get-zone-id"; import { Direction } from "../DraggableComponent/collision/dynamic"; +import { createNestedDroppablePlugin } from "./NestedDroppablePlugin"; type Events = DragDropEvents; type DragCbs = Partial<{ [eventName in keyof Events]: Events[eventName][] }>; @@ -51,7 +52,23 @@ export function useDragListener( export const DragDropContext = ({ children }: { children: ReactNode }) => { const { state, config, deferred, dispatch } = useAppContext(); const { data } = deferred?.isDeferred ? deferred.state : state; - const [manager] = useState(new DragDropManager({ plugins: [Feedback] })); + const [deepest, setDeepest] = useState<{ + zone: string | null; + area: string | null; + } | null>(null); + + const [manager] = useState( + new DragDropManager({ + plugins: [ + Feedback, + createNestedDroppablePlugin({ + onChange: ({ deepestZoneId, deepestAreaId }) => { + setDeepest({ zone: deepestZoneId, area: deepestAreaId }); + }, + }), + ], + }) + ); const [draggedItem, setDraggedItem] = useState(); @@ -249,6 +266,8 @@ export const DragDropContext = ({ children }: { children: ReactNode }) => { collisionPriority: 1, registerPath, pathData, + deepestZone: deepest?.zone, + deepestArea: deepest?.area, }} > {children} diff --git a/packages/core/components/DraggableComponent/index.tsx b/packages/core/components/DraggableComponent/index.tsx index fd2b604fb..7e4e6cb06 100644 --- a/packages/core/components/DraggableComponent/index.tsx +++ b/packages/core/components/DraggableComponent/index.tsx @@ -40,7 +40,6 @@ export const DraggableComponent = ({ isSelected = false, debug, label, - indicativeHover = false, isEnabled, dragAxis, inDroppableZone = true, @@ -56,7 +55,6 @@ export const DraggableComponent = ({ label?: string; isLoading: boolean; isEnabled?: boolean; - indicativeHover?: boolean; dragAxis: DragAxis; inDroppableZone: boolean; }) => { @@ -66,23 +64,6 @@ export const DraggableComponent = ({ const overlayRef = useRef(null); - const { ref: sortableRef, status } = useSortable({ - id, - index, - group: zoneCompound, - data: { group: zoneCompound, index, componentType }, - collisionPriority: isEnabled ? collisionPriority : 0, - collisionDetector: createDynamicCollisionDetector(dragAxis), - disabled: !isEnabled, - // handle: overlayRef, - }); - - const userIsDragging = !!ctx?.draggedItem; - - const thisIsDragging = status === "dragging"; - - const ref = useRef(); - const [localZones, setLocalZones] = useState>({}); // TODO 26/08/24 this doesn't work when we have more than one level of zone @@ -105,6 +86,28 @@ export const DraggableComponent = ({ const containsActiveZone = Object.values(localZones).filter(Boolean).length > 0; + const { ref: sortableRef, status } = useSortable({ + id, + index, + group: zoneCompound, + data: { + group: zoneCompound, + index, + componentType, + containsActiveZone, + }, + collisionPriority: isEnabled ? collisionPriority : 0, + collisionDetector: createDynamicCollisionDetector(dragAxis), + disabled: !isEnabled, + // handle: overlayRef, + }); + + const userIsDragging = !!ctx?.draggedItem; + + const thisIsDragging = status === "dragging"; + + const ref = useRef(); + const refSetter = useCallback( (el: Element | null) => { sortableRef(el); @@ -199,17 +202,7 @@ export const DraggableComponent = ({ const [hover, setHover] = useState(false); - const activateParent = useCallback(() => { - if (inDroppableZone) { - if (ctx?.setHoveringArea) { - ctx.setHoveringArea(ctx.areaId || ""); - } - - if (ctx?.setHoveringZone) { - ctx.setHoveringZone(zoneCompound); - } - } - }, [inDroppableZone, ctx, zoneCompound]); + const indicativeHover = ctx?.hoveringComponent === id; useEffect(() => { if (!ref.current) { @@ -231,10 +224,6 @@ export const DraggableComponent = ({ } e.stopPropagation(); - - if (!containsActiveZone) { - activateParent(); - } }; const _onMouseOut = (e: Event) => { @@ -389,27 +378,6 @@ export const DraggableComponent = ({
- - {ctx?.hoveringArea === id && ( - <> -
-
-
-
- - )}
, document.getElementById("puck-preview") || document.body )} diff --git a/packages/core/components/DropZone/context.tsx b/packages/core/components/DropZone/context.tsx index cec7b641c..4d9360fbb 100644 --- a/packages/core/components/DropZone/context.tsx +++ b/packages/core/components/DropZone/context.tsx @@ -1,18 +1,7 @@ -import { - CSSProperties, - ReactNode, - SetStateAction, - createContext, - useCallback, - useState, -} from "react"; +import { ReactNode, createContext, useCallback, useState } from "react"; import { Config, Data } from "../../types/Config"; -import { DragStart, DragUpdate } from "@measured/dnd"; -import { ItemSelector, getItem } from "../../lib/get-item"; +import { ItemSelector } from "../../lib/get-item"; import { PuckAction } from "../../reducer"; -import { rootDroppableId } from "../../lib/root-droppable-id"; -import { useDebounce } from "use-debounce"; -import { getZoneId } from "../../lib/get-zone-id"; import type { Draggable } from "@dnd-kit/dom"; export type PathData = Record; @@ -28,11 +17,6 @@ export type DropZoneContext = { zoneCompound?: string; index?: number; draggedItem?: Draggable | null; - placeholderStyle?: CSSProperties; - hoveringArea?: string | null; - setHoveringArea?: (area: string | null) => void; - hoveringZone?: string | null; - setHoveringZone?: (zone: string | null) => void; hoveringComponent?: string | null; setHoveringComponent?: (id: string | null) => void; registerZoneArea?: (areaId: string) => void; @@ -43,10 +27,10 @@ export type DropZoneContext = { pathData?: PathData; registerPath?: (selector: ItemSelector) => void; mode?: "edit" | "render"; - zoneWillDrag?: string; - setZoneWillDrag?: (zone: string) => void; collisionPriority: number; registerLocalZone?: (zone: string, active: boolean) => void; // A zone as it pertains to the current area + deepestZone?: string | null; + deepestArea?: string | null; } | null; export const dropZoneContext = createContext(null); @@ -58,16 +42,9 @@ export const DropZoneProvider = ({ children: ReactNode; value: DropZoneContext; }) => { - const [hoveringArea, setHoveringArea] = useState(null); - const [hoveringZone, setHoveringZone] = useState( - rootDroppableId - ); - // Hovering component may match area, but areas must always contain zones const [hoveringComponent, setHoveringComponent] = useState(); - const [hoveringAreaDb] = useDebounce(hoveringArea, 75, { leading: false }); - const [areasWithZones, setAreasWithZones] = useState>( {} ); @@ -118,17 +95,11 @@ export const DropZoneProvider = ({ [setActiveZones, dispatch] ); - const [zoneWillDrag, setZoneWillDrag] = useState(""); - return ( <> {value && ( diff --git a/packages/core/components/DropZone/index.tsx b/packages/core/components/DropZone/index.tsx index e4bb94d9d..387b74d02 100644 --- a/packages/core/components/DropZone/index.tsx +++ b/packages/core/components/DropZone/index.tsx @@ -6,12 +6,11 @@ import { rootDroppableId } from "../../lib/root-droppable-id"; import { getClassNameFactory } from "../../lib"; import styles from "./styles.module.css"; import { DropZoneProvider, dropZoneContext } from "./context"; -import { getZoneId } from "../../lib/get-zone-id"; import { useAppContext } from "../Puck/context"; import { DropZoneProps } from "./types"; import { ComponentConfig, PuckContext } from "../../types/Config"; -import { useDroppable } from "@dnd-kit/react"; +import { useDroppable, UseDroppableInput } from "@dnd-kit/react"; import { DrawerItemInner } from "../Drawer"; import { pointerIntersection } from "@dnd-kit/collision"; import { insert } from "../../lib/insert"; @@ -38,12 +37,7 @@ function DropZoneEdit({ config, areaId, draggedItem, - placeholderStyle, registerZoneArea, - areasWithZones, - hoveringComponent, - zoneWillDrag, - setZoneWillDrag = () => null, collisionPriority, registerLocalZone, } = ctx! || {}; @@ -87,25 +81,16 @@ function DropZoneEdit({ const ref = useRef(); - const { - hoveringArea = "root", - setHoveringArea, - hoveringZone, - setHoveringZone, - setHoveringComponent, - } = ctx!; + const { deepestArea = "root" } = ctx!; const isDroppableTarget = useCallback(() => { if (!isDragging) { - // console.log("not dragging"); return true; } const { componentType } = draggedItem.data; if (disallow) { - // console.log("has disallow"); - const defaultedAllow = allow || []; // remove any explicitly allowed items from disallow @@ -114,15 +99,10 @@ function DropZoneEdit({ ); if (filteredDisallow.indexOf(componentType) !== -1) { - // console.log("dragged item is disallowed"); - return false; } } else if (allow) { - // console.log("has allow"); if (allow.indexOf(componentType) === -1) { - // console.log("dragged item is not allowed"); - return false; } } @@ -132,40 +112,6 @@ function DropZoneEdit({ return true; }, [draggedItem]); - // Don't combine inline event handlers and event listeners, as they don't bubble together - useEffect(() => { - if (!ref.current) return; - - const onMouseOver = (e: Event) => { - if (!setHoveringArea || !setHoveringZone) return; - - // Eject if this zone isn't droppable for the item - // console.log( - // `${zoneCompound} (hover) isDroppableTarget ${isDroppableTarget()}` - // ); - if (!isDroppableTarget()) { - console.log(`${zoneCompound} is not a droppable target`); - - // setHoveringArea(zoneArea); - - return; - } - - // e.stopPropagation(); - - // console.log("dz", e.currentTarget, e.target, areaId, zoneCompound); - - setHoveringArea(areaId || zoneArea); - setHoveringZone(zoneCompound); - }; - - ref.current.addEventListener("mouseover", onMouseOver); - - return () => { - ref.current?.removeEventListener("mouseover", onMouseOver); - }; - }, [ref, isDroppableTarget]); - useEffect(() => { if (registerLocalZone) { registerLocalZone(zoneCompound, isDroppableTarget()); @@ -177,22 +123,9 @@ function DropZoneEdit({ zone === rootDroppableId || areaId === "root"; - // const draggedSourceId = draggedItem && draggedItem.source.droppableId; - // const draggedDestinationId = - // draggedItem && draggedItem.destination?.droppableId; - const [zoneArea] = getZoneId(zoneCompound); - - // we use the index rather than spread to prevent down-level iteration warnings: https://stackoverflow.com/questions/53441292/why-downleveliteration-is-not-on-by-default - // const [draggedSourceArea] = getZoneId(draggedSourceId); - - const userWillDrag = zoneWillDrag === zone; - - const hoveringOverArea = hoveringArea ? hoveringArea === areaId : isRootZone; - const hoveringOverZone = hoveringZone === zoneCompound; + const hoveringOverArea = deepestArea ? deepestArea === areaId : isRootZone; const userIsDragging = !!draggedItem; - const draggingOverArea = userIsDragging && hoveringOverArea; - const draggingNewComponent = false; //draggedSourceId?.startsWith("component-list"); // if ( // !ctx?.config || @@ -209,7 +142,7 @@ function DropZoneEdit({ let isEnabled = true; if (draggedItem) { - isEnabled = hoveringOverArea && hoveringOverZone; + isEnabled = ctx?.deepestZone === zoneCompound; } if (isEnabled) { @@ -218,15 +151,18 @@ function DropZoneEdit({ const isDropEnabled = isEnabled && content.length === 0; - const { ref: dropRef } = useDroppable({ + const droppableConfig: UseDroppableInput = { id: zoneCompound, collisionPriority: isEnabled ? collisionPriority : 0, disabled: !isDropEnabled, collisionDetector: pointerIntersection, data: { zone: true, + areaId, }, - }); + }; + + const { ref: dropRef } = useDroppable(droppableConfig); const selectedItem = itemSelector ? getItem(itemSelector, data) : null; const isAreaSelected = selectedItem && areaId === selectedItem.props.id; @@ -265,11 +201,8 @@ function DropZoneEdit({ className={`${getClassName({ isRootZone, userIsDragging, - draggingOverArea, - // hoveringOverArea, - draggingNewComponent, - // isDestination: draggedDestinationId === zoneCompound, - isDisabled: !isEnabled, + hoveringOverArea, + isEnabled, isAreaSelected, hasChildren: content.length > 0, })}${className ? ` ${className}` : ""}`} @@ -280,9 +213,9 @@ function DropZoneEdit({ // Luckily, during a drag, isDropEnabled === false for this item, so we can // remove the ref. // TODO see if there's a fix for this upstream - if (isDropEnabled) { - dropRef(node); - } + // if (isDropEnabled) { + dropRef(node); + // } if (dragRef) dragRef(node); }} diff --git a/packages/core/components/DropZone/styles.module.css b/packages/core/components/DropZone/styles.module.css index 47104a9ca..2d603d321 100644 --- a/packages/core/components/DropZone/styles.module.css +++ b/packages/core/components/DropZone/styles.module.css @@ -7,7 +7,7 @@ } .DropZone--isAreaSelected, -.DropZone--draggingOverArea:not(.DropZone--isRootZone) { +.DropZone--hoveringOverArea:not(.DropZone--isRootZone) { background: color-mix(in srgb, var(--puck-color-azure-09) 30%, transparent); outline: 2px dashed var(--puck-color-azure-08); } @@ -40,3 +40,7 @@ width: 100%; z-index: 1; } + +.DropZone--isEnabled.DropZone--userIsDragging { + outline: 2px dashed var(--puck-color-azure-06); +} diff --git a/packages/core/components/LayerTree/index.tsx b/packages/core/components/LayerTree/index.tsx index 113e0a27c..58cc7ceec 100644 --- a/packages/core/components/LayerTree/index.tsx +++ b/packages/core/components/LayerTree/index.tsx @@ -58,11 +58,8 @@ export const LayerTree = ({ const zonesForItem = findZonesForArea(data, item.props.id); const containsZone = Object.keys(zonesForItem).length > 0; - const { - setHoveringArea = () => {}, - setHoveringComponent = () => {}, - hoveringComponent, - } = ctx || {}; + const { setHoveringComponent = () => {}, hoveringComponent } = + ctx || {}; const selectedItem = itemSelector && data ? getItem(itemSelector, data) : null; @@ -111,12 +108,10 @@ export const LayerTree = ({ }} onMouseOver={(e) => { e.stopPropagation(); - setHoveringArea(item.props.id); setHoveringComponent(item.props.id); }} onMouseOut={(e) => { e.stopPropagation(); - setHoveringArea(null); setHoveringComponent(null); }} > diff --git a/packages/core/components/Puck/index.tsx b/packages/core/components/Puck/index.tsx index 4b82fb451..a44bcbbbf 100644 --- a/packages/core/components/Puck/index.tsx +++ b/packages/core/components/Puck/index.tsx @@ -7,13 +7,11 @@ import { useReducer, useState, } from "react"; -import { DragStart, DragUpdate } from "@measured/dnd"; import type { AppState, Config, Data, UiState } from "../../types/Config"; import { Button } from "../Button"; import { Plugin } from "../../types/Plugin"; -import { usePlaceholderStyle } from "../../lib/use-placeholder-style"; import { SidebarSection } from "../SidebarSection"; import { @@ -25,8 +23,7 @@ import { } from "lucide-react"; import { Heading } from "../Heading"; import { IconButton } from "../IconButton/IconButton"; -import { DropZoneProvider } from "../DropZone"; -import { ItemSelector, getItem } from "../../lib/get-item"; +import { getItem } from "../../lib/get-item"; import { PuckAction, StateReducer, createReducer } from "../../reducer"; import { flushZones } from "../../lib/flush-zones"; import getClassNameFactory from "../../lib/get-class-name-factory"; @@ -47,12 +44,7 @@ import { defaultViewports } from "../ViewportControls/default-viewports"; import { Viewports } from "../../types/Viewports"; import { DragDropContext } from "../DragDropContext"; import { IframeConfig } from "../../types/IframeConfig"; -import { DragDropProvider, useDragDropManager } from "@dnd-kit/react"; import { DragDropManager, Feedback } from "@dnd-kit/dom"; -import type { Draggable } from "@dnd-kit/dom"; -import { setupZone } from "../../lib/setup-zone"; -import { rootDroppableId } from "../../lib/root-droppable-id"; -import { generateId } from "../../lib/generate-id"; import { useInjectGlobalCss } from "../../lib/use-inject-css"; import { useDeferred } from "../../lib/use-deferred"; @@ -230,27 +222,12 @@ export function Puck({ const { itemSelector, leftSideBarVisible, rightSideBarVisible } = ui; - const setItemSelector = useCallback( - (newItemSelector: ItemSelector | null) => { - if (newItemSelector === itemSelector) return; - - dispatch({ - type: "setUi", - ui: { itemSelector: newItemSelector }, - recordHistory: true, - }); - }, - [itemSelector] - ); - const selectedItem = itemSelector ? getItem(itemSelector, data) : null; useEffect(() => { if (onChange) onChange(data); }, [data]); - const { onDragStartOrUpdate, placeholderStyle } = usePlaceholderStyle(); - // DEPRECATED const rootProps = data.root.props || data.root; diff --git a/packages/core/lib/throttle.ts b/packages/core/lib/throttle.ts new file mode 100644 index 000000000..a23d2bce4 --- /dev/null +++ b/packages/core/lib/throttle.ts @@ -0,0 +1,32 @@ +export function timeout(callback: () => void, duration: number): () => void { + const id = setTimeout(callback, duration); + + return () => clearTimeout(id); +} + +export function throttle any>( + func: T, + limit: number +): (...args: Parameters) => void { + const time = () => performance.now(); + let cancel: (() => void) | undefined; + let lastRan = 0; // Start with 0 to indicate it hasn't run yet + + return function (this: any, ...args: Parameters) { + const now = time(); + const context = this; + + if (now - lastRan >= limit) { + // If enough time has passed, run the function immediately + func.apply(context, args); + lastRan = now; + } else { + // Otherwise, schedule it to run after the remaining time + cancel?.(); // Cancel any previously scheduled call + cancel = timeout(() => { + func.apply(context, args); + lastRan = time(); + }, limit - (now - lastRan)); + } + }; +} diff --git a/packages/core/lib/use-placeholder-style.ts b/packages/core/lib/use-placeholder-style.ts deleted file mode 100644 index ef98169e2..000000000 --- a/packages/core/lib/use-placeholder-style.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { CSSProperties, useState } from "react"; -import { DragStart, DragUpdate } from "@measured/dnd"; -import { getFrame } from "./get-frame"; - -export const usePlaceholderStyle = () => { - const queryAttr = "data-rfd-drag-handle-draggable-id"; - - const [placeholderStyle, setPlaceholderStyle] = useState(); - - const onDragStartOrUpdate = ( - draggedItem: DragStart & Partial - ) => { - const draggableId = draggedItem.draggableId; - const destinationIndex = (draggedItem.destination || draggedItem.source) - .index; - const droppableId = (draggedItem.destination || draggedItem.source) - .droppableId; - - const domQuery = `[${queryAttr}='${draggableId}']`; - - const frame = getFrame(); - const draggedDOM = - document.querySelector(domQuery) || frame?.querySelector(domQuery); - - if (!draggedDOM) { - return; - } - - const targetListElement = frame?.querySelector( - `[data-rfd-droppable-id='${droppableId}']` - ); - - const { clientHeight } = draggedDOM; - - if (!targetListElement) { - return; - } - - let clientY = 0; - - const isSameDroppable = - draggedItem.source.droppableId === draggedItem.destination?.droppableId; - - if (destinationIndex > 0) { - const end = - destinationIndex > draggedItem.source.index && isSameDroppable - ? destinationIndex + 1 - : destinationIndex; - - const children = Array.from(targetListElement.children) - .filter( - (item) => - item !== draggedDOM && - item.getAttributeNames().indexOf("data-puck-placeholder") === -1 && - item - .getAttributeNames() - .indexOf("data-rfd-placeholder-context-id") === -1 - ) - .slice(0, end); - - clientY = children.reduce( - (total, item) => - total + - item.clientHeight + - parseInt(window.getComputedStyle(item).marginTop.replace("px", "")) + - parseInt( - window.getComputedStyle(item).marginBottom.replace("px", "") - ), - - 0 - ); - } - - setPlaceholderStyle({ - position: "absolute", - top: clientY, - left: 0, - height: clientHeight, - width: "100%", - }); - }; - - return { onDragStartOrUpdate, placeholderStyle }; -};