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")