diff --git a/app/client/src/layoutSystems/anvil/common/hooks/useWidgetBorderStyles.ts b/app/client/src/layoutSystems/anvil/common/hooks/useWidgetBorderStyles.ts index 958153468b5..bc771ffd677 100644 --- a/app/client/src/layoutSystems/anvil/common/hooks/useWidgetBorderStyles.ts +++ b/app/client/src/layoutSystems/anvil/common/hooks/useWidgetBorderStyles.ts @@ -4,12 +4,17 @@ import { getWidgetErrorCount } from "layoutSystems/anvil/editor/AnvilWidgetName/ import { getAnvilHighlightShown, getAnvilSpaceDistributionStatus, + getWidgetsDistributingSpace, } from "layoutSystems/anvil/integrations/selectors"; import { useSelector } from "react-redux"; import { combinedPreviewModeSelector } from "selectors/editorSelectors"; import { isWidgetFocused, isWidgetSelected } from "selectors/widgetSelectors"; -export function useWidgetBorderStyles(widgetId: string, widgetType: string) { +export function useWidgetBorderStyles( + widgetId: string, + widgetType: string, + elevatedBackground?: boolean, +) { /** Selectors */ const isFocused = useSelector(isWidgetFocused(widgetId)); const isSelected = useSelector(isWidgetSelected(widgetId)); @@ -28,7 +33,9 @@ export function useWidgetBorderStyles(widgetId: string, widgetType: string) { const isDistributingSpace: boolean = useSelector( getAnvilSpaceDistributionStatus, ); - + const widgetsEffectedBySpaceDistribution = useSelector( + getWidgetsDistributingSpace, + ); const isPreviewMode = useSelector(combinedPreviewModeSelector); /** EO selectors */ @@ -37,11 +44,14 @@ export function useWidgetBorderStyles(widgetId: string, widgetType: string) { if (isPreviewMode) { return {}; } - + const isZoneDistributingSpace = + widgetsEffectedBySpaceDistribution.zones.includes(widgetId); + // If the widget is a zone and is distributing space and has no elevated background + const isZoneNotElevated = isZoneDistributingSpace && !elevatedBackground; // Show the border if the widget has widgets being dragged or redistributed inside it const showDraggedOnBorder = (highlightShown && highlightShown.canvasId === widgetId) || - (isDistributingSpace && isSelected); + isZoneNotElevated; const onCanvasUI = WidgetFactory.getConfig(widgetType)?.onCanvasUI; diff --git a/app/client/src/layoutSystems/anvil/editor/AnvilEditorFlexComponent.tsx b/app/client/src/layoutSystems/anvil/editor/AnvilEditorFlexComponent.tsx index 9ec2bdf280c..a8a08e5420e 100644 --- a/app/client/src/layoutSystems/anvil/editor/AnvilEditorFlexComponent.tsx +++ b/app/client/src/layoutSystems/anvil/editor/AnvilEditorFlexComponent.tsx @@ -47,6 +47,7 @@ export const AnvilEditorFlexComponent = (props: AnvilFlexComponentProps) => { props.widgetName, props.isVisible, props.widgetType, + !!props.elevatedBackground, ref, ); useAnvilWidgetDrag(props.widgetId, props.widgetType, props.layoutId, ref); diff --git a/app/client/src/layoutSystems/anvil/editor/AnvilEditorWidgetOnion.tsx b/app/client/src/layoutSystems/anvil/editor/AnvilEditorWidgetOnion.tsx index 5075c75a88b..415332c9153 100644 --- a/app/client/src/layoutSystems/anvil/editor/AnvilEditorWidgetOnion.tsx +++ b/app/client/src/layoutSystems/anvil/editor/AnvilEditorWidgetOnion.tsx @@ -37,6 +37,7 @@ export const AnvilEditorWidgetOnion = (props: BaseWidgetProps) => { }, [isPreviewMode, props.type]); return ( { - const baseCondition = isEditorOpen && !isDragging; + const baseCondition = isEditorOpen && !isDragging && !isDistributingSpace; let onCanvasUIState: NameComponentStates = "none"; if (baseCondition) { if (isWidgetSelected) onCanvasUIState = "select"; diff --git a/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx b/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx index 7d5afc3dc0b..67319c5c097 100644 --- a/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx +++ b/app/client/src/layoutSystems/anvil/editor/canvas/AnvilEditorCanvas.tsx @@ -3,16 +3,14 @@ import { AnvilViewerCanvas } from "layoutSystems/anvil/viewer/canvas/AnvilViewer import React, { useCallback, useEffect, useRef } from "react"; import { useSelectWidgetListener } from "./hooks/useSelectWidgetListener"; import { useClickToClearSelections } from "./hooks/useClickToClearSelections"; -import { - useAnvilGlobalDnDStates, - type AnvilGlobalDnDStates, -} from "./hooks/useAnvilGlobalDnDStates"; +import type { AnvilGlobalDnDStates } from "./hooks/useAnvilGlobalDnDStates"; +import { useAnvilGlobalDnDStates } from "./hooks/useAnvilGlobalDnDStates"; import { AnvilDragPreview } from "../canvasArenas/AnvilDragPreview"; +import { AnvilWidgetElevationProvider } from "./providers/AnvilWidgetElevationProvider"; export const AnvilDnDStatesContext = React.createContext< AnvilGlobalDnDStates | undefined >(undefined); - /** * Anvil Main Canvas is just a wrapper around AnvilCanvas. * Why do we need this? @@ -58,14 +56,16 @@ export const AnvilEditorCanvas = (props: BaseWidgetProps) => { // 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/useAnvilWidgetElevationSetter.ts b/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilWidgetElevationSetter.ts new file mode 100644 index 00000000000..1e44a37f8b1 --- /dev/null +++ b/app/client/src/layoutSystems/anvil/editor/canvas/hooks/useAnvilWidgetElevationSetter.ts @@ -0,0 +1,15 @@ +import { useEffect } from "react"; +import { useAnvilWidgetElevation } from "../providers/AnvilWidgetElevationProvider"; + +export const useAnvilWidgetElevationSetter = ( + widgetId: string, + elevatedBackground: boolean, +) => { + const anvilWidgetElevation = useAnvilWidgetElevation(); + const { setWidgetElevation } = anvilWidgetElevation || {}; + useEffect(() => { + if (setWidgetElevation) { + setWidgetElevation(widgetId, elevatedBackground); + } + }, [elevatedBackground, setWidgetElevation]); +}; diff --git a/app/client/src/layoutSystems/anvil/editor/canvas/providers/AnvilWidgetElevationProvider.tsx b/app/client/src/layoutSystems/anvil/editor/canvas/providers/AnvilWidgetElevationProvider.tsx new file mode 100644 index 00000000000..fc825217215 --- /dev/null +++ b/app/client/src/layoutSystems/anvil/editor/canvas/providers/AnvilWidgetElevationProvider.tsx @@ -0,0 +1,66 @@ +import React, { + type ReactNode, + createContext, + useState, + useCallback, + useContext, +} from "react"; + +interface WidgetElevation { + [key: string]: boolean; +} + +interface AnvilWidgetElevationContextType { + elevatedWidgets: WidgetElevation; + setWidgetElevation: (widgetId: string, isElevated: boolean) => void; +} + +const AnvilWidgetElevationContext = createContext< + AnvilWidgetElevationContextType | undefined +>(undefined); + +export const useAnvilWidgetElevation = () => + useContext(AnvilWidgetElevationContext); +/** + * AnvilWidgetElevationProvider indexes all sections and zones and records their evaluated value of elevation(Visual Separation). + * + * Why not just use the evaluated values directly? + * Because we need to keep track of the elevation of each widget in the editor to apply the correct elevation styles. + * elevation being a bindable property, we need to keep track of the evaluated value of elevation of each sections and zones in the editor. + * + * When adding compensators to the dragged widgets(useAnvilDnDCompensators), we need to know the elevation of the zone widget as well as its corresponding siblings + * to decide if the zone has to treated as a elevated zone or not. + * + * In-order to skip iterating the data tree every time we need to know the elevation of a widget, we are storing the elevation of each widget in this context. + * This way we do not have dependency on the data tree to know the elevation of a widget. + */ +export const AnvilWidgetElevationProvider = ({ + children, +}: { + children: ReactNode; +}) => { + const [elevatedWidgets, setElevatedWidgets] = useState({}); + + const setWidgetElevation = useCallback( + (widgetId: string, isElevated: boolean) => { + setElevatedWidgets((prev) => { + return { + ...prev, + [widgetId]: isElevated, + }; + }); + }, + [setElevatedWidgets], + ); + + return ( + + {children} + + ); +}; diff --git a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx index 62e2c1d5a21..c2f0ea98853 100644 --- a/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx +++ b/app/client/src/layoutSystems/anvil/editor/canvasArenas/AnvilModalDropArena.tsx @@ -65,7 +65,7 @@ export const AnvilModalDropArena = ({ return ( !allWidgets[each].detachFromLayout, ).length === 0; - const allSiblingsWidgets = useMemo(() => { - const allSiblings = - (widgetProps.parentId && allWidgets[widgetProps.parentId]?.children) || - []; - return allSiblings.map((each) => allWidgets[each]); + const allSiblingsWidgetIds = useMemo(() => { + return ( + (widgetProps.parentId && allWidgets[widgetProps.parentId]?.children) || [] + ); }, [widgetProps, allWidgets]); const isElevatedWidget = useMemo(() => { if (widgetProps.type === ZoneWidget.type) { - const isAnyZoneElevated = allSiblingsWidgets.some( - (each) => !!each.elevatedBackground, + const isAnyZoneElevated = allSiblingsWidgetIds.some( + (each) => !!elevatedWidgets[each], ); return isAnyZoneElevated; } - return !!widgetProps.elevatedBackground; - }, [widgetProps, allSiblingsWidgets]); + return !!elevatedWidgets[widgetId]; + }, [widgetProps, elevatedWidgets, allSiblingsWidgetIds]); const { edgeCompensatorValues, diff --git a/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts index 54d3aee78da..cc7b5204250 100644 --- a/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts +++ b/app/client/src/layoutSystems/anvil/editor/hooks/useAnvilWidgetStyles.ts @@ -10,6 +10,7 @@ export const useAnvilWidgetStyles = ( widgetName: string, isVisible = true, widgetType: string, + elevatedBackground: boolean, ref: React.RefObject, // Ref object to reference the AnvilFlexComponent ) => { // Selectors to determine whether the widget is selected or dragging @@ -18,7 +19,11 @@ export const useAnvilWidgetStyles = ( (state: AppState) => state.ui.widgetDragResize.isDragging, ); // Get widget border styles using useWidgetBorderStyles - const widgetBorderStyles = useWidgetBorderStyles(widgetId, widgetType); + const widgetBorderStyles = useWidgetBorderStyles( + widgetId, + widgetType, + elevatedBackground, + ); // Effect hook to apply widget border styles to the widget useEffect(() => { diff --git a/app/client/src/layoutSystems/anvil/integrations/selectors.ts b/app/client/src/layoutSystems/anvil/integrations/selectors.ts index 87f6249221d..74a29723c04 100644 --- a/app/client/src/layoutSystems/anvil/integrations/selectors.ts +++ b/app/client/src/layoutSystems/anvil/integrations/selectors.ts @@ -24,7 +24,11 @@ export function getDropTargetLayoutId(state: AppState, canvasId: string) { * Returns a boolean indicating if space distribution is in progress */ export function getAnvilSpaceDistributionStatus(state: AppState) { - return state.ui.widgetDragResize.anvil.isDistributingSpace; + return state.ui.widgetDragResize.anvil.spaceDistribution.isDistributingSpace; +} + +export function getWidgetsDistributingSpace(state: AppState) { + return state.ui.widgetDragResize.anvil.spaceDistribution.widgetsEffected; } /** diff --git a/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/actions.ts b/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/actions.ts new file mode 100644 index 00000000000..3e524f77439 --- /dev/null +++ b/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/actions.ts @@ -0,0 +1,32 @@ +import { AnvilReduxActionTypes } from "../integrations/actions/actionTypes"; + +export const startAnvilSpaceDistributionAction = (payload: { + section: string; + zones: string[]; +}) => { + return { + type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_START, + payload, + }; +}; + +export const stopAnvilSpaceDistributionAction = () => { + return { + type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_STOP, + }; +}; + +export const updateSpaceDistributionAction = ( + sectionLayoutId: string, + zonesDistributed: { + [widgetId: string]: number; + }, +) => { + return { + type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_UPDATE, + payload: { + zonesDistributed, + sectionLayoutId, + }, + }; +}; diff --git a/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/useSpaceDistributionEvents.ts b/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/useSpaceDistributionEvents.ts index 6af8e575ae9..615ec6fd663 100644 --- a/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/useSpaceDistributionEvents.ts +++ b/app/client/src/layoutSystems/anvil/sectionSpaceDistributor/useSpaceDistributionEvents.ts @@ -1,7 +1,6 @@ import { getAnvilWidgetDOMId } from "layoutSystems/common/utils/LayoutElementPositionsObserver/utils"; import { useCallback, useEffect, useRef } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { AnvilReduxActionTypes } from "../integrations/actions/actionTypes"; import { getMouseSpeedTrackingCallback, getPropertyPaneZoneId, @@ -21,6 +20,11 @@ import { updateWidgetCSSOnHandleMove, updateWidgetCSSOnMinimumLimit, } from "./utils/onMouseMoveUtils"; +import { + startAnvilSpaceDistributionAction, + stopAnvilSpaceDistributionAction, + updateSpaceDistributionAction, +} from "./actions"; interface SpaceDistributionEventsProps { ref: React.RefObject; @@ -54,16 +58,19 @@ export const useSpaceDistributionEvents = ({ getMouseSpeedTrackingCallback(currentMouseSpeed); const selectedWidgets = useSelector(getSelectedWidgets); const { selectWidget } = useWidgetSelection(); + const onSpaceDistributionStart = useCallback(() => { + dispatch( + startAnvilSpaceDistributionAction({ + section: sectionWidgetId, + zones: zoneIds, + }), + ); + }, [sectionWidgetId, zoneIds]); const selectCorrespondingSectionWidget = useCallback(() => { - if ( - !( - selectedWidgets.includes(sectionWidgetId) || - zoneIds.some((each) => selectedWidgets.includes(each)) - ) - ) { + if (!selectedWidgets.includes(sectionWidgetId)) { selectWidget(SelectionRequestType.One, [sectionWidgetId]); } - }, [sectionWidgetId, selectedWidgets, zoneIds]); + }, [sectionWidgetId, selectedWidgets]); useEffect(() => { if (ref.current) { // Check if the ref to the DOM element exists @@ -143,21 +150,15 @@ export const useSpaceDistributionEvents = ({ currentFlexGrow.rightZone !== currentGrowthFactor.rightZone ) { // Dispatch action to update space distribution - dispatch({ - type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_UPDATE, - payload: { - zonesDistributed: { - [leftZone]: currentGrowthFactor.leftZone, - [rightZone]: currentGrowthFactor.rightZone, - }, - sectionLayoutId, - }, - }); + dispatch( + updateSpaceDistributionAction(sectionWidgetId, { + [leftZone]: currentGrowthFactor.leftZone, + [rightZone]: currentGrowthFactor.rightZone, + }), + ); } // Stop space distribution process - dispatch({ - type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_STOP, - }); + dispatch(stopAnvilSpaceDistributionAction()); resetCSSOnZones(spaceDistributed); removeMouseMoveHandlers(); currentMouseSpeed.current = 0; @@ -196,9 +197,7 @@ export const useSpaceDistributionEvents = ({ e.preventDefault(); x = e.clientX; // Store the initial mouse position isCurrentHandleDistributingSpace.current = true; // Set distribution flag - dispatch({ - type: AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_START, - }); + onSpaceDistributionStart(); addMouseMoveHandlers(); }; @@ -331,5 +330,6 @@ export const useSpaceDistributionEvents = ({ sectionWidgetId, spaceDistributed, spaceToWorkWith, + onSpaceDistributionStart, ]); }; diff --git a/app/client/src/layoutSystems/anvil/utils/types.ts b/app/client/src/layoutSystems/anvil/utils/types.ts index c467a876542..240ee9c292b 100644 --- a/app/client/src/layoutSystems/anvil/utils/types.ts +++ b/app/client/src/layoutSystems/anvil/utils/types.ts @@ -5,6 +5,7 @@ import type { WidgetType } from "WidgetProvider/factory"; export interface AnvilFlexComponentProps { children: ReactNode; className?: string; + elevatedBackground?: boolean; layoutId: string; parentId?: string; rowIndex: number; diff --git a/app/client/src/reducers/uiReducers/dragResizeReducer.ts b/app/client/src/reducers/uiReducers/dragResizeReducer.ts index 4303e711a81..5c5ddd21b7b 100644 --- a/app/client/src/reducers/uiReducers/dragResizeReducer.ts +++ b/app/client/src/reducers/uiReducers/dragResizeReducer.ts @@ -19,7 +19,13 @@ const initialState: WidgetDragResizeState = { isAutoCanvasResizing: false, anvil: { highlightShown: undefined, - isDistributingSpace: false, + spaceDistribution: { + isDistributingSpace: false, + widgetsEffected: { + section: "", + zones: [], + }, + }, }, isDraggingDisabled: false, blockSelection: false, @@ -138,13 +144,22 @@ export const widgetDraggingReducer = createImmerReducer(initialState, { //space distribution redux [AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_START]: ( state: WidgetDragResizeState, + action: ReduxAction<{ + section: string; + zones: string[]; + }>, ) => { - state.anvil.isDistributingSpace = true; + state.anvil.spaceDistribution.widgetsEffected.section = + action.payload.section; + state.anvil.spaceDistribution.widgetsEffected.zones = action.payload.zones; + state.anvil.spaceDistribution.isDistributingSpace = true; }, [AnvilReduxActionTypes.ANVIL_SPACE_DISTRIBUTION_STOP]: ( state: WidgetDragResizeState, ) => { - state.anvil.isDistributingSpace = false; + state.anvil.spaceDistribution.isDistributingSpace = false; + state.anvil.spaceDistribution.widgetsEffected.section = ""; + state.anvil.spaceDistribution.widgetsEffected.zones = []; }, [AnvilReduxActionTypes.ANVIL_SET_HIGHLIGHT_SHOWN]: ( state: WidgetDragResizeState, @@ -175,7 +190,13 @@ export interface WidgetDragResizeState { isResizing: boolean; anvil: { highlightShown?: AnvilHighlightInfo; - isDistributingSpace: boolean; + spaceDistribution: { + isDistributingSpace: boolean; + widgetsEffected: { + section: string; + zones: string[]; + }; + }; }; lastSelectedWidget?: string; focusedWidget?: string; diff --git a/app/client/src/widgets/anvil/Container.tsx b/app/client/src/widgets/anvil/Container.tsx index 26d3a68cef6..30e28e35c3e 100644 --- a/app/client/src/widgets/anvil/Container.tsx +++ b/app/client/src/widgets/anvil/Container.tsx @@ -3,6 +3,7 @@ import React from "react"; import styled from "styled-components"; import { generateClassName } from "utils/generators"; import type { Elevations } from "./constants"; +import { useAnvilWidgetElevationSetter } from "layoutSystems/anvil/editor/canvas/hooks/useAnvilWidgetElevationSetter"; /** * This container component wraps the Zone and Section widgets and allows Anvil to utilise tokens from the themes @@ -22,6 +23,7 @@ const StyledContainerComponent = styled.div< `; export function ContainerComponent(props: ContainerComponentProps) { + useAnvilWidgetElevationSetter(props.widgetId, props.elevatedBackground); return (