Svelte NodeToolbar with E2E (#3643)

* Added NodeToolbar

* Added comments & fixed default behaviour in svelte

* Added e2e-tests for NodeToolbar component & added data-id to react NodeToolbar

* refactor(svelte/NodeToolbar): cleanup

* refactor(node-toolbar): add system utils

---------

Co-authored-by: moklick <info@moritzklack.com>
This commit is contained in:
peterkogo
2023-11-20 18:40:43 +01:00
committed by GitHub
co-authored by moklick
parent b659443507
commit b8affc1c82
22 changed files with 564 additions and 71 deletions
@@ -1,13 +1,13 @@
import { useCallback, CSSProperties } from 'react';
import cc from 'classcat';
import { shallow } from 'zustand/shallow';
import { getNodesBounds, Transform, Rect, Position, internalsSymbol } from '@xyflow/system';
import { getNodesBounds, Rect, Position, internalsSymbol, getNodeToolbarTransform } from '@xyflow/system';
import { Node, ReactFlowState } from '../../types';
import { useStore } from '../../hooks/useStore';
import { useNodeId } from '../../contexts/NodeIdContext';
import NodeToolbarPortal from './NodeToolbarPortal';
import { Align, NodeToolbarProps } from './types';
import { NodeToolbarProps } from './types';
const nodeEqualityFn = (a: Node | undefined, b: Node | undefined) =>
a?.positionAbsolute?.x === b?.positionAbsolute?.x &&
@@ -22,53 +22,15 @@ const nodesEqualityFn = (a: Node[], b: Node[]) => {
};
const storeSelector = (state: ReactFlowState) => ({
transform: state.transform,
viewport: {
x: state.transform[0],
y: state.transform[1],
zoom: state.transform[2],
},
nodeOrigin: state.nodeOrigin,
selectedNodesCount: state.nodes.filter((node) => node.selected).length,
});
function getTransform(nodeRect: Rect, transform: Transform, position: Position, offset: number, align: Align): string {
let alignmentOffset = 0.5;
if (align === 'start') {
alignmentOffset = 0;
} else if (align === 'end') {
alignmentOffset = 1;
}
// position === Position.Top
// we set the x any y position of the toolbar based on the nodes position
let pos = [
(nodeRect.x + nodeRect.width * alignmentOffset) * transform[2] + transform[0],
nodeRect.y * transform[2] + transform[1] - offset,
];
// and than shift it based on the alignment. The shift values are in %.
let shift = [-100 * alignmentOffset, -100];
switch (position) {
case Position.Right:
pos = [
(nodeRect.x + nodeRect.width) * transform[2] + transform[0] + offset,
(nodeRect.y + nodeRect.height * alignmentOffset) * transform[2] + transform[1],
];
shift = [0, -100 * alignmentOffset];
break;
case Position.Bottom:
pos[1] = (nodeRect.y + nodeRect.height) * transform[2] + transform[1] + offset;
shift[1] = 0;
break;
case Position.Left:
pos = [
nodeRect.x * transform[2] + transform[0] - offset,
(nodeRect.y + nodeRect.height * alignmentOffset) * transform[2] + transform[1],
];
shift = [-100, -100 * alignmentOffset];
break;
}
return `translate(${pos[0]}px, ${pos[1]}px) translate(${shift[0]}%, ${shift[1]}%)`;
}
function NodeToolbar({
nodeId,
children,
@@ -97,7 +59,9 @@ function NodeToolbar({
[nodeId, contextNodeId]
);
const nodes = useStore(nodesSelector, nodesEqualityFn);
const { transform, nodeOrigin, selectedNodesCount } = useStore(storeSelector, shallow);
const { viewport, nodeOrigin, selectedNodesCount } = useStore(storeSelector, shallow);
// if isVisible is not set, we show the toolbar only if its node is selected and no other node is selected
const isActive =
typeof isVisible === 'boolean' ? isVisible : nodes.length === 1 && nodes[0].selected && selectedNodesCount === 1;
@@ -110,14 +74,19 @@ function NodeToolbar({
const wrapperStyle: CSSProperties = {
position: 'absolute',
transform: getTransform(nodeRect, transform, position, offset, align),
transform: getNodeToolbarTransform(nodeRect, viewport, position, offset, align),
zIndex,
...style,
};
return (
<NodeToolbarPortal>
<div style={wrapperStyle} className={cc(['react-flow__node-toolbar', className])} {...rest}>
<div
style={wrapperStyle}
className={cc(['react-flow__node-toolbar', className])}
{...rest}
data-id={nodes.reduce((acc, node) => `${acc}${node.id} `, '').trim()}
>
{children}
</div>
</NodeToolbarPortal>
@@ -1,5 +1,5 @@
import type { HTMLAttributes } from 'react';
import type { Position } from '@xyflow/system';
import type { Position, Align } from '@xyflow/system';
export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
nodeId?: string | string[];
@@ -8,5 +8,3 @@ export type NodeToolbarProps = HTMLAttributes<HTMLDivElement> & {
offset?: number;
align?: Align;
};
export type Align = 'center' | 'start' | 'end';