feat: Anvil: Interact with a focused widget's widget name component (#33646)
## Description - Add a ghost component (`AnvilWidgetNameComponentWrapper`) that prevents the widget underneath the widget name component from being focused - Select widget when dragging a focused widget from the widget name component - Adjust offsets and sizes of the ghost component and the widget name button - Remove `onMouseLeave` events from widgets and add an `onMouseLeave` event to the Canvas. - Change the pointer to `grab` for the widget name component Fixes #33385 ## Automation /ok-to-test tags="@tag.All" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!CAUTION] > 🔴 🔴 🔴 Some tests have failed. > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/9201483569> > Commit: 4373df84f255534a6eb839b1cad532ae327947ec > Cypress dashboard: <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=9201483569&attempt=2&selectiontype=test&testsstatus=failed&specsstatus=fail" target="_blank"> Click here!</a> > The following are new failures, please fix them before merging the PR: <ol> > <li>cypress/e2e/Regression/ClientSide/BugTests/GitBugs_Spec.ts > <li>cypress/e2e/Regression/ClientSide/PartialImportExport/PartialImport_spec.ts </ol> > To know the list of identified flaky tests - <a href="https://internal.appsmith.com/app/cypress-dashboard/identified-flaky-tests-65890b3c81d7400d08fa9ee3?branch=master" target="_blank">Refer here</a> <!-- end of auto-generated comment: Cypress test results --> ## Communication Should the DevRel and Marketing teams inform users about this change? - [ ] Yes - [x] No
This commit is contained in:
parent
cee654a19a
commit
cc5a21c957
|
|
@ -1,4 +1,4 @@
|
|||
import type { ForwardedRef } from "react";
|
||||
import type { CSSProperties, ForwardedRef } from "react";
|
||||
import React, { forwardRef, useCallback, useMemo } from "react";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
||||
|
|
@ -11,6 +11,22 @@ import { createMessage } from "@appsmith/constants/messages";
|
|||
import { debugWidget } from "layoutSystems/anvil/integrations/actions";
|
||||
import { useDispatch } from "react-redux";
|
||||
|
||||
/**
|
||||
* Floating UI doesn't seem to respect initial styles from styled components or modules
|
||||
* So, we're passing the styles as a react prop
|
||||
*/
|
||||
const styles: CSSProperties = {
|
||||
display: "inline-flex",
|
||||
height: "32px", // This is 2px more than the ones in the designs.
|
||||
width: "max-content",
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
visibility: "hidden",
|
||||
isolation: "isolate",
|
||||
background: "transparent",
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
* This component is responsible for rendering the widget name in the canvas.
|
||||
|
|
@ -72,16 +88,16 @@ export function _AnvilWidgetNameComponent(
|
|||
}, [props.showError, handleDebugClick]);
|
||||
|
||||
return (
|
||||
<SplitButton
|
||||
bGCSSVar={props.bGCSSVar}
|
||||
colorCSSVar={props.colorCSSVar}
|
||||
leftToggle={leftToggle}
|
||||
onClick={handleSelectWidget}
|
||||
onDragStart={props.onDragStart}
|
||||
ref={ref}
|
||||
rightToggle={rightToggle}
|
||||
text={props.name}
|
||||
/>
|
||||
<div draggable onDragStart={props.onDragStart} ref={ref} style={styles}>
|
||||
<SplitButton
|
||||
bGCSSVar={props.bGCSSVar}
|
||||
colorCSSVar={props.colorCSSVar}
|
||||
leftToggle={leftToggle}
|
||||
onClick={handleSelectWidget}
|
||||
rightToggle={rightToggle}
|
||||
text={props.name}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,8 @@
|
|||
import type { ForwardedRef, CSSProperties } from "react";
|
||||
import React, { forwardRef } from "react";
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { UpArrowSVG } from "./UpArrowIcon";
|
||||
import { ErrorSVG } from "./ErrorIcon";
|
||||
|
||||
const styles: CSSProperties = {
|
||||
display: "inline-flex",
|
||||
height: "24px", // This is 2px more than the ones in the designs.
|
||||
width: "max-content",
|
||||
position: "fixed",
|
||||
top: 0,
|
||||
left: 0,
|
||||
visibility: "hidden",
|
||||
isolation: "isolate",
|
||||
};
|
||||
|
||||
const SplitButtonWrapper = styled.div<{
|
||||
$BGCSSVar: string;
|
||||
$ColorCSSVar: string;
|
||||
|
|
@ -25,6 +13,11 @@ const SplitButtonWrapper = styled.div<{
|
|||
color: var(${(props) => props.$ColorCSSVar});
|
||||
fill: var(${(props) => props.$ColorCSSVar});
|
||||
stroke: var(${(props) => props.$ColorCSSVar});
|
||||
margin-block-end: 8px;
|
||||
|
||||
height: 24px;
|
||||
width: max-content;
|
||||
display: inline-flex;
|
||||
|
||||
touch-action: manipulation;
|
||||
user-select: none;
|
||||
|
|
@ -32,7 +25,7 @@ const SplitButtonWrapper = styled.div<{
|
|||
gap: 1px;
|
||||
|
||||
& button {
|
||||
cursor: pointer;
|
||||
cursor: grab;
|
||||
appearance: none;
|
||||
background: none;
|
||||
border: none;
|
||||
|
|
@ -47,8 +40,9 @@ const SplitButtonWrapper = styled.div<{
|
|||
font-size: inherit;
|
||||
font-weight: 500;
|
||||
|
||||
padding-block: 1.25ch;
|
||||
padding-inline: 2ch;
|
||||
padding-block: 3px;
|
||||
padding-inline: 5px;
|
||||
line-height: 17px;
|
||||
|
||||
color: var(${(props) => props.$ColorCSSVar});
|
||||
outline-color: var(${(props) => props.$BGCSSVar});
|
||||
|
|
@ -62,7 +56,8 @@ const SplitButtonWrapper = styled.div<{
|
|||
}
|
||||
|
||||
& span {
|
||||
inline-size: 3ch;
|
||||
inline-size: 2.4ch;
|
||||
block-size: 100%;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
|
@ -85,10 +80,10 @@ const SplitButtonWrapper = styled.div<{
|
|||
&:active {
|
||||
filter: brightness(0.6);
|
||||
}
|
||||
}
|
||||
|
||||
& > svg {
|
||||
stroke: var(${(props) => props.$ColorCSSVar});
|
||||
& > svg {
|
||||
stroke: var(${(props) => props.$ColorCSSVar});
|
||||
}
|
||||
}
|
||||
|
||||
& span:nth-of-type(${(props) => (props.$isLeftToggleDisabled ? 1 : 2)}) {
|
||||
|
|
@ -100,36 +95,28 @@ const SplitButtonWrapper = styled.div<{
|
|||
}
|
||||
`;
|
||||
|
||||
export function _SplitButton(
|
||||
props: {
|
||||
text: string;
|
||||
export function SplitButton(props: {
|
||||
text: string;
|
||||
onClick: React.MouseEventHandler;
|
||||
bGCSSVar: string;
|
||||
colorCSSVar: string;
|
||||
leftToggle: {
|
||||
disable: boolean;
|
||||
onClick: React.MouseEventHandler;
|
||||
bGCSSVar: string;
|
||||
colorCSSVar: string;
|
||||
leftToggle: {
|
||||
disable: boolean;
|
||||
onClick: React.MouseEventHandler;
|
||||
title: string;
|
||||
};
|
||||
rightToggle: {
|
||||
disable: boolean;
|
||||
onClick: React.MouseEventHandler;
|
||||
title: string;
|
||||
};
|
||||
onDragStart: React.DragEventHandler;
|
||||
},
|
||||
ref: ForwardedRef<HTMLDivElement>,
|
||||
) {
|
||||
title: string;
|
||||
};
|
||||
rightToggle: {
|
||||
disable: boolean;
|
||||
onClick: React.MouseEventHandler;
|
||||
title: string;
|
||||
};
|
||||
}) {
|
||||
return (
|
||||
<SplitButtonWrapper
|
||||
$BGCSSVar={props.bGCSSVar}
|
||||
$ColorCSSVar={props.colorCSSVar}
|
||||
$isLeftToggleDisabled={props.leftToggle.disable}
|
||||
$isRightToggleDisabled={props.rightToggle.disable}
|
||||
draggable
|
||||
onDragStart={props.onDragStart}
|
||||
ref={ref}
|
||||
style={styles}
|
||||
>
|
||||
{!props.leftToggle.disable && (
|
||||
<span
|
||||
|
|
@ -155,5 +142,3 @@ export function _SplitButton(
|
|||
</SplitButtonWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
export const SplitButton = forwardRef(_SplitButton);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,9 @@ import { AnvilWidgetNameComponent } from "./AnvilWidgetNameComponent";
|
|||
import { getWidgetErrorCount, shouldSelectOrFocus } from "./selectors";
|
||||
import type { NameComponentStates } from "./types";
|
||||
import { generateDragStateForAnvilLayout } from "layoutSystems/anvil/utils/widgetUtils";
|
||||
import { SelectionRequestType } from "sagas/WidgetSelectUtils";
|
||||
import { useWidgetSelection } from "utils/hooks/useWidgetSelection";
|
||||
import { isWidgetSelected } from "selectors/widgetSelectors";
|
||||
|
||||
export function AnvilWidgetName(props: {
|
||||
widgetId: string;
|
||||
|
|
@ -40,23 +43,28 @@ export function AnvilWidgetName(props: {
|
|||
(state) => getWidgetErrorCount(state, widgetId) > 0,
|
||||
);
|
||||
|
||||
const isParentSelected = useSelector(isWidgetSelected(parentId));
|
||||
|
||||
const styleProps = getWidgetNameComponentStyleProps(
|
||||
widgetType,
|
||||
nameComponentState,
|
||||
showError,
|
||||
isParentSelected,
|
||||
);
|
||||
|
||||
const { setDraggingState } = useWidgetDragResize();
|
||||
const { selectWidget } = useWidgetSelection();
|
||||
|
||||
const onDragStart = useCallback(
|
||||
(e: React.DragEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
if (nameComponentState === "select") {
|
||||
setDraggingState(generateDragState());
|
||||
}
|
||||
// If we're dragging a focused widget, we need to select it before dragging
|
||||
// Otherwise, the currently selected widget will instead be dragged.
|
||||
selectWidget(SelectionRequestType.One, [widgetId]);
|
||||
setDraggingState(generateDragState());
|
||||
},
|
||||
[setDraggingState, nameComponentState],
|
||||
[setDraggingState],
|
||||
);
|
||||
|
||||
/** Setup Floating UI logic */
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export function handleWidgetUpdate(
|
|||
middleware: [
|
||||
flip(),
|
||||
shift(),
|
||||
offset({ mainAxis: 8, crossAxis: -5 }),
|
||||
offset({ mainAxis: 0, crossAxis: -5 }),
|
||||
getOverflowMiddleware(widgetsEditorElement as HTMLDivElement),
|
||||
hide({ strategy: "referenceHidden" }),
|
||||
hide({ strategy: "escaped" }),
|
||||
|
|
@ -103,6 +103,7 @@ export function getWidgetNameComponentStyleProps(
|
|||
widgetType: string,
|
||||
nameComponentState: NameComponentStates,
|
||||
showError: boolean,
|
||||
isParentSelected: boolean,
|
||||
) {
|
||||
const config = WidgetFactory.getConfig(widgetType);
|
||||
const onCanvasUI = config?.onCanvasUI || {
|
||||
|
|
@ -121,11 +122,6 @@ export function getWidgetNameComponentStyleProps(
|
|||
? onCanvasUI.focusColorCSSVar
|
||||
: onCanvasUI.selectionColorCSSVar;
|
||||
|
||||
let disableParentToggle = onCanvasUI.disableParentSelection;
|
||||
if (nameComponentState === "focus") {
|
||||
disableParentToggle = true;
|
||||
}
|
||||
|
||||
// If there is an error, show the widget name in error state
|
||||
// This includes background being the error color
|
||||
// and font color being white.
|
||||
|
|
@ -134,7 +130,8 @@ export function getWidgetNameComponentStyleProps(
|
|||
colorCSSVar = "--on-canvas-ui-white";
|
||||
}
|
||||
return {
|
||||
disableParentToggle,
|
||||
// disable parent toggle if the parent is already selected
|
||||
disableParentToggle: isParentSelected || onCanvasUI.disableParentSelection,
|
||||
bGCSSVar,
|
||||
colorCSSVar,
|
||||
selectionBGCSSVar: onCanvasUI.selectionBGCSSVar,
|
||||
|
|
|
|||
|
|
@ -44,26 +44,18 @@ export const useAnvilWidgetHover = (
|
|||
],
|
||||
);
|
||||
|
||||
// Callback function for handling mouseleave events
|
||||
const handleMouseLeave = useCallback(() => {
|
||||
// On leaving a widget, reset the focused widget
|
||||
focusWidget && focusWidget();
|
||||
}, [focusWidget]);
|
||||
|
||||
// Effect hook to add and remove mouseover and mouseleave event listeners
|
||||
useEffect(() => {
|
||||
if (ref.current) {
|
||||
// Add mouseover and mouseleave event listeners
|
||||
ref.current.addEventListener("mouseover", handleMouseOver);
|
||||
ref.current.addEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
|
||||
// Clean up event listeners when the component unmounts
|
||||
return () => {
|
||||
if (ref.current) {
|
||||
ref.current.removeEventListener("mouseover", handleMouseOver);
|
||||
ref.current.removeEventListener("mouseleave", handleMouseLeave);
|
||||
}
|
||||
};
|
||||
}, [handleMouseOver, handleMouseLeave]);
|
||||
}, [handleMouseOver]);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import log from "loglevel";
|
||||
import React from "react";
|
||||
import React, { useCallback } from "react";
|
||||
import styled from "styled-components";
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import type { CanvasWidgetStructure } from "WidgetProvider/constants";
|
||||
import useWidgetFocus from "utils/hooks/useWidgetFocus";
|
||||
import { combinedPreviewModeSelector } from "selectors/editorSelectors";
|
||||
|
|
@ -19,6 +19,7 @@ import type { WidgetProps } from "widgets/BaseWidget";
|
|||
import { getAppThemeSettings } from "@appsmith/selectors/applicationSelectors";
|
||||
import CodeModeTooltip from "pages/Editor/WidgetsEditor/components/CodeModeTooltip";
|
||||
import { getIsAnvilLayout } from "layoutSystems/anvil/integrations/selectors";
|
||||
import { focusWidget } from "actions/widgetActions";
|
||||
|
||||
interface CanvasProps {
|
||||
widgetsStructure: CanvasWidgetStructure;
|
||||
|
|
@ -64,6 +65,11 @@ const Canvas = (props: CanvasProps) => {
|
|||
// so that fixedLayout theme does not break because of calculations done in useTheme
|
||||
const { theme } = useTheme(isAnvilLayout ? wdsThemeProps : {});
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const unfocusAllWidgets = useCallback(() => {
|
||||
dispatch(focusWidget());
|
||||
}, [dispatch]);
|
||||
|
||||
/**
|
||||
* background for canvas
|
||||
*/
|
||||
|
|
@ -93,6 +99,7 @@ const Canvas = (props: CanvasProps) => {
|
|||
)}`}
|
||||
data-testid={"t--canvas-artboard"}
|
||||
id={CANVAS_ART_BOARD}
|
||||
onMouseLeave={unfocusAllWidgets}
|
||||
ref={isAnvilLayout ? undefined : focusRef}
|
||||
width={canvasWidth}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -113,9 +113,9 @@ export const getParentToOpenSelector = (widgetId: string) => {
|
|||
};
|
||||
|
||||
// Check if widget is in the list of selected widgets
|
||||
export const isWidgetSelected = (widgetId: string) => {
|
||||
export const isWidgetSelected = (widgetId?: string) => {
|
||||
return createSelector(getSelectedWidgets, (widgets): boolean =>
|
||||
widgets.includes(widgetId),
|
||||
widgetId ? widgets.includes(widgetId) : false,
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user