diff --git a/app/client/src/layoutSystems/anvil/editor/AnvilEditorDetachedWidgetOnion.tsx b/app/client/src/layoutSystems/anvil/editor/AnvilEditorDetachedWidgetOnion.tsx
index 0ba6030314..2ddab4da05 100644
--- a/app/client/src/layoutSystems/anvil/editor/AnvilEditorDetachedWidgetOnion.tsx
+++ b/app/client/src/layoutSystems/anvil/editor/AnvilEditorDetachedWidgetOnion.tsx
@@ -7,6 +7,7 @@ import {
} from "layoutSystems/anvil/common/hooks/detachedWidgetHooks";
import { AnvilErrorBoundary } from "../common/widgetComponent/AnvilErrorBoundary";
import { SKELETON_WIDGET_TYPE } from "constants/WidgetConstants";
+import { useAnvilDetachedWidgetsDnD } from "./hooks/useAnvilDetachedWidgetsDnD";
/**
* AnvilEditorDetachedWidgetOnion
@@ -23,7 +24,11 @@ export const AnvilEditorDetachedWidgetOnion = (props: BaseWidgetProps) => {
useObserveDetachedWidget(props.widgetId);
useHandleDetachedWidgetSelect(props.widgetId);
useAddBordersToDetachedWidgets(props.widgetId, props.type);
-
+ useAnvilDetachedWidgetsDnD(
+ props.widgetId,
+ props.layout[0].layoutId,
+ !!props.isVisible,
+ );
return props.type !== SKELETON_WIDGET_TYPE ? (
{props.children}
) : null;
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx
index 993c9106ed..62e2c1d5a2 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx
@@ -9,7 +9,6 @@ export const EMPTY_MODAL_PADDING = 4;
const StyledModalEditorDropArenaWrapper = styled.div<{ isModalEmpty: boolean }>`
position: relative;
- height: 100% !important;
${(props) =>
props.isModalEmpty &&
`
@@ -64,7 +63,10 @@ export const AnvilModalDropArena = ({
const widget = useSelector(getWidgetByID(modalId));
const isModalEmpty = widget.children?.length === 0;
return (
-
+
{
- setDraggingCanvas("");
- }, [setDraggingCanvas]);
+ if (currentWidgetHierarchy !== widgetHierarchy.WDS_MODAL_WIDGET) {
+ // mouse out is handled by useAnvilDetachedWidgetsDnD for detached widgets(modal widgets)
+ setDraggingCanvas("");
+ }
+ }, [setDraggingCanvas, currentWidgetHierarchy]);
+
+ const onMouseMoveForDetachedWidgets = useCallback(
+ ((e: CustomEvent) => {
+ if (currentWidgetHierarchy === widgetHierarchy.WDS_MODAL_WIDGET) {
+ anvilDnDListenerRef.current?.dispatchEvent(
+ new MouseEvent("mousemove", e.detail.event),
+ );
+ }
+ }) as EventListener,
+ [currentWidgetHierarchy],
+ );
+
return {
onMouseMove,
+ onMouseMoveForDetachedWidgets,
onMouseOver,
onMouseOut,
onMouseUp,
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEvents.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEvents.ts
index 5b215d4e9b..57d168c076 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEvents.ts
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEvents.ts
@@ -5,6 +5,7 @@ import type { AnvilHighlightInfo } from "layoutSystems/anvil/utils/anvilTypes";
import { useAnvilDnDEventCallbacks } from "./useAnvilDnDEventCallbacks";
import { removeDisallowDroppingsUI } from "../utils/utils";
import { useCanvasDragToScroll } from "layoutSystems/common/canvasArenas/useCanvasDragToScroll";
+import { DETACHED_WIDGET_MOUSE_MOVE_EVENT } from "../../hooks/useAnvilDetachedWidgetsDnD";
/**
* Hook to handle Anvil DnD events
@@ -41,16 +42,23 @@ export const useAnvilDnDEvents = (
}
}
}, [isCurrentDraggedCanvas]);
- const { onMouseMove, onMouseOut, onMouseOver, onMouseUp, resetCanvasState } =
- useAnvilDnDEventCallbacks({
- anvilDragStates,
- anvilDnDListenerRef,
- canvasIsDragging,
- deriveAllHighlightsFn,
- layoutId,
- onDrop,
- setHighlightShown,
- });
+ const {
+ onMouseMove,
+ onMouseMoveForDetachedWidgets,
+ onMouseOut,
+ onMouseOver,
+ onMouseUp,
+ resetCanvasState,
+ } = useAnvilDnDEventCallbacks({
+ anvilDragStates,
+ anvilDnDListenerRef,
+ canvasIsDragging,
+ deriveAllHighlightsFn,
+ layoutId,
+ onDrop,
+ setHighlightShown,
+ });
+
useEffect(() => {
if (anvilDnDListenerRef.current && isDragging) {
// Initialize listeners
@@ -70,6 +78,10 @@ export const useAnvilDnDEvents = (
);
// To make sure drops on the main canvas boundary buffer are processed in the capturing phase.
document.addEventListener("mouseup", onMouseUp, true);
+ document.addEventListener(
+ DETACHED_WIDGET_MOUSE_MOVE_EVENT,
+ onMouseMoveForDetachedWidgets,
+ );
return () => {
anvilDnDListenerRef.current?.removeEventListener(
@@ -95,6 +107,10 @@ export const useAnvilDnDEvents = (
);
anvilDnDListenerRef.current?.removeEventListener("mouseup", onMouseUp);
document.removeEventListener("mouseup", onMouseUp, true);
+ document.removeEventListener(
+ DETACHED_WIDGET_MOUSE_MOVE_EVENT,
+ onMouseMoveForDetachedWidgets,
+ );
};
} else {
if (canvasIsDragging.current) {
@@ -110,6 +126,7 @@ export const useAnvilDnDEvents = (
onMouseOver,
onMouseUp,
resetCanvasState,
+ onMouseMoveForDetachedWidgets,
]);
return {
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDListenerStates.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDListenerStates.ts
index 45ef1bf44d..7c11ef9d69 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDListenerStates.ts
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDListenerStates.ts
@@ -28,6 +28,7 @@ export interface AnvilDnDListenerStates {
activateOverlayWidgetDrop: boolean;
allowToDrop: boolean;
canActivate: boolean;
+ currentWidgetHierarchy: number;
draggedBlocks: DraggedWidget[];
dragDetails: DragDetails;
isCurrentDraggedCanvas: boolean;
@@ -167,6 +168,7 @@ export const useAnvilDnDListenerStates = ({
activateOverlayWidgetDrop,
allowToDrop,
canActivate,
+ currentWidgetHierarchy,
draggedBlocks,
dragDetails,
dragMeta: {
diff --git a/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilDetachedWidgetsDnD.ts b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilDetachedWidgetsDnD.ts
new file mode 100644
index 0000000000..ec6920806f
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilDetachedWidgetsDnD.ts
@@ -0,0 +1,71 @@
+import { getPositionsByLayoutId } from "layoutSystems/common/selectors";
+import { getAnvilWidgetDOMId } from "layoutSystems/common/utils/LayoutElementPositionsObserver/utils";
+import { useCallback, useEffect, useRef } from "react";
+import { useSelector } from "react-redux";
+import { getIsDragging } from "selectors/widgetDragSelectors";
+import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
+
+export const DETACHED_WIDGET_MOUSE_MOVE_EVENT =
+ "DETACHED_WIDGET_MOUSE_MOVE_EVENT";
+
+export interface AnvilDetachedWidgetsDnDDetail {
+ event: MouseEvent;
+}
+
+export const useAnvilDetachedWidgetsDnD = (
+ widgetId: string,
+ layoutId: string,
+ isVisible: boolean,
+) => {
+ const isDragging = useSelector(getIsDragging);
+ const layoutPositions = useSelector(getPositionsByLayoutId(layoutId));
+ const widgetDomRef = useRef(null);
+ const { setDraggingCanvas } = useWidgetDragResize();
+ const onMouseMove = useCallback(
+ (e: MouseEvent) => {
+ if (!isVisible || e.target !== widgetDomRef.current) {
+ return;
+ }
+ // simulate move on the top most edge of the layout
+ const detail: AnvilDetachedWidgetsDnDDetail = {
+ event: e,
+ };
+ document.dispatchEvent(
+ new CustomEvent(DETACHED_WIDGET_MOUSE_MOVE_EVENT, {
+ detail,
+ }),
+ );
+ },
+ [isVisible],
+ );
+ const onMouseOut = useCallback(() => {
+ if (isVisible) {
+ setDraggingCanvas("");
+ }
+ }, [isVisible]);
+ useEffect(() => {
+ if (isDragging) {
+ const widgetClassName = `.${getAnvilWidgetDOMId(widgetId)}`;
+ const widgetDom = document.querySelector(widgetClassName);
+ if (widgetDom) {
+ widgetDomRef.current = widgetDom as HTMLDivElement;
+ }
+ } else {
+ widgetDomRef.current = null;
+ }
+ }, [isDragging]);
+ useEffect(() => {
+ if (isDragging && isVisible && layoutPositions && widgetDomRef.current) {
+ widgetDomRef.current.addEventListener("mousemove", onMouseMove);
+ widgetDomRef.current.addEventListener("mouseenter", onMouseMove);
+ widgetDomRef.current.addEventListener("mouseleave", onMouseOut);
+ }
+ return () => {
+ if (widgetDomRef.current) {
+ widgetDomRef.current.removeEventListener("mouseenter", onMouseMove);
+ widgetDomRef.current.removeEventListener("mousemove", onMouseMove);
+ widgetDomRef.current.removeEventListener("mouseleave", onMouseOut);
+ }
+ };
+ }, [isDragging, isVisible, onMouseMove, layoutPositions]);
+};
diff --git a/app/client/src/layoutSystems/common/selectors.ts b/app/client/src/layoutSystems/common/selectors.ts
index 93e1717916..cc39ff93b4 100644
--- a/app/client/src/layoutSystems/common/selectors.ts
+++ b/app/client/src/layoutSystems/common/selectors.ts
@@ -1,4 +1,11 @@
import type { AppState } from "@appsmith/reducers";
+import { createSelector } from "reselect";
export const getLayoutElementPositions = (state: AppState) =>
state.entities.layoutElementPositions;
+
+export const getPositionsByLayoutId = (layoutId: string) =>
+ createSelector(
+ getLayoutElementPositions,
+ (layoutElementPositions) => layoutElementPositions[layoutId],
+ );
diff --git a/app/client/src/layoutSystems/withLayoutSystemWidgetHOC.test.tsx b/app/client/src/layoutSystems/withLayoutSystemWidgetHOC.test.tsx
index 87a17ccfbf..226191dae9 100644
--- a/app/client/src/layoutSystems/withLayoutSystemWidgetHOC.test.tsx
+++ b/app/client/src/layoutSystems/withLayoutSystemWidgetHOC.test.tsx
@@ -207,6 +207,11 @@ describe("Layout System HOC's Tests", () => {
isVisible: true,
detachFromLayout: true,
renderMode: RenderModes.CANVAS,
+ layout: [
+ {
+ layoutId: "modalLayoutId",
+ },
+ ],
});
jest
.spyOn(editorSelectors, "getRenderMode")