Add custom drag context wrapper to inject the overlay rect on drag end
This commit is contained in:
parent
5727bade36
commit
b75db97c26
@ -1,10 +1,9 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useState } from "react";
|
||||||
import { createPortal } from "react-dom";
|
import { createPortal } from "react-dom";
|
||||||
import { Box, Flex } from "theme-ui";
|
import { Box, Flex } from "theme-ui";
|
||||||
import SimpleBar from "simplebar-react";
|
import SimpleBar from "simplebar-react";
|
||||||
import {
|
import {
|
||||||
DragOverlay,
|
DragOverlay,
|
||||||
DndContext,
|
|
||||||
MouseSensor,
|
MouseSensor,
|
||||||
TouchSensor,
|
TouchSensor,
|
||||||
KeyboardSensor,
|
KeyboardSensor,
|
||||||
@ -24,6 +23,7 @@ import usePreventSelect from "../../hooks/usePreventSelect";
|
|||||||
import { useTokenData } from "../../contexts/TokenDataContext";
|
import { useTokenData } from "../../contexts/TokenDataContext";
|
||||||
import { useAuth } from "../../contexts/AuthContext";
|
import { useAuth } from "../../contexts/AuthContext";
|
||||||
import { useMapStage } from "../../contexts/MapStageContext";
|
import { useMapStage } from "../../contexts/MapStageContext";
|
||||||
|
import DragContext from "../../contexts/DragContext";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createTokenState,
|
createTokenState,
|
||||||
@ -40,10 +40,6 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
const [dragId, setDragId] = useState();
|
const [dragId, setDragId] = useState();
|
||||||
|
|
||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
// Use a ref to the drag overlay to get it's position on dragEnd
|
|
||||||
// TODO: use active.rect when dnd-kit bug is fixed
|
|
||||||
// https://github.com/clauderic/dnd-kit/issues/238
|
|
||||||
const dragOverlayRef = useRef();
|
|
||||||
|
|
||||||
const mouseSensor = useSensor(MouseSensor, {
|
const mouseSensor = useSensor(MouseSensor, {
|
||||||
activationConstraint: { distance: 5 },
|
activationConstraint: { distance: 5 },
|
||||||
@ -61,13 +57,12 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
preventSelect();
|
preventSelect();
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleDragEnd({ active }) {
|
function handleDragEnd({ active, overlayNodeClientRect }) {
|
||||||
setDragId(null);
|
setDragId(null);
|
||||||
|
|
||||||
const mapStage = mapStageRef.current;
|
const mapStage = mapStageRef.current;
|
||||||
const dragOverlay = dragOverlayRef.current;
|
if (mapStage) {
|
||||||
if (mapStage && dragOverlay) {
|
const dragRect = overlayNodeClientRect;
|
||||||
const dragRect = dragOverlay.getBoundingClientRect();
|
|
||||||
const dragPosition = {
|
const dragPosition = {
|
||||||
x: dragRect.left + dragRect.width / 2,
|
x: dragRect.left + dragRect.width / 2,
|
||||||
y: dragRect.top + dragRect.height / 2,
|
y: dragRect.top + dragRect.height / 2,
|
||||||
@ -146,7 +141,7 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndContext
|
<DragContext
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
onDragCancel={handleDragCancel}
|
onDragCancel={handleDragCancel}
|
||||||
@ -185,24 +180,13 @@ function TokenBar({ onMapTokensStateCreate }) {
|
|||||||
<SelectTokensButton onMapTokensStateCreate={onMapTokensStateCreate} />
|
<SelectTokensButton onMapTokensStateCreate={onMapTokensStateCreate} />
|
||||||
</Flex>
|
</Flex>
|
||||||
{createPortal(
|
{createPortal(
|
||||||
<DragOverlay
|
<DragOverlay dropAnimation={null}>
|
||||||
// Ensure a drop animation plays to allow us to get the position of the drag overlay in drag end
|
{dragId && renderToken(findGroup(tokenGroups, dragId), false)}
|
||||||
dropAnimation={{
|
|
||||||
dragSourceOpacity: 0,
|
|
||||||
duration: 1,
|
|
||||||
easing: "ease",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{dragId && (
|
|
||||||
<div ref={dragOverlayRef}>
|
|
||||||
{renderToken(findGroup(tokenGroups, dragId), false)}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</DragOverlay>,
|
</DragOverlay>,
|
||||||
document.body
|
document.body
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</DndContext>
|
</DragContext>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
75
src/contexts/DragContext.js
Normal file
75
src/contexts/DragContext.js
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
import React, { useRef, ReactNode } from "react";
|
||||||
|
import {
|
||||||
|
DndContext,
|
||||||
|
useDndContext,
|
||||||
|
useDndMonitor,
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
DragEndEvent,
|
||||||
|
} from "@dnd-kit/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a dnd-kit DndContext with a position monitor to get the
|
||||||
|
* active drag element on drag end
|
||||||
|
* TODO: use look into fixing this upstream
|
||||||
|
* Related: https://github.com/clauderic/dnd-kit/issues/238
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef DragEndOverlayEvent
|
||||||
|
* @property {DOMRect} overlayNodeClientRect
|
||||||
|
*
|
||||||
|
* @typedef {DragEndEvent & DragEndOverlayEvent} DragEndWithOverlayProps
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback DragEndWithOverlayEvent
|
||||||
|
* @param {DragEndWithOverlayProps} props
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef CustomDragProps
|
||||||
|
* @property {DragEndWithOverlayEvent=} onDragEnd
|
||||||
|
* @property {ReactNode} children
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {CustomDragProps} props
|
||||||
|
*/
|
||||||
|
function DragPositionMonitor({ children, onDragEnd }) {
|
||||||
|
const { overlayNode } = useDndContext();
|
||||||
|
|
||||||
|
const overlayNodeClientRectRef = useRef();
|
||||||
|
function handleDragMove() {
|
||||||
|
if (overlayNode?.nodeRef?.current) {
|
||||||
|
overlayNodeClientRectRef.current = overlayNode.nodeRef.current.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragEnd(props) {
|
||||||
|
onDragEnd &&
|
||||||
|
onDragEnd({
|
||||||
|
...props,
|
||||||
|
overlayNodeClientRect: overlayNodeClientRectRef.current,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
useDndMonitor({ onDragEnd: handleDragEnd, onDragMove: handleDragMove });
|
||||||
|
|
||||||
|
return children;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: Import Props interface from dnd-kit with conversion to Typescript
|
||||||
|
* @param {CustomDragProps} props
|
||||||
|
*/
|
||||||
|
function DragContext({ children, onDragEnd, ...props }) {
|
||||||
|
return (
|
||||||
|
<DndContext {...props}>
|
||||||
|
<DragPositionMonitor onDragEnd={onDragEnd}>
|
||||||
|
{children}
|
||||||
|
</DragPositionMonitor>
|
||||||
|
</DndContext>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default DragContext;
|
@ -1,6 +1,5 @@
|
|||||||
import React, { useState, useContext } from "react";
|
import React, { useState, useContext } from "react";
|
||||||
import {
|
import {
|
||||||
DndContext,
|
|
||||||
MouseSensor,
|
MouseSensor,
|
||||||
TouchSensor,
|
TouchSensor,
|
||||||
KeyboardSensor,
|
KeyboardSensor,
|
||||||
@ -9,6 +8,8 @@ import {
|
|||||||
closestCenter,
|
closestCenter,
|
||||||
} from "@dnd-kit/core";
|
} from "@dnd-kit/core";
|
||||||
|
|
||||||
|
import DragContext from "./DragContext";
|
||||||
|
|
||||||
import { useGroup } from "./GroupContext";
|
import { useGroup } from "./GroupContext";
|
||||||
|
|
||||||
import { moveGroupsInto, moveGroups, ungroup } from "../helpers/group";
|
import { moveGroupsInto, moveGroups, ungroup } from "../helpers/group";
|
||||||
@ -108,7 +109,7 @@ export function TileDragProvider({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleDragEnd(event) {
|
function handleDragEnd(event) {
|
||||||
const { active, over } = event;
|
const { active, over, overlayNodeClientRect } = event;
|
||||||
|
|
||||||
setDragId();
|
setDragId();
|
||||||
setOverId();
|
setOverId();
|
||||||
@ -143,7 +144,9 @@ export function TileDragProvider({
|
|||||||
}
|
}
|
||||||
onGroupsChange(newGroups);
|
onGroupsChange(newGroups);
|
||||||
} else if (over.id === ADD_TO_MAP_ID) {
|
} else if (over.id === ADD_TO_MAP_ID) {
|
||||||
onDragAdd && onDragAdd(selectedGroupIds, over.rect);
|
onDragAdd &&
|
||||||
|
overlayNodeClientRect &&
|
||||||
|
onDragAdd(selectedGroupIds, overlayNodeClientRect);
|
||||||
} else if (!filter) {
|
} else if (!filter) {
|
||||||
// Hanlde tile move only if we have no filter
|
// Hanlde tile move only if we have no filter
|
||||||
const overGroupIndex = activeGroups.findIndex(
|
const overGroupIndex = activeGroups.findIndex(
|
||||||
@ -210,7 +213,7 @@ export function TileDragProvider({
|
|||||||
const value = { dragId, overId, dragCursor };
|
const value = { dragId, overId, dragCursor };
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DndContext
|
<DragContext
|
||||||
onDragStart={handleDragStart}
|
onDragStart={handleDragStart}
|
||||||
onDragEnd={handleDragEnd}
|
onDragEnd={handleDragEnd}
|
||||||
onDragOver={handleDragOver}
|
onDragOver={handleDragOver}
|
||||||
@ -221,7 +224,7 @@ export function TileDragProvider({
|
|||||||
<TileDragContext.Provider value={value}>
|
<TileDragContext.Provider value={value}>
|
||||||
{children}
|
{children}
|
||||||
</TileDragContext.Provider>
|
</TileDragContext.Provider>
|
||||||
</DndContext>
|
</DragContext>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,8 +144,8 @@ function SelectTokensModal({ isOpen, onRequestClose, onMapTokensStateCreate }) {
|
|||||||
const mapStageRef = useMapStage();
|
const mapStageRef = useMapStage();
|
||||||
function handleTokensAddToMap(groupIds, rect) {
|
function handleTokensAddToMap(groupIds, rect) {
|
||||||
let clientPosition = new Vector2(
|
let clientPosition = new Vector2(
|
||||||
rect.width / 2 + rect.offsetLeft,
|
rect.width / 2 + rect.left,
|
||||||
rect.height / 2 + rect.offsetTop
|
rect.height / 2 + rect.top
|
||||||
);
|
);
|
||||||
const mapStage = mapStageRef.current;
|
const mapStage = mapStageRef.current;
|
||||||
if (!mapStage) {
|
if (!mapStage) {
|
||||||
|
Loading…
Reference in New Issue
Block a user