diff --git a/CHANGELOG.md b/CHANGELOG.md index af20b6b7..23325814 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,11 +18,14 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p - `searchListPredicate` property: Allows to filter the complete list of search options at once. - Following optional BlueprintJs properties are forwarded now to override default behaviour: `noResults`, `createNewItemRenderer` and `itemRenderer` - `isValidNewOption` property: Checks if an input string is or can be turned into a valid new option. +- `ActivityControlWidge` + - Support `badge` on activity control menu button. ### Fixed - `` - border of the BlueprintJS `Tag` elements were fixed +- `extendedTooltip` of a handle in the ReactFlow (v12) component does not show the tooltip. ### Changed diff --git a/src/cmem/ActivityControl/ActivityControlWidget.tsx b/src/cmem/ActivityControl/ActivityControlWidget.tsx index 4663a108..c7fc36e1 100644 --- a/src/cmem/ActivityControl/ActivityControlWidget.tsx +++ b/src/cmem/ActivityControl/ActivityControlWidget.tsx @@ -93,6 +93,8 @@ export interface ActivityControlWidgetProps extends TestableComponent { interface IActivityContextMenu extends TestableComponent { // Tooltip for the context menu tooltip?: string; + // Optional badge shown on the context menu button. + badge?: string | number; // The entries of the context menu menuItems: IActivityMenuAction[]; } @@ -228,6 +230,15 @@ export function ActivityControlWidget(props: ActivityControlWidgetProps) { + } > {activityContextMenu.menuItems.map((menuAction, idx) => { return ( diff --git a/src/cmem/ActivityControl/tests/ActivityControlWidget.test.tsx b/src/cmem/ActivityControl/tests/ActivityControlWidget.test.tsx index 5df29c6d..b3a63dea 100644 --- a/src/cmem/ActivityControl/tests/ActivityControlWidget.test.tsx +++ b/src/cmem/ActivityControl/tests/ActivityControlWidget.test.tsx @@ -96,4 +96,25 @@ describe("ActivityControlWidget", () => { fireEvent.click(customButton); expect(mockAction).toHaveBeenCalledTimes(1); }); + + it("renders a badge on the context menu trigger", () => { + const { container } = render( + , + ); + + expect(container.querySelector("button[title='More options']")).toHaveTextContent("3"); + }); }); diff --git a/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx b/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx index a23bb963..8f36a512 100644 --- a/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx +++ b/src/cmem/react-flow/ReactFlow/ReactFlow.stories.tsx @@ -97,7 +97,7 @@ const nodeExamples = { content: "Example content.", minimalShape: "none", }, - position: { x: 400, y: 200 }, + position: { x: 400, y: 100 }, }, { id: "linking-3", @@ -141,6 +141,16 @@ const nodeExamples = { }, position: { x: 50, y: 300 }, }, + { + id: "linking-6", + type: "ruleblock", + data: { + label: "Rule block", + content: "Example content.", + minimalShape: "none", + }, + position: { x: 400, y: 200 }, + }, ], edges: [ { diff --git a/src/cmem/react-flow/_handles.scss b/src/cmem/react-flow/_handles.scss index a753b745..92021c33 100644 --- a/src/cmem/react-flow/_handles.scss +++ b/src/cmem/react-flow/_handles.scss @@ -30,5 +30,6 @@ @include handletypestyles("sourcepath"); @include handletypestyles("targetpath"); @include handletypestyles("transformation"); +@include handletypestyles("ruleblock"); @include handletypestyles("comparator"); @include handletypestyles("aggregator"); diff --git a/src/cmem/react-flow/_minimap.scss b/src/cmem/react-flow/_minimap.scss index 864d932b..2b4078eb 100644 --- a/src/cmem/react-flow/_minimap.scss +++ b/src/cmem/react-flow/_minimap.scss @@ -44,5 +44,6 @@ @include mapnodestyles("sourcepath"); @include mapnodestyles("targetpath"); @include mapnodestyles("transformation"); +@include mapnodestyles("ruleblock"); @include mapnodestyles("comparator"); @include mapnodestyles("aggregator"); diff --git a/src/cmem/react-flow/configuration/_colors-linking.scss b/src/cmem/react-flow/configuration/_colors-linking.scss index c9e47d42..7d2acfc6 100644 --- a/src/cmem/react-flow/configuration/_colors-linking.scss +++ b/src/cmem/react-flow/configuration/_colors-linking.scss @@ -5,6 +5,8 @@ --#{$eccgui}-targetpath-node-bright: #{eccgui-color-var("layout", "petrol", 300)}; --#{$eccgui}-transformation-node: #{eccgui-color-var("layout", "pink", 700)}; --#{$eccgui}-transformation-node-bright: #{eccgui-color-var("layout", "pink", 300)}; + --#{$eccgui}-ruleblock-node: #{eccgui-color-var("layout", "vermilion", 700)}; + --#{$eccgui}-ruleblock-node-bright: #{eccgui-color-var("layout", "vermilion", 300)}; --#{$eccgui}-comparator-node: #{eccgui-color-var("layout", "teal", 700)}; --#{$eccgui}-comparator-node-bright: #{eccgui-color-var("layout", "teal", 300)}; --#{$eccgui}-aggregator-node: #{eccgui-color-var("layout", "cyan", 700)}; diff --git a/src/cmem/react-flow/configuration/linking.ts b/src/cmem/react-flow/configuration/linking.ts index d7635614..3fd23323 100644 --- a/src/cmem/react-flow/configuration/linking.ts +++ b/src/cmem/react-flow/configuration/linking.ts @@ -19,6 +19,7 @@ const nodeTypes: Record> = { sourcepath: NodeDefault, targetpath: NodeDefault, transformation: NodeDefault, + ruleblock: NodeDefault, comparator: NodeDefault, aggregator: NodeDefault, stickynote: StickyNoteNode, diff --git a/src/cmem/react-flow/configuration/typing.ts b/src/cmem/react-flow/configuration/typing.ts index 0f01eae2..5719ad1e 100644 --- a/src/cmem/react-flow/configuration/typing.ts +++ b/src/cmem/react-flow/configuration/typing.ts @@ -4,6 +4,7 @@ export enum LINKING_NODE_TYPES { sourcepath = "sourcepath", targetpath = "targetpath", transformation = "transformation", + ruleblock = "ruleblock", comparator = "comparator", aggregator = "aggregator", stickynote = "stickynote", diff --git a/src/cmem/react-flow/nodes/_colors.scss b/src/cmem/react-flow/nodes/_colors.scss index e5d1950f..6ab1cdab 100644 --- a/src/cmem/react-flow/nodes/_colors.scss +++ b/src/cmem/react-flow/nodes/_colors.scss @@ -62,5 +62,6 @@ @include nodetypestyles("sourcepath"); @include nodetypestyles("targetpath"); @include nodetypestyles("transformation"); +@include nodetypestyles("ruleblock"); @include nodetypestyles("comparator"); @include nodetypestyles("aggregator"); diff --git a/src/components/Icon/canonicalIconNames.tsx b/src/components/Icon/canonicalIconNames.tsx index d253a0bd..00a04599 100644 --- a/src/components/Icon/canonicalIconNames.tsx +++ b/src/components/Icon/canonicalIconNames.tsx @@ -63,6 +63,7 @@ const canonicalIcons = { "artefact-report": icons.Report, "artefact-task": icons.Script, "artefact-transform": icons.DataRefinery, + "artefact-ruleblock": transform(icons.Fragments, 90), "artefact-uncategorized": icons.Unknown, "artefact-workflow": icons.ModelBuilder, diff --git a/src/extensions/react-flow/handles/HandleContent.tsx b/src/extensions/react-flow/handles/HandleContent.tsx index 5a1b8836..48c89adb 100644 --- a/src/extensions/react-flow/handles/HandleContent.tsx +++ b/src/extensions/react-flow/handles/HandleContent.tsx @@ -21,7 +21,7 @@ export const HandleContent = memo( {children} ) : extendedTooltip ? ( -
+
) : ( <> ); @@ -33,6 +33,7 @@ export const HandleContent = memo( autoFocus={false} enforceFocus={false} openOnTargetFocus={false} + usePlaceholder={false} {...tooltipProps} > {handleContent} diff --git a/src/extensions/react-flow/handles/HandleDefault.tsx b/src/extensions/react-flow/handles/HandleDefault.tsx index 621bf9c9..1d08ff96 100644 --- a/src/extensions/react-flow/handles/HandleDefault.tsx +++ b/src/extensions/react-flow/handles/HandleDefault.tsx @@ -80,7 +80,7 @@ export const HandleDefault = memo( offset: { enabled: true, options: { - offset: [3, 20], + offset: [0, 20], }, }, }, @@ -89,21 +89,15 @@ export const HandleDefault = memo( isOpen: extendedTooltipDisplayed, }; - const handleContentProps = React.useMemo( - () => ({ - ...data, - tooltipProps: { - ...handleContentTooltipProps, - ...data?.tooltipProps, - } as TooltipProps, - }), - [intent, category, handleProps.isConnectable], - ); + const handleContentProps = { + ...data, + tooltipProps: { + ...handleContentTooltipProps, + ...data?.tooltipProps, + } as TooltipProps, + }; - const handleContent = React.useMemo( - () => {children}, - [], - ); + const handleContent = {children}; let switchTooltipTimerOn: ReturnType; let switchToolsTimerOff: ReturnType; @@ -119,7 +113,7 @@ export const HandleDefault = memo( if (handleProps.onClick) { handleProps.onClick(e); } - if (toolsTarget.length > 0 && e.target === handleDefaultRef.current) { + if (toolsTarget.length > 0 && e.currentTarget === handleDefaultRef.current) { setExtendedTooltipDisplayed(false); (toolsTarget[0] as HTMLElement).click(); } @@ -127,7 +121,7 @@ export const HandleDefault = memo( "data-category": category, onMouseEnter: (e: React.MouseEvent) => { if (switchToolsTimerOff) clearTimeout(switchToolsTimerOff); - if (e.target === handleDefaultRef.current) { + if (e.currentTarget === handleDefaultRef.current) { switchTooltipTimerOn = setTimeout( () => setExtendedTooltipDisplayed(true), data?.tooltipProps?.hoverOpenDelay ?? 500, @@ -142,7 +136,16 @@ export const HandleDefault = memo( setExtendedTooltipDisplayed(false); }, }), - [intent, category, tooltip, handleProps.isConnectable, handleProps.style], + [ + intent, + category, + tooltip, + flowVersionCheck, + handleProps.isConnectable, + handleProps.style, + handleProps.onClick, + data?.tooltipProps?.hoverOpenDelay, + ], ); switch (flowVersionCheck) { diff --git a/src/extensions/react-flow/handles/_handles.scss b/src/extensions/react-flow/handles/_handles.scss index 7f276267..e1c0018e 100644 --- a/src/extensions/react-flow/handles/_handles.scss +++ b/src/extensions/react-flow/handles/_handles.scss @@ -146,7 +146,14 @@ div.react-flow__handle { position: absolute; inset: 0; overflow: hidden; - cursor: help; +} + +.#{$eccgui}-graphviz__handle__content--extendedTooltip { + inset: unset; + top: 50%; + left: 50%; + height: 0; + width: 0; } div.react-flow__handle-right { diff --git a/src/extensions/react-flow/handles/tests/HandleDefault.test.tsx b/src/extensions/react-flow/handles/tests/HandleDefault.test.tsx new file mode 100644 index 00000000..53f2f563 --- /dev/null +++ b/src/extensions/react-flow/handles/tests/HandleDefault.test.tsx @@ -0,0 +1,60 @@ +import React from "react"; +import { fireEvent, render, screen } from "@testing-library/react"; +import { OverlaysProvider } from "@blueprintjs/core"; +import "@testing-library/jest-dom"; + +import { HandleDefault } from "../HandleDefault"; + +jest.mock("react-flow-renderer", () => { + const React = require("react"); + return { + Handle: React.forwardRef( + ({ children, isConnectable, position, type, ...props }: any, ref: React.Ref) => ( +
+ {children} +
+ ), + ), + }; +}); + +jest.mock("@xyflow/react", () => { + const React = require("react"); + return { + Handle: React.forwardRef( + ({ children, isConnectable, position, type, ...props }: any, ref: React.Ref) => ( +
+ {children} +
+ ), + ), + }; +}); + +jest.mock("../../versionsupport", () => ({ + useReactFlowVersion: () => "v9", +})); + +describe("HandleDefault", () => { + it("shows the extended tooltip on handle hover", async () => { + render( + + + , + ); + + fireEvent.mouseEnter(screen.getByTestId("handle")); + + expect(await screen.findByText("This is another Tooltip")).toBeVisible(); + }); +});