diff --git a/app/client/cypress/support/Objects/CommonLocators.ts b/app/client/cypress/support/Objects/CommonLocators.ts
index f43e9febed..66ad6e752b 100644
--- a/app/client/cypress/support/Objects/CommonLocators.ts
+++ b/app/client/cypress/support/Objects/CommonLocators.ts
@@ -84,7 +84,8 @@ export class CommonLocators {
_visibleTextSpan = (spanText: string, isCss = false) =>
isCss ? `span:contains("${spanText}")` : `//span[text()="${spanText}"]`;
_dropHere = ".t--drop-target";
- _canvasSlider = "[data-type=canvas-slider]";
+ _anvilDnDListener = "[data-type=anvil-dnd-listener]";
+ _anvilDnDHighlight = "[data-type=anvil-dnd-highlight]";
_editPage = "[data-testid=onboarding-tasks-datasource-text], .t--drop-target";
_crossBtn = "span.cancel-icon";
_createNew = ".t--add-item";
diff --git a/app/client/cypress/support/Pages/AnvilLayout.ts b/app/client/cypress/support/Pages/AnvilLayout.ts
index 9940822a8b..3666678480 100644
--- a/app/client/cypress/support/Pages/AnvilLayout.ts
+++ b/app/client/cypress/support/Pages/AnvilLayout.ts
@@ -32,11 +32,11 @@ export class AnvilLayout {
}
if (dropTarget.name) {
return `${getWidgetSelector(dropTarget.name.toLowerCase() as any)} ${
- this.locator._canvasSlider
+ this.locator._anvilDnDListener
}`;
}
}
- return this.locator._canvasSlider;
+ return this.locator._anvilDnDListener;
};
private performDnDInAnvil(
@@ -47,33 +47,30 @@ export class AnvilLayout {
const dropAreaSelector = this.getAnvilDropTargetSelectorFromOptions(
options.dropTargetDetails,
);
- cy.get(dropAreaSelector)
- .first()
- .then((dropAreaDom) => {
- const { left, top } = dropAreaDom[0].getBoundingClientRect();
- cy.document()
- // to activate ANVIL canvas
- .trigger("mousemove", left + xPos, top + yPos, {
- eventConstructor: "MouseEvent",
- force: true,
- });
- this.agHelper.Sleep(200);
- cy.get(dropAreaSelector).first().trigger("mousemove", xPos, yPos, {
- eventConstructor: "MouseEvent",
- force: true,
- });
- this.agHelper.Sleep(200);
- cy.get(dropAreaSelector).first().trigger("mousemove", xPos, yPos, {
- eventConstructor: "MouseEvent",
- force: true,
- });
- cy.get(this.locator._canvasSlider)
- .first()
- .trigger("mouseup", xPos, yPos, {
- eventConstructor: "MouseEvent",
- force: true,
- });
+ cy.document()
+ // to activate ANVIL canvas
+ .trigger("mousemove", xPos, yPos, {
+ eventConstructor: "MouseEvent",
+ force: true,
});
+ this.agHelper.Sleep(200);
+ cy.get(dropAreaSelector).first().trigger("mouseover", xPos, yPos, {
+ eventConstructor: "MouseEvent",
+ force: true,
+ });
+ cy.get(dropAreaSelector).first().trigger("mousemove", xPos, yPos, {
+ eventConstructor: "MouseEvent",
+ force: true,
+ });
+ cy.get(dropAreaSelector).first().trigger("mousemove", xPos, yPos, {
+ eventConstructor: "MouseEvent",
+ force: true,
+ });
+ cy.get(this.locator._anvilDnDHighlight);
+ cy.get(dropAreaSelector).first().trigger("mouseup", xPos, yPos, {
+ eventConstructor: "MouseEvent",
+ force: true,
+ });
}
private startDraggingWidgetFromPane(widgetType: string) {
diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx
index 89ee7deda6..3fc5d66d48 100644
--- a/app/client/src/constants/WidgetConstants.tsx
+++ b/app/client/src/constants/WidgetConstants.tsx
@@ -178,6 +178,7 @@ export const WIDGET_DSL_STRUCTURE_PROPS = {
topRow: true,
type: true,
widgetId: true,
+ layout: true,
};
export type TextSize = keyof typeof TextSizes;
diff --git a/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilErrorBoundary.tsx b/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilErrorBoundary.tsx
new file mode 100644
index 0000000000..423602ecb6
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilErrorBoundary.tsx
@@ -0,0 +1,24 @@
+import styled from "styled-components";
+import ErrorBoundary from "components/editorComponents/ErrorBoundry";
+import React from "react";
+
+const RetryLink = styled.span`
+ color: ${(props) => props.theme.colors.primaryDarkest};
+ cursor: pointer;
+`;
+
+export class AnvilErrorBoundary extends ErrorBoundary {
+ render() {
+ return this.state.hasError ? (
+
+ Something went wrong.
+
+ this.setState({ hasError: false })}>
+ Click here to retry
+
+
+ ) : (
+ (this.props.children as any)
+ );
+ }
+}
diff --git a/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx b/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx
index 7bb702df19..281694998a 100644
--- a/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx
+++ b/app/client/src/layoutSystems/anvil/common/widgetComponent/AnvilWidgetComponent.tsx
@@ -1,11 +1,10 @@
-import ErrorBoundary from "components/editorComponents/ErrorBoundry";
-import WidgetComponentBoundary from "layoutSystems/common/widgetComponent/WidgetComponentBoundary";
import React from "react";
import type { BaseWidgetProps } from "widgets/BaseWidgetHOC/withBaseWidgetHOC";
import Skeleton from "widgets/Skeleton";
+import { AnvilErrorBoundary } from "./AnvilErrorBoundary";
export const AnvilWidgetComponent = (props: BaseWidgetProps) => {
- const { deferRender, detachFromLayout, type } = props;
+ const { children, deferRender, type } = props;
/**
* The widget mount calls the withWidgetProps with the widgetId and type to fetch the
* widget props. During the computation of the props (in withWidgetProps) if the evaluated
@@ -19,14 +18,5 @@ export const AnvilWidgetComponent = (props: BaseWidgetProps) => {
return ;
}
- if (!detachFromLayout) return props.children;
-
- return (
- // delete style as soon as we switch to Anvil layout completely
-
-
- {props.children}
-
-
- );
+ return {children};
};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx b/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx
index 4109def1ce..88d10b4f33 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx
+++ b/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx
@@ -1,10 +1,18 @@
import type { BaseWidgetProps } from "widgets/BaseWidgetHOC/withBaseWidgetHOC";
import { AnvilViewerCanvas } from "layoutSystems/anvil/viewer/canvas/AnvilViewerCanvas";
import React, { useCallback, useEffect, useRef } from "react";
-import { useCanvasActivation } from "./hooks/useCanvasActivation";
import { useSelectWidgetListener } from "./hooks/useSelectWidgetListener";
import { useClickToClearSelections } from "./hooks/useClickToClearSelections";
import "./styles/anvilEditorVariables.css";
+import {
+ useAnvilGlobalDnDStates,
+ type AnvilGlobalDnDStates,
+} from "./hooks/useAnvilGlobalDnDStates";
+
+export const AnvilDnDStatesContext = React.createContext<
+ AnvilGlobalDnDStates | undefined
+>(undefined);
+
/**
* Anvil Main Canvas is just a wrapper around AnvilCanvas.
* Why do we need this?
@@ -45,7 +53,13 @@ export const AnvilEditorCanvas = (props: BaseWidgetProps) => {
}, []);
/* End of click event listener */
- useCanvasActivation();
useSelectWidgetListener();
- return ;
+ // Fetching all states used in Anvil DnD using the useAnvilGlobalDnDStates hook
+ // using AnvilDnDStatesContext to provide the states to the child AnvilDraggingArena
+ const anvilGlobalDnDStates = useAnvilGlobalDnDStates();
+ return (
+
+
+
+ );
};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilDnDDeactivation.ts b/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilDnDDeactivation.ts
new file mode 100644
index 0000000000..9c764f0eea
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilDnDDeactivation.ts
@@ -0,0 +1,41 @@
+import { useEffect } from "react";
+import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
+
+/**
+ * This hook handles the deactivation of the DnD Listeners while dragging.
+ */
+
+export const useAnvilDnDDeactivation = (
+ isDragging: boolean,
+ isNewWidget: boolean,
+) => {
+ // Destructuring hook functions for dragging new widgets and setting dragging state
+ const { setDraggingNewWidget, setDraggingState } = useWidgetDragResize();
+
+ // Callback function to handle mouse up events and reset dragging state
+ const onMouseUp = () => {
+ if (isDragging) {
+ if (isNewWidget) {
+ setDraggingNewWidget(false, undefined);
+ } else {
+ setDraggingState({
+ isDragging: false,
+ });
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (isDragging) {
+ // Adding event listeners for mouse move and mouse up events
+ document.body.addEventListener("mouseup", onMouseUp, false);
+ window.addEventListener("mouseup", onMouseUp, false);
+
+ // Removing event listeners when the component unmounts or when dragging ends
+ return () => {
+ document.body.removeEventListener("mouseup", onMouseUp);
+ window.removeEventListener("mouseup", onMouseUp);
+ };
+ }
+ }, [isDragging, onMouseUp]);
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useCanvasActivationStates.ts b/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilGlobalDnDStates.ts
similarity index 62%
rename from app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useCanvasActivationStates.ts
rename to app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilGlobalDnDStates.ts
index d50d011d52..fa606273c5 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useCanvasActivationStates.ts
+++ b/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilGlobalDnDStates.ts
@@ -12,45 +12,62 @@ import {
getDraggedBlocks,
getDraggedWidgetHierarchy,
getDraggedWidgetTypes,
-} from "./utils";
-import type { AnvilDraggedWidgetTypes } from "../types";
+} from "../../canvasArenas/utils/utils";
+import type { DraggedWidget } from "layoutSystems/anvil/utils/anvilTypes";
+import type { AnvilDraggedWidgetTypesEnum } from "../../canvasArenas/types";
+import { useAnvilDnDDeactivation } from "./useAnvilDnDDeactivation";
-export interface AnvilCanvasActivationStates {
+export interface AnvilGlobalDnDStates {
activateOverlayWidgetDrop: boolean;
+ draggedBlocks: DraggedWidget[];
dragDetails: DragDetails;
draggedWidgetHierarchy: number;
- draggedWidgetTypes: AnvilDraggedWidgetTypes;
+ draggedWidgetTypes: AnvilDraggedWidgetTypesEnum;
isDragging: boolean;
isNewWidget: boolean;
layoutElementPositions: LayoutElementPositions;
mainCanvasLayoutId: string;
- selectedWidgets: string[];
}
-export const useCanvasActivationStates = (): AnvilCanvasActivationStates => {
+/**
+ * This hook is used to get the global states of the canvas while dragging.
+ * It also is responsible for deactivating the canvas while dragging.
+ * @returns AnvilGlobalDnDStates
+ */
+export const useAnvilGlobalDnDStates = (): AnvilGlobalDnDStates => {
const mainCanvasLayoutId: string = useSelector((state) =>
getDropTargetLayoutId(state, MAIN_CONTAINER_WIDGET_ID),
);
const layoutElementPositions = useSelector(getLayoutElementPositions);
const allWidgets = useSelector(getWidgets);
const selectedWidgets = useSelector(getSelectedWidgets);
- // dragDetails contains of info needed for a container jump:
- // which parent the dragging widget belongs,
- // which canvas is active(being dragged on),
- // which widget is grabbed while dragging started,
- // relative position of mouse pointer wrt to the last grabbed widget.
+
+ /**
+ * dragDetails is the state that holds the details of the widget being dragged.
+ */
const dragDetails: DragDetails = useSelector(getDragDetails);
+
+ /**
+ * isDragging is a boolean that indicates if a widget is being dragged.
+ */
const isDragging = useSelector(
(state: AppState) => state.ui.widgetDragResize.isDragging,
);
+ /**
+ * dragParent is the parent of the widget being dragged.
+ */
const { dragGroupActualParent: dragParent, newWidget } = dragDetails;
+
/**
* boolean to indicate if the widget being dragged is a new widget
*/
const isNewWidget = !!newWidget && !dragParent;
- // process drag blocks only once and per first render
- // this is by taking advantage of the fact that isNewWidget and dragDetails are unchanged states during the dragging action.
+
+ /**
+ * compute drag blocks only once and per first render
+ * this is by taking advantage of the fact that isNewWidget and dragDetails are unchanged states during the dragging action.
+ */
const draggedBlocks = useMemo(
() =>
isDragging
@@ -63,25 +80,40 @@ export const useCanvasActivationStates = (): AnvilCanvasActivationStates => {
: [],
[isDragging, selectedWidgets],
);
+
+ /**
+ * boolean to indicate if the widget is being dragged on this particular canvas.
+ */
+ const draggedWidgetHierarchy = getDraggedWidgetHierarchy(draggedBlocks);
+
/**
* boolean that indicates if the widget being dragged in an overlay widget like the Modal widget.
*/
- const activateOverlayWidgetDrop = isNewWidget && !!newWidget.detachFromLayout;
+ const activateOverlayWidgetDrop =
+ isNewWidget && newWidget.detachFromLayout === true;
+
+ /**
+ * get the dragged widget types to assess if the widget can be dropped on the canvas.
+ */
const draggedWidgetTypes = useMemo(
() => getDraggedWidgetTypes(draggedBlocks),
[draggedBlocks],
);
- const draggedWidgetHierarchy = getDraggedWidgetHierarchy(draggedBlocks);
+
+ /**
+ * This hook handles the deactivation of the canvas(Drop targets) while dragging.
+ */
+ useAnvilDnDDeactivation(isDragging, isNewWidget);
return {
activateOverlayWidgetDrop,
- dragDetails,
+ draggedBlocks,
draggedWidgetHierarchy,
+ dragDetails,
draggedWidgetTypes,
isDragging,
isNewWidget,
- layoutElementPositions,
mainCanvasLayoutId,
- selectedWidgets,
+ layoutElementPositions,
};
};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useCanvasActivation.ts b/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useCanvasActivation.ts
deleted file mode 100644
index 8983681214..0000000000
--- a/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useCanvasActivation.ts
+++ /dev/null
@@ -1,237 +0,0 @@
-import { CANVAS_ART_BOARD } from "constants/componentClassNameConstants";
-import { Indices } from "constants/Layers";
-import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
-import type { LayoutElementPosition } from "layoutSystems/common/types";
-import { positionObserver } from "layoutSystems/common/utils/LayoutElementPositionsObserver";
-import { getAnvilLayoutDOMId } from "layoutSystems/common/utils/LayoutElementPositionsObserver/utils";
-import { debounce, uniq } from "lodash";
-import { useEffect, useRef } from "react";
-import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
-import { LayoutComponentTypes } from "layoutSystems/anvil/utils/anvilTypes";
-import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
-import { useSelector } from "react-redux";
-import { getWidgets } from "sagas/selectors";
-import type { FlattenedWidgetProps } from "WidgetProvider/constants";
-import { useCanvasActivationStates } from "layoutSystems/anvil/editor/canvasArenas/hooks/useCanvasActivationStates";
-import { canActivateCanvasForDraggedWidget } from "layoutSystems/anvil/editor/canvasArenas/hooks/utils";
-
-// Z-Index values for activated and deactivated states
-export const AnvilCanvasZIndex = {
- // we can decrease the z-index once we are able to provide fix for the issue #28471
- activated: Indices.Layer10.toString(),
- deactivated: "",
-};
-
-// Function to check if mouse position is inside a block
-const checkIfMousePositionIsInsideBlock = (
- e: MouseEvent,
- mainCanvasRect: DOMRect,
- layoutElementPosition: LayoutElementPosition,
-) => {
- return (
- layoutElementPosition.left <= e.clientX - mainCanvasRect.left &&
- e.clientX - mainCanvasRect.left <=
- layoutElementPosition.left + layoutElementPosition.width &&
- layoutElementPosition.top <= e.clientY - mainCanvasRect.top &&
- e.clientY - mainCanvasRect.top <=
- layoutElementPosition.top + layoutElementPosition.height
- );
-};
-
-// Buffer value for the main canvas
-// This buffer will make sure main canvas is not deactivated
-// until its about the below pixel distance from the main canvas border.
-const MAIN_CANVAS_BUFFER = 20;
-const SECTION_BUFFER = 20;
-
-/**
- * This hook handles the activation and deactivation of the canvas(Drop targets) while dragging.
- */
-
-export const useCanvasActivation = () => {
- const {
- activateOverlayWidgetDrop,
- dragDetails,
- draggedWidgetHierarchy,
- isDragging,
- isNewWidget,
- layoutElementPositions,
- mainCanvasLayoutId,
- selectedWidgets,
- } = useCanvasActivationStates();
- const allWidgets: CanvasWidgetsReduxState = useSelector(getWidgets);
- // Getting the main canvas DOM node
- const mainContainerDOMNode = document.getElementById(CANVAS_ART_BOARD);
-
- // Destructuring hook functions for drag and resize functionality
- const { setDraggingCanvas, setDraggingNewWidget, setDraggingState } =
- useWidgetDragResize();
-
- // Mapping selected widget positions
- const draggedWidgetPositions = selectedWidgets.map((each) => {
- return layoutElementPositions[each];
- });
- /**
- * boolean ref that indicates if the mouse position is outside of main canvas while dragging
- * this is being tracked in order to activate/deactivate canvas.
- */
- const isMouseOutOfMainCanvas = useRef(false);
-
- // Function to handle mouse leaving the canvas while dragging
- const mouseOutOfCanvasArtBoard = () => {
- isMouseOutOfMainCanvas.current = true;
- setDraggingCanvas();
- };
-
- // Debouncing functions for smoother transitions
- const debouncedSetDraggingCanvas = debounce(setDraggingCanvas);
- const debouncedMouseOutOfCanvasArtBoard = debounce(mouseOutOfCanvasArtBoard);
-
- // All layouts registered on the position observer
- const allLayouts: any = isDragging
- ? positionObserver.getRegisteredLayouts()
- : {};
-
- // All layout IDs on the page
- const allLayoutIds = Object.keys(allLayouts);
-
- // DOM ID of the main canvas layout
- const mainCanvasLayoutDomId = getAnvilLayoutDOMId(
- MAIN_CONTAINER_WIDGET_ID,
- mainCanvasLayoutId,
- );
-
- /**
- * layoutIds that are supported to drop while dragging.
- * when dragging an AnvilOverlayWidgetTypes widget only the main canvas is supported for dropping.
- */
- const filteredLayoutIds: string[] = activateOverlayWidgetDrop
- ? allLayoutIds.filter((each) => each === mainCanvasLayoutDomId)
- : allLayoutIds;
- // All droppable layout IDs
- const allDroppableLayoutIds = uniq(
- filteredLayoutIds
- .filter((each) => {
- const layoutInfo = allLayouts[each];
- const currentPositions = layoutElementPositions[layoutInfo.layoutId];
- const widget: FlattenedWidgetProps = allWidgets[layoutInfo.canvasId];
- const canActivate = canActivateCanvasForDraggedWidget(
- draggedWidgetHierarchy,
- widget?.widgetId,
- widget?.type,
- );
- return canActivate && currentPositions && !!layoutInfo.isDropTarget;
- })
- .map((each) => allLayouts[each].layoutId),
- );
- /**
- * Droppable layout IDs sorted by area in ascending order
- * This is done because a point can be inside multiple canvas areas, but only the smallest of them is the immediate parent.
- */
- const smallToLargeSortedDroppableLayoutIds = allDroppableLayoutIds.sort(
- (droppableLayout1Id: string, droppableLayout2Id: string) => {
- const droppableLayout1 = layoutElementPositions[droppableLayout1Id];
- const droppableLayout2 = layoutElementPositions[droppableLayout2Id];
- return (
- droppableLayout1.height * droppableLayout1.width -
- droppableLayout2.height * droppableLayout2.width
- );
- },
- );
- /**
- * Callback function to handle mouse move events while dragging state is set.
- * The function uses the mouse position and checks through smallToLargeSortedDroppableLayoutIds
- * to find under which layout the point is positioned and activates that layout canvas.
- *
- * Canvas activation means that the layout's canvas is raised up in z-index to register and process mouse events
- * and draw highlights appropriately.
- */
- const onMouseMoveWhileDragging = (e: MouseEvent) => {
- if (
- isDragging &&
- mainContainerDOMNode &&
- smallToLargeSortedDroppableLayoutIds.length > 0
- ) {
- // Getting the main canvas bounding rect
- const mainCanvasRect = mainContainerDOMNode.getBoundingClientRect();
-
- // Checking if the mouse position is outside of dragging widgets
- const isMousePositionOutsideOfDraggingWidgets =
- !isNewWidget &&
- draggedWidgetPositions.find((each) => {
- return checkIfMousePositionIsInsideBlock(e, mainCanvasRect, each);
- });
-
- // Finding the layout under the mouse position
- const hoveredCanvas = isMousePositionOutsideOfDraggingWidgets
- ? dragDetails.dragGroupActualParent
- : smallToLargeSortedDroppableLayoutIds.find((each) => {
- const currentCanvasPositions = { ...layoutElementPositions[each] };
- if (each === mainCanvasLayoutId) {
- currentCanvasPositions.left -= MAIN_CANVAS_BUFFER;
- currentCanvasPositions.top -= MAIN_CANVAS_BUFFER;
- currentCanvasPositions.width += 2 * MAIN_CANVAS_BUFFER;
- currentCanvasPositions.height += 2 * MAIN_CANVAS_BUFFER;
- }
- const layoutInfo = allLayouts[each];
- if (layoutInfo.layoutType === LayoutComponentTypes.SECTION) {
- currentCanvasPositions.top += SECTION_BUFFER;
- currentCanvasPositions.height -= 2 * SECTION_BUFFER;
- currentCanvasPositions.width += 2 * SECTION_BUFFER;
- currentCanvasPositions.left -= SECTION_BUFFER;
- }
- if (currentCanvasPositions) {
- return checkIfMousePositionIsInsideBlock(
- e,
- mainCanvasRect,
- currentCanvasPositions,
- );
- }
- });
-
- // Handling canvas activation and deactivation
- if (dragDetails.draggedOn !== hoveredCanvas) {
- if (hoveredCanvas) {
- isMouseOutOfMainCanvas.current = false;
- debouncedSetDraggingCanvas(hoveredCanvas);
- } else {
- debouncedMouseOutOfCanvasArtBoard();
- }
- }
- }
- };
-
- // Callback function to handle mouse up events and reset dragging state
- const onMouseUp = () => {
- if (isDragging) {
- if (isNewWidget) {
- setDraggingNewWidget(false, undefined);
- } else {
- setDraggingState({
- isDragging: false,
- });
- }
- }
- };
-
- useEffect(() => {
- if (isDragging) {
- // Adding event listeners for mouse move and mouse up events
- document?.addEventListener("mousemove", onMouseMoveWhileDragging);
- document.body.addEventListener("mouseup", onMouseUp, false);
- window.addEventListener("mouseup", onMouseUp, false);
-
- // Removing event listeners when the component unmounts or when dragging ends
- return () => {
- document?.removeEventListener("mousemove", onMouseMoveWhileDragging);
- document.body.removeEventListener("mouseup", onMouseUp);
- window.removeEventListener("mouseup", onMouseUp);
- };
- }
- }, [
- isDragging,
- onMouseMoveWhileDragging,
- onMouseUp,
- debouncedMouseOutOfCanvasArtBoard,
- ]);
-};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilCanvasDraggingArena.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilCanvasDraggingArena.tsx
deleted file mode 100644
index 86a6bdfd9d..0000000000
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilCanvasDraggingArena.tsx
+++ /dev/null
@@ -1,67 +0,0 @@
-import type { LayoutElementPositions } from "layoutSystems/common/types";
-import React from "react";
-import type {
- DraggedWidget,
- HighlightPayload,
- LayoutComponentTypes,
-} from "layoutSystems/anvil/utils/anvilTypes";
-import { AnvilHighlightingCanvas } from "./AnvilHighlightingCanvas";
-import { useAnvilDnDStates } from "./hooks/useAnvilDnDStates";
-import { useAnvilWidgetDrop } from "./hooks/useAnvilWidgetDrop";
-import { DetachedWidgetsDropArena } from "./DetachedWidgetsDropArena";
-import { useSelector } from "react-redux";
-import { isEditOnlyModeSelector } from "selectors/editorSelectors";
-
-// Props interface for AnvilCanvasDraggingArena component
-interface AnvilCanvasDraggingArenaProps {
- canvasId: string;
- layoutId: string;
- layoutType: LayoutComponentTypes;
- allowedWidgetTypes: string[];
- deriveAllHighlightsFn: (
- layoutElementPositions: LayoutElementPositions,
- draggedWidgets: DraggedWidget[],
- ) => HighlightPayload;
-}
-
-export const AnvilCanvasDraggingArena = (
- props: AnvilCanvasDraggingArenaProps,
-) => {
- const isEditOnlyMode = useSelector(isEditOnlyModeSelector);
- const {
- allowedWidgetTypes,
- canvasId,
- deriveAllHighlightsFn,
- layoutId,
- layoutType,
- } = props;
-
- // Fetching all states used in Anvil DnD using the useAnvilDnDStates hook
- const anvilDragStates = useAnvilDnDStates({
- allowedWidgetTypes,
- canvasId,
- layoutId,
- layoutType,
- });
-
- // Using the useAnvilWidgetDrop hook to handle widget dropping
- const onDrop = useAnvilWidgetDrop(canvasId, anvilDragStates);
- const isMainCanvasDropArena =
- anvilDragStates.mainCanvasLayoutId === props.layoutId;
- return isEditOnlyMode ? (
- <>
-
- {isMainCanvasDropArena && (
-
- )}
- >
- ) : null;
-};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDnDHighlight.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDnDHighlight.tsx
new file mode 100644
index 0000000000..ed5945e5af
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDnDHighlight.tsx
@@ -0,0 +1,66 @@
+import type { AnvilHighlightInfo } from "layoutSystems/anvil/utils/anvilTypes";
+import { PADDING_FOR_HORIZONTAL_HIGHLIGHT } from "layoutSystems/anvil/utils/constants";
+import React, { useMemo } from "react";
+import styled from "styled-components";
+
+// Styled component for the highlight element
+const AnvilStyledHighlight = styled.div<{ zIndex: number }>`
+ background-color: var(--anvil-drop-indicator);
+ border-radius: 2px;
+ position: absolute;
+ z-index: ${(props) => props.zIndex};
+ pointer-events: none;
+`;
+
+export const AnvilDnDHighlight = ({
+ compensatorValues = {
+ left: 0,
+ top: 0,
+ },
+ highlightShown,
+ zIndex = 0,
+}: {
+ compensatorValues?: {
+ left: number;
+ top: number;
+ };
+ highlightShown: AnvilHighlightInfo | null;
+ zIndex?: number;
+}) => {
+ // Memoized calculation of highlight dimensions styles
+ const highlightDimensionStyles = useMemo(() => {
+ if (!highlightShown) {
+ // If no highlight info is provided, return default dimensions
+ return {
+ height: 0,
+ left: 0,
+ top: 0,
+ width: 0,
+ };
+ }
+ // Calculate padding based on highlight orientation
+ const horizontalPadding = highlightShown.isVertical
+ ? 0
+ : PADDING_FOR_HORIZONTAL_HIGHLIGHT;
+ const verticalPadding = highlightShown.isVertical
+ ? PADDING_FOR_HORIZONTAL_HIGHLIGHT
+ : 0;
+
+ // Calculate dimension styles based on highlight info
+ return {
+ height: highlightShown.height - verticalPadding * 2,
+ left: highlightShown.posX + horizontalPadding - compensatorValues.left,
+ top: highlightShown.posY + verticalPadding - compensatorValues.top,
+ width: highlightShown.width - horizontalPadding * 2,
+ };
+ }, [highlightShown]);
+
+ // Render the highlight element if highlight info is provided
+ return highlightShown ? (
+
+ ) : null; // Otherwise, return null
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDnDListener.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDnDListener.tsx
new file mode 100644
index 0000000000..33fff138f6
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDnDListener.tsx
@@ -0,0 +1,51 @@
+import type { Ref, RefObject } from "react";
+import React, { forwardRef } from "react";
+import styled from "styled-components";
+
+interface AnvilDnDListenerProps {
+ compensatorValues: {
+ left: number;
+ top: number;
+ };
+ ref: RefObject;
+ zIndex: number;
+}
+
+const StyledDnDListener = styled.div<{
+ paddingLeft: number;
+ paddingTop: number;
+ zIndex: number;
+}>`
+ &.disallow-dropping {
+ background-color: #eb714d;
+ color: white;
+ text-align: center;
+ opacity: 0.8;
+ }
+ position: absolute;
+ pointer-events: all;
+ top: ${(props) => -props.paddingTop}px;
+ left: ${(props) => -props.paddingLeft}px;
+ height: calc(100% + ${(props) => 2 * props.paddingTop}px);
+ width: calc(100% + ${(props) => 2 * props.paddingLeft}px);
+ padding-inline: ${(props) => props.paddingLeft}px;
+ padding-block: ${(props) => props.paddingTop}px;
+ z-index: ${(props) => props.zIndex};
+`;
+
+export const AnvilDnDListener = forwardRef(
+ (props: AnvilDnDListenerProps, ref: Ref) => {
+ // Refer to useAnvilDnDCompensators to understand zIndex and compensatorValues
+ const { compensatorValues, zIndex } = props;
+
+ return (
+
+ );
+ },
+);
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDraggingArena.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDraggingArena.tsx
new file mode 100644
index 0000000000..a6eae2e263
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilDraggingArena.tsx
@@ -0,0 +1,90 @@
+import type { LayoutElementPositions } from "layoutSystems/common/types";
+import React, { useContext } from "react";
+import type {
+ DraggedWidget,
+ HighlightPayload,
+ LayoutComponentTypes,
+} from "layoutSystems/anvil/utils/anvilTypes";
+import { AnvilHighlightingCanvas } from "./AnvilHighlightingCanvas";
+import { useAnvilWidgetDrop } from "./hooks/useAnvilWidgetDrop";
+import { DetachedWidgetsDropArena } from "./DetachedWidgetsDropArena";
+import { useSelector } from "react-redux";
+import { isEditOnlyModeSelector } from "selectors/editorSelectors";
+import { useAnvilDnDListenerStates } from "./hooks/useAnvilDnDListenerStates";
+import { AnvilDnDStatesContext } from "../canvas/AnvilEditorCanvas";
+import type { AnvilGlobalDnDStates } from "../canvas/hooks/useAnvilGlobalDnDStates";
+
+interface AnvilCanvasDraggingArenaProps {
+ widgetId: string;
+ layoutId: string;
+ layoutType: LayoutComponentTypes;
+ allowedWidgetTypes: string[];
+ deriveAllHighlightsFn: (
+ layoutElementPositions: LayoutElementPositions,
+ draggedWidgets: DraggedWidget[],
+ ) => HighlightPayload;
+}
+
+/**
+ * AnvilDraggingArenaComponent is the main component that renders the AnvilHighlightingCanvas and DetachedWidgetsDropArena.
+ * It also uses the useAnvilWidgetDrop hook to handle widget dropping.
+ * It also makes sure that the DetachedWidgetsDropArena is rendered only when the main canvas is the drop arena.
+ */
+const AnvilDraggingArenaComponent = ({
+ anvilGlobalDragStates,
+ dragArenaProps,
+}: {
+ dragArenaProps: AnvilCanvasDraggingArenaProps;
+ anvilGlobalDragStates: AnvilGlobalDnDStates;
+}) => {
+ const isEditOnlyMode = useSelector(isEditOnlyModeSelector);
+ const {
+ allowedWidgetTypes,
+ deriveAllHighlightsFn,
+ layoutId,
+ layoutType,
+ widgetId,
+ } = dragArenaProps;
+ // Fetching all states used in Anvil DnD Listener using the useAnvilDnDListenerStates hook
+ const anvilDragStates = useAnvilDnDListenerStates({
+ allowedWidgetTypes,
+ anvilGlobalDragStates,
+ widgetId,
+ layoutId,
+ layoutType,
+ });
+ // Using the useAnvilWidgetDrop hook to handle widget dropping
+ const onDrop = useAnvilWidgetDrop(widgetId, anvilDragStates);
+ const isMainCanvasDropArena =
+ anvilGlobalDragStates.mainCanvasLayoutId === layoutId;
+ return isEditOnlyMode ? (
+ <>
+
+ {isMainCanvasDropArena && (
+
+ )}
+ >
+ ) : null;
+};
+
+/**
+ * AnvilDraggingArena is a wrapper component for AnvilHighlightingCanvas and DetachedWidgetsDropArena.
+ */
+export const AnvilDraggingArena = (props: AnvilCanvasDraggingArenaProps) => {
+ const anvilGlobalDragStates = useContext(AnvilDnDStatesContext);
+ return anvilGlobalDragStates ? (
+
+ ) : null;
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilHighlightingCanvas.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilHighlightingCanvas.tsx
index 12e590fbf2..53b88eff56 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilHighlightingCanvas.tsx
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilHighlightingCanvas.tsx
@@ -1,17 +1,18 @@
-import { getNearestParentCanvas } from "utils/generators";
-import { useCanvasDragging } from "./hooks/useCanvasDragging";
-import { StickyCanvasArena } from "layoutSystems/common/canvasArenas/StickyCanvasArena";
+import { useAnvilDnDEvents } from "./hooks/useAnvilDnDEvents";
import React from "react";
import type {
AnvilHighlightInfo,
DraggedWidget,
HighlightPayload,
} from "layoutSystems/anvil/utils/anvilTypes";
-import type { AnvilDnDStates } from "./hooks/useAnvilDnDStates";
import type { LayoutElementPositions } from "layoutSystems/common/types";
+import { AnvilDnDListener } from "./AnvilDnDListener";
+import { AnvilDnDHighlight } from "./AnvilDnDHighlight";
+import type { AnvilDnDListenerStates } from "./hooks/useAnvilDnDListenerStates";
export interface AnvilHighlightingCanvasProps {
- anvilDragStates: AnvilDnDStates;
+ anvilDragStates: AnvilDnDListenerStates;
+ widgetId: string;
layoutId: string;
deriveAllHighlightsFn: (
layoutElementPositions: LayoutElementPositions,
@@ -25,35 +26,38 @@ export function AnvilHighlightingCanvas({
deriveAllHighlightsFn,
layoutId,
onDrop,
+ widgetId,
}: AnvilHighlightingCanvasProps) {
- const slidingArenaRef = React.useRef(null);
- const stickyCanvasRef = React.useRef(null);
- // showDraggingCanvas indicates if the current dragging canvas i.e. the html canvas renders
- const { showCanvas: showDraggingCanvas } = useCanvasDragging(
- slidingArenaRef,
- stickyCanvasRef,
+ const anvilDnDListenerRef = React.useRef(null);
+ const [highlightShown, setHighlightShown] =
+ React.useState(null);
+
+ const { isCurrentDraggedCanvas } = anvilDragStates;
+ const { showDnDListener } = useAnvilDnDEvents(
+ anvilDnDListenerRef,
{
anvilDragStates,
+ widgetId,
deriveAllHighlightsFn,
layoutId,
onDrop,
},
+ setHighlightShown,
);
- const canvasRef = React.useRef({
- stickyCanvasRef,
- slidingArenaRef,
- });
- return showDraggingCanvas ? (
-
+ return showDnDListener ? (
+ <>
+ {isCurrentDraggedCanvas && (
+
+ )}
+
+ >
) : null;
}
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx
index 21c500a5c7..b7e8742c5a 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx
@@ -5,13 +5,15 @@ import styled from "styled-components";
import type { DragDetails } from "reducers/uiReducers/dragResizeReducer";
import { DropWidgetsHereMessage } from "layoutSystems/anvil/common/messages";
+export const EMPTY_MODAL_PADDING = 4;
+
const StyledEmptyModalDropArenaWrapper = styled.div<{ isModalEmpty: boolean }>`
+ position: relative;
${(props) =>
props.isModalEmpty &&
`
- position: relative;
height: 100% !important;
- padding: 4px;
+ padding: ${EMPTY_MODAL_PADDING}px;
`}
`;
const StyledEmptyModalDropArena = styled.div<{
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/DetachedWidgetsDropArena.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/DetachedWidgetsDropArena.tsx
index e4bc393251..e3d5031695 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/DetachedWidgetsDropArena.tsx
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/DetachedWidgetsDropArena.tsx
@@ -1,5 +1,4 @@
import React from "react";
-import type { AnvilDnDStates } from "./hooks/useAnvilDnDStates";
import type { AnvilHighlightInfo } from "layoutSystems/anvil/utils/anvilTypes";
import { FlexLayerAlignment } from "layoutSystems/common/utils/constants";
import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
@@ -7,6 +6,7 @@ import styled from "styled-components";
import { Popover, PopoverModalContent } from "@design-system/headless";
import { DropModalHereMessage } from "layoutSystems/anvil/common/messages";
import styles from "./styles.module.css";
+import type { AnvilGlobalDnDStates } from "../canvas/hooks/useAnvilGlobalDnDStates";
/**
* Default highlight passed for AnvilOverlayWidgetTypes widgets
*/
@@ -21,6 +21,12 @@ const overlayWidgetHighlight: AnvilHighlightInfo = {
posY: 0,
rowIndex: 0,
width: 0,
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
};
const DetachedWidgetsDropArenaWrapper = styled.span`
@@ -30,16 +36,16 @@ const DetachedWidgetsDropArenaWrapper = styled.span`
`;
export const DetachedWidgetsDropArena = (props: {
- anvilDragStates: AnvilDnDStates;
+ anvilGlobalDragStates: AnvilGlobalDnDStates;
onDrop: (renderedBlock: AnvilHighlightInfo) => void;
}) => {
const onMouseUp = () => {
props.onDrop({
...overlayWidgetHighlight,
- layoutOrder: [props.anvilDragStates.mainCanvasLayoutId],
+ layoutOrder: [props.anvilGlobalDragStates.mainCanvasLayoutId],
});
};
- return props.anvilDragStates.activateOverlayWidgetDrop ? (
+ return props.anvilGlobalDragStates.activateOverlayWidgetDrop ? (
{
+ const { theme } = useTheme();
+ const isElevatedWidget = !!widgetProps.elevatedBackground;
+ const {
+ edgeCompensatorValues,
+ layoutCompensatorValues,
+ widgetCompensatorValues,
+ } = getCompensatorsForHierarchy(
+ currentWidgetHierarchy,
+ isEmptyLayout,
+ isElevatedWidget,
+ theme.outerSpacing,
+ );
+ // to make sure main canvas and modal are both treated alike
+ const currentHierarchy = Math.max(currentWidgetHierarchy, 1);
+
+ // zIndex is set in a way that drag layers with least hierarchy(as per the constant widgetHierarchy) are below so that all layers of different hierarchy are accessible for mouse events.
+ // also setting zIndex only for layers below the dragged widget to restrict being dropped from lower to upper hierarchy.
+ // ex: when a zone is being dragged other zones DnD is not activated,
+ // because a zone cannot be dropped into another zone as they are both of same hierarchy.
+ // same zIndex with an increment of 1 is set for the highlight(AnvilDnDHighlight) to make sure it is always on top of the dnd listener(AnvilDnDListener).
+ const zIndex =
+ canActivate && currentHierarchy < draggedWidgetHierarchy - 1 ? 0 : 1;
+ return {
+ edgeCompensatorValues,
+ layoutCompensatorValues,
+ widgetCompensatorValues,
+ zIndex,
+ };
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEventCallbacks.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEventCallbacks.ts
new file mode 100644
index 0000000000..21ad6707e6
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEventCallbacks.ts
@@ -0,0 +1,219 @@
+import { setHighlightsDrawnAction } from "layoutSystems/anvil/integrations/actions/draggingActions";
+import type {
+ AnvilHighlightInfo,
+ DraggedWidget,
+ HighlightPayload,
+} from "layoutSystems/anvil/utils/anvilTypes";
+import { throttle } from "lodash";
+import { useCallback, useRef } from "react";
+import { getPositionCompensatedHighlight } from "../utils/dndCompensatorUtils";
+import { useDispatch } from "react-redux";
+import {
+ getClosestHighlight,
+ removeDisallowDroppingsUI,
+ renderDisallowDroppingUI,
+} from "../utils/utils";
+import { useWidgetDragResize } from "utils/hooks/dragResizeHooks";
+import type { AnvilDnDListenerStates } from "./useAnvilDnDListenerStates";
+import type { LayoutElementPositions } from "layoutSystems/common/types";
+
+export const useAnvilDnDEventCallbacks = ({
+ anvilDnDListenerRef,
+ anvilDragStates,
+ canvasIsDragging,
+ deriveAllHighlightsFn,
+ layoutId,
+ onDrop,
+ setHighlightShown,
+}: {
+ anvilDragStates: AnvilDnDListenerStates;
+ anvilDnDListenerRef: React.RefObject;
+ canvasIsDragging: React.MutableRefObject;
+ deriveAllHighlightsFn: (
+ layoutElementPositions: LayoutElementPositions,
+ draggedWidgets: DraggedWidget[],
+ ) => HighlightPayload;
+ layoutId: string;
+ onDrop: (renderedBlock: AnvilHighlightInfo) => void;
+ setHighlightShown: (highlight: AnvilHighlightInfo | null) => void;
+}) => {
+ const {
+ activateOverlayWidgetDrop,
+ allowToDrop,
+ canActivate,
+ draggedBlocks,
+ edgeCompensatorValues,
+ isCurrentDraggedCanvas,
+ isDragging,
+ layoutCompensatorValues,
+ layoutElementPositions,
+ } = anvilDragStates;
+ const allHighlightsRef = useRef([] as AnvilHighlightInfo[]);
+ const currentSelectedHighlight = useRef(null);
+ const dispatch = useDispatch();
+ const { setDraggingCanvas } = useWidgetDragResize();
+ const calculateHighlights = useCallback(() => {
+ if (activateOverlayWidgetDrop) {
+ allHighlightsRef.current = [];
+ } else {
+ allHighlightsRef.current = deriveAllHighlightsFn(
+ layoutElementPositions,
+ draggedBlocks,
+ )?.highlights;
+ }
+ }, [
+ activateOverlayWidgetDrop,
+ deriveAllHighlightsFn,
+ draggedBlocks,
+ layoutElementPositions,
+ ]);
+ const resetCanvasState = useCallback(() => {
+ // Resetting the dnd listener state when necessary
+ if (anvilDnDListenerRef.current) {
+ removeDisallowDroppingsUI(anvilDnDListenerRef.current);
+ canvasIsDragging.current = false;
+ dispatch(setHighlightsDrawnAction());
+ setHighlightShown(null);
+ }
+ }, [dispatch, setHighlightShown]);
+ const onMouseUp = useCallback(() => {
+ if (
+ isDragging &&
+ isCurrentDraggedCanvas &&
+ canvasIsDragging.current &&
+ currentSelectedHighlight.current &&
+ !currentSelectedHighlight.current.existingPositionHighlight &&
+ allowToDrop
+ ) {
+ // Invoke onDrop callback with the appropriate highlight info
+ onDrop(currentSelectedHighlight.current);
+ }
+ resetCanvasState();
+ }, [
+ allowToDrop,
+ isDragging,
+ isCurrentDraggedCanvas,
+ onDrop,
+ resetCanvasState,
+ ]);
+
+ const getHighlightCompensator = useCallback(
+ (highlight: AnvilHighlightInfo) =>
+ getPositionCompensatedHighlight(
+ highlight,
+ layoutCompensatorValues,
+ edgeCompensatorValues,
+ ),
+ [layoutCompensatorValues, edgeCompensatorValues],
+ );
+ // make sure rendering highlights on dnd listener and highlighting cell happens once every 50ms
+ const throttledSetHighlight = useCallback(
+ throttle(
+ () => {
+ if (
+ canvasIsDragging.current &&
+ isCurrentDraggedCanvas &&
+ currentSelectedHighlight.current
+ ) {
+ const compensatedHighlight = getHighlightCompensator(
+ currentSelectedHighlight.current,
+ );
+ dispatch(setHighlightsDrawnAction(compensatedHighlight));
+ setHighlightShown(compensatedHighlight);
+ }
+ },
+ 50,
+ {
+ leading: true,
+ trailing: true,
+ },
+ ),
+ [
+ dispatch,
+ getHighlightCompensator,
+ isCurrentDraggedCanvas,
+ setHighlightShown,
+ ],
+ );
+
+ const onMouseOver = useCallback(
+ (e: any) => {
+ if (canActivate) {
+ setDraggingCanvas(layoutId);
+ e.stopPropagation();
+ }
+ },
+ [canActivate, layoutId, setDraggingCanvas],
+ );
+
+ const checkForHighlights = useCallback(
+ (e: MouseEvent) => {
+ if (canvasIsDragging.current) {
+ {
+ if (anvilDnDListenerRef.current && !allowToDrop) {
+ // Render disallow message if dropping is not allowed
+ renderDisallowDroppingUI(anvilDnDListenerRef.current);
+ return;
+ }
+ // Get the closest highlight based on the mouse position
+ const processedHighlight = getClosestHighlight(
+ {
+ x: e.offsetX - layoutCompensatorValues.left,
+ y: e.offsetY - layoutCompensatorValues.top,
+ },
+ allHighlightsRef.current,
+ );
+ if (processedHighlight) {
+ currentSelectedHighlight.current = processedHighlight;
+ throttledSetHighlight();
+ }
+ }
+ }
+ },
+ [allowToDrop, layoutCompensatorValues, throttledSetHighlight],
+ );
+
+ const onMouseMove = useCallback(
+ (e: any) => {
+ if (!canActivate) {
+ return;
+ }
+ if (isCurrentDraggedCanvas) {
+ // dragging state is set and the canvas is already being used to drag
+ if (canvasIsDragging.current) {
+ checkForHighlights(e);
+ } else {
+ // first move after dragging state is set
+ calculateHighlights();
+ canvasIsDragging.current = true;
+ requestAnimationFrame(() => onMouseMove(e));
+ }
+ } else {
+ // first move to set the dragging state
+ onMouseOver(e);
+ }
+ },
+ [
+ activateOverlayWidgetDrop,
+ allowToDrop,
+ calculateHighlights,
+ canActivate,
+ isCurrentDraggedCanvas,
+ isDragging,
+ layoutCompensatorValues,
+ onMouseOver,
+ throttledSetHighlight,
+ ],
+ );
+
+ const onMouseOut = useCallback(() => {
+ setDraggingCanvas("");
+ }, [setDraggingCanvas]);
+ return {
+ onMouseMove,
+ onMouseOver,
+ onMouseOut,
+ onMouseUp,
+ resetCanvasState,
+ };
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEvents.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEvents.ts
new file mode 100644
index 0000000000..e6d47cd6bd
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDEvents.ts
@@ -0,0 +1,111 @@
+import type React from "react";
+import { useEffect, useRef } from "react";
+import type { AnvilHighlightingCanvasProps } from "layoutSystems/anvil/editor/canvasArenas/AnvilHighlightingCanvas";
+import type { AnvilHighlightInfo } from "layoutSystems/anvil/utils/anvilTypes";
+import { useAnvilDnDEventCallbacks } from "./useAnvilDnDEventCallbacks";
+import { removeDisallowDroppingsUI } from "../utils/utils";
+
+/**
+ * Hook to handle Anvil DnD events
+ */
+export const useAnvilDnDEvents = (
+ anvilDnDListenerRef: React.RefObject,
+ props: AnvilHighlightingCanvasProps,
+ setHighlightShown: (highlight: AnvilHighlightInfo | null) => void,
+) => {
+ const { anvilDragStates, deriveAllHighlightsFn, layoutId, onDrop } = props;
+ const {
+ activateOverlayWidgetDrop,
+ canActivate,
+ isCurrentDraggedCanvas,
+ isDragging,
+ } = anvilDragStates;
+
+ /**
+ * Ref to store highlights derived in real time once dragging starts
+ */
+ const canvasIsDragging = useRef(false);
+
+ useEffect(() => {
+ // Effect to handle changes in isCurrentDraggedCanvas
+ if (anvilDnDListenerRef.current) {
+ if (!isCurrentDraggedCanvas) {
+ removeDisallowDroppingsUI(anvilDnDListenerRef.current);
+ canvasIsDragging.current = false;
+ setHighlightShown(null);
+ }
+ }
+ }, [isCurrentDraggedCanvas]);
+ const { onMouseMove, onMouseOut, onMouseOver, onMouseUp, resetCanvasState } =
+ useAnvilDnDEventCallbacks({
+ anvilDragStates,
+ anvilDnDListenerRef,
+ canvasIsDragging,
+ deriveAllHighlightsFn,
+ layoutId,
+ onDrop,
+ setHighlightShown,
+ });
+ useEffect(() => {
+ if (anvilDnDListenerRef.current && isDragging) {
+ // Initialize listeners
+ anvilDnDListenerRef.current?.addEventListener("mouseenter", onMouseOver);
+ anvilDnDListenerRef.current.addEventListener("mouseover", onMouseOver);
+ anvilDnDListenerRef.current.addEventListener("mouseleave", onMouseOut);
+ anvilDnDListenerRef.current.addEventListener("mouseout", onMouseOut);
+ anvilDnDListenerRef.current?.addEventListener(
+ "mousemove",
+ onMouseMove,
+ false,
+ );
+ anvilDnDListenerRef.current?.addEventListener(
+ "mouseup",
+ onMouseUp,
+ false,
+ );
+ // To make sure drops on the main canvas boundary buffer are processed in the capturing phase.
+ document.addEventListener("mouseup", onMouseUp, true);
+
+ return () => {
+ anvilDnDListenerRef.current?.removeEventListener(
+ "mouseover",
+ onMouseOver,
+ );
+ anvilDnDListenerRef.current?.removeEventListener(
+ "mouseenter",
+ onMouseOver,
+ );
+ anvilDnDListenerRef.current?.removeEventListener(
+ "mouseleave",
+ onMouseOut,
+ );
+ anvilDnDListenerRef.current?.removeEventListener(
+ "mouseout",
+ onMouseOut,
+ );
+ // Cleanup listeners on component unmount
+ anvilDnDListenerRef.current?.removeEventListener(
+ "mousemove",
+ onMouseMove,
+ );
+ anvilDnDListenerRef.current?.removeEventListener("mouseup", onMouseUp);
+ document.removeEventListener("mouseup", onMouseUp, true);
+ };
+ } else {
+ canvasIsDragging.current = false;
+ // Reset canvas state if not dragging
+ resetCanvasState();
+ }
+ }, [
+ isDragging,
+ onMouseMove,
+ onMouseOut,
+ onMouseOver,
+ onMouseUp,
+ resetCanvasState,
+ ]);
+
+ return {
+ showDnDListener: isDragging && !activateOverlayWidgetDrop && canActivate,
+ };
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDStates.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDListenerStates.ts
similarity index 58%
rename from app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDStates.ts
rename to app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDListenerStates.ts
index 4b221107da..ae6199cbfe 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDStates.ts
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilDnDListenerStates.ts
@@ -1,32 +1,31 @@
-import { MAIN_CONTAINER_WIDGET_ID } from "constants/WidgetConstants";
-import type { AppState } from "@appsmith/reducers";
-import { getDragDetails, getWidgets } from "sagas/selectors";
import { useSelector } from "react-redux";
import type { DragDetails } from "reducers/uiReducers/dragResizeReducer";
-import { useMemo } from "react";
import { getSelectedWidgets } from "selectors/ui";
import {
type DraggedWidget,
LayoutComponentTypes,
} from "layoutSystems/anvil/utils/anvilTypes";
-import { getDropTargetLayoutId } from "layoutSystems/anvil/integrations/selectors";
import type { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsReducer";
-import { getLayoutElementPositions } from "layoutSystems/common/selectors";
import type { LayoutElementPositions } from "layoutSystems/common/types";
import { areWidgetsWhitelisted } from "layoutSystems/anvil/utils/layouts/whitelistUtils";
import { AnvilDropTargetTypesEnum, type AnvilDragMeta } from "../types";
-import { getDraggedBlocks, getDraggedWidgetTypes } from "./utils";
+import { canActivateCanvasForDraggedWidget } from "../utils/utils";
+import { useAnvilDnDCompensators } from "./useAnvilDnDCompensators";
+import { getWidgetHierarchy } from "layoutSystems/anvil/utils/paste/utils";
+import type { AnvilGlobalDnDStates } from "../../canvas/hooks/useAnvilGlobalDnDStates";
+import { getWidgets } from "sagas/selectors";
-interface AnvilDnDStatesProps {
+interface AnvilDnDListenerStatesProps {
+ anvilGlobalDragStates: AnvilGlobalDnDStates;
allowedWidgetTypes: string[];
- canvasId: string;
+ widgetId: string;
layoutId: string;
layoutType: LayoutComponentTypes;
}
-
-export interface AnvilDnDStates {
+export interface AnvilDnDListenerStates {
activateOverlayWidgetDrop: boolean;
allowToDrop: boolean;
+ canActivate: boolean;
draggedBlocks: DraggedWidget[];
dragDetails: DragDetails;
isCurrentDraggedCanvas: boolean;
@@ -35,6 +34,19 @@ export interface AnvilDnDStates {
layoutElementPositions: LayoutElementPositions;
dragMeta: AnvilDragMeta;
mainCanvasLayoutId: string;
+ widgetCompensatorValues: {
+ left: number;
+ top: number;
+ };
+ edgeCompensatorValues: {
+ left: number;
+ top: number;
+ };
+ layoutCompensatorValues: {
+ left: number;
+ top: number;
+ };
+ zIndex: number;
}
/**
@@ -63,35 +75,36 @@ const checkIfWidgetTypeDraggedIsAllowedToDrop = (
return areWidgetsWhitelisted(draggedWidgetTypes, allowedWidgetTypes);
};
-export const useAnvilDnDStates = ({
+export const useAnvilDnDListenerStates = ({
allowedWidgetTypes,
+ anvilGlobalDragStates,
layoutId,
layoutType,
-}: AnvilDnDStatesProps): AnvilDnDStates => {
- const mainCanvasLayoutId: string = useSelector((state) =>
- getDropTargetLayoutId(state, MAIN_CONTAINER_WIDGET_ID),
- );
- const layoutElementPositions = useSelector(getLayoutElementPositions);
+ widgetId,
+}: AnvilDnDListenerStatesProps): AnvilDnDListenerStates => {
+ const {
+ activateOverlayWidgetDrop,
+ dragDetails,
+ draggedBlocks,
+ draggedWidgetHierarchy,
+ draggedWidgetTypes,
+ isDragging,
+ isNewWidget,
+ layoutElementPositions,
+ mainCanvasLayoutId,
+ } = anvilGlobalDragStates;
const allWidgets = useSelector(getWidgets);
+ const widgetProps = allWidgets[widgetId];
const selectedWidgets = useSelector(getSelectedWidgets);
- // dragDetails contains of info needed for a container jump:
- // which parent the dragging widget belongs,
- // which canvas is active(being dragged on),
- // which widget is grabbed while dragging started,
- // relative position of mouse pointer wrt to the last grabbed widget.
- const dragDetails: DragDetails = useSelector(getDragDetails);
- const isDragging = useSelector(
- (state: AppState) => state.ui.widgetDragResize.isDragging,
- );
-
- const { dragGroupActualParent: dragParent, newWidget } = dragDetails;
- /**
- * boolean to indicate if the widget being dragged is a new widget
- */
- const isNewWidget = !!newWidget && !dragParent;
/**
* boolean to indicate if the widget is being dragged on this particular canvas.
*/
+ const currentWidgetHierarchy = getWidgetHierarchy(widgetProps.type, widgetId);
+ const canActivate = canActivateCanvasForDraggedWidget(
+ draggedWidgetHierarchy,
+ widgetProps.widgetId,
+ widgetProps.type,
+ );
const isCurrentDraggedCanvas = dragDetails.draggedOn === layoutId;
/**
* boolean to indicate if the widgets being dragged are all allowed to drop in this particular canvas.
@@ -106,40 +119,34 @@ export const useAnvilDnDStates = ({
selectedWidgets,
allWidgets,
);
- // process drag blocks only once and per first render
- // this is by taking advantage of the fact that isNewWidget and dragDetails are unchanged states during the dragging action.
- const draggedBlocks = useMemo(
- () =>
- isDragging
- ? getDraggedBlocks(
- isNewWidget,
- dragDetails,
- selectedWidgets,
- allWidgets,
- )
- : [],
- [isDragging, selectedWidgets],
- );
- /**
- * boolean that indicates if the widget being dragged in an overlay widget like the Modal widget.
- */
- const activateOverlayWidgetDrop =
- isNewWidget && newWidget.detachFromLayout === true;
const isMainCanvas: boolean = layoutId === mainCanvasLayoutId;
const isSection: boolean = layoutType === LayoutComponentTypes.SECTION;
- const draggedWidgetTypes = useMemo(
- () => getDraggedWidgetTypes(draggedBlocks),
- [draggedBlocks],
- );
const draggedOn = isMainCanvas
? AnvilDropTargetTypesEnum.MAIN_CANVAS
: isSection
? AnvilDropTargetTypesEnum.SECTION
: AnvilDropTargetTypesEnum.ZONE;
+ const isEmptyLayout =
+ (widgetProps.children || []).filter(
+ (each) => !allWidgets[each].detachFromLayout,
+ ).length === 0;
+ const {
+ edgeCompensatorValues,
+ layoutCompensatorValues,
+ widgetCompensatorValues,
+ zIndex,
+ } = useAnvilDnDCompensators(
+ canActivate,
+ draggedWidgetHierarchy,
+ currentWidgetHierarchy,
+ isEmptyLayout,
+ widgetProps,
+ );
return {
activateOverlayWidgetDrop,
allowToDrop,
+ canActivate,
draggedBlocks,
dragDetails,
dragMeta: {
@@ -151,5 +158,9 @@ export const useAnvilDnDStates = ({
isNewWidget,
mainCanvasLayoutId,
layoutElementPositions,
+ widgetCompensatorValues,
+ edgeCompensatorValues,
+ layoutCompensatorValues,
+ zIndex,
};
};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilWidgetDrop.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilWidgetDrop.ts
index 806e29dc97..46cf0d2a6f 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilWidgetDrop.ts
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useAnvilWidgetDrop.ts
@@ -6,16 +6,21 @@ import {
import type { AnvilHighlightInfo } from "layoutSystems/anvil/utils/anvilTypes";
import { useCallback } from "react";
import { useDispatch } from "react-redux";
-import type { AnvilDnDStates } from "./useAnvilDnDStates";
+import type { AnvilDnDListenerStates } from "./useAnvilDnDListenerStates";
import { anvilWidgets } from "widgets/anvil/constants";
export const useAnvilWidgetDrop = (
canvasId: string,
- anvilDragStates: AnvilDnDStates,
+ anvilDragStates: AnvilDnDListenerStates,
) => {
const dispatch = useDispatch();
- const { dragDetails, dragMeta, isNewWidget, layoutElementPositions } =
- anvilDragStates;
+ const {
+ dragDetails,
+ draggedBlocks,
+ dragMeta,
+ isNewWidget,
+ layoutElementPositions,
+ } = anvilDragStates;
const generateNewWidgetBlock = useCallback(() => {
const { newWidget } = dragDetails;
const isSectionWidget = newWidget.type === anvilWidgets.SECTION_WIDGET;
@@ -36,17 +41,15 @@ export const useAnvilWidgetDrop = (
addNewAnvilWidgetAction(newWidgetBlock, renderedBlock, dragMeta),
);
} else {
- const sortDraggedBlocksByPosition = anvilDragStates.draggedBlocks.sort(
- (a, b) => {
- const aPos = layoutElementPositions[a.widgetId];
- const bPos = layoutElementPositions[b.widgetId];
- // sort by left then top
- if (aPos.left === bPos.left) {
- return aPos.top - bPos.top;
- }
- return aPos.left - bPos.left;
- },
- );
+ const sortDraggedBlocksByPosition = draggedBlocks.sort((a, b) => {
+ const aPos = layoutElementPositions[a.widgetId];
+ const bPos = layoutElementPositions[b.widgetId];
+ // sort by left then top
+ if (aPos.left === bPos.left) {
+ return aPos.top - bPos.top;
+ }
+ return aPos.left - bPos.left;
+ });
dispatch(
moveAnvilWidgets(renderedBlock, sortDraggedBlocksByPosition, dragMeta),
);
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useCanvasDragging.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useCanvasDragging.ts
deleted file mode 100644
index 50f553c432..0000000000
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/useCanvasDragging.ts
+++ /dev/null
@@ -1,356 +0,0 @@
-import type React from "react";
-import { useEffect, useRef } from "react";
-import type { AnvilHighlightingCanvasProps } from "layoutSystems/anvil/editor/canvasArenas/AnvilHighlightingCanvas";
-import { useCanvasDragToScroll } from "layoutSystems/common/canvasArenas/useCanvasDragToScroll";
-import type { AnvilHighlightInfo } from "layoutSystems/anvil/utils/anvilTypes";
-import { getAbsolutePixels } from "utils/helpers";
-import { getNearestParentCanvas } from "utils/generators";
-import { getClosestHighlight } from "./utils";
-import { AnvilCanvasZIndex } from "layoutSystems/anvil/editor/canvas/hooks/useCanvasActivation";
-import { AnvilReduxActionTypes } from "layoutSystems/anvil/integrations/actions/actionTypes";
-import { useDispatch } from "react-redux";
-import { throttle } from "lodash";
-import { PADDING_FOR_HORIZONTAL_HIGHLIGHT } from "layoutSystems/anvil/utils/constants";
-import memoize from "micro-memoize";
-
-const setHighlightsDrawn = (highlight?: AnvilHighlightInfo) => {
- return {
- type: AnvilReduxActionTypes.ANVIL_SET_HIGHLIGHT_SHOWN,
- payload: {
- highlight,
- },
- };
-};
-
-/**
- * Function to render UX to denote that the widget type cannot be dropped in the layout
- */
-const renderDisallowOnCanvas = (slidingArena: HTMLDivElement) => {
- slidingArena.style.backgroundColor = "#EB714D";
- slidingArena.style.color = "white";
- slidingArena.innerText = "This Layout doesn't support the widget";
-
- slidingArena.style.textAlign = "center";
- slidingArena.style.opacity = "0.8";
-};
-
-const getDropIndicatorColor = memoize(() => {
- const rootStyles = getComputedStyle(document.documentElement);
- return rootStyles.getPropertyValue("--anvil-drop-indicator");
-});
-
-/**
- * Function to stroke a rectangle on the canvas that looks like a highlight/drop area.
- */
-const renderBlocksOnCanvas = (
- stickyCanvas: HTMLCanvasElement,
- blockToRender: AnvilHighlightInfo,
- shouldDraw: boolean,
-) => {
- if (!shouldDraw) {
- return;
- }
- // Calculating offset based on the position of the canvas
- const topOffset = getAbsolutePixels(stickyCanvas.style.top);
- const leftOffset = getAbsolutePixels(stickyCanvas.style.left);
- const dropIndicatorColor = getDropIndicatorColor();
- const canvasCtx = stickyCanvas.getContext("2d") as CanvasRenderingContext2D;
-
- // Clearing previous drawings on the canvas
- canvasCtx.clearRect(0, 0, stickyCanvas.width, stickyCanvas.height);
- canvasCtx.beginPath();
- // Extracting dimensions of the block to render
- const { height, posX, posY, width } = blockToRender;
- // using custom function to draw a rounded rectangle to achieve more sharper rounder corners
- const horizontalPadding = blockToRender.isVertical
- ? 0
- : PADDING_FOR_HORIZONTAL_HIGHLIGHT;
- const verticalPadding = blockToRender.isVertical
- ? PADDING_FOR_HORIZONTAL_HIGHLIGHT / 2
- : 0;
- canvasCtx.roundRect(
- posX - leftOffset + horizontalPadding,
- posY - topOffset + verticalPadding,
- width - horizontalPadding * 2,
- height - verticalPadding * 2,
- 2,
- );
- canvasCtx.fillStyle = dropIndicatorColor;
- canvasCtx.fill();
- canvasCtx.closePath();
-};
-
-/**
- *
- * This hook is written to accumulate all logic that is needed to
- * - initialize event listeners for canvas
- * - adjust z-index of canvas
- * - track mouse position on canvas
- * - render highlights on the canvas
- * - render warning to denote that a particular widget type is not allowed to drop on canvas
- * - auto scroll canvas when needed.
- * - invoke onDrop callback as per the anvilDragStates
- */
-export const useCanvasDragging = (
- slidingArenaRef: React.RefObject,
- stickyCanvasRef: React.RefObject,
- props: AnvilHighlightingCanvasProps,
-) => {
- const { anvilDragStates, deriveAllHighlightsFn, onDrop } = props;
- const {
- activateOverlayWidgetDrop,
- allowToDrop,
- draggedBlocks,
- isCurrentDraggedCanvas,
- isDragging,
- layoutElementPositions,
- mainCanvasLayoutId,
- } = anvilDragStates;
- const dispatch = useDispatch();
- /**
- * Provides auto-scroll functionality
- */
- const canScroll = useCanvasDragToScroll(
- slidingArenaRef,
- isCurrentDraggedCanvas && !activateOverlayWidgetDrop,
- isDragging,
- );
-
- /**
- * Ref to store highlights derived in real time once dragging starts
- */
- const allHighlightsRef = useRef([] as AnvilHighlightInfo[]);
-
- /**
- * Function to calculate and store highlights
- */
- const calculateHighlights = () => {
- if (activateOverlayWidgetDrop) {
- allHighlightsRef.current = [];
- } else {
- allHighlightsRef.current = deriveAllHighlightsFn(
- layoutElementPositions,
- draggedBlocks,
- )?.highlights;
- }
- };
- const canvasIsDragging = useRef(false);
-
- useEffect(() => {
- // Effect to handle changes in isCurrentDraggedCanvas
- if (stickyCanvasRef.current && slidingArenaRef.current) {
- if (!isCurrentDraggedCanvas) {
- // If not currently dragged, reset the canvas and styles
- const canvasCtx = stickyCanvasRef.current.getContext(
- "2d",
- ) as CanvasRenderingContext2D;
- canvasCtx.clearRect(
- 0,
- 0,
- stickyCanvasRef.current.width,
- stickyCanvasRef.current.height,
- );
- slidingArenaRef.current.style.zIndex = AnvilCanvasZIndex.deactivated;
- stickyCanvasRef.current.style.zIndex = AnvilCanvasZIndex.deactivated;
- slidingArenaRef.current.style.backgroundColor = "unset";
- slidingArenaRef.current.style.color = "unset";
- slidingArenaRef.current.innerText = "";
- canvasIsDragging.current = false;
- } else {
- // If currently dragged, set the z-index to activate the canvas
- slidingArenaRef.current.style.zIndex = AnvilCanvasZIndex.activated;
- stickyCanvasRef.current.style.zIndex = AnvilCanvasZIndex.activated;
- }
- }
- }, [isCurrentDraggedCanvas]);
-
- useEffect(() => {
- if (slidingArenaRef.current && isDragging) {
- const scrollParent: Element | null = getNearestParentCanvas(
- slidingArenaRef.current,
- );
-
- let currentRectanglesToDraw: AnvilHighlightInfo;
- const scrollObj: any = {};
- const resetCanvasState = () => {
- // Resetting the canvas state when necessary
- if (stickyCanvasRef.current && slidingArenaRef.current) {
- const canvasCtx = stickyCanvasRef.current.getContext(
- "2d",
- ) as CanvasRenderingContext2D;
- canvasCtx.clearRect(
- 0,
- 0,
- stickyCanvasRef.current.width,
- stickyCanvasRef.current.height,
- );
- slidingArenaRef.current.style.zIndex = AnvilCanvasZIndex.deactivated;
- slidingArenaRef.current.style.backgroundColor = "unset";
- slidingArenaRef.current.style.color = "unset";
- slidingArenaRef.current.innerText = "";
- canvasIsDragging.current = false;
- dispatch(setHighlightsDrawn());
- }
- };
-
- if (isDragging) {
- const onMouseUp = () => {
- if (
- isDragging &&
- canvasIsDragging.current &&
- currentRectanglesToDraw &&
- !currentRectanglesToDraw.existingPositionHighlight &&
- allowToDrop
- ) {
- // Invoke onDrop callback with the appropriate highlight info
- onDrop(currentRectanglesToDraw);
- }
- resetCanvasState();
- };
-
- const onFirstMoveOnCanvas = (e: MouseEvent) => {
- if (
- isCurrentDraggedCanvas &&
- isDragging &&
- !canvasIsDragging.current &&
- slidingArenaRef.current
- ) {
- // Calculate highlights when the mouse enters the canvas
- calculateHighlights();
- canvasIsDragging.current = true;
- onMouseMove(e);
- }
- };
- // make sure rendering highlights on canvas and highlighting cell happens once every 50ms
- const throttledRenderOnCanvas = throttle(
- () => {
- if (
- stickyCanvasRef.current &&
- canvasIsDragging.current &&
- isCurrentDraggedCanvas
- ) {
- dispatch(setHighlightsDrawn(currentRectanglesToDraw));
- // Render blocks on the canvas based on the highlight
- renderBlocksOnCanvas(
- stickyCanvasRef.current,
- currentRectanglesToDraw,
- canvasIsDragging.current,
- );
- }
- },
- 50,
- {
- leading: true,
- trailing: true,
- },
- );
-
- const onMouseMove = (e: any) => {
- if (
- isCurrentDraggedCanvas &&
- canvasIsDragging.current &&
- slidingArenaRef.current &&
- stickyCanvasRef.current
- ) {
- if (!allowToDrop) {
- // Render disallow message if dropping is not allowed
- renderDisallowOnCanvas(slidingArenaRef.current);
- return;
- }
- // Get the closest highlight based on the mouse position
- const processedHighlight = getClosestHighlight(
- e,
- allHighlightsRef.current,
- );
- if (processedHighlight) {
- currentRectanglesToDraw = processedHighlight;
- throttledRenderOnCanvas();
- // Store information for auto-scroll functionality
- scrollObj.lastMouseMoveEvent = {
- offsetX: e.offsetX,
- offsetY: e.offsetY,
- };
- scrollObj.lastScrollTop = scrollParent?.scrollTop;
- scrollObj.lastScrollHeight = scrollParent?.scrollHeight;
- }
- } else {
- // Call onFirstMoveOnCanvas for the initial move on the canvas
- onFirstMoveOnCanvas(e);
- }
- };
-
- // Adding setTimeout to make sure this gets called after
- // the onscroll that resets intersectionObserver in StickyCanvasArena.tsx
- const onScroll = () =>
- setTimeout(() => {
- const { lastMouseMoveEvent, lastScrollHeight, lastScrollTop } =
- scrollObj;
- if (
- lastMouseMoveEvent &&
- lastScrollHeight &&
- lastScrollTop &&
- scrollParent &&
- canScroll.current
- ) {
- // Adjusting mouse position based on scrolling for auto-scroll
- const delta =
- scrollParent?.scrollHeight +
- scrollParent?.scrollTop -
- (lastScrollHeight + lastScrollTop);
- onMouseMove({
- offsetX: lastMouseMoveEvent.offsetX,
- offsetY: lastMouseMoveEvent.offsetY + delta,
- });
- }
- }, 0);
-
- if (
- slidingArenaRef.current &&
- stickyCanvasRef.current &&
- scrollParent
- ) {
- // Initialize listeners
- slidingArenaRef.current?.addEventListener(
- "mousemove",
- onMouseMove,
- false,
- );
- slidingArenaRef.current?.addEventListener(
- "mouseup",
- onMouseUp,
- false,
- );
- // To make sure drops on the main canvas boundary buffer are processed in the capturing phase.
- document.addEventListener("mouseup", onMouseUp, true);
- scrollParent?.addEventListener("scroll", onScroll, false);
- }
-
- return () => {
- // Cleanup listeners on component unmount
- slidingArenaRef.current?.removeEventListener(
- "mousemove",
- onMouseMove,
- );
- slidingArenaRef.current?.removeEventListener("mouseup", onMouseUp);
- document.removeEventListener("mouseup", onMouseUp, true);
- scrollParent?.removeEventListener("scroll", onScroll);
- };
- } else {
- // Reset canvas state if not dragging
- resetCanvasState();
- }
- }
- }, [
- isDragging,
- allowToDrop,
- draggedBlocks,
- isCurrentDraggedCanvas,
- isDragging,
- layoutElementPositions,
- mainCanvasLayoutId,
- ]);
-
- return {
- showCanvas: isDragging && !activateOverlayWidgetDrop,
- };
-};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/dndCompensatorUtils.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/dndCompensatorUtils.ts
new file mode 100644
index 0000000000..93df83465e
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/dndCompensatorUtils.ts
@@ -0,0 +1,325 @@
+import type { Token } from "@design-system/theming";
+import type { AnvilHighlightInfo } from "layoutSystems/anvil/utils/anvilTypes";
+import { HIGHLIGHT_SIZE } from "layoutSystems/anvil/utils/constants";
+import { EMPTY_MODAL_PADDING } from "../AnvilModalDropArena";
+
+/**
+ * DnD Compensation spacing tokens
+ *
+ * main canvas (Aligned Column layout component) using the value spacing-4 which is set via dsl transformer
+ * section widget (WDS widget) - no tokens currently, however we extend the DnD layer on both sides of the section inorder to be able to show highlights and catch mouse movements.
+ * zone widget spacing when elevated (WDS widget) - uses the --outer-spacing-3 value which is set on the widget from the container component.
+ * modal component body top spacing (WDS component) - uses the --outer-spacing-2 value which is set on the WDS component
+ * modal component body left spacing (WDS component) - uses the --outer-spacing-4 value which is set on the WDS component
+ *
+ * ToDo(#32983): These values are hardcoded here for now.
+ *
+ * Ideally they should be coming from a constant or from the entity it-selves as a property to the drag and drop layer.
+ * But we have DnD rendering on the layout component and each of these entities are defining there spacing in different places.
+ */
+const CompensationSpacingTokens = {
+ MAIN_CANVAS: "4",
+ ZONE: "3",
+ MODAL_TOP: "2",
+ MODAL_LEFT: "4",
+};
+
+const extractFloatValuesOutOfToken = (token: Token) => {
+ if (token) {
+ return parseFloat(token.value + "");
+ }
+ return 0;
+};
+
+/**
+ * Get widget spacing CSS variable values
+ */
+const getWidgetSpacingCSSVariableValues = (outerSpacingTokens: {
+ [key: string]: Token;
+}) => {
+ return {
+ mainCanvasSpacing: extractFloatValuesOutOfToken(
+ outerSpacingTokens[CompensationSpacingTokens.MAIN_CANVAS],
+ ),
+ modalSpacing: {
+ top: extractFloatValuesOutOfToken(
+ outerSpacingTokens[CompensationSpacingTokens.MODAL_TOP],
+ ),
+ left: extractFloatValuesOutOfToken(
+ outerSpacingTokens[CompensationSpacingTokens.MODAL_LEFT],
+ ),
+ },
+ zoneSpacing: extractFloatValuesOutOfToken(
+ outerSpacingTokens[CompensationSpacingTokens.ZONE],
+ ),
+ };
+};
+
+/**
+ * Get compensators for the main canvas widget
+ */
+const getMainCanvasCompensators = (
+ isEmptyLayout: boolean,
+ mainCanvasSpacing: number,
+) => {
+ const widgetCompensatorValues = {
+ left: 0,
+ top: 0,
+ };
+ const edgeCompensatorValues = {
+ left: isEmptyLayout ? -mainCanvasSpacing : 0,
+ top: isEmptyLayout ? -mainCanvasSpacing : mainCanvasSpacing,
+ };
+ const layoutCompensatorValues = {
+ left: 0,
+ top: 0,
+ };
+ return {
+ widgetCompensatorValues,
+ edgeCompensatorValues,
+ layoutCompensatorValues,
+ };
+};
+
+/**
+ * Get compensators for the section widget
+ */
+const getSectionCompensators = (mainCanvasSpacing: number) => {
+ const widgetCompensatorValues = {
+ left: mainCanvasSpacing,
+ top: 0,
+ };
+ const edgeCompensatorValues = {
+ left: HIGHLIGHT_SIZE * 2,
+ top: 0,
+ };
+ return {
+ widgetCompensatorValues,
+ edgeCompensatorValues,
+ layoutCompensatorValues: widgetCompensatorValues,
+ };
+};
+/**
+ * Get compensators for the modal widget
+ */
+const getModalCompensators = (
+ isEmptyLayout: boolean,
+ modalSpacing: {
+ top: number;
+ left: number;
+ },
+) => {
+ const widgetCompensatorValues = {
+ left: 0,
+ top: isEmptyLayout ? 0 : modalSpacing.top,
+ };
+ const layoutCompensatorValues = {
+ left: isEmptyLayout ? EMPTY_MODAL_PADDING : 0,
+ top: isEmptyLayout ? EMPTY_MODAL_PADDING : modalSpacing.top,
+ };
+ return {
+ widgetCompensatorValues,
+ edgeCompensatorValues: widgetCompensatorValues,
+ layoutCompensatorValues,
+ };
+};
+
+/**
+ * Get compensators for the zone widget
+ */
+const getZoneCompensators = (
+ zoneSpacing: number,
+ isElevatedWidget: boolean,
+) => {
+ const widgetCompensatorValues = {
+ left: 0,
+ top: 0,
+ };
+ const edgeCompensatorValues = isElevatedWidget
+ ? {
+ left: zoneSpacing,
+ top: zoneSpacing,
+ }
+ : {
+ left: HIGHLIGHT_SIZE / 2,
+ top: HIGHLIGHT_SIZE / 2,
+ };
+ const layoutCompensatorValues = isElevatedWidget
+ ? {
+ left: zoneSpacing,
+ top: zoneSpacing,
+ }
+ : {
+ left: 0,
+ top: 0,
+ };
+ return {
+ widgetCompensatorValues,
+ edgeCompensatorValues,
+ layoutCompensatorValues,
+ };
+};
+
+/**
+ * Get compensators for the widget based on the hierarchy
+ */
+export const getCompensatorsForHierarchy = (
+ hierarchy: number,
+ isEmptyLayout: boolean,
+ isElevatedWidget: boolean,
+ outerSpacingTokens:
+ | {
+ [key: string]: Token;
+ }
+ | undefined,
+) => {
+ if (!outerSpacingTokens) {
+ return {
+ widgetCompensatorValues: {
+ left: 0,
+ top: 0,
+ },
+ edgeCompensatorValues: {
+ left: 0,
+ top: 0,
+ },
+ layoutCompensatorValues: {
+ left: 0,
+ top: 0,
+ },
+ };
+ }
+ const { mainCanvasSpacing, modalSpacing, zoneSpacing } =
+ getWidgetSpacingCSSVariableValues(outerSpacingTokens);
+ /**
+ * Get compensators based on hierarchy
+ * widgetCompensatorValues - compensates for the widget's additional dragging space outside widget and its layout ( Section Widget)
+ * edgeCompensatorValues - compensates for the highlights at the edges of the layout of the widget (Zone Widget)
+ * layoutCompensatorValues - compensates for the layout's additional dragging space inside widget (Modal Widget)
+ */
+ switch (true) {
+ case hierarchy === 0:
+ return getMainCanvasCompensators(isEmptyLayout, mainCanvasSpacing);
+ case hierarchy === 1:
+ return getModalCompensators(isEmptyLayout, modalSpacing);
+ case hierarchy === 2:
+ return getSectionCompensators(mainCanvasSpacing);
+ case hierarchy === 3:
+ return getZoneCompensators(zoneSpacing, isElevatedWidget);
+ default:
+ return {
+ widgetCompensatorValues: {
+ left: 0,
+ top: 0,
+ },
+ edgeCompensatorValues: {
+ left: 0,
+ top: 0,
+ },
+ layoutCompensatorValues: {
+ left: 0,
+ top: 0,
+ },
+ };
+ }
+};
+
+/**
+ * Calculate the top offset based on the edge details
+ */
+const calculateEdgeTopOffset = (
+ isVertical: boolean,
+ isTopEdge: boolean,
+ isBottomEdge: boolean,
+ topGap: number,
+) => {
+ return !isVertical ? (isTopEdge ? -topGap : isBottomEdge ? topGap : 0) : 0;
+};
+
+/**
+ * Calculate the left offset based on the edge details
+ */
+const calculateEdgeLeftOffset = (
+ isVertical: boolean,
+ isLeftEdge: boolean,
+ isRightEdge: boolean,
+ leftGap: number,
+) => {
+ return isVertical ? (isLeftEdge ? -leftGap : isRightEdge ? leftGap : 0) : 0;
+};
+
+/**
+ * Get the edge compensating offset values
+ */
+const getEdgeCompensatingOffsetValues = (
+ highlight: AnvilHighlightInfo,
+ highlightCompensatorValues: {
+ top: number;
+ left: number;
+ },
+) => {
+ const {
+ edgeDetails,
+ height: highlightHeight,
+ isVertical,
+ width: highlightWidth,
+ } = highlight;
+ const compensatorTop = highlightCompensatorValues.top;
+ const compensatorLeft = highlightCompensatorValues.left;
+ const {
+ bottom: isBottomEdge,
+ left: isLeftEdge,
+ right: isRightEdge,
+ top: isTopEdge,
+ } = edgeDetails;
+ const topGap = (compensatorTop + highlightHeight) * 0.5;
+ const leftGap = (compensatorLeft + highlightWidth) * 0.5;
+ const topOffset = calculateEdgeTopOffset(
+ isVertical,
+ isTopEdge,
+ isBottomEdge,
+ topGap,
+ );
+ const leftOffset = calculateEdgeLeftOffset(
+ isVertical,
+ isLeftEdge,
+ isRightEdge,
+ leftGap,
+ );
+ return {
+ topOffset,
+ leftOffset,
+ };
+};
+
+/**
+ * Get the position compensated highlight
+ */
+export const getPositionCompensatedHighlight = (
+ highlight: AnvilHighlightInfo,
+ layoutCompensatorValues: {
+ top: number;
+ left: number;
+ },
+ edgeCompensatorValues: {
+ top: number;
+ left: number;
+ },
+): AnvilHighlightInfo => {
+ const layoutCompensatedHighlight = {
+ ...highlight,
+ posX: highlight.posX + layoutCompensatorValues.left,
+ posY: highlight.posY + layoutCompensatorValues.top,
+ };
+ const { posX: left, posY: top } = layoutCompensatedHighlight;
+ const compensatingOffsetValues = getEdgeCompensatingOffsetValues(
+ highlight,
+ edgeCompensatorValues,
+ );
+ const positionUpdatedHighlightInfo = {
+ ...layoutCompensatedHighlight,
+ posX: left + compensatingOffsetValues.leftOffset,
+ posY: top + compensatingOffsetValues.topOffset,
+ };
+ return positionUpdatedHighlightInfo;
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/dndEventUtils.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/dndEventUtils.ts
new file mode 100644
index 0000000000..c2c834e13c
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/dndEventUtils.ts
@@ -0,0 +1,9 @@
+export const resetAnvilDnDListener = (
+ anvilDnDListener: HTMLDivElement | null,
+) => {
+ if (anvilDnDListener) {
+ anvilDnDListener.style.backgroundColor = "unset";
+ anvilDnDListener.style.color = "unset";
+ anvilDnDListener.innerText = "";
+ }
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/utils.test.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/utils.test.ts
similarity index 85%
rename from app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/utils.test.ts
rename to app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/utils.test.ts
index 7595c12665..82ad152ad8 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/utils.test.ts
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/utils.test.ts
@@ -17,6 +17,12 @@ describe("Highlight selection algos", () => {
canvasId: "canvasId",
rowIndex: 0,
layoutOrder: [],
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
},
{
layoutId: "",
@@ -29,6 +35,12 @@ describe("Highlight selection algos", () => {
canvasId: "canvasId",
rowIndex: 1,
layoutOrder: [],
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
},
{
layoutId: "",
@@ -41,6 +53,12 @@ describe("Highlight selection algos", () => {
canvasId: "canvasId",
rowIndex: 0,
layoutOrder: [],
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
},
// Add other highlights as needed...
];
diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/utils.ts b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/utils.ts
similarity index 96%
rename from app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/utils.ts
rename to app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/utils.ts
index 69328834ce..6b5db412e6 100644
--- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/hooks/utils.ts
+++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/utils/utils.ts
@@ -122,16 +122,11 @@ export const getDraggedBlocks = (
};
export const getClosestHighlight = (
- e: MouseEvent,
+ pos: XYCord,
highlights: AnvilHighlightInfo[],
) => {
if (!highlights || !highlights.length) return;
- // Current mouse coordinates.
- const pos: XYCord = {
- x: e.offsetX,
- y: e.offsetY,
- };
/**
* Filter highlights that span the current mouse position.
*/
@@ -330,3 +325,16 @@ function calculateDistance(a: AnvilHighlightInfo, b: XYCord): number {
}
return Math.hypot(distX, distY);
}
+
+/**
+ * Function to render UX to denote that the widget type cannot be dropped in the layout
+ */
+export const renderDisallowDroppingUI = (slidingArena: HTMLDivElement) => {
+ slidingArena.classList.add("disallow-dropping");
+ slidingArena.innerText = "This Layout doesn't support the widget";
+};
+
+export const removeDisallowDroppingsUI = (slidingArena: HTMLDivElement) => {
+ slidingArena.classList.remove("disallow-dropping");
+ slidingArena.innerText = "";
+};
diff --git a/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetHover.ts b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetHover.ts
index 268d4f92d1..678839987d 100644
--- a/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetHover.ts
+++ b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetHover.ts
@@ -1,3 +1,4 @@
+import type { AppState } from "@appsmith/reducers";
import { getAnvilSpaceDistributionStatus } from "layoutSystems/anvil/integrations/selectors";
import { useCallback, useEffect } from "react";
import { useSelector } from "react-redux";
@@ -13,7 +14,9 @@ export const useAnvilWidgetHover = (
const isFocused = useSelector(isCurrentWidgetFocused(widgetId));
const isPreviewMode = useSelector(combinedPreviewModeSelector);
const isDistributingSpace = useSelector(getAnvilSpaceDistributionStatus);
-
+ const isDragging = useSelector(
+ (state: AppState) => state.ui.widgetDragResize.isDragging,
+ );
// Access the focusWidget function from the useWidgetSelection hook
const { focusWidget } = useWidgetSelection();
@@ -24,13 +27,21 @@ export const useAnvilWidgetHover = (
focusWidget &&
!isFocused &&
!isDistributingSpace &&
+ !isDragging &&
!isPreviewMode &&
focusWidget(widgetId);
// Prevent the event from propagating further
e.stopPropagation();
},
- [focusWidget, isFocused, isDistributingSpace, isPreviewMode, widgetId],
+ [
+ focusWidget,
+ isFocused,
+ isDistributingSpace,
+ isPreviewMode,
+ widgetId,
+ isDragging,
+ ],
);
// Callback function for handling mouseleave events
diff --git a/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts
index 102d33bd6b..ab3b5db0be 100644
--- a/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts
+++ b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts
@@ -3,6 +3,7 @@ import { isWidgetSelected } from "selectors/widgetSelectors";
import { useSelector } from "react-redux";
import { useWidgetBorderStyles } from "layoutSystems/anvil/common/hooks/useWidgetBorderStyles";
import type { AppState } from "@appsmith/reducers";
+import { getIsNewWidgetBeingDragged } from "sagas/selectors";
export const useAnvilWidgetStyles = (
widgetId: string,
@@ -35,9 +36,10 @@ export const useAnvilWidgetStyles = (
ref.current.setAttribute("data-testid", isSelected ? "t--selected" : "");
}
}, [widgetName, isSelected]);
-
+ const isNewWidgetDrag = useSelector(getIsNewWidgetBeingDragged);
// Calculate whether the widget should fade based on dragging, selection, and visibility
- const shouldFadeWidget = (isDragging && isSelected) || !isVisible;
+ const shouldFadeWidget =
+ (isDragging && !isNewWidgetDrag && isSelected) || !isVisible;
// Calculate opacity factor based on whether the widget should fade
const opacityFactor = useMemo(() => {
diff --git a/app/client/src/layoutSystems/anvil/integrations/actions/draggingActions.ts b/app/client/src/layoutSystems/anvil/integrations/actions/draggingActions.ts
index 1547a8478f..06c172e869 100644
--- a/app/client/src/layoutSystems/anvil/integrations/actions/draggingActions.ts
+++ b/app/client/src/layoutSystems/anvil/integrations/actions/draggingActions.ts
@@ -10,6 +10,14 @@ import type {
} from "./actionTypes";
import { AnvilReduxActionTypes } from "./actionTypes";
+export const setHighlightsDrawnAction = (highlight?: AnvilHighlightInfo) => {
+ return {
+ type: AnvilReduxActionTypes.ANVIL_SET_HIGHLIGHT_SHOWN,
+ payload: {
+ highlight,
+ },
+ };
+};
/**
* Add new anvil widget to canvas.
*/
diff --git a/app/client/src/layoutSystems/anvil/integrations/modalSelectors.ts b/app/client/src/layoutSystems/anvil/integrations/modalSelectors.ts
new file mode 100644
index 0000000000..8b04d4c27c
--- /dev/null
+++ b/app/client/src/layoutSystems/anvil/integrations/modalSelectors.ts
@@ -0,0 +1,16 @@
+import type { AppState } from "@appsmith/reducers";
+import { getWidgetIdsByType, getWidgetsMeta } from "sagas/selectors";
+import { WDSModalWidget } from "widgets/wds/WDSModalWidget";
+
+export const getCurrentlyOpenAnvilModal = (state: AppState) => {
+ const allExistingModals = getWidgetIdsByType(state, WDSModalWidget.type);
+ if (allExistingModals.length === 0) {
+ return;
+ }
+ const metaWidgets = getWidgetsMeta(state);
+ const currentlyOpenModal = allExistingModals.find((modalId) => {
+ const modal = metaWidgets[modalId];
+ return modal && modal.isVisible;
+ });
+ return currentlyOpenModal;
+};
diff --git a/app/client/src/layoutSystems/anvil/layoutComponents/BaseLayoutComponent.tsx b/app/client/src/layoutSystems/anvil/layoutComponents/BaseLayoutComponent.tsx
index a21f9a3050..41097191ed 100644
--- a/app/client/src/layoutSystems/anvil/layoutComponents/BaseLayoutComponent.tsx
+++ b/app/client/src/layoutSystems/anvil/layoutComponents/BaseLayoutComponent.tsx
@@ -16,7 +16,7 @@ import {
} from "../utils/layouts/layoutUtils";
import { RenderModes } from "constants/WidgetConstants";
import LayoutFactory from "./LayoutFactory";
-import { AnvilCanvasDraggingArena } from "../editor/canvasArenas/AnvilCanvasDraggingArena";
+import { AnvilDraggingArena } from "../editor/canvasArenas/AnvilDraggingArena";
import { FlexLayout, type FlexLayoutProps } from "./components/FlexLayout";
import { defaultHighlightPayload } from "../utils/constants";
@@ -111,9 +111,8 @@ abstract class BaseLayoutComponent extends PureComponent<
this.props;
if (!isDropTarget) return null;
return (
-
);
}
@@ -130,11 +130,7 @@ abstract class BaseLayoutComponent extends PureComponent<
static rendersWidgets: boolean = false;
render(): JSX.Element | null {
- return (
-
- {this.renderContent()}
-
- );
+ return <>{this.renderContent()}>;
}
protected renderContent(): React.ReactNode {
@@ -146,14 +142,18 @@ abstract class BaseLayoutComponent extends PureComponent<
renderEditMode(): JSX.Element {
return (
<>
+ {this.renderViewMode()}
{this.renderDraggingArena()}
- {this.renderChildren()}
>
);
}
renderViewMode(): React.ReactNode {
- return <>{this.renderChildren()}>;
+ return (
+
+ {this.renderChildren()}
+
+ );
}
renderChildren(): React.ReactNode {
diff --git a/app/client/src/layoutSystems/anvil/layoutComponents/components/section/index.tsx b/app/client/src/layoutSystems/anvil/layoutComponents/components/section/index.tsx
index cb95a62686..3f053c587e 100644
--- a/app/client/src/layoutSystems/anvil/layoutComponents/components/section/index.tsx
+++ b/app/client/src/layoutSystems/anvil/layoutComponents/components/section/index.tsx
@@ -39,19 +39,28 @@ class Section extends WidgetRow {
/>
);
}
- renderDraggingArena(): React.ReactNode {
+
+ renderEditMode(): JSX.Element {
return (
<>
- {super.renderDraggingArena()}
- {this.renderSectionSpaceDistributor()}
+ {this.renderDraggingArena()}
+ {this.renderSpaceDistributedSection()}
>
);
}
-
- render(): JSX.Element {
+ renderSpaceDistributedSection(): JSX.Element {
return (
- {this.renderContent()}
+ {this.renderSectionSpaceDistributor()}
+ {super.renderChildren()}
+
+ );
+ }
+
+ renderViewMode(): JSX.Element {
+ return (
+
+ {super.renderChildren()}
);
}
diff --git a/app/client/src/layoutSystems/anvil/layoutComponents/components/zone/index.tsx b/app/client/src/layoutSystems/anvil/layoutComponents/components/zone/index.tsx
index fcd01117cb..45557c2d70 100644
--- a/app/client/src/layoutSystems/anvil/layoutComponents/components/zone/index.tsx
+++ b/app/client/src/layoutSystems/anvil/layoutComponents/components/zone/index.tsx
@@ -45,10 +45,10 @@ class Zone extends AlignedLayoutColumn {
};
}
- render() {
+ renderViewMode() {
return (
- {this.renderContent()}
+ {this.renderChildren()}
);
}
diff --git a/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/SectionSpaceDistributor.tsx b/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/SectionSpaceDistributor.tsx
index bafe7cc511..20bf049164 100644
--- a/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/SectionSpaceDistributor.tsx
+++ b/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/SectionSpaceDistributor.tsx
@@ -2,12 +2,13 @@ import { getLayoutElementPositions } from "layoutSystems/common/selectors";
import type { LayoutElementPosition } from "layoutSystems/common/types";
import React, { useMemo } from "react";
import { useSelector } from "react-redux";
-import { previewModeSelector } from "selectors/editorSelectors";
+import { combinedPreviewModeSelector } from "selectors/editorSelectors";
import type { WidgetLayoutProps } from "../utils/anvilTypes";
import { getWidgetByID } from "sagas/selectors";
import { getDefaultSpaceDistributed } from "./utils/spaceRedistributionSagaUtils";
import { SpaceDistributionHandle } from "./SpaceDistributionHandle";
import { getAnvilZoneBoundaryOffset } from "./utils/spaceDistributionEditorUtils";
+import { getWidgetSelectionBlock } from "selectors/ui";
interface SectionSpaceDistributorProps {
sectionWidgetId: string;
@@ -110,7 +111,8 @@ export const SectionSpaceDistributor = (
props: SectionSpaceDistributorProps,
) => {
const { zones } = props;
- const isPreviewMode = useSelector(previewModeSelector);
+ const isPreviewMode = useSelector(combinedPreviewModeSelector);
+ const isWidgetSelectionBlocked = useSelector(getWidgetSelectionBlock);
const isDragging = useSelector(
(state) => state.ui.widgetDragResize.isDragging,
);
@@ -123,6 +125,7 @@ export const SectionSpaceDistributor = (
const canRedistributeSpace =
!isPreviewMode &&
!isDragging &&
+ !isWidgetSelectionBlocked &&
allZonePositionsAreAvailable &&
zones.length > 1;
return canRedistributeSpace ? (
diff --git a/app/client/src/layoutSystems/anvil/utils/anvilTypes.ts b/app/client/src/layoutSystems/anvil/utils/anvilTypes.ts
index d8f5b77a0c..741fb80cf6 100644
--- a/app/client/src/layoutSystems/anvil/utils/anvilTypes.ts
+++ b/app/client/src/layoutSystems/anvil/utils/anvilTypes.ts
@@ -112,6 +112,12 @@ export interface HighlightRenderInfo {
width: number; // width of the highlight.
posX: number; // x position of the highlight.
posY: number; // y position of the highlight.
+ edgeDetails: {
+ top: boolean; // Whether the highlight is at the top edge of the layout.
+ bottom: boolean; // Whether the highlight is at the bottom edge of the layout.
+ left: boolean; // Whether the highlight is at the left edge of the layout.
+ right: boolean; // Whether the highlight is at the right edge of the layout.
+ };
}
export interface HighlightDropInfo {
diff --git a/app/client/src/layoutSystems/anvil/utils/constants.ts b/app/client/src/layoutSystems/anvil/utils/constants.ts
index f5331cccda..5e0be9284b 100644
--- a/app/client/src/layoutSystems/anvil/utils/constants.ts
+++ b/app/client/src/layoutSystems/anvil/utils/constants.ts
@@ -4,8 +4,8 @@ import { anvilWidgets } from "widgets/anvil/constants";
export const MOBILE_BREAKPOINT = 480;
-export const HIGHLIGHT_SIZE = 4;
-export const PADDING_FOR_HORIZONTAL_HIGHLIGHT = 4;
+export const HIGHLIGHT_SIZE = 2;
+export const PADDING_FOR_HORIZONTAL_HIGHLIGHT = 2;
export const DEFAULT_VERTICAL_HIGHLIGHT_HEIGHT = 60;
export const AlignmentIndexMap: { [key: string]: number } = {
[FlexLayerAlignment.Start]: 0,
@@ -24,6 +24,12 @@ export const defaultHighlightRenderInfo: HighlightRenderInfo = {
posX: 0,
posY: 0,
width: 0,
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
};
// Constants for the minimum and maximum zone count
diff --git a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedColumnHighlights.ts b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedColumnHighlights.ts
index 19bcc0bd43..10063cc2f8 100644
--- a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedColumnHighlights.ts
+++ b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedColumnHighlights.ts
@@ -53,6 +53,12 @@ export const deriveAlignedColumnHighlights =
posY: HIGHLIGHT_SIZE / 2,
rowIndex: 0,
width: 0,
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
};
const hasFillWidget: boolean = draggedWidgets.some(
diff --git a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedRowHighlights.ts b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedRowHighlights.ts
index 0864df4ce5..68ca5277a0 100644
--- a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedRowHighlights.ts
+++ b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/alignedRowHighlights.ts
@@ -66,6 +66,12 @@ export const deriveAlignedRowHighlights =
posY: HIGHLIGHT_SIZE / 2,
rowIndex: 0,
width: HIGHLIGHT_SIZE,
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
};
/**
@@ -376,15 +382,24 @@ function generateHighlight(
layoutDimension.left,
);
}
-
+ const posY = tallestWidget ? tallestWidget.top : layoutDimension.top;
+ const edgeDetails = {
+ top: posY === layoutDimension.top,
+ bottom:
+ posY + HIGHLIGHT_SIZE === layoutDimension.top + layoutDimension.height,
+ left: posX === layoutDimension.left,
+ right:
+ posX + HIGHLIGHT_SIZE === layoutDimension.left + layoutDimension.width,
+ };
return {
...baseHighlight,
layoutId,
alignment,
height: tallestWidget?.height ?? layoutDimension.height,
posX,
- posY: tallestWidget ? tallestWidget?.top : layoutDimension.top,
+ posY,
rowIndex: childCount,
+ edgeDetails,
};
}
diff --git a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/columnHighlights.ts b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/columnHighlights.ts
index cf75a14643..cbb49a3401 100644
--- a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/columnHighlights.ts
+++ b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/columnHighlights.ts
@@ -50,6 +50,12 @@ export const deriveColumnHighlights =
posY: HIGHLIGHT_SIZE / 2,
rowIndex: 0,
width: 0,
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
};
return deriveHighlights(
diff --git a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/horizontalHighlights.ts b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/horizontalHighlights.ts
index 46f78eabcf..98664018b4 100644
--- a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/horizontalHighlights.ts
+++ b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/horizontalHighlights.ts
@@ -426,7 +426,6 @@ export function generateHighlights(
const width: number = layoutDimension.width / arr.length;
const isFirstHighlight: boolean = rowIndex === 0;
-
let posY = 0;
const emptyLayout = isFirstHighlight && isLastHighlight;
let gap = 0;
@@ -468,6 +467,12 @@ export function generateHighlights(
posY,
rowIndex,
width,
+ edgeDetails: {
+ top: isFirstHighlight,
+ bottom: isLastHighlight,
+ left: width * index === 0,
+ right: width * (index + 1) === layoutDimension.width,
+ },
...(isCurrentLayoutEmpty && !hasFillWidget
? {
isVertical: true,
diff --git a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.test.ts b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.test.ts
index 1aee4db1c3..99d091cdb5 100644
--- a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.test.ts
+++ b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.test.ts
@@ -295,6 +295,12 @@ describe("rowHighlights tests", () => {
posY: 0,
rowIndex: 0,
width: HIGHLIGHT_SIZE,
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
};
it("should derive highlights for a row", () => {
const data: WidgetLayoutProps[] = [
diff --git a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.ts b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.ts
index 034f86e0f9..134d3eb8cb 100644
--- a/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.ts
+++ b/app/client/src/layoutSystems/anvil/utils/layouts/highlights/rowHighlights.ts
@@ -83,6 +83,12 @@ export const deriveRowHighlights =
posY: HIGHLIGHT_SIZE / 2,
rowIndex: 0,
width: HIGHLIGHT_SIZE,
+ edgeDetails: {
+ top: false,
+ bottom: false,
+ left: false,
+ right: false,
+ },
};
// If layout is empty, add an initial highlight.
@@ -520,13 +526,20 @@ export function generateHighlights(
layoutDimension.left,
);
}
-
+ const posY = tallestDimension?.top ?? layoutDimension.top;
return {
...baseHighlight,
height: tallestDimension?.height ?? layoutDimension.height,
posX,
- posY: tallestDimension?.top ?? layoutDimension.top,
+ posY,
rowIndex,
+ edgeDetails: {
+ top: posY === layoutDimension.top,
+ bottom: posY === layoutDimension.top + layoutDimension.height,
+ left: posX === layoutDimension.left,
+ right:
+ posX + HIGHLIGHT_SIZE === layoutDimension.left + layoutDimension.width,
+ },
};
}
diff --git a/app/client/src/mocks/mockHighlightInfo.ts b/app/client/src/mocks/mockHighlightInfo.ts
index 9b276e3600..93149399d7 100644
--- a/app/client/src/mocks/mockHighlightInfo.ts
+++ b/app/client/src/mocks/mockHighlightInfo.ts
@@ -36,6 +36,12 @@ export function mockAnvilHighlightInfo(
posX: 0,
posY: 0,
width: 4,
+ edgeDetails: {
+ bottom: false,
+ left: false,
+ right: false,
+ top: false,
+ },
...data,
};
}
diff --git a/app/client/src/sagas/selectors.tsx b/app/client/src/sagas/selectors.tsx
index d54ab1c0de..38caf303e2 100644
--- a/app/client/src/sagas/selectors.tsx
+++ b/app/client/src/sagas/selectors.tsx
@@ -191,6 +191,15 @@ export const getPluginIdOfPackageName = (
export const getDragDetails = (state: AppState) => {
return state.ui.widgetDragResize.dragDetails;
};
+
+export const getIsNewWidgetBeingDragged = (state: AppState) => {
+ const { isDragging } = state.ui.widgetDragResize;
+ if (!isDragging) return false;
+ const dragDetails: DragDetails = getDragDetails(state);
+ const { dragGroupActualParent: dragParent, newWidget } = dragDetails;
+ return !!newWidget && !dragParent;
+};
+
export const isCurrentCanvasDragging = createSelector(
(state: AppState) => state.ui.widgetDragResize.isDragging,
getDragDetails,
diff --git a/app/client/src/widgets/wds/WDSModalWidget/widget/index.tsx b/app/client/src/widgets/wds/WDSModalWidget/widget/index.tsx
index c08b07b66a..1705b20a01 100644
--- a/app/client/src/widgets/wds/WDSModalWidget/widget/index.tsx
+++ b/app/client/src/widgets/wds/WDSModalWidget/widget/index.tsx
@@ -122,7 +122,7 @@ class WDSModalWidget extends BaseWidget {
? this.props.submitButtonText || "Submit"
: undefined;
const contentClassName = `${this.props.className} ${
- this.props.allowWidgetInteraction ? styles.disableModalInteraction : ""
+ this.props.allowWidgetInteraction ? "" : styles.disableModalInteraction
}`;
return (
* {
+ pointer-events: none;
+ }
}